commit b23819fff1500cd43bb7c44dc9d866e3fb701672 Author: FrameBridge Development Date: Mon Oct 26 22:09:18 2026 -0400 FrameBridge 0.3.1 community source release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..231b874 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing + +Original FrameBridge code and project documentation use GPL-3.0-only within [LICENSING.md](LICENSING.md). Clearly marked original blog article text uses CC-BY-SA-4.0 under [blog/LICENSING.md](blog/LICENSING.md). Contributors keep their copyrights. No automatic copyright assignment or broad contributor agreement is created here. + +You may use, change and redistribute the software under its applicable licenses. Private modifications and independent forks do not have to be submitted upstream. To improve an official release, propose the change through the project Git so maintainers and the community can review it. + +Work in a separate source checkout. Describe the problem, resulting behavior, affected version and focused validation. Keep changes reviewable, identify third-party material and retain its notices. Never include credentials, runtime profiles, private keys, cookies or unrelated personal data in an issue, recording, commit or patch. + +Official branches and release artifacts are maintainer-controlled. Changes enter them through review; official packages are built from identified source revisions and signed through the release process. A pull request does not itself become an official binary. See [OFFICIAL-RELEASES.md](OFFICIAL-RELEASES.md). + +Preserve the historical RC7 installer and source evidence. New executable or payload changes require a separately identifiable build. Do not present a modified artifact as the unchanged certified RC7 release. diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 0000000..0337012 --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1,13 @@ +# Rights and attribution + +**Programming: ChatGPT.** + +**Original concept and project coordination: Gerardo Betancourt.** + +Project maintainer and licensing decision: Gerardo Betancourt. + +Copyright (C) 2026 Gerardo Betancourt, for his copyrightable contributions to FrameBridge. The programming credit describes the development collaboration; it does not identify ChatGPT as a legal copyright holder or transfer third-party rights. + +The project-level GPL-3.0-only grant applies to original material that the project rightsholders are entitled to license, as described in [LICENSING.md](LICENSING.md). It does not claim ownership of incorporated packages, the OpenAI tunnel client, other authors' work, or uncopyrightable ideas. Capturing or importing a file does not transfer copyright. + +Third-party notices stay with their components. Contributors retain their copyrights; contributing does not automatically assign rights to the maintainer or the Free Software Foundation. FrameBridge is an independent community project. The credits do not claim sponsorship, certification or endorsement by OpenAI, GNU or the Free Software Foundation. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE-STATUS.md b/LICENSE-STATUS.md new file mode 100644 index 0000000..b195cf2 --- /dev/null +++ b/LICENSE-STATUS.md @@ -0,0 +1,11 @@ +# Project license status + +The owner-approved first-party license is GNU GPL version 3 only (GPL-3.0-only), selected October 25, 2026. Apache-2.0 was an earlier first-party proposal and remains applicable to third-party components that use it. The complete GPL is in [LICENSE](LICENSE), with scope in [LICENSING.md](LICENSING.md). + +The current release target is FrameBridge 0.3.1, installer FrameBridge-0.3.1.deb. Historical RC7 and Community Preview artifacts retain their separate identities. + +The pinned Python/Rust dependency notices and source delivery were reconciled in the retained native terms review. The 0.3.1 preparation additionally identifies the OpenAI tunnel-client as third-party Apache-2.0 material, retains its notices, and supplies exact h1-verified source archives and notices for its 47 embedded Go modules. Its own development-build source commit is not embedded and remains a provenance limit. + +Public delivery is complete only when the actual matching installer, FrameBridge source, dependency-source assets, supplements and notices are downloadable with verified hashes. Private collection does not establish public availability. The final release delivery record is authoritative for staged or public status; this file does not assert that the port is open or public downloads are live. + +Release signatures identify the maintainer's official builds and do not restrict the GPL rights to modify and redistribute. Original blog text explicitly marked for that purpose uses the separate [blog policy](blog/LICENSING.md). Credits are recorded in [COPYRIGHT.md](COPYRIGHT.md). diff --git a/LICENSING.md b/LICENSING.md new file mode 100644 index 0000000..f8145bf --- /dev/null +++ b/LICENSING.md @@ -0,0 +1,31 @@ +# Licensing scope + +## Original FrameBridge material: GNU GPL version 3 only + +The owner selected GNU General Public License version 3 only, SPDX identifier GPL-3.0-only. The full license is [LICENSE](LICENSE). Future GPL versions are not selected automatically. + +The grant covers original FrameBridge code and project documentation that the rightsholders are entitled to license. Recipients may use, study, modify and redistribute that material under the GPL. Modified private copies and lawful independent distributions do not require submitting a pull request to this project. Original blog article text explicitly marked for that purpose uses CC-BY-SA-4.0 under [blog/LICENSING.md](blog/LICENSING.md); media and separately licensed components retain their own terms. + +[licensing/SOURCE-LICENSE-SCOPE.json](licensing/SOURCE-LICENSE-SCOPE.json) records classification and provenance. File location is not proof of ownership. Preserve [COPYRIGHT.md](COPYRIGHT.md) and applicable notices. + +## Third-party material + +Vendored Python packages, incorporated native libraries, Go modules and the OpenAI tunnel-client executable retain their own licenses. The FrameBridge GPL notice does not relabel MIT, BSD, Apache, MPL, PSF, Unicode or other material. + +The Python/Rust native terms and source arrangement are recorded in [docs/THIRD-PARTY-RELEASE-REVIEW.md](docs/THIRD-PARTY-RELEASE-REVIEW.md) and [licensing/third-party/NATIVE-DEPENDENCY-TERMS.md](licensing/third-party/NATIVE-DEPENDENCY-TERMS.md). The separate OpenAI executable is Apache-2.0; its retained notices, exact Go module versions and checksum-verified source archives are described in [licensing/third-party/tunnel-client/README.md](licensing/third-party/tunnel-client/README.md). + +The tunnel executable identifies its own module as a development build without an embedded VCS revision. Its precise upstream source commit has not been established. The 47 incorporated Go module versions and h1 checksums were recovered and verified against publisher archives; that does not establish the executable's original build provenance. The current pinned upstream LICENSE/NOTICE are retained with this limitation stated explicitly. + +## Source delivery + +Distribute the matching FrameBridge 0.3.1 source archive beside its installer, the immutable shared Python/Rust dependency-source asset, the new tunnel-client dependency-source supplement and retained notices. [Source availability](licensing/third-party/SOURCE-AVAILABILITY.md) identifies the files and hashes. + +The unchanged Certifi source arrangement retains its exact upstream source assets, original notice, full MPL-2.0 text and recipient access instructions. The historical Mozilla certdata revision remains unidentified. No independent native-wheel or tunnel-client rebuild is claimed. These are provenance and reproducibility limits, not claims that unavailable proof has been obtained. + +## Official releases and freedom to modify + +[OFFICIAL-RELEASES.md](OFFICIAL-RELEASES.md) distinguishes maintainer-reviewed, signed releases from independent variants. Signatures and hashes authenticate official artifacts; they do not encrypt the program or prohibit modification. Installation privileges and ordinary file ownership protect the installed copy against unprivileged accidental edits. An authorized owner can still modify their own installation. + +The official Git accepts proposed changes for review. Maintainers decide what enters official branches, release tags and signed packages. This workflow does not add a license condition requiring all forks or private changes to return upstream. + +This is a scoped technical licensing record, not a universal legal or security certification. diff --git a/OFFICIAL-RELEASES.md b/OFFICIAL-RELEASES.md new file mode 100644 index 0000000..1650601 --- /dev/null +++ b/OFFICIAL-RELEASES.md @@ -0,0 +1,23 @@ +# Official releases and independent variants + +FrameBridge is free software under the applicable licenses. Everyone retains the right to make private changes, publish forks and redistribute lawful modified copies. Contributing changes back to this Git is welcome, not an additional license condition. + +## What official means + +An official FrameBridge release is built from an identified source revision accepted by the project maintainers. It has a version, source archive, installer, checksums and release signatures. The Debian APT repository publishes signed repository metadata. Official distribution starts at https://framebridge.betancourt.one/. + +Maintainers control write access, reviewed branches, release tags and signing operations. Public visitors and agents do not receive release-signing or direct protected-branch write access. External contributors propose changes through review. + +Independent builds must not be represented as maintainer-approved or cryptographically signed official releases when they are not. Keep original attribution and clearly identify modifications as required by the applicable license. + +## What signing protects + +A valid signature authenticates the signed release or repository metadata against the independently trusted public key. SHA-256 detects byte changes; an unsigned checksum fetched through the same compromised channel is not sufficient proof of origin. A signed repository connects its package indexes to the package hashes. + +Signing does not encrypt the software, prohibit source modification, or guarantee that a privileged administrator has not modified an installed machine. Keep signing credentials outside source control and web content; publish only the verification material. + +## Ordinary agent use + +An agent using FrameBridge should treat installed program files as the application, use its documented CLI and MCP tools, and keep task data separate. Routine task execution should not rewrite the application or create an unofficial replacement binary. When the owner explicitly requests development, use a separate checkout and present reviewable changes. + +This is operational guidance for reliable use, not a restriction on a recipient's GPL rights or an additional owner-device/phone pairing mechanism. diff --git a/README-SOURCE-ARCHIVE.md b/README-SOURCE-ARCHIVE.md new file mode 100644 index 0000000..06075e6 --- /dev/null +++ b/README-SOURCE-ARCHIVE.md @@ -0,0 +1,37 @@ +# FrameBridge 0.3.1 source archive + +This archive preserves the repository layout needed by the offline builder. +From this directory, run python3 source/packaging/build_expanded.py to build +FrameBridge-0.3.1.deb. The builder writes source/build and source/release; +it does not install or start FrameBridge. Debian's dpkg-deb and the documented +build prerequisites must already be available. + +Original FrameBridge material is GNU GPL version 3 only. Read LICENSE, +LICENSING.md, COPYRIGHT.md, LICENSE-STATUS.md and OFFICIAL-RELEASES.md for +scope, attribution, third-party terms and the official-release trust model. + +The project source asset is accompanied by these separate dependency assets: + +- FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz + 82,606,998 bytes + SHA-256 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a +- FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz + 26,941,745 bytes + SHA-256 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9 + +The Preview1 filename deliberately preserves the identity of an immutable +shared asset. Retain licensing/third-party/R2-SUPPLEMENTS.json and its referenced +source notices with the dependency sources. The source indices and +SOURCE-AVAILABILITY.md there record scope and provenance. Do not substitute +unrelated archives. The retained tunnel executable is not claimed to have been +independently rebuilt from an identified own-source revision. + +The blog directory contains licensing policies and the post template. +Editorial article drafts are omitted because they are not source build inputs. + +Only source/evidence/BASELINE.json is included from technical evidence. Runtime +profiles, private QA logs, generated build/release files and Git history are +outside the source archive allowlist. + +A completed archive or builder run is not a declaration of public readiness. +Release checksums, signatures and publication status are recorded separately. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac8181f --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# FrameBridge + +**Bring your ChatGPT conversations to Linux computers you control.** + +FrameBridge connects a hosted ChatGPT conversation to tools running on your own computers. Run a task, return to a persistent Bash terminal, inspect the real desktop, and select an enrolled node. + +**Community Release 0.3.1** · **Debian 13 amd64** · **Python 3.13** + +The installer and control console are text interfaces in a Linux TTY or desktop terminal. FrameBridge detects a usable graphical X11 session automatically and adds graphical CUA when available. Text mode provides FBEXEC, persistent terminals and CUA-TTY. Adaptive mode does not silently create a virtual desktop. + +ChatGPT remains hosted. The tools execute on your own machines. Your own ChatGPT account and manual browser approval are required; a local account label does not authenticate you. No phone pairing is required. + +## Install and connect + +Verify the release checksums and signatures using the independently trusted project key before installing. See [Official releases](OFFICIAL-RELEASES.md) for the trust model and the project site for the signed release record. + +On Debian 13 amd64, with the downloaded installer in the current directory: + +~~~bash +sudo apt install ./FrameBridge-0.3.1.deb +fb connect +fb status +~~~ + +Normal first connection needs no separate setup or start command. The terminal asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. This access key is not an OpenAI API key. Complete any browser sign-in and approval separately. + +Normal connection prepares managed Linux startup, stores the saved credential encrypted on this machine, reports progress and returns to the prompt. It does not force a reboot. Repeating the command resumes valid saved settings; explicit replacement is available through fb connect --replace. + +## One command interface for people and agents + +The executable names fb and framebridge run the same program. + +| Command | Purpose | +| --- | --- | +| fb connect | Establish or resume the managed ChatGPT connection. | +| fb disconnect | Pause that connection until the next explicit connect, including after restart. Local tools and saved material remain. | +| fb status | Observe actual tools, connection and boot policy together. | +| fb status connection | Observe just the connection, including its intended state. | +| fb doctor | Diagnose prerequisites and problems; it does not repair or connect. | +| fb console | Open or reattach the text Bash/FBTERM work view. | +| fb help | Read the versioned English guide. | +| fb setup | Optional advanced role/profile configuration. | + +Read fb help commands for specialized service, boot, node, framebuffer-access and terminal-session controls. Legacy spellings are described separately in fb help compatibility. + +For an agent or script: + +~~~bash +fb --json status +fb help commands --json +~~~ + +Explicit JSON output is versioned and does not request interactive input. When owner authentication is needed, it reports INTERACTION_REQUIRED with an action for a normal terminal. The MCP server also invites an agent to read its registered help tool. This supplies usage context; client compliance must not be assumed from the invitation alone. + +## Services, terminals and desktops + +Service execution, connection intent, boot policy and saved material are separate: + +- fb service start and fb service stop control the current stack. The first start of a configured node can also prepare its managed startup; that initial provisioning is reported explicitly. +- fb service boot enable and fb service boot disable control future startup without stopping current work or forgetting a saved credential. +- fb service credentials forget --confirm is a separate destructive operation: it removes the managed connection setup and saved material while retaining the profile and user persistence. + +Persistent Bash sessions retain shell state between calls while the session and service remain alive. They do not preserve running processes across an operating-system reboot. Changing a session's sudo mode recreates Bash and loses its internal shell state; consult the guide before doing so. + +CLI terminal observation is text. Real CUA captures are separate, authentic screen evidence. Private virtual displays, when explicitly selected, are identified as virtual. + +Independent FBEXEC tasks have been observed to work on one computer when their targets and changes do not overlap. This is not an isolation guarantee. Use one controller at a time for a shared graphical desktop. + +## Source, licenses and official releases + +Original FrameBridge material is licensed under **GNU GPL version 3 only (GPL-3.0-only)**. Third-party material retains its own notices and license terms. Read [LICENSING.md](LICENSING.md), [COPYRIGHT.md](COPYRIGHT.md) and [LICENSE-STATUS.md](LICENSE-STATUS.md). + +The distribution source set contains: + +- FrameBridge-0.3.1-source.tar.gz — FrameBridge implementation, builder, tests and legal records. +- FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz — the immutable shared Python/Rust dependency source asset; its historical name is retained deliberately. +- FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz — retained source archives and notices for the tunnel executable's Go dependencies. + +Exact sizes, hashes, scope and upstream provenance are recorded in licensing/third-party. The retained tunnel executable is not presented as independently rebuilt from an identified upstream source revision. + +~~~bash +python3 source/packaging/build_expanded.py +python3 source/packaging/archive_sources.py +~~~ + +These commands assemble the Debian package and project source archive; they do not install or start FrameBridge. Build prerequisites must already be available. Rebuilding the project package does not claim an independent rebuild of every embedded native component. + +Release signatures and publication readiness are recorded separately from the builder. A successful build alone is not proof that a release is signed or publicly available. + +Everyone retains the applicable freedoms to modify and redistribute lawful copies. Contributing through the project Git is welcome; it is not an additional license condition. Maintainers review what enters the official repository and sign official releases. Independent variants must not be represented as maintainer-approved releases. + +## Community and credits + +Project home: https://framebridge.betancourt.one/ + +Programming: **ChatGPT**. +Original concept and project coordination: **Gerardo Betancourt**. + +FrameBridge is an independent community project. No endorsement by OpenAI is claimed. + +Read [CONTRIBUTING.md](CONTRIBUTING.md) before proposing changes and [SECURITY.md](SECURITY.md) for the security process. The English Debian guide is available through fb help and the included documentation. Demonstration animations are conceptual illustrations, not recordings of tool execution. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 0000000..6d6cd2d --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,64 @@ +# FrameBridge release notes + +## 0.3.1 — Community Release + +Installer: FrameBridge-0.3.1.deb +Debian package: framebridge-original-expanded +Debian version: 0.3.1 +Project source: FrameBridge-0.3.1-source.tar.gz +Target: Debian 13 amd64 with Python 3.13. + +This is a separately identifiable revision of FrameBridge Original Expanded. It does not replace the historical RC7 archive or claim RC7 certification for newly changed behavior. + +### A shared human and agent interface + +- The same fb/framebridge command interface serves people and authorized agents. +- Normal connection uses fb connect; an ordinary initial connection needs no setup/start sequence. The existing connect chatgpt spelling remains compatible. +- fb status combines local tools, actual connection, connection intent and boot policy. Scoped connection/runtime/boot/console views use the same observation model. +- fb doctor remains a separate diagnostic operation. Query success is distinguished from a verified connection. +- Explicit --json output has a versioned envelope. --json and --non-interactive avoid prompts and report a required normal-terminal action when appropriate. +- fb help, the installed manual/tutorial and MCP agent guide share the versioned guide. The server supplies a concise learning invitation and a registered read-only help tool. + +### Explicit lifecycle boundaries + +- Canonical fb disconnect preserves an intentional pause through worker recovery and reboot until the next connect. +- Recovering a saved connection does not require restarting otherwise healthy local tools. +- Current service start/stop and future boot policy are separate. An initial node service setup is reported explicitly; it uses the central connection rather than creating another ChatGPT tunnel. +- Disabling future startup retains saved connection material. Forgetting it is a separately named, explicitly confirmed operation. +- Compatibility routes retain their documented historical effects. In particular, legacy autostart disable remains different from policy-only service boot disable. + +### Console and terminal clarity + +- Console reattachment preserves an existing zoom/focus choice instead of treating every attachment as initial layout. +- The dedicated console accepts the supported z/Ctrl+Z/Z zoom-key variants after its prefix. +- The horizontal work view follows the FBTERM-left/Bash-right arrangement; graphical and text layouts use the reviewed context and responsive rules. +- The launcher captures setup output instead of presenting technical READY JSON as the normal work screen. +- Real-screen CUA and CLI terminal transcripts remain distinct. Neither a terminal transcript nor a conceptual animation is presented as an authentic complete screen capture. +- Per-session sudo-mode changes document that Bash is recreated and internal shell state is lost. + +### Distribution and provenance + +- Original FrameBridge material remains GNU GPL version 3 only. Third-party notices, native dependency terms and source provenance remain separate. +- OFFICIAL-RELEASES.md explains maintainer-controlled branches, signed official artifacts and lawful independent variants. +- The release source set includes the project source plus the two dependency assets identified below. +- Signing and publication readiness belong to the external release record. Building the package does not assert that the repository is public, signed or ready to expose to the Internet. + +### Corresponding source assets + +| Asset | Bytes | SHA-256 | +| --- | ---: | --- | +| FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz | 82,606,998 | 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a | +| FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz | 26,941,745 | 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9 | + +The historical Preview1 filename identifies an immutable shared dependency asset. It is not the version of the FrameBridge installer. The tunnel dependency archive identifies exact dependency module sources and retained notices; it is not proof of an independently reproduced tunnel executable or its exact own-source revision. + +### Validation record + +Release-specific check results, artifact hashes and signature status are recorded in the accompanying external release evidence. No new manual ChatGPT connection or node-creation campaign is implied by these notes. Focused checks cover the revised interfaces and packaging without claiming that every existing integration scenario was repeated. + +### Credits + +Programming: ChatGPT. +Original concept and project coordination: Gerardo Betancourt. + +This is an independent community project, not an OpenAI product. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f984c68 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security and responsible use + +## Scope and release evidence + +FrameBridge 0.3.1 is a separate community release from historical Original Expanded RC7. Debian 13 amd64 is the documented platform. Historical test counts do not certify every later build or deployment. Consult the matching release validation record for what was actually checked. + +## Deliberate access + +Use an appropriate account, confirm the receiving machine and selected node, and authorize the intended operation. Keep owner authentication in the normal terminal or interface. Never place passwords, private keys, tokens, cookies or runtime credentials in chat, issues, recordings or source control. + +Agents should normally use the installed CLI/MCP interface without rewriting application files. Explicit development belongs in a separate source checkout and reviewable change set. This guidance preserves ordinary owner control and does not restrict free-software rights. + +Graphical control shares the desktop's focus, keyboard and mouse. Use one controller per shared desktop at a time. That is an operating recommendation, not a claim of an automatic certified lock. Terminal transcripts are not authentic complete screen captures; label evidence accurately. + +## Authentic releases + +Use official signed artifacts and the signed APT repository described in [OFFICIAL-RELEASES.md](OFFICIAL-RELEASES.md). Verify the published signing-key fingerprint through a trusted channel. Hashes identify bytes, while authenticated signatures establish the signed artifact's origin. Neither guarantees that a privileged local user cannot change an installed system. + +## Reporting a problem + +Report the affected version, platform, command and observed behavior through the project Git. For a potential security issue, provide a minimal description without credentials or exploitation against systems you do not own. Do not post sensitive data to a public issue. The project does not claim a private reporting mailbox until one is explicitly published. + +The source/privacy review is bounded and does not guarantee that every possible secret or vulnerability is absent. Preserve third-party notices during cleanup and keep historical release evidence separate from new builds. diff --git a/blog/LICENSE-CC-BY-SA-4.0.txt b/blog/LICENSE-CC-BY-SA-4.0.txt new file mode 100644 index 0000000..2d58298 --- /dev/null +++ b/blog/LICENSE-CC-BY-SA-4.0.txt @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/blog/LICENSING.md b/blog/LICENSING.md new file mode 100644 index 0000000..c52c32a --- /dev/null +++ b/blog/LICENSING.md @@ -0,0 +1,15 @@ +# Blog licensing policy + +## Article text + +Original article text explicitly marked `CC-BY-SA-4.0` is licensed under Creative Commons Attribution-ShareAlike 4.0 International. Give appropriate attribution, link to the license and identify changes. Redistributed adaptations must follow its ShareAlike conditions. The [complete legal text](LICENSE-CC-BY-SA-4.0.txt) controls. + +Use an explicit notice on each article, including its author and publication date. The [post template](POST-TEMPLATE.md) provides a starting point. A contribution must identify its author and confirm the contributor has permission to offer the original text under these terms. Copyright is not transferred automatically. + +## Code and other material + +Code examples use the project's GPL-3.0-only terms unless a separate notice identifies another applicable license. Retain third-party code notices. + +This policy does not automatically cover site themes, software, logos, trademarks, screenshots, quotations, photographs, music, video, personal information or material supplied by others. Record their applicable rights separately before publication. Inclusion in an article is not a new license grant for them. + +The policy and template do not constitute a public blog launch or permission to publish private laboratory evidence. diff --git a/blog/POST-TEMPLATE.md b/blog/POST-TEMPLATE.md new file mode 100644 index 0000000..9a2c93d --- /dev/null +++ b/blog/POST-TEMPLATE.md @@ -0,0 +1,22 @@ +--- +title: "Replace with article title" +author: "Replace with the actual author" +date: "YYYY-MM-DD" +status: draft +text_license: CC-BY-SA-4.0 +code_license: GPL-3.0-only +--- + +# Replace with article title + +Describe the purpose, environment and observed result. Identify proposals and remaining uncertainties separately. Replace all template fields before review or publication. + +## Reproduction and evidence + +Include the exact version and safe reproduction steps. Keep credentials and unrelated personal information out of the article. Identify rights for each image, quotation or recording separately. + +## Example article notice — activate only for the reviewed article + +This template file is project documentation under GPL-3.0-only. The metadata fields and the following notice are examples for a future article; replace the placeholder identity and identify the original article text before applying its CC license. + +Example notice: The original article text by the identified author is offered under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Code examples use GPL-3.0-only unless another applicable notice is stated. Other material is excluded unless its own rights notice expressly includes it. This file is an unpublished template. diff --git a/blog/README.md b/blog/README.md new file mode 100644 index 0000000..d44b69f --- /dev/null +++ b/blog/README.md @@ -0,0 +1,11 @@ +# FrameBridge development journal + +This directory contains English editorial material about what FrameBridge does, how it is evaluated and what remains to improve. + +The first [article draft](2026-10-25-why-framebridge.md) introduces persistent terminals, real console interaction and selected Linux nodes. It remains unpublished. Its editorial identity is the FrameBridge project; legal ownership and attribution remain in [COPYRIGHT.md](../COPYRIGHT.md). + +Describe reproducible work, observed results and current limitations. Distinguish the accepted RC7 baseline from the separate community candidate and its unfinished release checks. Lead with capabilities and useful workflows rather than founder biography. + +Use complete authentic screen captures with approved demonstration content. Identify conceptual animations as illustrations. A fresh real recording of the community candidate remains to be produced and checked. + +Read the [licensing policy](LICENSING.md) and [post template](POST-TEMPLATE.md). Original article text explicitly marked CC-BY-SA-4.0 follows that policy; code and other material retain their own terms. Review the actual publication date, release state, links and media rights before publishing a draft. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..4468fab --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,25 @@ +# Architecture at a glance + +FrameBridge connects a hosted ChatGPT conversation to tools on computers controlled by the owner. A manual connection links the configured standalone or central instance to the owner's ChatGPT account. + +## One computer + +A standalone installation provides execution, persistent terminal and screen interaction on its computer. FBEXEC carries out a selected task. FBTERM keeps a terminal session available so work can be resumed with its existing shell state. CUA and CUA-TTY expose the actual graphical or text console supported by that installation. + +## Central and nodes + +A central instance provides the fleet entry point. Approved worker nodes supply execution on their own computers; the operator selects the intended node for each task. The node workflow is described in the [private evaluation guide](PRIVATE-EVALUATION.md) and [Debian tutorial](../source/docs/DEBIAN-TUTORIAL.md). + +A worker does not need a separate ChatGPT tunnel. ChatGPT remains hosted while tools run on the selected owner-controlled machines. + +## Sessions and shared resources + +A persistent terminal is useful for returning to an existing shell. It does not make simultaneous edits to the same files safe. Separate conversations have been used for non-overlapping FBEXEC work; isolation between those conversations has not been certified. + +Graphical control shares the desktop's focus, keyboard and mouse. Use one controlling instance per desktop at a time. This is a recommendation for operation, not a certified automatic exclusion mechanism. + +## Evidence and presentation + +Show the complete, authentic screen when presenting a CUA capture. Label a conceptual animation as an illustration. A conceptual promotional video is separate from a real execution recording. + +The recorded RC7 acceptance evidence and the current runtime state answer different questions. After a reboot, inspect the current connection and session before claiming that a live demonstration is ready. diff --git a/docs/AUTOMATIC-STARTUP.md b/docs/AUTOMATIC-STARTUP.md new file mode 100644 index 0000000..85b0cb3 --- /dev/null +++ b/docs/AUTOMATIC-STARTUP.md @@ -0,0 +1,63 @@ +# Automatic startup: service, credentials and recovery + +The normal `fb connect chatgpt` wizard configures Linux startup and reconnection as part of the connection workflow. No separate `autostart enable` command or startup opt-in is needed. See [connection setup and status](CONNECTION-STATUS.md) for the three-field workflow. This page describes the underlying service, advanced maintenance and reversal. + +## Requirements and setup + +Run the wizard as the Unix user who owns the profile, not as root. Use the same `--state-dir` selection for every command on a custom profile. This implementation requires systemd 256 or later; Debian 13 provides a suitable version. A missing normal profile is created as standalone with automatic desktop detection when setup commits. An existing central profile can use the same connection wizard. Nodes use their central's tunnel rather than connecting ChatGPT separately. + +A first unmanaged connection asks for the visible account/email label, the hidden tunnel input and the hidden MCP server access key, in that order. A valid managed profile can be checked or resumed with the same command without reentering the fields. The label is not browser authentication; the access key is not an OpenAI API key. Administrator authentication stays in the normal terminal. The saved access key is encrypted on this machine. + +Setup enables the managed service immediately, then reports current connection readiness separately from configuration. No routine reboot is required. Inspect the detailed service result with: + +```sh +fb autostart status +``` + +A successful configuration is not proof that ChatGPT has linked the intended account. Complete browser consent and verify a small, safe real tool call. + +## Service and desktop behavior + +FrameBridge installs one profile-specific systemd user service and enables the owner's user manager at boot using systemd linger. This permits startup without a monitor or graphical login. It does not enable operating-system autologin or turn a user service into an unrestricted root service. + +The default adaptive desktop setting detects a usable physical X11 session. If none is available, the local runtime starts FBEXEC, FBTERM and CUA-TTY in text mode. Graphical CUA requests re-check the physical session when it becomes available later, without restarting persistent FBTERM sessions. The selected display is checked around each operation to reject a session change during that operation. + +For a legacy profile that strictly requires a physical desktop (`headless=no`), managed connection setup saves the original configuration and changes that setting to `headless=auto`. This lets boot-time text tools start before graphical login. If the profile explicitly requests a private desktop (`headless=yes`), that remains separate from the physical console: the display is reported as `virtual-x11` with `physical=false`. Adaptive mode does not create a private virtual desktop automatically. + +Network failures use retry/backoff. A revoked or expired connection key needs manual replacement. Explicit `framebridge disconnect chatgpt` and `fb stop` leave automatic startup enabled for the next boot; they do not mean it has been disabled permanently. + +## Encrypted connection credential + +The durable runtime-key copy is a host-bound encrypted systemd credential associated with this machine's host encryption key and the owner UID. The worker supplies the encrypted credential to `systemd-creds decrypt --user` through a pipe and receives the decrypted key through another pipe in process memory. It does not write a plaintext credential file. The pinned tunnel client consumes the key from its own temporary process environment. + +This protects the saved profile copy from plaintext exposure. Root access or a complete machine image that includes the host encryption key can recover it. The encrypted credential is not a portable backup for another machine. Do not publish a live profile, credential file or machine image as a project source archive. + +To change the saved account label, tunnel or access key, explicitly reopen the three-field wizard: + +```sh +fb connect chatgpt --replace +``` + +The update is transactional and does not require disabling startup first. Enter the replacement only at `MCP server access key (hidden):`. + +The advanced `fb autostart enable` command remains available for maintenance and node-local runtime startup. Its own confirmation and hidden-key prompts are separate from the normal connection wizard and are not additional normal setup steps. + +## Disable, reverse or remove + +Run as the profile owner: + +```sh +fb autostart disable +``` + +Disable stops the managed service, connection and local runtime. It removes only the matching FrameBridge-owned unit and encrypted credential. A desktop setting changed by setup is restored only if the profile has not subsequently changed. Linger is restored only when FrameBridge originally enabled it and the recorded enabled-unit set still permits that reversal; otherwise the shared user-manager setting remains in place. If owned files have changed externally, removal is refused for review. + +The package can be removed normally without first running manual FrameBridge stop/disable commands: + +```sh +sudo apt remove framebridge-original-expanded +``` + +The package-removal hook stops and disables only recognized FrameBridge-managed user services belonging to this package. It preserves the user's profile, encrypted credential files and linger setting. It does not perform this cleanup during a package upgrade. Purging the package likewise does not erase those user-owned profile and credential files. + +If you also want to remove the matching managed unit and encrypted credential, the explicit `fb autostart disable` command above remains available while FrameBridge is installed. Its guarded cleanup is optional, not a prerequisite for normal package removal. If a matching managed service cannot be stopped or disabled safely, package removal pauses for review rather than deleting unrelated user configuration. diff --git a/docs/CLAIMS-AND-LIMITS.md b/docs/CLAIMS-AND-LIMITS.md new file mode 100644 index 0000000..38629b7 --- /dev/null +++ b/docs/CLAIMS-AND-LIMITS.md @@ -0,0 +1,23 @@ +# Claims and evidence boundaries + +The current version is `0.3.1~community.20261026.2`, a separate candidate from RC7 and Preview 1. + +| Statement | Evidence and boundary | +| --- | --- | +| Preview 2 unit suite | 376 tests passed in 22.351 seconds; focused peer regression also passed. | +| Preview 2 package and VM | Pending at documentation freeze; no new VM acceptance or artifact hash is invented here. | +| Preview 1 | Historical record: 368 tests and a reproducible package build. | +| RC7 | Historical record: 361 tests, reproducible package, Debian 13 VM lifecycle and real ChatGPT/console acceptance. | +| ChatGPT connection | Manual owner account consent; the model remains hosted while tools run on selected owner-controlled computers. | +| Persistent console | Session continuity is not a promise that every process survives reboot. | +| Independent FBEXEC tasks | Repeated maintainer experience with non-overlapping work; no certified isolation or simultaneous execution of every command. | +| Graphical control | One controller per shared desktop is a recommendation, not an automatic lock. | +| sudo | Actual privileges depend on the Unix account and normal authentication; an authorization flag does not grant root. | +| Real images and demo | Use complete authentic captures. A fresh Preview 2 real recording is pending; conceptual animation is illustration. | +| Dependency terms/source | Scoped technical terms review and exact source availability recorded with delivery conditions; component terms remain in force. | + +## Retained control components + +The retained development broker defaults to its existing 127.0.0.1 peer only. Repeatable --control-peer IP_LITERAL arguments add explicit peers. Invalid entries fail before Store creation/binding; checks use the real client address, not proxy headers. Retained RPC UID, host, client/socket and administrator identity guards remain unchanged. Normal Expanded handling uses the generic terminal entrypoint; FRAMEBRIDGE_ENABLE_GERAMEG_CUA_TTY=0 is not an effective guard. + +The classic lab-target map is empty, while the compatibility route remains available for explicit integrator configuration. Do not describe retained code as entirely inactive. Further executable edits require identifiable source and candidate-specific validation. diff --git a/docs/COMMAND-REFERENCE.md b/docs/COMMAND-REFERENCE.md new file mode 100644 index 0000000..191527b --- /dev/null +++ b/docs/COMMAND-REFERENCE.md @@ -0,0 +1,404 @@ +# FrameBridge — command guide + +Version: 0.3.1 | Guide schema: 1 + +This reference is generated from the shared CLI/MCP guide. Use the matching installed version. + +## Contents + +1. [Start with FrameBridge](#1-start) +2. [Command reference](#2-commands) +3. [Connect and disconnect ChatGPT](#3-connection) +4. [Services, connection intent and boot policy](#4-lifecycle) +5. [Central and node operation](#5-nodes) +6. [Persistent Bash terminals](#6-terminals) +7. [Authentic screen observation and input](#7-screens) +8. [Status and diagnosis](#8-diagnostics) +9. [Authorization, distribution and safe operation](#9-security) +10. [Compatibility with earlier commands](#10-compatibility) + + + +## 1. Start with FrameBridge + +### One interface for people and agents + +FrameBridge provides tools and a shared command-line interface for human operators and authorized agents. fb is the short name for framebridge; both run the same program. The tools run on your Linux computers. The ChatGPT model remains hosted. Read this guide for the installed version before operating; confirm the receiver, target and available capabilities. Use the installed CLI and tools for normal work. Edit software only when the owner explicitly requests development, using a separate source clone and reviewable changes rather than rewriting the installed program. + +~~~sh +fb help +fb status +fb --json status +~~~ + +### Install and connect + +On a supported Debian system, install the downloaded package with the package manager. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal. Advanced setup is optional for selecting central/node roles and public settings. + +~~~sh +sudo apt install ./FrameBridge-0.3.1.deb +fb connect +~~~ + +### Updating and removal + +Install an official update through the normal package manager. Normal package removal stops package-owned managed processes and retains the profile and saved encrypted connection material for deliberate reuse; it is not a credential-forgetting operation. Use the separately documented explicit forget action first if that is your intended outcome. Do not assume purge erases user-profile data. + +~~~sh +sudo dpkg --remove framebridge-original-expanded +~~~ + +### Human authentication + +The normal connection asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. The label is metadata, not proof of ChatGPT login. Browser login/consent and normal operating-system authentication remain human actions in the normal interface. Never put credentials in chat, command arguments, documentation or example files. A successful managed connection returns to the prompt and can run independently of that terminal. Reboot only when an actual system change requires it. + +### Choose the right guide + +Use commands for the reference, connection and lifecycle for state changes, nodes for central/node work, terminals for persistent Bash, and screens for authentic CUA evidence. Query success alone does not mean the connection is ready. The guide describes registered operations; use status/doctor to investigate current state. + +~~~sh +fb help commands +fb help connection +fb help lifecycle +~~~ + + + + +## 2. Command reference + +### Everyday commands + +connect establishes or resumes the managed ChatGPT connection. disconnect pauses that connection until an explicit connect, including across recovery and reboot. status reports runtime, connection, intended connection state and boot policy. doctor diagnoses prerequisites/problems. setup edits advanced stopped-profile settings. console opens the text work view. help reads this versioned reference. + +~~~sh +fb connect +fb disconnect +fb status +fb status connection +fb status runtime +fb status boot +fb status console +fb doctor +fb setup +fb console +fb help +~~~ + +### Services and boot + +For an already configured managed profile, service start/stop control the current stack symmetrically. Boot enable/disable changes future startup policy without stopping current work or forgetting the saved credential. First node provisioning is an explicit exception described in lifecycle. Without managed setup, non-node service start starts local tools only; normal first connection should use connect. Read status before and after changes. The separate credentials forget action is destructive and requires explicit confirmation. + +~~~sh +fb service start +fb service stop +fb service boot enable +fb service boot disable +~~~ + +### Console and specialized operations + +console login manages optional work-view opening after an authenticated tty1 login; it does not enable automatic login. tty service manages the terminal daemon; tty list lists sessions. nodes, certificates and screen-access retain their domain-specific subcommands. Use --help at the relevant command for its exact arguments. Avoid compatibility aliases in new automation. + +~~~sh +fb console login status +fb tty service status +fb tty list +fb nodes --help +fb certificates --help +fb screen-access --help +~~~ + +### Human and structured output + +Use explicit commands in automation rather than the interactive no-argument menu. --json selects structured output for an agent; examine operation success, readiness and state separately. Read errors and the exit status. If human interaction is required, complete it in the normal terminal/interface rather than attempting to supply a secret through the agent. CLI JSON uses an envelope with schema_version, product, version, command, ok and result. Read runtime, connection and boot truth inside result; envelope ok denotes request success. Exit 0 means successful query/operation, not necessarily a connected transport; exit 1 reports operation/diagnostic failure, exit 2 invalid arguments or required human interaction/confirmation, and exit 130 cancellation. + +~~~sh +framebridge --json status +fb --json status connection +~~~ + + + + +## 3. Connect and disconnect ChatGPT + +### Normal managed connection + +connect creates the default standalone profile when absent and establishes managed startup. It asks for initial human input in the terminal, checks progress, reports readiness or an actionable failure, and returns to the prompt. Valid saved setup can be resumed without repeating the initial fields. The MCP access key is not an OpenAI API key. Keep the browser's ChatGPT account login and consent separate from the optional local account label. + +~~~sh +fb connect +fb status connection +~~~ + +### Saved credentials and next boot + +Managed startup uses protected, host-bound encrypted connection material. The normal terminal does not have to remain open. This is a persistence feature, not a promise that an account, network or tunnel service can never fail. Connection intent and boot policy are separate: enabled startup runs the stack at boot, while an intentional disconnected state remains disconnected until connect. + +~~~sh +fb status boot +fb status +~~~ + +### Intentional disconnect + +disconnect keeps local tools and saved material, but records the intention to stay disconnected across worker recovery and reboot. Explicit connect clears that pause. Check the returned state rather than interpreting a successful query as an active transport. + +~~~sh +fb disconnect +fb status connection +~~~ + +### Replacement and temporary foreground mode + +Use connect --replace only when deliberately replacing the account label/tunnel/access configuration. The advanced --foreground mode is temporary, needs a ready local runtime and occupies its terminal until interrupted; it is not the normal managed workflow. Do not use advanced mode merely to recover a normal saved connection. + + + + +## 4. Services, connection intent and boot policy + +### Separate states + +FrameBridge distinguishes actual local runtime, desired connection, verified transport, boot enablement and saved credential presence. A healthy runtime can be intentionally disconnected; a service can be active while disabled for the next boot. Read the labelled status fields together. + +~~~sh +fb status +fb --json status +~~~ + +### Current managed stack + +For an already configured managed profile, service start and service stop use the same current-stack boundary without changing boot enablement. Stop ends current managed work and transport but keeps profile, boot policy and saved material. Startup on the next boot follows the retained policy and intended connection state. A non-node profile without managed setup starts only local tools. Stopping a terminal service or session can interrupt its running work. + +~~~sh +fb service start +fb service stop +~~~ + +### Future boot policy + +Once managed setup exists, service boot enable/disable changes unit enablement only. It does not immediately stop or start current work and does not delete saved connection material. Initial node provisioning is described separately below. Explicitly forgetting saved material is a separate destructive operation. Legacy autostart disable has its old broader destructive behavior; use the canonical service boot commands for new workflows. + +~~~sh +fb service boot enable +fb service boot disable +fb status boot +~~~ + +### First managed startup for a node + +After advanced node setup and the required enrollment/approval, a node without a managed installation can run service start in a normal terminal. This first call creates managed startup, enables future boot and starts the node now; the result explicitly reports initial_configuration and boot_policy_changed. Alternatively, first service boot enable prepares and enables the node unit without starting current work. Initial preparation can require normal owner authentication and is not silently performed by an unattended JSON request. Once configured, the ordinary start/stop and boot-policy-only contracts apply. A node does not need its own ChatGPT tunnel. + +~~~sh +fb service start +fb service boot enable +fb status boot +~~~ + +### Console opening + +Opening console is a view operation, separate from Linux service startup. Optional console login enable/disable changes the owned startup hook after an authenticated tty1 Bash login. This hook does not bypass normal login. Review the command's scope before changing it. + +~~~sh +fb console login status +~~~ + + + + +## 5. Central and node operation + +### Roles + +Standalone provides local tools. Central adds the broker for enrolled nodes. A node joins one selected central instance and uses its ChatGPT connection; it does not need another ChatGPT tunnel or phone pairing. Advanced setup selects the role. Preserve explicit authenticated node transport and real identity approval. + +~~~sh +fb setup +fb nodes --help +~~~ + +### Enrollment is not availability + +Enrollment submission, central approval, node receipt of authorization and a running node agent are separate stages. status reports what it actually knows; an unchecked agent is not evidence that the node is online. Central list/pending/approve and node enroll/poll/status/run have role-specific purposes. poll can complete saved authorization; it is not a purely observational operation. After enrollment, consult the lifecycle topic for first managed node startup and its explicit initial boot-policy effect. + +~~~sh +fb nodes list +fb nodes pending +fb nodes status +~~~ + +### Targets + +LOCAL means the actual receiving machine. Use enrolled, confirmed aliases or IDs and inspect the selected target before work. Do not guess SSH identities or use historical lab hostnames as defaults. CLI tty --node can use the authenticated node transport. The interactive console command is local; run it at the destination or use an explicitly configured SSH destination. + +### Transport trust + +certificates create manages a private central/node transport CA, not a public website certificate or ChatGPT account authentication. Private keys stay protected on the central host; distribute only the required public trust material. Off-loopback broker use requires correctly configured verified TLS. Do not recreate trust merely to work around a connection symptom. + +~~~sh +fb certificates --help +~~~ + + + + +## 6. Persistent Bash terminals + +### Service and sessions + +tty service controls the profile's terminal daemon. tty open creates a real Bash session and returns its identifier. Use that identifier for subsequent observations and input. The same Bash preserves directory, variables and work between requests while the session/service remains alive. It does not preserve running processes across an operating-system reboot. Tool jobs use a profile-specific HOME; a job's home directory can therefore differ from the human login home. Commands retain the connecting account's normal operating-system permissions. + +~~~sh +fb tty service status +fb tty list +fb tty open +~~~ + +### Observe, type and keys + +tty observe returns terminal transcript text, cursor, dimensions and process state; it is not an authentic full-screen image. tty type sends text without automatically adding Enter, though supplied newlines can affect execution. tty key accepts exact uppercase names such as ENTER and CTRL_C. Replace session arguments with an identifier returned by open/list; never guess one. + +~~~sh +fb tty key --help +fb tty type --help +fb tty observe --help +~~~ + +### Lifetime and closing + +Default real idle expiry is disabled, but a profile can configure a timeout. Global status/list does not refresh each session's per-session activity. close destroys a session rather than merely separating a viewer. Graceful service stop attempts to close its sessions; elevated or unverifiable process state can prevent cleanup. Inspect the outcome instead of assuming that all child processes disappeared. + +~~~sh +fb tty close --help +fb tty resize --help +~~~ + +### Sudo mode + +A session's sudo on/off controls normal privilege eligibility; it does not log in as root or bypass operating-system authentication. Changing the mode recreates Bash, preserving directory and dimensions but not shell variables or internal state. Authentication stays in the normal human terminal/interface. Never send a password through type or chat. + +~~~sh +fb tty sudo --help +~~~ + +### Work console panels + +console combines an ordinary Bash pane with a read-only FBTERM activity dashboard. The dashboard is not a second interactive shell. The tmux prefix is Ctrl+B: release both keys, then press lowercase z to enlarge/restore a pane, or lowercase o to switch panes. The release accepts common zoom-key variants within its dedicated console. Closing a viewer normally leaves its tmux session alive; leaving Bash, killing its server or rebooting is different. In a desktop terminal the shared work view is horizontal with FBTERM on the left and Bash on the right. Explicit TTY mode is vertical with a short FBTERM monitor above Bash. The layout belongs to the shared tmux window and follows the last requested view mode; it is not isolated per viewer. + +~~~sh +fb console +fb status console +~~~ + + + + +## 7. Authentic screen observation and input + +### Text transcripts and pixels + +A CLI tty transcript describes a terminal and explicitly does not claim authentic pixels. MCP real-screen CUA-TTY and graphical screen tools operate through the real screen client and capture-linked interaction. Use those supported observation tools when authentic complete screen evidence is required. Never present a reconstructed terminal, animation or partial invented view as a real screenshot. + +### Automatic graphical discovery + +The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence. + +~~~sh +fb status +fb doctor +~~~ + +### Frame-buffer read access + +screen-access is a narrow, explicit administrative permission for reading the real frame buffer. It does not by itself enable graphical CUA, keyboard/mouse control or arbitrary VT switching. Persistent read access can remain effective after logout or a VT switch; review the command's displayed scope and use normal local administrator authentication. + +~~~sh +fb screen-access status +~~~ + +### Shared desktop + +A graphical desktop shares focus, keyboard and mouse. Use one controlling agent per desktop at a time and avoid overlapping work. This is an operating recommendation, not a claim of certified automatic isolation. Independent non-overlapping execution jobs may be possible; do not infer isolation or physical parallel execution from that alone. + + + + +## 8. Status and diagnosis + +### Inspect before acting + +status summarizes current runtime, connection, desired state and boot policy. A query completing successfully is distinct from the components being ready. Connection verification has a freshness boundary; retrying or unknown must not be treated as connected. Focused status uses the same underlying state model. + +~~~sh +fb status +fb status connection +fb status runtime +fb status boot +~~~ + +### Doctor + +doctor diagnoses prerequisites and reports findings with suggested next steps. It is not an implicit repair, and it is not a substitute for checking actual live connection state. Inspect the diagnosis and relevant private application logs before considering configuration changes. + +~~~sh +fb doctor +~~~ + +### Logs and progress + +Normal connection presents concise progress and a final result, then returns to the prompt. Detailed diagnostics belong in protected application logs. Share only the minimum relevant redacted result; never expose credentials, private keys, browser session data or unrelated personal paths. Avoid rerunning setup, enrollment or broad lifecycle tests merely because a view is confusing. + +~~~sh +fb help connection +fb help lifecycle +~~~ + + + + +## 9. Authorization, distribution and safe operation + +### Authorized scope + +An agent can use the documented CLI and MCP tools within the user's authorized scope. Reading this guide does not grant permission. Verify the receiver and selected target. Normal human terminal/browser authentication remains separate; do not request credentials in chat or invent phone/Owner-TTY enrollment requirements. Normal use should operate through the installed CLI and tools, not edit or rebuild installed FrameBridge program files. If the owner explicitly requests development, use a separate source clone, make reviewable changes, and do not present a modified build as an official release. This operating guidance does not restrict GNU GPL freedoms or require private forks to submit changes upstream. + +### Official releases + +The source is distributed under GNU GPLv3-only terms and the applicable third-party notices. Verify official release checksums and published signatures/repository metadata before installing. Signature verification identifies the publisher and detects alteration; it does not prohibit lawful modified versions. Official releases are reviewed through the project's release process. + +### Side effects + +Read status before service stop, credential forgetting, node approval, startup-hook changes or administrative screen permission changes. Explain material scope and consequences and follow the user's authorization. A sensitive operation must report the actual result, not just that a query succeeded. Keep real captures complete and distinguish observations from hypotheses. + + + + +## 10. Compatibility with earlier commands + +### Preferred spelling + +Use the short canonical interface in new documentation and automation. fb and framebridge remain equivalent. Older connect chatgpt, disconnect chatgpt and connection status spellings are compatibility routes. New status scopes replace the need to learn an extra connection noun. + +~~~sh +fb connect +fb disconnect +fb status connection +~~~ + +### Legacy lifecycle behavior + +Older root start/stop and autostart commands are retained for compatibility where documented, but their historical scopes can differ from the canonical service controls. In particular, legacy autostart disable removes managed setup and saved encrypted material; it is not an alias for boot-policy-only disable. Read the command's help before using a legacy script. + +~~~sh +fb help lifecycle +~~~ + +### Versioned help + +This guide describes the installed product and guide schema. Read the matching release notes for any semantic migration. The CLI help, MCP guide and static web documentation are generated from the same reviewed guide data; a cached manual from an earlier build can describe a different workflow. + +~~~sh +fb help compatibility +~~~ diff --git a/docs/CONNECTION-STATUS.md b/docs/CONNECTION-STATUS.md new file mode 100644 index 0000000..5d44c65 --- /dev/null +++ b/docs/CONNECTION-STATUS.md @@ -0,0 +1,97 @@ +# Connection setup, status and private logs + +## Connect in three steps + +Run as the Unix user who will own this FrameBridge profile: + +```sh +fb connect chatgpt +``` + +For a first unmanaged connection, the text wizard asks for these fields in order: + +| Step | Input | Meaning | +| --- | --- | --- | +| 1 | ChatGPT account/email label | A local label for the intended account. It does not sign in or authenticate that account. | +| 2 | Tunnel ID, entered at a hidden prompt | The raw ID of the tunnel selected in your own ChatGPT account. The normal wizard accepts IDs only. | +| 3 | MCP server access key | Entered at `MCP server access key (hidden):`. This is the connection's access key, not an OpenAI API key. | + +No earlier `fb setup`, `fb start` or separate automatic-startup command is required for a normal first standalone connection. If the profile does not exist, the successful setup creates one with local standalone defaults and automatic desktop detection. Existing central profiles use the same connection command. Nodes use their approved central connection and do not open a separate ChatGPT tunnel. + +Sign in and complete consent through your own ChatGPT browser session. A local account label, configured service or online tunnel does not prove that the browser account has been linked. Finish the browser workflow and verify a small, safe real tool call. + +## Setup progress and the result + +The same workflow configures the profile's Linux services, encrypts the saved access key and enables automatic startup and reconnection. Administrator authentication takes place in the normal terminal when required. There is no separate automatic-startup opt-in in this normal workflow. + +The command shows concise progress, checks current readiness and returns the shell prompt. It waits for up to 95 seconds for startup verification. `[OK] Connected.` is reported only for a verified current connection. A failure reports an error; if verification remains pending at the limit, the command reports the pending/retrying state and returns exit status 1 while the service continues in the background. Inspect current state with: + +```sh +fb connection status +``` + +On a valid managed profile, repeating `fb connect chatgpt` checks or resumes the saved encrypted settings without asking for those fields again. A damaged or unowned managed setup produces an error, not a connected result. + +After setup finishes, the terminal can be closed. The service starts immediately; a routine reboot is not part of this workflow. Later Linux boots do not repeat the connection prompts. Temporary failures use retry/backoff. An expired or revoked key requires replacement; retrying cannot make it valid. + +Monitoring can take a polling interval to detect a loss. A running process or an earlier success message does not guarantee current availability. Configuration success, current local/tunnel readiness and a successful real ChatGPT tool call are separate results. + +## Automatic tool selection + +The installer and control console remain text interfaces in a Linux TTY or a terminal emulator. FrameBridge queries Linux for the available environment automatically. + +| Available environment | Tools | +| --- | --- | +| Text console | FBEXEC, persistent FBTERM and CUA-TTY. | +| Usable physical X11 desktop | The text tools plus graphical CUA on that actual desktop. | +| Explicit private-desktop mode | The text tools plus CUA on a separate virtual X11 desktop, reported as `virtual-x11` with `physical=false`. | + +A graphical CUA request re-checks a physical X11 desktop that becomes available later, without restarting persistent FBTERM sessions. Checks around the operation reject a desktop change during it. The default adaptive mode does not create a private desktop or enable operating-system autologin. + +## Stop, replace a key or remove startup + +To stop only this profile's managed ChatGPT connection and retain its local tools: + +```sh +fb disconnect chatgpt +``` + +To stop its connection and local runtime now: + +```sh +fb stop +``` + +Both leave automatic startup enabled for the next boot. To stop it and disable later startup: + +```sh +fb autostart disable +``` + +To change the saved account label, tunnel or access key, run `fb connect chatgpt --replace` and complete the three fields. The update is transactional and does not require a prior disable. See [automatic startup details](AUTOMATIC-STARTUP.md) for service scope, encrypted-credential limits, guarded reversal and package removal. + +Normal package removal is available through `sudo apt remove framebridge-original-expanded`; no manual stop/disable sequence is required first. The package hook stops and disables only its recognized managed user services and preserves profile data, encrypted credentials and linger. See the technical startup page for the separate optional cleanup. + +## Advanced manual controls + +Use the same global `--state-dir` selection for all commands on a custom profile. `fb setup` remains available for central/node roles and nondefault settings. `fb autostart status` exposes the service, worker, runtime and connection details. The explicit `fb autostart enable` maintenance command remains available; it is not a prerequisite for the normal connection wizard. + +An attached diagnostic connection is an advanced nonpersistent workflow: + +```sh +fb connect chatgpt --foreground +``` + +It is mutually exclusive with `--replace`, requires an already-running local runtime and does not configure encrypted automatic startup. It asks for its connection inputs separately. Keep that terminal open; Ctrl+C or closing it ends that foreground connection. Stop an existing connection before starting a diagnostic attempt on the same profile. + +The normal tunnel prompt hides input and accepts only a raw ID. If a complete command is pasted accidentally, it is rejected without echoing it. The advanced foreground workflow retains support for a complete compatible tunnel-client command through its hidden prompt. That command is parsed as data and is never executed by a shell. FrameBridge uses its bundled client and rebuilds the MCP endpoint from the selected profile, so a pasted endpoint cannot redirect the connection to another server. + +## Credential handling and private diagnostics + +The normal wizard persists the access key only as a host-bound encrypted systemd credential. Its worker decrypts through pipes in process memory. The advanced foreground workflow keeps its key in process memory without creating a saved credential. In both cases the pinned tunnel client consumes the key through its own temporary process environment. Keys are not put in command arguments, plaintext profile files or the progress display. The complete pasted command is not saved or echoed. Public tunnel/account-label metadata may be saved. + +Technical startup messages and client diagnostics go to the selected profile's private connection log. The default is `~/.local/share/framebridge-expanded/logs/chatgpt/connection.log`; a custom profile uses its own `logs/chatgpt/connection.log`. Known key values and recognized credential patterns are redacted before writing. Directories use mode `0700`; log files use `0600`. Retention is bounded to the current file plus `connection.log.1` and `connection.log.2`, with a 2 MiB limit per file. + +When reporting a problem, include the FrameBridge version, current state and short error message. Review excerpts before sharing; omit keys, account passwords, cookies and complete credential-bearing commands. Do not publish a live profile or VM image. + +See the [Debian tutorial](../source/docs/DEBIAN-TUTORIAL.md) for package installation and advanced fleet setup. diff --git a/docs/DEMO-SCRIPT.md b/docs/DEMO-SCRIPT.md new file mode 100644 index 0000000..291aa65 --- /dev/null +++ b/docs/DEMO-SCRIPT.md @@ -0,0 +1,41 @@ +# Real demonstration script — review draft + +Target duration: 90 seconds, plus the existing animated introduction if desired. This document is a shot plan; it is not a claim that the recording has already been made. + +## Readiness + +Use the existing isolated Debian test VMs. Verify their current boot and node identities, normal user session, exact intended package and its verified identity, runtime readiness, owner-approved manual ChatGPT connection and selected node. Keep the foreground tunnel terminal out of the task workspace. A community-candidate demonstration requires that candidate's own checked installer and release evidence; historical RC7 acceptance is not a substitute. No new community recording or VM acceptance is claimed by this shot plan. + +A host reboot can leave the VMs stopped and requires a new manual tunnel connection. Restore the documented VM lifecycle first. Do not reset login credentials, approve a different account, or record secret-entry screens. + +Use a new nonce in every demo path. The data must be synthetic and the chosen paths must not already exist. + +## 0–15 seconds: the instruction starts in ChatGPT + +Show the selected test node label. In ChatGPT, request: "On the selected test node, create a new demo directory under my home with a unique suffix. Write a short installation checklist in notes.txt. Report its path, byte count and SHA-256. Use only new demo files." + +Show the actual tool call and backend result. Record the original timing; label any editing or speed changes in the finished video. + +## 15–35 seconds: the instruction becomes visible in the console + +Show the official FrameBridge console with FBTERM activity and its Bash pane. Display the newly created file and its checksum from that same selected node. Keep the complete native frame available and preserve the connection between the request, job identity, file and visible result. + +Do not render a transcript as if it were a screen capture. + +## 35–55 seconds: independent work + +Use a second ChatGPT conversation for a separate task in another new directory: create and summarize a tiny synthetic CSV. Keep the first conversation's checklist task independent. + +If both conversations are genuinely active at the same time, record that overlap. If execution is queued, show the queue honestly. Do not imply certified isolation or fake simultaneous actions with an edited split-screen. + +## 55–80 seconds: another request, a graphical result + +From ChatGPT, request a graphical action in the same test environment. A single conversation controls the shared desktop. Show a full authentic screen image, the action and its actual result. + +Open or attach the same persistent terminal session from the graphical desktop and display the checklist created at the beginning. Verify that the session/profile is the same rather than presenting a newly created terminal as continuity. + +## 80–90 seconds: evidence + +Show the installer filename, verified SHA-256, supported Debian version and a brief invitation to review the project. Keep installation details and account entry in the separate tutorial. + +The current music draft can accompany the edited presentation after its ownership and use are checked separately from the blog-text license. Record application and console evidence separately, then combine them with the clearly labeled animation. diff --git a/docs/LAUNCH-COPY.md b/docs/LAUNCH-COPY.md new file mode 100644 index 0000000..55aee5d --- /dev/null +++ b/docs/LAUNCH-COPY.md @@ -0,0 +1,35 @@ +# FrameBridge launch copy — drafts + +Use the pre-release wording while the community candidate is being prepared. Recheck the release state, links and evidence before publication. These drafts do not authorize publication. + +## Project description + +FrameBridge connects ChatGPT to tools running on Linux computers you control. Execute tasks, return to persistent terminals, inspect the real desktop and select approved worker nodes through a central connection. ChatGPT remains hosted; tool execution stays on your machines. + +## Pre-release announcement + +FrameBridge is preparing a community release for people who want to work with their own Linux computers through ChatGPT. Its recorded RC7 baseline demonstrates command execution, persistent terminals, real console interaction and selected nodes. Preview 2 has passed 376 unit tests; package and VM checks, verified downloads and a fresh real demonstration remain in progress. Pinned dependency terms and actual source-delivery conditions are documented. The project's original code uses GPL-3.0-only within its stated licensing scope. + +## Short social draft + +Your Linux computers. A persistent terminal. The node you choose. FrameBridge connects those tools to your ChatGPT conversations. Community release preparation is underway; follow the project for verified builds, real demonstrations and ways to contribute. + +## README hero + +**Bring your ChatGPT conversations to Linux computers you control.** + +Execute a task, return to the same terminal, inspect the real desktop and direct work to a selected node. + +## Use with a verified real demonstration + +This caption is reserved for a completed, checked real recording: + +> One request, an observable result on the selected Linux computer, and the same persistent terminal ready for the next step. + +Identify the exact build and node shown. Use complete authentic captures, remove credentials and unrelated personal information, and verify the actions before publishing. Label a conceptual animation as an illustration instead. + +## Editorial focus and legal attribution + +Lead with the capability, the workflow and its observed result. Keep founder biography and personal branding out of the promotional copy. Preserve legal attribution in COPYRIGHT.md, component notices and article attribution metadata. + +Avoid claims of automatic isolation, unrestricted autonomy, universal compatibility, one-click setup or guaranteed safety. Explain the actual connection steps and the shared focus, keyboard and mouse of graphical control where relevant. diff --git a/docs/LICENSING-PROPOSAL.md b/docs/LICENSING-PROPOSAL.md new file mode 100644 index 0000000..2bb0712 --- /dev/null +++ b/docs/LICENSING-PROPOSAL.md @@ -0,0 +1,9 @@ +# Licensing decision and proposal history + +The owner approved GNU GPL version 3 for original FrameBridge material after considering a community-oriented release. This preparation records the exact choice as **GPL-3.0-only**. Future FSF license versions are not selected automatically. + +The earlier Apache-2.0 recommendation favored permissive reuse and commercial integration. It was a proposal, never a first-party license applied by the private preparation. It is superseded by the recorded GPL choice. Dependencies that use Apache or another license retain their applicable terms. + +Read the active [LICENSE](../LICENSE), [licensing scope](../LICENSING.md) and [license status](../LICENSE-STATUS.md). Original blog article text explicitly identified for that purpose uses CC-BY-SA-4.0, with separate treatment of code and other media. + +The remaining provenance, third-party and privacy review items are recorded in the [publication checklist](PUBLICATION-CHECKLIST.md). Choosing a project license does not close them or publish the project. diff --git a/docs/OPENAI-BRIEF.md b/docs/OPENAI-BRIEF.md new file mode 100644 index 0000000..e4551ab --- /dev/null +++ b/docs/OPENAI-BRIEF.md @@ -0,0 +1,22 @@ +# FrameBridge — private technical evaluation brief + +FrameBridge connects hosted ChatGPT conversations to execution, persistent-terminal and real-screen tools on Linux computers controlled by their owner. A central instance provides access to approved worker nodes so the operator can select where a task runs. + +The core workflow is to request a small task, inspect its result on the selected computer and continue in the same terminal. Separate conversations have been used for independent FBEXEC tasks when objectives and changes did not overlap. That observed use does not establish isolation or simultaneous execution of every command. A graphical desktop shares focus, keyboard and mouse; one controlling instance per desktop is an operating recommendation. + +## Recorded baseline and current candidate + +Original Expanded RC7 targets Debian 13 amd64 with manual account consent and connection. Its historical acceptance record includes 361 unit tests, a byte-identical package rebuild, Debian VM installation/lifecycle checks, worker enrollment and authentic console images. + +The current candidate is `0.3.1~community.20261026.2`. It changes implementation and packaging files and adds a regression test. Preview 2 passed 376 unit tests in 22.351 seconds. Package and VM validation and a fresh real demonstration remain pending at documentation freeze. Historical RC7 results do not certify this build. + +A dependency source and notice supplement has been collected. [Source availability](../licensing/third-party/SOURCE-AVAILABILITY.md) and [third-party review](THIRD-PARTY-RELEASE-REVIEW.md) record reviewed permissive terms, the technically accepted unchanged Certifi source route, R2 supplements and actual source-delivery conditions. + +## Useful evaluation questions + +- Can a tester follow the correct build's installation and connection instructions? +- Can a reviewer trace a request to the selected node, persistent console and visible result? +- Are account consent, node identity, desktop coordination and recovery behavior clear? +- What would improve the documentation and maintainability of this workflow? + +Original FrameBridge material uses GPL-3.0-only within its stated scope. The project is independent. This brief remains private, has not been sent to OpenAI and claims no affiliation or endorsement. diff --git a/docs/PRIVACY-REVIEW.md b/docs/PRIVACY-REVIEW.md new file mode 100644 index 0000000..ad71d74 --- /dev/null +++ b/docs/PRIVACY-REVIEW.md @@ -0,0 +1,13 @@ +# Privacy review status — FrameBridge 0.3.1 preparation + +This review describes the separate 0.3.1 source preparation. Historical RC7 and Community Preview inventories remain provenance records; final source manifests must describe the actual assembled release. + +A bounded tracked-text scan examined 1,259 UTF-8 files for private-key banners and common credential-token shapes. Two candidates were reviewed as examples: a synthetic rejection-test input and the public PyJWT package metadata encode/decode example. Neither was validated as a secret. No matched credential values are retained in review reports. This is not a guarantee that every possible secret is absent. + +A separate non-vendor path scan found no matches for the owner's known home-path and email patterns. Remaining laboratory names occur in retained source/provenance labels and implementation messages, not owner credentials. Matched private-network strings were generic RFC1918 network policy ranges or unit-test fixture addresses, not validated owner access endpoints. Keep public interface help generic and preserve legitimate third-party copyright/author notices. + +CLASSIC_NODE_TARGETS is currently initialized to an empty dictionary. The compatibility routing function is conditionally reachable; this review does not dismiss it as wholly dead code. An empty implicit target map must not become an excuse to infer or restore personal infrastructure routes. + +The normal Expanded implementation uses account-relative state and generic transport. Review final staged source and approved promotional content for unrelated personal information. Do not publish runtime profiles, SSH material, passwords, tokens, cookies, logs containing hidden inputs or unrelated owner data. Source filenames preserving historical origin are not a claim that a matching machine is an available public node. + +No production services, profiles or owner credentials were accessed or changed by this text review. The final package, source archive and download delivery checks are separate release evidence. Subsequent implementation changes require a final artifact scan before public exposure. diff --git a/docs/PRIVATE-EVALUATION.md b/docs/PRIVATE-EVALUATION.md new file mode 100644 index 0000000..7c301be --- /dev/null +++ b/docs/PRIVATE-EVALUATION.md @@ -0,0 +1,58 @@ +# Community Preview 2 evaluation guide + +Current package: `FrameBridge-0.3.1-Community-Preview2.deb`, version `0.3.1~community.20261026.2`. Preview 2 passed 376 unit tests; package/VM checks remain pending at this documentation freeze. This guide is the intended workflow, not a completed VM acceptance record. + +## Supported starting point + +Use an isolated Debian 13 amd64 VM or test computer and keep normal owner/admin credentials under the owner's control. Preserve production profiles. + +Obtain the Preview 2 installer and its release checksum record. Follow the [Debian tutorial](../source/docs/DEBIAN-TUTORIAL.md) to verify the selected file before installing. RC7 and Preview 1 installer hashes do not apply to Preview 2. Source delivery uses the matching Preview 2 source archive, shared immutable Preview 1-named dependency asset and R2 supplements. + +## One computer + +Run setup, select standalone, review the node label and profile, run doctor, and start the runtime. Connect manually with `fb connect chatgpt`. The foreground connection needs the owner-supplied tunnel and runtime key each time it is established. Keep it open; a second local terminal can inspect `fb status`. + +In the intended ChatGPT account, enable the intended app/tunnel, complete consent, make a small tool call, and inspect the backend result. An online tunnel or a remembered account label alone is not successful end-to-end acceptance. + +## Central first, workers later + +Configure and start a central, then connect ChatGPT manually. Workers can be added afterward without reconnecting that central. Follow the complete certificate and identity instructions in the Debian tutorial. + +On a new worker, install the same verified package, choose node during setup, run doctor, and enroll before starting the node runtime: + +```sh +fb setup +fb doctor +fb nodes enroll --central https://CENTRAL_ADDRESS:20878 --name worker-1 +``` + +Use the actual verified HTTPS URL printed by the central, with the public CA checked through a trusted channel. CENTRAL_ADDRESS and worker-1 above are placeholders. + +On the central, inspect the pending request and compare the node identity and public fingerprint: + +```sh +fb nodes pending +fb nodes approve REQUEST_ID --node-id NODE_UUID +``` + +On the worker, poll for confirmation and start only after approval and pairing complete: + +```sh +fb nodes poll +fb start +fb status +``` + +On the central, use `fb nodes list` to check current operational transport readiness. Registration alone does not establish that a worker is currently reachable. + +## Suggested small evaluation + +Use new demo-only files under the selected node user's home. Ask ChatGPT to create a short checklist, compute its SHA-256, and display the same file from the persistent console. For concurrent work, use a separate directory and an independent task in each conversation. Compare results and record interference if it occurs. + +During graphical work, give one conversation control of the shared desktop. Use a full authentic screen capture, inspect it before input, and verify the visible outcome. Reopen the same terminal session when demonstrating continuity. + +Do not include passwords, tokens, private keys, cookies, real account pages or unrelated personal files in recordings. Public test labels and synthetic documents are sufficient. + +## End of evaluation + +Stop the foreground tunnel with Ctrl+C when finished, then use the normal runtime stop command as appropriate for the test profile. Keep the owner's production installations and unrelated tasks outside the evaluation. diff --git a/docs/PUBLICATION-CHECKLIST.md b/docs/PUBLICATION-CHECKLIST.md new file mode 100644 index 0000000..eae07f5 --- /dev/null +++ b/docs/PUBLICATION-CHECKLIST.md @@ -0,0 +1,23 @@ +# Community Preview 2 publication checklist + +Status: private validation of `0.3.1~community.20261026.2`; no public release or blog publication is performed by this preparation. + +| Item | Current position | +| --- | --- | +| Candidate identity | Preview 2 installer/source names and current inventories; RC7 and Preview 1 remain separate historical artifacts. | +| Unit tests | Preview 2 full suite: 376 PASS in 22.351 seconds. Focused peer regression: 20 PASS, including 8 new peer tests; not an additional full-suite count. | +| Package and VM | Pending at documentation freeze. Use the external QA record for later outcomes and exact hashes. | +| Control peers | Existing loopback peer by default; explicit repeatable IP-literal CLI additions; malformed configuration fails before Store/bind. | +| Retained guards | RPC and administrator identity guards preserved; normal Expanded terminal entrypoint remains separate. | +| Original code/docs | GPL-3.0-only with scope and attribution preserved. | +| Original blog text | Explicit CC-BY-SA-4.0 draft; images, recordings and code retain separate treatment. | +| Native terms | Pinned set reconciled through documented permissive routes; original alternatives and combined notices retained. | +| Legal records | 137 text files, including full MPL and the separate R2 wit-bindgen-rt MIT notice. | +| Source delivery | Matching Preview 2 source plus immutable 82,606,998-byte Preview 1-named dependency asset and R2 supplements, with clear recipient access. | +| Certifi | Unchanged exact upstream sources and notices technically accepted with actual source-access conditions. | +| Provenance limits | Original Mozilla certdata revision unidentified; native wheels not independently rebuilt. Recorded limits, not generic automatic blockers. | +| Privacy/media | Inspect final export and approved demo; real captures complete, conceptual animations labeled. | +| Downloads and public hosting | Verify final accessible files/hashes and actual endpoint when publication is authorized. | +| Real demonstration | Fresh checked Preview 2 recording remains pending. | + +The legal/source records describe scoped review and concrete delivery obligations, not a universal legal certification. The project's license does not relabel dependencies. Preserve legal attribution and current artifact identities. diff --git a/docs/THIRD-PARTY-INVENTORY.md b/docs/THIRD-PARTY-INVENTORY.md new file mode 100644 index 0000000..283b853 --- /dev/null +++ b/docs/THIRD-PARTY-INVENTORY.md @@ -0,0 +1,47 @@ +# Python distribution inventory and Preview 2 source delivery + +The following table records the 29 distributions and 33 legal files inventoried in RC7. Their component versions and original notices remain retained in the 930-file vendor subset unchanged between Community Preview 1 and Preview 2. The table is not a complete native-dependency or current legal-text count. + +| Distribution | Version | Declared license | Historical legal-file count | +| --- | --- | --- | --- | +| annotated-types | 0.8.0 | MIT | 1 | +| anyio | 4.15.1 | MIT | 1 | +| attrs | 26.1.0 | MIT | 1 | +| certifi | 2026.7.22 | MPL-2.0 | 1 | +| cffi | 2.1.1 | MIT-0 | 1 | +| click | 8.5.0 | BSD-3-Clause | 1 | +| cryptography | 50.0.1 | Apache-2.0 OR BSD-3-Clause | 3 | +| h11 | 0.16.0 | MIT | 1 | +| httpcore | 1.0.9 | BSD-3-Clause | 1 | +| httpx | 0.28.1 | BSD-3-Clause | 1 | +| httpx-sse | 0.4.3 | MIT | 1 | +| idna | 3.20 | BSD-3-Clause | 1 | +| jsonschema | 4.26.0 | MIT | 1 | +| jsonschema-specifications | 2025.9.1 | MIT | 1 | +| mcp | 1.26.0 | MIT | 1 | +| pycparser | 3.0 | BSD-3-Clause | 1 | +| pydantic | 2.13.5 | MIT | 1 | +| pydantic_core | 2.46.5 | MIT | 1 | +| pydantic-settings | 2.15.0 | MIT | 1 | +| PyJWT | 2.14.0 | MIT | 2 | +| python-dotenv | 1.2.3 | BSD-3-Clause | 1 | +| python-multipart | 0.0.32 | Apache-2.0 | 1 | +| referencing | 0.37.0 | MIT | 1 | +| rpds-py | 2026.6.3 | MIT | 1 | +| sse-starlette | 3.4.11 | BSD-3-Clause | 2 | +| starlette | 1.7.0 | BSD-3-Clause | 1 | +| typing_extensions | 4.16.0 | PSF-2.0 | 1 | +| typing-inspection | 0.4.4 | MIT | 1 | +| uvicorn | 0.53.0 | BSD-3-Clause | 1 | + +## Shared source asset and R2 supplements + +The immutable `FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz` is 82,606,998 bytes, SHA-256 `64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a`. Its Preview 1 name is retained intentionally. It includes five Python source distributions, 143 lock-verified crate archives, OpenSSL/libffi source evidence, pinned Git sources, the exact Certifi bundle and identified Rust source supplements. + +Its 135 legal texts are accompanied by full MPL-2.0 and the R2 MIT notice for wit-bindgen-rt 0.39.0, for 137 legal text files. Keep R2-SUPPLEMENTS.json and its referenced notice with the complete source delivery. This supplement does not silently change the sealed shared asset or original index. + +Read [source availability](../licensing/third-party/SOURCE-AVAILABILITY.md), the [native terms matrix](../licensing/third-party/NATIVE-DEPENDENCY-TERMS.md) and [release review](THIRD-PARTY-RELEASE-REVIEW.md). The pinned terms were reconciled through documented permissive routes while preserving original alternatives, combined obligations and attribution. + +Unchanged Certifi source availability is technically accepted with exact source assets, original notices, full MPL and clear recipient access. The original Mozilla revision remains unidentified; that is a provenance limit, not an automatic blocker. Four native objects match official wheels; independent rebuilding was not performed and is not imposed as a generic source-availability requirement. + +At distribution, supply the matching Preview 2 installer and source, shared dependency asset, R2 supplements and notices, and verify access and download hashes. A project GPL choice does not relabel component terms or convert a private cache into public delivery. diff --git a/docs/THIRD-PARTY-RELEASE-REVIEW.md b/docs/THIRD-PARTY-RELEASE-REVIEW.md new file mode 100644 index 0000000..8e8c236 --- /dev/null +++ b/docs/THIRD-PARTY-RELEASE-REVIEW.md @@ -0,0 +1,27 @@ +# Third-party terms and source delivery — FrameBridge 0.3.1 + +This scoped release record combines the retained Python/Rust review with the 0.3.1 OpenAI tunnel-client notice supplement. It does not relabel incorporated components as FrameBridge-owned GPL material. + +## Immutable Python/Rust source asset + +FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz is 82,606,998 bytes, SHA-256 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a. Its historical filename is retained intentionally. + +The asset supplies five Python source distributions, 143 lockfile-verified crate archives, pinned OpenSSL/libffi source evidence, upstream Git sources, exact Certifi CA bundle and identified Rust supplements. The retained Python vendor/native set has its applicable original notices. [Native terms](../licensing/third-party/NATIVE-DEPENDENCY-TERMS.md) records selected permissive routes, original alternatives and combined obligations. + +The immutable asset's original 135 legal texts are supplemented by the full MPL-2.0 text and R2 wit-bindgen-rt MIT notice, for the documented 137-text set. Retain R2-SUPPLEMENTS.json and its referenced notice; do not rewrite the shared asset. + +## OpenAI executable and Go supplement + +The OpenAI tunnel-client executable is separately licensed Apache-2.0. [Its notice record](../licensing/third-party/tunnel-client/README.md) supplies the upstream license/notice and Go toolchain terms, identifies the 47 embedded Go modules, and preserves 61 module legal files. All 47 exact module source archives were verified against embedded h1 checksums. + +FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz is 26,941,745 bytes, SHA-256 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9. Keep it with the matching release and source. + +The executable's own module is marked (devel) and has no embedded VCS revision. Its original source commit and an independent rebuild are not established. The official upstream notice source is pinned separately and is not misrepresented as that build commit. + +## Delivery conditions and limits + +Provide FrameBridge-0.3.1.deb, its exact corresponding FrameBridge source archive, both dependency-source assets, R2 supplement and retained notices through actual working download paths. Verify file hashes at staging and public delivery. Private collection or a future URL is not public availability. + +The unchanged Certifi arrangement retains its exact source assets, original notice, full MPL-2.0 text and recipient access instructions. The original historical Mozilla certdata revision remains unidentified. No independent native-wheel rebuild is claimed. These provenance/reproducibility limits remain explicit. + +Read [SOURCE-AVAILABILITY.md](../licensing/third-party/SOURCE-AVAILABILITY.md), [LICENSING.md](../LICENSING.md) and [COPYRIGHT.md](../COPYRIGHT.md). First-party GPL-3.0-only scope remains distinct from third-party terms. This is a technical release record, not a blanket legal or security certification. diff --git a/licensing/SOURCE-LICENSE-SCOPE.json b/licensing/SOURCE-LICENSE-SCOPE.json new file mode 100644 index 0000000..a3f3663 --- /dev/null +++ b/licensing/SOURCE-LICENSE-SCOPE.json @@ -0,0 +1,4356 @@ +{ + "schema_version": 4, + "project_license": "GPL-3.0-only", + "basis": "Owner decision for original FrameBridge material the rightsholders are entitled to license.", + "scope_notice": "../LICENSING.md", + "source_snapshot_files": 1082, + "path_classification_is_ownership_proof": false, + "ownership_review": "Further provenance review required before external distribution. Non-vendor paths are a review set, not an assertion that every byte is solely owned by the maintainer. Separately licensed or discovered third-party material retains its terms.", + "vendor_paths_retaining_original_terms": [ + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py", + "sha256": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py", + "sha256": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so", + "sha256": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA", + "sha256": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD", + "sha256": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL", + "sha256": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE", + "sha256": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py", + "sha256": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py", + "sha256": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA", + "sha256": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD", + "sha256": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL", + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt", + "sha256": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE", + "sha256": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt", + "sha256": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py", + "sha256": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py", + "sha256": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py", + "sha256": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py", + "sha256": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py", + "sha256": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py", + "sha256": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py", + "sha256": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py", + "sha256": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py", + "sha256": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py", + "sha256": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py", + "sha256": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py", + "sha256": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py", + "sha256": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py", + "sha256": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py", + "sha256": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py", + "sha256": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py", + "sha256": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py", + "sha256": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py", + "sha256": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py", + "sha256": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py", + "sha256": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py", + "sha256": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py", + "sha256": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py", + "sha256": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py", + "sha256": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py", + "sha256": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py", + "sha256": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py", + "sha256": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py", + "sha256": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py", + "sha256": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py", + "sha256": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py", + "sha256": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py", + "sha256": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py", + "sha256": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py", + "sha256": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py", + "sha256": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py", + "sha256": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py", + "sha256": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py", + "sha256": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py", + "sha256": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py", + "sha256": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py", + "sha256": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py", + "sha256": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py", + "sha256": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi", + "sha256": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py", + "sha256": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi", + "sha256": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py", + "sha256": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py", + "sha256": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py", + "sha256": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py", + "sha256": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py", + "sha256": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi", + "sha256": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py", + "sha256": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi", + "sha256": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py", + "sha256": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi", + "sha256": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py", + "sha256": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi", + "sha256": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py", + "sha256": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi", + "sha256": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py", + "sha256": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi", + "sha256": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py", + "sha256": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi", + "sha256": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA", + "sha256": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD", + "sha256": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL", + "sha256": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE", + "sha256": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py", + "sha256": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi", + "sha256": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py", + "sha256": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py", + "sha256": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py", + "sha256": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py", + "sha256": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py", + "sha256": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA", + "sha256": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD", + "sha256": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL", + "sha256": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt", + "sha256": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py", + "sha256": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py", + "sha256": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem", + "sha256": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py", + "sha256": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py", + "sha256": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA", + "sha256": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD", + "sha256": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL", + "sha256": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt", + "sha256": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE", + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt", + "sha256": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py", + "sha256": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h", + "sha256": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py", + "sha256": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h", + "sha256": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h", + "sha256": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py", + "sha256": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py", + "sha256": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py", + "sha256": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py", + "sha256": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py", + "sha256": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py", + "sha256": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py", + "sha256": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py", + "sha256": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py", + "sha256": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py", + "sha256": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py", + "sha256": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py", + "sha256": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h", + "sha256": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py", + "sha256": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py", + "sha256": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py", + "sha256": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py", + "sha256": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py", + "sha256": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py", + "sha256": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA", + "sha256": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD", + "sha256": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL", + "sha256": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt", + "sha256": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py", + "sha256": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py", + "sha256": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py", + "sha256": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py", + "sha256": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py", + "sha256": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py", + "sha256": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py", + "sha256": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py", + "sha256": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py", + "sha256": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py", + "sha256": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py", + "sha256": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py", + "sha256": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py", + "sha256": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py", + "sha256": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py", + "sha256": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py", + "sha256": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py", + "sha256": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA", + "sha256": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD", + "sha256": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL", + "sha256": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE", + "sha256": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE", + "sha256": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD", + "sha256": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json", + "sha256": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json", + "sha256": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py", + "sha256": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py", + "sha256": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py", + "sha256": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py", + "sha256": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py", + "sha256": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py", + "sha256": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py", + "sha256": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py", + "sha256": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py", + "sha256": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py", + "sha256": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py", + "sha256": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py", + "sha256": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so", + "sha256": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi", + "sha256": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi", + "sha256": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi", + "sha256": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi", + "sha256": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi", + "sha256": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi", + "sha256": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi", + "sha256": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi", + "sha256": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi", + "sha256": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi", + "sha256": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi", + "sha256": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi", + "sha256": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi", + "sha256": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi", + "sha256": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi", + "sha256": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi", + "sha256": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi", + "sha256": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi", + "sha256": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi", + "sha256": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi", + "sha256": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi", + "sha256": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi", + "sha256": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi", + "sha256": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi", + "sha256": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi", + "sha256": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi", + "sha256": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi", + "sha256": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi", + "sha256": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi", + "sha256": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi", + "sha256": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi", + "sha256": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi", + "sha256": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py", + "sha256": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py", + "sha256": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py", + "sha256": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py", + "sha256": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py", + "sha256": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py", + "sha256": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py", + "sha256": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py", + "sha256": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py", + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py", + "sha256": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py", + "sha256": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py", + "sha256": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py", + "sha256": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py", + "sha256": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py", + "sha256": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py", + "sha256": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py", + "sha256": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py", + "sha256": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py", + "sha256": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py", + "sha256": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py", + "sha256": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py", + "sha256": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py", + "sha256": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py", + "sha256": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py", + "sha256": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py", + "sha256": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py", + "sha256": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py", + "sha256": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py", + "sha256": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py", + "sha256": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py", + "sha256": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py", + "sha256": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py", + "sha256": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py", + "sha256": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py", + "sha256": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py", + "sha256": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py", + "sha256": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py", + "sha256": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py", + "sha256": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py", + "sha256": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py", + "sha256": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py", + "sha256": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py", + "sha256": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py", + "sha256": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py", + "sha256": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py", + "sha256": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py", + "sha256": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py", + "sha256": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py", + "sha256": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py", + "sha256": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py", + "sha256": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py", + "sha256": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py", + "sha256": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py", + "sha256": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py", + "sha256": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py", + "sha256": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py", + "sha256": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py", + "sha256": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py", + "sha256": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py", + "sha256": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py", + "sha256": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py", + "sha256": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py", + "sha256": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py", + "sha256": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py", + "sha256": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py", + "sha256": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py", + "sha256": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed", + "sha256": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py", + "sha256": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py", + "sha256": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA", + "sha256": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD", + "sha256": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL", + "sha256": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt", + "sha256": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt", + "sha256": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py", + "sha256": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py", + "sha256": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py", + "sha256": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py", + "sha256": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py", + "sha256": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py", + "sha256": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py", + "sha256": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py", + "sha256": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py", + "sha256": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py", + "sha256": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py", + "sha256": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed", + "sha256": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA", + "sha256": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD", + "sha256": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL", + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md", + "sha256": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py", + "sha256": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py", + "sha256": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py", + "sha256": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py", + "sha256": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py", + "sha256": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py", + "sha256": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py", + "sha256": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py", + "sha256": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py", + "sha256": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py", + "sha256": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py", + "sha256": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py", + "sha256": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py", + "sha256": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py", + "sha256": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py", + "sha256": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py", + "sha256": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py", + "sha256": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py", + "sha256": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py", + "sha256": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py", + "sha256": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py", + "sha256": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py", + "sha256": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py", + "sha256": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py", + "sha256": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py", + "sha256": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py", + "sha256": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py", + "sha256": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py", + "sha256": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py", + "sha256": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py", + "sha256": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA", + "sha256": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD", + "sha256": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL", + "sha256": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt", + "sha256": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md", + "sha256": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py", + "sha256": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py", + "sha256": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py", + "sha256": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py", + "sha256": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py", + "sha256": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py", + "sha256": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py", + "sha256": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py", + "sha256": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py", + "sha256": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py", + "sha256": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py", + "sha256": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py", + "sha256": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py", + "sha256": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py", + "sha256": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py", + "sha256": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py", + "sha256": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py", + "sha256": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py", + "sha256": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py", + "sha256": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py", + "sha256": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py", + "sha256": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py", + "sha256": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py", + "sha256": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA", + "sha256": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD", + "sha256": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL", + "sha256": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE", + "sha256": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt", + "sha256": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py", + "sha256": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py", + "sha256": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py", + "sha256": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py", + "sha256": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py", + "sha256": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA", + "sha256": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD", + "sha256": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL", + "sha256": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt", + "sha256": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md", + "sha256": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py", + "sha256": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py", + "sha256": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py", + "sha256": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py", + "sha256": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py", + "sha256": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py", + "sha256": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py", + "sha256": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py", + "sha256": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py", + "sha256": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py", + "sha256": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA", + "sha256": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD", + "sha256": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL", + "sha256": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt", + "sha256": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING", + "sha256": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py", + "sha256": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py", + "sha256": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py", + "sha256": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py", + "sha256": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py", + "sha256": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py", + "sha256": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py", + "sha256": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py", + "sha256": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py", + "sha256": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py", + "sha256": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py", + "sha256": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py", + "sha256": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py", + "sha256": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json", + "sha256": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py", + "sha256": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py", + "sha256": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py", + "sha256": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py", + "sha256": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py", + "sha256": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py", + "sha256": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py", + "sha256": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py", + "sha256": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py", + "sha256": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py", + "sha256": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py", + "sha256": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py", + "sha256": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py", + "sha256": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py", + "sha256": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py", + "sha256": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py", + "sha256": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py", + "sha256": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py", + "sha256": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py", + "sha256": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py", + "sha256": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py", + "sha256": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py", + "sha256": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA", + "sha256": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD", + "sha256": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL", + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING", + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py", + "sha256": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py", + "sha256": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json", + "sha256": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator", + "sha256": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content", + "sha256": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core", + "sha256": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format", + "sha256": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data", + "sha256": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation", + "sha256": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json", + "sha256": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator", + "sha256": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content", + "sha256": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core", + "sha256": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation", + "sha256": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion", + "sha256": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data", + "sha256": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated", + "sha256": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation", + "sha256": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json", + "sha256": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json", + "sha256": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json", + "sha256": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json", + "sha256": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py", + "sha256": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py", + "sha256": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py", + "sha256": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py", + "sha256": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py", + "sha256": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py", + "sha256": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py", + "sha256": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py", + "sha256": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py", + "sha256": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py", + "sha256": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py", + "sha256": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py", + "sha256": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py", + "sha256": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA", + "sha256": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD", + "sha256": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL", + "sha256": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt", + "sha256": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE", + "sha256": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py", + "sha256": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py", + "sha256": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py", + "sha256": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py", + "sha256": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py", + "sha256": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py", + "sha256": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py", + "sha256": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py", + "sha256": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py", + "sha256": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py", + "sha256": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py", + "sha256": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py", + "sha256": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py", + "sha256": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py", + "sha256": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py", + "sha256": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py", + "sha256": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py", + "sha256": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py", + "sha256": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py", + "sha256": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py", + "sha256": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py", + "sha256": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py", + "sha256": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py", + "sha256": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py", + "sha256": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py", + "sha256": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py", + "sha256": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py", + "sha256": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py", + "sha256": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py", + "sha256": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py", + "sha256": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py", + "sha256": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py", + "sha256": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py", + "sha256": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py", + "sha256": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py", + "sha256": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py", + "sha256": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py", + "sha256": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py", + "sha256": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py", + "sha256": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py", + "sha256": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py", + "sha256": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py", + "sha256": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py", + "sha256": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py", + "sha256": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py", + "sha256": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py", + "sha256": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py", + "sha256": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py", + "sha256": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py", + "sha256": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py", + "sha256": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py", + "sha256": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py", + "sha256": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py", + "sha256": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py", + "sha256": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py", + "sha256": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py", + "sha256": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py", + "sha256": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py", + "sha256": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py", + "sha256": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py", + "sha256": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py", + "sha256": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py", + "sha256": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py", + "sha256": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py", + "sha256": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py", + "sha256": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py", + "sha256": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py", + "sha256": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py", + "sha256": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py", + "sha256": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py", + "sha256": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py", + "sha256": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py", + "sha256": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py", + "sha256": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py", + "sha256": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py", + "sha256": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py", + "sha256": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py", + "sha256": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py", + "sha256": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py", + "sha256": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py", + "sha256": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py", + "sha256": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py", + "sha256": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py", + "sha256": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py", + "sha256": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py", + "sha256": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py", + "sha256": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py", + "sha256": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py", + "sha256": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py", + "sha256": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py", + "sha256": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py", + "sha256": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py", + "sha256": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py", + "sha256": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py", + "sha256": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py", + "sha256": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py", + "sha256": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py", + "sha256": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py", + "sha256": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py", + "sha256": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py", + "sha256": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py", + "sha256": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py", + "sha256": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py", + "sha256": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py", + "sha256": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py", + "sha256": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py", + "sha256": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py", + "sha256": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py", + "sha256": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py", + "sha256": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py", + "sha256": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA", + "sha256": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD", + "sha256": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL", + "sha256": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE", + "sha256": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt", + "sha256": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py", + "sha256": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py", + "sha256": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg", + "sha256": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py", + "sha256": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py", + "sha256": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py", + "sha256": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py", + "sha256": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py", + "sha256": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA", + "sha256": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD", + "sha256": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL", + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE", + "sha256": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py", + "sha256": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py", + "sha256": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py", + "sha256": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py", + "sha256": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py", + "sha256": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py", + "sha256": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py", + "sha256": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py", + "sha256": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py", + "sha256": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py", + "sha256": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py", + "sha256": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py", + "sha256": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py", + "sha256": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py", + "sha256": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py", + "sha256": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py", + "sha256": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py", + "sha256": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py", + "sha256": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py", + "sha256": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py", + "sha256": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py", + "sha256": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py", + "sha256": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py", + "sha256": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py", + "sha256": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py", + "sha256": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py", + "sha256": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py", + "sha256": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py", + "sha256": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py", + "sha256": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py", + "sha256": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py", + "sha256": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py", + "sha256": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py", + "sha256": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py", + "sha256": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py", + "sha256": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py", + "sha256": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py", + "sha256": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py", + "sha256": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py", + "sha256": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py", + "sha256": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py", + "sha256": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py", + "sha256": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py", + "sha256": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py", + "sha256": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py", + "sha256": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py", + "sha256": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py", + "sha256": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py", + "sha256": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py", + "sha256": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py", + "sha256": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py", + "sha256": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py", + "sha256": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py", + "sha256": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py", + "sha256": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py", + "sha256": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py", + "sha256": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py", + "sha256": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py", + "sha256": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py", + "sha256": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py", + "sha256": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py", + "sha256": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py", + "sha256": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py", + "sha256": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py", + "sha256": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py", + "sha256": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py", + "sha256": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py", + "sha256": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py", + "sha256": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py", + "sha256": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py", + "sha256": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py", + "sha256": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py", + "sha256": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py", + "sha256": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py", + "sha256": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py", + "sha256": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py", + "sha256": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py", + "sha256": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py", + "sha256": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py", + "sha256": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py", + "sha256": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py", + "sha256": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py", + "sha256": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py", + "sha256": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py", + "sha256": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py", + "sha256": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py", + "sha256": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py", + "sha256": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py", + "sha256": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py", + "sha256": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py", + "sha256": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py", + "sha256": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py", + "sha256": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py", + "sha256": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py", + "sha256": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py", + "sha256": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py", + "sha256": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py", + "sha256": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py", + "sha256": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py", + "sha256": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py", + "sha256": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py", + "sha256": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py", + "sha256": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py", + "sha256": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA", + "sha256": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD", + "sha256": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL", + "sha256": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE", + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json", + "sha256": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py", + "sha256": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so", + "sha256": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi", + "sha256": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py", + "sha256": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA", + "sha256": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD", + "sha256": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL", + "sha256": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE", + "sha256": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py", + "sha256": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py", + "sha256": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py", + "sha256": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py", + "sha256": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py", + "sha256": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py", + "sha256": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py", + "sha256": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py", + "sha256": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py", + "sha256": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py", + "sha256": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py", + "sha256": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py", + "sha256": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py", + "sha256": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py", + "sha256": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py", + "sha256": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py", + "sha256": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py", + "sha256": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py", + "sha256": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py", + "sha256": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py", + "sha256": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py", + "sha256": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py", + "sha256": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA", + "sha256": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD", + "sha256": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL", + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst", + "sha256": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE", + "sha256": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt", + "sha256": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA", + "sha256": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD", + "sha256": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL", + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt", + "sha256": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE", + "sha256": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt", + "sha256": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA", + "sha256": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD", + "sha256": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL", + "sha256": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py", + "sha256": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py", + "sha256": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py", + "sha256": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py", + "sha256": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA", + "sha256": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD", + "sha256": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL", + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING", + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py", + "sha256": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py", + "sha256": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi", + "sha256": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py", + "sha256": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py", + "sha256": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py", + "sha256": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py", + "sha256": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py", + "sha256": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py", + "sha256": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py", + "sha256": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py", + "sha256": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py", + "sha256": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py", + "sha256": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py", + "sha256": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi", + "sha256": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so", + "sha256": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA", + "sha256": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD", + "sha256": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL", + "sha256": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE", + "sha256": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json", + "sha256": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA", + "sha256": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD", + "sha256": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL", + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS", + "sha256": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE", + "sha256": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt", + "sha256": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py", + "sha256": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py", + "sha256": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py", + "sha256": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py", + "sha256": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA", + "sha256": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD", + "sha256": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL", + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md", + "sha256": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py", + "sha256": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py", + "sha256": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py", + "sha256": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py", + "sha256": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py", + "sha256": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py", + "sha256": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py", + "sha256": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py", + "sha256": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py", + "sha256": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py", + "sha256": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py", + "sha256": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py", + "sha256": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py", + "sha256": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py", + "sha256": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py", + "sha256": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py", + "sha256": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py", + "sha256": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py", + "sha256": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py", + "sha256": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py", + "sha256": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py", + "sha256": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py", + "sha256": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py", + "sha256": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py", + "sha256": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py", + "sha256": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py", + "sha256": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py", + "sha256": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py", + "sha256": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py", + "sha256": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py", + "sha256": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py", + "sha256": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py", + "sha256": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py", + "sha256": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py", + "sha256": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py", + "sha256": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py", + "sha256": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA", + "sha256": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD", + "sha256": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL", + "sha256": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE", + "sha256": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py", + "sha256": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA", + "sha256": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD", + "sha256": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL", + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE", + "sha256": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py", + "sha256": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py", + "sha256": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi", + "sha256": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA", + "sha256": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD", + "sha256": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL", + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt", + "sha256": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md", + "sha256": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py", + "sha256": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py", + "sha256": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py", + "sha256": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py", + "sha256": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py", + "sha256": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py", + "sha256": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py", + "sha256": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py", + "sha256": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py", + "sha256": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py", + "sha256": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py", + "sha256": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py", + "sha256": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py", + "sha256": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py", + "sha256": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py", + "sha256": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py", + "sha256": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py", + "sha256": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py", + "sha256": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py", + "sha256": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py", + "sha256": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py", + "sha256": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py", + "sha256": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py", + "sha256": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py", + "sha256": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py", + "sha256": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py", + "sha256": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py", + "sha256": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py", + "sha256": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py", + "sha256": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py", + "sha256": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py", + "sha256": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py", + "sha256": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed", + "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py", + "sha256": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py", + "sha256": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py", + "sha256": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py", + "sha256": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py", + "sha256": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py", + "sha256": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py", + "sha256": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871" + } + ], + "original_project_review_set": [ + { + "path": "source/docs/DEBIAN-TUTORIAL.md", + "sha256": "0c114a537b6c6db8ec92be104fd2fb3e9966c4f737d78e6c5bbe9b18f648f972" + }, + { + "path": "source/docs/R2-ADDITIONS.md", + "sha256": "2c88f94aaa9b305d4c6e67b3f0b5aa840441f2d539a9f67e4e827f61d97529cc" + }, + { + "path": "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md", + "sha256": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a" + }, + { + "path": "source/docs/REQUIREMENTS.md", + "sha256": "ea476e7d4777e8b21d831f0c5f7ddaa1117629a713ac5e5fdd7b33b8c8b21fe8" + }, + { + "path": "source/docs/VALIDATION.md", + "sha256": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546" + }, + { + "path": "source/docs/framebridge.1", + "sha256": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112" + }, + { + "path": "source/evidence/BASELINE.json", + "sha256": "7b30559b3c2abd9fee5d62c924e3c273cf89002b223f751e4e5a2a46e1ad625e" + }, + { + "path": "source/expanded/framebridge_expanded/__init__.py", + "sha256": "f51137cf6d340140267a286a426731a274dd1774855c88808907c1eba8363038" + }, + { + "path": "source/expanded/framebridge_expanded/_tty_backend.py", + "sha256": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87" + }, + { + "path": "source/expanded/framebridge_expanded/adapter.py", + "sha256": "4dfc8c4c6692457407344ff4444876315e42ec1b856e65e7b3e9ea8c2185ac60" + }, + { + "path": "source/expanded/framebridge_expanded/autostart.py", + "sha256": "6f0cfc475769d85181a6e25b9d3ea1c2f17045f29788ca1d215010d0aa5a2f88" + }, + { + "path": "source/expanded/framebridge_expanded/broker.py", + "sha256": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7" + }, + { + "path": "source/expanded/framebridge_expanded/cli.py", + "sha256": "74f7bd91b6d0255cfbe48c093ae703e9ae9f5bda746cc87a1f60c7406832057d" + }, + { + "path": "source/expanded/framebridge_expanded/config.py", + "sha256": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b" + }, + { + "path": "source/expanded/framebridge_expanded/connection_background.py", + "sha256": "a594fbe3f52942eab0f620f38f0e7ca3de959db2909666b89ddd14f04603f18a" + }, + { + "path": "source/expanded/framebridge_expanded/connection_monitor.py", + "sha256": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef" + }, + { + "path": "source/expanded/framebridge_expanded/console.py", + "sha256": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08" + }, + { + "path": "source/expanded/framebridge_expanded/console_startup.py", + "sha256": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5" + }, + { + "path": "source/expanded/framebridge_expanded/console_ui/__init__.py", + "sha256": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c" + }, + { + "path": "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py", + "sha256": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943" + }, + { + "path": "source/expanded/framebridge_expanded/console_ui/framebridge_console.py", + "sha256": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929" + }, + { + "path": "source/expanded/framebridge_expanded/display_context.py", + "sha256": "e71c138e6858432285a7895d9f6ef884e77e04f3a47d22891b9a463bc31ca4df" + }, + { + "path": "source/expanded/framebridge_expanded/fleet.py", + "sha256": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed" + }, + { + "path": "source/expanded/framebridge_expanded/native_host.py", + "sha256": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce" + }, + { + "path": "source/expanded/framebridge_expanded/network.py", + "sha256": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b" + }, + { + "path": "source/expanded/framebridge_expanded/node.py", + "sha256": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/__init__.py", + "sha256": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_capture.py", + "sha256": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_control.py", + "sha256": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py", + "sha256": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_presentation.py", + "sha256": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_rpc.py", + "sha256": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c" + }, + { + "path": "source/expanded/framebridge_expanded/real_screen/screen_transport.py", + "sha256": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3" + }, + { + "path": "source/expanded/framebridge_expanded/runtime.py", + "sha256": "97b3586a240336e8fb71e34176cc819530765d8aa46201555d68b0d63d719c3a" + }, + { + "path": "source/expanded/framebridge_expanded/screen_access.py", + "sha256": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27" + }, + { + "path": "source/expanded/framebridge_expanded/tls.py", + "sha256": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6" + }, + { + "path": "source/expanded/framebridge_expanded/tty.py", + "sha256": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78" + }, + { + "path": "source/expanded/framebridge_expanded/tty_mcp.py", + "sha256": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3" + }, + { + "path": "source/expanded/framebridge_expanded/tty_sudo_control.py", + "sha256": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1" + }, + { + "path": "source/expanded/framebridge_expanded/tunnel.py", + "sha256": "df2a77d6e84e87762d4951b745e42f1d14dea0ac1df19a4534af9d8b38991bb6" + }, + { + "path": "source/expanded/framebridge_expanded/uninstall.py", + "sha256": "5ac197d93358deea8402309c15c969f8e48d6d03b7aa072c729c80c80165baf0" + }, + { + "path": "source/packaging/70-framebridge-screen.rules", + "sha256": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0" + }, + { + "path": "source/packaging/archive_sources.py", + "sha256": "32900ae2d03f77a4d2cea8b9f9d0dca256f35cab75c83400c87731dcfeb209bf" + }, + { + "path": "source/packaging/build_expanded.py", + "sha256": "9dc572eb4e9791f740f0580d5ce24ac0ac193698f23966a9a3b2b75e0c43726c" + }, + { + "path": "source/packaging/build_original.py", + "sha256": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7" + }, + { + "path": "source/packaging/framebridge", + "sha256": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142" + }, + { + "path": "source/packaging/framebridge-native-expanded", + "sha256": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a" + }, + { + "path": "source/packaging/framebridge-original", + "sha256": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1" + }, + { + "path": "source/packaging/framebridge-screen-access", + "sha256": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f" + }, + { + "path": "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff", + "sha256": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1" + }, + { + "path": "source/portable/FRONT-DOOR-PATCH.diff", + "sha256": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb" + }, + { + "path": "source/portable/README-PORTABILIDAD.md", + "sha256": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17" + }, + { + "path": "source/portable/front_door_mcp.py", + "sha256": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697" + }, + { + "path": "source/portable/gerameg_cua_frontdoor.py", + "sha256": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666" + }, + { + "path": "source/sources/original-asus/SOURCE-MANIFEST.json", + "sha256": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322" + }, + { + "path": "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready", + "sha256": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c" + }, + { + "path": "source/sources/original-asus/fbterm/bin/fbterm-run", + "sha256": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71" + }, + { + "path": "source/sources/original-asus/fbterm/bin/fbterm-shell", + "sha256": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a" + }, + { + "path": "source/sources/original-asus/fbterm/bin/fbterm_engine.py", + "sha256": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635" + }, + { + "path": "source/sources/original-asus/fbterm/bin/fbx", + "sha256": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py", + "sha256": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/framebridge_host.py", + "sha256": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py", + "sha256": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py", + "sha256": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh", + "sha256": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/open-progress-console.sh", + "sha256": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65" + }, + { + "path": "source/sources/original-asus/framebridge-native/host/open-terminal.sh", + "sha256": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c" + }, + { + "path": "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py", + "sha256": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b" + }, + { + "path": "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py", + "sha256": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e" + }, + { + "path": "source/sources/original-gerameg/MANIFEST.json", + "sha256": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6" + }, + { + "path": "source/sources/original-gerameg/cua_tty_rpc.py", + "sha256": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/__init__.py", + "sha256": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/client.py", + "sha256": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/core.py", + "sha256": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/demo.py", + "sha256": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/screen.py", + "sha256": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7" + }, + { + "path": "source/sources/original-gerameg/framebridge_cua_tty/server.py", + "sha256": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d" + }, + { + "path": "source/sources/original-gerameg/gerameg_cua_runtime.py", + "sha256": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887" + }, + { + "path": "source/sources/original-gerameg/real_tty_manager.py", + "sha256": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8" + }, + { + "path": "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py", + "sha256": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py", + "sha256": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py", + "sha256": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py", + "sha256": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py", + "sha256": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py", + "sha256": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py", + "sha256": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py", + "sha256": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py", + "sha256": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py", + "sha256": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py", + "sha256": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py", + "sha256": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py", + "sha256": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py", + "sha256": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py", + "sha256": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py", + "sha256": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py", + "sha256": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py", + "sha256": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py", + "sha256": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py", + "sha256": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py", + "sha256": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py", + "sha256": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py", + "sha256": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py", + "sha256": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py", + "sha256": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py", + "sha256": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py", + "sha256": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py", + "sha256": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py", + "sha256": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py", + "sha256": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py", + "sha256": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py", + "sha256": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py", + "sha256": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88" + }, + { + "path": "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py", + "sha256": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666" + }, + { + "path": "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run", + "sha256": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68" + }, + { + "path": "source/sources/original-geranas/MANIFEST.json", + "sha256": "a39c8364800da04ca48eb0ce4ce7781346d22f85110f110462340ff0229d3d6a" + }, + { + "path": "source/tests/test_adaptive_runtime.py", + "sha256": "415ea467ad8c95cc8bfa7185b7ec1bd0e4772c1667371f85dfb9d994acd6b777" + }, + { + "path": "source/tests/test_autostart.py", + "sha256": "d4437493caa924f9767ca482d885691667ee74ec2f912e229c70ede890ea4434" + }, + { + "path": "source/tests/test_cli.py", + "sha256": "b7ea1522a875091446107ca48f0711c1f88eb5c27c3dcea2efb337c9c286f146" + }, + { + "path": "source/tests/test_cli_extensions.py", + "sha256": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996" + }, + { + "path": "source/tests/test_connection_background.py", + "sha256": "27d7f43112bb9fefc2cf008165cca902048ca63e5970dabf2d6787c8184e9d63" + }, + { + "path": "source/tests/test_connection_monitor.py", + "sha256": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0" + }, + { + "path": "source/tests/test_console.py", + "sha256": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c" + }, + { + "path": "source/tests/test_console_nesting.py", + "sha256": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b" + }, + { + "path": "source/tests/test_console_startup.py", + "sha256": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce" + }, + { + "path": "source/tests/test_dashboard_sources.py", + "sha256": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2" + }, + { + "path": "source/tests/test_display_context.py", + "sha256": "14d0091fd8bcb1ba0eff153407c9d6df6336781d8418e10adad9c75c18f2b8bf" + }, + { + "path": "source/tests/test_fleet_broker_node.py", + "sha256": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89" + }, + { + "path": "source/tests/test_fleet_routing.py", + "sha256": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a" + }, + { + "path": "source/tests/test_legacy_broker_peers.py", + "sha256": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211" + }, + { + "path": "source/tests/test_native_dispatch.py", + "sha256": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234" + }, + { + "path": "source/tests/test_network.py", + "sha256": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181" + }, + { + "path": "source/tests/test_portable_capture_scope.py", + "sha256": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935" + }, + { + "path": "source/tests/test_public_release_privacy.py", + "sha256": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff" + }, + { + "path": "source/tests/test_runtime_ports.py", + "sha256": "47b929261a384585ed2e3de4529a91b00e4477b16a38a88cc32f2ac1c942552b" + }, + { + "path": "source/tests/test_screen_access.py", + "sha256": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579" + }, + { + "path": "source/tests/test_screen_capture.py", + "sha256": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81" + }, + { + "path": "source/tests/test_screen_control.py", + "sha256": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa" + }, + { + "path": "source/tests/test_screen_frontdoor.py", + "sha256": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876" + }, + { + "path": "source/tests/test_screen_presentation.py", + "sha256": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f" + }, + { + "path": "source/tests/test_screen_profile_routing.py", + "sha256": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81" + }, + { + "path": "source/tests/test_screen_registration.py", + "sha256": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4" + }, + { + "path": "source/tests/test_screen_rpc.py", + "sha256": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3" + }, + { + "path": "source/tests/test_screen_rpc_x11.py", + "sha256": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b" + }, + { + "path": "source/tests/test_screen_transport.py", + "sha256": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369" + }, + { + "path": "source/tests/test_screen_viewer_js.py", + "sha256": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64" + }, + { + "path": "source/tests/test_tls.py", + "sha256": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6" + }, + { + "path": "source/tests/test_tty_mcp.py", + "sha256": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081" + }, + { + "path": "source/tests/test_tty_runtime.py", + "sha256": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78" + }, + { + "path": "source/tests/test_tty_sudo.py", + "sha256": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38" + }, + { + "path": "source/tests/test_tunnel.py", + "sha256": "c9f97ce40e12be22177ae1b7c5e47073a17a984ae32932fef43557590996f15c" + }, + { + "path": "source/tests/test_uninstall.py", + "sha256": "21036a8839b1e2c730cfdadb5b184d949516525d7aa27d1a661af892573d9c73" + } + ], + "source_bytes_modified": true, + "version": "0.3.1", + "historical_baseline_files": 1070, + "historical_baseline_manifest": "../provenance/RC7-SOURCE-SNAPSHOT-SHA256.json", + "current_source_manifest": "../provenance/SOURCE-SNAPSHOT-SHA256.json", + "additional_third_party_legal_material": "third-party/; original terms retained; inclusion is not a compatibility clearance", + "previous_community_files": 1080, + "previous_community_manifest": "../provenance/COMMUNITY-PREVIEW6-SOURCE-SNAPSHOT-SHA256.json", + "separately_licensed_native_components": [ + { + "path": "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client", + "component": "OpenAI Secure MCP Tunnel client", + "license": "Apache-2.0", + "sha256": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "notice_record": "third-party/tunnel-client/README.md", + "own_source_revision_embedded": false + } + ], + "classification_change_note": "OpenAI tunnel executable removed from original-project review set and identified as separately licensed third-party material. Historical snapshot counts/manifests describe their recorded captures; refresh the final release manifest after implementation is complete." +} diff --git a/licensing/third-party/DEPENDENCY-SOURCES.json b/licensing/third-party/DEPENDENCY-SOURCES.json new file mode 100644 index 0000000..f1c9b59 --- /dev/null +++ b/licensing/third-party/DEPENDENCY-SOURCES.json @@ -0,0 +1,4445 @@ +{ + "archive": { + "bytes": 82606998, + "filename": "FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz", + "sha256": "64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a" + }, + "candidate": "0.3.1~community.20261025.1", + "certifi_source_identity": { + "cacert_sha256": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "matches_wheel_sdist_and_pinned_git": true, + "mozilla_certdata_exact_revision_identified": false, + "upstream_commit": "f4bc676bc101fe2235846e37044e8c693d6cbaf4", + "version": "2026.7.22" + }, + "dependency_resolver_or_installer_used": false, + "native_binary_identities": [ + { + "component": "cryptography 50.0.1", + "elf_dt_needed": [ + "libgcc_s.so.1", + "libc.so.6", + "ld-linux-x86-64.so.2" + ], + "object_byte_identical_to_official_wheel": true, + "object_path": "cryptography/hazmat/bindings/_rust.abi3.so", + "object_sha256": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "official_wheel_filename": "cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", + "official_wheel_sha256": "51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", + "official_wheel_url": "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl" + }, + { + "component": "pydantic_core 2.46.5", + "elf_dt_needed": [ + "libgcc_s.so.1", + "librt.so.1", + "libpthread.so.0", + "libdl.so.2", + "libc.so.6", + "ld-linux-x86-64.so.2" + ], + "object_byte_identical_to_official_wheel": true, + "object_path": "pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "official_wheel_filename": "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "official_wheel_sha256": "6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", + "official_wheel_url": "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "component": "rpds-py 2026.6.3", + "elf_dt_needed": [ + "libgcc_s.so.1", + "libpthread.so.0", + "libc.so.6", + "ld-linux-x86-64.so.2" + ], + "object_byte_identical_to_official_wheel": true, + "object_path": "rpds/rpds.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "official_wheel_filename": "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "official_wheel_sha256": "acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", + "official_wheel_url": "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "component": "cffi 2.1.1", + "libffi_source_association": { + "actual_object_contains_cffistatic_ffi_call": true, + "independently_rebuilt": false, + "libffi_version": "3.4.6", + "workflow_commit": "fd33e7700f0ebafe6f30bd5053f13327221b77a2" + }, + "object_byte_identical_to_official_wheel": true, + "object_path": "_cffi_backend.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "official_wheel_filename": "cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", + "official_wheel_sha256": "a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", + "official_wheel_url": "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" + } + ], + "native_code_built_or_executed": false, + "notices": [ + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8.txt", + "sha256": "2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8", + "source_member": "cffi-2.1.1.tar.gz/cffi-2.1.1/AUTHORS" + }, + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt", + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source_member": "cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl/cffi-2.1.1.dist-info/licenses/LICENSE" + }, + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt", + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source_member": "cffi-2.1.1.tar.gz/cffi-2.1.1/LICENSE" + }, + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195.txt", + "sha256": "67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195", + "source_member": "libffi-v3.4.6/libffi-3.4.6/LICENSE" + }, + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source_member": "certifi-2026.7.22-py3-none-any.whl/certifi-2026.7.22.dist-info/licenses/LICENSE" + }, + { + "category": "Upstream source notice", + "component": "CFFI / certifi / libffi upstream source", + "path": "notices/texts/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source_member": "certifi-2026.7.22.tar.gz/certifi-2026.7.22/LICENSE" + }, + { + "category": "OpenSSL source notice", + "component": "OpenSSL 4.0.2", + "path": "notices/texts/04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608.txt", + "sha256": "04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608", + "source_member": "openssl-4.0.2/util/perl/OpenSSL/copyright.pm" + }, + { + "category": "OpenSSL source notice", + "component": "OpenSSL 4.0.2", + "path": "notices/texts/7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a.txt", + "sha256": "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a", + "source_member": "openssl-4.0.2/LICENSE.txt" + }, + { + "category": "OpenSSL source notice", + "component": "OpenSSL 4.0.2", + "path": "notices/texts/9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181.txt", + "sha256": "9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181", + "source_member": "openssl-4.0.2/external/perl/Text-Template-1.56/LICENSE" + }, + { + "category": "OpenSSL source notice", + "component": "OpenSSL 4.0.2", + "path": "notices/texts/b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e.txt", + "sha256": "b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e", + "source_member": "openssl-4.0.2/AUTHORS.md" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "PyJWT 2.14.0", + "path": "notices/texts/797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5.txt", + "sha256": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source_member": "pyjwt-2.14.0.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "PyJWT 2.14.0", + "path": "notices/texts/925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07.txt", + "sha256": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source_member": "pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt", + "sha256": "172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af", + "source_member": "rust-src-1.96.0/COPYRIGHT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt", + "sha256": "29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/stdarch/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt", + "sha256": "29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/core_arch/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/backtrace/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt", + "sha256": "3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/compiler-builtins/libm/LICENSE.txt" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rust-src-1.96.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/backtrace/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/stdarch/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/core_arch/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt", + "sha256": "ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/compiler-builtins/LICENSE.txt" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt", + "sha256": "b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/src/llvm-project/libunwind/LICENSE.TXT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt", + "sha256": "b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1", + "source_member": "rust-src-1.96.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/portable-simd/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/portable-simd/crates/core_simd/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt", + "sha256": "eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/portable-simd/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.96.0", + "path": "notices/texts/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt", + "sha256": "eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9", + "source_member": "rust-src-1.96.0/rust-src/lib/rustlib/src/rust/library/portable-simd/crates/core_simd/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/memchr-2.7.6/COPYING" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39.txt", + "sha256": "0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/miniz_oxide-0.8.9/LICENSE-ZLIB.md" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6.txt", + "sha256": "0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/object-0.37.3/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/miniz_oxide-0.8.9/LICENSE-APACHE.md" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/memchr-2.7.6/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/libc-0.2.185/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt", + "sha256": "172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af", + "source_member": "rust-src-1.98.0/COPYRIGHT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt", + "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand-0.9.2/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt", + "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_core-0.9.3/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt", + "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_xorshift-0.4.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/adler2-2.0.1/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/hermit-abi-0.5.2/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rustc-literal-escaper-0.0.8/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/unwinding-0.2.8/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip2-1.0.3+wasi-0.2.9/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip3-0.6.0+wasi-0.3.0-rc-2026-03-15/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wit-bindgen-0.57.1/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip2-1.0.3+wasi-0.2.9/LICENSE-Apache-2.0_WITH_LLVM-exception" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip3-0.6.0+wasi-0.3.0-rc-2026-03-15/LICENSE-Apache-2.0_WITH_LLVM-exception" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wit-bindgen-0.57.1/LICENSE-Apache-2.0_WITH_LLVM-exception" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt", + "sha256": "29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt", + "sha256": "29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/core_arch/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt", + "sha256": "35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand-0.9.2/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt", + "sha256": "35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_xorshift-0.4.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/backtrace/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/cc-1.2.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/cfg-if-1.0.4/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/dlmalloc-0.2.13/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rustc-demangle-0.1.27/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt", + "sha256": "3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/compiler-builtins/libm/LICENSE.txt" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b.txt", + "sha256": "4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/miniz_oxide-0.8.9/LICENSE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt", + "sha256": "4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/shlex-1.3.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt", + "sha256": "553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/shlex-1.3.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rust-src-1.98.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/libc-0.2.185/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rustc-literal-escaper-0.0.8/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/unwinding-0.2.8/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/getopts-0.2.24/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd.txt", + "sha256": "69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/moto-rt-0.16.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt", + "sha256": "6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_core-0.9.3/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d.txt", + "sha256": "799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/miniz_oxide-0.8.9/LICENSE-MIT.md" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt", + "sha256": "7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/gimli-0.32.3/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17.txt", + "sha256": "861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/adler2-2.0.1/LICENSE-0BSD" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb.txt", + "sha256": "8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/adler2-2.0.1/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da.txt", + "sha256": "8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/intrinsic-test/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt", + "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand-0.9.2/COPYRIGHT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt", + "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_core-0.9.3/COPYRIGHT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt", + "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rand_xorshift-0.4.0/COPYRIGHT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/backtrace/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/core_arch/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/stdarch/crates/intrinsic-test/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/addr2line-0.25.1/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/cc-1.2.0/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/cfg-if-1.0.4/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/dlmalloc-0.2.13/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/getopts-0.2.24/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/gimli-0.32.3/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/hashbrown-0.17.1/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/hermit-abi-0.5.2/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/object-0.37.3/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/rustc-demangle-0.1.27/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip2-1.0.3+wasi-0.2.9/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wasip3-0.6.0+wasi-0.3.0-rc-2026-03-15/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/wit-bindgen-0.57.1/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b.txt", + "sha256": "a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/moto-rt-0.16.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt", + "sha256": "ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/compiler-builtins/LICENSE.txt" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt", + "sha256": "b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/foldhash-0.2.0/LICENSE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt", + "sha256": "b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/src/llvm-project/libunwind/LICENSE.TXT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt", + "sha256": "b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1", + "source_member": "rust-src-1.98.0/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/portable-simd/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/portable-simd/crates/core_simd/LICENSE-APACHE" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40.txt", + "sha256": "e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/addr2line-0.25.1/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt", + "sha256": "eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/portable-simd/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt", + "sha256": "eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/portable-simd/crates/core_simd/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c.txt", + "sha256": "fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/r-efi-alloc-2.1.0/AUTHORS" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt", + "sha256": "ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/hashbrown-0.17.1/LICENSE-MIT" + }, + { + "category": "Compiler/runtime source supplement notice", + "component": "Rust 1.98.0", + "path": "notices/texts/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt", + "sha256": "ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7", + "source_member": "rust-src-1.98.0/rust-src/lib/rustlib/src/rust/library/vendor/r-efi-5.3.0/AUTHORS" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "ahash 0.8.12", + "path": "notices/texts/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt", + "sha256": "0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb", + "source_member": "ahash-0.8.12/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "ahash 0.8.12", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "ahash-0.8.12/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "aho-corasick 1.1.3", + "path": "notices/texts/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "source_member": "aho-corasick-1.1.3/COPYING" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "aho-corasick 1.1.3", + "path": "notices/texts/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "source_member": "aho-corasick-1.1.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "allocator-api2 0.2.21", + "path": "notices/texts/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt", + "sha256": "20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58", + "source_member": "allocator-api2-0.2.21/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "allocator-api2 0.2.21", + "path": "notices/texts/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt", + "sha256": "36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9", + "source_member": "allocator-api2-0.2.21/LICENSE-MIT" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "annotated-types 0.8.0", + "path": "notices/texts/fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d.txt", + "sha256": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source_member": "annotated_types-0.8.0.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "anyio 4.15.1", + "path": "notices/texts/5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0.txt", + "sha256": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source_member": "anyio-4.15.1.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "archery 1.2.2", + "path": "notices/texts/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt", + "sha256": "e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f", + "source_member": "archery-1.2.2/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "asn1 0.24.1", + "path": "notices/texts/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt", + "sha256": "c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985", + "source_member": "asn1-0.24.1/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "asn1_derive 0.24.1", + "path": "notices/texts/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt", + "sha256": "c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985", + "source_member": "asn1_derive-0.24.1/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "attrs 26.1.0", + "path": "notices/texts/882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f.txt", + "sha256": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source_member": "attrs-26.1.0.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "autocfg 1.3.0", + "path": "notices/texts/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt", + "sha256": "27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac", + "source_member": "autocfg-1.3.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "autocfg 1.3.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "autocfg-1.3.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "base64 0.22.1", + "path": "notices/texts/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt", + "sha256": "0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7", + "source_member": "base64-0.22.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "base64 0.22.1", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "base64-0.22.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "base64 0.23.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "base64-0.23.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "base64 0.23.0", + "path": "notices/texts/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt", + "sha256": "ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c", + "source_member": "base64-0.23.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bitflags 2.13.1", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "bitflags-2.13.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bitflags 2.13.1", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bitflags-2.13.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bitflags 2.9.1", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "bitflags-2.9.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bitflags 2.9.1", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bitflags-2.9.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bitvec 1.0.1", + "path": "notices/texts/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt", + "sha256": "411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5", + "source_member": "bitvec-1.0.1/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bumpalo 3.19.0", + "path": "notices/texts/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt", + "sha256": "65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee", + "source_member": "bumpalo-3.19.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "bumpalo 3.19.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bumpalo-3.19.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cc 1.0.101", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cc-1.0.101/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cc 1.0.101", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cc-1.0.101/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cc 1.4.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cc-1.4.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cc 1.4.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cc-1.4.0/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "certifi 2026.7.22", + "path": "notices/texts/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source_member": "certifi-2026.7.22.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "cffi 2.1.1", + "path": "notices/texts/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt", + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source_member": "cffi-2.1.1.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cfg-if 1.0.0", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cfg-if-1.0.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cfg-if 1.0.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cfg-if-1.0.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cfg-if 1.0.4", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cfg-if-1.0.4/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "cfg-if 1.0.4", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cfg-if-1.0.4/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "click 8.5.0", + "path": "notices/texts/9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d.txt", + "sha256": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source_member": "click-8.5.0.dist-info/licenses/LICENSE.txt" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "cryptography 50.0.1", + "path": "notices/texts/3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb.txt", + "sha256": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source_member": "cryptography-50.0.1.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "cryptography 50.0.1", + "path": "notices/texts/602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb.txt", + "sha256": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source_member": "cryptography-50.0.1.dist-info/licenses/LICENSE.BSD" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "cryptography 50.0.1", + "path": "notices/texts/aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe.txt", + "sha256": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source_member": "cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "displaydoc 0.2.5", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "displaydoc-0.2.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "displaydoc 0.2.5", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "displaydoc-0.2.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "enum_dispatch 0.3.13", + "path": "notices/texts/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt", + "sha256": "1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be", + "source_member": "enum_dispatch-0.3.13/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "equivalent 1.0.2", + "path": "notices/texts/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt", + "sha256": "7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349", + "source_member": "equivalent-1.0.2/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "equivalent 1.0.2", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "equivalent-1.0.2/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "find-msvc-tools 0.1.9", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "find-msvc-tools-0.1.9/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "find-msvc-tools 0.1.9", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "find-msvc-tools-0.1.9/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "foldhash 0.2.0", + "path": "notices/texts/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt", + "sha256": "b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744", + "source_member": "foldhash-0.2.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "foreign-types 0.3.2", + "path": "notices/texts/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt", + "sha256": "333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734", + "source_member": "foreign-types-0.3.2/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "foreign-types 0.3.2", + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "foreign-types-0.3.2/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "foreign-types-shared 0.1.1", + "path": "notices/texts/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt", + "sha256": "333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734", + "source_member": "foreign-types-shared-0.1.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "foreign-types-shared 0.1.1", + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "foreign-types-shared-0.1.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "form_urlencoded 1.2.2", + "path": "notices/texts/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt", + "sha256": "20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e", + "source_member": "form_urlencoded-1.2.2/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "form_urlencoded 1.2.2", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "form_urlencoded-1.2.2/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "funty 2.0.0", + "path": "notices/texts/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt", + "sha256": "f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f", + "source_member": "funty-2.0.0/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "getrandom 0.3.3", + "path": "notices/texts/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt", + "sha256": "29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4", + "source_member": "getrandom-0.3.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "getrandom 0.3.3", + "path": "notices/texts/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt", + "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf", + "source_member": "getrandom-0.3.3/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "h11 0.16.0", + "path": "notices/texts/37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361.txt", + "sha256": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source_member": "h11-0.16.0.dist-info/licenses/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "hashbrown 0.16.1", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "hashbrown-0.16.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "hashbrown 0.16.1", + "path": "notices/texts/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt", + "sha256": "ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2", + "source_member": "hashbrown-0.16.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "heck 0.5.0", + "path": "notices/texts/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt", + "sha256": "7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0", + "source_member": "heck-0.5.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "heck 0.5.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "heck-0.5.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "hex 0.4.3", + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "hex-0.4.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "hex 0.4.3", + "path": "notices/texts/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt", + "sha256": "f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9", + "source_member": "hex-0.4.3/LICENSE-MIT" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "httpcore 1.0.9", + "path": "notices/texts/fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5.txt", + "sha256": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source_member": "httpcore-1.0.9.dist-info/licenses/LICENSE.md" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "httpx 0.28.1", + "path": "notices/texts/4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d.txt", + "sha256": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source_member": "httpx-0.28.1.dist-info/licenses/LICENSE.md" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "httpx-sse 0.4.3", + "path": "notices/texts/beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901.txt", + "sha256": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source_member": "httpx_sse-0.4.3.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_collections 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_collections-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_locid 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_locid_transform 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid_transform-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_locid_transform_data 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid_transform_data-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_normalizer 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_normalizer-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_normalizer_data 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_normalizer_data-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_properties 1.5.1", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_properties-1.5.1/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_properties_data 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_properties_data-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_provider 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_provider-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "icu_provider_macros 1.5.0", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_provider_macros-1.5.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "idna 1.1.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "idna-1.1.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "idna 1.1.0", + "path": "notices/texts/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "idna-1.1.0/LICENSE-MIT" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "idna 3.20", + "path": "notices/texts/1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04.txt", + "sha256": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source_member": "idna-3.20.dist-info/licenses/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "idna_adapter 1.2.0", + "path": "notices/texts/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt", + "sha256": "8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726", + "source_member": "idna_adapter-1.2.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "idna_adapter 1.2.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "idna_adapter-1.2.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "itoa 1.0.11", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "itoa-1.0.11/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "itoa 1.0.11", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "itoa-1.0.11/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "itoa 1.0.18", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "itoa-1.0.18/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "itoa 1.0.18", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "itoa-1.0.18/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "jiter 0.14.0", + "path": "notices/texts/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt", + "sha256": "7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e", + "source_member": "jiter-0.14.0/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "js-sys 0.3.77", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "js-sys-0.3.77/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "js-sys 0.3.77", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "js-sys-0.3.77/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "jsonschema 4.26.0", + "path": "notices/texts/4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439.txt", + "sha256": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source_member": "jsonschema-4.26.0.dist-info/licenses/COPYING" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "jsonschema-specifications 2025.9.1", + "path": "notices/texts/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt", + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source_member": "jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-float 1.0.5", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-parse-float-1.0.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-float 1.0.5", + "path": "notices/texts/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-parse-float-1.0.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-float 1.0.5", + "path": "notices/texts/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt", + "sha256": "99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe", + "source_member": "lexical-parse-float-1.0.5/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-integer 1.0.5", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-parse-integer-1.0.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-integer 1.0.5", + "path": "notices/texts/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-parse-integer-1.0.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-parse-integer 1.0.5", + "path": "notices/texts/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt", + "sha256": "99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe", + "source_member": "lexical-parse-integer-1.0.5/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-util 1.0.6", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-util-1.0.6/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lexical-util 1.0.6", + "path": "notices/texts/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-util-1.0.6/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.177", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.177/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.177", + "path": "notices/texts/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt", + "sha256": "a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1", + "source_member": "libc-0.2.177/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.185", + "path": "notices/texts/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", + "source_member": "libc-0.2.185/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.185", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.185/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.189", + "path": "notices/texts/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", + "source_member": "libc-0.2.189/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "libc 0.2.189", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.189/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "litemap 0.7.3", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "litemap-0.7.3/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "log 0.4.27", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "log-0.4.27/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "log 0.4.27", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "log-0.4.27/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "lru 0.16.3", + "path": "notices/texts/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt", + "sha256": "061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e", + "source_member": "lru-0.16.3/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "mcp 1.26.0", + "path": "notices/texts/5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d.txt", + "sha256": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source_member": "mcp-1.26.0.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "memchr 2.7.4", + "path": "notices/texts/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "source_member": "memchr-2.7.4/COPYING" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "memchr 2.7.4", + "path": "notices/texts/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "source_member": "memchr-2.7.4/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-bigint 0.4.6", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-bigint-0.4.6/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-bigint 0.4.6", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-bigint-0.4.6/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-integer 0.1.46", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-integer-0.1.46/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-integer 0.1.46", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-integer-0.1.46/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-traits 0.2.19", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-traits-0.2.19/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "num-traits 0.2.19", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-traits-0.2.19/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "once_cell 1.21.3", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "once_cell-1.21.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "once_cell 1.21.3", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "once_cell-1.21.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "once_cell 1.21.4", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "once_cell-1.21.4/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "once_cell 1.21.4", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "once_cell-1.21.4/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "openssl 0.10.81", + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "openssl-0.10.81/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "openssl 0.10.81", + "path": "notices/texts/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt", + "sha256": "f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941", + "source_member": "openssl-0.10.81/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "openssl-macros 0.1.1", + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "openssl-macros-0.1.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "openssl-macros 0.1.1", + "path": "notices/texts/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt", + "sha256": "ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30", + "source_member": "openssl-macros-0.1.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "openssl-sys 0.9.117", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "openssl-sys-0.9.117/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pem 4.0.0", + "path": "notices/texts/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt", + "sha256": "b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf", + "source_member": "pem-4.0.0/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "percent-encoding 2.3.2", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "percent-encoding-2.3.2/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "percent-encoding 2.3.2", + "path": "notices/texts/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "percent-encoding-2.3.2/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pkg-config 0.3.33", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "pkg-config-0.3.33/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pkg-config 0.3.33", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "pkg-config-0.3.33/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "portable-atomic 1.14.0", + "path": "notices/texts/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", + "source_member": "portable-atomic-1.14.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "portable-atomic 1.14.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "portable-atomic-1.14.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "portable-atomic 1.6.0", + "path": "notices/texts/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", + "source_member": "portable-atomic-1.6.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "portable-atomic 1.6.0", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "portable-atomic-1.6.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.103", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.103/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.103", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.103/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.107", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.107/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.107", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.107/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.86", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.86/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "proc-macro2 1.0.86", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.86/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "pycparser 3.0", + "path": "notices/texts/0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1.txt", + "sha256": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source_member": "pycparser-3.0.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "pydantic 2.13.5", + "path": "notices/texts/a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3.txt", + "sha256": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source_member": "pydantic-2.13.5.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "pydantic-settings 2.15.0", + "path": "notices/texts/eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998.txt", + "sha256": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source_member": "pydantic_settings-2.15.0.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "pydantic_core 2.46.5", + "path": "notices/texts/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt", + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source_member": "pydantic_core-2.46.5.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.28.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.28.3/pyo3-runtime/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.28.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.28.3/pyo3-runtime/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.29.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.29.0/pyo3-runtime/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.29.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.29.0/pyo3-runtime/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-build-config 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-build-config-0.28.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-build-config 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-build-config-0.28.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-build-config 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-build-config-0.29.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-build-config 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-build-config-0.29.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-ffi 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-ffi-0.28.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-ffi 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-ffi-0.28.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-ffi 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-ffi-0.29.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-ffi 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-ffi-0.29.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-0.28.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-0.28.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-0.29.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-0.29.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros-backend 0.28.3", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-backend-0.28.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros-backend 0.28.3", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-backend-0.28.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros-backend 0.29.0", + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-backend-0.29.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "pyo3-macros-backend 0.29.0", + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-backend-0.29.0/LICENSE-MIT" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "python-dotenv 1.2.3", + "path": "notices/texts/80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9.txt", + "sha256": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source_member": "python_dotenv-1.2.3.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "python-multipart 0.0.32", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "python_multipart-0.0.32.dist-info/licenses/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "python3-dll-a 0.2.14", + "path": "notices/texts/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt", + "sha256": "c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763", + "source_member": "python3-dll-a-0.2.14/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.42", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.42/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.42", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.42/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.44", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.44/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.44", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.44/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.47", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.47/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "quote 1.0.47", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.47/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "r-efi 5.2.0", + "path": "notices/texts/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt", + "sha256": "c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d", + "source_member": "r-efi-5.2.0/AUTHORS" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "radium 0.7.0", + "path": "notices/texts/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt", + "sha256": "13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02", + "source_member": "radium-0.7.0/LICENSE.txt" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "referencing 0.37.0", + "path": "notices/texts/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt", + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source_member": "referencing-0.37.0.dist-info/licenses/COPYING" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex 1.12.3", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-1.12.3/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex 1.12.3", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-1.12.3/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex-automata 0.4.13", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-automata-0.4.13/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex-automata 0.4.13", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-automata-0.4.13/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex-syntax 0.8.5", + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-syntax-0.8.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex-syntax 0.8.5", + "path": "notices/texts/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt", + "sha256": "74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3", + "source_member": "regex-syntax-0.8.5/src/unicode_tables/LICENSE-UNICODE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "regex-syntax 0.8.5", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-syntax-0.8.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "rpds 1.2.1", + "path": "notices/texts/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt", + "sha256": "e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f", + "source_member": "rpds-1.2.1/LICENSE.md" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "rpds-py 2026.6.3", + "path": "notices/texts/314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909.txt", + "sha256": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source_member": "rpds_py-2026.6.3.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "rustversion 1.0.17", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rustversion-1.0.17/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "rustversion 1.0.17", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rustversion-1.0.17/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "self_cell 1.3.0", + "path": "notices/texts/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "source_member": "self_cell-1.3.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "self_cell 1.3.0", + "path": "notices/texts/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt", + "sha256": "f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505", + "source_member": "self_cell-1.3.0/LICENSE-GPLv2" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde 1.0.228", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde-1.0.228/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde 1.0.228", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde-1.0.228/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_core 1.0.228", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_core-1.0.228/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_core 1.0.228", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_core-1.0.228/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_derive 1.0.228", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_derive-1.0.228/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_derive 1.0.228", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_derive-1.0.228/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_json 1.0.149", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_json-1.0.149/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "serde_json 1.0.149", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_json-1.0.149/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "shlex 2.0.1", + "path": "notices/texts/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt", + "sha256": "4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1", + "source_member": "shlex-2.0.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "shlex 2.0.1", + "path": "notices/texts/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt", + "sha256": "553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583", + "source_member": "shlex-2.0.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "smallvec 1.15.1", + "path": "notices/texts/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt", + "sha256": "0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9", + "source_member": "smallvec-1.15.1/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "smallvec 1.15.1", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "smallvec-1.15.1/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "speedate 0.17.0", + "path": "notices/texts/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt", + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source_member": "speedate-0.17.0/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "sse-starlette 3.4.11", + "path": "notices/texts/80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e.txt", + "sha256": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source_member": "sse_starlette-3.4.11.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "sse-starlette 3.4.11", + "path": "notices/texts/c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a.txt", + "sha256": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source_member": "sse_starlette-3.4.11.dist-info/licenses/AUTHORS" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "stable_deref_trait 1.2.0", + "path": "notices/texts/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt", + "sha256": "5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7", + "source_member": "stable_deref_trait-1.2.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "stable_deref_trait 1.2.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "stable_deref_trait-1.2.0/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "starlette 1.7.0", + "path": "notices/texts/dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf.txt", + "sha256": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source_member": "starlette-1.7.0.dist-info/licenses/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "static_assertions 1.1.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "static_assertions-1.1.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "static_assertions 1.1.0", + "path": "notices/texts/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt", + "sha256": "ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061", + "source_member": "static_assertions-1.1.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "strum 0.27.2", + "path": "notices/texts/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt", + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "source_member": "strum-0.27.2/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "strum_macros 0.27.2", + "path": "notices/texts/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt", + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "source_member": "strum_macros-0.27.2/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.111", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.111/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.111", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.111/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.119", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.119/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.119", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.119/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.82", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.82/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "syn 2.0.82", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.82/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "synstructure 0.13.1", + "path": "notices/texts/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt", + "sha256": "219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59", + "source_member": "synstructure-0.13.1/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "tap 1.0.1", + "path": "notices/texts/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt", + "sha256": "8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a", + "source_member": "tap-1.0.1/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "target-lexicon 0.13.3", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.3/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "target-lexicon 0.13.4", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "target-lexicon 0.13.5", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.5/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "tinystr 0.7.6", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "tinystr-0.7.6/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "triomphe 0.1.15", + "path": "notices/texts/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt", + "sha256": "15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c", + "source_member": "triomphe-0.1.15/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "triomphe 0.1.15", + "path": "notices/texts/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt", + "sha256": "a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c", + "source_member": "triomphe-0.1.15/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "typing-inspection 0.4.4", + "path": "notices/texts/804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3.txt", + "sha256": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source_member": "typing_inspection-0.4.4.dist-info/licenses/LICENSE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "typing_extensions 4.16.0", + "path": "notices/texts/3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf.txt", + "sha256": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source_member": "typing_extensions-4.16.0.dist-info/licenses/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.12", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.12/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.12", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.12/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.12", + "path": "notices/texts/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt", + "sha256": "68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844", + "source_member": "unicode-ident-1.0.12/LICENSE-UNICODE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.22", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.22/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.22", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.22/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.22", + "path": "notices/texts/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt", + "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1", + "source_member": "unicode-ident-1.0.22/LICENSE-UNICODE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.24", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.24/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.24", + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.24/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "unicode-ident 1.0.24", + "path": "notices/texts/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt", + "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1", + "source_member": "unicode-ident-1.0.24/LICENSE-UNICODE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "url 2.5.8", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "url-2.5.8/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "url 2.5.8", + "path": "notices/texts/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "url-2.5.8/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf16_iter 1.0.5", + "path": "notices/texts/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "utf16_iter-1.0.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf16_iter 1.0.5", + "path": "notices/texts/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt", + "sha256": "b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426", + "source_member": "utf16_iter-1.0.5/COPYRIGHT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf16_iter 1.0.5", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "utf16_iter-1.0.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf8_iter 1.0.4", + "path": "notices/texts/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "utf8_iter-1.0.4/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf8_iter 1.0.4", + "path": "notices/texts/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt", + "sha256": "c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c", + "source_member": "utf8_iter-1.0.4/COPYRIGHT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "utf8_iter 1.0.4", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "utf8_iter-1.0.4/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "uuid 1.23.0", + "path": "notices/texts/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt", + "sha256": "436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d", + "source_member": "uuid-1.23.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "uuid 1.23.0", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "uuid-1.23.0/LICENSE-APACHE" + }, + { + "category": "Existing bundled Python distribution notice", + "component": "uvicorn 0.53.0", + "path": "notices/texts/efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371.txt", + "sha256": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source_member": "uvicorn-0.53.0.dist-info/licenses/LICENSE.md" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "vcpkg 0.2.15", + "path": "notices/texts/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt", + "sha256": "016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc", + "source_member": "vcpkg-0.2.15/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "vcpkg 0.2.15", + "path": "notices/texts/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt", + "sha256": "60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3", + "source_member": "vcpkg-0.2.15/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "version_check 0.9.5", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "version_check-0.9.5/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "version_check 0.9.5", + "path": "notices/texts/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt", + "sha256": "b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e", + "source_member": "version_check-0.9.5/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasi 0.14.2+wasi-0.2.4", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasi 0.14.2+wasi-0.2.4", + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasi 0.14.2+wasi-0.2.4", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen 0.2.100", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-0.2.100/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen 0.2.100", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-0.2.100/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-backend 0.2.100", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-backend-0.2.100/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-backend 0.2.100", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-backend-0.2.100/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-macro 0.2.100", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-macro-0.2.100/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-macro 0.2.100", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-macro-0.2.100/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-macro-support 0.2.100", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-macro-support-0.2.100/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-macro-support 0.2.100", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-macro-support-0.2.100/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-shared 0.2.100", + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-shared-0.2.100/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wasm-bindgen-shared 0.2.100", + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-shared-0.2.100/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "write16 1.0.0", + "path": "notices/texts/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt", + "sha256": "3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0", + "source_member": "write16-1.0.0/COPYRIGHT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "write16 1.0.0", + "path": "notices/texts/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "write16-1.0.0/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "write16 1.0.0", + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "write16-1.0.0/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "writeable 0.5.5", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "writeable-0.5.5/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "wyz 0.5.1", + "path": "notices/texts/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt", + "sha256": "411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5", + "source_member": "wyz-0.5.1/LICENSE.txt" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "yoke 0.7.4", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "yoke-0.7.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "yoke-derive 0.7.4", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "yoke-derive-0.7.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy 0.8.25", + "path": "notices/texts/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt", + "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df", + "source_member": "zerocopy-0.8.25/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy 0.8.25", + "path": "notices/texts/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt", + "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32", + "source_member": "zerocopy-0.8.25/LICENSE-BSD" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy 0.8.25", + "path": "notices/texts/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt", + "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3", + "source_member": "zerocopy-0.8.25/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy-derive 0.8.25", + "path": "notices/texts/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt", + "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df", + "source_member": "zerocopy-derive-0.8.25/LICENSE-MIT" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy-derive 0.8.25", + "path": "notices/texts/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt", + "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32", + "source_member": "zerocopy-derive-0.8.25/LICENSE-BSD" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerocopy-derive 0.8.25", + "path": "notices/texts/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt", + "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3", + "source_member": "zerocopy-derive-0.8.25/LICENSE-APACHE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerofrom 0.1.4", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerofrom-0.1.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerofrom-derive 0.1.4", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerofrom-derive-0.1.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerovec 0.10.4", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerovec-0.10.4/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zerovec-derive 0.10.3", + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerovec-derive-0.10.3/LICENSE" + }, + { + "category": "registry-crate candidate; build/runtime role not conclusively classified", + "component": "zmij 1.0.6", + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "zmij-1.0.6/LICENSE-MIT" + } + ], + "python_distribution_inventory": [ + { + "declared_license_expression": "MIT", + "name": "annotated-types", + "version": "0.8.0" + }, + { + "declared_license_expression": "MIT", + "name": "anyio", + "version": "4.15.1" + }, + { + "declared_license_expression": "MIT", + "name": "attrs", + "version": "26.1.0" + }, + { + "declared_license_expression": "MPL-2.0", + "name": "certifi", + "version": "2026.7.22" + }, + { + "declared_license_expression": "MIT-0", + "name": "cffi", + "version": "2.1.1" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "click", + "version": "8.5.0" + }, + { + "declared_license_expression": "Apache-2.0 OR BSD-3-Clause", + "name": "cryptography", + "version": "50.0.1" + }, + { + "declared_license_expression": "MIT", + "name": "h11", + "version": "0.16.0" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "httpcore", + "version": "1.0.9" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "httpx", + "version": "0.28.1" + }, + { + "declared_license_expression": "MIT", + "name": "httpx-sse", + "version": "0.4.3" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "idna", + "version": "3.20" + }, + { + "declared_license_expression": "MIT", + "name": "jsonschema", + "version": "4.26.0" + }, + { + "declared_license_expression": "MIT", + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "declared_license_expression": "MIT", + "name": "mcp", + "version": "1.26.0" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "pycparser", + "version": "3.0" + }, + { + "declared_license_expression": "MIT", + "name": "pydantic", + "version": "2.13.5" + }, + { + "declared_license_expression": "MIT", + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "declared_license_expression": "MIT", + "name": "pydantic-settings", + "version": "2.15.0" + }, + { + "declared_license_expression": "MIT", + "name": "PyJWT", + "version": "2.14.0" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "python-dotenv", + "version": "1.2.3" + }, + { + "declared_license_expression": "Apache-2.0", + "name": "python-multipart", + "version": "0.0.32" + }, + { + "declared_license_expression": "MIT", + "name": "referencing", + "version": "0.37.0" + }, + { + "declared_license_expression": "MIT", + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "sse-starlette", + "version": "3.4.11" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "starlette", + "version": "1.7.0" + }, + { + "declared_license_expression": "PSF-2.0", + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "declared_license_expression": "MIT", + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "declared_license_expression": "BSD-3-Clause", + "name": "uvicorn", + "version": "0.53.0" + } + ], + "registry_crate_count": 143, + "residual_review_items": [ + "Four shipped native objects match exact official wheels. This does not constitute an independently reproduced native build or an attestation verification of the complete wheel toolchain.", + "The crate catalog is deduplicated by name/version. SBOM presence is not proof that every entry is embedded at runtime; direct build dependency flags are classification aids only.", + "Original upstream license texts and all declared alternatives are preserved. AND/OR expressions and which obligations apply to the delivered combination remain a release-review decision; no upstream license is replaced by the project GPL choice.", + "The certifi CA bundle is byte-identical in the shipped payload, official wheel, sdist and pinned upstream Git source. Its generator calls mkcert.org; the original Mozilla certdata revision and a reproducible generation chain were not identified. Preferred-source/legal classification remains an explicit review item.", + "This supplement must accompany an identifiable distribution and its matching FrameBridge source. Private collection alone is not a public source offer or a complete legal compliance certification." + ], + "schema_version": 1, + "source_artifacts": [ + { + "bytes": 240216, + "component": "certifi 2026.7.22 CA bundle", + "path": "sources/certifi/cacert.pem", + "role": "exact unmodified upstream certificate bundle source/data", + "sha256": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "tls_verified_at_collection": true, + "upstream_url": "https://raw.githubusercontent.com/certifi/python-certifi/f4bc676bc101fe2235846e37044e8c693d6cbaf4/certifi/cacert.pem" + }, + { + "bytes": 43413, + "component": "ahash 0.8.12", + "path": "sources/crates/ahash-0.8.12.crate", + "role": "source", + "sha256": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/ahash/ahash-0.8.12.crate" + }, + { + "bytes": 183311, + "component": "aho-corasick 1.1.3", + "path": "sources/crates/aho-corasick-1.1.3.crate", + "role": "source", + "sha256": "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/aho-corasick/aho-corasick-1.1.3.crate" + }, + { + "bytes": 63622, + "component": "allocator-api2 0.2.21", + "path": "sources/crates/allocator-api2-0.2.21.crate", + "role": "source", + "sha256": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/allocator-api2/allocator-api2-0.2.21.crate" + }, + { + "bytes": 16228, + "component": "archery 1.2.2", + "path": "sources/crates/archery-1.2.2.crate", + "role": "source", + "sha256": "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/archery/archery-1.2.2.crate" + }, + { + "bytes": 50502, + "component": "asn1 0.24.1", + "path": "sources/crates/asn1-0.24.1.crate", + "role": "source", + "sha256": "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/asn1/asn1-0.24.1.crate" + }, + { + "bytes": 9534, + "component": "asn1_derive 0.24.1", + "path": "sources/crates/asn1_derive-0.24.1.crate", + "role": "source", + "sha256": "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/asn1_derive/asn1_derive-0.24.1.crate" + }, + { + "bytes": 16524, + "component": "autocfg 1.3.0", + "path": "sources/crates/autocfg-1.3.0.crate", + "role": "source", + "sha256": "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/autocfg/autocfg-1.3.0.crate" + }, + { + "bytes": 81597, + "component": "base64 0.22.1", + "path": "sources/crates/base64-0.22.1.crate", + "role": "source", + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/base64/base64-0.22.1.crate" + }, + { + "bytes": 93578, + "component": "base64 0.23.0", + "path": "sources/crates/base64-0.23.0.crate", + "role": "source", + "sha256": "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/base64/base64-0.23.0.crate" + }, + { + "bytes": 51395, + "component": "bitflags 2.13.1", + "path": "sources/crates/bitflags-2.13.1.crate", + "role": "source", + "sha256": "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/bitflags/bitflags-2.13.1.crate" + }, + { + "bytes": 47913, + "component": "bitflags 2.9.1", + "path": "sources/crates/bitflags-2.9.1.crate", + "role": "source", + "sha256": "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/bitflags/bitflags-2.9.1.crate" + }, + { + "bytes": 224375, + "component": "bitvec 1.0.1", + "path": "sources/crates/bitvec-1.0.1.crate", + "role": "source", + "sha256": "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/bitvec/bitvec-1.0.1.crate" + }, + { + "bytes": 96414, + "component": "bumpalo 3.19.0", + "path": "sources/crates/bumpalo-3.19.0.crate", + "role": "source", + "sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.0.crate" + }, + { + "bytes": 79446, + "component": "cc 1.0.101", + "path": "sources/crates/cc-1.0.101.crate", + "role": "source", + "sha256": "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/cc/cc-1.0.101.crate" + }, + { + "bytes": 97878, + "component": "cc 1.4.0", + "path": "sources/crates/cc-1.4.0.crate", + "role": "source", + "sha256": "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/cc/cc-1.4.0.crate" + }, + { + "bytes": 7934, + "component": "cfg-if 1.0.0", + "path": "sources/crates/cfg-if-1.0.0.crate", + "role": "source", + "sha256": "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/cfg-if/cfg-if-1.0.0.crate" + }, + { + "bytes": 9360, + "component": "cfg-if 1.0.4", + "path": "sources/crates/cfg-if-1.0.4.crate", + "role": "source", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/cfg-if/cfg-if-1.0.4.crate" + }, + { + "bytes": 24219, + "component": "displaydoc 0.2.5", + "path": "sources/crates/displaydoc-0.2.5.crate", + "role": "source", + "sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.5.crate" + }, + { + "bytes": 29876, + "component": "enum_dispatch 0.3.13", + "path": "sources/crates/enum_dispatch-0.3.13.crate", + "role": "source", + "sha256": "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/enum_dispatch/enum_dispatch-0.3.13.crate" + }, + { + "bytes": 7419, + "component": "equivalent 1.0.2", + "path": "sources/crates/equivalent-1.0.2.crate", + "role": "source", + "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/equivalent/equivalent-1.0.2.crate" + }, + { + "bytes": 31493, + "component": "find-msvc-tools 0.1.9", + "path": "sources/crates/find-msvc-tools-0.1.9.crate", + "role": "source", + "sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.9.crate" + }, + { + "bytes": 23329, + "component": "foldhash 0.2.0", + "path": "sources/crates/foldhash-0.2.0.crate", + "role": "source", + "sha256": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/foldhash/foldhash-0.2.0.crate" + }, + { + "bytes": 7504, + "component": "foreign-types 0.3.2", + "path": "sources/crates/foreign-types-0.3.2.crate", + "role": "source", + "sha256": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/foreign-types/foreign-types-0.3.2.crate" + }, + { + "bytes": 5672, + "component": "foreign-types-shared 0.1.1", + "path": "sources/crates/foreign-types-shared-0.1.1.crate", + "role": "source", + "sha256": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/foreign-types-shared/foreign-types-shared-0.1.1.crate" + }, + { + "bytes": 9347, + "component": "form_urlencoded 1.2.2", + "path": "sources/crates/form_urlencoded-1.2.2.crate", + "role": "source", + "sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/form_urlencoded/form_urlencoded-1.2.2.crate" + }, + { + "bytes": 13160, + "component": "funty 2.0.0", + "path": "sources/crates/funty-2.0.0.crate", + "role": "source", + "sha256": "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/funty/funty-2.0.0.crate" + }, + { + "bytes": 49493, + "component": "getrandom 0.3.3", + "path": "sources/crates/getrandom-0.3.3.crate", + "role": "source", + "sha256": "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/getrandom/getrandom-0.3.3.crate" + }, + { + "bytes": 147785, + "component": "hashbrown 0.16.1", + "path": "sources/crates/hashbrown-0.16.1.crate", + "role": "source", + "sha256": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/hashbrown/hashbrown-0.16.1.crate" + }, + { + "bytes": 11517, + "component": "heck 0.5.0", + "path": "sources/crates/heck-0.5.0.crate", + "role": "source", + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/heck/heck-0.5.0.crate" + }, + { + "bytes": 13299, + "component": "hex 0.4.3", + "path": "sources/crates/hex-0.4.3.crate", + "role": "source", + "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/hex/hex-0.4.3.crate" + }, + { + "bytes": 82762, + "component": "icu_collections 1.5.0", + "path": "sources/crates/icu_collections-1.5.0.crate", + "role": "source", + "sha256": "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_collections/icu_collections-1.5.0.crate" + }, + { + "bytes": 55131, + "component": "icu_locid 1.5.0", + "path": "sources/crates/icu_locid-1.5.0.crate", + "role": "source", + "sha256": "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_locid/icu_locid-1.5.0.crate" + }, + { + "bytes": 29094, + "component": "icu_locid_transform 1.5.0", + "path": "sources/crates/icu_locid_transform-1.5.0.crate", + "role": "source", + "sha256": "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_locid_transform/icu_locid_transform-1.5.0.crate" + }, + { + "bytes": 44727, + "component": "icu_locid_transform_data 1.5.0", + "path": "sources/crates/icu_locid_transform_data-1.5.0.crate", + "role": "source", + "sha256": "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_locid_transform_data/icu_locid_transform_data-1.5.0.crate" + }, + { + "bytes": 53113, + "component": "icu_normalizer 1.5.0", + "path": "sources/crates/icu_normalizer-1.5.0.crate", + "role": "source", + "sha256": "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_normalizer/icu_normalizer-1.5.0.crate" + }, + { + "bytes": 50561, + "component": "icu_normalizer_data 1.5.0", + "path": "sources/crates/icu_normalizer_data-1.5.0.crate", + "role": "source", + "sha256": "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_normalizer_data/icu_normalizer_data-1.5.0.crate" + }, + { + "bytes": 64479, + "component": "icu_properties 1.5.1", + "path": "sources/crates/icu_properties-1.5.1.crate", + "role": "source", + "sha256": "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_properties/icu_properties-1.5.1.crate" + }, + { + "bytes": 227993, + "component": "icu_properties_data 1.5.0", + "path": "sources/crates/icu_properties_data-1.5.0.crate", + "role": "source", + "sha256": "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_properties_data/icu_properties_data-1.5.0.crate" + }, + { + "bytes": 52722, + "component": "icu_provider 1.5.0", + "path": "sources/crates/icu_provider-1.5.0.crate", + "role": "source", + "sha256": "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_provider/icu_provider-1.5.0.crate" + }, + { + "bytes": 6436, + "component": "icu_provider_macros 1.5.0", + "path": "sources/crates/icu_provider_macros-1.5.0.crate", + "role": "source", + "sha256": "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/icu_provider_macros/icu_provider_macros-1.5.0.crate" + }, + { + "bytes": 148747, + "component": "idna 1.1.0", + "path": "sources/crates/idna-1.1.0.crate", + "role": "source", + "sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/idna/idna-1.1.0.crate" + }, + { + "bytes": 8206, + "component": "idna_adapter 1.2.0", + "path": "sources/crates/idna_adapter-1.2.0.crate", + "role": "source", + "sha256": "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/idna_adapter/idna_adapter-1.2.0.crate" + }, + { + "bytes": 10563, + "component": "itoa 1.0.11", + "path": "sources/crates/itoa-1.0.11.crate", + "role": "source", + "sha256": "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/itoa/itoa-1.0.11.crate" + }, + { + "bytes": 15935, + "component": "itoa 1.0.18", + "path": "sources/crates/itoa-1.0.18.crate", + "role": "source", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/itoa/itoa-1.0.18.crate" + }, + { + "bytes": 155564, + "component": "jiter 0.14.0", + "path": "sources/crates/jiter-0.14.0.crate", + "role": "source", + "sha256": "b6f3b5d3f84b36f4ad09fd1da896d23d9852a1aa86556578dd0289f43dce311d", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/jiter/jiter-0.14.0.crate" + }, + { + "bytes": 55538, + "component": "js-sys 0.3.77", + "path": "sources/crates/js-sys-0.3.77.crate", + "role": "source", + "sha256": "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/js-sys/js-sys-0.3.77.crate" + }, + { + "bytes": 186731, + "component": "lexical-parse-float 1.0.5", + "path": "sources/crates/lexical-parse-float-1.0.5.crate", + "role": "source", + "sha256": "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/lexical-parse-float/lexical-parse-float-1.0.5.crate" + }, + { + "bytes": 36912, + "component": "lexical-parse-integer 1.0.5", + "path": "sources/crates/lexical-parse-integer-1.0.5.crate", + "role": "source", + "sha256": "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/lexical-parse-integer/lexical-parse-integer-1.0.5.crate" + }, + { + "bytes": 106117, + "component": "lexical-util 1.0.6", + "path": "sources/crates/lexical-util-1.0.6.crate", + "role": "source", + "sha256": "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/lexical-util/lexical-util-1.0.6.crate" + }, + { + "bytes": 792045, + "component": "libc 0.2.177", + "path": "sources/crates/libc-0.2.177.crate", + "role": "source", + "sha256": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/libc/libc-0.2.177.crate" + }, + { + "bytes": 821775, + "component": "libc 0.2.185", + "path": "sources/crates/libc-0.2.185.crate", + "role": "source", + "sha256": "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/libc/libc-0.2.185.crate" + }, + { + "bytes": 851502, + "component": "libc 0.2.189", + "path": "sources/crates/libc-0.2.189.crate", + "role": "source", + "sha256": "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/libc/libc-0.2.189.crate" + }, + { + "bytes": 28728, + "component": "litemap 0.7.3", + "path": "sources/crates/litemap-0.7.3.crate", + "role": "source", + "sha256": "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/litemap/litemap-0.7.3.crate" + }, + { + "bytes": 48120, + "component": "log 0.4.27", + "path": "sources/crates/log-0.4.27.crate", + "role": "source", + "sha256": "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/log/log-0.4.27.crate" + }, + { + "bytes": 16743, + "component": "lru 0.16.3", + "path": "sources/crates/lru-0.16.3.crate", + "role": "source", + "sha256": "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/lru/lru-0.16.3.crate" + }, + { + "bytes": 96670, + "component": "memchr 2.7.4", + "path": "sources/crates/memchr-2.7.4.crate", + "role": "source", + "sha256": "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/memchr/memchr-2.7.4.crate" + }, + { + "bytes": 102801, + "component": "num-bigint 0.4.6", + "path": "sources/crates/num-bigint-0.4.6.crate", + "role": "source", + "sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/num-bigint/num-bigint-0.4.6.crate" + }, + { + "bytes": 22331, + "component": "num-integer 0.1.46", + "path": "sources/crates/num-integer-0.1.46.crate", + "role": "source", + "sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/num-integer/num-integer-0.1.46.crate" + }, + { + "bytes": 51631, + "component": "num-traits 0.2.19", + "path": "sources/crates/num-traits-0.2.19.crate", + "role": "source", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/num-traits/num-traits-0.2.19.crate" + }, + { + "bytes": 34534, + "component": "once_cell 1.21.3", + "path": "sources/crates/once_cell-1.21.3.crate", + "role": "source", + "sha256": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/once_cell/once_cell-1.21.3.crate" + }, + { + "bytes": 35010, + "component": "once_cell 1.21.4", + "path": "sources/crates/once_cell-1.21.4.crate", + "role": "source", + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/once_cell/once_cell-1.21.4.crate" + }, + { + "bytes": 311187, + "component": "openssl 0.10.81", + "path": "sources/crates/openssl-0.10.81.crate", + "role": "source", + "sha256": "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/openssl/openssl-0.10.81.crate" + }, + { + "bytes": 5601, + "component": "openssl-macros 0.1.1", + "path": "sources/crates/openssl-macros-0.1.1.crate", + "role": "source", + "sha256": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/openssl-macros/openssl-macros-0.1.1.crate" + }, + { + "bytes": 76045, + "component": "openssl-sys 0.9.117", + "path": "sources/crates/openssl-sys-0.9.117.crate", + "role": "source", + "sha256": "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/openssl-sys/openssl-sys-0.9.117.crate" + }, + { + "bytes": 18770, + "component": "pem 4.0.0", + "path": "sources/crates/pem-4.0.0.crate", + "role": "source", + "sha256": "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pem/pem-4.0.0.crate" + }, + { + "bytes": 11583, + "component": "percent-encoding 2.3.2", + "path": "sources/crates/percent-encoding-2.3.2.crate", + "role": "source", + "sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/percent-encoding/percent-encoding-2.3.2.crate" + }, + { + "bytes": 21590, + "component": "pkg-config 0.3.33", + "path": "sources/crates/pkg-config-0.3.33.crate", + "role": "source", + "sha256": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pkg-config/pkg-config-0.3.33.crate" + }, + { + "bytes": 200703, + "component": "portable-atomic 1.14.0", + "path": "sources/crates/portable-atomic-1.14.0.crate", + "role": "source", + "sha256": "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.14.0.crate" + }, + { + "bytes": 140689, + "component": "portable-atomic 1.6.0", + "path": "sources/crates/portable-atomic-1.6.0.crate", + "role": "source", + "sha256": "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.6.0.crate" + }, + { + "bytes": 60024, + "component": "proc-macro2 1.0.103", + "path": "sources/crates/proc-macro2-1.0.103.crate", + "role": "source", + "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.103.crate" + }, + { + "bytes": 59588, + "component": "proc-macro2 1.0.107", + "path": "sources/crates/proc-macro2-1.0.107.crate", + "role": "source", + "sha256": "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.107.crate" + }, + { + "bytes": 48958, + "component": "proc-macro2 1.0.86", + "path": "sources/crates/proc-macro2-1.0.86.crate", + "role": "source", + "sha256": "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.86.crate" + }, + { + "bytes": 1204476, + "component": "pyo3 0.28.3", + "path": "sources/crates/pyo3-0.28.3.crate", + "role": "source", + "sha256": "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3/pyo3-0.28.3.crate" + }, + { + "bytes": 1241253, + "component": "pyo3 0.29.0", + "path": "sources/crates/pyo3-0.29.0.crate", + "role": "source", + "sha256": "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3/pyo3-0.29.0.crate" + }, + { + "bytes": 35961, + "component": "pyo3-build-config 0.28.3", + "path": "sources/crates/pyo3-build-config-0.28.3.crate", + "role": "source", + "sha256": "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-build-config/pyo3-build-config-0.28.3.crate" + }, + { + "bytes": 39616, + "component": "pyo3-build-config 0.29.0", + "path": "sources/crates/pyo3-build-config-0.29.0.crate", + "role": "source", + "sha256": "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-build-config/pyo3-build-config-0.29.0.crate" + }, + { + "bytes": 80881, + "component": "pyo3-ffi 0.28.3", + "path": "sources/crates/pyo3-ffi-0.28.3.crate", + "role": "source", + "sha256": "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-ffi/pyo3-ffi-0.28.3.crate" + }, + { + "bytes": 88184, + "component": "pyo3-ffi 0.29.0", + "path": "sources/crates/pyo3-ffi-0.29.0.crate", + "role": "source", + "sha256": "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-ffi/pyo3-ffi-0.29.0.crate" + }, + { + "bytes": 8963, + "component": "pyo3-macros 0.28.3", + "path": "sources/crates/pyo3-macros-0.28.3.crate", + "role": "source", + "sha256": "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-macros/pyo3-macros-0.28.3.crate" + }, + { + "bytes": 8915, + "component": "pyo3-macros 0.29.0", + "path": "sources/crates/pyo3-macros-0.29.0.crate", + "role": "source", + "sha256": "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-macros/pyo3-macros-0.29.0.crate" + }, + { + "bytes": 88063, + "component": "pyo3-macros-backend 0.28.3", + "path": "sources/crates/pyo3-macros-backend-0.28.3.crate", + "role": "source", + "sha256": "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-macros-backend/pyo3-macros-backend-0.28.3.crate" + }, + { + "bytes": 91943, + "component": "pyo3-macros-backend 0.29.0", + "path": "sources/crates/pyo3-macros-backend-0.29.0.crate", + "role": "source", + "sha256": "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/pyo3-macros-backend/pyo3-macros-backend-0.29.0.crate" + }, + { + "bytes": 103489, + "component": "python3-dll-a 0.2.14", + "path": "sources/crates/python3-dll-a-0.2.14.crate", + "role": "source", + "sha256": "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/python3-dll-a/python3-dll-a-0.2.14.crate" + }, + { + "bytes": 31504, + "component": "quote 1.0.42", + "path": "sources/crates/quote-1.0.42.crate", + "role": "source", + "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/quote/quote-1.0.42.crate" + }, + { + "bytes": 31629, + "component": "quote 1.0.44", + "path": "sources/crates/quote-1.0.44.crate", + "role": "source", + "sha256": "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/quote/quote-1.0.44.crate" + }, + { + "bytes": 31622, + "component": "quote 1.0.47", + "path": "sources/crates/quote-1.0.47.crate", + "role": "source", + "sha256": "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/quote/quote-1.0.47.crate" + }, + { + "bytes": 64764, + "component": "r-efi 5.2.0", + "path": "sources/crates/r-efi-5.2.0.crate", + "role": "source", + "sha256": "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/r-efi/r-efi-5.2.0.crate" + }, + { + "bytes": 10906, + "component": "radium 0.7.0", + "path": "sources/crates/radium-0.7.0.crate", + "role": "source", + "sha256": "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/radium/radium-0.7.0.crate" + }, + { + "bytes": 153392, + "component": "regex 1.12.3", + "path": "sources/crates/regex-1.12.3.crate", + "role": "source", + "sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/regex/regex-1.12.3.crate" + }, + { + "bytes": 625250, + "component": "regex-automata 0.4.13", + "path": "sources/crates/regex-automata-0.4.13.crate", + "role": "source", + "sha256": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/regex-automata/regex-automata-0.4.13.crate" + }, + { + "bytes": 357541, + "component": "regex-syntax 0.8.5", + "path": "sources/crates/regex-syntax-0.8.5.crate", + "role": "source", + "sha256": "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.5.crate" + }, + { + "bytes": 75113, + "component": "rpds 1.2.1", + "path": "sources/crates/rpds-1.2.1.crate", + "role": "source", + "sha256": "e025feb26210bc196b908e72deb063b1b4000754304341cbc168a1e72c857ebc", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/rpds/rpds-1.2.1.crate" + }, + { + "bytes": 17621, + "component": "rustversion 1.0.17", + "path": "sources/crates/rustversion-1.0.17.crate", + "role": "source", + "sha256": "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/rustversion/rustversion-1.0.17.crate" + }, + { + "bytes": 23869, + "component": "self_cell 1.3.0", + "path": "sources/crates/self_cell-1.3.0.crate", + "role": "source", + "sha256": "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/self_cell/self_cell-1.3.0.crate" + }, + { + "bytes": 83652, + "component": "serde 1.0.228", + "path": "sources/crates/serde-1.0.228.crate", + "role": "source", + "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/serde/serde-1.0.228.crate" + }, + { + "bytes": 63111, + "component": "serde_core 1.0.228", + "path": "sources/crates/serde_core-1.0.228.crate", + "role": "source", + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/serde_core/serde_core-1.0.228.crate" + }, + { + "bytes": 59605, + "component": "serde_derive 1.0.228", + "path": "sources/crates/serde_derive-1.0.228.crate", + "role": "source", + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/serde_derive/serde_derive-1.0.228.crate" + }, + { + "bytes": 155994, + "component": "serde_json 1.0.149", + "path": "sources/crates/serde_json-1.0.149.crate", + "role": "source", + "sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/serde_json/serde_json-1.0.149.crate" + }, + { + "bytes": 19332, + "component": "shlex 2.0.1", + "path": "sources/crates/shlex-2.0.1.crate", + "role": "source", + "sha256": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/shlex/shlex-2.0.1.crate" + }, + { + "bytes": 38116, + "component": "smallvec 1.15.1", + "path": "sources/crates/smallvec-1.15.1.crate", + "role": "source", + "sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/smallvec/smallvec-1.15.1.crate" + }, + { + "bytes": 40124, + "component": "speedate 0.17.0", + "path": "sources/crates/speedate-0.17.0.crate", + "role": "source", + "sha256": "aba069c070b5e213f2a094deb7e5ed50ecb092be36102a4f4042e8d2056d060e", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/speedate/speedate-0.17.0.crate" + }, + { + "bytes": 8054, + "component": "stable_deref_trait 1.2.0", + "path": "sources/crates/stable_deref_trait-1.2.0.crate", + "role": "source", + "sha256": "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/stable_deref_trait/stable_deref_trait-1.2.0.crate" + }, + { + "bytes": 18480, + "component": "static_assertions 1.1.0", + "path": "sources/crates/static_assertions-1.1.0.crate", + "role": "source", + "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/static_assertions/static_assertions-1.1.0.crate" + }, + { + "bytes": 8489, + "component": "strum 0.27.2", + "path": "sources/crates/strum-0.27.2.crate", + "role": "source", + "sha256": "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/strum/strum-0.27.2.crate" + }, + { + "bytes": 30522, + "component": "strum_macros 0.27.2", + "path": "sources/crates/strum_macros-0.27.2.crate", + "role": "source", + "sha256": "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/strum_macros/strum_macros-0.27.2.crate" + }, + { + "bytes": 302117, + "component": "syn 2.0.111", + "path": "sources/crates/syn-2.0.111.crate", + "role": "source", + "sha256": "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/syn/syn-2.0.111.crate" + }, + { + "bytes": 307407, + "component": "syn 2.0.119", + "path": "sources/crates/syn-2.0.119.crate", + "role": "source", + "sha256": "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/syn/syn-2.0.119.crate" + }, + { + "bytes": 272300, + "component": "syn 2.0.82", + "path": "sources/crates/syn-2.0.82.crate", + "role": "source", + "sha256": "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/syn/syn-2.0.82.crate" + }, + { + "bytes": 18327, + "component": "synstructure 0.13.1", + "path": "sources/crates/synstructure-0.13.1.crate", + "role": "source", + "sha256": "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/synstructure/synstructure-0.13.1.crate" + }, + { + "bytes": 11316, + "component": "tap 1.0.1", + "path": "sources/crates/tap-1.0.1.crate", + "role": "source", + "sha256": "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/tap/tap-1.0.1.crate" + }, + { + "bytes": 28498, + "component": "target-lexicon 0.13.3", + "path": "sources/crates/target-lexicon-0.13.3.crate", + "role": "source", + "sha256": "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/target-lexicon/target-lexicon-0.13.3.crate" + }, + { + "bytes": 26832, + "component": "target-lexicon 0.13.4", + "path": "sources/crates/target-lexicon-0.13.4.crate", + "role": "source", + "sha256": "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/target-lexicon/target-lexicon-0.13.4.crate" + }, + { + "bytes": 26928, + "component": "target-lexicon 0.13.5", + "path": "sources/crates/target-lexicon-0.13.5.crate", + "role": "source", + "sha256": "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/target-lexicon/target-lexicon-0.13.5.crate" + }, + { + "bytes": 16971, + "component": "tinystr 0.7.6", + "path": "sources/crates/tinystr-0.7.6.crate", + "role": "source", + "sha256": "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/tinystr/tinystr-0.7.6.crate" + }, + { + "bytes": 34711, + "component": "triomphe 0.1.15", + "path": "sources/crates/triomphe-0.1.15.crate", + "role": "source", + "sha256": "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/triomphe/triomphe-0.1.15.crate" + }, + { + "bytes": 42168, + "component": "unicode-ident 1.0.12", + "path": "sources/crates/unicode-ident-1.0.12.crate", + "role": "source", + "sha256": "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.12.crate" + }, + { + "bytes": 47919, + "component": "unicode-ident 1.0.22", + "path": "sources/crates/unicode-ident-1.0.22.crate", + "role": "source", + "sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.22.crate" + }, + { + "bytes": 49298, + "component": "unicode-ident 1.0.24", + "path": "sources/crates/unicode-ident-1.0.24.crate", + "role": "source", + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.24.crate" + }, + { + "bytes": 86512, + "component": "url 2.5.8", + "path": "sources/crates/url-2.5.8.crate", + "role": "source", + "sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/url/url-2.5.8.crate" + }, + { + "bytes": 9736, + "component": "utf16_iter 1.0.5", + "path": "sources/crates/utf16_iter-1.0.5.crate", + "role": "source", + "sha256": "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/utf16_iter/utf16_iter-1.0.5.crate" + }, + { + "bytes": 10437, + "component": "utf8_iter 1.0.4", + "path": "sources/crates/utf8_iter-1.0.4.crate", + "role": "source", + "sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/utf8_iter/utf8_iter-1.0.4.crate" + }, + { + "bytes": 63006, + "component": "uuid 1.23.0", + "path": "sources/crates/uuid-1.23.0.crate", + "role": "source", + "sha256": "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/uuid/uuid-1.23.0.crate" + }, + { + "bytes": 228735, + "component": "vcpkg 0.2.15", + "path": "sources/crates/vcpkg-0.2.15.crate", + "role": "source", + "sha256": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/vcpkg/vcpkg-0.2.15.crate" + }, + { + "bytes": 15554, + "component": "version_check 0.9.5", + "path": "sources/crates/version_check-0.9.5.crate", + "role": "source", + "sha256": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/version_check/version_check-0.9.5.crate" + }, + { + "bytes": 140921, + "component": "wasi 0.14.2+wasi-0.2.4", + "path": "sources/crates/wasi-0.14.2+wasi-0.2.4.crate", + "role": "source", + "sha256": "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasi/wasi-0.14.2+wasi-0.2.4.crate" + }, + { + "bytes": 48288, + "component": "wasm-bindgen 0.2.100", + "path": "sources/crates/wasm-bindgen-0.2.100.crate", + "role": "source", + "sha256": "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.100.crate" + }, + { + "bytes": 32111, + "component": "wasm-bindgen-backend 0.2.100", + "path": "sources/crates/wasm-bindgen-backend-0.2.100.crate", + "role": "source", + "sha256": "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasm-bindgen-backend/wasm-bindgen-backend-0.2.100.crate" + }, + { + "bytes": 9663, + "component": "wasm-bindgen-macro 0.2.100", + "path": "sources/crates/wasm-bindgen-macro-0.2.100.crate", + "role": "source", + "sha256": "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.100.crate" + }, + { + "bytes": 26243, + "component": "wasm-bindgen-macro-support 0.2.100", + "path": "sources/crates/wasm-bindgen-macro-support-0.2.100.crate", + "role": "source", + "sha256": "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.100.crate" + }, + { + "bytes": 8570, + "component": "wasm-bindgen-shared 0.2.100", + "path": "sources/crates/wasm-bindgen-shared-0.2.100.crate", + "role": "source", + "sha256": "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.100.crate" + }, + { + "bytes": 12241, + "component": "wit-bindgen-rt 0.39.0", + "path": "sources/crates/wit-bindgen-rt-0.39.0.crate", + "role": "source", + "sha256": "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wit-bindgen-rt/wit-bindgen-rt-0.39.0.crate" + }, + { + "bytes": 7218, + "component": "write16 1.0.0", + "path": "sources/crates/write16-1.0.0.crate", + "role": "source", + "sha256": "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/write16/write16-1.0.0.crate" + }, + { + "bytes": 22354, + "component": "writeable 0.5.5", + "path": "sources/crates/writeable-0.5.5.crate", + "role": "source", + "sha256": "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/writeable/writeable-0.5.5.crate" + }, + { + "bytes": 18790, + "component": "wyz 0.5.1", + "path": "sources/crates/wyz-0.5.1.crate", + "role": "source", + "sha256": "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/wyz/wyz-0.5.1.crate" + }, + { + "bytes": 27781, + "component": "yoke 0.7.4", + "path": "sources/crates/yoke-0.7.4.crate", + "role": "source", + "sha256": "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/yoke/yoke-0.7.4.crate" + }, + { + "bytes": 7470, + "component": "yoke-derive 0.7.4", + "path": "sources/crates/yoke-derive-0.7.4.crate", + "role": "source", + "sha256": "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/yoke-derive/yoke-derive-0.7.4.crate" + }, + { + "bytes": 252714, + "component": "zerocopy 0.8.25", + "path": "sources/crates/zerocopy-0.8.25.crate", + "role": "source", + "sha256": "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.25.crate" + }, + { + "bytes": 87671, + "component": "zerocopy-derive 0.8.25", + "path": "sources/crates/zerocopy-derive-0.8.25.crate", + "role": "source", + "sha256": "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.25.crate" + }, + { + "bytes": 5044, + "component": "zerofrom 0.1.4", + "path": "sources/crates/zerofrom-0.1.4.crate", + "role": "source", + "sha256": "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerofrom/zerofrom-0.1.4.crate" + }, + { + "bytes": 8232, + "component": "zerofrom-derive 0.1.4", + "path": "sources/crates/zerofrom-derive-0.1.4.crate", + "role": "source", + "sha256": "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.4.crate" + }, + { + "bytes": 126398, + "component": "zerovec 0.10.4", + "path": "sources/crates/zerovec-0.10.4.crate", + "role": "source", + "sha256": "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerovec/zerovec-0.10.4.crate" + }, + { + "bytes": 19438, + "component": "zerovec-derive 0.10.3", + "path": "sources/crates/zerovec-derive-0.10.3.crate", + "role": "source", + "sha256": "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zerovec-derive/zerovec-derive-0.10.3.crate" + }, + { + "bytes": 21298, + "component": "zmij 1.0.6", + "path": "sources/crates/zmij-1.0.6.crate", + "role": "source", + "sha256": "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f", + "tls_verified_at_collection": true, + "upstream_url": "https://static.crates.io/crates/zmij/zmij-1.0.6.crate" + }, + { + "bytes": 598175, + "component": "libffi 3.4.6", + "path": "sources/libffi/libffi-v3.4.6-from-cffi-workflow.tar.gz", + "role": "source", + "sha256": "9ac790464c1eb2f5ab5809e978a1683e9393131aede72d1b0a0703771d3c6cda", + "tls_verified_at_collection": true, + "upstream_url": "https://github.com/libffi/libffi/archive/v3.4.6.tar.gz" + }, + { + "bytes": 55153883, + "component": "OpenSSL 4.0.2", + "path": "sources/openssl/openssl-4.0.2.tar.gz", + "role": "source", + "sha256": "736b467530f916737b7031310ccb21d8218c6229e61e8e160cd1d3458cd543a8", + "tls_verified_at_collection": true, + "upstream_url": "https://github.com/openssl/openssl/releases/download/openssl-4.0.2/openssl-4.0.2.tar.gz" + }, + { + "bytes": 138112, + "component": "certifi-2026.7.22", + "path": "sources/python/certifi-2026.7.22.tar.gz", + "role": "source", + "sha256": "741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", + "tls_verified_at_collection": true, + "upstream_url": "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz" + }, + { + "bytes": 530807, + "component": "cffi-2.1.1", + "path": "sources/python/cffi-2.1.1.tar.gz", + "role": "source", + "sha256": "dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", + "tls_verified_at_collection": true, + "upstream_url": "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz" + }, + { + "bytes": 880381, + "component": "cryptography 50.0.1", + "path": "sources/python/cryptography-50.0.1.tar.gz", + "role": "source", + "sha256": "5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", + "tls_verified_at_collection": true, + "upstream_url": "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz" + }, + { + "bytes": 472262, + "component": "pydantic_core 2.46.5", + "path": "sources/python/pydantic_core-2.46.5.tar.gz", + "role": "source", + "sha256": "10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", + "tls_verified_at_collection": true, + "upstream_url": "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz" + }, + { + "bytes": 64051, + "component": "rpds-py 2026.6.3", + "path": "sources/python/rpds_py-2026.6.3.tar.gz", + "role": "source", + "sha256": "1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", + "tls_verified_at_collection": true, + "upstream_url": "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz" + }, + { + "bytes": 3868128, + "component": "Rust source supplement 1.96.0", + "path": "sources/rust-runtime/rust-src-1.96.0.tar.xz", + "role": "compiler/runtime source supplement; classification not a legal determination", + "sha256": "7875f9f4b91455ac25e2ea0e0f1cb3d7d16c309ef0720778538d20ed0e9b818b", + "tls_verified_at_collection": true, + "upstream_url": "https://static.rust-lang.org/dist/2026-05-28/rust-src-1.96.0.tar.xz" + }, + { + "bytes": 5741336, + "component": "Rust source supplement 1.98.0", + "path": "sources/rust-runtime/rust-src-1.98.0.tar.xz", + "role": "compiler/runtime source supplement; classification not a legal determination", + "sha256": "0e977492aed5ff137815bf9b4b796e8a85b80fb2367ed64e82d5c5839870f2bf", + "tls_verified_at_collection": true, + "upstream_url": "https://static.rust-lang.org/dist/2026-08-20/rust-src-1.98.0.tar.xz" + }, + { + "bytes": 138175, + "component": "certifi-git-f4bc676b", + "path": "sources/upstream-git/certifi-git-f4bc676b.tar.gz", + "role": "source", + "sha256": "60bdada2558188c3d2d4942f6fe7023b98955a5b1b2e6b03058bd92381ad6ef3", + "tls_verified_at_collection": true, + "upstream_url": "https://github.com/certifi/python-certifi/archive/f4bc676bc101fe2235846e37044e8c693d6cbaf4.tar.gz" + }, + { + "bytes": 540132, + "component": "cffi-git-fd33e770", + "path": "sources/upstream-git/cffi-git-fd33e770.tar.gz", + "role": "source", + "sha256": "3c63006cdf332696a1e16b76e0c6aed38c17208e5de26737459ae6125c091482", + "tls_verified_at_collection": true, + "upstream_url": "https://github.com/python-cffi/cffi/archive/fd33e7700f0ebafe6f30bd5053f13327221b77a2.tar.gz" + } + ], + "source_local_workspace_packages": [ + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-cffi", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-crypto", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-keepalive", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-key-parsing", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-openssl", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-x509", + "project": "cryptography", + "version": "0.50.1" + }, + { + "declared_license_expressions": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "name": "cryptography-x509-verification", + "project": "cryptography", + "version": "0.50.1" + } + ], + "status": "SOURCE_EVIDENCE_COLLECTED_EXTERNAL_REDISTRIBUTION_REVIEW_REQUIRED" +} diff --git a/licensing/third-party/MPL-2.0.SOURCE.json b/licensing/third-party/MPL-2.0.SOURCE.json new file mode 100644 index 0000000..2fe0d40 --- /dev/null +++ b/licensing/third-party/MPL-2.0.SOURCE.json @@ -0,0 +1,12 @@ +{ + "license_spdx": "MPL-2.0", + "filename": "MPL-2.0.txt", + "url": "https://raw.githubusercontent.com/spdx/license-list-data/main/text/MPL-2.0.txt", + "final_url": "https://raw.githubusercontent.com/spdx/license-list-data/main/text/MPL-2.0.txt", + "sha256": "66a3107d5ad6a058aab753eaac2047ccb2ed0e39465dd0fe5844da3e300d5172", + "bytes": 16727, + "tls_verified": true, + "http_status": 200, + "retrieved_utc": "2026-10-26T01:02:57.240974+00:00", + "scope": "Full license text supplement delivered separately; sealed dependency source archive was not recreated or changed." +} diff --git a/licensing/third-party/NATIVE-DEPENDENCY-TERMS.json b/licensing/third-party/NATIVE-DEPENDENCY-TERMS.json new file mode 100644 index 0000000..682488c --- /dev/null +++ b/licensing/third-party/NATIVE-DEPENDENCY-TERMS.json @@ -0,0 +1,3720 @@ +{ + "actual_shipped_native_objects": [ + { + "component": "cryptography 50.0.1", + "declared_expression": "Apache-2.0 OR BSD-3-Clause", + "documented_distribution_route": "Apache-2.0", + "matches_official_wheel": true, + "object_path_in_package": "usr/lib/framebridge-expanded/original/geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so", + "object_sha256": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source_and_notice_association": "Matching official wheel; pinned official sdist; exact upstream notices; Cargo.lock/SBOM source closure where applicable. CFFI adds its pinned static-libffi workflow and libffi source." + }, + { + "component": "pydantic_core 2.46.5", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "matches_official_wheel": true, + "object_path_in_package": "usr/lib/framebridge-expanded/original/geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source_and_notice_association": "Matching official wheel; pinned official sdist; exact upstream notices; Cargo.lock/SBOM source closure where applicable. CFFI adds its pinned static-libffi workflow and libffi source." + }, + { + "component": "rpds-py 2026.6.3", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "matches_official_wheel": true, + "object_path_in_package": "usr/lib/framebridge-expanded/original/geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source_and_notice_association": "Matching official wheel; pinned official sdist; exact upstream notices; Cargo.lock/SBOM source closure where applicable. CFFI adds its pinned static-libffi workflow and libffi source." + }, + { + "component": "cffi 2.1.1", + "declared_expression": "MIT-0", + "documented_distribution_route": "MIT-0", + "matches_official_wheel": true, + "object_path_in_package": "usr/lib/framebridge-expanded/original/geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so", + "object_sha256": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source_and_notice_association": "Matching official wheel; pinned official sdist; exact upstream notices; Cargo.lock/SBOM source closure where applicable. CFFI adds its pinned static-libffi workflow and libffi source." + } + ], + "associated_components": [ + { + "component": "OpenSSL 4.0.2", + "declared_expression": "Apache-2.0", + "documented_distribution_route": "Apache-2.0", + "evidence": "Official-wheel SBOM source checksum matches supplied OpenSSL source archive and its license/notices. Static-source association is documented; no independent native build is claimed." + }, + { + "component": "libffi 3.4.6", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "evidence": "Pinned CFFI CI selects this source with static flags and symbol renaming; shipped object contains the renamed symbol. Full LICENSE and source are supplied." + }, + { + "component": "certifi 2026.7.22", + "declared_expression": "MPL-2.0", + "documented_distribution_route": "MPL-2.0 retained; GPLv3 combination permission under MPL 3.3 where applicable, without removing the MPL option", + "evidence": "Unmodified CA bundle matches official wheel, sdist and pinned Git; original notice and full MPL text are supplied. No Exhibit B incompatibility notice was observed in the reviewed certifi payload/notice. Exact Mozilla history is not an automatic source-delivery prerequisite." + }, + { + "component": "Rust compiler/runtime source supplements 1.98.0 and 1.96.0", + "declared_expression": "Upstream MIT/Apache and more specific retained notices", + "documented_distribution_route": "Retain source-component licensing and all accompanying notices", + "evidence": "Actual objects identify these compiler versions in ELF comments; official manifest-verified rust-src supplements are provided. This is not proof every compiler component is embedded." + } + ], + "checks": { + "all_143_crates_hash_verified": true, + "all_143_crates_have_mapped_notice_evidence_after_supplement": true, + "all_existing_indexed_135_notices_hash_matched": true, + "four_actual_native_objects_matched": true, + "full_mpl_present_and_hash_matched": true, + "no_forced_gpl2_only_or_lgpl_branch": true, + "sealed_source_archive_hash_unchanged": true, + "unicode_and_conjuncts_retained": true + }, + "delivery_conditions": [ + "Attach the unchanged 82 MB source archive, the matching complete FrameBridge source, existing full legal texts and both separate supplements to an actual distribution.", + "Provide clear source access instructions and preserve the MPL source/notice and applicable secondary-license options.", + "Do not describe this focused technical review as a universal legal opinion, copyright/patent clearance or authorization to publish." + ], + "family_obligations_and_satisfaction": { + "Apache-2.0": "Provide the license, preserve applicable notices, preserve supplied NOTICE attributions and identify changes if any. Original dependency sources are unmodified; applicable texts/attributions are retained. Its patent terms remain operative.", + "Apache-2.0 WITH LLVM-exception": "Retain Apache and exception terms; the exception relaxes specified Apache obligations for compiler-embedded object portions. Retaining full notices is a conservative permitted delivery route.", + "BSD-3-Clause": "Retain source and binary redistribution notices/disclaimer; do not imply author endorsement. Mapped notices remain with the distribution.", + "GPL-3.0-only original FrameBridge": "When conveying the covered combined program, deliver applicable notices/license and its Corresponding Source using an allowed GPL delivery method. General build tools and qualifying System Libraries are excepted by the license. It does not require an independent bit-identical native rebuild experiment.", + "MIT": "Keep upstream copyright and permission notices in copies/substantial portions. Original sources and mapped full notices are preserved.", + "MIT-0": "This permissive form does not impose MIT attribution conditions; the received notice is nevertheless preserved.", + "MPL-2.0": "Keep MPL rights and notices for covered files; provide their source and explain how recipients obtain it. Full official certifi source and exact CA data plus full MPL are supplied. For a qualifying GPL Larger Work, preserve the MPL option while applying MPL 3.3 secondary-license permission as needed.", + "Unicode-3.0 / Unicode-DFS-2016": "Preserve the Unicode copyright/permission notices for covered data/software. In AND expressions this requirement remains additional to the selected MIT branch; all three unicode-ident variants include LICENSE-UNICODE.", + "Zlib": "Do not misrepresent origin; mark altered source if present and retain the notice. The dependency source is supplied unchanged." + }, + "first_party_license": "GPL-3.0-only", + "limitations": [ + "This is a focused technical terms/source/notice review of identified artifacts, not an integral legal opinion.", + "The original Preview1 repository and 82 MB archive are not changed by this R2 report; only new R2 legal files and the specifically authorized supplemental notice are written." + ], + "not_blocking_by_themselves": [ + "No independently reproduced native-wheel build was performed. This is an evidence limit, not a GPL/MPL requirement to run such an experiment.", + "The exact original Mozilla certdata revision is unknown. The exact unmodified official certifi source/data is supplied; historical regeneration is not an automatic additional delivery condition.", + "SBOM entries may be build-only or target-specific; supplying conservative source/notices does not claim all entries are runtime-shipped." + ], + "primary_sources": [ + { + "local_control_text": "LICENSE", + "name": "GNU GPLv3 sections 1, 4-6", + "url": "https://www.gnu.org/licenses/gpl-3.0.en.html" + }, + { + "name": "ASF GPLv3 compatibility", + "url": "https://www.apache.org/licenses/GPL-compatibility.html" + }, + { + "name": "Mozilla MPL 2.0 sections 1.13, 3.1-3.4", + "url": "https://www.mozilla.org/en-US/MPL/2.0/" + }, + { + "name": "Mozilla MPL FAQ Q7, Q8 and Q14", + "url": "https://www.mozilla.org/en-US/MPL/2.0/FAQ/" + }, + { + "name": "LLVM exception text", + "url": "https://spdx.org/licenses/LLVM-exception.html" + } + ], + "registry_components": [ + { + "component": "ahash", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "ahash-0.8.12/LICENSE-APACHE" + }, + { + "notice_path": "licenses/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt", + "sha256": "0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb", + "source_member": "ahash-0.8.12/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/ahash-0.8.12.crate", + "source_checksum_verified": true, + "source_sha256": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75", + "version": "0.8.12" + }, + { + "component": "aho-corasick", + "declared_expression": "Unlicense OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "source_member": "aho-corasick-1.1.3/COPYING" + }, + { + "notice_path": "licenses/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "source_member": "aho-corasick-1.1.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/aho-corasick-1.1.3.crate", + "source_checksum_verified": true, + "source_sha256": "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916", + "version": "1.1.3" + }, + { + "component": "allocator-api2", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt", + "sha256": "20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58", + "source_member": "allocator-api2-0.2.21/LICENSE-APACHE" + }, + { + "notice_path": "licenses/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt", + "sha256": "36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9", + "source_member": "allocator-api2-0.2.21/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/allocator-api2-0.2.21.crate", + "source_checksum_verified": true, + "source_sha256": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923", + "version": "0.2.21" + }, + { + "component": "archery", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt", + "sha256": "e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f", + "source_member": "archery-1.2.2/LICENSE.md" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/archery-1.2.2.crate", + "source_checksum_verified": true, + "source_sha256": "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d", + "version": "1.2.2" + }, + { + "component": "asn1", + "declared_expression": "BSD-3-Clause", + "documented_distribution_route": "BSD-3-Clause", + "notice_evidence": [ + { + "notice_path": "licenses/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt", + "sha256": "c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985", + "source_member": "asn1-0.24.1/LICENSE" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/asn1-0.24.1.crate", + "source_checksum_verified": true, + "source_sha256": "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1", + "version": "0.24.1" + }, + { + "component": "asn1_derive", + "declared_expression": "BSD-3-Clause", + "documented_distribution_route": "BSD-3-Clause", + "notice_evidence": [ + { + "notice_path": "licenses/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt", + "sha256": "c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985", + "source_member": "asn1_derive-0.24.1/LICENSE" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/asn1_derive-0.24.1.crate", + "source_checksum_verified": true, + "source_sha256": "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a", + "version": "0.24.1" + }, + { + "component": "autocfg", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "autocfg-1.3.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt", + "sha256": "27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac", + "source_member": "autocfg-1.3.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/autocfg-1.3.0.crate", + "source_checksum_verified": true, + "source_sha256": "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0", + "version": "1.3.0" + }, + { + "component": "base64", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "base64-0.22.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt", + "sha256": "0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7", + "source_member": "base64-0.22.1/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/base64-0.22.1.crate", + "source_checksum_verified": true, + "source_sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6", + "version": "0.22.1" + }, + { + "component": "base64", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "base64-0.23.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt", + "sha256": "ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c", + "source_member": "base64-0.23.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/base64-0.23.0.crate", + "source_checksum_verified": true, + "source_sha256": "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9", + "version": "0.23.0" + }, + { + "component": "bitflags", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bitflags-2.13.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "bitflags-2.13.1/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/bitflags-2.13.1.crate", + "source_checksum_verified": true, + "source_sha256": "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da", + "version": "2.13.1" + }, + { + "component": "bitflags", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bitflags-2.9.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "bitflags-2.9.1/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/bitflags-2.9.1.crate", + "source_checksum_verified": true, + "source_sha256": "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967", + "version": "2.9.1" + }, + { + "component": "bitvec", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt", + "sha256": "411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5", + "source_member": "bitvec-1.0.1/LICENSE.txt" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/bitvec-1.0.1.crate", + "source_checksum_verified": true, + "source_sha256": "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c", + "version": "1.0.1" + }, + { + "component": "bumpalo", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "bumpalo-3.19.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt", + "sha256": "65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee", + "source_member": "bumpalo-3.19.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/bumpalo-3.19.0.crate", + "source_checksum_verified": true, + "source_sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43", + "version": "3.19.0" + }, + { + "component": "cc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cc-1.0.101/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cc-1.0.101/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/cc-1.0.101.crate", + "source_checksum_verified": true, + "source_sha256": "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d", + "version": "1.0.101" + }, + { + "component": "cc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cc-1.4.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cc-1.4.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/cc-1.4.0.crate", + "source_checksum_verified": true, + "source_sha256": "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9", + "version": "1.4.0" + }, + { + "component": "cfg-if", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cfg-if-1.0.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cfg-if-1.0.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/cfg-if-1.0.0.crate", + "source_checksum_verified": true, + "source_sha256": "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd", + "version": "1.0.0" + }, + { + "component": "cfg-if", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "cfg-if-1.0.4/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "cfg-if-1.0.4/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/cfg-if-1.0.4.crate", + "source_checksum_verified": true, + "source_sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", + "version": "1.0.4" + }, + { + "component": "displaydoc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "displaydoc-0.2.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "displaydoc-0.2.5/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/displaydoc-0.2.5.crate", + "source_checksum_verified": true, + "source_sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0", + "version": "0.2.5" + }, + { + "component": "enum_dispatch", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt", + "sha256": "1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be", + "source_member": "enum_dispatch-0.3.13/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/enum_dispatch-0.3.13.crate", + "source_checksum_verified": true, + "source_sha256": "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd", + "version": "0.3.13" + }, + { + "component": "equivalent", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "equivalent-1.0.2/LICENSE-APACHE" + }, + { + "notice_path": "licenses/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt", + "sha256": "7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349", + "source_member": "equivalent-1.0.2/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/equivalent-1.0.2.crate", + "source_checksum_verified": true, + "source_sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f", + "version": "1.0.2" + }, + { + "component": "find-msvc-tools", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "find-msvc-tools-0.1.9/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "find-msvc-tools-0.1.9/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/find-msvc-tools-0.1.9.crate", + "source_checksum_verified": true, + "source_sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582", + "version": "0.1.9" + }, + { + "component": "foldhash", + "declared_expression": "Zlib", + "documented_distribution_route": "Zlib", + "notice_evidence": [ + { + "notice_path": "licenses/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt", + "sha256": "b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744", + "source_member": "foldhash-0.2.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/foldhash-0.2.0.crate", + "source_checksum_verified": true, + "source_sha256": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb", + "version": "0.2.0" + }, + { + "component": "foreign-types", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "foreign-types-0.3.2/LICENSE-APACHE" + }, + { + "notice_path": "licenses/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt", + "sha256": "333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734", + "source_member": "foreign-types-0.3.2/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/foreign-types-0.3.2.crate", + "source_checksum_verified": true, + "source_sha256": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1", + "version": "0.3.2" + }, + { + "component": "foreign-types-shared", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "foreign-types-shared-0.1.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt", + "sha256": "333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734", + "source_member": "foreign-types-shared-0.1.1/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/foreign-types-shared-0.1.1.crate", + "source_checksum_verified": true, + "source_sha256": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b", + "version": "0.1.1" + }, + { + "component": "form_urlencoded", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "form_urlencoded-1.2.2/LICENSE-APACHE" + }, + { + "notice_path": "licenses/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt", + "sha256": "20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e", + "source_member": "form_urlencoded-1.2.2/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/form_urlencoded-1.2.2.crate", + "source_checksum_verified": true, + "source_sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf", + "version": "1.2.2" + }, + { + "component": "funty", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt", + "sha256": "f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f", + "source_member": "funty-2.0.0/LICENSE.txt" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/funty-2.0.0.crate", + "source_checksum_verified": true, + "source_sha256": "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c", + "version": "2.0.0" + }, + { + "component": "getrandom", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt", + "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf", + "source_member": "getrandom-0.3.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt", + "sha256": "29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4", + "source_member": "getrandom-0.3.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/getrandom-0.3.3.crate", + "source_checksum_verified": true, + "source_sha256": "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4", + "version": "0.3.3" + }, + { + "component": "hashbrown", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "hashbrown-0.16.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt", + "sha256": "ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2", + "source_member": "hashbrown-0.16.1/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/hashbrown-0.16.1.crate", + "source_checksum_verified": true, + "source_sha256": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100", + "version": "0.16.1" + }, + { + "component": "heck", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "heck-0.5.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt", + "sha256": "7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0", + "source_member": "heck-0.5.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "pydantic_core", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/heck-0.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", + "version": "0.5.0" + }, + { + "component": "hex", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "hex-0.4.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt", + "sha256": "f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9", + "source_member": "hex-0.4.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/hex-0.4.3.crate", + "source_checksum_verified": true, + "source_sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", + "version": "0.4.3" + }, + { + "component": "icu_collections", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_collections-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_collections-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526", + "version": "1.5.0" + }, + { + "component": "icu_locid", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_locid-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637", + "version": "1.5.0" + }, + { + "component": "icu_locid_transform", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid_transform-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_locid_transform-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e", + "version": "1.5.0" + }, + { + "component": "icu_locid_transform_data", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_locid_transform_data-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_locid_transform_data-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e", + "version": "1.5.0" + }, + { + "component": "icu_normalizer", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_normalizer-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_normalizer-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f", + "version": "1.5.0" + }, + { + "component": "icu_normalizer_data", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_normalizer_data-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_normalizer_data-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516", + "version": "1.5.0" + }, + { + "component": "icu_properties", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_properties-1.5.1/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_properties-1.5.1.crate", + "source_checksum_verified": true, + "source_sha256": "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5", + "version": "1.5.1" + }, + { + "component": "icu_properties_data", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_properties_data-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_properties_data-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569", + "version": "1.5.0" + }, + { + "component": "icu_provider", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_provider-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_provider-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9", + "version": "1.5.0" + }, + { + "component": "icu_provider_macros", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "icu_provider_macros-1.5.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/icu_provider_macros-1.5.0.crate", + "source_checksum_verified": true, + "source_sha256": "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6", + "version": "1.5.0" + }, + { + "component": "idna", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "idna-1.1.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "idna-1.1.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/idna-1.1.0.crate", + "source_checksum_verified": true, + "source_sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de", + "version": "1.1.0" + }, + { + "component": "idna_adapter", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "idna_adapter-1.2.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt", + "sha256": "8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726", + "source_member": "idna_adapter-1.2.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/idna_adapter-1.2.0.crate", + "source_checksum_verified": true, + "source_sha256": "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71", + "version": "1.2.0" + }, + { + "component": "itoa", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "itoa-1.0.11/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "itoa-1.0.11/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/itoa-1.0.11.crate", + "source_checksum_verified": true, + "source_sha256": "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b", + "version": "1.0.11" + }, + { + "component": "itoa", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "itoa-1.0.18/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "itoa-1.0.18/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/itoa-1.0.18.crate", + "source_checksum_verified": true, + "source_sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682", + "version": "1.0.18" + }, + { + "component": "jiter", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt", + "sha256": "7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e", + "source_member": "jiter-0.14.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/jiter-0.14.0.crate", + "source_checksum_verified": true, + "source_sha256": "b6f3b5d3f84b36f4ad09fd1da896d23d9852a1aa86556578dd0289f43dce311d", + "version": "0.14.0" + }, + { + "component": "js-sys", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "js-sys-0.3.77/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "js-sys-0.3.77/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/js-sys-0.3.77.crate", + "source_checksum_verified": true, + "source_sha256": "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f", + "version": "0.3.77" + }, + { + "component": "lexical-parse-float", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-parse-float-1.0.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-parse-float-1.0.5/LICENSE-MIT" + }, + { + "notice_path": "licenses/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt", + "sha256": "99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe", + "source_member": "lexical-parse-float-1.0.5/LICENSE.md" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/lexical-parse-float-1.0.5.crate", + "source_checksum_verified": true, + "source_sha256": "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2", + "version": "1.0.5" + }, + { + "component": "lexical-parse-integer", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-parse-integer-1.0.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-parse-integer-1.0.5/LICENSE-MIT" + }, + { + "notice_path": "licenses/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt", + "sha256": "99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe", + "source_member": "lexical-parse-integer-1.0.5/LICENSE.md" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/lexical-parse-integer-1.0.5.crate", + "source_checksum_verified": true, + "source_sha256": "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e", + "version": "1.0.5" + }, + { + "component": "lexical-util", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90", + "source_member": "lexical-util-1.0.6/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "lexical-util-1.0.6/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/lexical-util-1.0.6.crate", + "source_checksum_verified": true, + "source_sha256": "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3", + "version": "1.0.6" + }, + { + "component": "libc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.177/LICENSE-APACHE" + }, + { + "notice_path": "licenses/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt", + "sha256": "a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1", + "source_member": "libc-0.2.177/LICENSE-MIT" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/libc-0.2.177.crate", + "source_checksum_verified": true, + "source_sha256": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976", + "version": "0.2.177" + }, + { + "component": "libc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.185/LICENSE-APACHE" + }, + { + "notice_path": "licenses/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", + "source_member": "libc-0.2.185/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/libc-0.2.185.crate", + "source_checksum_verified": true, + "source_sha256": "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f", + "version": "0.2.185" + }, + { + "component": "libc", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "libc-0.2.189/LICENSE-APACHE" + }, + { + "notice_path": "licenses/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e", + "source_member": "libc-0.2.189/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/libc-0.2.189.crate", + "source_checksum_verified": true, + "source_sha256": "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2", + "version": "0.2.189" + }, + { + "component": "litemap", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "litemap-0.7.3/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/litemap-0.7.3.crate", + "source_checksum_verified": true, + "source_sha256": "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704", + "version": "0.7.3" + }, + { + "component": "log", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "log-0.4.27/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "log-0.4.27/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/log-0.4.27.crate", + "source_checksum_verified": true, + "source_sha256": "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94", + "version": "0.4.27" + }, + { + "component": "lru", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt", + "sha256": "061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e", + "source_member": "lru-0.16.3/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/lru-0.16.3.crate", + "source_checksum_verified": true, + "source_sha256": "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593", + "version": "0.16.3" + }, + { + "component": "memchr", + "declared_expression": "Unlicense OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f", + "source_member": "memchr-2.7.4/COPYING" + }, + { + "notice_path": "licenses/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f", + "source_member": "memchr-2.7.4/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/memchr-2.7.4.crate", + "source_checksum_verified": true, + "source_sha256": "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3", + "version": "2.7.4" + }, + { + "component": "num-bigint", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-bigint-0.4.6/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-bigint-0.4.6/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/num-bigint-0.4.6.crate", + "source_checksum_verified": true, + "source_sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9", + "version": "0.4.6" + }, + { + "component": "num-integer", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-integer-0.1.46/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-integer-0.1.46/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/num-integer-0.1.46.crate", + "source_checksum_verified": true, + "source_sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f", + "version": "0.1.46" + }, + { + "component": "num-traits", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "num-traits-0.2.19/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "num-traits-0.2.19/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/num-traits-0.2.19.crate", + "source_checksum_verified": true, + "source_sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "version": "0.2.19" + }, + { + "component": "once_cell", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "once_cell-1.21.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "once_cell-1.21.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/once_cell-1.21.3.crate", + "source_checksum_verified": true, + "source_sha256": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d", + "version": "1.21.3" + }, + { + "component": "once_cell", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "once_cell-1.21.4/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "once_cell-1.21.4/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/once_cell-1.21.4.crate", + "source_checksum_verified": true, + "source_sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50", + "version": "1.21.4" + }, + { + "component": "openssl", + "declared_expression": "Apache-2.0", + "documented_distribution_route": "Apache-2.0", + "notice_evidence": [ + { + "notice_path": "licenses/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt", + "sha256": "f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941", + "source_member": "openssl-0.10.81/LICENSE" + }, + { + "notice_path": "licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "openssl-0.10.81/LICENSE-APACHE" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/openssl-0.10.81.crate", + "source_checksum_verified": true, + "source_sha256": "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45", + "version": "0.10.81" + }, + { + "component": "openssl-macros", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08", + "source_member": "openssl-macros-0.1.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt", + "sha256": "ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30", + "source_member": "openssl-macros-0.1.1/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/openssl-macros-0.1.1.crate", + "source_checksum_verified": true, + "source_sha256": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c", + "version": "0.1.1" + }, + { + "component": "openssl-sys", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "openssl-sys-0.9.117/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/openssl-sys-0.9.117.crate", + "source_checksum_verified": true, + "source_sha256": "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695", + "version": "0.9.117" + }, + { + "component": "pem", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt", + "sha256": "b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf", + "source_member": "pem-4.0.0/LICENSE.md" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pem-4.0.0.crate", + "source_checksum_verified": true, + "source_sha256": "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120", + "version": "4.0.0" + }, + { + "component": "percent-encoding", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "percent-encoding-2.3.2/LICENSE-APACHE" + }, + { + "notice_path": "licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "percent-encoding-2.3.2/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/percent-encoding-2.3.2.crate", + "source_checksum_verified": true, + "source_sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220", + "version": "2.3.2" + }, + { + "component": "pkg-config", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "pkg-config-0.3.33/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "pkg-config-0.3.33/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pkg-config-0.3.33.crate", + "source_checksum_verified": true, + "source_sha256": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e", + "version": "0.3.33" + }, + { + "component": "portable-atomic", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", + "source_member": "portable-atomic-1.14.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "portable-atomic-1.14.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/portable-atomic-1.14.0.crate", + "source_checksum_verified": true, + "source_sha256": "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3", + "version": "1.14.0" + }, + { + "component": "portable-atomic", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", + "source_member": "portable-atomic-1.6.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "portable-atomic-1.6.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/portable-atomic-1.6.0.crate", + "source_checksum_verified": true, + "source_sha256": "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0", + "version": "1.6.0" + }, + { + "component": "proc-macro2", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.103/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.103/LICENSE-MIT" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/proc-macro2-1.0.103.crate", + "source_checksum_verified": true, + "source_sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", + "version": "1.0.103" + }, + { + "component": "proc-macro2", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.107/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.107/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/proc-macro2-1.0.107.crate", + "source_checksum_verified": true, + "source_sha256": "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9", + "version": "1.0.107" + }, + { + "component": "proc-macro2", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "proc-macro2-1.0.86/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "proc-macro2-1.0.86/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/proc-macro2-1.0.86.crate", + "source_checksum_verified": true, + "source_sha256": "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77", + "version": "1.0.86" + }, + { + "component": "pyo3", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.28.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.28.3/LICENSE-MIT" + }, + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.28.3/pyo3-runtime/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.28.3/pyo3-runtime/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-0.28.3.crate", + "source_checksum_verified": true, + "source_sha256": "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12", + "version": "0.28.3" + }, + { + "component": "pyo3", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.29.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.29.0/LICENSE-MIT" + }, + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-0.29.0/pyo3-runtime/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-0.29.0/pyo3-runtime/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-0.29.0.crate", + "source_checksum_verified": true, + "source_sha256": "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c", + "version": "0.29.0" + }, + { + "component": "pyo3-build-config", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-build-config-0.28.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-build-config-0.28.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-build-config-0.28.3.crate", + "source_checksum_verified": true, + "source_sha256": "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e", + "version": "0.28.3" + }, + { + "component": "pyo3-build-config", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-build-config-0.29.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-build-config-0.29.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-build-config-0.29.0.crate", + "source_checksum_verified": true, + "source_sha256": "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078", + "version": "0.29.0" + }, + { + "component": "pyo3-ffi", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-ffi-0.28.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-ffi-0.28.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-ffi-0.28.3.crate", + "source_checksum_verified": true, + "source_sha256": "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e", + "version": "0.28.3" + }, + { + "component": "pyo3-ffi", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-ffi-0.29.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-ffi-0.29.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-ffi-0.29.0.crate", + "source_checksum_verified": true, + "source_sha256": "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b", + "version": "0.29.0" + }, + { + "component": "pyo3-macros", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-0.28.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-0.28.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-macros-0.28.3.crate", + "source_checksum_verified": true, + "source_sha256": "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813", + "version": "0.28.3" + }, + { + "component": "pyo3-macros", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-0.29.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-0.29.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-macros-0.29.0.crate", + "source_checksum_verified": true, + "source_sha256": "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771", + "version": "0.29.0" + }, + { + "component": "pyo3-macros-backend", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-backend-0.28.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-backend-0.28.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-macros-backend-0.28.3.crate", + "source_checksum_verified": true, + "source_sha256": "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb", + "version": "0.28.3" + }, + { + "component": "pyo3-macros-backend", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9", + "source_member": "pyo3-macros-backend-0.29.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6", + "source_member": "pyo3-macros-backend-0.29.0/LICENSE-MIT" + } + ], + "projects": [ + "cryptography", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/pyo3-macros-backend-0.29.0.crate", + "source_checksum_verified": true, + "source_sha256": "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362", + "version": "0.29.0" + }, + { + "component": "python3-dll-a", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt", + "sha256": "c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763", + "source_member": "python3-dll-a-0.2.14/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/python3-dll-a-0.2.14.crate", + "source_checksum_verified": true, + "source_sha256": "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8", + "version": "0.2.14" + }, + { + "component": "quote", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.42/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.42/LICENSE-MIT" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/quote-1.0.42.crate", + "source_checksum_verified": true, + "source_sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", + "version": "1.0.42" + }, + { + "component": "quote", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.44/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.44/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/quote-1.0.44.crate", + "source_checksum_verified": true, + "source_sha256": "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4", + "version": "1.0.44" + }, + { + "component": "quote", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "quote-1.0.47/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "quote-1.0.47/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/quote-1.0.47.crate", + "source_checksum_verified": true, + "source_sha256": "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001", + "version": "1.0.47" + }, + { + "component": "r-efi", + "declared_expression": "MIT OR Apache-2.0 OR LGPL-2.1-or-later", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt", + "sha256": "c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d", + "source_member": "r-efi-5.2.0/AUTHORS" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/r-efi-5.2.0.crate", + "source_checksum_verified": true, + "source_sha256": "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5", + "version": "5.2.0" + }, + { + "component": "radium", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt", + "sha256": "13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02", + "source_member": "radium-0.7.0/LICENSE.txt" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/radium-0.7.0.crate", + "source_checksum_verified": true, + "source_sha256": "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09", + "version": "0.7.0" + }, + { + "component": "regex", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-1.12.3/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-1.12.3/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/regex-1.12.3.crate", + "source_checksum_verified": true, + "source_sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276", + "version": "1.12.3" + }, + { + "component": "regex-automata", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-automata-0.4.13/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-automata-0.4.13/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/regex-automata-0.4.13.crate", + "source_checksum_verified": true, + "source_sha256": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c", + "version": "0.4.13" + }, + { + "component": "regex-syntax", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "regex-syntax-0.8.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb", + "source_member": "regex-syntax-0.8.5/LICENSE-MIT" + }, + { + "notice_path": "licenses/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt", + "sha256": "74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3", + "source_member": "regex-syntax-0.8.5/src/unicode_tables/LICENSE-UNICODE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/regex-syntax-0.8.5.crate", + "source_checksum_verified": true, + "source_sha256": "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c", + "version": "0.8.5" + }, + { + "component": "rpds", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt", + "sha256": "e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f", + "source_member": "rpds-1.2.1/LICENSE.md" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/rpds-1.2.1.crate", + "source_checksum_verified": true, + "source_sha256": "e025feb26210bc196b908e72deb063b1b4000754304341cbc168a1e72c857ebc", + "version": "1.2.1" + }, + { + "component": "rustversion", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "rustversion-1.0.17/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "rustversion-1.0.17/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/rustversion-1.0.17.crate", + "source_checksum_verified": true, + "source_sha256": "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6", + "version": "1.0.17" + }, + { + "component": "self_cell", + "declared_expression": "Apache-2.0 OR GPL-2.0-only", + "documented_distribution_route": "Apache-2.0", + "notice_evidence": [ + { + "notice_path": "licenses/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "source_member": "self_cell-1.3.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt", + "sha256": "f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505", + "source_member": "self_cell-1.3.0/LICENSE-GPLv2" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/self_cell-1.3.0.crate", + "source_checksum_verified": true, + "source_sha256": "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813", + "version": "1.3.0" + }, + { + "component": "serde", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde-1.0.228/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde-1.0.228/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/serde-1.0.228.crate", + "source_checksum_verified": true, + "source_sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", + "version": "1.0.228" + }, + { + "component": "serde_core", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_core-1.0.228/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_core-1.0.228/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/serde_core-1.0.228.crate", + "source_checksum_verified": true, + "source_sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "version": "1.0.228" + }, + { + "component": "serde_derive", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_derive-1.0.228/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_derive-1.0.228/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/serde_derive-1.0.228.crate", + "source_checksum_verified": true, + "source_sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "version": "1.0.228" + }, + { + "component": "serde_json", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "serde_json-1.0.149/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "serde_json-1.0.149/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/serde_json-1.0.149.crate", + "source_checksum_verified": true, + "source_sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86", + "version": "1.0.149" + }, + { + "component": "shlex", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt", + "sha256": "553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583", + "source_member": "shlex-2.0.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt", + "sha256": "4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1", + "source_member": "shlex-2.0.1/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/shlex-2.0.1.crate", + "source_checksum_verified": true, + "source_sha256": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba", + "version": "2.0.1" + }, + { + "component": "smallvec", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "smallvec-1.15.1/LICENSE-APACHE" + }, + { + "notice_path": "licenses/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt", + "sha256": "0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9", + "source_member": "smallvec-1.15.1/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core", + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/smallvec-1.15.1.crate", + "source_checksum_verified": true, + "source_sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03", + "version": "1.15.1" + }, + { + "component": "speedate", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt", + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source_member": "speedate-0.17.0/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/speedate-0.17.0.crate", + "source_checksum_verified": true, + "source_sha256": "aba069c070b5e213f2a094deb7e5ed50ecb092be36102a4f4042e8d2056d060e", + "version": "0.17.0" + }, + { + "component": "stable_deref_trait", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "stable_deref_trait-1.2.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt", + "sha256": "5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7", + "source_member": "stable_deref_trait-1.2.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/stable_deref_trait-1.2.0.crate", + "source_checksum_verified": true, + "source_sha256": "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3", + "version": "1.2.0" + }, + { + "component": "static_assertions", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "static_assertions-1.1.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt", + "sha256": "ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061", + "source_member": "static_assertions-1.1.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/static_assertions-1.1.0.crate", + "source_checksum_verified": true, + "source_sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", + "version": "1.1.0" + }, + { + "component": "strum", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt", + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "source_member": "strum-0.27.2/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/strum-0.27.2.crate", + "source_checksum_verified": true, + "source_sha256": "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf", + "version": "0.27.2" + }, + { + "component": "strum_macros", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt", + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42", + "source_member": "strum_macros-0.27.2/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/strum_macros-0.27.2.crate", + "source_checksum_verified": true, + "source_sha256": "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7", + "version": "0.27.2" + }, + { + "component": "syn", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.111/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.111/LICENSE-MIT" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/syn-2.0.111.crate", + "source_checksum_verified": true, + "source_sha256": "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87", + "version": "2.0.111" + }, + { + "component": "syn", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.119/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.119/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/syn-2.0.119.crate", + "source_checksum_verified": true, + "source_sha256": "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297", + "version": "2.0.119" + }, + { + "component": "syn", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "syn-2.0.82/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "syn-2.0.82/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/syn-2.0.82.crate", + "source_checksum_verified": true, + "source_sha256": "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021", + "version": "2.0.82" + }, + { + "component": "synstructure", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt", + "sha256": "219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59", + "source_member": "synstructure-0.13.1/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/synstructure-0.13.1.crate", + "source_checksum_verified": true, + "source_sha256": "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971", + "version": "0.13.1" + }, + { + "component": "tap", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt", + "sha256": "8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a", + "source_member": "tap-1.0.1/LICENSE.txt" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/tap-1.0.1.crate", + "source_checksum_verified": true, + "source_sha256": "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369", + "version": "1.0.1" + }, + { + "component": "target-lexicon", + "declared_expression": "Apache-2.0 WITH LLVM-exception", + "documented_distribution_route": "Apache-2.0 WITH LLVM-exception", + "notice_evidence": [ + { + "notice_path": "licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.3/LICENSE" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/target-lexicon-0.13.3.crate", + "source_checksum_verified": true, + "source_sha256": "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c", + "version": "0.13.3" + }, + { + "component": "target-lexicon", + "declared_expression": "Apache-2.0 WITH LLVM-exception", + "documented_distribution_route": "Apache-2.0 WITH LLVM-exception", + "notice_evidence": [ + { + "notice_path": "licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/target-lexicon-0.13.4.crate", + "source_checksum_verified": true, + "source_sha256": "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba", + "version": "0.13.4" + }, + { + "component": "target-lexicon", + "declared_expression": "Apache-2.0 WITH LLVM-exception", + "documented_distribution_route": "Apache-2.0 WITH LLVM-exception", + "notice_evidence": [ + { + "notice_path": "licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "target-lexicon-0.13.5/LICENSE" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/target-lexicon-0.13.5.crate", + "source_checksum_verified": true, + "source_sha256": "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca", + "version": "0.13.5" + }, + { + "component": "tinystr", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "tinystr-0.7.6/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/tinystr-0.7.6.crate", + "source_checksum_verified": true, + "source_sha256": "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f", + "version": "0.7.6" + }, + { + "component": "triomphe", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt", + "sha256": "a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c", + "source_member": "triomphe-0.1.15/LICENSE-APACHE" + }, + { + "notice_path": "licenses/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt", + "sha256": "15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c", + "source_member": "triomphe-0.1.15/LICENSE-MIT" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/triomphe-0.1.15.crate", + "source_checksum_verified": true, + "source_sha256": "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39", + "version": "0.1.15" + }, + { + "component": "unicode-ident", + "declared_expression": "(MIT OR Apache-2.0) AND Unicode-DFS-2016", + "documented_distribution_route": "MIT AND Unicode-DFS-2016", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.12/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.12/LICENSE-MIT" + }, + { + "notice_path": "licenses/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt", + "sha256": "68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844", + "source_member": "unicode-ident-1.0.12/LICENSE-UNICODE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/unicode-ident-1.0.12.crate", + "source_checksum_verified": true, + "source_sha256": "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b", + "version": "1.0.12" + }, + { + "component": "unicode-ident", + "declared_expression": "(MIT OR Apache-2.0) AND Unicode-3.0", + "documented_distribution_route": "MIT AND Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.22/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.22/LICENSE-MIT" + }, + { + "notice_path": "licenses/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt", + "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1", + "source_member": "unicode-ident-1.0.22/LICENSE-UNICODE" + } + ], + "projects": [ + "rpds-py" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/unicode-ident-1.0.22.crate", + "source_checksum_verified": true, + "source_sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5", + "version": "1.0.22" + }, + { + "component": "unicode-ident", + "declared_expression": "(MIT OR Apache-2.0) AND Unicode-3.0", + "documented_distribution_route": "MIT AND Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "source_member": "unicode-ident-1.0.24/LICENSE-APACHE" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "unicode-ident-1.0.24/LICENSE-MIT" + }, + { + "notice_path": "licenses/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt", + "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1", + "source_member": "unicode-ident-1.0.24/LICENSE-UNICODE" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/unicode-ident-1.0.24.crate", + "source_checksum_verified": true, + "source_sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "version": "1.0.24" + }, + { + "component": "url", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "url-2.5.8/LICENSE-APACHE" + }, + { + "notice_path": "licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5", + "source_member": "url-2.5.8/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/url-2.5.8.crate", + "source_checksum_verified": true, + "source_sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed", + "version": "2.5.8" + }, + { + "component": "utf16_iter", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt", + "sha256": "b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426", + "source_member": "utf16_iter-1.0.5/COPYRIGHT" + }, + { + "notice_path": "licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "utf16_iter-1.0.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "utf16_iter-1.0.5/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/utf16_iter-1.0.5.crate", + "source_checksum_verified": true, + "source_sha256": "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246", + "version": "1.0.5" + }, + { + "component": "utf8_iter", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt", + "sha256": "c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c", + "source_member": "utf8_iter-1.0.4/COPYRIGHT" + }, + { + "notice_path": "licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "utf8_iter-1.0.4/LICENSE-APACHE" + }, + { + "notice_path": "licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "utf8_iter-1.0.4/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/utf8_iter-1.0.4.crate", + "source_checksum_verified": true, + "source_sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be", + "version": "1.0.4" + }, + { + "component": "uuid", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "uuid-1.23.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt", + "sha256": "436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d", + "source_member": "uuid-1.23.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/uuid-1.23.0.crate", + "source_checksum_verified": true, + "source_sha256": "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9", + "version": "1.23.0" + }, + { + "component": "vcpkg", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt", + "sha256": "60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3", + "source_member": "vcpkg-0.2.15/LICENSE-APACHE" + }, + { + "notice_path": "licenses/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt", + "sha256": "016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc", + "source_member": "vcpkg-0.2.15/LICENSE-MIT" + } + ], + "projects": [ + "cryptography" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/vcpkg-0.2.15.crate", + "source_checksum_verified": true, + "source_sha256": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426", + "version": "0.2.15" + }, + { + "component": "version_check", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "version_check-0.9.5/LICENSE-APACHE" + }, + { + "notice_path": "licenses/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt", + "sha256": "b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e", + "source_member": "version_check-0.9.5/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/version_check-0.9.5.crate", + "source_checksum_verified": true, + "source_sha256": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a", + "version": "0.9.5" + }, + { + "component": "wasi", + "declared_expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-APACHE" + }, + { + "notice_path": "licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception" + }, + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "wasi-0.14.2+wasi-0.2.4/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasi-0.14.2+wasi-0.2.4.crate", + "source_checksum_verified": true, + "source_sha256": "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3", + "version": "0.14.2+wasi-0.2.4" + }, + { + "component": "wasm-bindgen", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-0.2.100/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-0.2.100/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasm-bindgen-0.2.100.crate", + "source_checksum_verified": true, + "source_sha256": "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5", + "version": "0.2.100" + }, + { + "component": "wasm-bindgen-backend", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-backend-0.2.100/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-backend-0.2.100/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasm-bindgen-backend-0.2.100.crate", + "source_checksum_verified": true, + "source_sha256": "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6", + "version": "0.2.100" + }, + { + "component": "wasm-bindgen-macro", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-macro-0.2.100/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-macro-0.2.100/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasm-bindgen-macro-0.2.100.crate", + "source_checksum_verified": true, + "source_sha256": "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407", + "version": "0.2.100" + }, + { + "component": "wasm-bindgen-macro-support", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-macro-support-0.2.100/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-macro-support-0.2.100/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasm-bindgen-macro-support-0.2.100.crate", + "source_checksum_verified": true, + "source_sha256": "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de", + "version": "0.2.100" + }, + { + "component": "wasm-bindgen-shared", + "declared_expression": "MIT OR Apache-2.0", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2", + "source_member": "wasm-bindgen-shared-0.2.100/LICENSE-APACHE" + }, + { + "notice_path": "licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397", + "source_member": "wasm-bindgen-shared-0.2.100/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wasm-bindgen-shared-0.2.100.crate", + "source_checksum_verified": true, + "source_sha256": "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d", + "version": "0.2.100" + }, + { + "component": "wit-bindgen-rt", + "declared_expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/wit-bindgen-rt-0.39.0-LICENSE-MIT.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "Pinned upstream VCS LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "Target-specific source: getrandom 0.3.3 selects wasi only for wasm32/wasi/p2; wasi depends on this crate. Not treated as a Linux runtime library.", + "source_archive_path": "sources/crates/wit-bindgen-rt-0.39.0.crate", + "source_checksum_verified": true, + "source_sha256": "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1", + "version": "0.39.0" + }, + { + "component": "write16", + "declared_expression": "Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt", + "sha256": "3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0", + "source_member": "write16-1.0.0/COPYRIGHT" + }, + { + "notice_path": "licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source_member": "write16-1.0.0/LICENSE-APACHE" + }, + { + "notice_path": "licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c", + "source_member": "write16-1.0.0/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/write16-1.0.0.crate", + "source_checksum_verified": true, + "source_sha256": "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936", + "version": "1.0.0" + }, + { + "component": "writeable", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "writeable-0.5.5/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/writeable-0.5.5.crate", + "source_checksum_verified": true, + "source_sha256": "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51", + "version": "0.5.5" + }, + { + "component": "wyz", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt", + "sha256": "411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5", + "source_member": "wyz-0.5.1/LICENSE.txt" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/wyz-0.5.1.crate", + "source_checksum_verified": true, + "source_sha256": "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed", + "version": "0.5.1" + }, + { + "component": "yoke", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "yoke-0.7.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/yoke-0.7.4.crate", + "source_checksum_verified": true, + "source_sha256": "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5", + "version": "0.7.4" + }, + { + "component": "yoke-derive", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "yoke-derive-0.7.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/yoke-derive-0.7.4.crate", + "source_checksum_verified": true, + "source_sha256": "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95", + "version": "0.7.4" + }, + { + "component": "zerocopy", + "declared_expression": "BSD-2-Clause OR Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt", + "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3", + "source_member": "zerocopy-0.8.25/LICENSE-APACHE" + }, + { + "notice_path": "licenses/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt", + "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32", + "source_member": "zerocopy-0.8.25/LICENSE-BSD" + }, + { + "notice_path": "licenses/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt", + "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df", + "source_member": "zerocopy-0.8.25/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerocopy-0.8.25.crate", + "source_checksum_verified": true, + "source_sha256": "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb", + "version": "0.8.25" + }, + { + "component": "zerocopy-derive", + "declared_expression": "BSD-2-Clause OR Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt", + "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3", + "source_member": "zerocopy-derive-0.8.25/LICENSE-APACHE" + }, + { + "notice_path": "licenses/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt", + "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32", + "source_member": "zerocopy-derive-0.8.25/LICENSE-BSD" + }, + { + "notice_path": "licenses/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt", + "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df", + "source_member": "zerocopy-derive-0.8.25/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerocopy-derive-0.8.25.crate", + "source_checksum_verified": true, + "source_sha256": "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef", + "version": "0.8.25" + }, + { + "component": "zerofrom", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerofrom-0.1.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerofrom-0.1.4.crate", + "source_checksum_verified": true, + "source_sha256": "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55", + "version": "0.1.4" + }, + { + "component": "zerofrom-derive", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerofrom-derive-0.1.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerofrom-derive-0.1.4.crate", + "source_checksum_verified": true, + "source_sha256": "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5", + "version": "0.1.4" + }, + { + "component": "zerovec", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerovec-0.10.4/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerovec-0.10.4.crate", + "source_checksum_verified": true, + "source_sha256": "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079", + "version": "0.10.4" + }, + { + "component": "zerovec-derive", + "declared_expression": "Unicode-3.0", + "documented_distribution_route": "Unicode-3.0", + "notice_evidence": [ + { + "notice_path": "licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2", + "source_member": "zerovec-derive-0.10.3/LICENSE" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zerovec-derive-0.10.3.crate", + "source_checksum_verified": true, + "source_sha256": "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6", + "version": "0.10.3" + }, + { + "component": "zmij", + "declared_expression": "MIT", + "documented_distribution_route": "MIT", + "notice_evidence": [ + { + "notice_path": "licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_member": "zmij-1.0.6/LICENSE-MIT" + } + ], + "projects": [ + "pydantic_core" + ], + "reason": "Use an upstream-permitted permissive alternative compatible with GPLv3; retain every AND conjunct, original expression and upstream notice. This does not replace upstream licensing.", + "role": "SBOM/lock inventory; runtime versus build role is not inferred merely from inclusion", + "source_archive_path": "sources/crates/zmij-1.0.6.crate", + "source_checksum_verified": true, + "source_sha256": "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f", + "version": "1.0.6" + } + ], + "reviewed_preview1_binary_sha256": "be0a838c31d0603eb45169167ec780284407040cf3ca50a56b3bae59b1185845", + "reviewed_repository_commit": "eb31ce593ba5362c17b93da1e73dd7236d715882", + "schema_version": 1, + "sealed_source_archive": { + "bytes": 82606998, + "filename": "FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz", + "sha256": "64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a" + }, + "selection_policy": "Rely on an allowed MIT branch where offered, otherwise an allowed Apache branch; preserve all AND conjuncts and original upstream alternatives. This records a distribution route, not a relicensing of third-party code.", + "status": "DEPENDENCY_TERMS_TECHNICAL_REVIEW_PASS_WITH_DOCUMENTED_DELIVERY_CONDITIONS", + "technical_decision": "The reviewed native dependencies have a documented GPLv3-compatible permitted route and concrete source/notice delivery evidence. No license incompatibility is identified from the reviewed declarations under these routes. The selected unmodified certifi source-delivery control is technically accepted; changing the CA trust store is not required by this evidence." +} diff --git a/licensing/third-party/NATIVE-DEPENDENCY-TERMS.md b/licensing/third-party/NATIVE-DEPENDENCY-TERMS.md new file mode 100644 index 0000000..586626b --- /dev/null +++ b/licensing/third-party/NATIVE-DEPENDENCY-TERMS.md @@ -0,0 +1,46 @@ +# Native dependency terms — R2 + +This review records an allowed distribution route for the actual Preview1 native artifacts and their supplied source/notices. It does not replace upstream licensing or authorize publication. + +## Actual native objects + +| Component | Original terms | Route used for this GPLv3 combination | +|---|---|---| +| cryptography 50.0.1 | Apache-2.0 OR BSD-3-Clause | Apache-2.0 | +| pydantic_core 2.46.5 | MIT | MIT | +| rpds-py 2026.6.3 | MIT | MIT | +| cffi 2.1.1 | MIT-0 | MIT-0 | + +## Upstream alternatives and additional terms + +The exact original expression remains in the JSON matrix and source. MIT is selected where offered; Apache is used for self_cell rather than its GPL-2.0-only alternative. r-efi uses MIT rather than its LGPL alternative. Every Unicode AND condition remains applicable. + +| Original crate expression | Documented route | Unique name/version entries | +|---|---|---:| +| (MIT OR Apache-2.0) AND Unicode-3.0 | MIT AND Unicode-3.0 | 2 | +| (MIT OR Apache-2.0) AND Unicode-DFS-2016 | MIT AND Unicode-DFS-2016 | 1 | +| Apache-2.0 | Apache-2.0 | 1 | +| Apache-2.0 OR GPL-2.0-only | Apache-2.0 | 1 | +| Apache-2.0 OR MIT | MIT | 9 | +| Apache-2.0 WITH LLVM-exception | Apache-2.0 WITH LLVM-exception | 3 | +| Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | MIT | 2 | +| BSD-2-Clause OR Apache-2.0 OR MIT | MIT | 2 | +| BSD-3-Clause | BSD-3-Clause | 2 | +| MIT | MIT | 17 | +| MIT OR Apache-2.0 | MIT | 80 | +| MIT OR Apache-2.0 OR LGPL-2.1-or-later | MIT | 1 | +| Unicode-3.0 | Unicode-3.0 | 19 | +| Unlicense OR MIT | MIT | 2 | +| Zlib | Zlib | 1 | + +## Concrete delivery controls + +All four actual native objects match official wheel payloads. The sealed source archive supplies exact project sdists, 143 lockfile-verified crates and associated OpenSSL/libffi sources, upstream Git source, CA data and runtime-source supplements. All 135 indexed upstream notice texts match the legal payload. The full MPL text is delivered separately, and R2 adds the pinned upstream MIT notice for wit-bindgen-rt, whose registry source crate omitted a standalone notice. This gives 137 legal texts in the payload, not 137 members inside the unchanged archive. + +Deliver R2-SUPPLEMENTS.json and both separate notice texts together with the sealed dependency-source archive, matching installer and complete project source. Preserve all upstream notices and identify actual modifications where required. The exact source URLs, hashes and notice mappings are in DEPENDENCY-SOURCES.json, NATIVE-DEPENDENCY-TERMS.json and R2-SUPPLEMENTS.json. + +## Certifi decision and evidence limits + +The unmodified certifi CA bundle matches its official wheel, sdist and pinned Git source; its original notice and full MPL are supplied. MPL source delivery does not require an independent bit-identical regeneration experiment or automatically require recovering every historical Mozilla generation revision. The unknown historical revision remains a provenance limitation, not a new automatic blocker. The documented source-delivery control is technically accepted for this unchanged material; preserve clear source access and MPL rights when distributing. For a qualifying GPL Larger Work, preserve the MPL option while applying the MPL secondary-license permission as needed. + +Some lock/SBOM entries are build-only or target-specific. In particular, getrandom selects wasi/wit-bindgen for wasm32/wasi/p2, not as a Linux runtime dependency; the full source archive still receives its supplemental notice. An independent native-wheel rebuild was not performed, and that experiment is not itself required by GPL/MPL. This review finds no incompatible compulsory branch in the reviewed terms when the documented alternatives and AND conditions are respected. It is a focused technical review, not an integral legal opinion or public-release approval. diff --git a/licensing/third-party/R2-SUPPLEMENTS.json b/licensing/third-party/R2-SUPPLEMENTS.json new file mode 100644 index 0000000..366e03d --- /dev/null +++ b/licensing/third-party/R2-SUPPLEMENTS.json @@ -0,0 +1,39 @@ +{ + "applies_to": "R2 legal payload alongside the unchanged Preview1 dependency source archive", + "delivery_condition": "Distribute these legal supplements with the unchanged source bundle, matching installer and complete project source. They are outside the sealed archive; its hash and member counts do not change.", + "schema_version": 1, + "sealed_archive": { + "bytes": 82606998, + "filename": "FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz", + "sha256": "64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a" + }, + "supplements": [ + { + "bytes": 16727, + "component": "certifi / MPL-2.0", + "license_branch": "MPL-2.0 full text retained", + "path": "licenses/MPL-2.0.txt", + "sha256": "66a3107d5ad6a058aab753eaac2047ccb2ed0e39465dd0fe5844da3e300d5172", + "source_url": "https://raw.githubusercontent.com/spdx/license-list-data/main/text/MPL-2.0.txt" + }, + { + "bytes": 1023, + "component": "wit-bindgen-rt 0.39.0", + "declared_expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", + "documented_distribution_route": "MIT", + "http_status": 200, + "path": "licenses/wit-bindgen-rt-0.39.0-LICENSE-MIT.txt", + "reason": "Complete the standalone MIT notice omitted from the exact registry crate; retain the source crate and its original alternative expression. Attach this supplement when distributing the full sealed source bundle, even though this is a WASI target dependency.", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "source_url": "https://raw.githubusercontent.com/bytecodealliance/wit-bindgen/f2393e6e98fa5f9236cac580db8a3fc9de6a4b70/LICENSE-MIT", + "tls_verified": true, + "upstream_commit": "f2393e6e98fa5f9236cac580db8a3fc9de6a4b70" + } + ], + "text_counts": { + "full_mpl_separate_supplement": 1, + "r2_wit_mit_separate_supplement": 1, + "sealed_archive_unique_upstream_texts": 135, + "total_payload_texts_after_r2": 137 + } +} diff --git a/licensing/third-party/SOURCE-AVAILABILITY.md b/licensing/third-party/SOURCE-AVAILABILITY.md new file mode 100644 index 0000000..0db7213 --- /dev/null +++ b/licensing/third-party/SOURCE-AVAILABILITY.md @@ -0,0 +1,18 @@ +# Sources accompanying FrameBridge 0.3.1 + +The matching release set is intended to be delivered together through the project download area. This record identifies the required assets; it does not assert that external access has already been opened. + +- FrameBridge-0.3.1.deb, Debian version 0.3.1. +- FrameBridge-0.3.1-source.tar.gz: the corresponding FrameBridge implementation, build scripts, tests and legal records. +- FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz: immutable shared Python/Rust dependency source asset, 82,606,998 bytes, SHA-256 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a. The historical filename is deliberately retained; verify bytes rather than inferring the current project version from that name. +- FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz: exact source module archives for the 47 Go dependencies embedded in the retained OpenAI tunnel executable, plus retained notices and provenance records. Its release hash is recorded in the adjacent release checksum manifest and TUNNEL-DEPENDENCY-SOURCES.json. + +The complete FrameBridge source also carries [R2-SUPPLEMENTS.json](R2-SUPPLEMENTS.json), the wit-bindgen-rt MIT supplement, the full MPL-2.0 text, Python/native notices and the [tunnel-client notices](tunnel-client/README.md). Do not omit those because a shared source archive has an older filename. + +The shared asset contains five Python source distributions, 143 lockfile-verified crate archives, pinned OpenSSL/libffi source evidence, upstream Git archives, the exact Certifi CA bundle and identified Rust supplements. The R2 notice supplement does not silently alter that immutable asset. + +The OpenAI executable identifies a development module without a VCS revision; its exact original own-source commit is not established. Its Apache-2.0 notice source and the verified Go dependency versions are documented separately, without claiming an independent rebuild or complete upstream provenance. + +For the unchanged Certifi component, retain the exact source assets, original notice, full MPL-2.0 terms and recipient access instructions. The historical Mozilla certdata revision remains unidentified. See [NATIVE-DEPENDENCY-TERMS.md](NATIVE-DEPENDENCY-TERMS.md) and [docs/THIRD-PARTY-RELEASE-REVIEW.md](../../docs/THIRD-PARTY-RELEASE-REVIEW.md). + +Before public distribution, verify every actual download and SHA-256, provide the matching installer and source together, and retain source access for distributed versions. Private preparation or a link to a planned path is not public delivery. diff --git a/licensing/third-party/THIRD-PARTY-NOTICES.md b/licensing/third-party/THIRD-PARTY-NOTICES.md new file mode 100644 index 0000000..f6e53c0 --- /dev/null +++ b/licensing/third-party/THIRD-PARTY-NOTICES.md @@ -0,0 +1,13 @@ +# Third-party notices and source availability + +This private community-preview supplement preserves upstream source archives and legal notices for dependencies used by FrameBridge. Original FrameBridge code is covered separately by GPL-3.0-only; that project license does not replace dependency licenses or author attributions. + +The four shipped native objects were matched byte-for-byte to official release wheels. Their pinned project source distributions are included, together with 143 Cargo.lock-verified crate archives, SBOM-pinned OpenSSL source, CFFI's pinned static-libffi build-source evidence, the exact certifi CA bundle and upstream source, and identified Rust compiler/runtime source supplements. Binary wheels remain in the private verification cache and are not in this source archive. + +See [DEPENDENCY-SOURCES.json](DEPENDENCY-SOURCES.json) for exact versions, public HTTPS locations, hashes, source identities, notice mappings and limitations. Full legal texts are preserved verbatim under [licenses/](licenses/), indexed by SHA-256 to avoid duplicate copies. Upstream authors and copyright notices remain in those texts and source archives. + +Path fields in [DEPENDENCY-SOURCES.json](DEPENDENCY-SOURCES.json) and [UPSTREAM-SOURCES-MANIFEST.json](UPSTREAM-SOURCES-MANIFEST.json) describe members relative to the top-level directory of the sealed dependency-source archive, not paths in this installed legal directory. For notices, an archive path `notices/texts/.txt` maps here to `licenses/.txt`. The upstream manifest describes the sealed archive and is not a checksum manifest for renamed installed copies. + +The source supplement is `FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz`. Supply it beside the matching installer and complete FrameBridge source when distributing that candidate, with its SHA256SUMS and index. This document does not invent a future hosting address or assert that a source offer has already been published. + +Some SBOM entries may be used only during compilation. Their inclusion in this source review is not a claim that they are separate runtime libraries. License alternatives, target/build classification and the certifi preferred-source question remain documented review items. No independently reproducible native rebuild or blanket legal-compliance conclusion is claimed. diff --git a/licensing/third-party/TUNNEL-DEPENDENCY-SOURCES.json b/licensing/third-party/TUNNEL-DEPENDENCY-SOURCES.json new file mode 100644 index 0000000..af35850 --- /dev/null +++ b/licensing/third-party/TUNNEL-DEPENDENCY-SOURCES.json @@ -0,0 +1,12 @@ +{ + "archive": "FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz", + "size_bytes": 26941745, + "sha256": "6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9", + "binary_sha256": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "go_modules_h1_verified": 47, + "module_legal_files": 61, + "archive_entries": 116, + "own_component_license": "Apache-2.0", + "own_source_vcs_revision_known": false, + "scope": "Exact Go dependency sources and retained upstream notices; not a claim of exact tunnel executable build provenance." +} diff --git a/licensing/third-party/UPSTREAM-SOURCES-MANIFEST.json b/licensing/third-party/UPSTREAM-SOURCES-MANIFEST.json new file mode 100644 index 0000000..db0c085 --- /dev/null +++ b/licensing/third-party/UPSTREAM-SOURCES-MANIFEST.json @@ -0,0 +1,1538 @@ +{ + "archive_filename": "FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz", + "deterministic_encoding": { + "gid": 0, + "gzip_mtime": 0, + "sorted_paths": true, + "tar_mtime": 0, + "uid": 0 + }, + "files": [ + { + "bytes": 221431, + "path": "SOURCE-AVAILABILITY.json", + "sha256": "8821e0a289f15d355d8cd867592bbfcd7617c21eedd860d7aa9889cd454d935d" + }, + { + "bytes": 1842, + "path": "THIRD-PARTY-NOTICES.md", + "sha256": "20164557ef385b38c9ffc98ecff47a4eb4e8d54d5448cbcd6bd8945766ded30d" + }, + { + "bytes": 45685, + "path": "evidence/sboms/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json", + "sha256": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0" + }, + { + "bytes": 1206, + "path": "evidence/sboms/cryptography-50.0.1.dist-info/sboms/sbom.json", + "sha256": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720" + }, + { + "bytes": 125376, + "path": "evidence/sboms/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json", + "sha256": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9" + }, + { + "bytes": 20594, + "path": "evidence/sboms/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json", + "sha256": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763" + }, + { + "bytes": 1180, + "path": "evidence/upstream-pinned/certifi-f4bc676b/.github/workflows/bump.yml", + "sha256": "85c36da81dec70414ce530ecf9df25ce020190d8ddf50f625e29dcae5978c84f" + }, + { + "bytes": 447, + "path": "evidence/upstream-pinned/certifi-f4bc676b/.github/workflows/lock.yml", + "sha256": "a5bb4cff201af3793d7a39dd8d105130bd794d4426e910dd368796b9f21075be" + }, + { + "bytes": 1324, + "path": "evidence/upstream-pinned/certifi-f4bc676b/.github/workflows/release.yml", + "sha256": "72013730856a931f920693afa18b775f8386a544150d8a9655ebb592650765e7" + }, + { + "bytes": 989, + "path": "evidence/upstream-pinned/certifi-f4bc676b/LICENSE", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7" + }, + { + "bytes": 155, + "path": "evidence/upstream-pinned/certifi-f4bc676b/Makefile", + "sha256": "ca810f526668d1cffca90428cb6d13030a66afdcd3c5ccf88e5f91fc7956d04c" + }, + { + "bytes": 1157, + "path": "evidence/upstream-pinned/certifi-f4bc676b/README.rst", + "sha256": "b36c3a5405b444d155327a647cce2ec27b564bb04710359e6322205ee04994e4" + }, + { + "bytes": 25212, + "path": "evidence/upstream-pinned/cffi-fd33e770/.github/workflows/ci.yaml", + "sha256": "b1910ddbfc69d2fbe41f1493cb564658d0970522bfeb5e6fe93d3288c802736c" + }, + { + "bytes": 1592, + "path": "evidence/upstream-pinned/cffi-fd33e770/pyproject.toml", + "sha256": "5319b77e4b41ab9bab0f352a3e2ef6277b0626c4c239a0736f9e15dbc417d693" + }, + { + "bytes": 1060, + "path": "evidence/upstream-pinned/cffi-fd33e770/src/c/libffi_x86_x64/LICENSE", + "sha256": "40a537c7d74baeed3831304e92d1664ababd7ad8a3cf29f11a257c9e47145248" + }, + { + "bytes": 1082, + "path": "notices/texts/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt", + "sha256": "016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc" + }, + { + "bytes": 126, + "path": "notices/texts/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt", + "sha256": "01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f" + }, + { + "bytes": 1145, + "path": "notices/texts/04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608.txt", + "sha256": "04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608" + }, + { + "bytes": 1057, + "path": "notices/texts/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt", + "sha256": "0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb" + }, + { + "bytes": 1071, + "path": "notices/texts/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt", + "sha256": "061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e" + }, + { + "bytes": 984, + "path": "notices/texts/0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39.txt", + "sha256": "0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39" + }, + { + "bytes": 1072, + "path": "notices/texts/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt", + "sha256": "0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9" + }, + { + "bytes": 1064, + "path": "notices/texts/0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6.txt", + "sha256": "0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6" + }, + { + "bytes": 1543, + "path": "notices/texts/0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1.txt", + "sha256": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1" + }, + { + "bytes": 10174, + "path": "notices/texts/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt", + "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594" + }, + { + "bytes": 1076, + "path": "notices/texts/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt", + "sha256": "0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7" + }, + { + "bytes": 1081, + "path": "notices/texts/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt", + "sha256": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f" + }, + { + "bytes": 1066, + "path": "notices/texts/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt", + "sha256": "123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e" + }, + { + "bytes": 1079, + "path": "notices/texts/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt", + "sha256": "13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02" + }, + { + "bytes": 1074, + "path": "notices/texts/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt", + "sha256": "15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c" + }, + { + "bytes": 1571, + "path": "notices/texts/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt", + "sha256": "172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af" + }, + { + "bytes": 1060, + "path": "notices/texts/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt", + "sha256": "1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df" + }, + { + "bytes": 1541, + "path": "notices/texts/1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04.txt", + "sha256": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04" + }, + { + "bytes": 1070, + "path": "notices/texts/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt", + "sha256": "1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be" + }, + { + "bytes": 1117, + "path": "notices/texts/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt", + "sha256": "209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b" + }, + { + "bytes": 1072, + "path": "notices/texts/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt", + "sha256": "20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e" + }, + { + "bytes": 9899, + "path": "notices/texts/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt", + "sha256": "20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58" + }, + { + "bytes": 1052, + "path": "notices/texts/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt", + "sha256": "219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59" + }, + { + "bytes": 1023, + "path": "notices/texts/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3" + }, + { + "bytes": 12243, + "path": "notices/texts/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt", + "sha256": "268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5" + }, + { + "bytes": 1054, + "path": "notices/texts/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt", + "sha256": "27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac" + }, + { + "bytes": 1071, + "path": "notices/texts/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt", + "sha256": "29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21" + }, + { + "bytes": 1130, + "path": "notices/texts/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt", + "sha256": "29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4" + }, + { + "bytes": 208, + "path": "notices/texts/2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8.txt", + "sha256": "2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8" + }, + { + "bytes": 1080, + "path": "notices/texts/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt", + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a" + }, + { + "bytes": 1057, + "path": "notices/texts/314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909.txt", + "sha256": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909" + }, + { + "bytes": 770, + "path": "notices/texts/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt", + "sha256": "3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0" + }, + { + "bytes": 9807, + "path": "notices/texts/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt", + "sha256": "32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9" + }, + { + "bytes": 1072, + "path": "notices/texts/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt", + "sha256": "333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734" + }, + { + "bytes": 9724, + "path": "notices/texts/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt", + "sha256": "35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab" + }, + { + "bytes": 1046, + "path": "notices/texts/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt", + "sha256": "36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9" + }, + { + "bytes": 1057, + "path": "notices/texts/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt", + "sha256": "378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397" + }, + { + "bytes": 1124, + "path": "notices/texts/37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361.txt", + "sha256": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361" + }, + { + "bytes": 14088, + "path": "notices/texts/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt", + "sha256": "3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7" + }, + { + "bytes": 13936, + "path": "notices/texts/3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf.txt", + "sha256": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf" + }, + { + "bytes": 197, + "path": "notices/texts/3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb.txt", + "sha256": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb" + }, + { + "bytes": 1053, + "path": "notices/texts/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt", + "sha256": "3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c" + }, + { + "bytes": 1213, + "path": "notices/texts/4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b.txt", + "sha256": "4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b" + }, + { + "bytes": 1082, + "path": "notices/texts/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt", + "sha256": "411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5" + }, + { + "bytes": 1057, + "path": "notices/texts/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt", + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c" + }, + { + "bytes": 1157, + "path": "notices/texts/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt", + "sha256": "436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d" + }, + { + "bytes": 1092, + "path": "notices/texts/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt", + "sha256": "4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1" + }, + { + "bytes": 1508, + "path": "notices/texts/4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d.txt", + "sha256": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d" + }, + { + "bytes": 1057, + "path": "notices/texts/4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439.txt", + "sha256": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439" + }, + { + "bytes": 1081, + "path": "notices/texts/5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0.txt", + "sha256": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0" + }, + { + "bytes": 566, + "path": "notices/texts/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt", + "sha256": "553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583" + }, + { + "bytes": 1123, + "path": "notices/texts/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt", + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63" + }, + { + "bytes": 1056, + "path": "notices/texts/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt", + "sha256": "5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7" + }, + { + "bytes": 1071, + "path": "notices/texts/5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d.txt", + "sha256": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d" + }, + { + "bytes": 1532, + "path": "notices/texts/602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb.txt", + "sha256": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb" + }, + { + "bytes": 11050, + "path": "notices/texts/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt", + "sha256": "60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3" + }, + { + "bytes": 9723, + "path": "notices/texts/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a" + }, + { + "bytes": 1071, + "path": "notices/texts/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt", + "sha256": "6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb" + }, + { + "bytes": 1059, + "path": "notices/texts/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt", + "sha256": "65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee" + }, + { + "bytes": 1132, + "path": "notices/texts/67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195.txt", + "sha256": "67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195" + }, + { + "bytes": 2315, + "path": "notices/texts/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt", + "sha256": "68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844" + }, + { + "bytes": 10851, + "path": "notices/texts/69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd.txt", + "sha256": "69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd" + }, + { + "bytes": 10282, + "path": "notices/texts/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt", + "sha256": "6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51" + }, + { + "bytes": 1049, + "path": "notices/texts/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt", + "sha256": "7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349" + }, + { + "bytes": 2847, + "path": "notices/texts/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt", + "sha256": "74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3" + }, + { + "bytes": 1085, + "path": "notices/texts/797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5.txt", + "sha256": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5" + }, + { + "bytes": 1212, + "path": "notices/texts/799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d.txt", + "sha256": "799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d" + }, + { + "bytes": 1071, + "path": "notices/texts/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt", + "sha256": "7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0" + }, + { + "bytes": 1091, + "path": "notices/texts/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt", + "sha256": "7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e" + }, + { + "bytes": 10175, + "path": "notices/texts/7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a.txt", + "sha256": "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a" + }, + { + "bytes": 1090, + "path": "notices/texts/804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3.txt", + "sha256": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3" + }, + { + "bytes": 1556, + "path": "notices/texts/80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9.txt", + "sha256": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9" + }, + { + "bytes": 1511, + "path": "notices/texts/80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e.txt", + "sha256": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e" + }, + { + "bytes": 10850, + "path": "notices/texts/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt", + "sha256": "8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90" + }, + { + "bytes": 1275, + "path": "notices/texts/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt", + "sha256": "83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32" + }, + { + "bytes": 665, + "path": "notices/texts/861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17.txt", + "sha256": "861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17" + }, + { + "bytes": 1109, + "path": "notices/texts/882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f.txt", + "sha256": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f" + }, + { + "bytes": 10860, + "path": "notices/texts/8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb.txt", + "sha256": "8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb" + }, + { + "bytes": 1062, + "path": "notices/texts/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt", + "sha256": "8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726" + }, + { + "bytes": 1090, + "path": "notices/texts/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt", + "sha256": "8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a" + }, + { + "bytes": 1076, + "path": "notices/texts/8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da.txt", + "sha256": "8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da" + }, + { + "bytes": 1072, + "path": "notices/texts/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt", + "sha256": "8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42" + }, + { + "bytes": 569, + "path": "notices/texts/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt", + "sha256": "90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5" + }, + { + "bytes": 322, + "path": "notices/texts/925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07.txt", + "sha256": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07" + }, + { + "bytes": 18412, + "path": "notices/texts/9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181.txt", + "sha256": "9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181" + }, + { + "bytes": 19251, + "path": "notices/texts/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt", + "sha256": "99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe" + }, + { + "bytes": 1475, + "path": "notices/texts/9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d.txt", + "sha256": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d" + }, + { + "bytes": 11350, + "path": "notices/texts/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt", + "sha256": "9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3" + }, + { + "bytes": 10847, + "path": "notices/texts/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2" + }, + { + "bytes": 10849, + "path": "notices/texts/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt", + "sha256": "a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c" + }, + { + "bytes": 1075, + "path": "notices/texts/a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b.txt", + "sha256": "a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b" + }, + { + "bytes": 1076, + "path": "notices/texts/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt", + "sha256": "a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1" + }, + { + "bytes": 1129, + "path": "notices/texts/a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3.txt", + "sha256": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3" + }, + { + "bytes": 11360, + "path": "notices/texts/aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe.txt", + "sha256": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe" + }, + { + "bytes": 10849, + "path": "notices/texts/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt", + "sha256": "aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf" + }, + { + "bytes": 1093, + "path": "notices/texts/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt", + "sha256": "ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c" + }, + { + "bytes": 15078, + "path": "notices/texts/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt", + "sha256": "ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b" + }, + { + "bytes": 1107, + "path": "notices/texts/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt", + "sha256": "afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6" + }, + { + "bytes": 856, + "path": "notices/texts/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt", + "sha256": "b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744" + }, + { + "bytes": 1072, + "path": "notices/texts/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt", + "sha256": "b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5" + }, + { + "bytes": 16706, + "path": "notices/texts/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt", + "sha256": "b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d" + }, + { + "bytes": 1068, + "path": "notices/texts/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt", + "sha256": "b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1" + }, + { + "bytes": 1085, + "path": "notices/texts/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt", + "sha256": "b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf" + }, + { + "bytes": 1085, + "path": "notices/texts/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt", + "sha256": "b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e" + }, + { + "bytes": 1743, + "path": "notices/texts/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt", + "sha256": "b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426" + }, + { + "bytes": 1022, + "path": "notices/texts/b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e.txt", + "sha256": "b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e" + }, + { + "bytes": 1072, + "path": "notices/texts/beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901.txt", + "sha256": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901" + }, + { + "bytes": 395, + "path": "notices/texts/c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a.txt", + "sha256": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a" + }, + { + "bytes": 1742, + "path": "notices/texts/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt", + "sha256": "c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c" + }, + { + "bytes": 3652, + "path": "notices/texts/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt", + "sha256": "c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d" + }, + { + "bytes": 11358, + "path": "notices/texts/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt", + "sha256": "c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08" + }, + { + "bytes": 1540, + "path": "notices/texts/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt", + "sha256": "c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985" + }, + { + "bytes": 11357, + "path": "notices/texts/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "bytes": 1120, + "path": "notices/texts/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt", + "sha256": "c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763" + }, + { + "bytes": 11358, + "path": "notices/texts/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + { + "bytes": 1518, + "path": "notices/texts/dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf.txt", + "sha256": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf" + }, + { + "bytes": 1068, + "path": "notices/texts/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt", + "sha256": "e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f" + }, + { + "bytes": 989, + "path": "notices/texts/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt", + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7" + }, + { + "bytes": 1069, + "path": "notices/texts/e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40.txt", + "sha256": "e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40" + }, + { + "bytes": 1072, + "path": "notices/texts/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt", + "sha256": "ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061" + }, + { + "bytes": 1058, + "path": "notices/texts/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt", + "sha256": "ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30" + }, + { + "bytes": 1071, + "path": "notices/texts/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt", + "sha256": "eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9" + }, + { + "bytes": 1103, + "path": "notices/texts/eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998.txt", + "sha256": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998" + }, + { + "bytes": 1526, + "path": "notices/texts/efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371.txt", + "sha256": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371" + }, + { + "bytes": 2195, + "path": "notices/texts/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt", + "sha256": "f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2" + }, + { + "bytes": 618, + "path": "notices/texts/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt", + "sha256": "f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941" + }, + { + "bytes": 1082, + "path": "notices/texts/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt", + "sha256": "f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f" + }, + { + "bytes": 1125, + "path": "notices/texts/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt", + "sha256": "f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9" + }, + { + "bytes": 1995, + "path": "notices/texts/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt", + "sha256": "f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1" + }, + { + "bytes": 18091, + "path": "notices/texts/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt", + "sha256": "f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505" + }, + { + "bytes": 3067, + "path": "notices/texts/fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c.txt", + "sha256": "fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c" + }, + { + "bytes": 1518, + "path": "notices/texts/fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5.txt", + "sha256": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5" + }, + { + "bytes": 1083, + "path": "notices/texts/fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d.txt", + "sha256": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d" + }, + { + "bytes": 1060, + "path": "notices/texts/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt", + "sha256": "ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2" + }, + { + "bytes": 3693, + "path": "notices/texts/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt", + "sha256": "ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7" + }, + { + "bytes": 240216, + "path": "sources/certifi/cacert.pem", + "sha256": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f" + }, + { + "bytes": 43413, + "path": "sources/crates/ahash-0.8.12.crate", + "sha256": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" + }, + { + "bytes": 183311, + "path": "sources/crates/aho-corasick-1.1.3.crate", + "sha256": "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" + }, + { + "bytes": 63622, + "path": "sources/crates/allocator-api2-0.2.21.crate", + "sha256": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + }, + { + "bytes": 16228, + "path": "sources/crates/archery-1.2.2.crate", + "sha256": "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + }, + { + "bytes": 50502, + "path": "sources/crates/asn1-0.24.1.crate", + "sha256": "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1" + }, + { + "bytes": 9534, + "path": "sources/crates/asn1_derive-0.24.1.crate", + "sha256": "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a" + }, + { + "bytes": 16524, + "path": "sources/crates/autocfg-1.3.0.crate", + "sha256": "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + }, + { + "bytes": 81597, + "path": "sources/crates/base64-0.22.1.crate", + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + }, + { + "bytes": 93578, + "path": "sources/crates/base64-0.23.0.crate", + "sha256": "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + }, + { + "bytes": 51395, + "path": "sources/crates/bitflags-2.13.1.crate", + "sha256": "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + }, + { + "bytes": 47913, + "path": "sources/crates/bitflags-2.9.1.crate", + "sha256": "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + }, + { + "bytes": 224375, + "path": "sources/crates/bitvec-1.0.1.crate", + "sha256": "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" + }, + { + "bytes": 96414, + "path": "sources/crates/bumpalo-3.19.0.crate", + "sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + }, + { + "bytes": 79446, + "path": "sources/crates/cc-1.0.101.crate", + "sha256": "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" + }, + { + "bytes": 97878, + "path": "sources/crates/cc-1.4.0.crate", + "sha256": "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" + }, + { + "bytes": 7934, + "path": "sources/crates/cfg-if-1.0.0.crate", + "sha256": "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + }, + { + "bytes": 9360, + "path": "sources/crates/cfg-if-1.0.4.crate", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + }, + { + "bytes": 24219, + "path": "sources/crates/displaydoc-0.2.5.crate", + "sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" + }, + { + "bytes": 29876, + "path": "sources/crates/enum_dispatch-0.3.13.crate", + "sha256": "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" + }, + { + "bytes": 7419, + "path": "sources/crates/equivalent-1.0.2.crate", + "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + }, + { + "bytes": 31493, + "path": "sources/crates/find-msvc-tools-0.1.9.crate", + "sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + }, + { + "bytes": 23329, + "path": "sources/crates/foldhash-0.2.0.crate", + "sha256": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + }, + { + "bytes": 7504, + "path": "sources/crates/foreign-types-0.3.2.crate", + "sha256": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" + }, + { + "bytes": 5672, + "path": "sources/crates/foreign-types-shared-0.1.1.crate", + "sha256": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + }, + { + "bytes": 9347, + "path": "sources/crates/form_urlencoded-1.2.2.crate", + "sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" + }, + { + "bytes": 13160, + "path": "sources/crates/funty-2.0.0.crate", + "sha256": "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + }, + { + "bytes": 49493, + "path": "sources/crates/getrandom-0.3.3.crate", + "sha256": "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" + }, + { + "bytes": 147785, + "path": "sources/crates/hashbrown-0.16.1.crate", + "sha256": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + }, + { + "bytes": 11517, + "path": "sources/crates/heck-0.5.0.crate", + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + }, + { + "bytes": 13299, + "path": "sources/crates/hex-0.4.3.crate", + "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + }, + { + "bytes": 82762, + "path": "sources/crates/icu_collections-1.5.0.crate", + "sha256": "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" + }, + { + "bytes": 55131, + "path": "sources/crates/icu_locid-1.5.0.crate", + "sha256": "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" + }, + { + "bytes": 29094, + "path": "sources/crates/icu_locid_transform-1.5.0.crate", + "sha256": "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" + }, + { + "bytes": 44727, + "path": "sources/crates/icu_locid_transform_data-1.5.0.crate", + "sha256": "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + }, + { + "bytes": 53113, + "path": "sources/crates/icu_normalizer-1.5.0.crate", + "sha256": "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" + }, + { + "bytes": 50561, + "path": "sources/crates/icu_normalizer_data-1.5.0.crate", + "sha256": "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + }, + { + "bytes": 64479, + "path": "sources/crates/icu_properties-1.5.1.crate", + "sha256": "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" + }, + { + "bytes": 227993, + "path": "sources/crates/icu_properties_data-1.5.0.crate", + "sha256": "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + }, + { + "bytes": 52722, + "path": "sources/crates/icu_provider-1.5.0.crate", + "sha256": "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" + }, + { + "bytes": 6436, + "path": "sources/crates/icu_provider_macros-1.5.0.crate", + "sha256": "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" + }, + { + "bytes": 148747, + "path": "sources/crates/idna-1.1.0.crate", + "sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" + }, + { + "bytes": 8206, + "path": "sources/crates/idna_adapter-1.2.0.crate", + "sha256": "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" + }, + { + "bytes": 10563, + "path": "sources/crates/itoa-1.0.11.crate", + "sha256": "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + }, + { + "bytes": 15935, + "path": "sources/crates/itoa-1.0.18.crate", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + }, + { + "bytes": 155564, + "path": "sources/crates/jiter-0.14.0.crate", + "sha256": "b6f3b5d3f84b36f4ad09fd1da896d23d9852a1aa86556578dd0289f43dce311d" + }, + { + "bytes": 55538, + "path": "sources/crates/js-sys-0.3.77.crate", + "sha256": "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" + }, + { + "bytes": 186731, + "path": "sources/crates/lexical-parse-float-1.0.5.crate", + "sha256": "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" + }, + { + "bytes": 36912, + "path": "sources/crates/lexical-parse-integer-1.0.5.crate", + "sha256": "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" + }, + { + "bytes": 106117, + "path": "sources/crates/lexical-util-1.0.6.crate", + "sha256": "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" + }, + { + "bytes": 792045, + "path": "sources/crates/libc-0.2.177.crate", + "sha256": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + }, + { + "bytes": 821775, + "path": "sources/crates/libc-0.2.185.crate", + "sha256": "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + }, + { + "bytes": 851502, + "path": "sources/crates/libc-0.2.189.crate", + "sha256": "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + }, + { + "bytes": 28728, + "path": "sources/crates/litemap-0.7.3.crate", + "sha256": "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + }, + { + "bytes": 48120, + "path": "sources/crates/log-0.4.27.crate", + "sha256": "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + }, + { + "bytes": 16743, + "path": "sources/crates/lru-0.16.3.crate", + "sha256": "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" + }, + { + "bytes": 96670, + "path": "sources/crates/memchr-2.7.4.crate", + "sha256": "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + }, + { + "bytes": 102801, + "path": "sources/crates/num-bigint-0.4.6.crate", + "sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" + }, + { + "bytes": 22331, + "path": "sources/crates/num-integer-0.1.46.crate", + "sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" + }, + { + "bytes": 51631, + "path": "sources/crates/num-traits-0.2.19.crate", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + }, + { + "bytes": 34534, + "path": "sources/crates/once_cell-1.21.3.crate", + "sha256": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + }, + { + "bytes": 35010, + "path": "sources/crates/once_cell-1.21.4.crate", + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + }, + { + "bytes": 311187, + "path": "sources/crates/openssl-0.10.81.crate", + "sha256": "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" + }, + { + "bytes": 5601, + "path": "sources/crates/openssl-macros-0.1.1.crate", + "sha256": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" + }, + { + "bytes": 76045, + "path": "sources/crates/openssl-sys-0.9.117.crate", + "sha256": "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" + }, + { + "bytes": 18770, + "path": "sources/crates/pem-4.0.0.crate", + "sha256": "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" + }, + { + "bytes": 11583, + "path": "sources/crates/percent-encoding-2.3.2.crate", + "sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + }, + { + "bytes": 21590, + "path": "sources/crates/pkg-config-0.3.33.crate", + "sha256": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + }, + { + "bytes": 200703, + "path": "sources/crates/portable-atomic-1.14.0.crate", + "sha256": "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + }, + { + "bytes": 140689, + "path": "sources/crates/portable-atomic-1.6.0.crate", + "sha256": "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + }, + { + "bytes": 60024, + "path": "sources/crates/proc-macro2-1.0.103.crate", + "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" + }, + { + "bytes": 59588, + "path": "sources/crates/proc-macro2-1.0.107.crate", + "sha256": "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" + }, + { + "bytes": 48958, + "path": "sources/crates/proc-macro2-1.0.86.crate", + "sha256": "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" + }, + { + "bytes": 1204476, + "path": "sources/crates/pyo3-0.28.3.crate", + "sha256": "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" + }, + { + "bytes": 1241253, + "path": "sources/crates/pyo3-0.29.0.crate", + "sha256": "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" + }, + { + "bytes": 35961, + "path": "sources/crates/pyo3-build-config-0.28.3.crate", + "sha256": "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" + }, + { + "bytes": 39616, + "path": "sources/crates/pyo3-build-config-0.29.0.crate", + "sha256": "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" + }, + { + "bytes": 80881, + "path": "sources/crates/pyo3-ffi-0.28.3.crate", + "sha256": "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" + }, + { + "bytes": 88184, + "path": "sources/crates/pyo3-ffi-0.29.0.crate", + "sha256": "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" + }, + { + "bytes": 8963, + "path": "sources/crates/pyo3-macros-0.28.3.crate", + "sha256": "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" + }, + { + "bytes": 8915, + "path": "sources/crates/pyo3-macros-0.29.0.crate", + "sha256": "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" + }, + { + "bytes": 88063, + "path": "sources/crates/pyo3-macros-backend-0.28.3.crate", + "sha256": "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" + }, + { + "bytes": 91943, + "path": "sources/crates/pyo3-macros-backend-0.29.0.crate", + "sha256": "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" + }, + { + "bytes": 103489, + "path": "sources/crates/python3-dll-a-0.2.14.crate", + "sha256": "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8" + }, + { + "bytes": 31504, + "path": "sources/crates/quote-1.0.42.crate", + "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" + }, + { + "bytes": 31629, + "path": "sources/crates/quote-1.0.44.crate", + "sha256": "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" + }, + { + "bytes": 31622, + "path": "sources/crates/quote-1.0.47.crate", + "sha256": "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" + }, + { + "bytes": 64764, + "path": "sources/crates/r-efi-5.2.0.crate", + "sha256": "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + }, + { + "bytes": 10906, + "path": "sources/crates/radium-0.7.0.crate", + "sha256": "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + }, + { + "bytes": 153392, + "path": "sources/crates/regex-1.12.3.crate", + "sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" + }, + { + "bytes": 625250, + "path": "sources/crates/regex-automata-0.4.13.crate", + "sha256": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" + }, + { + "bytes": 357541, + "path": "sources/crates/regex-syntax-0.8.5.crate", + "sha256": "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + }, + { + "bytes": 75113, + "path": "sources/crates/rpds-1.2.1.crate", + "sha256": "e025feb26210bc196b908e72deb063b1b4000754304341cbc168a1e72c857ebc" + }, + { + "bytes": 17621, + "path": "sources/crates/rustversion-1.0.17.crate", + "sha256": "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + }, + { + "bytes": 23869, + "path": "sources/crates/self_cell-1.3.0.crate", + "sha256": "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + }, + { + "bytes": 83652, + "path": "sources/crates/serde-1.0.228.crate", + "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" + }, + { + "bytes": 63111, + "path": "sources/crates/serde_core-1.0.228.crate", + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" + }, + { + "bytes": 59605, + "path": "sources/crates/serde_derive-1.0.228.crate", + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" + }, + { + "bytes": 155994, + "path": "sources/crates/serde_json-1.0.149.crate", + "sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" + }, + { + "bytes": 19332, + "path": "sources/crates/shlex-2.0.1.crate", + "sha256": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + }, + { + "bytes": 38116, + "path": "sources/crates/smallvec-1.15.1.crate", + "sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + }, + { + "bytes": 40124, + "path": "sources/crates/speedate-0.17.0.crate", + "sha256": "aba069c070b5e213f2a094deb7e5ed50ecb092be36102a4f4042e8d2056d060e" + }, + { + "bytes": 8054, + "path": "sources/crates/stable_deref_trait-1.2.0.crate", + "sha256": "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + }, + { + "bytes": 18480, + "path": "sources/crates/static_assertions-1.1.0.crate", + "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + }, + { + "bytes": 8489, + "path": "sources/crates/strum-0.27.2.crate", + "sha256": "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + }, + { + "bytes": 30522, + "path": "sources/crates/strum_macros-0.27.2.crate", + "sha256": "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" + }, + { + "bytes": 302117, + "path": "sources/crates/syn-2.0.111.crate", + "sha256": "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" + }, + { + "bytes": 307407, + "path": "sources/crates/syn-2.0.119.crate", + "sha256": "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" + }, + { + "bytes": 272300, + "path": "sources/crates/syn-2.0.82.crate", + "sha256": "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" + }, + { + "bytes": 18327, + "path": "sources/crates/synstructure-0.13.1.crate", + "sha256": "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" + }, + { + "bytes": 11316, + "path": "sources/crates/tap-1.0.1.crate", + "sha256": "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + }, + { + "bytes": 28498, + "path": "sources/crates/target-lexicon-0.13.3.crate", + "sha256": "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + }, + { + "bytes": 26832, + "path": "sources/crates/target-lexicon-0.13.4.crate", + "sha256": "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" + }, + { + "bytes": 26928, + "path": "sources/crates/target-lexicon-0.13.5.crate", + "sha256": "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + }, + { + "bytes": 16971, + "path": "sources/crates/tinystr-0.7.6.crate", + "sha256": "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" + }, + { + "bytes": 34711, + "path": "sources/crates/triomphe-0.1.15.crate", + "sha256": "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" + }, + { + "bytes": 42168, + "path": "sources/crates/unicode-ident-1.0.12.crate", + "sha256": "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + }, + { + "bytes": 47919, + "path": "sources/crates/unicode-ident-1.0.22.crate", + "sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + }, + { + "bytes": 49298, + "path": "sources/crates/unicode-ident-1.0.24.crate", + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + }, + { + "bytes": 86512, + "path": "sources/crates/url-2.5.8.crate", + "sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" + }, + { + "bytes": 9736, + "path": "sources/crates/utf16_iter-1.0.5.crate", + "sha256": "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + }, + { + "bytes": 10437, + "path": "sources/crates/utf8_iter-1.0.4.crate", + "sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + }, + { + "bytes": 63006, + "path": "sources/crates/uuid-1.23.0.crate", + "sha256": "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" + }, + { + "bytes": 228735, + "path": "sources/crates/vcpkg-0.2.15.crate", + "sha256": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + }, + { + "bytes": 15554, + "path": "sources/crates/version_check-0.9.5.crate", + "sha256": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + }, + { + "bytes": 140921, + "path": "sources/crates/wasi-0.14.2+wasi-0.2.4.crate", + "sha256": "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" + }, + { + "bytes": 48288, + "path": "sources/crates/wasm-bindgen-0.2.100.crate", + "sha256": "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" + }, + { + "bytes": 32111, + "path": "sources/crates/wasm-bindgen-backend-0.2.100.crate", + "sha256": "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" + }, + { + "bytes": 9663, + "path": "sources/crates/wasm-bindgen-macro-0.2.100.crate", + "sha256": "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" + }, + { + "bytes": 26243, + "path": "sources/crates/wasm-bindgen-macro-support-0.2.100.crate", + "sha256": "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" + }, + { + "bytes": 8570, + "path": "sources/crates/wasm-bindgen-shared-0.2.100.crate", + "sha256": "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" + }, + { + "bytes": 12241, + "path": "sources/crates/wit-bindgen-rt-0.39.0.crate", + "sha256": "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" + }, + { + "bytes": 7218, + "path": "sources/crates/write16-1.0.0.crate", + "sha256": "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + }, + { + "bytes": 22354, + "path": "sources/crates/writeable-0.5.5.crate", + "sha256": "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + }, + { + "bytes": 18790, + "path": "sources/crates/wyz-0.5.1.crate", + "sha256": "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" + }, + { + "bytes": 27781, + "path": "sources/crates/yoke-0.7.4.crate", + "sha256": "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" + }, + { + "bytes": 7470, + "path": "sources/crates/yoke-derive-0.7.4.crate", + "sha256": "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" + }, + { + "bytes": 252714, + "path": "sources/crates/zerocopy-0.8.25.crate", + "sha256": "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" + }, + { + "bytes": 87671, + "path": "sources/crates/zerocopy-derive-0.8.25.crate", + "sha256": "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" + }, + { + "bytes": 5044, + "path": "sources/crates/zerofrom-0.1.4.crate", + "sha256": "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" + }, + { + "bytes": 8232, + "path": "sources/crates/zerofrom-derive-0.1.4.crate", + "sha256": "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" + }, + { + "bytes": 126398, + "path": "sources/crates/zerovec-0.10.4.crate", + "sha256": "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" + }, + { + "bytes": 19438, + "path": "sources/crates/zerovec-derive-0.10.3.crate", + "sha256": "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" + }, + { + "bytes": 21298, + "path": "sources/crates/zmij-1.0.6.crate", + "sha256": "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f" + }, + { + "bytes": 598175, + "path": "sources/libffi/libffi-v3.4.6-from-cffi-workflow.tar.gz", + "sha256": "9ac790464c1eb2f5ab5809e978a1683e9393131aede72d1b0a0703771d3c6cda" + }, + { + "bytes": 55153883, + "path": "sources/openssl/openssl-4.0.2.tar.gz", + "sha256": "736b467530f916737b7031310ccb21d8218c6229e61e8e160cd1d3458cd543a8" + }, + { + "bytes": 138112, + "path": "sources/python/certifi-2026.7.22.tar.gz", + "sha256": "741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55" + }, + { + "bytes": 530807, + "path": "sources/python/cffi-2.1.1.tar.gz", + "sha256": "dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be" + }, + { + "bytes": 880381, + "path": "sources/python/cryptography-50.0.1.tar.gz", + "sha256": "5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20" + }, + { + "bytes": 472262, + "path": "sources/python/pydantic_core-2.46.5.tar.gz", + "sha256": "10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc" + }, + { + "bytes": 64051, + "path": "sources/python/rpds_py-2026.6.3.tar.gz", + "sha256": "1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4" + }, + { + "bytes": 3868128, + "path": "sources/rust-runtime/rust-src-1.96.0.tar.xz", + "sha256": "7875f9f4b91455ac25e2ea0e0f1cb3d7d16c309ef0720778538d20ed0e9b818b" + }, + { + "bytes": 5741336, + "path": "sources/rust-runtime/rust-src-1.98.0.tar.xz", + "sha256": "0e977492aed5ff137815bf9b4b796e8a85b80fb2367ed64e82d5c5839870f2bf" + }, + { + "bytes": 138175, + "path": "sources/upstream-git/certifi-git-f4bc676b.tar.gz", + "sha256": "60bdada2558188c3d2d4942f6fe7023b98955a5b1b2e6b03058bd92381ad6ef3" + }, + { + "bytes": 540132, + "path": "sources/upstream-git/cffi-git-fd33e770.tar.gz", + "sha256": "3c63006cdf332696a1e16b76e0c6aed38c17208e5de26737459ae6125c091482" + } + ], + "schema_version": 1 +} diff --git a/licensing/third-party/licenses/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt b/licensing/third-party/licenses/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt new file mode 100644 index 0000000..180fc3b --- /dev/null +++ b/licensing/third-party/licenses/016d20f335060a70e79d9fcf8dfaa6201114d65d592211f4bdfb8ae9ca2bc1dc.txt @@ -0,0 +1,26 @@ +Copyright (c) 2017 Jim McGrath + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/licensing/third-party/licenses/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt b/licensing/third-party/licenses/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt new file mode 100644 index 0000000..bb9c20a --- /dev/null +++ b/licensing/third-party/licenses/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt @@ -0,0 +1,3 @@ +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. diff --git a/licensing/third-party/licenses/04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608.txt b/licensing/third-party/licenses/04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608.txt new file mode 100644 index 0000000..87567c0 --- /dev/null +++ b/licensing/third-party/licenses/04213b69dbebc8ba0dd8a6216af1d9422f9b63619c1cf2f1d8aa107b83bbb608.txt @@ -0,0 +1,43 @@ +#! /usr/bin/env perl +# Copyright 2021-2022 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; +use warnings; + +package OpenSSL::copyright; + +sub year_of { + my $file = shift; + + return $ENV{'OSSL_COPYRIGHT_YEAR'} if defined $ENV{'OSSL_COPYRIGHT_YEAR'}; + + # Get the current year. We use that as the default because the other + # common case is that someone unpacked a tarfile and the file dates + # are't properly set on extract. + my $YEAR = [localtime()]->[5] + 1900; + + # See if git's available + open my $FH, + "git log -1 --date=short --format=format:%cd $file 2>/dev/null|" + or return $YEAR; + my $LINE = <$FH>; + close $FH; + $LINE =~ s/^([0-9]*)-.*/$1/; + $YEAR = $LINE if $LINE; + return $YEAR; +} + +sub latest { + my $l = 0; + foreach my $f (@_ ) { + my $y = year_of($f); + $l = $y if $y > $l; + } + return $l +} +1; diff --git a/licensing/third-party/licenses/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt b/licensing/third-party/licenses/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt new file mode 100644 index 0000000..cba2010 --- /dev/null +++ b/licensing/third-party/licenses/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt @@ -0,0 +1,25 @@ +Copyright (c) 2018 Tom Kaitchuck + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt b/licensing/third-party/licenses/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt new file mode 100644 index 0000000..0b3d7a8 --- /dev/null +++ b/licensing/third-party/licenses/061dc50af2cd9340703daf61978af3200cf681b12ea67a323c33ba109a23a45e.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Jerome Froelich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/licensing/third-party/licenses/0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39.txt b/licensing/third-party/licenses/0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39.txt new file mode 100644 index 0000000..92871cd --- /dev/null +++ b/licensing/third-party/licenses/0a54e647fe54104658b5e563c04c6f9edf251710e47bce692e0bd990a4ddaa39.txt @@ -0,0 +1,14 @@ +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2020 Frommi +Copyright (c) 2017-2024 oyvindln + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/licensing/third-party/licenses/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt b/licensing/third-party/licenses/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt new file mode 100644 index 0000000..9729c12 --- /dev/null +++ b/licensing/third-party/licenses/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt @@ -0,0 +1,25 @@ +Copyright (c) 2018 The Servo Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6.txt b/licensing/third-party/licenses/0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6.txt new file mode 100644 index 0000000..8440c7e --- /dev/null +++ b/licensing/third-party/licenses/0b74dfa0bcee5c420c6b7f67b4b2658f9ab8388c97b8e733975f2cecbdd668a6.txt @@ -0,0 +1,25 @@ +Copyright (c) 2015 The Gimli Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1.txt b/licensing/third-party/licenses/0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1.txt new file mode 100644 index 0000000..bee14a4 --- /dev/null +++ b/licensing/third-party/licenses/0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1.txt @@ -0,0 +1,27 @@ +pycparser -- A C parser in Python + +Copyright (c) 2008-2022, Eli Bendersky +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt b/licensing/third-party/licenses/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/licensing/third-party/licenses/0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt b/licensing/third-party/licenses/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt new file mode 100644 index 0000000..7bc10f8 --- /dev/null +++ b/licensing/third-party/licenses/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Alice Maz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt b/licensing/third-party/licenses/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt new file mode 100644 index 0000000..3b0a5dc --- /dev/null +++ b/licensing/third-party/licenses/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew Gallant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt b/licensing/third-party/licenses/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt new file mode 100644 index 0000000..f4095b2 --- /dev/null +++ b/licensing/third-party/licenses/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt @@ -0,0 +1,25 @@ +Copyright (c) The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt b/licensing/third-party/licenses/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt new file mode 100644 index 0000000..504df28 --- /dev/null +++ b/licensing/third-party/licenses/13f4cc9fbc8d4a447b28aa84019c10ad4abf4b5f6919db061bf6690ccc23bc02.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 kneecaw (Nika Layzell) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt b/licensing/third-party/licenses/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt new file mode 100644 index 0000000..d74f9e9 --- /dev/null +++ b/licensing/third-party/licenses/15656cc11a8331f28c0986b8ab97220d3e76f98e60ed388b5ffad37dfac4710c.txt @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2019 Manish Goregaokar + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt b/licensing/third-party/licenses/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt new file mode 100644 index 0000000..4dad74f --- /dev/null +++ b/licensing/third-party/licenses/172020dbfd5b53a226dfde77616190a48dcff519b0bc0e6deb91a8450782c4af.txt @@ -0,0 +1,34 @@ +Short version for non-lawyers: + +The Rust Project is dual-licensed under Apache 2.0 and MIT +terms. + +It is Copyright (c) The Rust Project Contributors. + +Longer version: + +Copyrights in the Rust project are retained by their contributors. No +copyright assignment is required to contribute to the Rust project. + +Some files include explicit copyright notices and/or license notices. +For full authorship information, see the version control history or + + +Except as otherwise noted, Rust is licensed under the Apache License, Version +2.0 or or the MIT +license or , at your option. + +We track licenses for third-party materials in two ways: + +* We use [REUSE](https://reuse.software) to track license information for + in-tree source files - both those authored by the Rust project and those + authored by third parties. See `REUSE.toml`, and our cached output of the + `reuse` tool which is committed to `license-metadata.json`. +* We use `cargo` to track license information for out-of-tree dependencies. + +These two sources of information are collected by the tool `generate-copyright` +into a file called `COPYRIGHT.html`, which is shipped with each binary release +of Rust. Please refer to that file for detailed information as to the components of +any given Rust release. We also produce a `COPYRIGHT-library.html` file which only +covers the subset of source code used in the Rust Standard Library, as opposed +to the toolchain as a whole. diff --git a/licensing/third-party/licenses/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt b/licensing/third-party/licenses/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt new file mode 100644 index 0000000..26e1521 --- /dev/null +++ b/licensing/third-party/licenses/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt @@ -0,0 +1,26 @@ +Copyright 2023 The Fuchsia Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/licensing/third-party/licenses/1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04.txt b/licensing/third-party/licenses/1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04.txt new file mode 100644 index 0000000..f706835 --- /dev/null +++ b/licensing/third-party/licenses/1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04.txt @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2026, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt b/licensing/third-party/licenses/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt new file mode 100644 index 0000000..ff3bf47 --- /dev/null +++ b/licensing/third-party/licenses/1e5d3ddaf5cbc111c8517df39e6f252e4c16dbda26b7cc1ad54c9e3efb0586be.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Anton Lazarev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt b/licensing/third-party/licenses/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt new file mode 100644 index 0000000..d93b5ba --- /dev/null +++ b/licensing/third-party/licenses/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt @@ -0,0 +1,26 @@ +Copyright 2018 Developers of the Rand project +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt b/licensing/third-party/licenses/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt new file mode 100644 index 0000000..24de6b4 --- /dev/null +++ b/licensing/third-party/licenses/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013-2016 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt b/licensing/third-party/licenses/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt new file mode 100644 index 0000000..a2ce4d9 --- /dev/null +++ b/licensing/third-party/licenses/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt b/licensing/third-party/licenses/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt new file mode 100644 index 0000000..f78f1c1 --- /dev/null +++ b/licensing/third-party/licenses/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt @@ -0,0 +1,7 @@ +Copyright 2016 Nika Layzell + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt b/licensing/third-party/licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/licensing/third-party/licenses/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt b/licensing/third-party/licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt new file mode 100644 index 0000000..f9d8195 --- /dev/null +++ b/licensing/third-party/licenses/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/licensing/third-party/licenses/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt b/licensing/third-party/licenses/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt new file mode 100644 index 0000000..44fbc4d --- /dev/null +++ b/licensing/third-party/licenses/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt @@ -0,0 +1,25 @@ +Copyright (c) 2018 Josh Stone + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt b/licensing/third-party/licenses/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt new file mode 100644 index 0000000..52d8241 --- /dev/null +++ b/licensing/third-party/licenses/29662666b44dff84977b46e05642cdef910bc3a93a17b5fd86e632bafa59cf21.txt @@ -0,0 +1,25 @@ +Copyright (c) 2017 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt b/licensing/third-party/licenses/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt new file mode 100644 index 0000000..e54440a --- /dev/null +++ b/licensing/third-party/licenses/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt @@ -0,0 +1,26 @@ +Copyright (c) 2018-2025 The rust-random Project Developers +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8.txt b/licensing/third-party/licenses/2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8.txt new file mode 100644 index 0000000..370a25d --- /dev/null +++ b/licensing/third-party/licenses/2a67a60bbfb33759d67d645ff131b8e6d6bc4cafc232d751fcac3eda4d314cc8.txt @@ -0,0 +1,8 @@ +This package has been mostly done by Armin Rigo with help from +Maciej Fijałkowski. The idea is heavily based (although not directly +copied) from LuaJIT ffi by Mike Pall. + + +Other contributors: + + Google Inc. diff --git a/licensing/third-party/licenses/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt b/licensing/third-party/licenses/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt new file mode 100644 index 0000000..0716871 --- /dev/null +++ b/licensing/third-party/licenses/2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Samuel Colvin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909.txt b/licensing/third-party/licenses/314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909.txt new file mode 100644 index 0000000..119a1f2 --- /dev/null +++ b/licensing/third-party/licenses/314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909.txt @@ -0,0 +1,19 @@ +Copyright (c) 2023 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt b/licensing/third-party/licenses/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt new file mode 100644 index 0000000..9fb61cd --- /dev/null +++ b/licensing/third-party/licenses/3210be7332b5bdf48eb24a945258b9f38616a2cceb0dfc06e3c3c7e9740475a0.txt @@ -0,0 +1,20 @@ +Copyright Mozilla Foundation + +Licensed under the Apache License (Version 2.0), or the MIT license, +(the "Licenses") at your option. You may not use this file except in +compliance with one of the Licenses. You may obtain copies of the +Licenses at: + + https://www.apache.org/licenses/LICENSE-2.0 + https://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software +distributed under the Licenses is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Licenses for the specific language governing permissions and +limitations under the Licenses. + +-- + +Test code is dedicated to the Public Domain when so designated (see +the individual files for PD/CC0-dedicated sections). diff --git a/licensing/third-party/licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt b/licensing/third-party/licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt new file mode 100644 index 0000000..72207b8 --- /dev/null +++ b/licensing/third-party/licenses/32c76dbe0e73d79100d5ece77c158399f2e2541bc5c78548a4ba45c1cb53c5c9.txt @@ -0,0 +1,178 @@ +Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt b/licensing/third-party/licenses/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt new file mode 100644 index 0000000..bb76d01 --- /dev/null +++ b/licensing/third-party/licenses/333ea3aaa3cadb819f4acd9f9153f9feee060a995ca8710f32bc5bd9a4b91734.txt @@ -0,0 +1,19 @@ +Copyright (c) 2017 The foreign-types Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt b/licensing/third-party/licenses/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt new file mode 100644 index 0000000..494ad3b --- /dev/null +++ b/licensing/third-party/licenses/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt b/licensing/third-party/licenses/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt new file mode 100644 index 0000000..458723b --- /dev/null +++ b/licensing/third-party/licenses/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt b/licensing/third-party/licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt new file mode 100644 index 0000000..39e0ed6 --- /dev/null +++ b/licensing/third-party/licenses/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361.txt b/licensing/third-party/licenses/37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361.txt new file mode 100644 index 0000000..8f080ea --- /dev/null +++ b/licensing/third-party/licenses/37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Nathaniel J. Smith and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt b/licensing/third-party/licenses/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt new file mode 100644 index 0000000..2f8e41f --- /dev/null +++ b/licensing/third-party/licenses/3823dda7cf046602f4b4e77ec8e227863dc4736037cc85bb33d9f19febe16bb7.txt @@ -0,0 +1,258 @@ +rust-lang/libm as a whole is available for use under the MIT license: + +------------------------------------------------------------------------------ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ + +As a contributor, you agree that your code can be used under either the MIT +license or the Apache-2.0 license: + +------------------------------------------------------------------------------ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +------------------------------------------------------------------------------ + +This Rust library contains the following copyrights: + + Copyright (c) 2018 Jorge Aparicio + +Portions of this software are derived from third-party works licensed under +terms compatible with the above MIT license: + +* musl libc https://www.musl-libc.org/. This library contains the following + copyright: + + Copyright © 2005-2020 Rich Felker, et al. + +* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH + routines are available under the MIT license on a per-file basis. + +The musl libc COPYRIGHT file also includes the following notice relevant to +math portions of the library: + +------------------------------------------------------------------------------ +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. +------------------------------------------------------------------------------ + +Copyright notices are retained in src/* files where relevant. diff --git a/licensing/third-party/licenses/3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf.txt b/licensing/third-party/licenses/3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf.txt new file mode 100644 index 0000000..f26bcf4 --- /dev/null +++ b/licensing/third-party/licenses/3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf.txt @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/licensing/third-party/licenses/3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb.txt b/licensing/third-party/licenses/3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb.txt new file mode 100644 index 0000000..b11f379 --- /dev/null +++ b/licensing/third-party/licenses/3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb.txt @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made +under the terms of *both* these licenses. diff --git a/licensing/third-party/licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt b/licensing/third-party/licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt new file mode 100644 index 0000000..3317c82 --- /dev/null +++ b/licensing/third-party/licenses/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt @@ -0,0 +1,25 @@ +Copyright Mozilla Foundation + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b.txt b/licensing/third-party/licenses/4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b.txt new file mode 100644 index 0000000..a198002 --- /dev/null +++ b/licensing/third-party/licenses/4108245a1f2df9d4e94df8abed5b4ba0759bb2f9b40a6b939f1be141077ae50b.txt @@ -0,0 +1,25 @@ +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt b/licensing/third-party/licenses/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt new file mode 100644 index 0000000..467fbd9 --- /dev/null +++ b/licensing/third-party/licenses/411781fd38700f2357a14126d0ab048164ab881f1dcb335c1bb932e232c9a2f5.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 myrrlyn (Alexander Payne) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt b/licensing/third-party/licenses/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt new file mode 100644 index 0000000..a9f853e --- /dev/null +++ b/licensing/third-party/licenses/42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c.txt @@ -0,0 +1,19 @@ +Copyright (c) 2022 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt b/licensing/third-party/licenses/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt new file mode 100644 index 0000000..99a3078 --- /dev/null +++ b/licensing/third-party/licenses/436bc5a105d8e57dcd8778730f3754f7bf39c14d2f530e4cde4bd2d17a83ec3d.txt @@ -0,0 +1,26 @@ +Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt b/licensing/third-party/licenses/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt new file mode 100644 index 0000000..5ec1fe1 --- /dev/null +++ b/licensing/third-party/licenses/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Nicholas Allegra (comex). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d.txt b/licensing/third-party/licenses/4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d.txt new file mode 100644 index 0000000..ab79d16 --- /dev/null +++ b/licensing/third-party/licenses/4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d.txt @@ -0,0 +1,12 @@ +Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439.txt b/licensing/third-party/licenses/4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439.txt new file mode 100644 index 0000000..af9cfbd --- /dev/null +++ b/licensing/third-party/licenses/4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0.txt b/licensing/third-party/licenses/5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0.txt new file mode 100644 index 0000000..104eebf --- /dev/null +++ b/licensing/third-party/licenses/5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt b/licensing/third-party/licenses/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt new file mode 100644 index 0000000..3746504 --- /dev/null +++ b/licensing/third-party/licenses/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt @@ -0,0 +1,13 @@ +Copyright 2015 Nicholas Allegra (comex). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt b/licensing/third-party/licenses/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt new file mode 100644 index 0000000..0a1dbfb --- /dev/null +++ b/licensing/third-party/licenses/5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63.txt @@ -0,0 +1,23 @@ + +Except when otherwise stated (look for LICENSE files in directories or +information at the beginning of each file) all software and +documentation is licensed as follows: + + MIT No Attribution + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the + Software is furnished to do so. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + diff --git a/licensing/third-party/licenses/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt b/licensing/third-party/licenses/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt new file mode 100644 index 0000000..3bf61a0 --- /dev/null +++ b/licensing/third-party/licenses/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt @@ -0,0 +1,25 @@ +Copyright (c) 2017 Robert Grosse + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licensing/third-party/licenses/5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d.txt b/licensing/third-party/licenses/5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d.txt new file mode 100644 index 0000000..3d48435 --- /dev/null +++ b/licensing/third-party/licenses/5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Anthropic, PBC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb.txt b/licensing/third-party/licenses/602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb.txt new file mode 100644 index 0000000..ec1a29d --- /dev/null +++ b/licensing/third-party/licenses/602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb.txt @@ -0,0 +1,27 @@ +Copyright (c) Individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of PyCA Cryptography nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt b/licensing/third-party/licenses/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt new file mode 100644 index 0000000..7e81c31 --- /dev/null +++ b/licensing/third-party/licenses/60c93a31f490375aadf64098b75f10715010379541021e00261045d7800611d3.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/licensing/third-party/licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt b/licensing/third-party/licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/licensing/third-party/licenses/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt b/licensing/third-party/licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt new file mode 100644 index 0000000..39d4bdb --- /dev/null +++ b/licensing/third-party/licenses/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt @@ -0,0 +1,25 @@ +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt b/licensing/third-party/licenses/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt new file mode 100644 index 0000000..bac6fb9 --- /dev/null +++ b/licensing/third-party/licenses/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt @@ -0,0 +1,25 @@ +Copyright (c) 2019 Nick Fitzgerald + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195.txt b/licensing/third-party/licenses/67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195.txt new file mode 100644 index 0000000..7282e79 --- /dev/null +++ b/licensing/third-party/licenses/67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195.txt @@ -0,0 +1,21 @@ +libffi - Copyright (c) 1996-2024 Anthony Green, Red Hat, Inc and others. +See source files for details. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +``Software''), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt b/licensing/third-party/licenses/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt new file mode 100644 index 0000000..85d0d58 --- /dev/null +++ b/licensing/third-party/licenses/68f5b9f5ea36881a0942ba02f558e9e1faf76cc09cb165ad801744c61b738844.txt @@ -0,0 +1,46 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +See Terms of Use +for definitions of Unicode Inc.’s Data Files and Software. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2022 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. diff --git a/licensing/third-party/licenses/69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd.txt b/licensing/third-party/licenses/69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd.txt new file mode 100644 index 0000000..ba147fa --- /dev/null +++ b/licensing/third-party/licenses/69fef7b0f322a65554156141f2bc6256ed0bb78cba7e49f0d8829d7ec4ee62cd.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2023 The Motor OS Project Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt b/licensing/third-party/licenses/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt new file mode 100644 index 0000000..455787c --- /dev/null +++ b/licensing/third-party/licenses/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt @@ -0,0 +1,187 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. diff --git a/licensing/third-party/licenses/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt b/licensing/third-party/licenses/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt new file mode 100644 index 0000000..5ac40da --- /dev/null +++ b/licensing/third-party/licenses/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt @@ -0,0 +1,25 @@ +Copyright (c) 2016--2023 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt b/licensing/third-party/licenses/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt new file mode 100644 index 0000000..b82826b --- /dev/null +++ b/licensing/third-party/licenses/74db5baf44a41b1000312c673544b3374e4198af5605c7f9080a402cec42cfa3.txt @@ -0,0 +1,57 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard +or under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2018 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. diff --git a/licensing/third-party/licenses/797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5.txt b/licensing/third-party/licenses/797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5.txt new file mode 100644 index 0000000..fd0ecbc --- /dev/null +++ b/licensing/third-party/licenses/797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 José Padilla + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d.txt b/licensing/third-party/licenses/799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d.txt new file mode 100644 index 0000000..ec71b31 --- /dev/null +++ b/licensing/third-party/licenses/799e9ca9d179295ef372f25d3769cdda7d25bb2668add6a6a1e22d1e4c678b8d.txt @@ -0,0 +1,24 @@ +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt b/licensing/third-party/licenses/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt new file mode 100644 index 0000000..e69282e --- /dev/null +++ b/licensing/third-party/licenses/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt @@ -0,0 +1,25 @@ +Copyright (c) 2015 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt b/licensing/third-party/licenses/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt new file mode 100644 index 0000000..47de40d --- /dev/null +++ b/licensing/third-party/licenses/7c7134b9f7b978c03fca875517cf398db91f19bbb8109b6685e742aa3f57468e.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 to present Samuel Colvin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a.txt b/licensing/third-party/licenses/7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a.txt new file mode 100644 index 0000000..49cc83d --- /dev/null +++ b/licensing/third-party/licenses/7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/licensing/third-party/licenses/804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3.txt b/licensing/third-party/licenses/804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3.txt new file mode 100644 index 0000000..e825ad5 --- /dev/null +++ b/licensing/third-party/licenses/804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Pydantic Services Inc. 2025 to present + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9.txt b/licensing/third-party/licenses/80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9.txt new file mode 100644 index 0000000..3a97119 --- /dev/null +++ b/licensing/third-party/licenses/80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9.txt @@ -0,0 +1,27 @@ +Copyright (c) 2014, Saurabh Kumar (python-dotenv), 2013, Ted Tieken (django-dotenv-rw), 2013, Jacob Kaplan-Moss (django-dotenv) + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of django-dotenv nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e.txt b/licensing/third-party/licenses/80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e.txt new file mode 100644 index 0000000..3d12971 --- /dev/null +++ b/licensing/third-party/licenses/80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e.txt @@ -0,0 +1,27 @@ +Copyright © 2020, [sysid](https://sysid.github.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt b/licensing/third-party/licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/licensing/third-party/licenses/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt b/licensing/third-party/licenses/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt new file mode 100644 index 0000000..7ed244f --- /dev/null +++ b/licensing/third-party/licenses/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt @@ -0,0 +1,24 @@ +Copyright 2019 The Fuchsia Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17.txt b/licensing/third-party/licenses/861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17.txt new file mode 100644 index 0000000..89336aa --- /dev/null +++ b/licensing/third-party/licenses/861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17.txt @@ -0,0 +1,12 @@ +Copyright (C) Jonas Schievink + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/licensing/third-party/licenses/882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f.txt b/licensing/third-party/licenses/882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f.txt new file mode 100644 index 0000000..2bd6453 --- /dev/null +++ b/licensing/third-party/licenses/882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb.txt b/licensing/third-party/licenses/8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb.txt new file mode 100644 index 0000000..c98d27d --- /dev/null +++ b/licensing/third-party/licenses/8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/LICENSE-2.0 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt b/licensing/third-party/licenses/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt new file mode 100644 index 0000000..d794388 --- /dev/null +++ b/licensing/third-party/licenses/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt @@ -0,0 +1,25 @@ +Copyright (c) The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt b/licensing/third-party/licenses/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt new file mode 100644 index 0000000..6d9fc1e --- /dev/null +++ b/licensing/third-party/licenses/8b4e95f5cf0dc40269c99f7b787203ffe04ded245ca2427422c196efa2b2f42a.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Elliot Linder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da.txt b/licensing/third-party/licenses/8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da.txt new file mode 100644 index 0000000..ef223ae --- /dev/null +++ b/licensing/third-party/licenses/8bc20184c0ddf3006df05e89fdf7193b33dbe4c751ae59d6bb1835f71bbe70da.txt @@ -0,0 +1,25 @@ +Copyright (c) 2021-2023 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt b/licensing/third-party/licenses/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt new file mode 100644 index 0000000..588b4a7 --- /dev/null +++ b/licensing/third-party/licenses/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Peter Glotfelty + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt b/licensing/third-party/licenses/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt new file mode 100644 index 0000000..468d907 --- /dev/null +++ b/licensing/third-party/licenses/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt @@ -0,0 +1,12 @@ +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + +The Rand project includes code from the Rust project +published under these same licenses. diff --git a/licensing/third-party/licenses/925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07.txt b/licensing/third-party/licenses/925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07.txt new file mode 100644 index 0000000..88e2b6a --- /dev/null +++ b/licensing/third-party/licenses/925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07.txt @@ -0,0 +1,7 @@ +Authors +======= + +``pyjwt`` is currently written and maintained by `Jose Padilla `_. +Originally written and maintained by `Jeff Lindsay `_. + +A full list of contributors can be found on GitHub’s `overview `_. diff --git a/licensing/third-party/licenses/9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181.txt b/licensing/third-party/licenses/9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181.txt new file mode 100644 index 0000000..718b860 --- /dev/null +++ b/licensing/third-party/licenses/9837f05336ef3cbacb6a96e1672a0426d81ad01191f214b8d48e22ca62338181.txt @@ -0,0 +1,379 @@ +This software is copyright (c) 2013 by Mark Jason Dominus . + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2013 by Mark Jason Dominus . + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2013 by Mark Jason Dominus . + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End + diff --git a/licensing/third-party/licenses/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt b/licensing/third-party/licenses/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt new file mode 100644 index 0000000..c5799de --- /dev/null +++ b/licensing/third-party/licenses/99aab70a96f3ecab683d8a319d9070af2213ae4c1601fb053fbe85e1361e32fe.txt @@ -0,0 +1,337 @@ +# Licensing + +Lexical is dual licensed under the Apache 2.0 license as well as the MIT +license. See the LICENCE-MIT and the LICENCE-APACHE files for the licenses. + +Other licensing terms may apply, as described in depth below for various features and functionality. All assume use of `lexical` or `lexical-core`. + +## `write-floats, not(compact)` + +`lexical-write-float/src/algorithm.rs` is a direct port of the reference C++ implementation of Dragonbox, found [here](https://github.com/jk-jeon/dragonbox/). +This code (used if the `write-floats` feature is enabled and the `compact` feature is disabled) is subject to a [Boost Software License](https://github.com/jk-jeon/dragonbox/blob/71993f55067a89f4b4e27591605e21521f5c61be/LICENSE-Boost) and a modified [Apache2 license](https://github.com/jk-jeon/dragonbox/blob/71993f55067a89f4b4e27591605e21521f5c61be/LICENSE-Apache2-LLVM), shown in the [Boost Software License](#boost-software-license) and [Apache2 With LLVM Exceptions](#apache2-with-llvm-exceptions) sections below. + +## `write-floats, compact` + +`lexical-write-float/src/compact.rs` is a direct port of a C++ implementation of the Grisu algorithm, found [here](https://github.com/night-shift/fpconv/). +This code (used if both the `write-floats` and `compact` features are enabled) is subject to a [MIT License](https://github.com/night-shift/fpconv/blob/dfeb7e938fb85fb5eca130b84f856705ced75012/license), shown in the [fpconv License](#fpconv-license) section below. + +## `write-floats, radix` + +`lexical-write-float/src/radix.rs` is adapted from the V8 implementation found [here](). This code (used if both the `parse-floats` and `radix` features are enabled) is subject to a [3-clause BSD license](https://github.com/v8/v8/blob/f80bfeaf0792652bfbc1f174d5a7b8ab8bc0cbbd/LICENSE.v8), shown in the [V8 License](#v8-license) section below. + +## `parse-floats, compact` + +`lexical-parse-float/src/bellerophon.rs` is loosely based off the Golang implementation, +found [here](https://github.com/golang/go/blob/b10849fbb97a2244c086991b4623ae9f32c212d0/src/strconv/extfloat.go). This code (used if both the `parse-floats` and `compact` features are enabled) is subject to a [3-clause BSD license](https://github.com/golang/go/blob/b10849fbb97a2244c086991b4623ae9f32c212d0/LICENSE), shown in the [Go License](#go-license) section below. + +# License Terms + +This contains complete copies of the licensing terms for the feature-dependent code described above. + +## Go License + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Boost Software License + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +## Apache2 With LLVM Exceptions + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +### LLVM Exceptions to the Apache 2.0 License + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +## V8 License + +Copyright 2014, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## fpconv License + +The MIT License + +Copyright (c) 2013 Andreas Samoljuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d.txt b/licensing/third-party/licenses/9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d.txt new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/licensing/third-party/licenses/9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d.txt @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt b/licensing/third-party/licenses/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt new file mode 100644 index 0000000..2dc22c1 --- /dev/null +++ b/licensing/third-party/licenses/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 The Fuchsia Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/licensing/third-party/licenses/MPL-2.0.txt b/licensing/third-party/licenses/MPL-2.0.txt new file mode 100644 index 0000000..ee6256c --- /dev/null +++ b/licensing/third-party/licenses/MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/licensing/third-party/licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt b/licensing/third-party/licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/licensing/third-party/licenses/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt b/licensing/third-party/licenses/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt new file mode 100644 index 0000000..c28ea2a --- /dev/null +++ b/licensing/third-party/licenses/a6b7260485462860838b76ad1846d21f1605e6d239141c3aa949a601d992676c.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b.txt b/licensing/third-party/licenses/a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b.txt new file mode 100644 index 0000000..a72f8a9 --- /dev/null +++ b/licensing/third-party/licenses/a7c936ff1ed8fa340172d42a98185afee078f818e907da69f3a8e336b1623b4b.txt @@ -0,0 +1,25 @@ +Copyright (c) 2023 The Motor OS Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt b/licensing/third-party/licenses/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt new file mode 100644 index 0000000..7806181 --- /dev/null +++ b/licensing/third-party/licenses/a8d47ff51ca256f56a8932dba07660672dbfe3004257ca8de708aac1415937a1.txt @@ -0,0 +1,25 @@ +Copyright (c) 2014-2020 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3.txt b/licensing/third-party/licenses/a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3.txt new file mode 100644 index 0000000..488c626 --- /dev/null +++ b/licensing/third-party/licenses/a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 to present Pydantic Services Inc. and individual contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe.txt b/licensing/third-party/licenses/aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe.txt new file mode 100644 index 0000000..62589ed --- /dev/null +++ b/licensing/third-party/licenses/aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/licenses/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt b/licensing/third-party/licenses/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt new file mode 100644 index 0000000..17d7468 --- /dev/null +++ b/licensing/third-party/licenses/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt b/licensing/third-party/licenses/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt new file mode 100644 index 0000000..5864d38 --- /dev/null +++ b/licensing/third-party/licenses/ab499c75a0f0da8e0fe83bf9ba3a27e5c39705310f07107c6c66b69958d2401c.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2025 Alice Maz, Marshall Pierce + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt b/licensing/third-party/licenses/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt new file mode 100644 index 0000000..00ae614 --- /dev/null +++ b/licensing/third-party/licenses/ab6eec6caf0fa5775e411c7a8bc6a45c4ef2956b0980b157ab74fc5cd62a928b.txt @@ -0,0 +1,275 @@ +The compiler-builtins crate is available for use under both the MIT license +and the Apache-2.0 license with the LLVM exception (MIT AND Apache-2.0 WITH +LLVM-exception). + +The libm crate is available for use under the MIT license. + +As a contributor, you agree that your code may be used under any of the +following: the MIT license, the Apache-2.0 license, or the Apache-2.0 license +with the LLVM exception. In other words, original (non-derivative) work is +licensed under MIT OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception. This is +the default license for all other source in this repository. + +Text of the relevant licenses is provided below: + +------------------------------------------------------------------------------ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. +------------------------------------------------------------------------------ + +Portions of this software are derived from third-party works licensed under +terms compatible with the above Apache-2.0 WITH LLVM-exception AND MIT +license: + +* compiler-builtins is derived from LLVM's compiler-rt (https://llvm.org/). + Work derived from compiler-rt prior to 2019-01-19 is usable under the MIT + license, with the following copyright: + + Copyright (c) 2009-2016 by the contributors listed in CREDITS.TXT + + The relevant CREDITS.TXT is located at + https://github.com/llvm/llvm-project/blob/main/compiler-rt/CREDITS.TXT. + +* Work derived from compiler-rt after 2019-01-19 is usable under the + Apache-2.0 license with the LLVM exception. + +* The bundled `math` module is from the libm crate, usable under the MIT + license. For further details and copyrights, see see libm/LICENSE.txt at + https://github.com/rust-lang/compiler-builtins. + +Additionally, some source files may contain comments with specific copyrights +or licenses. diff --git a/licensing/third-party/licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt b/licensing/third-party/licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt new file mode 100644 index 0000000..cd0ad00 --- /dev/null +++ b/licensing/third-party/licenses/afcbe3b2e6b37172b5a9ca869ee4c0b8cdc09316e5d4384864154482c33e5af6.txt @@ -0,0 +1,25 @@ +Copyright (c) 2023-present PyO3 Project and Contributors. https://github.com/PyO3 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt b/licensing/third-party/licenses/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt new file mode 100644 index 0000000..2f65b0a --- /dev/null +++ b/licensing/third-party/licenses/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt @@ -0,0 +1,19 @@ +Copyright (c) 2024 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is + not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/licensing/third-party/licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt b/licensing/third-party/licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt new file mode 100644 index 0000000..b4ae481 --- /dev/null +++ b/licensing/third-party/licenses/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013-2025 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt b/licensing/third-party/licenses/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt new file mode 100644 index 0000000..1e31206 --- /dev/null +++ b/licensing/third-party/licenses/b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d.txt @@ -0,0 +1,311 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libunwind library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/licenses/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt b/licensing/third-party/licenses/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt new file mode 100644 index 0000000..a2070c4 --- /dev/null +++ b/licensing/third-party/licenses/b71bd43a069ca0641a9ecfe585ca7b3c53b5cc1608f8b68321168698e28b5ea1.txt @@ -0,0 +1,25 @@ +Copyright (c) The Rust Project Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt b/licensing/third-party/licenses/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt new file mode 100644 index 0000000..8d7f438 --- /dev/null +++ b/licensing/third-party/licenses/b7c76ff4466a3aeb6a7d903e81b651335e97d64ecb857e2ee9812a36c6fd1acf.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jonathan Creekmore + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt b/licensing/third-party/licenses/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt new file mode 100644 index 0000000..dfc0e73 --- /dev/null +++ b/licensing/third-party/licenses/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt @@ -0,0 +1,19 @@ +The MIT License (MIT) +Copyright (c) 2017-2018 Sergio Benitez + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt b/licensing/third-party/licenses/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt new file mode 100644 index 0000000..e306b65 --- /dev/null +++ b/licensing/third-party/licenses/b84efe109a420fa3ca98be33f4227327af7ffa426195812c270feb1268bc2426.txt @@ -0,0 +1,42 @@ +Copyright Mozilla Foundation + +Licensed under the Apache License (Version 2.0), or the MIT license, +(the "Licenses") at your option. You may not use this file except in +compliance with one of the Licenses. You may obtain copies of the +Licenses at: + + https://www.apache.org/licenses/LICENSE-2.0 + https://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software +distributed under the Licenses is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Licenses for the specific language governing permissions and +limitations under the Licenses. + +-- + +Test code is dedicated to the Public Domain when so designated (see +the individual files for PD/CC0-dedicated sections). + +-- + +The implementation for Utf16CharIndices was adapted from the +CharIndices implementation of the Rust standard library at revision +ab32548539ec38a939c1b58599249f3b54130026 +(https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/library/core/src/str/iter.rs). + +Excerpt from https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/COPYRIGHT , +which refers to +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-APACHE +and +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-MIT +: + +For full authorship information, see the version control history or +https://thanks.rust-lang.org + +Except as otherwise noted (below and/or in individual files), Rust is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. diff --git a/licensing/third-party/licenses/b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e.txt b/licensing/third-party/licenses/b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e.txt new file mode 100644 index 0000000..5b036a0 --- /dev/null +++ b/licensing/third-party/licenses/b9f638dfc06b59c2d3b808186c3b30f713805a5db390f5b77b1703ac58acb83e.txt @@ -0,0 +1,53 @@ +Authors +======= + +This is the list of OpenSSL authors for copyright purposes. +It does not necessarily list everyone who has contributed code, +since in some cases, their employer may be the copyright holder. +To see the full list of contributors, see the revision history in +source control. + +Groups +------ + + * OpenSSL Software Services, Inc. + * OpenSSL Software Foundation, Inc. + * Google LLC + +Individuals +----------- + + * Andy Polyakov + * Ben Laurie + * Ben Kaduk + * Bernd Edlinger + * Bodo Möller + * David Benjamin + * David von Oheimb + * Dmitry Belyavskiy (Дмитрий Белявский) + * Emilia Käsper + * Eric Young + * Geoff Thorpe + * Holger Reif + * Kurt Roeckx + * Lutz Jänicke + * Mark J. Cox + * Matt Caswell + * Matthias St. Pierre + * Nicola Tuveri + * Nils Larsch + * Patrick Steuer + * Paul Dale + * Paul C. Sutton + * Paul Yang + * Ralf S. Engelschall + * Rich Salz + * Richard Levitte + * Shane Lontis + * Stephen Henson + * Steve Marquess + * Tim Hudson + * Tomáš Mráz + * Ulf Möller + * Valerii Krygin + * Viktor Dukhovni diff --git a/licensing/third-party/licenses/beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901.txt b/licensing/third-party/licenses/beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901.txt new file mode 100644 index 0000000..36600eb --- /dev/null +++ b/licensing/third-party/licenses/beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Florimond Manca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a.txt b/licensing/third-party/licenses/c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a.txt new file mode 100644 index 0000000..26fddfc --- /dev/null +++ b/licensing/third-party/licenses/c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a.txt @@ -0,0 +1,10 @@ +sysid +synodriver +Skyler Lewis +Margret `Pax` Williams +nekonoshiri +justindujardin +Håvard Thom <34199185+havardthom@users.noreply.github.com> +Vaibhav Mule +issmirnov +Cyprien diff --git a/licensing/third-party/licenses/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt b/licensing/third-party/licenses/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt new file mode 100644 index 0000000..b5e9457 --- /dev/null +++ b/licensing/third-party/licenses/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt @@ -0,0 +1,42 @@ +Copyright Mozilla Foundation + +Licensed under the Apache License (Version 2.0), or the MIT license, +(the "Licenses") at your option. You may not use this file except in +compliance with one of the Licenses. You may obtain copies of the +Licenses at: + + https://www.apache.org/licenses/LICENSE-2.0 + https://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software +distributed under the Licenses is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Licenses for the specific language governing permissions and +limitations under the Licenses. + +-- + +Test code is dedicated to the Public Domain when so designated (see +the individual files for PD/CC0-dedicated sections). + +-- + +The implementation for Utf8CharIndices was adapted from the +CharIndices implementation of the Rust standard library at revision +ab32548539ec38a939c1b58599249f3b54130026 +(https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/library/core/src/str/iter.rs). + +Excerpt from https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/COPYRIGHT , +which refers to +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-APACHE +and +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-MIT +: + +For full authorship information, see the version control history or +https://thanks.rust-lang.org + +Except as otherwise noted (below and/or in individual files), Rust is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. diff --git a/licensing/third-party/licenses/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt b/licensing/third-party/licenses/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt new file mode 100644 index 0000000..f8f501c --- /dev/null +++ b/licensing/third-party/licenses/c42285aa32440c3b5358fd4805b817cd8d6e1795111d2e4c547530be41896b2d.txt @@ -0,0 +1,73 @@ +LICENSE: + This project is triple-licensed under the MIT License, the Apache + License, Version 2.0, and the GNU Lesser General Public License, + Version 2.1+. + +AUTHORS-MIT: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2017-2023 Red Hat, Inc. + Copyright (C) 2019-2023 Microsoft Corporation + Copyright (C) 2022-2023 David Rheinsberg + +AUTHORS: (ordered alphabetically) + Alex James + Ayush Singh + Boris-Chengbiao Zhou + Bret Barkelew + Christopher Zurcher + David Rheinsberg + Dmitry Mostovenko + Hiroki Tokunaga + Joe Richey + John Schock + Michael Kubacki + Oliver Smith-Denny + Richard Wiedenhöft + Rob Bradford , + Tom Gundersen diff --git a/licensing/third-party/licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt b/licensing/third-party/licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/licensing/third-party/licenses/c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/licensing/third-party/licenses/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt b/licensing/third-party/licenses/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt new file mode 100644 index 0000000..908d517 --- /dev/null +++ b/licensing/third-party/licenses/c6deb2d156619439e147f8b62e1b3928598c8e3652ec8b56cc039490696b3985.txt @@ -0,0 +1,27 @@ +Copyright (c) Alex Gaynor and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of rust-asn1 nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt b/licensing/third-party/licenses/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/licenses/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/licenses/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt b/licensing/third-party/licenses/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt new file mode 100644 index 0000000..0c4eaff --- /dev/null +++ b/licensing/third-party/licenses/c760d1f2e4614d58f2b91cd41b9826457989c09041be0219158b8f7f6d3f8763.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sergey Kvachonok, Adam Reichold, messense + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt b/licensing/third-party/licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/licensing/third-party/licenses/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/licenses/dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf.txt b/licensing/third-party/licenses/dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf.txt new file mode 100644 index 0000000..d16a60e --- /dev/null +++ b/licensing/third-party/licenses/dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf.txt @@ -0,0 +1,27 @@ +Copyright © 2018, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt b/licensing/third-party/licenses/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt new file mode 100644 index 0000000..e51fb63 --- /dev/null +++ b/licensing/third-party/licenses/e326b5fef9084c437878cc2184ba6dfa15503f46e5359dd67246f51761ea709f.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Diogo Sousa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt b/licensing/third-party/licenses/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/licensing/third-party/licenses/e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7.txt @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/licensing/third-party/licenses/e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40.txt b/licensing/third-party/licenses/e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40.txt new file mode 100644 index 0000000..3a03f1f --- /dev/null +++ b/licensing/third-party/licenses/e99d88d232bf57d70f0fb87f6b496d44b6653f99f8a63d250a54c61ea4bcde40.txt @@ -0,0 +1,25 @@ +Copyright (c) 2016-2018 The gimli Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt b/licensing/third-party/licenses/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt new file mode 100644 index 0000000..ccc9445 --- /dev/null +++ b/licensing/third-party/licenses/ea084a2373ebc1f0902c09266e7bf25a05ab3814c1805bb017ffa7308f90c061.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Nikolai Vazquez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt b/licensing/third-party/licenses/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt new file mode 100644 index 0000000..743bbf8 --- /dev/null +++ b/licensing/third-party/licenses/ea5ab440900ab271811d5c2dc02a9de6e03a6956ac5727e85c11d76df5883d30.txt @@ -0,0 +1,19 @@ +Copyright (c) 2022 Steven Fackler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt b/licensing/third-party/licenses/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt new file mode 100644 index 0000000..0e9d2f4 --- /dev/null +++ b/licensing/third-party/licenses/eb07d497d26e6d68fbc76e793f5e5c9cfa197df2a580e47383569c287a55edf9.txt @@ -0,0 +1,19 @@ +Copyright (c) 2020 The Rust Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998.txt b/licensing/third-party/licenses/eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998.txt new file mode 100644 index 0000000..d90598f --- /dev/null +++ b/licensing/third-party/licenses/eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Samuel Colvin and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371.txt b/licensing/third-party/licenses/efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371.txt new file mode 100644 index 0000000..a6bba14 --- /dev/null +++ b/licensing/third-party/licenses/efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371.txt @@ -0,0 +1,27 @@ +Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt b/licensing/third-party/licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt new file mode 100644 index 0000000..c9be601 --- /dev/null +++ b/licensing/third-party/licenses/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt @@ -0,0 +1,46 @@ +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. diff --git a/licensing/third-party/licenses/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt b/licensing/third-party/licenses/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt new file mode 100644 index 0000000..f259067 --- /dev/null +++ b/licensing/third-party/licenses/f3d4287b4a21c5176fea2f9bd4ae800696004e2fb8e05cbc818be513f188a941.txt @@ -0,0 +1,15 @@ +Copyright 2011-2017 Google Inc. + 2013 Jack Lloyd + 2013-2014 Steven Fackler + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/licenses/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt b/licensing/third-party/licenses/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt new file mode 100644 index 0000000..56224c4 --- /dev/null +++ b/licensing/third-party/licenses/f790cc576999f5998c766d3d26d7d64dc368e805a98461484f65e8d961ec6d9f.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 myrrlyn (Alexander Payne) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt b/licensing/third-party/licenses/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt new file mode 100644 index 0000000..a7b461f --- /dev/null +++ b/licensing/third-party/licenses/f7bdb3426d045cd50efd4953026e3eb5a83d0199f458a075602611b9344da5b9.txt @@ -0,0 +1,20 @@ +Copyright (c) 2013-2014 The Rust Project Developers. +Copyright (c) 2015-2020 The rust-hex Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt b/licensing/third-party/licenses/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt new file mode 100644 index 0000000..11f2842 --- /dev/null +++ b/licensing/third-party/licenses/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt @@ -0,0 +1,39 @@ +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2023 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. diff --git a/licensing/third-party/licenses/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt b/licensing/third-party/licenses/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt new file mode 100644 index 0000000..ecbc059 --- /dev/null +++ b/licensing/third-party/licenses/f9c375a1be4a41f7b70301dd83c91cb89e41567478859b77eef375a52d782505.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/licensing/third-party/licenses/fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c.txt b/licensing/third-party/licenses/fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c.txt new file mode 100644 index 0000000..998f1d2 --- /dev/null +++ b/licensing/third-party/licenses/fba3318d0c600380177edf7ba4778ff6588faa8d106c679ac5dfee620011239c.txt @@ -0,0 +1,62 @@ +LICENSE: + This project is triple-licensed under the MIT License, the Apache + License, Version 2.0, and the GNU Lesser General Public License, + Version 2.1+. + +AUTHORS-MIT: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2017-2022 Red Hat, Inc. + Copyright (C) 2022-2025 David Rheinsberg + +AUTHORS: (ordered alphabetically) + Ayush Singh + David Rheinsberg + Mizuho MORI + Tom Gundersen + Trevor Gross diff --git a/licensing/third-party/licenses/fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5.txt b/licensing/third-party/licenses/fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5.txt new file mode 100644 index 0000000..311b2b5 --- /dev/null +++ b/licensing/third-party/licenses/fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5.txt @@ -0,0 +1,27 @@ +Copyright © 2020, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/licenses/fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d.txt b/licensing/third-party/licenses/fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d.txt new file mode 100644 index 0000000..d99323a --- /dev/null +++ b/licensing/third-party/licenses/fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 the contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/licenses/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt b/licensing/third-party/licenses/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt new file mode 100644 index 0000000..5afc2a7 --- /dev/null +++ b/licensing/third-party/licenses/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt @@ -0,0 +1,25 @@ +Copyright (c) 2016 Amanieu d'Antras + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/licenses/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt b/licensing/third-party/licenses/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt new file mode 100644 index 0000000..7397b2b --- /dev/null +++ b/licensing/third-party/licenses/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt @@ -0,0 +1,74 @@ +LICENSE: + This project is triple-licensed under the MIT License, the Apache + License, Version 2.0, and the GNU Lesser General Public License, + Version 2.1+. + +AUTHORS-MIT: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2017-2023 Red Hat, Inc. + Copyright (C) 2019-2023 Microsoft Corporation + Copyright (C) 2022-2023 David Rheinsberg + +AUTHORS: (ordered alphabetically) + Alex James + Ayush Singh + Boris-Chengbiao Zhou + Bret Barkelew + Christopher Zurcher + David Rheinsberg + Dmitry Mostovenko + Hiroki Tokunaga + Joe Richey + John Schock + Michael Kubacki + Oliver Smith-Denny + Richard Wiedenhöft + Rob Bradford , + Tom Gundersen + Trevor Gross diff --git a/licensing/third-party/licenses/wit-bindgen-rt-0.39.0-LICENSE-MIT.txt b/licensing/third-party/licenses/wit-bindgen-rt-0.39.0-LICENSE-MIT.txt new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/licensing/third-party/licenses/wit-bindgen-rt-0.39.0-LICENSE-MIT.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/GO-BUILDINFO.json b/licensing/third-party/tunnel-client/GO-BUILDINFO.json new file mode 100644 index 0000000..0490b08 --- /dev/null +++ b/licensing/third-party/tunnel-client/GO-BUILDINFO.json @@ -0,0 +1,243 @@ +{ + "go_version": "go1.27.0", + "component": "github.com/openai/tunnel-client", + "module_version": "(devel)", + "vcs_revision_embedded": false, + "dependencies": [ + { + "module": "github.com/bahlo/generic-list-go", + "version": "v0.2.0", + "h1": "h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=" + }, + { + "module": "github.com/beorn7/perks", + "version": "v1.0.1", + "h1": "h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=" + }, + { + "module": "github.com/buger/jsonparser", + "version": "v1.1.2", + "h1": "h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=" + }, + { + "module": "github.com/cespare/xxhash/v2", + "version": "v2.3.0", + "h1": "h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=" + }, + { + "module": "github.com/felixge/httpsnoop", + "version": "v1.0.4", + "h1": "h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=" + }, + { + "module": "github.com/go-logr/logr", + "version": "v1.4.3", + "h1": "h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=" + }, + { + "module": "github.com/go-logr/stdr", + "version": "v1.2.2", + "h1": "h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=" + }, + { + "module": "github.com/google/jsonschema-go", + "version": "v0.4.3", + "h1": "h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=" + }, + { + "module": "github.com/google/uuid", + "version": "v1.6.0", + "h1": "h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=" + }, + { + "module": "github.com/grafana/regexp", + "version": "v0.0.0-20240518133315-a468a5bfb3bc", + "h1": "h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=" + }, + { + "module": "github.com/invopop/jsonschema", + "version": "v0.13.0", + "h1": "h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=" + }, + { + "module": "github.com/jpillora/backoff", + "version": "v1.0.0", + "h1": "h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=" + }, + { + "module": "github.com/mailru/easyjson", + "version": "v0.7.7", + "h1": "h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=" + }, + { + "module": "github.com/modelcontextprotocol/go-sdk", + "version": "v1.7.0", + "h1": "h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=" + }, + { + "module": "github.com/munnerz/goautoneg", + "version": "v0.0.0-20191010083416-a7dc8b61c822", + "h1": "h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=" + }, + { + "module": "github.com/panjf2000/ants/v2", + "version": "v2.11.3", + "h1": "h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=" + }, + { + "module": "github.com/prometheus/client_golang", + "version": "v1.23.2", + "h1": "h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=" + }, + { + "module": "github.com/prometheus/client_model", + "version": "v0.6.2", + "h1": "h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=" + }, + { + "module": "github.com/prometheus/common", + "version": "v0.66.1", + "h1": "h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=" + }, + { + "module": "github.com/prometheus/otlptranslator", + "version": "v0.0.2", + "h1": "h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=" + }, + { + "module": "github.com/prometheus/procfs", + "version": "v0.17.0", + "h1": "h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=" + }, + { + "module": "github.com/segmentio/asm", + "version": "v1.1.3", + "h1": "h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=" + }, + { + "module": "github.com/segmentio/encoding", + "version": "v0.5.4", + "h1": "h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=" + }, + { + "module": "github.com/spf13/cobra", + "version": "v1.9.1", + "h1": "h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=" + }, + { + "module": "github.com/spf13/pflag", + "version": "v1.0.6", + "h1": "h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=" + }, + { + "module": "github.com/wk8/go-ordered-map/v2", + "version": "v2.1.8", + "h1": "h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=" + }, + { + "module": "github.com/yosida95/uritemplate/v3", + "version": "v3.0.2", + "h1": "h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=" + }, + { + "module": "go.opentelemetry.io/auto/sdk", + "version": "v1.2.1", + "h1": "h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=" + }, + { + "module": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", + "version": "v0.63.0", + "h1": "h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=" + }, + { + "module": "go.opentelemetry.io/otel", + "version": "v1.41.0", + "h1": "h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=" + }, + { + "module": "go.opentelemetry.io/otel/exporters/prometheus", + "version": "v0.60.0", + "h1": "h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=" + }, + { + "module": "go.opentelemetry.io/otel/metric", + "version": "v1.41.0", + "h1": "h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=" + }, + { + "module": "go.opentelemetry.io/otel/sdk", + "version": "v1.41.0", + "h1": "h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=" + }, + { + "module": "go.opentelemetry.io/otel/sdk/metric", + "version": "v1.41.0", + "h1": "h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=" + }, + { + "module": "go.opentelemetry.io/otel/trace", + "version": "v1.41.0", + "h1": "h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=" + }, + { + "module": "go.uber.org/dig", + "version": "v1.19.0", + "h1": "h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=" + }, + { + "module": "go.uber.org/fx", + "version": "v1.23.0", + "h1": "h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=" + }, + { + "module": "go.uber.org/multierr", + "version": "v1.11.0", + "h1": "h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=" + }, + { + "module": "go.uber.org/zap", + "version": "v1.27.0", + "h1": "h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=" + }, + { + "module": "go.yaml.in/yaml/v2", + "version": "v2.4.2", + "h1": "h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=" + }, + { + "module": "golang.org/x/net", + "version": "v0.43.0", + "h1": "h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=" + }, + { + "module": "golang.org/x/oauth2", + "version": "v0.35.0", + "h1": "h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=" + }, + { + "module": "golang.org/x/sync", + "version": "v0.20.0", + "h1": "h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=" + }, + { + "module": "golang.org/x/sys", + "version": "v0.41.0", + "h1": "h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=" + }, + { + "module": "golang.org/x/time", + "version": "v0.15.0", + "h1": "h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=" + }, + { + "module": "google.golang.org/protobuf", + "version": "v1.36.8", + "h1": "h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=" + }, + { + "module": "gopkg.in/yaml.v3", + "version": "v3.0.1", + "h1": "h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=" + } + ] +} diff --git a/licensing/third-party/tunnel-client/GO-LICENSE b/licensing/third-party/tunnel-client/GO-LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/GO-LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/GO-LICENSE-MARKERS.json b/licensing/third-party/tunnel-client/GO-LICENSE-MARKERS.json new file mode 100644 index 0000000..186025b --- /dev/null +++ b/licensing/third-party/tunnel-client/GO-LICENSE-MARKERS.json @@ -0,0 +1,390 @@ +[ + { + "module": "github.com/bahlo/generic-list-go", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/beorn7/perks", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/buger/jsonparser", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/cespare/xxhash/v2", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/felixge/httpsnoop", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/go-logr/logr", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/go-logr/stdr", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/google/jsonschema-go", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/google/uuid", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/grafana/regexp", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/invopop/jsonschema", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/jpillora/backoff", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/mailru/easyjson", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/modelcontextprotocol/go-sdk", + "terms_markers": [ + "Apache-2.0", + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/munnerz/goautoneg", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/panjf2000/ants/v2", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/prometheus/client_golang", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 3 + }, + { + "module": "github.com/prometheus/client_model", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "github.com/prometheus/common", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "github.com/prometheus/otlptranslator", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/prometheus/procfs", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "github.com/segmentio/asm", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/segmentio/encoding", + "terms_markers": [ + "Apache-2.0", + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "github.com/spf13/cobra", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/spf13/pflag", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/wk8/go-ordered-map/v2", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "github.com/yosida95/uritemplate/v3", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/auto/sdk", + "terms_markers": [ + "Apache-2.0" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel/exporters/prometheus", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel/metric", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel/sdk", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel/sdk/metric", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.opentelemetry.io/otel/trace", + "terms_markers": [ + "Apache-2.0", + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.uber.org/dig", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.uber.org/fx", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.uber.org/multierr", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.uber.org/zap", + "terms_markers": [ + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "go.yaml.in/yaml/v2", + "terms_markers": [ + "Apache-2.0", + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 3 + }, + { + "module": "golang.org/x/net", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "golang.org/x/oauth2", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 1 + }, + { + "module": "golang.org/x/sync", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "golang.org/x/sys", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "golang.org/x/time", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "google.golang.org/protobuf", + "terms_markers": [ + "BSD-style" + ], + "license_text_found": true, + "legal_file_count": 2 + }, + { + "module": "gopkg.in/yaml.v3", + "terms_markers": [ + "Apache-2.0", + "MIT-style" + ], + "license_text_found": true, + "legal_file_count": 2 + } +] diff --git a/licensing/third-party/tunnel-client/GO-MODULE-SOURCE-NOTICES.json b/licensing/third-party/tunnel-client/GO-MODULE-SOURCE-NOTICES.json new file mode 100644 index 0000000..d937071 --- /dev/null +++ b/licensing/third-party/tunnel-client/GO-MODULE-SOURCE-NOTICES.json @@ -0,0 +1,828 @@ +{ + "source": "Go module dependencies embedded in the retained tunnel-client ELF; exact versions and h1 checksums verified against publisher module archives.", + "modules": [ + { + "module": "github.com/bahlo/generic-list-go", + "version": "v0.2.0", + "h1": "h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=", + "archive": "github.com__bahlo__generic-list-go@v0.2.0.zip", + "url": "https://proxy.golang.org/github.com/bahlo/generic-list-go/@v/v0.2.0.zip", + "archive_sha256": "e773fa812176a62f1d27d7a1e7589683f5e5af2d36628fa6a5a522e59474c504", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/bahlo/generic-list-go@v0.2.0/LICENSE", + "notice_file": "github.com__bahlo__generic-list-go@v0.2.0__bahlo__generic-list-go@v0.2.0__LICENSE", + "sha256": "2d36597f7117c38b006835ae7f537487207d8ec407aa9d9980794b2030cbc067" + } + ] + }, + { + "module": "github.com/beorn7/perks", + "version": "v1.0.1", + "h1": "h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", + "archive": "github.com__beorn7__perks@v1.0.1.zip", + "url": "https://proxy.golang.org/github.com/beorn7/perks/@v/v1.0.1.zip", + "archive_sha256": "25bd9e2d94aca770e6dbc1f53725f84f6af4432f631d35dd2c46f96ef0512f1a", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/beorn7/perks@v1.0.1/LICENSE", + "notice_file": "github.com__beorn7__perks@v1.0.1__beorn7__perks@v1.0.1__LICENSE", + "sha256": "0db7c9ebb3717e526f34f87dd1ee8bc77d36846e29cb0cec9246f7138fbe962b" + } + ] + }, + { + "module": "github.com/buger/jsonparser", + "version": "v1.1.2", + "h1": "h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", + "archive": "github.com__buger__jsonparser@v1.1.2.zip", + "url": "https://proxy.golang.org/github.com/buger/jsonparser/@v/v1.1.2.zip", + "archive_sha256": "07495b55fbdae886bcc970b6f5d5073435b5980291dc92674c53ab31d1ad298f", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/buger/jsonparser@v1.1.2/LICENSE", + "notice_file": "github.com__buger__jsonparser@v1.1.2__buger__jsonparser@v1.1.2__LICENSE", + "sha256": "632b59709152e1b5bfd3e2794848bcd2f4c70e3e175646726dbf53439ea1dbe1" + } + ] + }, + { + "module": "github.com/cespare/xxhash/v2", + "version": "v2.3.0", + "h1": "h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", + "archive": "github.com__cespare__xxhash__v2@v2.3.0.zip", + "url": "https://proxy.golang.org/github.com/cespare/xxhash/v2/@v/v2.3.0.zip", + "archive_sha256": "145a26cdc7c49db566017b807c4989ee7f7ddeb569423e9cb99f995fac3621d3", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/cespare/xxhash/v2@v2.3.0/LICENSE.txt", + "notice_file": "github.com__cespare__xxhash__v2@v2.3.0__cespare__xxhash__v2@v2.3.0__LICENSE.txt", + "sha256": "f566a9f97bacdaf00d9f21dd991e81dc11201c4e016c86b470799429a1c9a79c" + } + ] + }, + { + "module": "github.com/felixge/httpsnoop", + "version": "v1.0.4", + "h1": "h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=", + "archive": "github.com__felixge__httpsnoop@v1.0.4.zip", + "url": "https://proxy.golang.org/github.com/felixge/httpsnoop/@v/v1.0.4.zip", + "archive_sha256": "75aa471311265e9860df0e523400b4650ed0c1a33262786a421f07226792e494", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/felixge/httpsnoop@v1.0.4/LICENSE.txt", + "notice_file": "github.com__felixge__httpsnoop@v1.0.4__felixge__httpsnoop@v1.0.4__LICENSE.txt", + "sha256": "8108e14b5cb2b6bcd51583d3849ef0c2b5c4de61758256c20308bc70d1775dbb" + } + ] + }, + { + "module": "github.com/go-logr/logr", + "version": "v1.4.3", + "h1": "h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=", + "archive": "github.com__go-logr__logr@v1.4.3.zip", + "url": "https://proxy.golang.org/github.com/go-logr/logr/@v/v1.4.3.zip", + "archive_sha256": "523d7a3587fe73f61ef41c8bc627f7d7f38de8104d46278154b307e60770ad70", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/go-logr/logr@v1.4.3/LICENSE", + "notice_file": "github.com__go-logr__logr@v1.4.3__go-logr__logr@v1.4.3__LICENSE", + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1" + } + ] + }, + { + "module": "github.com/go-logr/stdr", + "version": "v1.2.2", + "h1": "h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", + "archive": "github.com__go-logr__stdr@v1.2.2.zip", + "url": "https://proxy.golang.org/github.com/go-logr/stdr/@v/v1.2.2.zip", + "archive_sha256": "9dd6893bf700198485ae699640b49bc1efbc6c73b37cb5792a0476e1fd8f7fef", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/go-logr/stdr@v1.2.2/LICENSE", + "notice_file": "github.com__go-logr__stdr@v1.2.2__go-logr__stdr@v1.2.2__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + } + ] + }, + { + "module": "github.com/google/jsonschema-go", + "version": "v0.4.3", + "h1": "h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=", + "archive": "github.com__google__jsonschema-go@v0.4.3.zip", + "url": "https://proxy.golang.org/github.com/google/jsonschema-go/@v/v0.4.3.zip", + "archive_sha256": "9359ac98ff69ed2f3c51e3b2c9e3ab4cc16ca7d54dfe310fa5d258bd5decdfb5", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/google/jsonschema-go@v0.4.3/LICENSE", + "notice_file": "github.com__google__jsonschema-go@v0.4.3__google__jsonschema-go@v0.4.3__LICENSE", + "sha256": "2d56c53449691d85d9aea245eb8dac12713e9075d70d5557b82ae1e94805b357" + } + ] + }, + { + "module": "github.com/google/uuid", + "version": "v1.6.0", + "h1": "h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", + "archive": "github.com__google__uuid@v1.6.0.zip", + "url": "https://proxy.golang.org/github.com/google/uuid/@v/v1.6.0.zip", + "archive_sha256": "d0f02f377217f42702e259684e06441edbf5140dddcc34ba9bea56038b38a6ed", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/google/uuid@v1.6.0/LICENSE", + "notice_file": "github.com__google__uuid@v1.6.0__google__uuid@v1.6.0__LICENSE", + "sha256": "0a8d61ed3cbfd5312326e8126c31ce9c627a283adc99131b56896d29ada04b2d" + } + ] + }, + { + "module": "github.com/grafana/regexp", + "version": "v0.0.0-20240518133315-a468a5bfb3bc", + "h1": "h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=", + "archive": "github.com__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc.zip", + "url": "https://proxy.golang.org/github.com/grafana/regexp/@v/v0.0.0-20240518133315-a468a5bfb3bc.zip", + "archive_sha256": "0834c0727a787e26ea060eaca46a8ecafb719aca0fd6860d69d98211acd22c00", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/grafana/regexp@v0.0.0-20240518133315-a468a5bfb3bc/LICENSE", + "notice_file": "github.com__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__LICENSE", + "sha256": "2d36597f7117c38b006835ae7f537487207d8ec407aa9d9980794b2030cbc067" + } + ] + }, + { + "module": "github.com/invopop/jsonschema", + "version": "v0.13.0", + "h1": "h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=", + "archive": "github.com__invopop__jsonschema@v0.13.0.zip", + "url": "https://proxy.golang.org/github.com/invopop/jsonschema/@v/v0.13.0.zip", + "archive_sha256": "43eb8b97636e7f7c17441db165e9c624c38033c9fca44c57db46b202e8841875", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/invopop/jsonschema@v0.13.0/COPYING", + "notice_file": "github.com__invopop__jsonschema@v0.13.0__invopop__jsonschema@v0.13.0__COPYING", + "sha256": "ed76298aa9c218e9010aa186c1107ceb6c7618db50ae0222b9a1b4196101a411" + } + ] + }, + { + "module": "github.com/jpillora/backoff", + "version": "v1.0.0", + "h1": "h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=", + "archive": "github.com__jpillora__backoff@v1.0.0.zip", + "url": "https://proxy.golang.org/github.com/jpillora/backoff/@v/v1.0.0.zip", + "archive_sha256": "f856692c725143c49b9cceabfbca8bc93d3dbde84a0aaa53fb26ed3774c220cc", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/jpillora/backoff@v1.0.0/LICENSE", + "notice_file": "github.com__jpillora__backoff@v1.0.0__jpillora__backoff@v1.0.0__LICENSE", + "sha256": "bcf0006f5320232ee703a1fff7052f8f36ad17736bafb82da60d67e8b45768ff" + } + ] + }, + { + "module": "github.com/mailru/easyjson", + "version": "v0.7.7", + "h1": "h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=", + "archive": "github.com__mailru__easyjson@v0.7.7.zip", + "url": "https://proxy.golang.org/github.com/mailru/easyjson/@v/v0.7.7.zip", + "archive_sha256": "139387981a220d499c9f47cece42a2002f105e4ee3ab9c74188a7fb8a9be711e", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/mailru/easyjson@v0.7.7/LICENSE", + "notice_file": "github.com__mailru__easyjson@v0.7.7__mailru__easyjson@v0.7.7__LICENSE", + "sha256": "87baa92c3100cf37441761262d622db99492ee386a063460d4676143db49af57" + } + ] + }, + { + "module": "github.com/modelcontextprotocol/go-sdk", + "version": "v1.7.0", + "h1": "h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=", + "archive": "github.com__modelcontextprotocol__go-sdk@v1.7.0.zip", + "url": "https://proxy.golang.org/github.com/modelcontextprotocol/go-sdk/@v/v1.7.0.zip", + "archive_sha256": "324a2a6956be6b4aa8a92057cf2ac71ad91bdbbf4dd3d9a00a192e789843a320", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/modelcontextprotocol/go-sdk@v1.7.0/LICENSE", + "notice_file": "github.com__modelcontextprotocol__go-sdk@v1.7.0__modelcontextprotocol__go-sdk@v1.7.0__LICENSE", + "sha256": "af679003d933f045393a6a029f43da113f9ae364eac651d9ae268392985580f5" + } + ] + }, + { + "module": "github.com/munnerz/goautoneg", + "version": "v0.0.0-20191010083416-a7dc8b61c822", + "h1": "h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", + "archive": "github.com__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822.zip", + "url": "https://proxy.golang.org/github.com/munnerz/goautoneg/@v/v0.0.0-20191010083416-a7dc8b61c822.zip", + "archive_sha256": "3d7ce17916779890be02ea6b3dd6345c3c30c1df502ad9d8b5b9b310e636afd9", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/munnerz/goautoneg@v0.0.0-20191010083416-a7dc8b61c822/LICENSE", + "notice_file": "github.com__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__LICENSE", + "sha256": "aa1376b9bc5dea6f30cdefde40c176a254f247d2814d0a9929395138631b2ae0" + } + ] + }, + { + "module": "github.com/panjf2000/ants/v2", + "version": "v2.11.3", + "h1": "h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=", + "archive": "github.com__panjf2000__ants__v2@v2.11.3.zip", + "url": "https://proxy.golang.org/github.com/panjf2000/ants/v2/@v/v2.11.3.zip", + "archive_sha256": "ce1bdd5658ecc36a45fbc1f08378561572c2a88a974f19d31dd5ce4402fd6f12", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/panjf2000/ants/v2@v2.11.3/LICENSE", + "notice_file": "github.com__panjf2000__ants__v2@v2.11.3__panjf2000__ants__v2@v2.11.3__LICENSE", + "sha256": "fcffa16d146c16e2be3c4f4373cb0998b043842d8a6822d201eb8b13a9339778" + } + ] + }, + { + "module": "github.com/prometheus/client_golang", + "version": "v1.23.2", + "h1": "h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=", + "archive": "github.com__prometheus__client_golang@v1.23.2.zip", + "url": "https://proxy.golang.org/github.com/prometheus/client_golang/@v/v1.23.2.zip", + "archive_sha256": "dfcc6c52ad09e3d0dca2e9f41de7f8d1c0dde7baae4b867353d112918a1511de", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/prometheus/client_golang@v1.23.2/LICENSE", + "notice_file": "github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "archive_path": "github.com/prometheus/client_golang@v1.23.2/NOTICE", + "notice_file": "github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__NOTICE", + "sha256": "8548bfb2bc1913396df38c548a6ec60b8dbcac3b0800485a40569c8bdd184471" + }, + { + "archive_path": "github.com/prometheus/client_golang@v1.23.2/internal/github.com/golang/gddo/LICENSE", + "notice_file": "github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__internal__github.com__golang__gddo__LICENSE", + "sha256": "dfffb9ec737eaba985243e95a549bd8c3a4d462032cfe603e2a6704c346427b6" + } + ] + }, + { + "module": "github.com/prometheus/client_model", + "version": "v0.6.2", + "h1": "h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=", + "archive": "github.com__prometheus__client_model@v0.6.2.zip", + "url": "https://proxy.golang.org/github.com/prometheus/client_model/@v/v0.6.2.zip", + "archive_sha256": "67017b19f220c6ab91ecfdd10a93824e2a8b09fcb6390054dbdc49df3354a9ca", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/prometheus/client_model@v0.6.2/LICENSE", + "notice_file": "github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "archive_path": "github.com/prometheus/client_model@v0.6.2/NOTICE", + "notice_file": "github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__NOTICE", + "sha256": "6c79faa15168885fb88a316ae0df18f486deafddefdc16826cdc56dfbd421e24" + } + ] + }, + { + "module": "github.com/prometheus/common", + "version": "v0.66.1", + "h1": "h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=", + "archive": "github.com__prometheus__common@v0.66.1.zip", + "url": "https://proxy.golang.org/github.com/prometheus/common/@v/v0.66.1.zip", + "archive_sha256": "05d9e8e5c289209245dc22a7d07812b1b72472c67f372b5579dfabc3774e17b4", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/prometheus/common@v0.66.1/LICENSE", + "notice_file": "github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "archive_path": "github.com/prometheus/common@v0.66.1/NOTICE", + "notice_file": "github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__NOTICE", + "sha256": "600244c8052c8c1d307043fafacbc83b429c171dd8e08f5a537c0a54014585ee" + } + ] + }, + { + "module": "github.com/prometheus/otlptranslator", + "version": "v0.0.2", + "h1": "h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=", + "archive": "github.com__prometheus__otlptranslator@v0.0.2.zip", + "url": "https://proxy.golang.org/github.com/prometheus/otlptranslator/@v/v0.0.2.zip", + "archive_sha256": "fcdb6463c6a8bf636a3e4a7ea964115b35ab45d66ed0b0d4f1661cf01132af4b", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/prometheus/otlptranslator@v0.0.2/LICENSE", + "notice_file": "github.com__prometheus__otlptranslator@v0.0.2__prometheus__otlptranslator@v0.0.2__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + } + ] + }, + { + "module": "github.com/prometheus/procfs", + "version": "v0.17.0", + "h1": "h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=", + "archive": "github.com__prometheus__procfs@v0.17.0.zip", + "url": "https://proxy.golang.org/github.com/prometheus/procfs/@v/v0.17.0.zip", + "archive_sha256": "e826b047a611fddd93a574ec72be3c2f28d5094e9d5d21fdcaed5db5dbafcfac", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/prometheus/procfs@v0.17.0/LICENSE", + "notice_file": "github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "archive_path": "github.com/prometheus/procfs@v0.17.0/NOTICE", + "notice_file": "github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__NOTICE", + "sha256": "497fcdb9fae55924c12f55f05cbe83a096662e5c591d79b74e4a8f23bab5ffee" + } + ] + }, + { + "module": "github.com/segmentio/asm", + "version": "v1.1.3", + "h1": "h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=", + "archive": "github.com__segmentio__asm@v1.1.3.zip", + "url": "https://proxy.golang.org/github.com/segmentio/asm/@v/v1.1.3.zip", + "archive_sha256": "1911909b67b0f86c04fb895434c4072602d7b7523350ab45cb1549425b3a3b1b", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/segmentio/asm@v1.1.3/LICENSE", + "notice_file": "github.com__segmentio__asm@v1.1.3__segmentio__asm@v1.1.3__LICENSE", + "sha256": "e2a78de21d6d8ded2dff0f3189cd32e011630d785da127ebfbc8949012c0947b" + } + ] + }, + { + "module": "github.com/segmentio/encoding", + "version": "v0.5.4", + "h1": "h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=", + "archive": "github.com__segmentio__encoding@v0.5.4.zip", + "url": "https://proxy.golang.org/github.com/segmentio/encoding/@v/v0.5.4.zip", + "archive_sha256": "2189639875fe1153571445dc9b5dcaf221554143fe6701426fba69aa77780681", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/segmentio/encoding@v0.5.4/LICENSE", + "notice_file": "github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__LICENSE", + "sha256": "d6d71a1f7dc6539e371120cc7af6e3257e55ca79634d473211f217b8965b0f16" + }, + { + "archive_path": "github.com/segmentio/encoding@v0.5.4/json/fuzz/LICENSE", + "notice_file": "github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__json__fuzz__LICENSE", + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1" + } + ] + }, + { + "module": "github.com/spf13/cobra", + "version": "v1.9.1", + "h1": "h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=", + "archive": "github.com__spf13__cobra@v1.9.1.zip", + "url": "https://proxy.golang.org/github.com/spf13/cobra/@v/v1.9.1.zip", + "archive_sha256": "e44a319d167f096bd3ba0bd0c13cf17d8dc0b7d51ac784fa7440d005dd2ed5cb", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/spf13/cobra@v1.9.1/LICENSE.txt", + "notice_file": "github.com__spf13__cobra@v1.9.1__spf13__cobra@v1.9.1__LICENSE.txt", + "sha256": "5e3400b93bbb099e83e52bab885e7441750673c21f97988ca3f1240639b63283" + } + ] + }, + { + "module": "github.com/spf13/pflag", + "version": "v1.0.6", + "h1": "h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=", + "archive": "github.com__spf13__pflag@v1.0.6.zip", + "url": "https://proxy.golang.org/github.com/spf13/pflag/@v/v1.0.6.zip", + "archive_sha256": "d8dac1115c17fa667a93d50f34ddf37648eeb6b92446f429c6120777aa839678", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/spf13/pflag@v1.0.6/LICENSE", + "notice_file": "github.com__spf13__pflag@v1.0.6__spf13__pflag@v1.0.6__LICENSE", + "sha256": "b8514c577c1c4b46cee454d5a882b15fa411e72c5bd7f801f241591789fce61a" + } + ] + }, + { + "module": "github.com/wk8/go-ordered-map/v2", + "version": "v2.1.8", + "h1": "h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=", + "archive": "github.com__wk8__go-ordered-map__v2@v2.1.8.zip", + "url": "https://proxy.golang.org/github.com/wk8/go-ordered-map/v2/@v/v2.1.8.zip", + "archive_sha256": "c1781435c64e4cd92dd3ca585d5075eda12c0bb719a0c5f9e6ed272b65282e38", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/wk8/go-ordered-map/v2@v2.1.8/LICENSE", + "notice_file": "github.com__wk8__go-ordered-map__v2@v2.1.8__wk8__go-ordered-map__v2@v2.1.8__LICENSE", + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1" + } + ] + }, + { + "module": "github.com/yosida95/uritemplate/v3", + "version": "v3.0.2", + "h1": "h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=", + "archive": "github.com__yosida95__uritemplate__v3@v3.0.2.zip", + "url": "https://proxy.golang.org/github.com/yosida95/uritemplate/v3/@v/v3.0.2.zip", + "archive_sha256": "65a29b6ab759cd30a1ed496a8a77b144a5683b89d505cfde51f907cb7ab9b8bd", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "github.com/yosida95/uritemplate/v3@v3.0.2/LICENSE", + "notice_file": "github.com__yosida95__uritemplate__v3@v3.0.2__yosida95__uritemplate__v3@v3.0.2__LICENSE", + "sha256": "0761aadfb1921103752869ee942d4a71bdd54494697684d4b13dc17ad9781191" + } + ] + }, + { + "module": "go.opentelemetry.io/auto/sdk", + "version": "v1.2.1", + "h1": "h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", + "archive": "go.opentelemetry.io__auto__sdk@v1.2.1.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/auto/sdk/@v/v1.2.1.zip", + "archive_sha256": "a08a16fc0a7041cdb875f47a260a56000632212c85f65a302a043c76380c8335", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/auto/sdk@v1.2.1/LICENSE", + "notice_file": "go.opentelemetry.io__auto__sdk@v1.2.1__auto__sdk@v1.2.1__LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + } + ] + }, + { + "module": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", + "version": "v0.63.0", + "h1": "h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=", + "archive": "go.opentelemetry.io__contrib__instrumentation__net__http__otelhttp@v0.63.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/@v/v0.63.0.zip", + "archive_sha256": "bf6c9a7e1596e1939c28395f588a4a8dc9bbb69ffa7f2ed17e20878126bc06ee", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.63.0/LICENSE", + "notice_file": "go.opentelemetry.io__contrib__instrumentation__net__http__otelhttp@v0.63.0__contrib__instrumentation__net__http__otelhttp@v0.63.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel", + "version": "v1.41.0", + "h1": "h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=", + "archive": "go.opentelemetry.io__otel@v1.41.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/@v/v1.41.0.zip", + "archive_sha256": "1a624187c6ed53704d278e4cf12c143e1046ba1e894b8ea470cb309d31adf742", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel@v1.41.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel@v1.41.0__otel@v1.41.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel/exporters/prometheus", + "version": "v0.60.0", + "h1": "h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=", + "archive": "go.opentelemetry.io__otel__exporters__prometheus@v0.60.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/exporters/prometheus/@v/v0.60.0.zip", + "archive_sha256": "6dbe0a257a8655bf291736cf6f0bd889af4b7e73194dc444eae09ea00dfd31b9", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel/exporters/prometheus@v0.60.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel__exporters__prometheus@v0.60.0__otel__exporters__prometheus@v0.60.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel/metric", + "version": "v1.41.0", + "h1": "h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=", + "archive": "go.opentelemetry.io__otel__metric@v1.41.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/metric/@v/v1.41.0.zip", + "archive_sha256": "ee6cd9515bff605dc265e36f60d7ca293ce920688906ef8baef5d016474f2c2f", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel/metric@v1.41.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel__metric@v1.41.0__otel__metric@v1.41.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel/sdk", + "version": "v1.41.0", + "h1": "h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=", + "archive": "go.opentelemetry.io__otel__sdk@v1.41.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/sdk/@v/v1.41.0.zip", + "archive_sha256": "143225cecc6fd0b6b54d3d5e350896c78c886ae1646bbb7e9ea5df1e2f08b40e", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel/sdk@v1.41.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel__sdk@v1.41.0__otel__sdk@v1.41.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel/sdk/metric", + "version": "v1.41.0", + "h1": "h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=", + "archive": "go.opentelemetry.io__otel__sdk__metric@v1.41.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/sdk/metric/@v/v1.41.0.zip", + "archive_sha256": "16826e33f8ddb554573b4cbd4640eab539e879344c3947650b9230131f75c65b", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel/sdk/metric@v1.41.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel__sdk__metric@v1.41.0__otel__sdk__metric@v1.41.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.opentelemetry.io/otel/trace", + "version": "v1.41.0", + "h1": "h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=", + "archive": "go.opentelemetry.io__otel__trace@v1.41.0.zip", + "url": "https://proxy.golang.org/go.opentelemetry.io/otel/trace/@v/v1.41.0.zip", + "archive_sha256": "6376f38119647b709e49bd13585722a685578fb4e4cdaa46ed421a3832a2fa9e", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.opentelemetry.io/otel/trace@v1.41.0/LICENSE", + "notice_file": "go.opentelemetry.io__otel__trace@v1.41.0__otel__trace@v1.41.0__LICENSE", + "sha256": "1ae07514be1d7bb33f0698f8d91fb51b8b9fe1463157ec1c72081a49b9bc6f40" + } + ] + }, + { + "module": "go.uber.org/dig", + "version": "v1.19.0", + "h1": "h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=", + "archive": "go.uber.org__dig@v1.19.0.zip", + "url": "https://proxy.golang.org/go.uber.org/dig/@v/v1.19.0.zip", + "archive_sha256": "de898d875da050391419c1234a2f040e841e4628adc17100a09d3b5f501fa047", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.uber.org/dig@v1.19.0/LICENSE", + "notice_file": "go.uber.org__dig@v1.19.0__dig@v1.19.0__LICENSE", + "sha256": "5cce09a0a02e15f476aa4385bf23d13ec8447de58a7057523a82dcc80f0aff04" + } + ] + }, + { + "module": "go.uber.org/fx", + "version": "v1.23.0", + "h1": "h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=", + "archive": "go.uber.org__fx@v1.23.0.zip", + "url": "https://proxy.golang.org/go.uber.org/fx/@v/v1.23.0.zip", + "archive_sha256": "fbc2dc3329dc48622dea2b2a02a060cac28064fc38527f2a6a771abf20a97018", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.uber.org/fx@v1.23.0/LICENSE", + "notice_file": "go.uber.org__fx@v1.23.0__fx@v1.23.0__LICENSE", + "sha256": "bbe3f17ba5d70ac3277fb0551878c408f7f37cd8749056a3cf74b07c3b3eea09" + } + ] + }, + { + "module": "go.uber.org/multierr", + "version": "v1.11.0", + "h1": "h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", + "archive": "go.uber.org__multierr@v1.11.0.zip", + "url": "https://proxy.golang.org/go.uber.org/multierr/@v/v1.11.0.zip", + "archive_sha256": "2249b5d2fdce61f6ee661a679d8552599af084a761cbbc871da77641bddce0c3", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.uber.org/multierr@v1.11.0/LICENSE.txt", + "notice_file": "go.uber.org__multierr@v1.11.0__multierr@v1.11.0__LICENSE.txt", + "sha256": "dcdabe03bef2382a130640d1c3a4cd5ec42aba1035095c38272fde694eb72405" + } + ] + }, + { + "module": "go.uber.org/zap", + "version": "v1.27.0", + "h1": "h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=", + "archive": "go.uber.org__zap@v1.27.0.zip", + "url": "https://proxy.golang.org/go.uber.org/zap/@v/v1.27.0.zip", + "archive_sha256": "b994b96ff0bb504a3d58288ab88b9f3c6604689ea1afb69d25b509769705a6c2", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.uber.org/zap@v1.27.0/LICENSE", + "notice_file": "go.uber.org__zap@v1.27.0__zap@v1.27.0__LICENSE", + "sha256": "7de716e70addb64f9305298ef32a9dd68e40d5b3095a5d868ba4461404dbfbcf" + } + ] + }, + { + "module": "go.yaml.in/yaml/v2", + "version": "v2.4.2", + "h1": "h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=", + "archive": "go.yaml.in__yaml__v2@v2.4.2.zip", + "url": "https://proxy.golang.org/go.yaml.in/yaml/v2/@v/v2.4.2.zip", + "archive_sha256": "ed63e181cbb5a991c104639b0e6d48d45fd951f22b47d4c5d1a36156d8ed06ee", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "go.yaml.in/yaml/v2@v2.4.2/LICENSE", + "notice_file": "go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE", + "sha256": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1" + }, + { + "archive_path": "go.yaml.in/yaml/v2@v2.4.2/LICENSE.libyaml", + "notice_file": "go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE.libyaml", + "sha256": "a94710b55e03b5285f77d048c5ba61bb9d6ee04a06c0eb90e68821e11b0c707a" + }, + { + "archive_path": "go.yaml.in/yaml/v2@v2.4.2/NOTICE", + "notice_file": "go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__NOTICE", + "sha256": "f6c2dd3a67b576eafb89b80200b8b1627230bf3821a0c14cb99a22ac19107d00" + } + ] + }, + { + "module": "golang.org/x/net", + "version": "v0.43.0", + "h1": "h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=", + "archive": "golang.org__x__net@v0.43.0.zip", + "url": "https://proxy.golang.org/golang.org/x/net/@v/v0.43.0.zip", + "archive_sha256": "24d4f49b7e781763942533d5a5acc49ebd054e05c50bf4402b264695ef8d10c5", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "golang.org/x/net@v0.43.0/LICENSE", + "notice_file": "golang.org__x__net@v0.43.0__x__net@v0.43.0__LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + }, + { + "archive_path": "golang.org/x/net@v0.43.0/PATENTS", + "notice_file": "golang.org__x__net@v0.43.0__x__net@v0.43.0__PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] + }, + { + "module": "golang.org/x/oauth2", + "version": "v0.35.0", + "h1": "h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=", + "archive": "golang.org__x__oauth2@v0.35.0.zip", + "url": "https://proxy.golang.org/golang.org/x/oauth2/@v/v0.35.0.zip", + "archive_sha256": "0a34abf6ee35593a630bc63ec425da2c1b40d773dd5b55f4fa42e518cfba1016", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "golang.org/x/oauth2@v0.35.0/LICENSE", + "notice_file": "golang.org__x__oauth2@v0.35.0__x__oauth2@v0.35.0__LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + } + ] + }, + { + "module": "golang.org/x/sync", + "version": "v0.20.0", + "h1": "h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=", + "archive": "golang.org__x__sync@v0.20.0.zip", + "url": "https://proxy.golang.org/golang.org/x/sync/@v/v0.20.0.zip", + "archive_sha256": "7179d4d68800f6fdcadb9d4bbf11cbb5df9b40360c89305c203fe20723cbd375", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "golang.org/x/sync@v0.20.0/LICENSE", + "notice_file": "golang.org__x__sync@v0.20.0__x__sync@v0.20.0__LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + }, + { + "archive_path": "golang.org/x/sync@v0.20.0/PATENTS", + "notice_file": "golang.org__x__sync@v0.20.0__x__sync@v0.20.0__PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] + }, + { + "module": "golang.org/x/sys", + "version": "v0.41.0", + "h1": "h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=", + "archive": "golang.org__x__sys@v0.41.0.zip", + "url": "https://proxy.golang.org/golang.org/x/sys/@v/v0.41.0.zip", + "archive_sha256": "c48084c62177045c1091e3cab99aa03ebb003027435ca8bd36bb851340480a40", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "golang.org/x/sys@v0.41.0/LICENSE", + "notice_file": "golang.org__x__sys@v0.41.0__x__sys@v0.41.0__LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + }, + { + "archive_path": "golang.org/x/sys@v0.41.0/PATENTS", + "notice_file": "golang.org__x__sys@v0.41.0__x__sys@v0.41.0__PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] + }, + { + "module": "golang.org/x/time", + "version": "v0.15.0", + "h1": "h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", + "archive": "golang.org__x__time@v0.15.0.zip", + "url": "https://proxy.golang.org/golang.org/x/time/@v/v0.15.0.zip", + "archive_sha256": "8cfb20e915fa5d6c3fd264304233f8d71a3385e48a37819239c532ad9da27f84", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "golang.org/x/time@v0.15.0/LICENSE", + "notice_file": "golang.org__x__time@v0.15.0__x__time@v0.15.0__LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + }, + { + "archive_path": "golang.org/x/time@v0.15.0/PATENTS", + "notice_file": "golang.org__x__time@v0.15.0__x__time@v0.15.0__PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] + }, + { + "module": "google.golang.org/protobuf", + "version": "v1.36.8", + "h1": "h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=", + "archive": "google.golang.org__protobuf@v1.36.8.zip", + "url": "https://proxy.golang.org/google.golang.org/protobuf/@v/v1.36.8.zip", + "archive_sha256": "af08d9c5021d78e8f3884449709a543aa250bca0cb07a5e1ff62ec869c33f81b", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "google.golang.org/protobuf@v1.36.8/LICENSE", + "notice_file": "google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__LICENSE", + "sha256": "4835612df0098ca95f8e7d9e3bffcb02358d435dbb38057c844c99d7f725eb20" + }, + { + "archive_path": "google.golang.org/protobuf@v1.36.8/PATENTS", + "notice_file": "google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] + }, + { + "module": "gopkg.in/yaml.v3", + "version": "v3.0.1", + "h1": "h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=", + "archive": "gopkg.in__yaml.v3@v3.0.1.zip", + "url": "https://proxy.golang.org/gopkg.in/yaml.v3/@v/v3.0.1.zip", + "archive_sha256": "aab8fbc4e6300ea08e6afe1caea18a21c90c79f489f52c53e2f20431f1a9a015", + "h1_verified": true, + "legal_files": [ + { + "archive_path": "gopkg.in/yaml.v3@v3.0.1/LICENSE", + "notice_file": "gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__LICENSE", + "sha256": "d18f6323b71b0b768bb5e9616e36da390fbd39369a81807cca352de4e4e6aa0b" + }, + { + "archive_path": "gopkg.in/yaml.v3@v3.0.1/NOTICE", + "notice_file": "gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__NOTICE", + "sha256": "f6c2dd3a67b576eafb89b80200b8b1627230bf3821a0c14cb99a22ac19107d00" + } + ] + } + ], + "failures": [] +} diff --git a/licensing/third-party/tunnel-client/GO-PATENTS b/licensing/third-party/tunnel-client/GO-PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/GO-PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/OPENAI-LICENSE b/licensing/third-party/tunnel-client/OPENAI-LICENSE new file mode 100644 index 0000000..3c4dfe0 --- /dev/null +++ b/licensing/third-party/tunnel-client/OPENAI-LICENSE @@ -0,0 +1,187 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. diff --git a/licensing/third-party/tunnel-client/OPENAI-NOTICE b/licensing/third-party/tunnel-client/OPENAI-NOTICE new file mode 100644 index 0000000..1e816a0 --- /dev/null +++ b/licensing/third-party/tunnel-client/OPENAI-NOTICE @@ -0,0 +1,13 @@ +Copyright 2026 OpenAI + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +Distributed under the License is distributed on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +Limitations under the License. diff --git a/licensing/third-party/tunnel-client/README.md b/licensing/third-party/tunnel-client/README.md new file mode 100644 index 0000000..9dc03ad --- /dev/null +++ b/licensing/third-party/tunnel-client/README.md @@ -0,0 +1,24 @@ +# OpenAI tunnel-client and its incorporated Go modules + +The retained executable source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client is third-party OpenAI Secure MCP Tunnel software, licensed Apache-2.0. It is not original FrameBridge code. Its SHA-256 is 472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6. + +The Go build information identifies github.com/openai/tunnel-client/cmd/client, Go 1.27.0, module version (devel), and 47 dependency modules. It does not embed an upstream VCS revision. No claim is made that its exact own-source commit or independent reproducible build has been established. + +## Retained legal material + +- OPENAI-LICENSE and OPENAI-NOTICE were retrieved from the official OpenAI repository at commit cc81b261aa51059f6ef464ca236ce8d0c2347382. This pins the notice evidence; it is not asserted to be the executable's build commit. +- GO-LICENSE and GO-PATENTS are the Go 1.27.0 toolchain terms. +- licenses/ contains 61 legal files from the 47 exact incorporated module versions. +- GO-BUILDINFO.json records the embedded public dependency identities. +- GO-MODULE-SOURCE-NOTICES.json records the source archive hashes, embedded h1 checksums, exact legal-file paths and hashes. +- UPSTREAM-NOTICE-PROVENANCE.json records the notice source URLs and hashes. + +Every module archive was downloaded from the publisher-backed Go module proxy and its canonical h1 zip checksum was verified against the binary's embedded build information. Full license and notice files are preserved. Detected terms fall within the retained MIT, BSD and Apache-style set; GO-LICENSE-MARKERS.json is an aid to review, not a substitute for the full legal texts or a universal legal opinion. + +## Source supplement + +FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz contains all 47 exact Go module source archives and the retained legal/provenance material. Size: 26,941,745 bytes. SHA-256: 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9. + +This supplement supplies dependency sources, not a claim that the tunnel executable's own original source revision is known. The upstream project is https://github.com/openai/tunnel-client. Apache-2.0 remains the component's license; FrameBridge's GPL notice does not replace it. + +The root TUNNEL-DEPENDENCY-SOURCES.json and overall release checksum manifest bind the supplement to the retained executable identity. Ship these notices in both the binary package's documentation and the matching FrameBridge source, and make the supplement downloadable beside the release. diff --git a/licensing/third-party/tunnel-client/UPSTREAM-NOTICE-PROVENANCE.json b/licensing/third-party/tunnel-client/UPSTREAM-NOTICE-PROVENANCE.json new file mode 100644 index 0000000..534238d --- /dev/null +++ b/licensing/third-party/tunnel-client/UPSTREAM-NOTICE-PROVENANCE.json @@ -0,0 +1,26 @@ +{ + "openai_notice_source_commit": "cc81b261aa51059f6ef464ca236ce8d0c2347382", + "not_claimed_to_be_binary_build_commit": true, + "assets": [ + { + "name": "OPENAI-LICENSE", + "url": "https://raw.githubusercontent.com/openai/tunnel-client/cc81b261aa51059f6ef464ca236ce8d0c2347382/LICENSE", + "sha256": "f4c1d7ba32ef5bcf5cf03e2eefec5825ebafedf50fa330a36700a49c605c1ef4" + }, + { + "name": "OPENAI-NOTICE", + "url": "https://raw.githubusercontent.com/openai/tunnel-client/cc81b261aa51059f6ef464ca236ce8d0c2347382/NOTICE", + "sha256": "1364c020d86ecf948b78b7c655175032068203d13aece70fb0bfe112d7802dc2" + }, + { + "name": "GO-LICENSE", + "url": "https://raw.githubusercontent.com/golang/go/go1.27.0/LICENSE", + "sha256": "911f8f5782931320f5b8d1160a76365b83aea6447ee6c04fa6d5591467db9dad" + }, + { + "name": "GO-PATENTS", + "url": "https://raw.githubusercontent.com/golang/go/go1.27.0/PATENTS", + "sha256": "96f408bfae65bf137fc2525d3ecb030271c50c1e90799f87abf8846d8dd505cc" + } + ] +} diff --git a/licensing/third-party/tunnel-client/licenses/github.com__bahlo__generic-list-go@v0.2.0__bahlo__generic-list-go@v0.2.0__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__bahlo__generic-list-go@v0.2.0__bahlo__generic-list-go@v0.2.0__LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__bahlo__generic-list-go@v0.2.0__bahlo__generic-list-go@v0.2.0__LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__beorn7__perks@v1.0.1__beorn7__perks@v1.0.1__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__beorn7__perks@v1.0.1__beorn7__perks@v1.0.1__LICENSE new file mode 100644 index 0000000..339177b --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__beorn7__perks@v1.0.1__beorn7__perks@v1.0.1__LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__buger__jsonparser@v1.1.2__buger__jsonparser@v1.1.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__buger__jsonparser@v1.1.2__buger__jsonparser@v1.1.2__LICENSE new file mode 100644 index 0000000..ac25aeb --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__buger__jsonparser@v1.1.2__buger__jsonparser@v1.1.2__LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Leonid Bugaev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__cespare__xxhash__v2@v2.3.0__cespare__xxhash__v2@v2.3.0__LICENSE.txt b/licensing/third-party/tunnel-client/licenses/github.com__cespare__xxhash__v2@v2.3.0__cespare__xxhash__v2@v2.3.0__LICENSE.txt new file mode 100644 index 0000000..24b5306 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__cespare__xxhash__v2@v2.3.0__cespare__xxhash__v2@v2.3.0__LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2016 Caleb Spare + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__felixge__httpsnoop@v1.0.4__felixge__httpsnoop@v1.0.4__LICENSE.txt b/licensing/third-party/tunnel-client/licenses/github.com__felixge__httpsnoop@v1.0.4__felixge__httpsnoop@v1.0.4__LICENSE.txt new file mode 100644 index 0000000..e028b46 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__felixge__httpsnoop@v1.0.4__felixge__httpsnoop@v1.0.4__LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__go-logr__logr@v1.4.3__go-logr__logr@v1.4.3__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__go-logr__logr@v1.4.3__go-logr__logr@v1.4.3__LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__go-logr__logr@v1.4.3__go-logr__logr@v1.4.3__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__go-logr__stdr@v1.2.2__go-logr__stdr@v1.2.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__go-logr__stdr@v1.2.2__go-logr__stdr@v1.2.2__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__go-logr__stdr@v1.2.2__go-logr__stdr@v1.2.2__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__google__jsonschema-go@v0.4.3__google__jsonschema-go@v0.4.3__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__google__jsonschema-go@v0.4.3__google__jsonschema-go@v0.4.3__LICENSE new file mode 100644 index 0000000..1cb53e9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__google__jsonschema-go@v0.4.3__google__jsonschema-go@v0.4.3__LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 JSON Schema Go Project Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__google__uuid@v1.6.0__google__uuid@v1.6.0__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__google__uuid@v1.6.0__google__uuid@v1.6.0__LICENSE new file mode 100644 index 0000000..5dc6826 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__google__uuid@v1.6.0__google__uuid@v1.6.0__LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__grafana__regexp@v0.0.0-20240518133315-a468a5bfb3bc__LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__invopop__jsonschema@v0.13.0__invopop__jsonschema@v0.13.0__COPYING b/licensing/third-party/tunnel-client/licenses/github.com__invopop__jsonschema@v0.13.0__invopop__jsonschema@v0.13.0__COPYING new file mode 100644 index 0000000..2993ec0 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__invopop__jsonschema@v0.13.0__invopop__jsonschema@v0.13.0__COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__jpillora__backoff@v1.0.0__jpillora__backoff@v1.0.0__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__jpillora__backoff@v1.0.0__jpillora__backoff@v1.0.0__LICENSE new file mode 100644 index 0000000..1cc7080 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__jpillora__backoff@v1.0.0__jpillora__backoff@v1.0.0__LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Jaime Pillora + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__mailru__easyjson@v0.7.7__mailru__easyjson@v0.7.7__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__mailru__easyjson@v0.7.7__mailru__easyjson@v0.7.7__LICENSE new file mode 100644 index 0000000..fbff658 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__mailru__easyjson@v0.7.7__mailru__easyjson@v0.7.7__LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2016 Mail.Ru Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__modelcontextprotocol__go-sdk@v1.7.0__modelcontextprotocol__go-sdk@v1.7.0__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__modelcontextprotocol__go-sdk@v1.7.0__modelcontextprotocol__go-sdk@v1.7.0__LICENSE new file mode 100644 index 0000000..5791499 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__modelcontextprotocol__go-sdk@v1.7.0__modelcontextprotocol__go-sdk@v1.7.0__LICENSE @@ -0,0 +1,216 @@ +The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. + +Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License. + +No rights beyond those granted by the applicable original license are conveyed for such contributions. + +--- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on behalf + of the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +--- + +MIT License + +Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Creative Commons Attribution 4.0 International (CC-BY-4.0) + +Documentation in this project (excluding specifications) is licensed under +CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for +the full license text. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__LICENSE new file mode 100644 index 0000000..bbc7b89 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__munnerz__goautoneg@v0.0.0-20191010083416-a7dc8b61c822__LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2011, Open Knowledge Foundation Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of the Open Knowledge Foundation Ltd. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__panjf2000__ants__v2@v2.11.3__panjf2000__ants__v2@v2.11.3__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__panjf2000__ants__v2@v2.11.3__panjf2000__ants__v2@v2.11.3__LICENSE new file mode 100644 index 0000000..2e2dbb8 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__panjf2000__ants__v2@v2.11.3__panjf2000__ants__v2@v2.11.3__LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Andy Pan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__NOTICE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__NOTICE new file mode 100644 index 0000000..b9cc55a --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__NOTICE @@ -0,0 +1,18 @@ +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__internal__github.com__golang__gddo__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__internal__github.com__golang__gddo__LICENSE new file mode 100644 index 0000000..65d761b --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_golang@v1.23.2__prometheus__client_golang@v1.23.2__internal__github.com__golang__gddo__LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__NOTICE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__NOTICE new file mode 100644 index 0000000..20110e4 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__client_model@v0.6.2__prometheus__client_model@v0.6.2__NOTICE @@ -0,0 +1,5 @@ +Data model artifacts for Prometheus. +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__NOTICE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__NOTICE new file mode 100644 index 0000000..636a2c1 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__common@v0.66.1__prometheus__common@v0.66.1__NOTICE @@ -0,0 +1,5 @@ +Common libraries shared by Prometheus Go components. +Copyright 2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__otlptranslator@v0.0.2__prometheus__otlptranslator@v0.0.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__otlptranslator@v0.0.2__prometheus__otlptranslator@v0.0.2__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__otlptranslator@v0.0.2__prometheus__otlptranslator@v0.0.2__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__NOTICE b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__NOTICE new file mode 100644 index 0000000..53c5e9a --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__prometheus__procfs@v0.17.0__prometheus__procfs@v0.17.0__NOTICE @@ -0,0 +1,7 @@ +procfs provides functions to retrieve system, kernel and process +metrics from the pseudo-filesystem proc. + +Copyright 2014-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/licensing/third-party/tunnel-client/licenses/github.com__segmentio__asm@v1.1.3__segmentio__asm@v1.1.3__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__asm@v1.1.3__segmentio__asm@v1.1.3__LICENSE new file mode 100644 index 0000000..29e1ab6 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__asm@v1.1.3__segmentio__asm@v1.1.3__LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Segment + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__LICENSE new file mode 100644 index 0000000..1fbffdf --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Segment.io, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__json__fuzz__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__json__fuzz__LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__segmentio__encoding@v0.5.4__segmentio__encoding@v0.5.4__json__fuzz__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__spf13__cobra@v1.9.1__spf13__cobra@v1.9.1__LICENSE.txt b/licensing/third-party/tunnel-client/licenses/github.com__spf13__cobra@v1.9.1__spf13__cobra@v1.9.1__LICENSE.txt new file mode 100644 index 0000000..298f0e2 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__spf13__cobra@v1.9.1__spf13__cobra@v1.9.1__LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__spf13__pflag@v1.0.6__spf13__pflag@v1.0.6__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__spf13__pflag@v1.0.6__spf13__pflag@v1.0.6__LICENSE new file mode 100644 index 0000000..63ed1cf --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__spf13__pflag@v1.0.6__spf13__pflag@v1.0.6__LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__wk8__go-ordered-map__v2@v2.1.8__wk8__go-ordered-map__v2@v2.1.8__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__wk8__go-ordered-map__v2@v2.1.8__wk8__go-ordered-map__v2@v2.1.8__LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__wk8__go-ordered-map__v2@v2.1.8__wk8__go-ordered-map__v2@v2.1.8__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/github.com__yosida95__uritemplate__v3@v3.0.2__yosida95__uritemplate__v3@v3.0.2__LICENSE b/licensing/third-party/tunnel-client/licenses/github.com__yosida95__uritemplate__v3@v3.0.2__yosida95__uritemplate__v3@v3.0.2__LICENSE new file mode 100644 index 0000000..79e8f87 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/github.com__yosida95__uritemplate__v3@v3.0.2__yosida95__uritemplate__v3@v3.0.2__LICENSE @@ -0,0 +1,25 @@ +Copyright (C) 2016, Kohei YOSHIDA . All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__auto__sdk@v1.2.1__auto__sdk@v1.2.1__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__auto__sdk@v1.2.1__auto__sdk@v1.2.1__LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__auto__sdk@v1.2.1__auto__sdk@v1.2.1__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__contrib__instrumentation__net__http__otelhttp@v0.63.0__contrib__instrumentation__net__http__otelhttp@v0.63.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__contrib__instrumentation__net__http__otelhttp@v0.63.0__contrib__instrumentation__net__http__otelhttp@v0.63.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__contrib__instrumentation__net__http__otelhttp@v0.63.0__contrib__instrumentation__net__http__otelhttp@v0.63.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel@v1.41.0__otel@v1.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel@v1.41.0__otel@v1.41.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel@v1.41.0__otel@v1.41.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__exporters__prometheus@v0.60.0__otel__exporters__prometheus@v0.60.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__exporters__prometheus@v0.60.0__otel__exporters__prometheus@v0.60.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__exporters__prometheus@v0.60.0__otel__exporters__prometheus@v0.60.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__metric@v1.41.0__otel__metric@v1.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__metric@v1.41.0__otel__metric@v1.41.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__metric@v1.41.0__otel__metric@v1.41.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk@v1.41.0__otel__sdk@v1.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk@v1.41.0__otel__sdk@v1.41.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk@v1.41.0__otel__sdk@v1.41.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk__metric@v1.41.0__otel__sdk__metric@v1.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk__metric@v1.41.0__otel__sdk__metric@v1.41.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__sdk__metric@v1.41.0__otel__sdk__metric@v1.41.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__trace@v1.41.0__otel__trace@v1.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__trace@v1.41.0__otel__trace@v1.41.0__LICENSE new file mode 100644 index 0000000..f1aee0f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.opentelemetry.io__otel__trace@v1.41.0__otel__trace@v1.41.0__LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licensing/third-party/tunnel-client/licenses/go.uber.org__dig@v1.19.0__dig@v1.19.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.uber.org__dig@v1.19.0__dig@v1.19.0__LICENSE new file mode 100644 index 0000000..00a8d05 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.uber.org__dig@v1.19.0__dig@v1.19.0__LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017-2018 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/go.uber.org__fx@v1.23.0__fx@v1.23.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.uber.org__fx@v1.23.0__fx@v1.23.0__LICENSE new file mode 100644 index 0000000..20e81ea --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.uber.org__fx@v1.23.0__fx@v1.23.0__LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2018 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/go.uber.org__multierr@v1.11.0__multierr@v1.11.0__LICENSE.txt b/licensing/third-party/tunnel-client/licenses/go.uber.org__multierr@v1.11.0__multierr@v1.11.0__LICENSE.txt new file mode 100644 index 0000000..413e30f --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.uber.org__multierr@v1.11.0__multierr@v1.11.0__LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2017-2021 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/go.uber.org__zap@v1.27.0__zap@v1.27.0__LICENSE b/licensing/third-party/tunnel-client/licenses/go.uber.org__zap@v1.27.0__zap@v1.27.0__LICENSE new file mode 100644 index 0000000..6652bed --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.uber.org__zap@v1.27.0__zap@v1.27.0__LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE.libyaml b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE.libyaml new file mode 100644 index 0000000..8da58fb --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__NOTICE b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__NOTICE new file mode 100644 index 0000000..866d74a --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/go.yaml.in__yaml__v2@v2.4.2__yaml__v2@v2.4.2__NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__LICENSE b/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__PATENTS b/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__net@v0.43.0__x__net@v0.43.0__PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__oauth2@v0.35.0__x__oauth2@v0.35.0__LICENSE b/licensing/third-party/tunnel-client/licenses/golang.org__x__oauth2@v0.35.0__x__oauth2@v0.35.0__LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__oauth2@v0.35.0__x__oauth2@v0.35.0__LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__LICENSE b/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__PATENTS b/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__sync@v0.20.0__x__sync@v0.20.0__PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__LICENSE b/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__PATENTS b/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__sys@v0.41.0__x__sys@v0.41.0__PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__LICENSE b/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__PATENTS b/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/golang.org__x__time@v0.15.0__x__time@v0.15.0__PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__LICENSE b/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__LICENSE new file mode 100644 index 0000000..49ea0f9 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2018 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__PATENTS b/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/google.golang.org__protobuf@v1.36.8__protobuf@v1.36.8__PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__LICENSE b/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__LICENSE new file mode 100644 index 0000000..2683e4b --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__LICENSE @@ -0,0 +1,50 @@ + +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__NOTICE b/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__NOTICE new file mode 100644 index 0000000..866d74a --- /dev/null +++ b/licensing/third-party/tunnel-client/licenses/gopkg.in__yaml.v3@v3.0.1__yaml.v3@v3.0.1__NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/provenance/COMMUNITY-PREVIEW1-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW1-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..cc0455a --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW1-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1079 @@ +{ + "schema_version": 2, + "version": "0.3.1~community.20261025.1", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "401a5ed8d0e0d3a50c0fea543c36e12e68a8538d61ce13eabc9be6cff8029bd4", + "source/docs/R2-ADDITIONS.md": "dae6ca26e1866380de9211611498b7cb0db0c6843b3a7d88a1bacf3175631d7e", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "786f370f37b5cf544f9fb4ccf54862765812bacddf3bb3ca014cdc1da5625be8", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "faa5ae81fc79683af3c880330c7e2f772cacfcd1e4a798e794eed9597e62b19c", + "source/expanded/framebridge_expanded/__init__.py": "5ad71b807125b4e942cffe341de63254e5bce8de3df9d615dabbf8ee7787e567", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "3e8f0330c75fbe50a82ae16559068e85967acc70fbded6acf98286dd78cf5114", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "844765b4bf145f6e7180727fee8a5409743ee95fd761f6510a99a9fdc297e573", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "9e374a53cff0374204c14cccf077dbb151763e30d299d87e2f138ae7fabcbb1a", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "d3001f4a90df96e1aa9d35be40e4e36041e5f2614c6d94bea67676e534b2965a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "5ce9bc2a418dfcde6fbba5c79b47c4aac8af93c894f50d8bca5bc77b0eedfabd", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "e5a6a77a660f772ce971405cc6713588c89ea3d9204a04513ea7787cf2a5bf51", + "source/packaging/build_expanded.py": "e72dbd0c11cfb0c5f64ab00d960087131036f05628b04ae3d4520e0a92874737", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "af9e83adec85eae38d097e81e2437deeadbf60e6fe80f6bbdc7fa0995f33ccf6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "d73f53672769bf3b3afbe4ceff2a70553a5ea07ced2d11dc1d44f413f267464a", + "source/tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "ad5e85c16e4d91367dfe7c8dcdc2c7e699f0bc4f913084d42ff7ad9196f61d50", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "224428a95dcbba5325d8c4923066c860a5e1ea81170f503da3c9f1a39e79e97f" + } +} diff --git a/provenance/COMMUNITY-PREVIEW2-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW2-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..3ea2073 --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW2-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1081 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.2", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW1-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "05cd1f5037e99bd9b44ed23dde2d296174d362f540a9ccba8ce27b8f195f728a", + "source/docs/R2-ADDITIONS.md": "dae6ca26e1866380de9211611498b7cb0db0c6843b3a7d88a1bacf3175631d7e", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "786f370f37b5cf544f9fb4ccf54862765812bacddf3bb3ca014cdc1da5625be8", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "066bb8f3f2c1b4ebf0d5994893f5e33fa9fa35b59c5106dbb77e176139baf3a7", + "source/expanded/framebridge_expanded/__init__.py": "0b5d187dd7e110e236796c79791483aa68b1a5a624ace858a774fbd2879fbaf5", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "3e8f0330c75fbe50a82ae16559068e85967acc70fbded6acf98286dd78cf5114", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "844765b4bf145f6e7180727fee8a5409743ee95fd761f6510a99a9fdc297e573", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "9e374a53cff0374204c14cccf077dbb151763e30d299d87e2f138ae7fabcbb1a", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "d3001f4a90df96e1aa9d35be40e4e36041e5f2614c6d94bea67676e534b2965a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "5ce9bc2a418dfcde6fbba5c79b47c4aac8af93c894f50d8bca5bc77b0eedfabd", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "944a708446cd454f7d0fb46416da5f4688cb851131d27ccb2fd01fdec6cd9e2c", + "source/packaging/build_expanded.py": "cd0d96a6a007fe8048ca7e8f1b24a11140439c5575c146eb0c1fe2f9f4416d17", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "477342f39b3b54b19f58f79be891683e759fae355df027f9f528079856e80686", + "source/tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "ad5e85c16e4d91367dfe7c8dcdc2c7e699f0bc4f913084d42ff7ad9196f61d50", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "224428a95dcbba5325d8c4923066c860a5e1ea81170f503da3c9f1a39e79e97f" + } +} diff --git a/provenance/COMMUNITY-PREVIEW3-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW3-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..fe6d157 --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW3-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1083 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.3", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW2-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "9eaa0ff80e33263a0a81cec30055e245bd7a83bed275a6cafe6f12baea32464e", + "source/docs/R2-ADDITIONS.md": "dae6ca26e1866380de9211611498b7cb0db0c6843b3a7d88a1bacf3175631d7e", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "786f370f37b5cf544f9fb4ccf54862765812bacddf3bb3ca014cdc1da5625be8", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "0409a5a6343cd98b400f6cb20eee1ce4f876d186342dbbeb7f567a0d5eb7fd6d", + "source/expanded/framebridge_expanded/__init__.py": "c59d5202fac41f8368c6336ee1b6791f485824d1679f2d2b58a2c992742b3ef9", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "3e8f0330c75fbe50a82ae16559068e85967acc70fbded6acf98286dd78cf5114", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "844765b4bf145f6e7180727fee8a5409743ee95fd761f6510a99a9fdc297e573", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/connection_monitor.py": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "9e374a53cff0374204c14cccf077dbb151763e30d299d87e2f138ae7fabcbb1a", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "d3001f4a90df96e1aa9d35be40e4e36041e5f2614c6d94bea67676e534b2965a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "ca3309c9a85efd4cd8ff755623c0f93008679fc55cf356ddbd9fbc53f36e5231", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "1361aa226847c82577bf1e8d080b05bdbaa5a5b7639d2a097f71796e9fb3c1fe", + "source/packaging/build_expanded.py": "206e6ea9e1cd46856224cde53c017051eb9969b7f4f8d71962ee509ffcb6d11e", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "f2feefe2fa5de35b604c2907cad2db2fc38ecb72099fa57b71dd98f6e9d8af44", + "source/tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_connection_monitor.py": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "ad5e85c16e4d91367dfe7c8dcdc2c7e699f0bc4f913084d42ff7ad9196f61d50", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "0aebabb0994555e2db4a32fce635ea827bb43e63891a7d0591994de7a605713d" + } +} diff --git a/provenance/COMMUNITY-PREVIEW4-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW4-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..bc2bb52 --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW4-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1088 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.4", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW3-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "6163ac61ed6d74085256b9ae6cfc603c510c74322f37529b1304caaa1fe79f5a", + "source/docs/R2-ADDITIONS.md": "8f1bd45949604444a70f16ecae738643aa430134965003530189bf3c860d8f3e", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "900cfb75de33c71c277ac381fcfc993e9aac924f47edcb8d229b8f2818d5bfd1", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "4c316edfb79236b56f53fe909407fd138e40a20adb3363efecb5525f6c957f6f", + "source/expanded/framebridge_expanded/__init__.py": "bdecaf51c012e5f3abc2b1d42d4acd5d25dd683b88e0965b516151d49deb1890", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "4dfc8c4c6692457407344ff4444876315e42ec1b856e65e7b3e9ea8c2185ac60", + "source/expanded/framebridge_expanded/autostart.py": "55fa70c18e929c9a85bd9eb70a1ff761927a18cb7199d374cfc1f02e75b576f3", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "0f2e04269d8f0c880c895dc9c1657cd2be4b42f5ea55f03f0ef2685529e692d7", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/connection_background.py": "a594fbe3f52942eab0f620f38f0e7ca3de959db2909666b89ddd14f04603f18a", + "source/expanded/framebridge_expanded/connection_monitor.py": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "e71c138e6858432285a7895d9f6ef884e77e04f3a47d22891b9a463bc31ca4df", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "e9fb16072c94b965d807571448b42c0c61a0875649420bad4c31a4cdeee17938", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "59acecef273d0b363ae6ea19663a7676910b6a593b9140d9bf875303cc7559f6", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "d921ec3e9b130ef536378a202c012d502a0ec1e9a4222cb33fa7590c38fd96a4", + "source/packaging/build_expanded.py": "81e6a92508ae2f2a6ce3d4742fcb444598b7f73dfdf5ebf11f0132b242c9e6ae", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "5f25c03b6313c21335e8d91371523e6b07172d588305518b60f3e1a0dc9f9c3e", + "source/tests/test_adaptive_runtime.py": "415ea467ad8c95cc8bfa7185b7ec1bd0e4772c1667371f85dfb9d994acd6b777", + "source/tests/test_autostart.py": "99da56608453c493434bbf40dd36ae4f2fe3f53fe1b780b7db7a55a10cec9ecc", + "source/tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_connection_background.py": "27d7f43112bb9fefc2cf008165cca902048ca63e5970dabf2d6787c8184e9d63", + "source/tests/test_connection_monitor.py": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "14d0091fd8bcb1ba0eff153407c9d6df6336781d8418e10adad9c75c18f2b8bf", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "99c28b2b12f62df380a60ce80f51a40dfa115f42e91d46625fdf034678ba93a9" + } +} diff --git a/provenance/COMMUNITY-PREVIEW5-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW5-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..f2bcb87 --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW5-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1089 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.5", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW4-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "4f5ca58876524ea243f778caf2517fbf751a5565dd14cf88937c49175a489416", + "source/docs/R2-ADDITIONS.md": "2c88f94aaa9b305d4c6e67b3f0b5aa840441f2d539a9f67e4e827f61d97529cc", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "b9c474fcbdceafca8d3806c393dc0a91cf8820901a72dcb2c2d6766578b94b77", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "d6d73e265812e37519b6830c5d6ea2368c649ec8ba07e340cdf0fb13ce75e955", + "source/expanded/framebridge_expanded/__init__.py": "6fee3f09c216d49036180d51db0981caca54822275b1342948ae820cb967011f", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "4dfc8c4c6692457407344ff4444876315e42ec1b856e65e7b3e9ea8c2185ac60", + "source/expanded/framebridge_expanded/autostart.py": "55fa70c18e929c9a85bd9eb70a1ff761927a18cb7199d374cfc1f02e75b576f3", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "0f2e04269d8f0c880c895dc9c1657cd2be4b42f5ea55f03f0ef2685529e692d7", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/connection_background.py": "a594fbe3f52942eab0f620f38f0e7ca3de959db2909666b89ddd14f04603f18a", + "source/expanded/framebridge_expanded/connection_monitor.py": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "e71c138e6858432285a7895d9f6ef884e77e04f3a47d22891b9a463bc31ca4df", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "97b3586a240336e8fb71e34176cc819530765d8aa46201555d68b0d63d719c3a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "59acecef273d0b363ae6ea19663a7676910b6a593b9140d9bf875303cc7559f6", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "836a5e47ddcef2793c4f72000e28a991a58a7ea715209182019b449c1691aa8a", + "source/packaging/build_expanded.py": "edfae75ac3f983e4c3dafd67c87b1b000d94cfa3f0da3c6ab8a75e2c293d7f8f", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "35fee3645d94e3ccbcfb3329949432cf7507853f999d8916ef525408194dcea1", + "source/tests/test_adaptive_runtime.py": "415ea467ad8c95cc8bfa7185b7ec1bd0e4772c1667371f85dfb9d994acd6b777", + "source/tests/test_autostart.py": "99da56608453c493434bbf40dd36ae4f2fe3f53fe1b780b7db7a55a10cec9ecc", + "source/tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_connection_background.py": "27d7f43112bb9fefc2cf008165cca902048ca63e5970dabf2d6787c8184e9d63", + "source/tests/test_connection_monitor.py": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "14d0091fd8bcb1ba0eff153407c9d6df6336781d8418e10adad9c75c18f2b8bf", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_runtime_ports.py": "47b929261a384585ed2e3de4529a91b00e4477b16a38a88cc32f2ac1c942552b", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "99c28b2b12f62df380a60ce80f51a40dfa115f42e91d46625fdf034678ba93a9" + } +} diff --git a/provenance/COMMUNITY-PREVIEW6-SOURCE-SNAPSHOT-SHA256.json b/provenance/COMMUNITY-PREVIEW6-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..a8c34ee --- /dev/null +++ b/provenance/COMMUNITY-PREVIEW6-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1089 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.6", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW5-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "f0048301f5a0bab1e56964aa08b4bf5c4285c78284a59862a17c68c591411aca", + "source/docs/R2-ADDITIONS.md": "2c88f94aaa9b305d4c6e67b3f0b5aa840441f2d539a9f67e4e827f61d97529cc", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "b9c474fcbdceafca8d3806c393dc0a91cf8820901a72dcb2c2d6766578b94b77", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "2a44d0206284f365be8e635715e6dbcc5fa72c8482e47d308bb4e6127178ec31", + "source/expanded/framebridge_expanded/__init__.py": "5a5d467e157bc7e074117a276643424ea6596060744192855552ce3404ff5efa", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "4dfc8c4c6692457407344ff4444876315e42ec1b856e65e7b3e9ea8c2185ac60", + "source/expanded/framebridge_expanded/autostart.py": "264c14d5bb6c100ae81eb2a85e80cabc6ee60051c39ffa724cc88972f0219d54", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "fc98b69ce2453630b0f2ca09527fd72ded243c6321d541fceb9d1dc4fdcfb9fa", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/connection_background.py": "a594fbe3f52942eab0f620f38f0e7ca3de959db2909666b89ddd14f04603f18a", + "source/expanded/framebridge_expanded/connection_monitor.py": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "e71c138e6858432285a7895d9f6ef884e77e04f3a47d22891b9a463bc31ca4df", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "97b3586a240336e8fb71e34176cc819530765d8aa46201555d68b0d63d719c3a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "59acecef273d0b363ae6ea19663a7676910b6a593b9140d9bf875303cc7559f6", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "a637eea3d77291e4ae3e5e2acb6b88a92905798afa4b320294f176b178fbedaa", + "source/packaging/build_expanded.py": "919fa661bb91f9e71e6d1c433ad9855af42d7b5e0d45e80dc2a83a1c753578a6", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "10de5e362f22d626925332c37da68f22a1a9ec0c86f475258a119429eab9fdf4", + "source/tests/test_adaptive_runtime.py": "415ea467ad8c95cc8bfa7185b7ec1bd0e4772c1667371f85dfb9d994acd6b777", + "source/tests/test_autostart.py": "4ab25de8e2b29d27262904c6b10cebf562c7f9924f3548a4b3b318671c488f17", + "source/tests/test_cli.py": "899e62a7bc37e0d1e7ea6dac9883c67cf4e7de0b30e97559c52bc47677aaf5c6", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_connection_background.py": "27d7f43112bb9fefc2cf008165cca902048ca63e5970dabf2d6787c8184e9d63", + "source/tests/test_connection_monitor.py": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "14d0091fd8bcb1ba0eff153407c9d6df6336781d8418e10adad9c75c18f2b8bf", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_runtime_ports.py": "47b929261a384585ed2e3de4529a91b00e4477b16a38a88cc32f2ac1c942552b", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "99c28b2b12f62df380a60ce80f51a40dfa115f42e91d46625fdf034678ba93a9" + } +} diff --git a/provenance/RC7-SOURCE-SNAPSHOT-SHA256.json b/provenance/RC7-SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..6685fbc --- /dev/null +++ b/provenance/RC7-SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1074 @@ +{ + "files": { + "docs/DEBIAN-TUTORIAL.md": "401a5ed8d0e0d3a50c0fea543c36e12e68a8538d61ce13eabc9be6cff8029bd4", + "docs/R2-ADDITIONS.md": "dae6ca26e1866380de9211611498b7cb0db0c6843b3a7d88a1bacf3175631d7e", + "docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "docs/REQUIREMENTS.md": "786f370f37b5cf544f9fb4ccf54862765812bacddf3bb3ca014cdc1da5625be8", + "docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "evidence/BASELINE.json": "a241eea39bb8c702a8cfa6bd2bf9ef807be751c0acf2622bafb75b3266bc8609", + "expanded/framebridge_expanded/__init__.py": "7e1de9719253ab0defb41e074731e61f453d4e7e973b26b436322bb45c623389", + "expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "expanded/framebridge_expanded/adapter.py": "3e8f0330c75fbe50a82ae16559068e85967acc70fbded6acf98286dd78cf5114", + "expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "expanded/framebridge_expanded/cli.py": "844765b4bf145f6e7180727fee8a5409743ee95fd761f6510a99a9fdc297e573", + "expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "expanded/framebridge_expanded/display_context.py": "9e374a53cff0374204c14cccf077dbb151763e30d299d87e2f138ae7fabcbb1a", + "expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "expanded/framebridge_expanded/runtime.py": "d3001f4a90df96e1aa9d35be40e4e36041e5f2614c6d94bea67676e534b2965a", + "expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "expanded/framebridge_expanded/tunnel.py": "5ce9bc2a418dfcde6fbba5c79b47c4aac8af93c894f50d8bca5bc77b0eedfabd", + "packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "packaging/archive_sources.py": "424bc5b1af4b76b66d70542a4b6bc99fa28cb48c2011317639c37746852b4361", + "packaging/build_expanded.py": "6becee241e96bba0d34026e481b47d28f81e554d25c8829604ff9213a8fa8b29", + "packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "packaging/framebridge-original": "a942b3c2be1e5293bfbd4adc6d98791e17773d4c69b00e724400966d2c7a7066", + "packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "portable/gerameg_cua_frontdoor.py": "9daa6dc803abde1046c87de671fa47949c3dbea06b395d3c49a2892c4c7052d9", + "sources/original-asus/SOURCE-MANIFEST.json": "304397ab92aa8c4c5066b4c1a664f428503eb1574395f1a4e0c4a6f607069234", + "sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "sources/original-asus/framebridge-native/host/framebridge_host.py": "f4e2570201a16e72b2b52529d46e8ff727d1d8a72ee98300ad1b418dc9dac04b", + "sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "sources/original-asus/framebridge-native/host/open-progress-console.sh": "2cd1d6499586ab0ef1232ab15b73e3b05b9310d884a7ff692109c8faa659329c", + "sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "sources/original-gerameg/MANIFEST.json": "6543c0a59b2f9f069c1cc3cda1936ffe268a8ad40e0f962ccc75e58a38f32c9f", + "sources/original-gerameg/cua_tty_rpc.py": "9295cb44dc8bf61362569be8accd8639f944787b8ebfe2cfad50579666c9b2c5", + "sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "af9e83adec85eae38d097e81e2437deeadbf60e6fe80f6bbdc7fa0995f33ccf6", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9daa6dc803abde1046c87de671fa47949c3dbea06b395d3c49a2892c4c7052d9", + "sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "sources/original-geranas/MANIFEST.json": "d73f53672769bf3b3afbe4ceff2a70553a5ea07ced2d11dc1d44f413f267464a", + "tests/test_cli.py": "3d4a8993e97c1851b8f186d97271b4e2de38c4cae697eb9532888c00cd908bda", + "tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "tests/test_display_context.py": "ad5e85c16e4d91367dfe7c8dcdc2c7e699f0bc4f913084d42ff7ad9196f61d50", + "tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "tests/test_tunnel.py": "224428a95dcbba5325d8c4923066c860a5e1ea81170f503da3c9f1a39e79e97f" + } +} diff --git a/provenance/RC7.md b/provenance/RC7.md new file mode 100644 index 0000000..286e4dc --- /dev/null +++ b/provenance/RC7.md @@ -0,0 +1,36 @@ +# Historical RC7 artifact provenance + +The following identifiers describe the accepted historical RC7 artifacts, not the current community candidate. + +| Field | Historical value | +| --- | --- | +| Public installer filename | `FrameBridge-0.3.0-RC7.deb` | +| Debian package | `framebridge-original-expanded` | +| Debian version | `0.3.0~expanded.20261024.7` | +| Source archive | `framebridge-source-0.3.0~expanded.20261024.7.tar.gz` | + +Installer SHA-256: + +```text +55f391ed169626b24f589d26774f13e89083badaf7fa6af830f4776a14452e47 +``` + +Original source archive SHA-256: + +```text +59e5e34a35a3e9f235a4cb2747b993b78af7e37c7407435dbdfa594b975d2557 +``` + +The historical `framebridge.deb` name and the public versioned installer refer to the same RC7 bytes. The original acceptance evidence, export and installer remain preserved. + +## Current source is a separate candidate + +Community Preview 1 (`0.3.1~community.20261025.1`) changed 12 baseline paths and added a test to the 1,070-file RC7 export. It passed 368 tests and a reproducible package build. + +The current `source/` tree targets Preview 2, `0.3.1~community.20261026.2`. It adds an explicit development-broker peer policy, current installation instructions and R2 source/notice records. Current manifests, rather than historical file counts, describe the actual release set. Preview 2 passed 376 tests in 22.351 seconds; package and VM validation are pending at this documentation freeze. + +`SOURCE-SNAPSHOT-SHA256.json` is retained as a historical baseline record. It must not be used as a claim that every current file is unchanged. The candidate's source and installer require their own artifact hashes and validation. + +Running `source/packaging/build_expanded.py` from the current tree builds the community candidate. To reproduce RC7, use a separate extraction of the exact original source archive above and its original builder. Do not overwrite historical artifacts during that comparison. + +The owner selected GPL-3.0-only for original FrameBridge material on October 25, 2026. Repository licensing records and the candidate's new legal material do not claim that those files were already present inside the historical RC7 package. Third-party obligations and the candidate's corresponding-source arrangements remain distinct release responsibilities. diff --git a/provenance/SOURCE-SNAPSHOT-SHA256.json b/provenance/SOURCE-SNAPSHOT-SHA256.json new file mode 100644 index 0000000..d960741 --- /dev/null +++ b/provenance/SOURCE-SNAPSHOT-SHA256.json @@ -0,0 +1,1091 @@ +{ + "schema_version": 3, + "version": "0.3.1~community.20261026.7", + "baseline": "RC7-SOURCE-SNAPSHOT-SHA256.json", + "previous_community_baseline": "COMMUNITY-PREVIEW6-SOURCE-SNAPSHOT-SHA256.json", + "source_bytes_modified": true, + "files": { + "source/docs/DEBIAN-TUTORIAL.md": "0c114a537b6c6db8ec92be104fd2fb3e9966c4f737d78e6c5bbe9b18f648f972", + "source/docs/R2-ADDITIONS.md": "2c88f94aaa9b305d4c6e67b3f0b5aa840441f2d539a9f67e4e827f61d97529cc", + "source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md": "366aa3ec51d23a449a1d08e0a9a76b1e1ee765df85485472cdb02ac180b0601a", + "source/docs/REQUIREMENTS.md": "ea476e7d4777e8b21d831f0c5f7ddaa1117629a713ac5e5fdd7b33b8c8b21fe8", + "source/docs/VALIDATION.md": "6e0a60ef20133ed02f558fa8d61a25a70488922360082fcc3eb91a2f1fe22546", + "source/docs/framebridge.1": "3a26ea50e6bb9d570f2a4584fef82ad54990bf3fb8a5ef7c5b61713d97b0b112", + "source/evidence/BASELINE.json": "7b30559b3c2abd9fee5d62c924e3c273cf89002b223f751e4e5a2a46e1ad625e", + "source/expanded/framebridge_expanded/__init__.py": "f51137cf6d340140267a286a426731a274dd1774855c88808907c1eba8363038", + "source/expanded/framebridge_expanded/_tty_backend.py": "a8e489c7555a67d3c9044fa801bb1f4f21215c393f4db40fd5441108e0836b87", + "source/expanded/framebridge_expanded/adapter.py": "4dfc8c4c6692457407344ff4444876315e42ec1b856e65e7b3e9ea8c2185ac60", + "source/expanded/framebridge_expanded/autostart.py": "6f0cfc475769d85181a6e25b9d3ea1c2f17045f29788ca1d215010d0aa5a2f88", + "source/expanded/framebridge_expanded/broker.py": "d45cdbc68ba5322768d0d0827044ca9418a0337deca08391d2a8992ffd0bbec7", + "source/expanded/framebridge_expanded/cli.py": "74f7bd91b6d0255cfbe48c093ae703e9ae9f5bda746cc87a1f60c7406832057d", + "source/expanded/framebridge_expanded/config.py": "c7c612896a7a271b0677c6407ce554b2f9272b1d4a1f82e428beb5b16df8b61b", + "source/expanded/framebridge_expanded/connection_background.py": "a594fbe3f52942eab0f620f38f0e7ca3de959db2909666b89ddd14f04603f18a", + "source/expanded/framebridge_expanded/connection_monitor.py": "c24ece3c96838855c42cabf15a0431da86cf596f08baf6d3ba593ba4beadfcef", + "source/expanded/framebridge_expanded/console.py": "d37f3a8fcfc876e2e012da3c211355d9eb4ff8ee9e2e1e1d8b74d01435194f08", + "source/expanded/framebridge_expanded/console_startup.py": "0b1bce5bee795f9e9e93daa212605b7379c9fa9588383020060f57aa79e71ca5", + "source/expanded/framebridge_expanded/console_ui/__init__.py": "9ea7677a94b46ba1761334c4ba4f65da8896a8596c415fd6980718db11f4c43c", + "source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py": "11edb6eb8acede578170bdd3fe0ce4dd63e0d14a819d1d2e96062050b165a943", + "source/expanded/framebridge_expanded/console_ui/framebridge_console.py": "0f91640363c9839be9af7edc07cfd5d60b6cc8565d4fd3582d671049a7497929", + "source/expanded/framebridge_expanded/display_context.py": "e71c138e6858432285a7895d9f6ef884e77e04f3a47d22891b9a463bc31ca4df", + "source/expanded/framebridge_expanded/fleet.py": "9429a0fbbbf6af76d4aa653d6fc467695061f2c6bf0e4a8939f5d6d4cf6da1ed", + "source/expanded/framebridge_expanded/native_host.py": "59faa50c86827d851ec46792d438e946a6711266b6f204cfb04d86baf88931ce", + "source/expanded/framebridge_expanded/network.py": "b1ba28f8a7ec500657894220e35f0c9935069de95157afa9fcfbb849e119fe5b", + "source/expanded/framebridge_expanded/node.py": "0b886325f968f9477a1be2914ef5f7902744bdbfa65743094de146cd5a13eb39", + "source/expanded/framebridge_expanded/real_screen/__init__.py": "c21d4fff96c4895556d344d084676ce6b4bad5874fb712e85d9bfc87a47ecc03", + "source/expanded/framebridge_expanded/real_screen/screen_capture.py": "65b105ca0f9bd2720a4345a8582fb4dfe4d18b8a368ac6fda10325cf21578f88", + "source/expanded/framebridge_expanded/real_screen/screen_control.py": "de551cc58610474e4821d263effb8ba51057e04b348f53dff6128e9052849845", + "source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py": "eb28af5858350a5cb73fcb57d0b2498f3954dc1ed06cbd57d271fee2cb7ff986", + "source/expanded/framebridge_expanded/real_screen/screen_presentation.py": "a1411f297e391e22f8ee70cc440dff838498fbe56e50b541181edace7e515ff4", + "source/expanded/framebridge_expanded/real_screen/screen_rpc.py": "c39023ad28cac9ae57e34367d7883ed2c27083546b8f8dddf9122369ee7a512c", + "source/expanded/framebridge_expanded/real_screen/screen_transport.py": "43ce3487744889bacaf1951b053fc4952fc1f8dc9d690b236c12e66508ad5fd3", + "source/expanded/framebridge_expanded/runtime.py": "97b3586a240336e8fb71e34176cc819530765d8aa46201555d68b0d63d719c3a", + "source/expanded/framebridge_expanded/screen_access.py": "3a56f3862defb00bf4048966ba8ddddd9416989ddda294314ac8351e6d57ad27", + "source/expanded/framebridge_expanded/tls.py": "cf59e8efd681fc26e5129c457eeb4396f0d3ca922afc0fde69ba165b94bc11d6", + "source/expanded/framebridge_expanded/tty.py": "4139f9f477c81b62adb64b370d07fd6967b27036967111beefa2ba59dd60ed78", + "source/expanded/framebridge_expanded/tty_mcp.py": "e6bf18f9925d9a67bfe78ccd9cd8825ed9bdb6ed041eead7e6103bb4d61eb2b3", + "source/expanded/framebridge_expanded/tty_sudo_control.py": "0e8ee6edd24e204a9330f0bfe726126702e0513833b711fe3671a7e1e5ce71b1", + "source/expanded/framebridge_expanded/tunnel.py": "df2a77d6e84e87762d4951b745e42f1d14dea0ac1df19a4534af9d8b38991bb6", + "source/expanded/framebridge_expanded/uninstall.py": "5ac197d93358deea8402309c15c969f8e48d6d03b7aa072c729c80c80165baf0", + "source/packaging/70-framebridge-screen.rules": "e92e9400666656a26a5408fb72d572e9f94751d86be62c1f4bde24bccfb55bc0", + "source/packaging/archive_sources.py": "32900ae2d03f77a4d2cea8b9f9d0dca256f35cab75c83400c87731dcfeb209bf", + "source/packaging/build_expanded.py": "9dc572eb4e9791f740f0580d5ce24ac0ac193698f23966a9a3b2b75e0c43726c", + "source/packaging/build_original.py": "cd7ba05a5ade8ed1b3d2be32bac5e951390194a73cce6a3558683b2160ff9ea7", + "source/packaging/framebridge": "7e51aad9cdc4ffccd7c42081e292912b0a1ceba3cf0794e2f41dd963177d3142", + "source/packaging/framebridge-native-expanded": "f80c7750c6571fb6c3a2501c665f81bec387ac7e33613002afc2e138775ae89a", + "source/packaging/framebridge-original": "746493c20b96344512eb778710db3cb661fa9b4c99992372a2112aea7a0ab2f1", + "source/packaging/framebridge-screen-access": "1f391e4bcbd8693a6bf336ccddf70c1897ff5ca71064b51970184c20c7099f1f", + "source/portable/FRONT-DOOR-EXPANDED-PATCH.diff": "0984ff2871787b5c022536bcd61e0acb610d1425bda995d5d408623551209fe1", + "source/portable/FRONT-DOOR-PATCH.diff": "42a3402cc18f5dddfd4e056c529adf562e97c09a189fa82a37c14e49518311fb", + "source/portable/README-PORTABILIDAD.md": "9161276265f39280e7e4afa803541a34f58682114987c862e25983e985441f17", + "source/portable/front_door_mcp.py": "e6b9d0687abade8fd85bfba118653a4fd603b84900bd8f1b29d4952346ac8697", + "source/portable/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "source/sources/original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source/sources/original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source/sources/original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source/sources/original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source/sources/original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source/sources/original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source/sources/original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source/sources/original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source/sources/original-gerameg/MANIFEST.json": "246c4fbbaee355f7ae46e27be195765f049f27d61750b382fe4fe0f001094af6", + "source/sources/original-gerameg/cua_tty_rpc.py": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175", + "source/sources/original-gerameg/framebridge_cua_tty/__init__.py": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c", + "source/sources/original-gerameg/framebridge_cua_tty/client.py": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7", + "source/sources/original-gerameg/framebridge_cua_tty/core.py": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590", + "source/sources/original-gerameg/framebridge_cua_tty/demo.py": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d", + "source/sources/original-gerameg/framebridge_cua_tty/screen.py": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7", + "source/sources/original-gerameg/framebridge_cua_tty/server.py": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d", + "source/sources/original-gerameg/gerameg_cua_runtime.py": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887", + "source/sources/original-gerameg/real_tty_manager.py": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8", + "source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "source/sources/original-geranas/MANIFEST.json": "a39c8364800da04ca48eb0ce4ce7781346d22f85110f110462340ff0229d3d6a", + "source/tests/test_adaptive_runtime.py": "415ea467ad8c95cc8bfa7185b7ec1bd0e4772c1667371f85dfb9d994acd6b777", + "source/tests/test_autostart.py": "d4437493caa924f9767ca482d885691667ee74ec2f912e229c70ede890ea4434", + "source/tests/test_cli.py": "b7ea1522a875091446107ca48f0711c1f88eb5c27c3dcea2efb337c9c286f146", + "source/tests/test_cli_extensions.py": "05bed5cbe069d736e12d272b7845caae08aa689cf15617489eedb90d82a55996", + "source/tests/test_connection_background.py": "27d7f43112bb9fefc2cf008165cca902048ca63e5970dabf2d6787c8184e9d63", + "source/tests/test_connection_monitor.py": "b4bf4c661a5df7b0a9058b08c715761ccd9b1ff2d83730441df6e5640d97a1e0", + "source/tests/test_console.py": "aa86f0eb716ca4dac01990208560b3f1d6c4fbdead457be28e9adeadf6190b2c", + "source/tests/test_console_nesting.py": "e7d76bf5b109bed0837a3146358d630c146bbd11eef3d7460320ddd3227f5d9b", + "source/tests/test_console_startup.py": "ce7d98d387d7b85ad2867633802acf31b2dcd9e8b7e4cc7fe9b287008d035cce", + "source/tests/test_dashboard_sources.py": "9e866e6ab2eab64a360f31dcdb84a2b3ca367e1fad0ca17b7ee6acfcf49205a2", + "source/tests/test_display_context.py": "14d0091fd8bcb1ba0eff153407c9d6df6336781d8418e10adad9c75c18f2b8bf", + "source/tests/test_fleet_broker_node.py": "c58fb1d7c39aea2c863bf7d5d08e9e6956ac27711449df440fbe44f017eedb89", + "source/tests/test_fleet_routing.py": "13ef9f53b2729ed090baab7d86ef07ef675c7ee0d6333bd76463aad72232ad1a", + "source/tests/test_legacy_broker_peers.py": "def344a76ab89a4f372e73175e42ccc5af8d17ded596088e44d0125cd31f9211", + "source/tests/test_native_dispatch.py": "6b230b5596f799d76226287f96208cb97a289157421e15846d02a6de1465f234", + "source/tests/test_network.py": "e52c5470a52fbc5a67f7a4beba2c1def6ec4a255df3a6c7b1e9ec830ab5f9181", + "source/tests/test_portable_capture_scope.py": "2f2d4bb7b4dc6a39bc66dca704c3f9d3675495b8d06899699c040c2f7f118935", + "source/tests/test_public_release_privacy.py": "f6a10daa435e20c7ec43d1c5751d9e6437cb6322b245275de6d38c465160b3ff", + "source/tests/test_runtime_ports.py": "47b929261a384585ed2e3de4529a91b00e4477b16a38a88cc32f2ac1c942552b", + "source/tests/test_screen_access.py": "dfc4a8057a9de2d6a57e83716cdf43220a78b6ad02cb7cae78810c32491af579", + "source/tests/test_screen_capture.py": "8e71c54dd1978bfc16ccd257f24a13a3640be0cdb41e3860c1d21ab671ec5c81", + "source/tests/test_screen_control.py": "83b7630df59b2d7f1c423949a326eb818171556c4fbb9a49b5de7910500a31aa", + "source/tests/test_screen_frontdoor.py": "95d39b62030be2875cd036ff89c5ddc1f513bce85dac500d875c688c9bbe3876", + "source/tests/test_screen_presentation.py": "11315f58b41a6acb8a02dd0f8b4a6cb982d4cb5dea8c632b7caecb4d8ce4770f", + "source/tests/test_screen_profile_routing.py": "7bc48c5eb68af52f93bde81f58571699c492b33c69c3c3e6b714e79a4bc31a81", + "source/tests/test_screen_registration.py": "77296cdbdafe8183149ea26c770543f431ed82d6ce7a8bc483f2c8ce998bd4c4", + "source/tests/test_screen_rpc.py": "1e05ef0d60803a3b3c82f5abf0d349e457bd2ff27858c25b991fcdfdd3699ce3", + "source/tests/test_screen_rpc_x11.py": "507df0a614d76212d101c3166bd605348755ab5cdfbde810dfa527b02c984a0b", + "source/tests/test_screen_transport.py": "3b1a6721f74d6f80a8c7d85983359331ff3f758404debfad315485d7cd93c369", + "source/tests/test_screen_viewer_js.py": "8c40ee639f7378ad4a483116d76619855b31c5d4556ce0f4ce8c27d9c5da6b64", + "source/tests/test_tls.py": "44ac534a6ae412eba961b81f84ac7ac5481efd1d4147328828bbec2acec53dd6", + "source/tests/test_tty_mcp.py": "8739c2e91a56c16a86a9319b9f3572ac11ebb042eb557470f6d235c4baf1c081", + "source/tests/test_tty_runtime.py": "4b36d973076d3c83c7824cfc344710b4df55cae4db88e880b4d204e94e822b78", + "source/tests/test_tty_sudo.py": "119e78eacdf4af4406fbe7047d0f78c27fea346f9016f753f34a3631a9c6ea38", + "source/tests/test_tunnel.py": "c9f97ce40e12be22177ae1b7c5e47073a17a984ae32932fef43557590996f15c", + "source/tests/test_uninstall.py": "21036a8839b1e2c730cfdadb5b184d949516525d7aa27d1a661af892573d9c73" + } +} diff --git a/source/docs/COMMAND-GUIDE.json b/source/docs/COMMAND-GUIDE.json new file mode 100644 index 0000000..8a736dc --- /dev/null +++ b/source/docs/COMMAND-GUIDE.json @@ -0,0 +1,571 @@ +{ + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topics": { + "start": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "start", + "title": "Start with FrameBridge", + "sections": [ + { + "heading": "One interface for people and agents", + "body": "FrameBridge provides tools and a shared command-line interface for human operators and authorized agents. fb is the short name for framebridge; both run the same program. The tools run on your Linux computers. The ChatGPT model remains hosted. Read this guide for the installed version before operating; confirm the receiver, target and available capabilities. Use the installed CLI and tools for normal work. Edit software only when the owner explicitly requests development, using a separate source clone and reviewable changes rather than rewriting the installed program.", + "commands": [ + "fb help", + "fb status", + "fb --json status" + ] + }, + { + "heading": "Install and connect", + "body": "On a supported Debian system, install the downloaded package with the package manager. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal. Advanced setup is optional for selecting central/node roles and public settings.", + "commands": [ + "sudo apt install ./FrameBridge-0.3.1.deb", + "fb connect" + ] + }, + { + "heading": "Updating and removal", + "body": "Install an official update through the normal package manager. Normal package removal stops package-owned managed processes and retains the profile and saved encrypted connection material for deliberate reuse; it is not a credential-forgetting operation. Use the separately documented explicit forget action first if that is your intended outcome. Do not assume purge erases user-profile data.", + "commands": [ + "sudo dpkg --remove framebridge-original-expanded" + ] + }, + { + "heading": "Human authentication", + "body": "The normal connection asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. The label is metadata, not proof of ChatGPT login. Browser login/consent and normal operating-system authentication remain human actions in the normal interface. Never put credentials in chat, command arguments, documentation or example files. A successful managed connection returns to the prompt and can run independently of that terminal. Reboot only when an actual system change requires it.", + "commands": [] + }, + { + "heading": "Choose the right guide", + "body": "Use commands for the reference, connection and lifecycle for state changes, nodes for central/node work, terminals for persistent Bash, and screens for authentic CUA evidence. Query success alone does not mean the connection is ready. The guide describes registered operations; use status/doctor to investigate current state.", + "commands": [ + "fb help commands", + "fb help connection", + "fb help lifecycle" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "ac55eed900b5d1687b784299c5dfa238c7502c8472b1135dfa2d7231352f9daa" + }, + "commands": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "commands", + "title": "Command reference", + "sections": [ + { + "heading": "Everyday commands", + "body": "connect establishes or resumes the managed ChatGPT connection. disconnect pauses that connection until an explicit connect, including across recovery and reboot. status reports runtime, connection, intended connection state and boot policy. doctor diagnoses prerequisites/problems. setup edits advanced stopped-profile settings. console opens the text work view. help reads this versioned reference.", + "commands": [ + "fb connect", + "fb disconnect", + "fb status", + "fb status connection", + "fb status runtime", + "fb status boot", + "fb status console", + "fb doctor", + "fb setup", + "fb console", + "fb help" + ] + }, + { + "heading": "Services and boot", + "body": "For an already configured managed profile, service start/stop control the current stack symmetrically. Boot enable/disable changes future startup policy without stopping current work or forgetting the saved credential. First node provisioning is an explicit exception described in lifecycle. Without managed setup, non-node service start starts local tools only; normal first connection should use connect. Read status before and after changes. The separate credentials forget action is destructive and requires explicit confirmation.", + "commands": [ + "fb service start", + "fb service stop", + "fb service boot enable", + "fb service boot disable" + ] + }, + { + "heading": "Console and specialized operations", + "body": "console login manages optional work-view opening after an authenticated tty1 login; it does not enable automatic login. tty service manages the terminal daemon; tty list lists sessions. nodes, certificates and screen-access retain their domain-specific subcommands. Use --help at the relevant command for its exact arguments. Avoid compatibility aliases in new automation.", + "commands": [ + "fb console login status", + "fb tty service status", + "fb tty list", + "fb nodes --help", + "fb certificates --help", + "fb screen-access --help" + ] + }, + { + "heading": "Human and structured output", + "body": "Use explicit commands in automation rather than the interactive no-argument menu. --json selects structured output for an agent; examine operation success, readiness and state separately. Read errors and the exit status. If human interaction is required, complete it in the normal terminal/interface rather than attempting to supply a secret through the agent. CLI JSON uses an envelope with schema_version, product, version, command, ok and result. Read runtime, connection and boot truth inside result; envelope ok denotes request success. Exit 0 means successful query/operation, not necessarily a connected transport; exit 1 reports operation/diagnostic failure, exit 2 invalid arguments or required human interaction/confirmation, and exit 130 cancellation.", + "commands": [ + "framebridge --json status", + "fb --json status connection" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "9bc22a59b75eba4a6a0734320f76b5216001bbe527b2575f9f5dcee2a6abfc99" + }, + "connection": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "connection", + "title": "Connect and disconnect ChatGPT", + "sections": [ + { + "heading": "Normal managed connection", + "body": "connect creates the default standalone profile when absent and establishes managed startup. It asks for initial human input in the terminal, checks progress, reports readiness or an actionable failure, and returns to the prompt. Valid saved setup can be resumed without repeating the initial fields. The MCP access key is not an OpenAI API key. Keep the browser's ChatGPT account login and consent separate from the optional local account label.", + "commands": [ + "fb connect", + "fb status connection" + ] + }, + { + "heading": "Saved credentials and next boot", + "body": "Managed startup uses protected, host-bound encrypted connection material. The normal terminal does not have to remain open. This is a persistence feature, not a promise that an account, network or tunnel service can never fail. Connection intent and boot policy are separate: enabled startup runs the stack at boot, while an intentional disconnected state remains disconnected until connect.", + "commands": [ + "fb status boot", + "fb status" + ] + }, + { + "heading": "Intentional disconnect", + "body": "disconnect keeps local tools and saved material, but records the intention to stay disconnected across worker recovery and reboot. Explicit connect clears that pause. Check the returned state rather than interpreting a successful query as an active transport.", + "commands": [ + "fb disconnect", + "fb status connection" + ] + }, + { + "heading": "Replacement and temporary foreground mode", + "body": "Use connect --replace only when deliberately replacing the account label/tunnel/access configuration. The advanced --foreground mode is temporary, needs a ready local runtime and occupies its terminal until interrupted; it is not the normal managed workflow. Do not use advanced mode merely to recover a normal saved connection.", + "commands": [] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "601d608bf403f0cf3cd531dc42013c1d53cddaf622dd73b8ec2a13aeb9c98fb8" + }, + "lifecycle": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "lifecycle", + "title": "Services, connection intent and boot policy", + "sections": [ + { + "heading": "Separate states", + "body": "FrameBridge distinguishes actual local runtime, desired connection, verified transport, boot enablement and saved credential presence. A healthy runtime can be intentionally disconnected; a service can be active while disabled for the next boot. Read the labelled status fields together.", + "commands": [ + "fb status", + "fb --json status" + ] + }, + { + "heading": "Current managed stack", + "body": "For an already configured managed profile, service start and service stop use the same current-stack boundary without changing boot enablement. Stop ends current managed work and transport but keeps profile, boot policy and saved material. Startup on the next boot follows the retained policy and intended connection state. A non-node profile without managed setup starts only local tools. Stopping a terminal service or session can interrupt its running work.", + "commands": [ + "fb service start", + "fb service stop" + ] + }, + { + "heading": "Future boot policy", + "body": "Once managed setup exists, service boot enable/disable changes unit enablement only. It does not immediately stop or start current work and does not delete saved connection material. Initial node provisioning is described separately below. Explicitly forgetting saved material is a separate destructive operation. Legacy autostart disable has its old broader destructive behavior; use the canonical service boot commands for new workflows.", + "commands": [ + "fb service boot enable", + "fb service boot disable", + "fb status boot" + ] + }, + { + "heading": "First managed startup for a node", + "body": "After advanced node setup and the required enrollment/approval, a node without a managed installation can run service start in a normal terminal. This first call creates managed startup, enables future boot and starts the node now; the result explicitly reports initial_configuration and boot_policy_changed. Alternatively, first service boot enable prepares and enables the node unit without starting current work. Initial preparation can require normal owner authentication and is not silently performed by an unattended JSON request. Once configured, the ordinary start/stop and boot-policy-only contracts apply. A node does not need its own ChatGPT tunnel.", + "commands": [ + "fb service start", + "fb service boot enable", + "fb status boot" + ] + }, + { + "heading": "Console opening", + "body": "Opening console is a view operation, separate from Linux service startup. Optional console login enable/disable changes the owned startup hook after an authenticated tty1 Bash login. This hook does not bypass normal login. Review the command's scope before changing it.", + "commands": [ + "fb console login status" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "e6aca4ba3fc004cc2475ea141a1f645070a5bde306387e89ec904daec2b2f317" + }, + "nodes": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "nodes", + "title": "Central and node operation", + "sections": [ + { + "heading": "Roles", + "body": "Standalone provides local tools. Central adds the broker for enrolled nodes. A node joins one selected central instance and uses its ChatGPT connection; it does not need another ChatGPT tunnel or phone pairing. Advanced setup selects the role. Preserve explicit authenticated node transport and real identity approval.", + "commands": [ + "fb setup", + "fb nodes --help" + ] + }, + { + "heading": "Enrollment is not availability", + "body": "Enrollment submission, central approval, node receipt of authorization and a running node agent are separate stages. status reports what it actually knows; an unchecked agent is not evidence that the node is online. Central list/pending/approve and node enroll/poll/status/run have role-specific purposes. poll can complete saved authorization; it is not a purely observational operation. After enrollment, consult the lifecycle topic for first managed node startup and its explicit initial boot-policy effect.", + "commands": [ + "fb nodes list", + "fb nodes pending", + "fb nodes status" + ] + }, + { + "heading": "Targets", + "body": "LOCAL means the actual receiving machine. Use enrolled, confirmed aliases or IDs and inspect the selected target before work. Do not guess SSH identities or use historical lab hostnames as defaults. CLI tty --node can use the authenticated node transport. The interactive console command is local; run it at the destination or use an explicitly configured SSH destination.", + "commands": [] + }, + { + "heading": "Transport trust", + "body": "certificates create manages a private central/node transport CA, not a public website certificate or ChatGPT account authentication. Private keys stay protected on the central host; distribute only the required public trust material. Off-loopback broker use requires correctly configured verified TLS. Do not recreate trust merely to work around a connection symptom.", + "commands": [ + "fb certificates --help" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "440ed8b07ab2d7c89d4383d91342801a36f46dba72692482d419f457bb412186" + }, + "terminals": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "terminals", + "title": "Persistent Bash terminals", + "sections": [ + { + "heading": "Service and sessions", + "body": "tty service controls the profile's terminal daemon. tty open creates a real Bash session and returns its identifier. Use that identifier for subsequent observations and input. The same Bash preserves directory, variables and work between requests while the session/service remains alive. It does not preserve running processes across an operating-system reboot. Tool jobs use a profile-specific HOME; a job's home directory can therefore differ from the human login home. Commands retain the connecting account's normal operating-system permissions.", + "commands": [ + "fb tty service status", + "fb tty list", + "fb tty open" + ] + }, + { + "heading": "Observe, type and keys", + "body": "tty observe returns terminal transcript text, cursor, dimensions and process state; it is not an authentic full-screen image. tty type sends text without automatically adding Enter, though supplied newlines can affect execution. tty key accepts exact uppercase names such as ENTER and CTRL_C. Replace session arguments with an identifier returned by open/list; never guess one.", + "commands": [ + "fb tty key --help", + "fb tty type --help", + "fb tty observe --help" + ] + }, + { + "heading": "Lifetime and closing", + "body": "Default real idle expiry is disabled, but a profile can configure a timeout. Global status/list does not refresh each session's per-session activity. close destroys a session rather than merely separating a viewer. Graceful service stop attempts to close its sessions; elevated or unverifiable process state can prevent cleanup. Inspect the outcome instead of assuming that all child processes disappeared.", + "commands": [ + "fb tty close --help", + "fb tty resize --help" + ] + }, + { + "heading": "Sudo mode", + "body": "A session's sudo on/off controls normal privilege eligibility; it does not log in as root or bypass operating-system authentication. Changing the mode recreates Bash, preserving directory and dimensions but not shell variables or internal state. Authentication stays in the normal human terminal/interface. Never send a password through type or chat.", + "commands": [ + "fb tty sudo --help" + ] + }, + { + "heading": "Work console panels", + "body": "console combines an ordinary Bash pane with a read-only FBTERM activity dashboard. The dashboard is not a second interactive shell. The tmux prefix is Ctrl+B: release both keys, then press lowercase z to enlarge/restore a pane, or lowercase o to switch panes. The release accepts common zoom-key variants within its dedicated console. Closing a viewer normally leaves its tmux session alive; leaving Bash, killing its server or rebooting is different. In a desktop terminal the shared work view is horizontal with FBTERM on the left and Bash on the right. Explicit TTY mode is vertical with a short FBTERM monitor above Bash. The layout belongs to the shared tmux window and follows the last requested view mode; it is not isolated per viewer.", + "commands": [ + "fb console", + "fb status console" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "d58f36e8b96bf4687d99fe009e0061d0e76435ddb30a1601826c9eb941b64e9d" + }, + "screens": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "screens", + "title": "Authentic screen observation and input", + "sections": [ + { + "heading": "Text transcripts and pixels", + "body": "A CLI tty transcript describes a terminal and explicitly does not claim authentic pixels. MCP real-screen CUA-TTY and graphical screen tools operate through the real screen client and capture-linked interaction. Use those supported observation tools when authentic complete screen evidence is required. Never present a reconstructed terminal, animation or partial invented view as a real screenshot.", + "commands": [] + }, + { + "heading": "Automatic graphical discovery", + "body": "The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence.", + "commands": [ + "fb status", + "fb doctor" + ] + }, + { + "heading": "Frame-buffer read access", + "body": "screen-access is a narrow, explicit administrative permission for reading the real frame buffer. It does not by itself enable graphical CUA, keyboard/mouse control or arbitrary VT switching. Persistent read access can remain effective after logout or a VT switch; review the command's displayed scope and use normal local administrator authentication.", + "commands": [ + "fb screen-access status" + ] + }, + { + "heading": "Shared desktop", + "body": "A graphical desktop shares focus, keyboard and mouse. Use one controlling agent per desktop at a time and avoid overlapping work. This is an operating recommendation, not a claim of certified automatic isolation. Independent non-overlapping execution jobs may be possible; do not infer isolation or physical parallel execution from that alone.", + "commands": [] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "b186eb36570b870490eedf424b30eabb4c130ee2e2c72d658fe09b77549bbc59" + }, + "diagnostics": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "diagnostics", + "title": "Status and diagnosis", + "sections": [ + { + "heading": "Inspect before acting", + "body": "status summarizes current runtime, connection, desired state and boot policy. A query completing successfully is distinct from the components being ready. Connection verification has a freshness boundary; retrying or unknown must not be treated as connected. Focused status uses the same underlying state model.", + "commands": [ + "fb status", + "fb status connection", + "fb status runtime", + "fb status boot" + ] + }, + { + "heading": "Doctor", + "body": "doctor diagnoses prerequisites and reports findings with suggested next steps. It is not an implicit repair, and it is not a substitute for checking actual live connection state. Inspect the diagnosis and relevant private application logs before considering configuration changes.", + "commands": [ + "fb doctor" + ] + }, + { + "heading": "Logs and progress", + "body": "Normal connection presents concise progress and a final result, then returns to the prompt. Detailed diagnostics belong in protected application logs. Share only the minimum relevant redacted result; never expose credentials, private keys, browser session data or unrelated personal paths. Avoid rerunning setup, enrollment or broad lifecycle tests merely because a view is confusing.", + "commands": [ + "fb help connection", + "fb help lifecycle" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "0e55b9cda6b528926ca2a297d8464908a300122edbc1550b841ab8c1b7673029" + }, + "security": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "security", + "title": "Authorization, distribution and safe operation", + "sections": [ + { + "heading": "Authorized scope", + "body": "An agent can use the documented CLI and MCP tools within the user's authorized scope. Reading this guide does not grant permission. Verify the receiver and selected target. Normal human terminal/browser authentication remains separate; do not request credentials in chat or invent phone/Owner-TTY enrollment requirements. Normal use should operate through the installed CLI and tools, not edit or rebuild installed FrameBridge program files. If the owner explicitly requests development, use a separate source clone, make reviewable changes, and do not present a modified build as an official release. This operating guidance does not restrict GNU GPL freedoms or require private forks to submit changes upstream.", + "commands": [] + }, + { + "heading": "Official releases", + "body": "The source is distributed under GNU GPLv3-only terms and the applicable third-party notices. Verify official release checksums and published signatures/repository metadata before installing. Signature verification identifies the publisher and detects alteration; it does not prohibit lawful modified versions. Official releases are reviewed through the project's release process.", + "commands": [] + }, + { + "heading": "Side effects", + "body": "Read status before service stop, credential forgetting, node approval, startup-hook changes or administrative screen permission changes. Explain material scope and consequences and follow the user's authorization. A sensitive operation must report the actual result, not just that a query succeeded. Keep real captures complete and distinguish observations from hypotheses.", + "commands": [] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "2be17b3d7cb261f6cb5fbb94bfd027ca34147b8a4ead902f6a53b477fb4c201c" + }, + "compatibility": { + "ok": true, + "product": "FrameBridge", + "version": "0.3.1", + "guide_schema": 1, + "topic": "compatibility", + "title": "Compatibility with earlier commands", + "sections": [ + { + "heading": "Preferred spelling", + "body": "Use the short canonical interface in new documentation and automation. fb and framebridge remain equivalent. Older connect chatgpt, disconnect chatgpt and connection status spellings are compatibility routes. New status scopes replace the need to learn an extra connection noun.", + "commands": [ + "fb connect", + "fb disconnect", + "fb status connection" + ] + }, + { + "heading": "Legacy lifecycle behavior", + "body": "Older root start/stop and autostart commands are retained for compatibility where documented, but their historical scopes can differ from the canonical service controls. In particular, legacy autostart disable removes managed setup and saved encrypted material; it is not an alias for boot-policy-only disable. Read the command's help before using a legacy script.", + "commands": [ + "fb help lifecycle" + ] + }, + { + "heading": "Versioned help", + "body": "This guide describes the installed product and guide schema. Read the matching release notes for any semantic migration. The CLI help, MCP guide and static web documentation are generated from the same reviewed guide data; a cached manual from an earlier build can describe a different workflow.", + "commands": [ + "fb help compatibility" + ] + } + ], + "topics": [ + "start", + "commands", + "connection", + "lifecycle", + "nodes", + "terminals", + "screens", + "diagnostics", + "security", + "compatibility" + ], + "read_only": true, + "capabilities_verified": false, + "guide_sha256": "a6aa6d12e37563866404fa5834bdd7e758f3782a93b59d454316a8c9359b7258" + } + }, + "guide_sha256": "504c010697017b50b26de89f793944072ca214ae92d772e8e282ad44a6645634" +} diff --git a/source/docs/COMMAND-REFERENCE.md b/source/docs/COMMAND-REFERENCE.md new file mode 100644 index 0000000..191527b --- /dev/null +++ b/source/docs/COMMAND-REFERENCE.md @@ -0,0 +1,404 @@ +# FrameBridge — command guide + +Version: 0.3.1 | Guide schema: 1 + +This reference is generated from the shared CLI/MCP guide. Use the matching installed version. + +## Contents + +1. [Start with FrameBridge](#1-start) +2. [Command reference](#2-commands) +3. [Connect and disconnect ChatGPT](#3-connection) +4. [Services, connection intent and boot policy](#4-lifecycle) +5. [Central and node operation](#5-nodes) +6. [Persistent Bash terminals](#6-terminals) +7. [Authentic screen observation and input](#7-screens) +8. [Status and diagnosis](#8-diagnostics) +9. [Authorization, distribution and safe operation](#9-security) +10. [Compatibility with earlier commands](#10-compatibility) + + + +## 1. Start with FrameBridge + +### One interface for people and agents + +FrameBridge provides tools and a shared command-line interface for human operators and authorized agents. fb is the short name for framebridge; both run the same program. The tools run on your Linux computers. The ChatGPT model remains hosted. Read this guide for the installed version before operating; confirm the receiver, target and available capabilities. Use the installed CLI and tools for normal work. Edit software only when the owner explicitly requests development, using a separate source clone and reviewable changes rather than rewriting the installed program. + +~~~sh +fb help +fb status +fb --json status +~~~ + +### Install and connect + +On a supported Debian system, install the downloaded package with the package manager. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal. Advanced setup is optional for selecting central/node roles and public settings. + +~~~sh +sudo apt install ./FrameBridge-0.3.1.deb +fb connect +~~~ + +### Updating and removal + +Install an official update through the normal package manager. Normal package removal stops package-owned managed processes and retains the profile and saved encrypted connection material for deliberate reuse; it is not a credential-forgetting operation. Use the separately documented explicit forget action first if that is your intended outcome. Do not assume purge erases user-profile data. + +~~~sh +sudo dpkg --remove framebridge-original-expanded +~~~ + +### Human authentication + +The normal connection asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. The label is metadata, not proof of ChatGPT login. Browser login/consent and normal operating-system authentication remain human actions in the normal interface. Never put credentials in chat, command arguments, documentation or example files. A successful managed connection returns to the prompt and can run independently of that terminal. Reboot only when an actual system change requires it. + +### Choose the right guide + +Use commands for the reference, connection and lifecycle for state changes, nodes for central/node work, terminals for persistent Bash, and screens for authentic CUA evidence. Query success alone does not mean the connection is ready. The guide describes registered operations; use status/doctor to investigate current state. + +~~~sh +fb help commands +fb help connection +fb help lifecycle +~~~ + + + + +## 2. Command reference + +### Everyday commands + +connect establishes or resumes the managed ChatGPT connection. disconnect pauses that connection until an explicit connect, including across recovery and reboot. status reports runtime, connection, intended connection state and boot policy. doctor diagnoses prerequisites/problems. setup edits advanced stopped-profile settings. console opens the text work view. help reads this versioned reference. + +~~~sh +fb connect +fb disconnect +fb status +fb status connection +fb status runtime +fb status boot +fb status console +fb doctor +fb setup +fb console +fb help +~~~ + +### Services and boot + +For an already configured managed profile, service start/stop control the current stack symmetrically. Boot enable/disable changes future startup policy without stopping current work or forgetting the saved credential. First node provisioning is an explicit exception described in lifecycle. Without managed setup, non-node service start starts local tools only; normal first connection should use connect. Read status before and after changes. The separate credentials forget action is destructive and requires explicit confirmation. + +~~~sh +fb service start +fb service stop +fb service boot enable +fb service boot disable +~~~ + +### Console and specialized operations + +console login manages optional work-view opening after an authenticated tty1 login; it does not enable automatic login. tty service manages the terminal daemon; tty list lists sessions. nodes, certificates and screen-access retain their domain-specific subcommands. Use --help at the relevant command for its exact arguments. Avoid compatibility aliases in new automation. + +~~~sh +fb console login status +fb tty service status +fb tty list +fb nodes --help +fb certificates --help +fb screen-access --help +~~~ + +### Human and structured output + +Use explicit commands in automation rather than the interactive no-argument menu. --json selects structured output for an agent; examine operation success, readiness and state separately. Read errors and the exit status. If human interaction is required, complete it in the normal terminal/interface rather than attempting to supply a secret through the agent. CLI JSON uses an envelope with schema_version, product, version, command, ok and result. Read runtime, connection and boot truth inside result; envelope ok denotes request success. Exit 0 means successful query/operation, not necessarily a connected transport; exit 1 reports operation/diagnostic failure, exit 2 invalid arguments or required human interaction/confirmation, and exit 130 cancellation. + +~~~sh +framebridge --json status +fb --json status connection +~~~ + + + + +## 3. Connect and disconnect ChatGPT + +### Normal managed connection + +connect creates the default standalone profile when absent and establishes managed startup. It asks for initial human input in the terminal, checks progress, reports readiness or an actionable failure, and returns to the prompt. Valid saved setup can be resumed without repeating the initial fields. The MCP access key is not an OpenAI API key. Keep the browser's ChatGPT account login and consent separate from the optional local account label. + +~~~sh +fb connect +fb status connection +~~~ + +### Saved credentials and next boot + +Managed startup uses protected, host-bound encrypted connection material. The normal terminal does not have to remain open. This is a persistence feature, not a promise that an account, network or tunnel service can never fail. Connection intent and boot policy are separate: enabled startup runs the stack at boot, while an intentional disconnected state remains disconnected until connect. + +~~~sh +fb status boot +fb status +~~~ + +### Intentional disconnect + +disconnect keeps local tools and saved material, but records the intention to stay disconnected across worker recovery and reboot. Explicit connect clears that pause. Check the returned state rather than interpreting a successful query as an active transport. + +~~~sh +fb disconnect +fb status connection +~~~ + +### Replacement and temporary foreground mode + +Use connect --replace only when deliberately replacing the account label/tunnel/access configuration. The advanced --foreground mode is temporary, needs a ready local runtime and occupies its terminal until interrupted; it is not the normal managed workflow. Do not use advanced mode merely to recover a normal saved connection. + + + + +## 4. Services, connection intent and boot policy + +### Separate states + +FrameBridge distinguishes actual local runtime, desired connection, verified transport, boot enablement and saved credential presence. A healthy runtime can be intentionally disconnected; a service can be active while disabled for the next boot. Read the labelled status fields together. + +~~~sh +fb status +fb --json status +~~~ + +### Current managed stack + +For an already configured managed profile, service start and service stop use the same current-stack boundary without changing boot enablement. Stop ends current managed work and transport but keeps profile, boot policy and saved material. Startup on the next boot follows the retained policy and intended connection state. A non-node profile without managed setup starts only local tools. Stopping a terminal service or session can interrupt its running work. + +~~~sh +fb service start +fb service stop +~~~ + +### Future boot policy + +Once managed setup exists, service boot enable/disable changes unit enablement only. It does not immediately stop or start current work and does not delete saved connection material. Initial node provisioning is described separately below. Explicitly forgetting saved material is a separate destructive operation. Legacy autostart disable has its old broader destructive behavior; use the canonical service boot commands for new workflows. + +~~~sh +fb service boot enable +fb service boot disable +fb status boot +~~~ + +### First managed startup for a node + +After advanced node setup and the required enrollment/approval, a node without a managed installation can run service start in a normal terminal. This first call creates managed startup, enables future boot and starts the node now; the result explicitly reports initial_configuration and boot_policy_changed. Alternatively, first service boot enable prepares and enables the node unit without starting current work. Initial preparation can require normal owner authentication and is not silently performed by an unattended JSON request. Once configured, the ordinary start/stop and boot-policy-only contracts apply. A node does not need its own ChatGPT tunnel. + +~~~sh +fb service start +fb service boot enable +fb status boot +~~~ + +### Console opening + +Opening console is a view operation, separate from Linux service startup. Optional console login enable/disable changes the owned startup hook after an authenticated tty1 Bash login. This hook does not bypass normal login. Review the command's scope before changing it. + +~~~sh +fb console login status +~~~ + + + + +## 5. Central and node operation + +### Roles + +Standalone provides local tools. Central adds the broker for enrolled nodes. A node joins one selected central instance and uses its ChatGPT connection; it does not need another ChatGPT tunnel or phone pairing. Advanced setup selects the role. Preserve explicit authenticated node transport and real identity approval. + +~~~sh +fb setup +fb nodes --help +~~~ + +### Enrollment is not availability + +Enrollment submission, central approval, node receipt of authorization and a running node agent are separate stages. status reports what it actually knows; an unchecked agent is not evidence that the node is online. Central list/pending/approve and node enroll/poll/status/run have role-specific purposes. poll can complete saved authorization; it is not a purely observational operation. After enrollment, consult the lifecycle topic for first managed node startup and its explicit initial boot-policy effect. + +~~~sh +fb nodes list +fb nodes pending +fb nodes status +~~~ + +### Targets + +LOCAL means the actual receiving machine. Use enrolled, confirmed aliases or IDs and inspect the selected target before work. Do not guess SSH identities or use historical lab hostnames as defaults. CLI tty --node can use the authenticated node transport. The interactive console command is local; run it at the destination or use an explicitly configured SSH destination. + +### Transport trust + +certificates create manages a private central/node transport CA, not a public website certificate or ChatGPT account authentication. Private keys stay protected on the central host; distribute only the required public trust material. Off-loopback broker use requires correctly configured verified TLS. Do not recreate trust merely to work around a connection symptom. + +~~~sh +fb certificates --help +~~~ + + + + +## 6. Persistent Bash terminals + +### Service and sessions + +tty service controls the profile's terminal daemon. tty open creates a real Bash session and returns its identifier. Use that identifier for subsequent observations and input. The same Bash preserves directory, variables and work between requests while the session/service remains alive. It does not preserve running processes across an operating-system reboot. Tool jobs use a profile-specific HOME; a job's home directory can therefore differ from the human login home. Commands retain the connecting account's normal operating-system permissions. + +~~~sh +fb tty service status +fb tty list +fb tty open +~~~ + +### Observe, type and keys + +tty observe returns terminal transcript text, cursor, dimensions and process state; it is not an authentic full-screen image. tty type sends text without automatically adding Enter, though supplied newlines can affect execution. tty key accepts exact uppercase names such as ENTER and CTRL_C. Replace session arguments with an identifier returned by open/list; never guess one. + +~~~sh +fb tty key --help +fb tty type --help +fb tty observe --help +~~~ + +### Lifetime and closing + +Default real idle expiry is disabled, but a profile can configure a timeout. Global status/list does not refresh each session's per-session activity. close destroys a session rather than merely separating a viewer. Graceful service stop attempts to close its sessions; elevated or unverifiable process state can prevent cleanup. Inspect the outcome instead of assuming that all child processes disappeared. + +~~~sh +fb tty close --help +fb tty resize --help +~~~ + +### Sudo mode + +A session's sudo on/off controls normal privilege eligibility; it does not log in as root or bypass operating-system authentication. Changing the mode recreates Bash, preserving directory and dimensions but not shell variables or internal state. Authentication stays in the normal human terminal/interface. Never send a password through type or chat. + +~~~sh +fb tty sudo --help +~~~ + +### Work console panels + +console combines an ordinary Bash pane with a read-only FBTERM activity dashboard. The dashboard is not a second interactive shell. The tmux prefix is Ctrl+B: release both keys, then press lowercase z to enlarge/restore a pane, or lowercase o to switch panes. The release accepts common zoom-key variants within its dedicated console. Closing a viewer normally leaves its tmux session alive; leaving Bash, killing its server or rebooting is different. In a desktop terminal the shared work view is horizontal with FBTERM on the left and Bash on the right. Explicit TTY mode is vertical with a short FBTERM monitor above Bash. The layout belongs to the shared tmux window and follows the last requested view mode; it is not isolated per viewer. + +~~~sh +fb console +fb status console +~~~ + + + + +## 7. Authentic screen observation and input + +### Text transcripts and pixels + +A CLI tty transcript describes a terminal and explicitly does not claim authentic pixels. MCP real-screen CUA-TTY and graphical screen tools operate through the real screen client and capture-linked interaction. Use those supported observation tools when authentic complete screen evidence is required. Never present a reconstructed terminal, animation or partial invented view as a real screenshot. + +### Automatic graphical discovery + +The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence. + +~~~sh +fb status +fb doctor +~~~ + +### Frame-buffer read access + +screen-access is a narrow, explicit administrative permission for reading the real frame buffer. It does not by itself enable graphical CUA, keyboard/mouse control or arbitrary VT switching. Persistent read access can remain effective after logout or a VT switch; review the command's displayed scope and use normal local administrator authentication. + +~~~sh +fb screen-access status +~~~ + +### Shared desktop + +A graphical desktop shares focus, keyboard and mouse. Use one controlling agent per desktop at a time and avoid overlapping work. This is an operating recommendation, not a claim of certified automatic isolation. Independent non-overlapping execution jobs may be possible; do not infer isolation or physical parallel execution from that alone. + + + + +## 8. Status and diagnosis + +### Inspect before acting + +status summarizes current runtime, connection, desired state and boot policy. A query completing successfully is distinct from the components being ready. Connection verification has a freshness boundary; retrying or unknown must not be treated as connected. Focused status uses the same underlying state model. + +~~~sh +fb status +fb status connection +fb status runtime +fb status boot +~~~ + +### Doctor + +doctor diagnoses prerequisites and reports findings with suggested next steps. It is not an implicit repair, and it is not a substitute for checking actual live connection state. Inspect the diagnosis and relevant private application logs before considering configuration changes. + +~~~sh +fb doctor +~~~ + +### Logs and progress + +Normal connection presents concise progress and a final result, then returns to the prompt. Detailed diagnostics belong in protected application logs. Share only the minimum relevant redacted result; never expose credentials, private keys, browser session data or unrelated personal paths. Avoid rerunning setup, enrollment or broad lifecycle tests merely because a view is confusing. + +~~~sh +fb help connection +fb help lifecycle +~~~ + + + + +## 9. Authorization, distribution and safe operation + +### Authorized scope + +An agent can use the documented CLI and MCP tools within the user's authorized scope. Reading this guide does not grant permission. Verify the receiver and selected target. Normal human terminal/browser authentication remains separate; do not request credentials in chat or invent phone/Owner-TTY enrollment requirements. Normal use should operate through the installed CLI and tools, not edit or rebuild installed FrameBridge program files. If the owner explicitly requests development, use a separate source clone, make reviewable changes, and do not present a modified build as an official release. This operating guidance does not restrict GNU GPL freedoms or require private forks to submit changes upstream. + +### Official releases + +The source is distributed under GNU GPLv3-only terms and the applicable third-party notices. Verify official release checksums and published signatures/repository metadata before installing. Signature verification identifies the publisher and detects alteration; it does not prohibit lawful modified versions. Official releases are reviewed through the project's release process. + +### Side effects + +Read status before service stop, credential forgetting, node approval, startup-hook changes or administrative screen permission changes. Explain material scope and consequences and follow the user's authorization. A sensitive operation must report the actual result, not just that a query succeeded. Keep real captures complete and distinguish observations from hypotheses. + + + + +## 10. Compatibility with earlier commands + +### Preferred spelling + +Use the short canonical interface in new documentation and automation. fb and framebridge remain equivalent. Older connect chatgpt, disconnect chatgpt and connection status spellings are compatibility routes. New status scopes replace the need to learn an extra connection noun. + +~~~sh +fb connect +fb disconnect +fb status connection +~~~ + +### Legacy lifecycle behavior + +Older root start/stop and autostart commands are retained for compatibility where documented, but their historical scopes can differ from the canonical service controls. In particular, legacy autostart disable removes managed setup and saved encrypted material; it is not an alias for boot-policy-only disable. Read the command's help before using a legacy script. + +~~~sh +fb help lifecycle +~~~ + +### Versioned help + +This guide describes the installed product and guide schema. Read the matching release notes for any semantic migration. The CLI help, MCP guide and static web documentation are generated from the same reviewed guide data; a cached manual from an earlier build can describe a different workflow. + +~~~sh +fb help compatibility +~~~ diff --git a/source/docs/DEBIAN-TUTORIAL.md b/source/docs/DEBIAN-TUTORIAL.md new file mode 100644 index 0000000..9e5b24f --- /dev/null +++ b/source/docs/DEBIAN-TUTORIAL.md @@ -0,0 +1,404 @@ +# FrameBridge — Debian installation and first use + +Version: 0.3.1 | Guide schema: 1 + +This reference is generated from the shared CLI/MCP guide. Use the matching installed version. + +## Contents + +1. [Start with FrameBridge](#1-start) +2. [Command reference](#2-commands) +3. [Connect and disconnect ChatGPT](#3-connection) +4. [Services, connection intent and boot policy](#4-lifecycle) +5. [Central and node operation](#5-nodes) +6. [Persistent Bash terminals](#6-terminals) +7. [Authentic screen observation and input](#7-screens) +8. [Status and diagnosis](#8-diagnostics) +9. [Authorization, distribution and safe operation](#9-security) +10. [Compatibility with earlier commands](#10-compatibility) + + + +## 1. Start with FrameBridge + +### One interface for people and agents + +FrameBridge provides tools and a shared command-line interface for human operators and authorized agents. fb is the short name for framebridge; both run the same program. The tools run on your Linux computers. The ChatGPT model remains hosted. Read this guide for the installed version before operating; confirm the receiver, target and available capabilities. Use the installed CLI and tools for normal work. Edit software only when the owner explicitly requests development, using a separate source clone and reviewable changes rather than rewriting the installed program. + +~~~sh +fb help +fb status +fb --json status +~~~ + +### Install and connect + +On a supported Debian system, install the downloaded package with the package manager. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal. Advanced setup is optional for selecting central/node roles and public settings. + +~~~sh +sudo apt install ./FrameBridge-0.3.1.deb +fb connect +~~~ + +### Updating and removal + +Install an official update through the normal package manager. Normal package removal stops package-owned managed processes and retains the profile and saved encrypted connection material for deliberate reuse; it is not a credential-forgetting operation. Use the separately documented explicit forget action first if that is your intended outcome. Do not assume purge erases user-profile data. + +~~~sh +sudo dpkg --remove framebridge-original-expanded +~~~ + +### Human authentication + +The normal connection asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. The label is metadata, not proof of ChatGPT login. Browser login/consent and normal operating-system authentication remain human actions in the normal interface. Never put credentials in chat, command arguments, documentation or example files. A successful managed connection returns to the prompt and can run independently of that terminal. Reboot only when an actual system change requires it. + +### Choose the right guide + +Use commands for the reference, connection and lifecycle for state changes, nodes for central/node work, terminals for persistent Bash, and screens for authentic CUA evidence. Query success alone does not mean the connection is ready. The guide describes registered operations; use status/doctor to investigate current state. + +~~~sh +fb help commands +fb help connection +fb help lifecycle +~~~ + + + + +## 2. Command reference + +### Everyday commands + +connect establishes or resumes the managed ChatGPT connection. disconnect pauses that connection until an explicit connect, including across recovery and reboot. status reports runtime, connection, intended connection state and boot policy. doctor diagnoses prerequisites/problems. setup edits advanced stopped-profile settings. console opens the text work view. help reads this versioned reference. + +~~~sh +fb connect +fb disconnect +fb status +fb status connection +fb status runtime +fb status boot +fb status console +fb doctor +fb setup +fb console +fb help +~~~ + +### Services and boot + +For an already configured managed profile, service start/stop control the current stack symmetrically. Boot enable/disable changes future startup policy without stopping current work or forgetting the saved credential. First node provisioning is an explicit exception described in lifecycle. Without managed setup, non-node service start starts local tools only; normal first connection should use connect. Read status before and after changes. The separate credentials forget action is destructive and requires explicit confirmation. + +~~~sh +fb service start +fb service stop +fb service boot enable +fb service boot disable +~~~ + +### Console and specialized operations + +console login manages optional work-view opening after an authenticated tty1 login; it does not enable automatic login. tty service manages the terminal daemon; tty list lists sessions. nodes, certificates and screen-access retain their domain-specific subcommands. Use --help at the relevant command for its exact arguments. Avoid compatibility aliases in new automation. + +~~~sh +fb console login status +fb tty service status +fb tty list +fb nodes --help +fb certificates --help +fb screen-access --help +~~~ + +### Human and structured output + +Use explicit commands in automation rather than the interactive no-argument menu. --json selects structured output for an agent; examine operation success, readiness and state separately. Read errors and the exit status. If human interaction is required, complete it in the normal terminal/interface rather than attempting to supply a secret through the agent. CLI JSON uses an envelope with schema_version, product, version, command, ok and result. Read runtime, connection and boot truth inside result; envelope ok denotes request success. Exit 0 means successful query/operation, not necessarily a connected transport; exit 1 reports operation/diagnostic failure, exit 2 invalid arguments or required human interaction/confirmation, and exit 130 cancellation. + +~~~sh +framebridge --json status +fb --json status connection +~~~ + + + + +## 3. Connect and disconnect ChatGPT + +### Normal managed connection + +connect creates the default standalone profile when absent and establishes managed startup. It asks for initial human input in the terminal, checks progress, reports readiness or an actionable failure, and returns to the prompt. Valid saved setup can be resumed without repeating the initial fields. The MCP access key is not an OpenAI API key. Keep the browser's ChatGPT account login and consent separate from the optional local account label. + +~~~sh +fb connect +fb status connection +~~~ + +### Saved credentials and next boot + +Managed startup uses protected, host-bound encrypted connection material. The normal terminal does not have to remain open. This is a persistence feature, not a promise that an account, network or tunnel service can never fail. Connection intent and boot policy are separate: enabled startup runs the stack at boot, while an intentional disconnected state remains disconnected until connect. + +~~~sh +fb status boot +fb status +~~~ + +### Intentional disconnect + +disconnect keeps local tools and saved material, but records the intention to stay disconnected across worker recovery and reboot. Explicit connect clears that pause. Check the returned state rather than interpreting a successful query as an active transport. + +~~~sh +fb disconnect +fb status connection +~~~ + +### Replacement and temporary foreground mode + +Use connect --replace only when deliberately replacing the account label/tunnel/access configuration. The advanced --foreground mode is temporary, needs a ready local runtime and occupies its terminal until interrupted; it is not the normal managed workflow. Do not use advanced mode merely to recover a normal saved connection. + + + + +## 4. Services, connection intent and boot policy + +### Separate states + +FrameBridge distinguishes actual local runtime, desired connection, verified transport, boot enablement and saved credential presence. A healthy runtime can be intentionally disconnected; a service can be active while disabled for the next boot. Read the labelled status fields together. + +~~~sh +fb status +fb --json status +~~~ + +### Current managed stack + +For an already configured managed profile, service start and service stop use the same current-stack boundary without changing boot enablement. Stop ends current managed work and transport but keeps profile, boot policy and saved material. Startup on the next boot follows the retained policy and intended connection state. A non-node profile without managed setup starts only local tools. Stopping a terminal service or session can interrupt its running work. + +~~~sh +fb service start +fb service stop +~~~ + +### Future boot policy + +Once managed setup exists, service boot enable/disable changes unit enablement only. It does not immediately stop or start current work and does not delete saved connection material. Initial node provisioning is described separately below. Explicitly forgetting saved material is a separate destructive operation. Legacy autostart disable has its old broader destructive behavior; use the canonical service boot commands for new workflows. + +~~~sh +fb service boot enable +fb service boot disable +fb status boot +~~~ + +### First managed startup for a node + +After advanced node setup and the required enrollment/approval, a node without a managed installation can run service start in a normal terminal. This first call creates managed startup, enables future boot and starts the node now; the result explicitly reports initial_configuration and boot_policy_changed. Alternatively, first service boot enable prepares and enables the node unit without starting current work. Initial preparation can require normal owner authentication and is not silently performed by an unattended JSON request. Once configured, the ordinary start/stop and boot-policy-only contracts apply. A node does not need its own ChatGPT tunnel. + +~~~sh +fb service start +fb service boot enable +fb status boot +~~~ + +### Console opening + +Opening console is a view operation, separate from Linux service startup. Optional console login enable/disable changes the owned startup hook after an authenticated tty1 Bash login. This hook does not bypass normal login. Review the command's scope before changing it. + +~~~sh +fb console login status +~~~ + + + + +## 5. Central and node operation + +### Roles + +Standalone provides local tools. Central adds the broker for enrolled nodes. A node joins one selected central instance and uses its ChatGPT connection; it does not need another ChatGPT tunnel or phone pairing. Advanced setup selects the role. Preserve explicit authenticated node transport and real identity approval. + +~~~sh +fb setup +fb nodes --help +~~~ + +### Enrollment is not availability + +Enrollment submission, central approval, node receipt of authorization and a running node agent are separate stages. status reports what it actually knows; an unchecked agent is not evidence that the node is online. Central list/pending/approve and node enroll/poll/status/run have role-specific purposes. poll can complete saved authorization; it is not a purely observational operation. After enrollment, consult the lifecycle topic for first managed node startup and its explicit initial boot-policy effect. + +~~~sh +fb nodes list +fb nodes pending +fb nodes status +~~~ + +### Targets + +LOCAL means the actual receiving machine. Use enrolled, confirmed aliases or IDs and inspect the selected target before work. Do not guess SSH identities or use historical lab hostnames as defaults. CLI tty --node can use the authenticated node transport. The interactive console command is local; run it at the destination or use an explicitly configured SSH destination. + +### Transport trust + +certificates create manages a private central/node transport CA, not a public website certificate or ChatGPT account authentication. Private keys stay protected on the central host; distribute only the required public trust material. Off-loopback broker use requires correctly configured verified TLS. Do not recreate trust merely to work around a connection symptom. + +~~~sh +fb certificates --help +~~~ + + + + +## 6. Persistent Bash terminals + +### Service and sessions + +tty service controls the profile's terminal daemon. tty open creates a real Bash session and returns its identifier. Use that identifier for subsequent observations and input. The same Bash preserves directory, variables and work between requests while the session/service remains alive. It does not preserve running processes across an operating-system reboot. Tool jobs use a profile-specific HOME; a job's home directory can therefore differ from the human login home. Commands retain the connecting account's normal operating-system permissions. + +~~~sh +fb tty service status +fb tty list +fb tty open +~~~ + +### Observe, type and keys + +tty observe returns terminal transcript text, cursor, dimensions and process state; it is not an authentic full-screen image. tty type sends text without automatically adding Enter, though supplied newlines can affect execution. tty key accepts exact uppercase names such as ENTER and CTRL_C. Replace session arguments with an identifier returned by open/list; never guess one. + +~~~sh +fb tty key --help +fb tty type --help +fb tty observe --help +~~~ + +### Lifetime and closing + +Default real idle expiry is disabled, but a profile can configure a timeout. Global status/list does not refresh each session's per-session activity. close destroys a session rather than merely separating a viewer. Graceful service stop attempts to close its sessions; elevated or unverifiable process state can prevent cleanup. Inspect the outcome instead of assuming that all child processes disappeared. + +~~~sh +fb tty close --help +fb tty resize --help +~~~ + +### Sudo mode + +A session's sudo on/off controls normal privilege eligibility; it does not log in as root or bypass operating-system authentication. Changing the mode recreates Bash, preserving directory and dimensions but not shell variables or internal state. Authentication stays in the normal human terminal/interface. Never send a password through type or chat. + +~~~sh +fb tty sudo --help +~~~ + +### Work console panels + +console combines an ordinary Bash pane with a read-only FBTERM activity dashboard. The dashboard is not a second interactive shell. The tmux prefix is Ctrl+B: release both keys, then press lowercase z to enlarge/restore a pane, or lowercase o to switch panes. The release accepts common zoom-key variants within its dedicated console. Closing a viewer normally leaves its tmux session alive; leaving Bash, killing its server or rebooting is different. In a desktop terminal the shared work view is horizontal with FBTERM on the left and Bash on the right. Explicit TTY mode is vertical with a short FBTERM monitor above Bash. The layout belongs to the shared tmux window and follows the last requested view mode; it is not isolated per viewer. + +~~~sh +fb console +fb status console +~~~ + + + + +## 7. Authentic screen observation and input + +### Text transcripts and pixels + +A CLI tty transcript describes a terminal and explicitly does not claim authentic pixels. MCP real-screen CUA-TTY and graphical screen tools operate through the real screen client and capture-linked interaction. Use those supported observation tools when authentic complete screen evidence is required. Never present a reconstructed terminal, animation or partial invented view as a real screenshot. + +### Automatic graphical discovery + +The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence. + +~~~sh +fb status +fb doctor +~~~ + +### Frame-buffer read access + +screen-access is a narrow, explicit administrative permission for reading the real frame buffer. It does not by itself enable graphical CUA, keyboard/mouse control or arbitrary VT switching. Persistent read access can remain effective after logout or a VT switch; review the command's displayed scope and use normal local administrator authentication. + +~~~sh +fb screen-access status +~~~ + +### Shared desktop + +A graphical desktop shares focus, keyboard and mouse. Use one controlling agent per desktop at a time and avoid overlapping work. This is an operating recommendation, not a claim of certified automatic isolation. Independent non-overlapping execution jobs may be possible; do not infer isolation or physical parallel execution from that alone. + + + + +## 8. Status and diagnosis + +### Inspect before acting + +status summarizes current runtime, connection, desired state and boot policy. A query completing successfully is distinct from the components being ready. Connection verification has a freshness boundary; retrying or unknown must not be treated as connected. Focused status uses the same underlying state model. + +~~~sh +fb status +fb status connection +fb status runtime +fb status boot +~~~ + +### Doctor + +doctor diagnoses prerequisites and reports findings with suggested next steps. It is not an implicit repair, and it is not a substitute for checking actual live connection state. Inspect the diagnosis and relevant private application logs before considering configuration changes. + +~~~sh +fb doctor +~~~ + +### Logs and progress + +Normal connection presents concise progress and a final result, then returns to the prompt. Detailed diagnostics belong in protected application logs. Share only the minimum relevant redacted result; never expose credentials, private keys, browser session data or unrelated personal paths. Avoid rerunning setup, enrollment or broad lifecycle tests merely because a view is confusing. + +~~~sh +fb help connection +fb help lifecycle +~~~ + + + + +## 9. Authorization, distribution and safe operation + +### Authorized scope + +An agent can use the documented CLI and MCP tools within the user's authorized scope. Reading this guide does not grant permission. Verify the receiver and selected target. Normal human terminal/browser authentication remains separate; do not request credentials in chat or invent phone/Owner-TTY enrollment requirements. Normal use should operate through the installed CLI and tools, not edit or rebuild installed FrameBridge program files. If the owner explicitly requests development, use a separate source clone, make reviewable changes, and do not present a modified build as an official release. This operating guidance does not restrict GNU GPL freedoms or require private forks to submit changes upstream. + +### Official releases + +The source is distributed under GNU GPLv3-only terms and the applicable third-party notices. Verify official release checksums and published signatures/repository metadata before installing. Signature verification identifies the publisher and detects alteration; it does not prohibit lawful modified versions. Official releases are reviewed through the project's release process. + +### Side effects + +Read status before service stop, credential forgetting, node approval, startup-hook changes or administrative screen permission changes. Explain material scope and consequences and follow the user's authorization. A sensitive operation must report the actual result, not just that a query succeeded. Keep real captures complete and distinguish observations from hypotheses. + + + + +## 10. Compatibility with earlier commands + +### Preferred spelling + +Use the short canonical interface in new documentation and automation. fb and framebridge remain equivalent. Older connect chatgpt, disconnect chatgpt and connection status spellings are compatibility routes. New status scopes replace the need to learn an extra connection noun. + +~~~sh +fb connect +fb disconnect +fb status connection +~~~ + +### Legacy lifecycle behavior + +Older root start/stop and autostart commands are retained for compatibility where documented, but their historical scopes can differ from the canonical service controls. In particular, legacy autostart disable removes managed setup and saved encrypted material; it is not an alias for boot-policy-only disable. Read the command's help before using a legacy script. + +~~~sh +fb help lifecycle +~~~ + +### Versioned help + +This guide describes the installed product and guide schema. Read the matching release notes for any semantic migration. The CLI help, MCP guide and static web documentation are generated from the same reviewed guide data; a cached manual from an earlier build can describe a different workflow. + +~~~sh +fb help compatibility +~~~ diff --git a/source/docs/R2-ADDITIONS.md b/source/docs/R2-ADDITIONS.md new file mode 100644 index 0000000..c2cdf1d --- /dev/null +++ b/source/docs/R2-ADDITIONS.md @@ -0,0 +1,103 @@ +# FrameBridge Original Expanded — R2 additions + +Date: 2026-10-23 +Package series: 0.3.0~expanded.20261023 + +> Historical R2 record. Community Preview 4 and later change the default adaptive desktop behavior: auto uses a verified physical X11 session when available and otherwise starts text-mode FBEXEC, FBTERM and CUA-TTY, without automatically creating Xvfb. Graphical CUA requests in adaptive mode re-check a usable physical session when one becomes available without restarting persistent FBTERM. Explicit headless=yes remains the private virtual-desktop option. The R2 fallback description below records its earlier behavior; use the current [Debian tutorial](DEBIAN-TUTORIAL.md) and [connection lifecycle](../../docs/CONNECTION-STATUS.md) for Preview 4. + +This release integrates the accepted Original features in an isolated package tree. It does not install GPU drivers, kernels, a complete Cinnamon desktop, phone pairing, an Owner Device agent, or a custom privileged authentication service. Product commands and documentation are in English. + +## Open the official console + +After setup, run this as the normal logged-in user: + +```sh +fb console +``` + +The menu entry is **FrameBridge Console**. With an active verified local X11 session, it opens the graphical terminal. From an authenticated text login, it attaches in the current terminal. Both access the same profile-scoped Bash and FBTERM console. Bash is on the left on a wide screen and above the monitor on a narrow screen. Closing a graphical client detaches it; it does not intentionally destroy the persistent session. + +```sh +fb console --mode tty +fb console --status +``` + +Ctrl+B, then o changes panes. Ctrl+B, then z zooms or restores a pane. A remote interactive attachment requires an explicitly configured SSH destination; the package never guesses an SSH account or installs an SSH server. Broker-routed remote EXEC, CUA and managed terminal operations remain separate supported interfaces. + +## Optional console after text login + +Opt in explicitly, as the normal user: + +```sh +fb console --enable-login +fb console --login-status +``` + +The hook runs only after an authenticated local tty1 login, outside SSH and tmux. It never enables automatic login. The selected existing Bash login file is preserved with an exact backup. Graphical logins and other VTs retain their ordinary startup behavior. + +To restore the saved login file: + +```sh +fb console --disable-login +``` + +Restoration refuses unknown later edits rather than overwriting them. Review those edits before attempting manual recovery. + +## Managed CUA-TTY sudo capability + +Start the profile and open a managed Bash console: + +```sh +fb start +fb tty open --program bash --rows 30 --cols 100 +``` + +Replace SESSION_ID below with the returned managed console ID. Replace worker-1 with the enrolled node alias when controlling a different node: + +```sh +fb tty sudo status SESSION_ID --node LOCAL +fb tty sudo on SESSION_ID --node LOCAL +fb tty sudo off SESSION_ID --node LOCAL +fb tty sudo status SESSION_ID --node worker-1 +``` + +The default managed console has NoNewPrivs=1. Enabling the capability creates a controlled replacement Bash with NoNewPrivs=0 through the normal user's systemd manager. The controller verifies the actual process and server flags. It retains the logical console ID, working directory and geometry. Shell-local variables and functions do not survive a Bash replacement. Busy, elevated, stopped or detached jobs prevent an unsafe transition. + +This is permission to attempt ordinary sudo, not root authentication. The system's normal sudo policy and password prompt still apply. No password is embedded or handled by FrameBridge, and no NOPASSWD entry is created. A normal interactive Bash in the official console remains an ordinary user terminal; its Linux sudo behavior is not a managed CUA-TTY ID. + +## Actual screen images and input + +The physical screen interface captures complete real pixels: + +```text +framebridge_cua(action="OBSERVE", authorized=true, capture_scope="node:worker-1") +framebridge_screen_observe(node="worker-1", authorized=true) +``` + +It supports the active owned Linux framebuffer TTY and a verified active X11 session. LightDM's empty TTY field and root session leader are handled by checking the active seat, numeric VT, session, user and private user authority context. Unsupported or unreadable screens fail explicitly; terminal text is never substituted for pixels. + +The seven CUA-TTY MCP tools retain terminal-management operations, but observe returns the actual physical screen. TTY key and text actions require the capture_id and visible screen session ID returned by a real observation. A hidden managed terminal ID is not proof that it is visible. Switching VT, changing the visible pane, or reusing an action's capture invalidates the binding. Use the existing verified graphical CUA for X11 clicks and keyboard input. + +The CLI terminal observation is different: `fb tty observe SESSION_ID` returns a **terminal transcript**, including text, cursor and process data. It is explicitly marked terminal_transcript, authentic_pixels=false and full_screen=false. It is not an image of the physical display. + +## Physical and virtual graphical displays + +In auto mode the runtime prefers a verified active physical X11 session. Its graphical adapter is bound to that display/session. The binding is checked before adapter dispatch, immediately before native X11 operations, and after execution. A changed foreground session invalidates the result. If input may already have been sent, the response states input_may_have_been_sent=true and do_not_replay=true; do not repeat that action automatically. + +Historical R2 behavior: a private Xvfb display is labelled virtual-x11, with physical=false. Selecting headless yes requests such a virtual workspace explicitly. In that version, an auto fallback is also labelled virtual; it is never advertised as the active physical screen. Selecting headless no requires the physical session and refuses a virtual fallback even if the display becomes unavailable after initial validation. Community Preview 4 replaces the implicit auto fallback with text mode as described at the start of this document. + +The node:ALIAS screen interface always resolves the actual active physical screen independently; it does not silently capture the private virtual workspace. Keep this distinction when reporting screenshots or test results. + +## Network diagnosis + +```sh +fb doctor +``` + +The report distinguishes kernel link/address/default-route data from the manager that owns the interface. A working ifupdown Ethernet connection can be unmanaged in NetworkManager; a Cinnamon applet based on NetworkManager may then show disconnected. A default route alone is not proof of Internet or DNS access. The diagnostic does not change interfaces, managers, routes, DNS, firewall or SSH. + +## Release acceptance + +Source and component checks do not certify a fresh installation. The release must also pass clean Debian APT/dpkg installation, actual boot, an authenticated console, physical GUI capture and input, ordinary password-authenticated sudo, and a separate central/node VM enrollment and routing check. The manual ChatGPT connection requires the user's own account consent and secret entry in the real terminal, never in chat. + +The release report and external evidence identify which gates were actually completed. Do not describe a private Xvfb fixture or same-host profiles as an installed physical LightDM session or separate machines. diff --git a/source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md b/source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md new file mode 100644 index 0000000..3caaa58 --- /dev/null +++ b/source/docs/RC7-PRESENTATION-AND-LIFECYCLE.md @@ -0,0 +1,56 @@ +# FrameBridge Original Expanded — RC7 image presentation and lifecycle + +Date: 2026-10-24 +Candidate: 0.3.0~expanded.20261024.7 +Status: implementation candidate; release acceptance requires the external report. + +## Connect the central first + +It is valid to install and configure a central, start it, complete its manual ChatGPT connection, and install worker nodes later. The central does not require a worker to publish its local tools. A standalone profile can also connect directly. A node profile joins an approved central and does not publish its own ChatGPT MCP endpoint. + +For a fleet, choose central at the initial setup. Start it and run `fb connect chatgpt`. Enter the tunnel information and runtime key at the hidden local prompts, then select the app and complete ChatGPT consent. The connection client remains in the foreground while the tunnel is active; lack of a new prompt is expected. Keep that terminal open. Browser login is separate from the optional account label. + +When adding another machine later, install the same verified package there and choose node. Use the central's verified HTTPS broker URL and public CA certificate. The node submits enrollment, the central operator compares the identity and approves the matching request, and the node polls before starting. Section 8 of DEBIAN-TUTORIAL.md provides the exact commands. The central resolves the current approved node list for each request, so this workflow does not require reinstalling the central or reconnecting ChatGPT. + +Changing a running central profile to another role is a different operation: stop that profile before editing setup. Installing every worker before connecting ChatGPT is an optional test order, not a product requirement. + +## Display and reopen the same actual screen image + +The existing actual-screen tools retain their contracts: + +- `framebridge_screen_observe` captures a new verified active physical screen. +- `framebridge_screen_key` and `framebridge_screen_text` require a valid fresh input binding and return the resulting capture. +- `framebridge_screen_show(node, capture_id, authorized)` retrieves an existing saved capture and displays its original PNG. + +Use the node and capture_id returned by observation when calling show. It reads the saved capture on that node, verifies the byte count, PNG dimensions and SHA-256, and returns the original ImageContent plus a structured result for the viewer. It does not run a new capture, open an execution console, send input or consume input authorization. An unavailable or corrupt saved capture produces an error rather than a substitute image. + +The offline MCP Apps viewer is registered as `ui://framebridge/screen-v1.html` with MIME type `text/html;profile=mcp-app`. It receives the original PNG through tool-result metadata and validates integrity before showing it. It provides fit, native-size, fullscreen and exact-PNG download controls, and identifies the node, dimensions and capture hash. A reopened image is labelled saved/historical with control_available=false: viewing an old image does not make it a safe basis for input. + +This UI requires a compatible ChatGPT client and refreshed RC7 app tools. Existing clients still receive the original PNG ImageContent. Tool/resource registration, direct SDK tests and capture backend success do not certify that ChatGPT rendered the image; an actual-account visual check is a separate release gate. The legacy graphical CUA JPEG preview remains capped at width 960 while reporting the true source geometry; that preview is separate from the native actual-screen PNG. + +## Explicit read-only text framebuffer access + +The package includes an inactive udev rule and a root helper. Installation does not enable a grant. The administrator can explicitly select an existing non-root user with: + +```sh +fb screen-access status +sudo fb screen-access enable --user "$(id -un)" +fb screen-access status +``` + +The command must run from the intended normal user's login. Only the selected user receives a named read-only ACL for the actual `/dev/fb0` graphics framebuffer (major 29, minor 0). Other video devices, device ownership and group ownership are outside its scope. The helper preserves the current ACL mask; if read access would require widening that mask, it refuses the change. No broad video-group membership or passwordless sudo rule is installed. + +The enabled policy under `/etc/framebridge-expanded/screen-access.json` contains the public user identity. Protected recovery data under `/var/lib/framebridge-expanded/screen-access` records the original access. The narrow udev rule reapplies the selected grant when fb0 is recreated. Persistent access lets any process of that Unix user read framebuffer pixels across logout and reboot, including another user's screen, so the administrator must assess that tradeoff. + +Revoke on the same machine with: + +```sh +sudo fb screen-access disable +fb screen-access status +``` + +Disable prevents regranting and restores only the access this helper owns. A conflicting external ACL change is reported instead of being overwritten; protected recovery data remains available for review. Package removal uses this same revocation and stops on unresolved conflict. Upgrade preserves the explicit policy. Normal per-console sudo enable/disable remains separate from framebuffer read access and still requires normal sudo authentication. + +## Acceptance and scope + +RC6 remains the frozen baseline. RC7 implementation tests, reproducible builds, installed-file verification, fresh installation, reboot, recovery, removal and the central-before-node workflow must be recorded for the exact RC7 artifact. Password-backed sudo must be checked interactively; root execution through a VM guest agent does not prove that the user entered a valid password. Source fixtures are not screenshots of a real screen. See VALIDATION.md and the external evidence for completed checks and remaining gates. diff --git a/source/docs/REQUIREMENTS.md b/source/docs/REQUIREMENTS.md new file mode 100644 index 0000000..fe9d1b8 --- /dev/null +++ b/source/docs/REQUIREMENTS.md @@ -0,0 +1,119 @@ +# FrameBridge Original Expanded — requirements and acceptance criteria + +PROJECT=FRAMEBRIDGE_ORIGINAL_EXPANDED +STATUS=PRODUCT_REQUIREMENTS_AND_ACCEPTANCE_CRITERIA +DATE=2026-10-26 +CANDIDATE=COMMUNITY_PREVIEW7 + +This document defines the requested product. An acceptance criterion is a requirement, not evidence that it already passes. Release claims must cite the final artifact and the corresponding test report. + +## Product and supported installation + +| ID | Requirement | Acceptance evidence required | +|---|---|---| +| PKG-01 | Deliver one amd64 Debian package for Debian 13 with Python 3.13. | Install the actual release package on a fresh Debian 13 amd64 environment and run its installed entry points. | +| PKG-02 | Include the original execution, CUA, fleet and terminal components needed by the supported roles. | A manifest maps packaged files to captured original sources or explicitly reviewed adaptations. | +| PKG-03 | Declare dependencies used by the actual execution path. | A clean installation resolves dependencies; doctor checks commands, Python imports and graphics requirements. | +| PKG-04 | Provide all help, prompts, instructions and release documentation in English. | Review the installed CLI, generated help and shipped documents. | +| PKG-05 | Preserve the existing production installations. | Tests use separate state, transport endpoints and terminal sockets; record the scope and cleanup performed. | + +## First use and roles + +| ID | Requirement | Acceptance evidence required | +|---|---|---| +| CLI-01 | Provide the framebridge command and the fb alias with an understandable first-use entry. | Exercise both installed entry points without arguments and inspect their help. | +| CLI-02 | Provide automatic standalone defaults for a normal first connection and separate guided setup for central/node roles and advanced settings. | Test first connection without a saved profile or prior setup/start, plus each advanced role's configuration and runtime behavior. | +| CLI-03 | Keep the normal connection wizard concise and expose the configured role, node name, ports and state location through status. | Status agrees with the selected profile and running processes; the first connection keeps the required three-field order. | +| CLI-04 | Keep setup, start, stop, status and doctor behavior explicit. | Test lifecycle, repeated invocation, occupied ports and process cleanup without touching production. | +| CLI-05 | Identify operations requiring administrator privileges and keep authentication in the normal terminal. | Fresh Debian instructions match package installation and managed-service setup; the connection wizard does not request administrator credentials in chat or conceal privileged setup. | + +Standalone runs the local tools independently. Central manages the fleet-facing entry point and node admission. Node joins an explicitly selected central instance. These descriptions do not authorize a role merely because a host was discovered on the network. + +## ChatGPT connection + +| ID | Requirement | Acceptance evidence required | +|---|---|---| +| GPT-01 | The user signs in to their own ChatGPT account in the browser and completes the required consent manually. | Record an authorized browser connection and real tool calls; never treat a local label as authenticated identity. | +| GPT-02 | Accept only a raw tunnel ID in the normal hidden tunnel field; retain complete supported commands in the advanced hidden foreground workflow. | Test normal ID validation and safe rejection of accidentally pasted commands. Advanced parser tests cover quoting and invalid commands; a command is parsed as data and never run by a shell. | +| GPT-03 | Read the MCP server access key at a hidden terminal prompt. The normal connection workflow stores only a host-bound encrypted credential for managed startup; advanced foreground mode remains nonpersistent. | Verify no plaintext key in profile configuration, persistent files, shell history, logs, command arguments or release artifacts. Verify encrypted storage and decryption through protected process-memory pipes. | +| GPT-04 | Do not save or repeat the complete pasted tunnel command. | Inspect successful and failing CLI output and resulting state. Only necessary public tunnel metadata may be retained. | +| GPT-05 | Run the normal connection through managed services with concise progress, explicit readiness, status and disconnection; retain foreground diagnostics through an explicit option. | Verify prompt return, survival of terminal closure, concurrent invocation, startup failure, interruption, process identity and cleanup. Pending readiness must not be called connected. | +| GPT-06 | Report local readiness separately from a real ChatGPT end-to-end success. | Status and documentation identify which account/browser and tool-call checks were actually performed for this release. | +| GPT-07 | Configure Linux boot startup and reconnection as part of the normal standalone/central connection, without prior manual start, a separate startup opt-in or repeated boot-time credential prompts. | Verify immediate managed startup and terminal closure; reboot a Debian 13 VM and verify text-mode tools and tunnel before graphical login. Setup must not force a routine reboot. | +| GPT-08 | Use systemd encrypted credentials for the normal saved MCP access key, with normal sudo authentication when setup requires it. | Verify systemd 256 or later, protected encrypted storage, no plaintext persistent profile copy, and documented root/full-image recovery limits. | +| GPT-09 | Keep disconnect, stop and disable behavior distinct and reversible. | Disconnect retains the local runtime and next-boot enablement; stop ends the current instance; disable stops it and removes only matching owned configuration, reversing changed settings with guards. | +| GPT-10 | Provide explicit transactional replacement and normal package removal without mandatory manual stop/disable steps. | Verify connect chatgpt --replace preserves prior managed settings on failure. Verify actual package removal stops/disables only recognized package-managed user units, preserves profiles/encrypted credentials/linger, and does not run removal cleanup on upgrade. Optional guarded autostart disable remains separate. | +| GPT-11 | Ask for the visible account/email label first, hidden tunnel input second and hidden MCP server access key third on a first unmanaged connection. | Inspect prompt order and hidden input; distinguish local account labeling from browser login and the MCP access key from an OpenAI API key. | +| GPT-12 | Check or resume a valid managed profile using saved encrypted settings without reentering the fields; reserve changes for explicit --replace. | Test repeated connect on running and stopped managed profiles, and safe rejection of corrupt or unowned configuration without a false connected result. | + +An email entered during setup is a local label. It is not proof of the browser account, tunnel ownership or node identity. No browser cookies, saved sessions or account passwords belong in this package or its reports. + +## Original execution and CUA behavior + +| ID | Requirement | Acceptance evidence required | +|---|---|---| +| EXEC-01 | Preserve original FBEXEC behavior and its job, exit-code, stdout, stderr and hash evidence. | Run real commands through the packaged route and verify their backend evidence. | +| EXEC-02 | Use the original execution path without importing the experimental job-approval queue. | Source and runtime review identify the handler actually reached by each public tool. | +| CUA-01 | Preserve original X11 CUA where the supported desktop or private display is available. | Run actual observation and interaction tests against the intended display. | +| TTY-01 | Provide access to the original GERAMEG CUA-TTY backend, including the supported real-terminal mode. | Verify text input, Enter, actual shell output and terminal cleanup through the original RPC. | +| TTY-02 | Distinguish an isolated demo from control of a real persistent Bash terminal. | Tests and help explicitly identify the active mode; demo acknowledgements alone do not certify a real shell command. | +| TTY-03 | Separate terminal state for independent instances. | Test distinct sockets and sessions; never copy live socket paths, session IDs or process IDs from another machine. | +| AUTH-01 | Do not require a phone or Owner Device for the requested local execution or CUA workflow. | Exercise the workflow without phone enrollment or an Owner Device gate. | + +The original GERAMEG documentation first certified a demo and later certified a real terminal. The real-terminal A/B certificate is the relevant reference for TTY-01; the earlier demo result is not interchangeable with it. + +## Fleet admission and authenticated transport + +| ID | Requirement | Acceptance evidence required | +|---|---|---| +| FLEET-01 | Central and node roles use authenticated transport. | Successful authenticated node traffic and rejected unauthenticated traffic are demonstrated. | +| FLEET-02 | A node's identity and intended central instance are reviewed and approved explicitly before admission. | Record the exact identity presented, the pending request and the resulting approval; no silent acceptance on discovery. | +| FLEET-03 | Keep route selection explicit and reject an unknown target without redirecting to another host. | Test local and enrolled-node routes plus an unknown target. | +| FLEET-04 | Include the original broker/outbound-agent workflow required by the selected roles. | Demonstrate actual node registration, online status, remote FBEXEC and CUA routing through the central instance. | + +Transport authentication and approved node identity remain required. Removing a phone requirement does not remove either condition. The normal connection workflow saves the MCP access key only as a host-bound encrypted systemd credential; advanced foreground diagnostics keep it in process memory without persistence. Plaintext credentials must not be persisted in profile files or distributed evidence. Generated node identity and transport-authentication material have a separate purpose and may require protected persistent profile state; such state must never be included in a redistributable package or evidence bundle. + +## Graphics and headless operation + +The installer and control console use a text interface in a Linux TTY or terminal emulator. Graphical CUA is a backend selected automatically from a usable supported desktop; its availability does not change the installer or console into a graphical interface. + +The historical R1 FBTERM engine opens a GNOME terminal and uses wmctrl; evidence for that path must identify it. The current persistent-terminal workflow must also work in text mode without a graphical desktop. Graphical X11 CUA needs a verified display, the correct Xauthority and its command-line tools. + +Community Preview 4 and later product decision: headless mode is auto by default. The runtime uses a verified usable physical X11 session when available; otherwise it starts in text mode, reporting display_context.kind=text. Text mode provides FBEXEC, FBTERM and CUA-TTY without a monitor, graphical login or forced virtual desktop. Graphical CUA requests in adaptive mode re-check for a usable physical session when one appears, without restarting persistent FBTERM sessions; per-operation screen checks reject a session change during input. Explicit headless=yes requests the separate private X11 desktop, reported as virtual-x11 and physical=false. Explicit headless=no requires the verified physical session; enabling automatic startup backs up and converts that physical-only setting to auto for boot operation. + +The retained xvfb, openbox, dbus-x11, xauth, x11-xkb-utils and fonts-dejavu-core dependencies support explicit private graphical operation alongside the original execution and CUA dependencies. Graphical capability checks must distinguish optional graphics from text-mode readiness. Prove adaptive text startup, later verified physical CUA, explicit private mode and safe refusal against the exact final runtime in Debian QA. These are acceptance requirements, not passing test results. See the current [connection lifecycle](../../docs/CONNECTION-STATUS.md) and [Debian tutorial](DEBIAN-TUTORIAL.md). + +Node operation must use the configured local user's identity and runtime directory rather than requiring a hard-coded UID such as 1000. + +## Required release evidence + +| Area | Current status | Evidence needed before a passing claim | +|---|---|---| +| Final package installation on fresh Debian | See VALIDATION.md | Exact package hash, dependencies, installed entry points and environment details. | +| English CLI and tutorial | See VALIDATION.md | Review against actual installed help and behavior. | +| Three role configurations | See VALIDATION.md | Per-role configuration and lifecycle results. | +| Fleet identity approval and transport | See VALIDATION.md | Real central/node admission and routed tools. | +| Tunnel parser and memory-only key | See VALIDATION.md | Parser, failure, cleanup and secret-retention checks. | +| Original FBEXEC, X11 CUA and real CUA-TTY | See VALIDATION.md | Backend receipts and observed results for the final package. | +| Manual ChatGPT connection | See VALIDATION.md | Browser consent and real tool calls against this package. | +| Source fidelity and reproducible package | See VALIDATION.md | Reviewed source mapping and independently compared builds. | + +The successful Original R1/R2 tests are historical reference evidence. They must not be reported as fresh Expanded package tests. + +## Historical source documents + +- FrameBridge C8 terminal-first onboarding R7, 2026-10-11: https://drive.google.com/file/d/1LuYF1vGw8FKaD4Izcpw4NnR0HRuws78s/view +- Terminal-first onboarding guide R1: https://drive.google.com/file/d/11MEm5eOVke4aSeIBc-024e1zNLke_y_s/view +- GERAMEG CUA-TTY demo-only live test, 2026-10-19: https://drive.google.com/file/d/1DixrNUYUhr6pM2wiXeILKmU2dmbE53_D/view +- GERAMEG real-terminal FBEXEC/CUA-TTY A/B certificate R3, 2026-10-19: https://drive.google.com/file/d/1VUzhCLJpV8DNQSOlfWYus-nzKAsloGlm/view + +These are historical implementation references. They do not override the current requested product or authorize copying runtime identities or secrets. + + +## RC7 saved-image and lifecycle acceptance + +The exact saved PNG must be retrievable by node/capture_id without taking a replacement capture or performing input. Presentation must preserve the original bytes, resolution and SHA-256, label saved images as historical, and fail explicitly for unknown or corrupt data. An offline MCP Apps viewer must be tested in the intended ChatGPT account; ImageContent serialization alone is insufficient proof of rendering. + +A central must be usable and manually connectable before any worker is installed. A later separately installed node must join the running central through explicit authenticated approval and become available without reconnecting ChatGPT. + +Persistent tty framebuffer access must be an explicit root opt-in for a named non-root user, limited to reading the real fb0 device. No installation-time broad grant is acceptable. Revocation and package removal must restore only the helper's own change, preserve unrelated changes, and report conflicts. Fresh installation, reboot/recovery/removal and password-backed sudo results must be recorded for the exact RC7 artifact; inherited RC6 evidence is historical context. diff --git a/source/docs/VALIDATION.md b/source/docs/VALIDATION.md new file mode 100644 index 0000000..3b22296 --- /dev/null +++ b/source/docs/VALIDATION.md @@ -0,0 +1,40 @@ +# FrameBridge Original Expanded — RC7 validation scope + +Date: 2026-10-24 +Candidate: 0.3.0~expanded.20261024.7 +Status: release acceptance is determined by the external report and evidence. + +## Preserved baseline evidence + +RC6 (0.3.0~expanded.20261023.6) is a separate frozen artifact. Its final source suite passed 314 tests and two builds produced identical package bytes. RC6 was installed on the isolated Debian CENTRAL and NODE VM filesystems. The real test account and manual tunnel verified status, local and remote EXEC, and authentic physical X11/framebuffer captures without new capture-created FBTERM jobs. + +Those results do not certify RC7. RC6 still had an open ChatGPT image presentation/reuse gate, and the lab framebuffer read ACL was temporary. Three additional direct diagnostic checks passed; two terminal-key negative cases were skipped because the required auxiliary Bash window was not in front. Official CLOUD MT publication was not verified by that diagnostic phase. + +## RC7 implementation scope + +RC7 adds read-only retrieval of saved original PNGs, an offline MCP Apps viewer, and explicit opt-in persistent read-only fb0 access. Existing capture and input contracts remain separate from historical viewing. Central-first/manual-ChatGPT/later-node setup is documented and must be exercised in an installed environment. + +The source suite covers setup, manual connection, TLS and routing, terminal identity and NoNewPrivs transitions, display context, actual-screen transport and input bindings. RC7 must also test saved-image byte integrity, authorization, unknown/corrupt capture handling, the SDK UI contract, and safe framebuffer access/revocation including external ACL conflicts. + +Image fixtures are unit data, not evidence that a physical screen was captured. Terminal transcripts are text data, not screenshots. Unit registration of a UI resource does not prove actual ChatGPT rendering. + +## Required installed-VM gates + +1. Verify the exact RC7 .deb hash and install with APT in clean Debian 13 on separate CENTRAL and NODE VM filesystems. +2. Verify declared dependencies, installed commands, package identity and installed critical-file hashes. +3. Check start, stop, reboot, recovery and removal with preserved evidence. Removal must not silently leave an enabled framebuffer grant. +4. Verify the shared official console from normal graphical and text logins. +5. Verify managed-console NoNewPrivs on/off from actual processes and ordinary password-authenticated sudo. Root guest-agent execution and NOPASSWD are not substitutes for a password test. +6. In a physical LightDM X11 session, capture complete display pixels and test visible graphical input. Verify stale observations fail after relevant window/geometry/session changes. +7. Verify physical tty capture permission persists after reboot only when the explicit opt-in is enabled, and that revocation restores the access this helper owns. +8. Connect the CENTRAL manually to the intended ChatGPT account, then enroll and approve a newly installed NODE through verified TLS without reconnecting ChatGPT. Verify routed tools on the added node. +9. Complete actual-account observation, original-PNG display and reuse through the RC7 tools. Match the shown capture/node/hash to the backend evidence; do not replace an unavailable capture or infer rendering from a tool response alone. +10. Record user-entered manual-tunnel/account consent outcomes without passwords, runtime keys, cookies or other secrets. Verify the selected account separately from its optional email label. + +A single host using two profile directories is not a two-machine installation test. Xvfb is explicitly virtual and cannot satisfy the physical display gate. Fresh isolated clones must not overwrite the existing working VM disks or interrupt their manual tunnel. + +## Evidence and limitations + +The external release report records exact package/source hashes, checks actually run, return codes, machine identities and unresolved gates. Package hashes remain outside packaged documentation to avoid circular builds. A prepared script, simulated APT run, active display-manager daemon or stale Xorg log is not a completed acceptance result. + +Creating the isolated RC7 source tree does not deploy to production or change host network, kernel or GPU configuration. The package does not grant passwordless root access, auto-enable a framebuffer policy or connect silently to ChatGPT. Read the external acceptance status before calling this candidate a finished release. diff --git a/source/docs/framebridge.1 b/source/docs/framebridge.1 new file mode 100644 index 0000000..d5a26a2 --- /dev/null +++ b/source/docs/framebridge.1 @@ -0,0 +1,285 @@ +.TH FRAMEBRIDGE 1 "2026-10-26" "FrameBridge 0.3.1" "User Commands" +.SH NAME +framebridge, fb \- use local Linux tools and enrolled nodes with ChatGPT +.SH SYNOPSIS +.B framebridge +[\-\-json] [\-\-state\-dir DIRECTORY] COMMAND [ARGUMENTS] +.SH DESCRIPTION +FrameBridge provides one command-line interface for human operators and authorized agents. +The names fb and framebridge execute the same program. +The tools run on the selected Linux computers; the ChatGPT model remains hosted. +.SH START WITH FRAMEBRIDGE +.SS One interface for people and agents +FrameBridge provides tools and a shared command\-line interface for human operators and authorized agents. fb is the short name for framebridge; both run the same program. The tools run on your Linux computers. The ChatGPT model remains hosted. Read this guide for the installed version before operating; confirm the receiver, target and available capabilities. Use the installed CLI and tools for normal work. Edit software only when the owner explicitly requests development, using a separate source clone and reviewable changes rather than rewriting the installed program. +.PP +.nf +fb help +fb status +fb \-\-json status +.fi +.SS Install and connect +On a supported Debian system, install the downloaded package with the package manager. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal. Advanced setup is optional for selecting central/node roles and public settings. +.PP +.nf +sudo apt install ./FrameBridge\-0.3.1.deb +fb connect +.fi +.SS Updating and removal +Install an official update through the normal package manager. Normal package removal stops package\-owned managed processes and retains the profile and saved encrypted connection material for deliberate reuse; it is not a credential\-forgetting operation. Use the separately documented explicit forget action first if that is your intended outcome. Do not assume purge erases user\-profile data. +.PP +.nf +sudo dpkg \-\-remove framebridge\-original\-expanded +.fi +.SS Human authentication +The normal connection asks for an optional account label, a hidden tunnel ID and a hidden MCP server access key. The label is metadata, not proof of ChatGPT login. Browser login/consent and normal operating\-system authentication remain human actions in the normal interface. Never put credentials in chat, command arguments, documentation or example files. A successful managed connection returns to the prompt and can run independently of that terminal. Reboot only when an actual system change requires it. +.SS Choose the right guide +Use commands for the reference, connection and lifecycle for state changes, nodes for central/node work, terminals for persistent Bash, and screens for authentic CUA evidence. Query success alone does not mean the connection is ready. The guide describes registered operations; use status/doctor to investigate current state. +.PP +.nf +fb help commands +fb help connection +fb help lifecycle +.fi +.SH COMMAND REFERENCE +.SS Everyday commands +connect establishes or resumes the managed ChatGPT connection. disconnect pauses that connection until an explicit connect, including across recovery and reboot. status reports runtime, connection, intended connection state and boot policy. doctor diagnoses prerequisites/problems. setup edits advanced stopped\-profile settings. console opens the text work view. help reads this versioned reference. +.PP +.nf +fb connect +fb disconnect +fb status +fb status connection +fb status runtime +fb status boot +fb status console +fb doctor +fb setup +fb console +fb help +.fi +.SS Services and boot +For an already configured managed profile, service start/stop control the current stack symmetrically. Boot enable/disable changes future startup policy without stopping current work or forgetting the saved credential. First node provisioning is an explicit exception described in lifecycle. Without managed setup, non\-node service start starts local tools only; normal first connection should use connect. Read status before and after changes. The separate credentials forget action is destructive and requires explicit confirmation. +.PP +.nf +fb service start +fb service stop +fb service boot enable +fb service boot disable +.fi +.SS Console and specialized operations +console login manages optional work\-view opening after an authenticated tty1 login; it does not enable automatic login. tty service manages the terminal daemon; tty list lists sessions. nodes, certificates and screen\-access retain their domain\-specific subcommands. Use \-\-help at the relevant command for its exact arguments. Avoid compatibility aliases in new automation. +.PP +.nf +fb console login status +fb tty service status +fb tty list +fb nodes \-\-help +fb certificates \-\-help +fb screen\-access \-\-help +.fi +.SS Human and structured output +Use explicit commands in automation rather than the interactive no\-argument menu. \-\-json selects structured output for an agent; examine operation success, readiness and state separately. Read errors and the exit status. If human interaction is required, complete it in the normal terminal/interface rather than attempting to supply a secret through the agent. CLI JSON uses an envelope with schema_version, product, version, command, ok and result. Read runtime, connection and boot truth inside result; envelope ok denotes request success. Exit 0 means successful query/operation, not necessarily a connected transport; exit 1 reports operation/diagnostic failure, exit 2 invalid arguments or required human interaction/confirmation, and exit 130 cancellation. +.PP +.nf +framebridge \-\-json status +fb \-\-json status connection +.fi +.SH CONNECT AND DISCONNECT CHATGPT +.SS Normal managed connection +connect creates the default standalone profile when absent and establishes managed startup. It asks for initial human input in the terminal, checks progress, reports readiness or an actionable failure, and returns to the prompt. Valid saved setup can be resumed without repeating the initial fields. The MCP access key is not an OpenAI API key. Keep the browser's ChatGPT account login and consent separate from the optional local account label. +.PP +.nf +fb connect +fb status connection +.fi +.SS Saved credentials and next boot +Managed startup uses protected, host\-bound encrypted connection material. The normal terminal does not have to remain open. This is a persistence feature, not a promise that an account, network or tunnel service can never fail. Connection intent and boot policy are separate: enabled startup runs the stack at boot, while an intentional disconnected state remains disconnected until connect. +.PP +.nf +fb status boot +fb status +.fi +.SS Intentional disconnect +disconnect keeps local tools and saved material, but records the intention to stay disconnected across worker recovery and reboot. Explicit connect clears that pause. Check the returned state rather than interpreting a successful query as an active transport. +.PP +.nf +fb disconnect +fb status connection +.fi +.SS Replacement and temporary foreground mode +Use connect \-\-replace only when deliberately replacing the account label/tunnel/access configuration. The advanced \-\-foreground mode is temporary, needs a ready local runtime and occupies its terminal until interrupted; it is not the normal managed workflow. Do not use advanced mode merely to recover a normal saved connection. +.SH SERVICES, CONNECTION INTENT AND BOOT POLICY +.SS Separate states +FrameBridge distinguishes actual local runtime, desired connection, verified transport, boot enablement and saved credential presence. A healthy runtime can be intentionally disconnected; a service can be active while disabled for the next boot. Read the labelled status fields together. +.PP +.nf +fb status +fb \-\-json status +.fi +.SS Current managed stack +For an already configured managed profile, service start and service stop use the same current\-stack boundary without changing boot enablement. Stop ends current managed work and transport but keeps profile, boot policy and saved material. Startup on the next boot follows the retained policy and intended connection state. A non\-node profile without managed setup starts only local tools. Stopping a terminal service or session can interrupt its running work. +.PP +.nf +fb service start +fb service stop +.fi +.SS Future boot policy +Once managed setup exists, service boot enable/disable changes unit enablement only. It does not immediately stop or start current work and does not delete saved connection material. Initial node provisioning is described separately below. Explicitly forgetting saved material is a separate destructive operation. Legacy autostart disable has its old broader destructive behavior; use the canonical service boot commands for new workflows. +.PP +.nf +fb service boot enable +fb service boot disable +fb status boot +.fi +.SS First managed startup for a node +After advanced node setup and the required enrollment/approval, a node without a managed installation can run service start in a normal terminal. This first call creates managed startup, enables future boot and starts the node now; the result explicitly reports initial_configuration and boot_policy_changed. Alternatively, first service boot enable prepares and enables the node unit without starting current work. Initial preparation can require normal owner authentication and is not silently performed by an unattended JSON request. Once configured, the ordinary start/stop and boot\-policy\-only contracts apply. A node does not need its own ChatGPT tunnel. +.PP +.nf +fb service start +fb service boot enable +fb status boot +.fi +.SS Console opening +Opening console is a view operation, separate from Linux service startup. Optional console login enable/disable changes the owned startup hook after an authenticated tty1 Bash login. This hook does not bypass normal login. Review the command's scope before changing it. +.PP +.nf +fb console login status +.fi +.SH CENTRAL AND NODE OPERATION +.SS Roles +Standalone provides local tools. Central adds the broker for enrolled nodes. A node joins one selected central instance and uses its ChatGPT connection; it does not need another ChatGPT tunnel or phone pairing. Advanced setup selects the role. Preserve explicit authenticated node transport and real identity approval. +.PP +.nf +fb setup +fb nodes \-\-help +.fi +.SS Enrollment is not availability +Enrollment submission, central approval, node receipt of authorization and a running node agent are separate stages. status reports what it actually knows; an unchecked agent is not evidence that the node is online. Central list/pending/approve and node enroll/poll/status/run have role\-specific purposes. poll can complete saved authorization; it is not a purely observational operation. After enrollment, consult the lifecycle topic for first managed node startup and its explicit initial boot\-policy effect. +.PP +.nf +fb nodes list +fb nodes pending +fb nodes status +.fi +.SS Targets +LOCAL means the actual receiving machine. Use enrolled, confirmed aliases or IDs and inspect the selected target before work. Do not guess SSH identities or use historical lab hostnames as defaults. CLI tty \-\-node can use the authenticated node transport. The interactive console command is local; run it at the destination or use an explicitly configured SSH destination. +.SS Transport trust +certificates create manages a private central/node transport CA, not a public website certificate or ChatGPT account authentication. Private keys stay protected on the central host; distribute only the required public trust material. Off\-loopback broker use requires correctly configured verified TLS. Do not recreate trust merely to work around a connection symptom. +.PP +.nf +fb certificates \-\-help +.fi +.SH PERSISTENT BASH TERMINALS +.SS Service and sessions +tty service controls the profile's terminal daemon. tty open creates a real Bash session and returns its identifier. Use that identifier for subsequent observations and input. The same Bash preserves directory, variables and work between requests while the session/service remains alive. It does not preserve running processes across an operating\-system reboot. Tool jobs use a profile\-specific HOME; a job's home directory can therefore differ from the human login home. Commands retain the connecting account's normal operating\-system permissions. +.PP +.nf +fb tty service status +fb tty list +fb tty open +.fi +.SS Observe, type and keys +tty observe returns terminal transcript text, cursor, dimensions and process state; it is not an authentic full\-screen image. tty type sends text without automatically adding Enter, though supplied newlines can affect execution. tty key accepts exact uppercase names such as ENTER and CTRL_C. Replace session arguments with an identifier returned by open/list; never guess one. +.PP +.nf +fb tty key \-\-help +fb tty type \-\-help +fb tty observe \-\-help +.fi +.SS Lifetime and closing +Default real idle expiry is disabled, but a profile can configure a timeout. Global status/list does not refresh each session's per\-session activity. close destroys a session rather than merely separating a viewer. Graceful service stop attempts to close its sessions; elevated or unverifiable process state can prevent cleanup. Inspect the outcome instead of assuming that all child processes disappeared. +.PP +.nf +fb tty close \-\-help +fb tty resize \-\-help +.fi +.SS Sudo mode +A session's sudo on/off controls normal privilege eligibility; it does not log in as root or bypass operating\-system authentication. Changing the mode recreates Bash, preserving directory and dimensions but not shell variables or internal state. Authentication stays in the normal human terminal/interface. Never send a password through type or chat. +.PP +.nf +fb tty sudo \-\-help +.fi +.SS Work console panels +console combines an ordinary Bash pane with a read\-only FBTERM activity dashboard. The dashboard is not a second interactive shell. The tmux prefix is Ctrl+B: release both keys, then press lowercase z to enlarge/restore a pane, or lowercase o to switch panes. The release accepts common zoom\-key variants within its dedicated console. Closing a viewer normally leaves its tmux session alive; leaving Bash, killing its server or rebooting is different. In a desktop terminal the shared work view is horizontal with FBTERM on the left and Bash on the right. Explicit TTY mode is vertical with a short FBTERM monitor above Bash. The layout belongs to the shared tmux window and follows the last requested view mode; it is not isolated per viewer. +.PP +.nf +fb console +fb status console +.fi +.SH AUTHENTIC SCREEN OBSERVATION AND INPUT +.SS Text transcripts and pixels +A CLI tty transcript describes a terminal and explicitly does not claim authentic pixels. MCP real\-screen CUA\-TTY and graphical screen tools operate through the real screen client and capture\-linked interaction. Use those supported observation tools when authentic complete screen evidence is required. Never present a reconstructed terminal, animation or partial invented view as a real screenshot. +.SS Automatic graphical discovery +The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA\-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual\-desktop settings remain advanced options and are labelled separately from physical\-screen evidence. +.PP +.nf +fb status +fb doctor +.fi +.SS Frame\-buffer read access +screen\-access is a narrow, explicit administrative permission for reading the real frame buffer. It does not by itself enable graphical CUA, keyboard/mouse control or arbitrary VT switching. Persistent read access can remain effective after logout or a VT switch; review the command's displayed scope and use normal local administrator authentication. +.PP +.nf +fb screen\-access status +.fi +.SS Shared desktop +A graphical desktop shares focus, keyboard and mouse. Use one controlling agent per desktop at a time and avoid overlapping work. This is an operating recommendation, not a claim of certified automatic isolation. Independent non\-overlapping execution jobs may be possible; do not infer isolation or physical parallel execution from that alone. +.SH STATUS AND DIAGNOSIS +.SS Inspect before acting +status summarizes current runtime, connection, desired state and boot policy. A query completing successfully is distinct from the components being ready. Connection verification has a freshness boundary; retrying or unknown must not be treated as connected. Focused status uses the same underlying state model. +.PP +.nf +fb status +fb status connection +fb status runtime +fb status boot +.fi +.SS Doctor +doctor diagnoses prerequisites and reports findings with suggested next steps. It is not an implicit repair, and it is not a substitute for checking actual live connection state. Inspect the diagnosis and relevant private application logs before considering configuration changes. +.PP +.nf +fb doctor +.fi +.SS Logs and progress +Normal connection presents concise progress and a final result, then returns to the prompt. Detailed diagnostics belong in protected application logs. Share only the minimum relevant redacted result; never expose credentials, private keys, browser session data or unrelated personal paths. Avoid rerunning setup, enrollment or broad lifecycle tests merely because a view is confusing. +.PP +.nf +fb help connection +fb help lifecycle +.fi +.SH AUTHORIZATION, DISTRIBUTION AND SAFE OPERATION +.SS Authorized scope +An agent can use the documented CLI and MCP tools within the user's authorized scope. Reading this guide does not grant permission. Verify the receiver and selected target. Normal human terminal/browser authentication remains separate; do not request credentials in chat or invent phone/Owner\-TTY enrollment requirements. Normal use should operate through the installed CLI and tools, not edit or rebuild installed FrameBridge program files. If the owner explicitly requests development, use a separate source clone, make reviewable changes, and do not present a modified build as an official release. This operating guidance does not restrict GNU GPL freedoms or require private forks to submit changes upstream. +.SS Official releases +The source is distributed under GNU GPLv3\-only terms and the applicable third\-party notices. Verify official release checksums and published signatures/repository metadata before installing. Signature verification identifies the publisher and detects alteration; it does not prohibit lawful modified versions. Official releases are reviewed through the project's release process. +.SS Side effects +Read status before service stop, credential forgetting, node approval, startup\-hook changes or administrative screen permission changes. Explain material scope and consequences and follow the user's authorization. A sensitive operation must report the actual result, not just that a query succeeded. Keep real captures complete and distinguish observations from hypotheses. +.SH COMPATIBILITY WITH EARLIER COMMANDS +.SS Preferred spelling +Use the short canonical interface in new documentation and automation. fb and framebridge remain equivalent. Older connect chatgpt, disconnect chatgpt and connection status spellings are compatibility routes. New status scopes replace the need to learn an extra connection noun. +.PP +.nf +fb connect +fb disconnect +fb status connection +.fi +.SS Legacy lifecycle behavior +Older root start/stop and autostart commands are retained for compatibility where documented, but their historical scopes can differ from the canonical service controls. In particular, legacy autostart disable removes managed setup and saved encrypted material; it is not an alias for boot\-policy\-only disable. Read the command's help before using a legacy script. +.PP +.nf +fb help lifecycle +.fi +.SS Versioned help +This guide describes the installed product and guide schema. Read the matching release notes for any semantic migration. The CLI help, MCP guide and static web documentation are generated from the same reviewed guide data; a cached manual from an earlier build can describe a different workflow. +.PP +.nf +fb help compatibility +.fi +.SH AUTHORS +Programming: ChatGPT. +Concept and project coordination: Gerardo Betancourt. +.SH LICENSE +GNU GPL version 3 only, with the applicable third-party license notices. +See the installed copyright and source license files. +.SH SEE ALSO +.BR systemctl (1), .BR dpkg (1), .BR apt (8) diff --git a/source/evidence/BASELINE.json b/source/evidence/BASELINE.json new file mode 100644 index 0000000..62eb692 --- /dev/null +++ b/source/evidence/BASELINE.json @@ -0,0 +1,993 @@ +{ + "base_workspace": ".", + "base_deb_sha256": "9517e405725a4c514841822eee3344392c13f21a171823edd9f45f2e955b96b7", + "source_file_count": 982, + "source_hashes": { + "original-asus/SOURCE-MANIFEST.json": "4735bc196b324423ba9579185cbe094164c5c2c58eff1975364e47fa6a57f322", + "original-geranas/MANIFEST.json": "a39c8364800da04ca48eb0ce4ce7781346d22f85110f110462340ff0229d3d6a", + "original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f", + "original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef", + "original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68", + "original-geranas/FrameBridge-MCP-Private/bin/tunnel-client": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6", + "original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88", + "original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666", + "original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696", + "original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443", + "original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901", + "original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909", + "original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371", + "original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141", + "original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79", + "original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36", + "original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34", + "original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361", + "original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3", + "original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8", + "original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c", + "original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3", + "original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89", + "original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec", + "original-asus/tool-adapter/bin/framebridge_tool_client.py": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "original-asus/tool-adapter/bin/framebridge_tool_adapter.py": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "original-asus/fbterm/bin/fbterm_engine.py": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "original-asus/fbterm/bin/fbx": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "original-asus/fbterm/bin/fbterm-run": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "original-asus/fbterm/bin/fbterm-prompt-ready": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "original-asus/fbterm/bin/fbterm-shell": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "original-asus/framebridge-native/host/open-progress-console.sh": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "original-asus/framebridge-native/host/framebridge_host.py": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "original-asus/framebridge-native/host/open-terminal.sh": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "original-asus/framebridge-native/host/open-interactive-terminal.sh": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "original-asus/framebridge-native/host/framebridge_voice_status.py": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "original-asus/framebridge-native/host/framebridge_voice_command.py": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "original-asus/framebridge-native/host/framebridge_cua_linux.py": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1" + }, + "created": "2026-10-21", + "base_deb_sha256_role": "Historical pre-RC7 input package; not this community candidate", + "community_version": "0.3.1~community.20261026.7", + "community_derivative": true +} diff --git a/source/expanded/framebridge_expanded/__init__.py b/source/expanded/framebridge_expanded/__init__.py new file mode 100644 index 0000000..260c070 --- /dev/null +++ b/source/expanded/framebridge_expanded/__init__.py @@ -0,0 +1 @@ +__version__ = "0.3.1" diff --git a/source/expanded/framebridge_expanded/_tty_backend.py b/source/expanded/framebridge_expanded/_tty_backend.py new file mode 100644 index 0000000..dcad4c1 --- /dev/null +++ b/source/expanded/framebridge_expanded/_tty_backend.py @@ -0,0 +1,218 @@ +"""Real terminals for the existing FrameBridge CUA-TTY Manager. +Transport identity and same-UID socket policy are unchanged. No Owner prompts. +Only this module's tmux sessions are operated on; no existing tty is hijacked. +""" +from __future__ import annotations +from dataclasses import dataclass +import hashlib +import os +from pathlib import Path +import re +import subprocess +import time +import uuid +import pwd +import shlex +from .tty_sudo_control import SudoConsoleController, SudoControlError +from framebridge_expanded._captured_cua_tty.core import TerminalDenied + +TMUX_SOCKET = None # Runtime socket is supplied by the portable manager. +KEY_NAMES = {'UP':'Up','DOWN':'Down','LEFT':'Left','RIGHT':'Right', + 'ENTER':'Enter','RETURN':'Enter','TAB':'Tab','SHIFT_TAB':'BTab','ESC':'Escape', + 'BACKSPACE':'BSpace','DELETE':'DC','INSERT':'IC','HOME':'Home','END':'End', + 'PAGE_UP':'PPage','PAGE_DOWN':'NPage','SPACE':'Space'} +KEY_NAMES.update({'CTRL_'+c:'C-'+c.lower() for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'}) +KEY_NAMES.update({'F'+str(i):'F'+str(i) for i in range(1,13)}) + +@dataclass +class RealSession: + session_id: str + name: str + pane: str + born: float + program: str = 'bash' + sequence: int = 0 + +class RealTerminalMixin: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._real = {} + self._sudo = SudoConsoleController( + Path(self.tmux_socket).parent / 'sudo-consoles', home=self.home, + timeout=self.limits.command_timeout_s, + min_rows=self.limits.min_rows, max_rows=self.limits.max_rows, + min_cols=self.limits.min_cols, max_cols=self.limits.max_cols) + + def _tmux(self, *args, data=None, optional=False, session=None): + env={k:v for k,v in os.environ.items() if k not in ('TMUX','TMUX_PANE')} + env.update({'HOME':str(self.home),'USER':pwd.getpwuid(os.getuid()).pw_name,'LOGNAME':pwd.getpwuid(os.getuid()).pw_name, + 'LANG':'C.UTF-8','TERM':'xterm-256color','HISTFILE':'/dev/null'}) + tmux_socket = getattr(session, '_sudo_record', {}).get('tmux_socket', str(self.tmux_socket)) + try: + p=subprocess.run([self.tmux_bin,'-S',tmux_socket,'-f','/dev/null',*args], + input=data,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,timeout=self.limits.command_timeout_s) + except subprocess.TimeoutExpired: + raise TerminalDenied('TMUX_COMMAND_TIMEOUT') from None + except OSError: + raise TerminalDenied('TMUX_NOT_AVAILABLE') from None + if p.returncode and not optional: + raise TerminalDenied('TMUX_COMMAND_FAILED') + return p + + def _info(self, session): + keys=['pane_width','pane_height','cursor_x','cursor_y','pane_dead', + 'pane_dead_status','pane_pid'] + fmt='|'.join('#{'+key+'}' for key in keys) + p=self._tmux('display-message','-p','-t',session.pane,fmt,session=session) + parts=p.stdout.decode('utf-8','replace').rstrip('\n').split('|') + if len(parts)!=len(keys):raise TerminalDenied('TMUX_METADATA_INVALID') + info=dict(zip(keys,parts)) + for key in ['pane_current_path','pane_current_command','pane_tty']: + p=self._tmux('display-message','-p','-t',session.pane,'#{'+key+'}',session=session) + info[key]=p.stdout.decode('utf-8','replace').rstrip('\n') + return info + + def open(self, program='demo-readonly', rows=24, cols=80): + if program!='bash': + with self._lock: + self.reap() + if len(self._sessions)+len(self._real)>=self.max_sessions: + raise TerminalDenied('SESSION_LIMIT') + return super().open(program,rows,cols) + self._size(rows, cols) + with self._lock: + self.reap() + if len(self._sessions)+len(self._real)>=self.max_sessions: + raise TerminalDenied('SESSION_LIMIT') + sid=uuid.uuid4().hex + record=None + try: + record=self._sudo.launch(sid,cwd=str(self.home),rows=rows,cols=cols,mode='off') + s=RealSession(sid,record['name'],record['pane'],time.monotonic()) + s._sudo_record=record + self._real[sid]=s + self._tmux('set-option','-p','-t',s.pane,'remain-on-exit','on',session=s) + self._tmux('set-option','-t',s.name,'status','off',session=s) + self._tmux('set-option','-t',s.name,'history-limit','10000',session=s) + info=self._info(s) + return {'session_id':sid,'program':'bash','state':'OPEN','backend':'tmux', + 'real_terminal':True,'tty':info['pane_tty'],'pid':int(info['pane_pid']), + 'cwd':info['pane_current_path'],'persistent_between_chat_calls':True, + 'sudo_mode':'off','no_new_privs':record['no_new_privs'], + 'attach_command':'tmux -S '+shlex.quote(record['tmux_socket'])+' attach-session -t '+shlex.quote(s.name)} + except BaseException: + self._real.pop(sid,None) + if record is not None: + self._sudo.close(record) + raise + + def sudo(self, sid, mode): + if mode not in ('on','off','status'): + raise TerminalDenied('TTY_SUDO_MODE_INVALID') + with self._lock: + if sid not in self._real: + raise TerminalDenied('TTY_SUDO_SESSION_NOT_FOUND') + s=self._real[sid] + if mode=='status': + return self._sudo.status(s) + value=self._sudo.set_mode(s,mode) + if value.get('recreated'): + s.name=value['name'];s.pane=value['pane'] + s._sudo_record=value + s.sequence+=1 + return value + + def status(self): + with self._lock: + value=super().status() + sessions=[] + for sid,s in list(self._real.items()): + try: + info=self._info(s) + except TerminalDenied as exc: + # A failed observation is not proof that the console closed. + sessions.append({'session_id':sid,'program':'bash','running':None, + 'tty':None,'cwd':None,'command':None,'sudo_mode':'unknown', + 'query_error':str(exc)}) + continue + try: + sudo_state=self._sudo.status(s) + except SudoControlError as exc: + sudo_state={'mode':'exited' if info['pane_dead']=='1' else 'unknown','error':exc.code} + sessions.append({'session_id':sid,'program':'bash', + 'running':info['pane_dead']!='1','tty':info['pane_tty'], + 'cwd':info['pane_current_path'],'command':info['pane_current_command'], + 'sudo_mode':sudo_state['mode']}) + value.update({'active_sessions':value['active_sessions']+len(sessions), + 'real_sessions':sessions,'real_terminal_enabled':True, + 'real_programs':['bash'],'input_policy':'same-uid-authorized', + 'owner_device_connected':False,'owner_signature_required':False, + 'input_enabled':'same-uid-authorized','real_idle_timeout_s':None}) + return value + + def observe(self, sid): + with self._lock: + if sid not in self._real:return super().observe(sid) + s=self._real[sid];info=self._info(s) + text=self._tmux('capture-pane','-p','-t',s.pane,session=s).stdout.decode('utf-8','replace') + lines=text.splitlines() + # The original RPC response has a finite frame size, not an approval gate. + limited=False + import json + while len(json.dumps(lines,ensure_ascii=True).encode())>self.limits.max_screen_bytes: + lines=lines[:-1];limited=True + return {'session_id':sid,'program':'bash','real_terminal':True,'backend':'tmux', + 'running':info['pane_dead']!='1','exit_code':int(info['pane_dead_status']) if info['pane_dead_status'] else None, + 'cwd':info['pane_current_path'],'current_command':info['pane_current_command'], + 'tty':info['pane_tty'],'pid':int(info['pane_pid']),'sequence':s.sequence, + 'screen':{'rows':int(info['pane_height']),'cols':int(info['pane_width']), + 'cursor':{'row':int(info['cursor_y']),'col':int(info['cursor_x'])},'lines':lines}, + 'screen_truncated':limited,'screen_sha256':hashlib.sha256(text.encode()).hexdigest()} + + def text(self, sid, text, approved_context=None): + with self._lock: + if sid not in self._real:return super().text(sid,text,approved_context=approved_context) + if not isinstance(text,str) or not text or '\x00' in text or len(text.encode())>self.limits.max_text_bytes: + raise TerminalDenied('TEXT_INVALID_OR_TOO_LARGE') + s=self._real[sid];payload=text.encode('utf-8') + self._authorize(s,'text.type',payload,approved_context) + if self._info(s)['pane_dead']=='1':raise TerminalDenied('SESSION_EXITED') + name='fb-input-'+sid + self._tmux('load-buffer','-b',name,'-',data=payload,session=s) + try:self._tmux('paste-buffer','-d','-r','-b',name,'-t',s.pane,session=s) + finally:self._tmux('delete-buffer','-b',name,optional=True,session=s) + s.sequence+=1 + return {'session_id':sid,'state':'TYPED','submitted_chars':len(text),'sequence':s.sequence} + + def key(self, sid, key, approved_context=None): + with self._lock: + if sid not in self._real:return super().key(sid,key,approved_context=approved_context) + if not isinstance(key,str) or key not in KEY_NAMES:raise TerminalDenied('KEY_NOT_ALLOWLISTED') + s=self._real[sid] + self._authorize(s,'key.send',key.encode(),approved_context) + if self._info(s)['pane_dead']=='1':raise TerminalDenied('SESSION_EXITED') + self._tmux('send-keys','-t',s.pane,KEY_NAMES[key],session=s);s.sequence+=1 + return {'session_id':sid,'state':'KEY_SENT','submitted':key,'sequence':s.sequence} + + def resize(self, sid, rows, cols): + with self._lock: + if sid not in self._real:return super().resize(sid,rows,cols) + self._size(rows, cols) + self._tmux('resize-window','-t',self._real[sid].pane,'-x',str(cols),'-y',str(rows),session=self._real[sid]) + self._real[sid].sequence+=1 + return {'session_id':sid,'rows':rows,'cols':cols} + + def abort(self, sid): + with self._lock: + if sid not in self._real:return super().abort(sid) + s=self._real[sid] + self._sudo.close(s) + self._real.pop(sid,None) + return {'session_id':sid,'state':'CLOSED','cleanup_verified':True} + + def shutdown(self): + with self._lock: + for sid in list(self._real): + try:self.abort(sid) + except (TerminalDenied,SudoControlError):pass + super().shutdown() diff --git a/source/expanded/framebridge_expanded/adapter.py b/source/expanded/framebridge_expanded/adapter.py new file mode 100644 index 0000000..993dbf2 --- /dev/null +++ b/source/expanded/framebridge_expanded/adapter.py @@ -0,0 +1,117 @@ +"""Original same-UID adapter with context checks at CUA dispatch.""" +from __future__ import annotations +import importlib.util +import base64 +import hashlib +import os +from pathlib import Path +import sys +import threading +from . import display_context as display +from .native_host import META + +def _blocked(error, may_have_sent=False): + value = {"ok": False, "error": str(error), "mode": "CUA", + "input_may_have_been_sent": bool(may_have_sent), + "do_not_replay": bool(may_have_sent), "result_discarded": True} + if not may_have_sent: + value["performed"] = False + return value + +def _successful_observation(native): + if not isinstance(native, dict) or native.get("executed") is not True: + return False + output = native.get("output") + if not isinstance(output, str) or len(output) > 2_000_000: + return False + fields = dict(line.split("=", 1) for line in output.splitlines() if "=" in line) + if (fields.get("RESULTADO_FINAL") != "CUA_VISUAL_OBSERVE_PASS" or fields.get("VISUAL_CAPTURE_GATE") != "PASS" or fields.get("MIME") != "image/jpeg"): + return False + try: + data = base64.b64decode(fields.get("BASE64", ""), validate=True) + return (data.startswith(b"\xff\xd8") and data.endswith(b"\xff\xd9") + and len(data) == int(fields.get("BYTES", "0")) + and hashlib.sha256(data).hexdigest() == fields.get("SHA256")) + except (ValueError, TypeError): + return False + + +def install_adapter_guard(adapter, *, env=None, checker=None): + env = os.environ if env is None else env + checker = display.check if checker is None else checker + original_handle = adapter.handle_cua_request + original_roundtrip = adapter.native_cua_roundtrip + local = threading.local() + request_lock = threading.RLock() + + def native_roundtrip(payload, timeout_s=60): + response = original_roundtrip(payload, timeout_s=timeout_s) + local.native = response + return response + + def guarded_handle(request, job_id): + local.native = None + try: + display.prepare(env) + context = checker(env) + except display.DisplayContextError as exc: + return _blocked(exc) + action = str(request.get("cua", {}).get("action", "OBSERVE")).upper().strip() + if isinstance(context, dict) and context.get("needs_observation") and action != "OBSERVE": + result = _blocked("CUA_DISPLAY_CHANGED_OBSERVE_REQUIRED") + result["display_context"] = display.describe(env) + return result + try: + result = original_handle(request, job_id) + except Exception as exc: + # The native child may have acted before a timeout or invalid reply. + # Never invite automatic replay of an uncertain input action. + is_input = str(request.get("cua", {}).get("action", "")).upper() != "OBSERVE" + code = str(exc) + if not code or len(code) > 100 or any(c not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:" for c in code): + code = "CUA_NATIVE_EXECUTION_UNCERTAIN" + return _blocked(code, is_input) + native = local.native or {} + result = dict(result) + for key in META: + if key in native: + result[key] = native[key] + if "display_context" not in result: + result["display_context"] = display.describe(env) + try: + checker(env) + except display.DisplayContextError as exc: + is_input = str(request.get("cua", {}).get("action", "")).upper() != "OBSERVE" + return _blocked(exc, native.get("input_may_have_been_sent", is_input)) + if action == "OBSERVE" and result.get("ok") is True and _successful_observation(native): + try: + display.observed(env) + except display.DisplayContextError as exc: + return _blocked(exc) + result["display_context"] = display.describe(env) + return result + + def handle(request, job_id): + with request_lock: + return guarded_handle(request, job_id) + + adapter.native_cua_roundtrip = native_roundtrip + adapter.handle_cua_request = handle + +def main(argv=None): + if list(sys.argv[1:] if argv is None else argv) != ["serve"]: + raise SystemExit("Usage: python3 -m framebridge_expanded.adapter serve") + prefix = Path(os.environ.get("FRAMEBRIDGE_EXPANDED_PREFIX", Path(__file__).resolve().parents[2])) + path = prefix / "original/asus/tool-adapter/bin/framebridge_tool_adapter.py" + if not path.is_file(): + path = prefix / "sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py" + spec = importlib.util.spec_from_file_location("_framebridge_original_adapter", path) + original = importlib.util.module_from_spec(spec) + spec.loader.exec_module(original) + install_adapter_guard(original) + from .real_screen.screen_transport import install_adapter_route + install_adapter_route(original) + original.serve() + +if __name__ == "__main__": + main() diff --git a/source/expanded/framebridge_expanded/autostart.py b/source/expanded/framebridge_expanded/autostart.py new file mode 100644 index 0000000..a62bdba --- /dev/null +++ b/source/expanded/framebridge_expanded/autostart.py @@ -0,0 +1,884 @@ +"""Explicit user-service startup with host-bound encrypted systemd credentials.""" +from __future__ import annotations +import argparse +import fcntl +import getpass +import hashlib +import io +import json +import os +from pathlib import Path +import pwd +import re +import resource +import secrets +import signal +import stat +import subprocess +import sys +import time +from .config import load_config, save_config, private_dir, state_path, default_config, validate + +CREDENTIAL = "chatgpt-runtime-key" +_STOP = False + + +def _paths(state): + state = state_path(state) + directory = private_dir(private_dir(state) / "autostart") + name = "framebridge-autostart-" + hashlib.sha256(str(state).encode()).hexdigest()[:16] + ".service" + return state, directory, Path.home() / ".config/systemd/user" / name + + +def _read(path, maximum=131072): + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1 or info.st_mode & 0o077: + raise RuntimeError("Unsafe autostart file ownership or permissions.") + data = os.read(fd, maximum + 1) + if len(data) > maximum: + raise RuntimeError("Autostart file exceeds the size limit.") + return data + finally: + os.close(fd) + + +def _write(path, data, replace=False): + path = Path(path) + if path.exists() or path.is_symlink(): + if not replace: + raise RuntimeError("Refusing to replace an existing autostart file.") + _read(path) + temporary = path.with_name("." + path.name + "." + secrets.token_hex(8)) + fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + try: + with os.fdopen(fd, "wb") as stream: + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + if replace: + os.replace(temporary, path) + else: + os.link(temporary, path, follow_symlinks=False) + finally: + temporary.unlink(missing_ok=True) + + +def _json(path, value, replace=False): + _write(path, (json.dumps(value, sort_keys=True, indent=2) + "\n").encode(), replace) + + +def _command(args, *, data=None, timeout=35, check=True): + result = subprocess.run(args, input=data, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) + if check and result.returncode: + raise RuntimeError("Autostart command failed: " + Path(args[0]).name + ". No command output was exported.") + return result + + +def _systemctl(*args, check=True): + return _command(["systemctl", "--user", *args], check=check) + + +def _quote(value, *, command=False): + value = str(value) + if any(ord(ch) < 32 or ord(ch) == 127 for ch in value): + raise ValueError("Control characters are not allowed in service paths.") + if command: + value = value.replace("$", "$$") + return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("%", "%%") + '"' + + +def unit_text(state, encrypted, *, node=False): + module_path = Path(__file__).resolve().parents[1] + executable = [sys.executable, "-B", "-m", "framebridge_expanded.autostart", "_run", "--state", str(state)] + lines = ["# FrameBridge managed autostart v1", "[Unit]", "Description=FrameBridge profile and ChatGPT connection", "StartLimitIntervalSec=0", "ConditionPathExists=" + _quote(module_path / "framebridge_expanded/autostart.py"), "", "[Service]", "Type=simple", "UMask=0077", "LimitCORE=0", "Environment=" + _quote("PYTHONPATH=" + str(module_path)), "Environment=LANG=C.UTF-8", "Environment=LC_ALL=C.UTF-8"] + lines += ["ExecStart=" + " ".join(_quote(x, command=True) for x in executable), "Restart=on-failure", "RestartSec=15", "TimeoutStopSec=25", "KillMode=control-group", "StandardInput=null", "StandardOutput=null", "StandardError=null", "", "[Install]", "WantedBy=default.target", ""] + return "\n".join(lines) + + +def _linger(user): + directory = Path("/var/lib/systemd/linger") + try: + info = directory.stat() + except FileNotFoundError: + return False + if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or directory.is_symlink(): + raise RuntimeError("The systemd linger directory is unavailable or unsafe.") + try: + marker = (directory / user).lstat() + except FileNotFoundError: + return False + if not stat.S_ISREG(marker.st_mode) or marker.st_uid != 0: + raise RuntimeError("The systemd linger record is unsafe.") + return True + + +def _enabled_units(): + result = _systemctl("list-unit-files", "--state=enabled", "--no-legend", "--no-pager") + return sorted(line.split()[0] for line in result.stdout.decode(errors="replace").splitlines() if line.split()) + + +def _check_unit_directory(path): + for directory in [Path.home() / ".config", Path.home() / ".config/systemd", path.parent]: + if directory.is_symlink(): + raise RuntimeError("User service directories must not be symbolic links.") + directory.mkdir(mode=0o700, exist_ok=True) + info = directory.stat() + if info.st_uid != os.getuid() or info.st_mode & 0o022: + raise RuntimeError("User service directories must be owned by this user and not writable by others.") + + +def _manifest(directory): + path = directory / "installation.json" + try: + value = json.loads(_read(path)) + except FileNotFoundError: + return None + if not isinstance(value, dict) or value.get("schema") != 1: + raise RuntimeError("Unsupported autostart installation record.") + return value + + +def status(state): + state, directory, unit = _paths(state) + manifest = _manifest(directory) + properties = _systemctl("show", unit.name, "--property=ActiveState,SubState,UnitFileState", check=False) + values = dict(line.split("=", 1) for line in properties.stdout.decode(errors="replace").splitlines() if "=" in line) + from . import runtime, connection_background + result = {"configured": bool(manifest), "unit": unit.name, "service_active": values.get("ActiveState") == "active", "service_state": values.get("ActiveState", "unavailable"), "enabled": values.get("UnitFileState") == "enabled", "linger": _linger(pwd.getpwuid(os.getuid()).pw_name), "encrypted_credential_present": False, "runtime": _safe_runtime_status(runtime, state), "connection": connection_background.status(state)} + if manifest and not manifest.get("node"): + try: + cipher = _read(directory / "credential.cred") + result["encrypted_credential_present"] = hashlib.sha256(cipher).hexdigest() == manifest.get("credential_sha256") + except FileNotFoundError: + pass + try: + result["worker"] = json.loads(_read(directory / "worker-status.json")) + except FileNotFoundError: + result["worker"] = {"state": "not_started"} + result["boot_desired_enabled"] = boot_requested(state) + result["desired_connection"] = "not_configured" if not manifest else ("not_applicable" if manifest.get("node") else ("connected" if connection_requested(state) else "disconnected")) + return result + + +def _safe_runtime_status(runtime, state): + try: + return runtime.status(state) + except Exception: + return {"running": False, "ready": False, "status_unavailable": True} + + +def _configuration_action(state, action): + _, directory, _ = _paths(state) + fd = os.open(directory / "configuration.lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1 or info.st_mode & 0o077: + raise RuntimeError("Unsafe autostart configuration lock.") + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise RuntimeError("Another autostart configuration is already in progress.") from None + return action(state) + finally: + os.close(fd) + + +def enable(state): + try: + return _configuration_action(state, _enable) + except (EOFError, KeyboardInterrupt): + print(flush=True) + return {"configured": False, "state": "cancelled"} + + +def disable(state): + return _configuration_action(state, _disable) + + +def _committed_connection_status(state): + try: + return status(state) + except (EOFError, KeyboardInterrupt): + # Enabling the service already committed. Cancelling only this status + # read must not suggest that the installed connection was cancelled. + return {"configured": True, "enabled": True, "interrupted_after_commit": True} + + +def configure_connection(state, public_settings, runtime_key, *, output=None): + """Install this connection once, using only the supplied hidden in-memory key.""" + if not isinstance(runtime_key, str) or not runtime_key or not runtime_key.isascii() or len(runtime_key) > 16384 or any(ord(ch) < 33 or ord(ch) > 126 for ch in runtime_key): + raise ValueError("Enter one nonempty MCP server access key.") + allowed = {"account_label", "tunnel_id", "control_plane_organization_id", "control_plane_url_path"} + if not isinstance(public_settings, dict) or set(public_settings) - allowed: + raise ValueError("Unsupported public connection settings.") + from .tunnel import TunnelSpec + settings = dict(public_settings) + spec = TunnelSpec(settings.get("tunnel_id", ""), settings.get("control_plane_organization_id", ""), settings.get("control_plane_url_path", "")) + label = settings.get("account_label", "") + if not isinstance(label, str) or len(label) > 320 or any(ord(ch) < 32 or ord(ch) == 127 for ch in label): + raise ValueError("Enter a valid account label.") + settings.update(spec.public_settings()) + settings["account_label"] = label + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + try: + return _configuration_action(state, lambda selected: _configure_connection(selected, settings, runtime_key)) + finally: + runtime_key = "" + + +def _connection_public_digest(config): + fields = {name: config.get(name, "") for name in ("account_label", "tunnel_id", "control_plane_organization_id", "control_plane_url_path", "role", "node_id")} + fields["mcp_port"] = config["ports"]["mcp"] + return hashlib.sha256(json.dumps(fields, sort_keys=True, separators=(",", ":")).encode()).hexdigest() + + +def resume_connection(state): + """Resume the verified saved connection without asking for its key again.""" + return _configuration_action(state, _resume_connection) + + +def _resume_connection(state): + if os.geteuid() == 0: + raise RuntimeError("Run connect as the profile owner, not as root.") + state, directory, unit = _paths(state) + config = load_config(state) + record = _manifest(directory) + error = "The saved connection cannot be verified. Run fb connect chatgpt --replace." + if not record or record.get("node") or config["role"] == "node" or record.get("unit") != unit.name: + raise RuntimeError(error) + try: + valid_unit = hashlib.sha256(_read(unit)).hexdigest() == record.get("unit_sha256") + valid_cipher = hashlib.sha256(_read(directory / "credential.cred")).hexdigest() == record.get("credential_sha256") + bound = record.get("connection_settings_sha256") + if bound: + valid_settings = _connection_public_digest(config) == bound + else: + # Older releases only bound the complete profile when converting its desktop setting. + old_bound = record.get("changed_config_sha256") + valid_settings = bool(old_bound) and hashlib.sha256(_read(state / "config.json")).hexdigest() == old_bound + if record.get("headless_changed"): + valid_backup = hashlib.sha256(_read(directory / "config-before.json")).hexdigest() == record.get("config_before_sha256") + else: + valid_backup = True + except (OSError, ValueError): + raise RuntimeError(error) from None + if not (valid_unit and valid_cipher and valid_settings and valid_backup): + raise RuntimeError(error) + from .tunnel import TunnelSpec + TunnelSpec(config["tunnel_id"], config.get("control_plane_organization_id", ""), config.get("control_plane_url_path", "")) + user = pwd.getpwuid(os.getuid()).pw_name + if not _linger(user): + _command(["sudo", "-v"], timeout=180) + _command(["sudo", "-n", "--", "loginctl", "enable-linger", user]) + was_manually_stopped = manual_stop_requested(state) + _connection_action(state, lambda _: _set_connection_requested(state, True)) + _clear_manual_stop(state) + current = status(state) + local = current.get("runtime", {}) + # A live managed worker owns local tools independently of its transport. + # Change its requested connection state instead of restarting those tools. + if current.get("service_active") and local.get("running") and local.get("ready") and current.get("worker", {}).get("state") not in {"failed", "stopped"} and not was_manually_stopped: + if not current.get("enabled") and boot_requested(state) is not False: + _systemctl("enable", unit.name) + return _committed_connection_status(state) + _systemctl("stop", unit.name) + from . import connection_background + stopped = connection_background.disconnect(state) + if stopped.get("ok") is False or stopped.get("alive") or connection_background.status(state).get("alive"): + raise RuntimeError("The previous connection is still stopping. Try connecting again shortly.") + _systemctl("daemon-reload") + if boot_requested(state) is False: + _systemctl("start", unit.name) + else: + _systemctl("enable", "--now", unit.name) + return _committed_connection_status(state) + + +def _optional_read(path): + try: + return _read(path) + except FileNotFoundError: + return None + + +def _configure_connection(state, settings, runtime_key): + if os.geteuid() == 0: + raise RuntimeError("Run connect as the profile owner, not as root.") + state, directory, unit = _paths(state) + config_path = state / "config.json" + original_config = _optional_read(config_path) + config = load_config(state) if original_config is not None else default_config() + if config["role"] == "node": + raise RuntimeError("Connect ChatGPT from the central profile.") + config.update(settings) + headless_before = config.get("headless", "auto") + changed_headless = headless_before in ("no", False) + if changed_headless: + config["headless"] = "auto" + validate(config) + config_bytes = (json.dumps(config, indent=2, sort_keys=True) + "\n").encode() + record_path = directory / "installation.json" + blob = directory / "credential.cred" + backup = directory / "config-before.json" + manual_stop = directory / "manual-stop.json" + intent = directory / "connection-intent.json" + targets = (config_path, unit, blob, record_path, backup, manual_stop, intent) + originals = {path: _optional_read(path) for path in targets} + previous = _manifest(directory) + if previous: + if previous.get("node") or previous.get("unit") != unit.name or originals[unit] is None or hashlib.sha256(originals[unit]).hexdigest() != previous.get("unit_sha256"): + raise RuntimeError("The existing service changed outside FrameBridge; nothing was replaced.") + if originals[blob] is None or hashlib.sha256(originals[blob]).hexdigest() != previous.get("credential_sha256"): + raise RuntimeError("The encrypted connection changed outside FrameBridge; nothing was replaced.") + if previous.get("headless_changed") and (originals[backup] is None or hashlib.sha256(originals[backup]).hexdigest() != previous.get("config_before_sha256")): + raise RuntimeError("The profile backup changed outside FrameBridge; nothing was replaced.") + elif any(originals[path] is not None for path in (unit, blob, backup)): + raise RuntimeError("Existing unowned connection files; nothing was replaced.") + _check_unit_directory(unit) + version = _command(["systemd-creds", "--version"]).stdout.decode(errors="replace") + match = re.match(r"systemd (\d+)", version) + if not match or int(match.group(1)) < 256: + raise RuntimeError("Protected connections require systemd 256 or later.") + _command(["sudo", "-v"], timeout=180) + user = pwd.getpwuid(os.getuid()).pw_name + linger_before = _linger(user) + units_before = _enabled_units() + properties = _systemctl("show", unit.name, "--property=ActiveState,UnitFileState", check=False) + values = dict(line.split("=", 1) for line in properties.stdout.decode(errors="replace").splitlines() if "=" in line) + was_enabled = values.get("UnitFileState") == "enabled" + was_active = values.get("ActiveState") in {"active", "activating", "reloading"} + try: + encrypted = _command(["sudo", "-n", "--", "systemd-creds", "encrypt", "--uid=" + str(os.getuid()), "--with-key=host", "--name=" + CREDENTIAL, "-", "-"], data=runtime_key.encode()).stdout + finally: + runtime_key = "" + if len(encrypted) < 80 or len(encrypted) > 131072: + raise RuntimeError("Connection protection did not complete.") + # Recheck the exact originals after authentication/encryption and before any stop. + if any(_optional_read(path) != data for path, data in originals.items()): + raise RuntimeError("The profile changed during connection setup; nothing was replaced.") + text = unit_text(state, blob).encode() + record = dict(previous) if previous else {"schema": 1, "unit": unit.name, "node": False, "linger_before": linger_before, "enabled_units_before": units_before, "headless_before": headless_before, "headless_changed": changed_headless, "config_before_sha256": hashlib.sha256(original_config).hexdigest() if original_config is not None else ""} + backup_bytes = originals[backup] + if changed_headless and not record.get("headless_changed"): + record.update(headless_before=headless_before, headless_changed=True, config_before_sha256=hashlib.sha256(original_config).hexdigest()) + backup_bytes = original_config + elif not previous and changed_headless: + backup_bytes = original_config + if record.get("headless_changed"): + # Disabling changes only the desktop preference, preserving the newly selected connection. + restored_profile = dict(config) + restored_profile["headless"] = record["headless_before"] + backup_bytes = (json.dumps(restored_profile, indent=2, sort_keys=True) + "\n").encode() + record["config_before_sha256"] = hashlib.sha256(backup_bytes).hexdigest() + record["changed_config_sha256"] = hashlib.sha256(config_bytes).hexdigest() + record.update(unit_sha256=hashlib.sha256(text).hexdigest(), credential_sha256=hashlib.sha256(encrypted).hexdigest(), connection_settings_sha256=_connection_public_digest(config)) + from . import connection_background + background_before = connection_background.status(state) + changed = {} + service_touched = False + manual_connection_stopped = False + def write(path, data): + changed[path] = data + _write(path, data, replace=originals.get(path) is not None) + try: + if previous: + service_touched = True + _systemctl("stop", unit.name) + disconnected = connection_background.disconnect(state) + if disconnected.get("ok") is False or disconnected.get("alive"): + raise RuntimeError("The previous connection could not be stopped safely; nothing was replaced.") + manual_connection_stopped = not previous and background_before.get("alive") is True + if connection_background.status(state).get("alive"): + raise RuntimeError("The previous connection is still stopping; nothing was replaced.") + write(blob, encrypted) + write(unit, text) + write(config_path, config_bytes) + if backup_bytes is not None and backup_bytes != originals[backup]: + write(backup, backup_bytes) + write(record_path, (json.dumps(record, sort_keys=True, indent=2) + "\n").encode()) + write(intent, (json.dumps({"schema": 1, "connect": True, "revision": connection_request_revision(state) + 1}, sort_keys=True) + "\n").encode()) + _command(["sudo", "-n", "--", "loginctl", "enable-linger", user]) + _systemctl("daemon-reload") + service_touched = True + if boot_requested(state) is False: + _systemctl("start", unit.name) + else: + _systemctl("enable", "--now", unit.name) + except BaseException as error: + rollback_failed = False + if service_touched or unit in changed: + try: + rollback_failed = bool(_systemctl("disable", "--now", unit.name, check=False).returncode) + except Exception: + rollback_failed = True + for path, installed in reversed(list(changed.items())): + try: + current = _optional_read(path) + if current == originals[path]: + continue + if current != installed: + rollback_failed = True + continue + if originals[path] is None: + path.unlink() + else: + _write(path, originals[path], replace=True) + except Exception: + rollback_failed = True + try: + if originals[manual_stop] is not None and _optional_read(manual_stop) is None: + _write(manual_stop, originals[manual_stop]) + _systemctl("daemon-reload") + if previous: + result = _systemctl("enable" if was_enabled else "disable", unit.name, check=False) + rollback_failed = rollback_failed or bool(result.returncode) + if was_active: + restarted = _systemctl("start", unit.name, check=False) + rollback_failed = rollback_failed or bool(restarted.returncode) + if not linger_before and _enabled_units() == units_before: + restored_linger = _command(["sudo", "-n", "--", "loginctl", "disable-linger", user], check=False) + rollback_failed = rollback_failed or bool(restored_linger.returncode) + except Exception: + rollback_failed = True + if rollback_failed: + raise RuntimeError("Connection setup did not complete, and restoration could not be fully verified. Inspect fb autostart status before retrying.") from None + if manual_connection_stopped: + raise RuntimeError("Connection setup did not complete. The previous manually started connection was stopped; reconnect it using its original key.") from None + raise + return _committed_connection_status(state) + + +def _enable(state, *, start_now=True, confirm=True): + if os.geteuid() == 0: + raise RuntimeError("Run autostart enable as the profile owner, not as root.") + state, directory, unit = _paths(state) + config = load_config(state) + if not confirm and config["role"] != "node": + raise RuntimeError("Only node startup can be initialized without the connection wizard.") + original_config = _read(state / "config.json") + existing = _manifest(directory) + if existing: + if existing.get("unit") != unit.name or hashlib.sha256(_read(unit)).hexdigest() != existing.get("unit_sha256"): + raise RuntimeError("The service changed outside FrameBridge; refusing to enable it.") + if not existing.get("node") and hashlib.sha256(_read(directory / "credential.cred")).hexdigest() != existing.get("credential_sha256"): + raise RuntimeError("The encrypted credential changed outside FrameBridge; refusing to enable it.") + user = pwd.getpwuid(os.getuid()).pw_name + if not _linger(user): + _command(["sudo", "-v"], timeout=180) + _command(["sudo", "-n", "--", "loginctl", "enable-linger", user]) + _set_boot_requested(state, True) + _systemctl("daemon-reload") + if start_now: + _systemctl("enable", "--now", unit.name) + else: + _systemctl("enable", unit.name) + return status(state) + version = _command(["systemd-creds", "--version"]).stdout.decode(errors="replace") + match = re.match(r"systemd (\d+)", version) + if not match or int(match.group(1)) < 256: + raise RuntimeError("Encrypted user credentials require systemd 256 or later.") + node = config["role"] == "node" + if not node and not config.get("tunnel_id"): + raise RuntimeError("Save a tunnel with fb connect chatgpt before enabling automatic startup.") + print("FrameBridge will start automatically when this computer starts.") + print("Available tools are detected automatically.") + if not node: + print("Your connection key will be encrypted on this computer.") + original_headless = config.get("headless", "auto") + changed_headless = original_headless in ("no", False) + if confirm and input("Enable automatic startup? [y/N]: ").strip().lower() not in {"y", "yes"}: + return {"configured": False, "state": "cancelled"} + print("[1/3] Preparing automatic startup...", flush=True) + _check_unit_directory(unit) + if unit.exists() or unit.is_symlink() or (directory / "credential.cred").exists() or (directory / "credential.cred").is_symlink(): + raise RuntimeError("Existing unowned service or encrypted credential; nothing was replaced.") + _command(["sudo", "-v"], timeout=180) + user = pwd.getpwuid(os.getuid()).pw_name + linger_before = _linger(user) + units_before = _enabled_units() + encrypted = b"" + print("[2/3] Preparing this node..." if node else "[2/3] Protecting the connection key...", flush=True) + if not node: + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + runtime_key = getpass.getpass("Connection key (hidden): ") + if not runtime_key or not runtime_key.isascii() or len(runtime_key) > 16384 or any(ord(ch) < 33 or ord(ch) > 126 for ch in runtime_key): + raise ValueError("The runtime key must be nonempty and contain no whitespace or control characters.") + try: + encrypted = _command(["sudo", "-n", "--", "systemd-creds", "encrypt", "--uid=" + str(os.getuid()), "--with-key=host", "--name=" + CREDENTIAL, "-", "-"], data=runtime_key.encode()).stdout + finally: + runtime_key = "" + if len(encrypted) < 80 or len(encrypted) > 131072: + raise RuntimeError("Credential encryption returned an invalid result.") + blob = directory / "credential.cred" + text = unit_text(state, blob, node=node).encode() + record = {"schema": 1, "unit": unit.name, "unit_sha256": hashlib.sha256(text).hexdigest(), "credential_sha256": hashlib.sha256(encrypted).hexdigest() if encrypted else "", "node": node, "linger_before": linger_before, "enabled_units_before": units_before, "headless_before": original_headless, "headless_changed": changed_headless, "config_before_sha256": hashlib.sha256(original_config).hexdigest()} + created = [] + try: + if encrypted: + _write(blob, encrypted); created.append(blob) + _write(unit, text); created.append(unit) + if changed_headless: + if _read(state / "config.json") != original_config: + raise RuntimeError("The profile changed during configuration; no profile settings were overwritten.") + _write(directory / "config-before.json", original_config) + created.append(directory / "config-before.json") + config["headless"] = "auto" + save_config(state, config) + record["changed_config_sha256"] = hashlib.sha256(_read(state / "config.json")).hexdigest() + _json(directory / "installation.json", record); created.append(directory / "installation.json") + print("[3/3] Enabling automatic startup...", flush=True) + _command(["sudo", "-n", "--", "loginctl", "enable-linger", user]) + _systemctl("daemon-reload") + if start_now: + _systemctl("enable", "--now", unit.name) + else: + _systemctl("enable", unit.name) + except BaseException: + if unit in created: + _systemctl("disable", "--now", unit.name, check=False) + for path in reversed(created): + try: path.unlink() + except FileNotFoundError: pass + if changed_headless and record.get("changed_config_sha256") == hashlib.sha256(_read(state / "config.json")).hexdigest(): + _write(state / "config.json", original_config, replace=True) + _systemctl("daemon-reload", check=False) + if not linger_before: + try: + if _enabled_units() == units_before: + _command(["sudo", "-n", "--", "loginctl", "disable-linger", user], check=False) + except Exception: + pass + raise + return status(state) + + +def _disable(state): + state, directory, unit = _paths(state) + record = _manifest(directory) + if not record: + return {"configured": False, "state": "already_disabled"} + if record.get("unit") != unit.name or hashlib.sha256(_read(unit)).hexdigest() != record.get("unit_sha256"): + raise RuntimeError("The service changed outside FrameBridge; refusing to remove it.") + blob = directory / "credential.cred" + if not record.get("node") and hashlib.sha256(_read(blob)).hexdigest() != record.get("credential_sha256"): + raise RuntimeError("The encrypted credential changed outside FrameBridge; refusing to remove it.") + backup = None + if record.get("headless_changed"): + backup = _read(directory / "config-before.json") + if hashlib.sha256(backup).hexdigest() != record.get("config_before_sha256"): + raise RuntimeError("The profile backup changed outside FrameBridge; refusing to remove it.") + owned_policy = [directory / "boot-policy.json", directory / "connection-intent.json"] + for policy in owned_policy: + if policy.exists() or policy.is_symlink(): + _read(policy) + restore_linger = not record.get("linger_before") and [name for name in _enabled_units() if name != unit.name] == record.get("enabled_units_before") + if restore_linger: + _command(["sudo", "-v"], timeout=180) + _systemctl("disable", "--now", unit.name) + unit.unlink() + if not record.get("node"): + blob.unlink() + config_restored = False + if record.get("headless_changed") and hashlib.sha256(_read(state / "config.json")).hexdigest() == record.get("changed_config_sha256"): + _write(state / "config.json", backup, replace=True) + config_restored = True + _systemctl("daemon-reload") + linger_restored = False + if restore_linger and _enabled_units() == record.get("enabled_units_before"): + user = pwd.getpwuid(os.getuid()).pw_name + _command(["sudo", "-n", "--", "loginctl", "disable-linger", user]) + linger_restored = True + if backup is not None: + (directory / "config-before.json").unlink() + for policy in owned_policy: + policy.unlink(missing_ok=True) + (directory / "installation.json").unlink() + return {"configured": False, "state": "disabled", "encrypted_credential_removed": True, "desktop_setting_restored": config_restored, "linger_restored": linger_restored} + + +def _credential(state): + # Debian 13 systemd 257 cannot load user encrypted credentials directly. + # Its systemd-creds CLI delegates owner-scoped decryption to the OS broker. + _, directory, _ = _paths(state) + record = _manifest(directory) + if not record or record.get("node"): + raise RuntimeError("The encrypted runtime credential is not configured.") + cipher = _read(directory / "credential.cred") + if hashlib.sha256(cipher).hexdigest() != record.get("credential_sha256"): + raise RuntimeError("The encrypted runtime credential changed outside FrameBridge.") + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + result = _command(["systemd-creds", "decrypt", "--user", "--name=" + CREDENTIAL, "-", "-"], data=cipher, timeout=15) + try: + key = result.stdout.decode("ascii") + except UnicodeDecodeError: + raise RuntimeError("The decrypted runtime credential is invalid.") from None + if not key or len(key) > 16384 or any(ord(ch) < 33 or ord(ch) == 127 for ch in key): + raise RuntimeError("The decrypted runtime credential is invalid.") + return key + + +def stop_for_current_boot(state): + # systemctl stop also cancels an already queued Restart=on-failure job. + # Keep the unit enabled and the encrypted credential for the next boot. + directory = _intent_directory(state) + if directory is None: + return {"configured": False, "state": "not_configured"} + _, _, unit = _paths(state) + record = _manifest(directory) + if record.get("unit") != unit.name or hashlib.sha256(_read(unit)).hexdigest() != record.get("unit_sha256"): + raise RuntimeError("The service changed outside FrameBridge; refusing to stop it.") + _systemctl("stop", unit.name) + return {"configured": True, "state": "stopped_for_current_boot", "autostart_configuration_preserved": True} + + +def _intent_directory(state): + directory = state_path(state) / "autostart" + if not directory.exists() and not directory.is_symlink(): + return None + private_dir(directory) + return directory if _manifest(directory) else None + + +def mark_manual_stop(state): + directory = _intent_directory(state) + if directory is None: + return False + _json(directory / "manual-stop.json", {"requested": True, "boot_id": Path("/proc/sys/kernel/random/boot_id").read_text().strip()}, replace=True) + return True + + +def manual_stop_requested(state): + directory = _intent_directory(state) + if directory is None: + return False + try: + record = json.loads(_read(directory / "manual-stop.json")) + except FileNotFoundError: + return False + return isinstance(record, dict) and record.get("requested") is True and record.get("boot_id") == Path("/proc/sys/kernel/random/boot_id").read_text().strip() + + +def _clear_manual_stop(state): + directory = _intent_directory(state) + if directory is None: + return + path = directory / "manual-stop.json" + try: + _read(path) + except FileNotFoundError: + return + path.unlink() + + +def boot_requested(state): + """Explicit boot policy; None retains the normal first-connect default.""" + directory = _intent_directory(state) + if directory is None: + return None + try: + value = json.loads(_read(directory / "boot-policy.json")) + except FileNotFoundError: + return None + if type(value) is not dict or value.get("schema") != 1 or type(value.get("enabled")) is not bool: + raise RuntimeError("The saved boot policy cannot be verified.") + return value["enabled"] + + +def _set_boot_requested(state, enabled): + if type(enabled) is not bool: + raise ValueError("Boot policy must be true or false.") + directory = _intent_directory(state) + if directory is None: + return False + _json(directory / "boot-policy.json", {"schema": 1, "enabled": enabled}, replace=True) + return True + + +def connection_requested(state): + """Desired transport state survives worker recovery and reboot until connect.""" + directory = _intent_directory(state) + if directory is None: + return True + try: + value = json.loads(_read(directory / "connection-intent.json")) + except FileNotFoundError: + return True + if type(value) is not dict or value.get("schema") != 1 or type(value.get("connect")) is not bool: + raise RuntimeError("The saved connection intent cannot be verified.") + return value["connect"] + + +def connection_request_revision(state): + directory = _intent_directory(state) + if directory is None: + return 0 + try: + value = json.loads(_read(directory / "connection-intent.json")) + except FileNotFoundError: + return 0 + revision = value.get("revision", 0) if type(value) is dict else None + if type(revision) is not int or revision < 0: + raise RuntimeError("The saved connection intent cannot be verified.") + return revision + + +def _set_connection_requested(state, requested): + if type(requested) is not bool: + raise ValueError("Connection intent must be true or false.") + directory = _intent_directory(state) + if directory is None: + return False + _json(directory / "connection-intent.json", {"schema": 1, "connect": requested, "revision": connection_request_revision(state) + 1}, replace=True) + return True + + +def _connection_action(state, action): + """Serialize the worker's transport startup with an explicit pause request.""" + directory = _intent_directory(state) + if directory is None: + return action(state) + fd = os.open(directory / "connection-action.lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1 or info.st_mode & 0o077: + raise RuntimeError("Unsafe connection action lock.") + deadline = time.monotonic() + 20 + while True: + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + break + except BlockingIOError: + if time.monotonic() >= deadline: + raise RuntimeError("Another connection operation is still finishing. Try again shortly.") + time.sleep(0.05) + return action(state) + finally: + os.close(fd) + + +def _worker_status(directory, name): + _json(directory / "worker-status.json", {"state": name, "updated_at": time.time()}, replace=True) + + +def worker(state): + global _STOP + _STOP = False + state, directory, unit = _paths(state) + config = load_config(state) + from . import runtime, connection_background, tunnel + from .display_context import DisplayContextError + def stop(signum, frame): + global _STOP + _STOP = True + old = {s: signal.signal(s, stop) for s in (signal.SIGTERM, signal.SIGINT)} + lockfd = os.open(directory / "worker.lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600) + info = os.fstat(lockfd) + if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1 or info.st_mode & 0o077: + os.close(lockfd) + raise RuntimeError("Unsafe autostart worker lock.") + fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) + connected = False + attempted_connection = False + last_revision = None + next_connection_attempt = 0.0 + try: + _clear_manual_stop(state) + while not _STOP: + if manual_stop_requested(state): + return 0 + try: + current = runtime.start(state) + if manual_stop_requested(state): + return 0 + except DisplayContextError: + if config.get("headless") in ("no", False): + _worker_status(directory, "waiting_for_graphical_login") + time.sleep(3) + continue + raise + if current.get("running") and current.get("ready"): + break + _worker_status(directory, "waiting_for_graphical_login" if config.get("headless") in ("no", False) else "waiting_for_local_services") + time.sleep(3) + if _STOP: + return 0 + _worker_status(directory, "local_services_ready") + if config["role"] == "node": + while not _STOP: + if manual_stop_requested(state): + return 0 + if not runtime.status(state).get("running"): + return 0 if manual_stop_requested(state) else 1 + time.sleep(2) + return 0 + while not _STOP: + if manual_stop_requested(state): + return 0 + def synchronize_connection(_state): + nonlocal connected, attempted_connection, last_revision, next_connection_attempt + revision = connection_request_revision(state) + if revision != last_revision: + attempted_connection = False + next_connection_attempt = 0.0 + last_revision = revision + current = connection_background.status(state) + connected = connected or current.get("alive") is True + if not connection_requested(state): + if current.get("alive"): + stopped = connection_background.disconnect(state) + if stopped.get("ok") is False or stopped.get("alive"): + raise RuntimeError("The paused connection is still stopping.") + _worker_status(directory, "disconnected") + return + if not current.get("alive") and (not attempted_connection or current.get("state") not in ("stopped", "closed")): + if time.monotonic() < next_connection_attempt: + _worker_status(directory, "reconnecting") + return + attempted_connection = True + next_connection_attempt = time.monotonic() + 15 + key = _credential(state) + try: + spec = tunnel.TunnelSpec(config["tunnel_id"], config.get("control_plane_organization_id", ""), config.get("control_plane_url_path", "")) + binary = runtime.PREFIX / "original/geranas/FrameBridge-MCP-Private/bin/tunnel-client" + mcp = "http://127.0.0.1:" + str(config["ports"]["mcp"]) + "/mcp" + command = tunnel.build_command(binary, state, mcp, spec) + _worker_status(directory, "connecting") + connected = True + connection_background.start(command, key, state, spec.tunnel_id, mcp, output=io.StringIO(), wait_timeout=1) + finally: + key = "" + current = connection_background.status(state) + if not current.get("alive"): + _worker_status(directory, "disconnected" if current.get("state") in ("stopped", "closed") else "reconnecting") + else: + _worker_status(directory, "connected" if current.get("verified") else "reconnecting") + _connection_action(state, synchronize_connection) + if not runtime.status(state).get("running"): + return 0 if manual_stop_requested(state) else 1 + time.sleep(2) + return 0 + except Exception: + _worker_status(directory, "failed") + return 1 + finally: + if connected: + connection_background.disconnect(state) + runtime.stop(state) + _worker_status(directory, "stopped") + os.close(lockfd) + for sig, handler in old.items(): + signal.signal(sig, handler) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("action", choices=["_run"]) + parser.add_argument("--state", required=True) + args = parser.parse_args() + return worker(args.state) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/broker.py b/source/expanded/framebridge_expanded/broker.py new file mode 100644 index 0000000..0f9cb6f --- /dev/null +++ b/source/expanded/framebridge_expanded/broker.py @@ -0,0 +1,502 @@ +"""Authenticated operator boundary around the unchanged Original FBPAIR broker.""" +from __future__ import annotations + +import hashlib +import importlib +import importlib.util +import ipaddress +import json +import os +import re +import secrets +import socket +import ssl +import stat +import sys +import threading +import time +import urllib.error +import urllib.parse +import urllib.request +import uuid +from functools import lru_cache +from http.server import ThreadingHTTPServer +from pathlib import Path + + +class BrokerError(ValueError): + """A fixed public failure code; never include HTTP bodies or credentials.""" + + +_state = None +_url = None +_control_path = None +_JOB = re.compile(r"[A-Za-z0-9._-]{1,80}") +_LABEL = re.compile(r"[A-Za-z0-9._-]{1,64}") + + +def state_path(state_dir=None): + from .config import state_path as canonical_state + return canonical_state(state_dir or os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") + or os.environ.get("FRAMEBRIDGE_STATE_DIR")).resolve() + + +def _config(state_dir=None): + p = state_path(state_dir) / "config.json" + if not p.is_file(): + return {} + data = json.loads(p.read_text()) + if not isinstance(data, dict): + raise BrokerError("CONFIG_INVALID") + return data + + +def _loopback(host): + try: + return ipaddress.ip_address(host).is_loopback + except ValueError: + return host == "localhost" and all(ipaddress.ip_address(a[4][0]).is_loopback + for a in socket.getaddrinfo(host, None)) + + +def validate_endpoint(url): + """Permit verified HTTPS, or literal/local loopback HTTP, without URL credentials.""" + if not isinstance(url, str) or len(url) > 2048: + raise BrokerError("BROKER_URL_INVALID") + parsed = urllib.parse.urlsplit(url) + try: + valid_port = parsed.port is None or 1 <= parsed.port <= 65535 + except ValueError: + valid_port = False + if (not parsed.hostname or not valid_port or parsed.username is not None + or parsed.password is not None or parsed.query or parsed.fragment + or parsed.scheme not in ("http", "https") + or any(ord(c) < 33 for c in url)): + raise BrokerError("BROKER_URL_INVALID") + if parsed.scheme == "http" and not _loopback(parsed.hostname): + raise BrokerError("BROKER_HTTP_LOOPBACK_REQUIRED") + return url.rstrip("/") + + +def configure(broker_url=None, state_dir=None, control_key_path=None): + global _state, _url, _control_path + _state = state_path(state_dir) + config = _config(_state) + port = config.get("ports", {}).get("broker", 20878) + _url = validate_endpoint(broker_url or config.get("broker_control_url") or config.get("broker_url") + or f"http://127.0.0.1:{port}") + _control_path = Path(control_key_path or _state / "broker" / "control.key") + + +def _settings(): + if _state is None: + configure() + return _state, _url, _control_path + + +def _private_read(path): + fd = os.open(str(path), os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + st = os.fstat(fd) + if not stat.S_ISREG(st.st_mode) or st.st_uid != os.getuid() or st.st_mode & 0o077: + raise BrokerError("PRIVATE_STATE_PERMISSIONS_INVALID") + data = os.read(fd, 4097) + if len(data) > 4096: + raise BrokerError("PRIVATE_STATE_TOO_LARGE") + return data + finally: + os.close(fd) + + +def _control_secret(state_dir, create=False): + p = state_path(state_dir) / "broker" / "control.key" + if create: + p.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + st = p.parent.lstat() + if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or st.st_mode & 0o077: + raise BrokerError("PRIVATE_CONTROL_DIRECTORY_INVALID") + if not p.exists(): + fd = os.open(str(p), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + try: + raw = secrets.token_urlsafe(48).encode() + with os.fdopen(fd, "wb") as stream: + stream.write(raw) + stream.flush() + os.fsync(stream.fileno()) + except Exception: + raise BrokerError("CONTROL_IDENTITY_CREATE_FAILED") from None + secret = _private_read(p).decode("ascii").strip() + if len(secret) < 40: + raise BrokerError("CONTROL_IDENTITY_INVALID") + return secret + + +class _NoRedirect(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + raise BrokerError("BROKER_REDIRECT_BLOCKED") + + +def post_json(url, value, *, state_dir=None, timeout=10, control=False): + validate_endpoint(url) + config = _config(state_dir) + ca = os.environ.get("FRAMEBRIDGE_TLS_CA") or config.get("tls_ca") or None + context = ssl.create_default_context(cafile=str(ca) if ca else None) + headers = {"Content-Type": "application/json", "Accept": "application/json"} + if control: + _, _, configured = _settings() + headers["Authorization"] = "Bearer " + _private_read(configured).decode("ascii").strip() + request = urllib.request.Request(url, data=json.dumps(value, separators=(",", ":"), + allow_nan=False).encode(), + headers=headers, method="POST") + opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), _NoRedirect(), + urllib.request.HTTPSHandler(context=context)) + try: + with opener.open(request, timeout=timeout) as response: + raw = response.read(20 * 1024 * 1024 + 1) + if len(raw) > 20 * 1024 * 1024: + raise BrokerError("BROKER_RESPONSE_TOO_LARGE") + result = json.loads(raw) + except BrokerError: + raise + except urllib.error.HTTPError as exc: + raise BrokerError("BROKER_HTTP_REJECTED_" + str(exc.code)) from None + except Exception: + raise BrokerError("BROKER_TRANSPORT_FAILED") from None + if not isinstance(result, dict): + raise BrokerError("BROKER_RESPONSE_INVALID") + return result + + +def _control(path, payload): + state, url, _ = _settings() + return post_json(url + "/fbpair/v1/control/" + path, payload, + state_dir=state, control=True) + + +def list_confirmed_nodes(): + result = _control("nodes", {}) + if result.get("ok") is not True or not isinstance(result.get("nodes"), list): + raise BrokerError("BROKER_DISCOVERY_FAILED") + nodes = result["nodes"] + ids, aliases = set(), set() + for item in nodes: + if not isinstance(item, dict): + raise BrokerError("BROKER_DISCOVERY_INVALID") + ident, alias = item.get("node_id"), item.get("alias") + if not isinstance(ident, str) or not isinstance(alias, str): + raise BrokerError("BROKER_DISCOVERY_INVALID") + if ident in ids or alias.casefold() in aliases: + raise BrokerError("BROKER_DISCOVERY_AMBIGUOUS") + ids.add(ident) + aliases.add(alias.casefold()) + return nodes + + +def list_pending(): + result = _control("pending", {}) + if result.get("ok") is not True: + raise BrokerError("BROKER_PENDING_FAILED") + return result.get("requests", []) + + +def approve(request_id, expected_node_id, state_dir=None): + if state_dir is not None: + configure(state_dir=state_dir) + return _control("approve", {"request_id": request_id, "expected_node_id": expected_node_id}) + + +def revoke(node_id): + return _control("revoke", {"node_id": node_id}) + + +def request(node_id, capability, args, timeout_s=120): + if type(timeout_s) is not int or not 1 <= timeout_s <= 600: + raise BrokerError("INVALID_TIMEOUT") + if not isinstance(args, dict): + raise BrokerError("ARGS_NOT_OBJECT") + confirmed = {n["node_id"]: n for n in list_confirmed_nodes()} + if node_id not in confirmed: + raise BrokerError("NODE_NOT_CONFIRMED") + if confirmed[node_id].get("ready") is not True: + raise BrokerError("NODE_OFFLINE") + path = {"admin.exec": "admin-exec", "admin.cua": "admin-cua"}.get(capability, "request") + payload = {"node_id": node_id} + if path == "request": + payload.update(capability=capability, args=args) + else: + payload["payload"] = args + created = _control(path, payload) + if created.get("ok") is not True or not created.get("request_id"): + raise BrokerError("BROKER_REQUEST_REJECTED") + rid = created["request_id"] + deadline = time.monotonic() + timeout_s + 10 + while time.monotonic() < deadline: + final = _control("result", {"request_id": rid}) + if final.get("node_id") != node_id: + raise BrokerError("BROKER_RESULT_NODE_MISMATCH") + if final.get("state") == "complete": + result = final.get("result") + if not isinstance(result, dict): + raise BrokerError("BROKER_RESULT_INVALID") + return dict(result, request_id=rid, node_id=node_id) + if final.get("state") == "expired": + raise BrokerError("BROKER_REQUEST_EXPIRED") + time.sleep(0.2) + raise BrokerError("BROKER_RESULT_TIMEOUT") + + +@lru_cache(maxsize=1) +def original_modules(): + from .fleet import bootstrap_original + bootstrap_original() + root = Path(os.environ.get("FRAMEBRIDGE_BROKER_SOURCE") + or str(Path(os.environ.get("FRAMEBRIDGE_EXPANDED_PREFIX", "/usr/lib/framebridge-expanded")) + / "original/geranas/FrameBridge-Broker-RC1")) + if not root.exists(): + root = Path(__file__).resolve().parents[2] / "sources/original-geranas/FrameBridge-Broker-RC1" + src = root if (root / "framebridge_linux_chatgpt").is_dir() else root / "src" + if not src.is_dir(): + raise BrokerError("ORIGINAL_BROKER_SOURCE_UNAVAILABLE") + sys.path.insert(0, str(src)) if str(src) not in sys.path else None + package = importlib.import_module("framebridge_linux_chatgpt") + path = str(src / "framebridge_linux_chatgpt") + if path not in package.__path__: + package.__path__.append(path) + script = (root.parent if root == src else root) / "dev/fbpair_dev_broker.py" + if not script.is_file(): + raise BrokerError("ORIGINAL_BROKER_SOURCE_UNAVAILABLE") + spec = importlib.util.spec_from_file_location("_framebridge_expanded_original_broker", script) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _admin_payload(operation, payload): + if not isinstance(payload, dict) or payload.get("authorized") is not True: + raise BrokerError("AUTHORIZED_TRUE_REQUIRED") + value = dict(payload) + timeout = value.get("timeout_s", 120) + if type(timeout) is not int or not 1 <= timeout <= 600: + raise BrokerError("INVALID_TIMEOUT") + job = value.get("job_id", "") + if not isinstance(job, str) or _JOB.fullmatch(job) is None: + raise BrokerError("INVALID_JOB_ID") + if operation == "admin.exec": + script = value.get("script") + if not isinstance(script, str) or not script or len(script.encode()) > 262144: + raise BrokerError("SCRIPT_INVALID") + digest = hashlib.sha256(script.encode()).hexdigest() + if value.get("sha256") != digest: + raise BrokerError("SCRIPT_SHA256_MISMATCH") + elif operation == "admin.cua": + if timeout > 180: + raise BrokerError("INVALID_CUA_TIMEOUT") + cua = value.get("cua") + if not isinstance(cua, dict): + raise BrokerError("CUA_PAYLOAD_INVALID") + if cua.get("action") not in {"OBSERVE", "CLICK", "DOUBLE_CLICK", "TYPE", "KEY", "SCROLL", "DRAG"}: + raise BrokerError("CUA_ACTION_UNSUPPORTED") + # Original admin.cua carries the adapter's cua object, not an EXEC script. + else: + raise BrokerError("OPERATION_NOT_ALLOWED") + value["timeout_s"] = timeout + return value + + +def make_server(host="127.0.0.1", port=20878, state_dir=None): + """Build without starting; tests and runtime own the server lifecycle.""" + state = state_path(state_dir) + config = _config(state) + if not isinstance(host, str) or not host: + raise BrokerError("BROKER_BIND_INVALID") + tls_cert, tls_key = config.get("tls_cert"), config.get("tls_key") + if not _loopback(host) and not (tls_cert and tls_key): + raise BrokerError("BROKER_REMOTE_BIND_REQUIRES_TLS") + if type(port) is not int or not 1 <= port <= 65535: + raise BrokerError("BROKER_PORT_INVALID") + secret = _control_secret(state, create=True) + original = original_modules() + discovery = state / "broker" / "paired-nodes.json" + public_base = config.get("broker_url") or (f"https://{host}:{port}" if tls_cert else f"http://{host}:{port}") + public_base = validate_endpoint(public_base) + admin_lock = threading.RLock() + + class Store(original.Store): + def token(self, pair_id, device_code): + result = super().token(pair_id, device_code) + if result.get("state") == "paired": + result.update(broker_issuer=public_base, + account_binding_id="local-operator-" + hashlib.sha256(secret.encode()).hexdigest()[:24], + scopes=["node.read", "admin.exec", "admin.cua"]) + with self.lock: + self.by_pair[pair_id]["receipt"] = dict(result) + return result + + def queue_admin(self, node_id, operation, payload): + node = self._paired_node(node_id) + if node is None: + return None, "node_not_paired" + if operation not in node.get("scopes", []): + return None, "node_scope_denied" + try: + value = _admin_payload(operation, payload) + except BrokerError as exc: + return None, str(exc) + rid = secrets.token_hex(16) + item = {"schema": 1, "request_id": rid, "node_id": node_id, + "operation": operation, "payload": value, "created_at": time.time(), + "expires_at": time.time() + value["timeout_s"] + 90} + with self.lock: + self.control_requests[rid] = item + self.control_results[rid] = {"state": "pending", "node_id": node_id} + self.control_queue[node_id].append(rid) + return item, None + + store = Store(discovery, public_base) + BaseHandler = original.make_handler(store, public_base) + + class Handler(BaseHandler): + server_version = "FrameBridgeOriginal/1" + + def do_GET(self): + self.send_json(200 if self.path == "/health" else 404, + {"ok": self.path == "/health", "protocol": "FBPAIR/1"}) + + def do_POST(self): + if self.path.startswith("/fbpair/v1/control/"): + if not _loopback(str(self.client_address[0])): + self.send_json(403, {"ok": False, "error": "CONTROL_LOOPBACK_REQUIRED"}) + return + if not secrets.compare_digest(self.headers.get("Authorization", ""), "Bearer " + secret): + self.send_json(403, {"ok": False, "error": "CONTROL_AUTH_REQUIRED"}) + return + try: + body = self.read_json() + if not isinstance(body, dict): + raise BrokerError("REQUEST_INVALID") + with admin_lock: + self._control(body) + except BrokerError as exc: + self.send_json(400, {"ok": False, "error": str(exc)}) + except Exception: + self.send_json(500, {"ok": False, "error": "BROKER_INTERNAL_ERROR"}) + return + if self.path not in {"/fbpair/v1/device_authorization", "/fbpair/v1/token", + "/fbpair/v1/confirm", "/fbpair/v1/cancel", + "/fbpair/v1/node/poll", "/fbpair/v1/node/result"}: + self.send_json(404, {"ok": False, "error": "ENDPOINT_NOT_AVAILABLE"}) + return + super().do_POST() + + def _control(self, body): + route = self.path.rsplit("/", 1)[-1] + if route == "pending": + pending = [] + for item in list(store.by_pair.values()): + req = item["request"] + if item.get("state") == "pending" and item["expires"] > time.time(): + pending.append({"request_id": item["pair_id"], "node_id": req["node_id"], + "node_label": req["node_label"], "state": "pending", + "fingerprint": original.parse_ed25519_public_key_line( + req.get("public_key_ssh", ""))["public_key_sha256"]}) + self.send_json(200, {"ok": True, "requests": pending}) + elif route == "approve": + rid, expected = body.get("request_id"), body.get("expected_node_id") + item = store.by_pair.get(rid) + if not item or item["request"]["node_id"] != expected: + raise BrokerError("APPROVAL_NODE_MISMATCH") + req = item["request"] + alias = req["node_label"] + reserved = {"local", str(config.get("name", "")).casefold()} + if not isinstance(alias, str) or _LABEL.fullmatch(alias) is None or alias.casefold() in reserved: + raise BrokerError("NODE_ALIAS_RESERVED_OR_INVALID") + if expected == config.get("node_id"): + raise BrokerError("NODE_ID_RESERVED") + for node in store._discovery_records(): + if node.get("state") == "paired" and (node.get("node_id") == expected + or str(node.get("node_label", "")).casefold() == alias.casefold()): + raise BrokerError("NODE_ALREADY_REGISTERED") + for other in store.by_pair.values(): + if other is item: + continue + if other.get("state") in {"approved", "receipt_issued"} and other["expires"] > time.time(): + if (other["request"].get("node_id") == expected + or str(other["request"].get("node_label", "")).casefold() == alias.casefold()): + raise BrokerError("NODE_ALIAS_OR_ID_PENDING") + if not store.approve(item["user_code"]): + raise BrokerError("PAIR_NOT_APPROVABLE") + self.send_json(200, {"ok": True, "request_id": rid, "node_id": expected, + "state": "approved", "approval_method": "authenticated-local-operator"}) + elif route == "nodes": + nodes = [] + for n in store._discovery_records(): + if n.get("state") != "paired": + continue + status = store.node_runtime_status(n["node_id"]) + nodes.append({"node_id": n["node_id"], "alias": n["node_label"], + "hostname": n["node_label"], "transport": "broker_outbound", + "ready": status.get("ready") is True, "pairing_state": "paired"}) + self.send_json(200, {"ok": True, "nodes": nodes}) + elif route == "node-status": + self.send_json(200, store.node_runtime_status(str(body.get("node_id", "")))) + elif route == "revoke": + node_id = body.get("node_id") + records = store._discovery_records() + found = False + for n in records: + if n.get("node_id") == node_id: + n["state"] = "revoked" + found = True + if not found: + raise BrokerError("NODE_NOT_FOUND") + tmp = discovery.with_name(".paired-nodes.revocation.tmp") + fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + with os.fdopen(fd, "w") as stream: + json.dump({"schema": 1, "nodes": records}, stream) + stream.flush() + os.fsync(stream.fileno()) + os.replace(tmp, discovery) + self.send_json(200, {"ok": True, "node_id": node_id, "state": "revoked"}) + elif route in {"request", "admin-exec", "admin-cua"}: + node_id = str(body.get("node_id", "")) + if route == "request": + item, error = store.queue_read(node_id, body.get("capability"), body.get("args")) + else: + operation = "admin.exec" if route == "admin-exec" else "admin.cua" + item, error = store.queue_admin(node_id, operation, body.get("payload")) + if error: + raise BrokerError(str(error)) + self.send_json(200, {"ok": True, "request_id": item["request_id"]}) + elif route == "result": + result = store.get_result(str(body.get("request_id", ""))) + if result is None: + raise BrokerError("REQUEST_NOT_FOUND") + self.send_json(200, {"ok": True, **result}) + else: + self.send_json(404, {"ok": False, "error": "ENDPOINT_NOT_AVAILABLE"}) + + if ":" in host: + class Server(ThreadingHTTPServer): + address_family = socket.AF_INET6 + else: + Server = ThreadingHTTPServer + server = Server((host, port), Handler) + server.daemon_threads = True + server.expanded_store = store + if tls_cert or tls_key: + if not tls_cert or not tls_key: + server.server_close() + raise BrokerError("TLS_CERT_AND_KEY_REQUIRED") + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.minimum_version = ssl.TLSVersion.TLSv1_2 + context.load_cert_chain(str(tls_cert), str(tls_key)) + server.socket = context.wrap_socket(server.socket, server_side=True) + return server + + +def serve(host="127.0.0.1", port=20878, state_dir=None): + server = make_server(host, port, state_dir) + try: + server.serve_forever(poll_interval=0.25) + finally: + server.server_close() diff --git a/source/expanded/framebridge_expanded/cli.py b/source/expanded/framebridge_expanded/cli.py new file mode 100644 index 0000000..19a54cb --- /dev/null +++ b/source/expanded/framebridge_expanded/cli.py @@ -0,0 +1,798 @@ +"""English command-line interface for FrameBridge Expanded.""" +from __future__ import annotations + +import argparse +import copy +import importlib +import json +import os +from pathlib import Path +import re +import socket +import sys +import shlex + +from .config import default_config, load_config, save_config +from . import tunnel + +_EMAIL = re.compile(r"[^\s@]+@[^\s@]+\.[^\s@]+\Z") + + +def resolve_state(value=None): + return Path(value or os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") + or os.environ.get("FRAMEBRIDGE_STATE_DIR") + or Path.home() / ".local/share/framebridge-expanded").expanduser().absolute() + + +def _module(name): + return importlib.import_module("." + name, __package__) + + +def _show(value): + print(json.dumps(_structured_result(value) if _OUTPUT and _OUTPUT.get("json") else value, ensure_ascii=False, indent=2, default=str)) + + +def _ask(label, default="", *, ask=None): + ask = ask or input + value = ask(label + (f" [{default}]" if default != "" else "") + ": ").strip() + return value if value else default + + +def _yes(label, default=False, *, ask=None): + answer = _ask(label + (" [Y/n]" if default else " [y/N]"), ask=ask).lower() + return default if not answer else answer in {"y", "yes"} + + + +def _show_autostart_enabled(result): + """Summarize observed state without treating configuration as readiness.""" + enabled = result.get("configured") is True and result.get("enabled") is True + if not enabled: + print("Automatic startup is not enabled. Check: fb status") + return 1 + print("Automatic startup enabled.") + service = result.get("service_state", "unavailable") + if service not in {"active", "activating", "deactivating", "inactive", "failed", "reloading", "maintenance", "refreshing"}: + service = "unavailable" + print("Service: " + service) + current = result.get("runtime") + current = current if isinstance(current, dict) else {} + capabilities = current.get("capabilities") + capabilities = capabilities if isinstance(capabilities, dict) else {} + ready = current.get("ready") is True + if ready and all(capabilities.get(name) is True for name in ("fbexec", "fbterm", "cua_tty_module")): + print("Tools: FBEXEC, FBTERM, CUA-TTY ready.") + elif current.get("status_unavailable"): + print("Tools: status unavailable. Check: fb status") + else: + print("Tools: starting.") + context = current.get("display_context") + context = context if isinstance(context, dict) else {} + kind = context.get("kind") + if ready and capabilities.get("graphical_cua") is True and kind in {"physical-x11", "virtual-x11"}: + print("Desktop: " + ("detected; CUA ready." if kind == "physical-x11" else "private; CUA ready.")) + elif kind == "text": + print("Desktop: text mode.") + else: + print("Desktop: checking.") + if current.get("role") == "node": + print("Connection: managed by the central computer.") + else: + connection = result.get("connection") + connection = connection if isinstance(connection, dict) else {} + phase = connection.get("state") + if connection.get("verified") is True: + print("Connection: ready.") + elif phase == "retrying": + print("Connection: retrying in the background.") + elif phase in {"starting", "checking", "access", "unverified"}: + print("Connection: checking in the background.") + elif phase in {"error", "rejected"}: + print("Connection: needs attention. Check: fb status connection") + elif phase in {"stopped", "closed"}: + print("Connection: stopped. Check: fb status connection") + else: + print("Connection: checking. Check: fb status connection") + return 0 if result.get("ok") is not False and service not in {"failed", "maintenance"} else 1 + + +def setup(state, *, ask=None): + """Review and save public settings; never start services or a tunnel.""" + state = resolve_state(state) + if (state / "config.json").exists(): + current = _module("runtime").status(state) + if current.get("running") or any(p.get("alive") for p in current.get("processes", [])): + raise RuntimeError("Stop this profile before changing setup. Run fb service stop.") + config = copy.deepcopy(load_config(state) if (state / "config.json").exists() else default_config()) + print("Set up this computer") + print("standalone: local tools; central: local tools and a node broker; node: joins a central broker.") + config["role"] = _ask("Role (standalone/central/node)", config["role"], ask=ask).lower() + config["name"] = _ask("Computer name", config["name"], ask=ask) + desktop_mode = config.get("headless", "auto") + if desktop_mode == "auto": + print("Desktop: automatic detection.") + elif desktop_mode in ("yes", True): + print("Desktop: existing private desktop setting preserved.") + else: + print("Desktop: existing physical desktop setting preserved.") + ports = {} + for service in ("mcp", "hub", "broker"): + value = _ask(service.upper() + " port", str(config["ports"][service]), ask=ask) + try: + ports[service] = int(value) + except ValueError: + raise ValueError("Service ports must be integers.") from None + config["ports"] = ports + label = _ask("ChatGPT email label (optional; browser login is separate)", config.get("account_label", ""), ask=ask) + if label and not _EMAIL.fullmatch(label): + raise ValueError("Enter an email label or leave it empty.") + config["account_label"] = label + certificate_plan = None + if config["role"] == "central": + print("Loopback is local only. Choose 0.0.0.0 explicitly to accept IPv4 LAN nodes with HTTPS.") + config["broker_listen_host"] = _ask("Broker listen host", config.get("broker_listen_host", "127.0.0.1"), ask=ask) + mode = _ask("TLS certificate setup (generate/existing)", "existing" if config.get("tls_cert") else "generate", ask=ask).lower() + if mode == "generate": + if (state / "tls").exists() or any(config.get(k) for k in ("tls_cert", "tls_key", "tls_ca")): + raise ValueError("This profile already has TLS material. Choose existing certificates or an explicit new profile.") + tls = _module("tls") + host = tls._dns_name(_ask("Certificate DNS name", config["name"].replace("_", "-"), ask=ask)) + address = str(tls._address(_ask("Advertised broker IP address", ask=ask))) + certificate_plan = {"host": host, "address": address, "listen_host": config["broker_listen_host"]} + print("A private local CA and server certificate will be created only after confirmation.") + elif mode == "existing": + config["tls_cert"] = _ask("Broker HTTPS certificate file (path only; optional on loopback)", config.get("tls_cert", ""), ask=ask) + config["tls_key"] = _ask("Broker HTTPS private key file (path only)", config.get("tls_key", ""), ask=ask) + config["tls_ca"] = _ask("Trusted CA file (path only; optional)", config.get("tls_ca", ""), ask=ask) + config["broker_url"] = _ask("Advertised broker URL (optional on loopback)", config.get("broker_url", ""), ask=ask) + scheme = "https" if config["tls_cert"] and config["tls_key"] else "http" + config["broker_control_url"] = scheme + "://127.0.0.1:" + str(ports["broker"]) + if scheme == "https": + print("Existing certificates must cover 127.0.0.1 or localhost for private local control.") + if config["broker_listen_host"] not in {"localhost", "127.0.0.1", "::1"} and not (config["tls_cert"] and config["tls_key"]): + raise ValueError("A broker listening outside loopback requires HTTPS certificate and key files.") + else: + raise ValueError("Choose generate or existing for TLS certificate setup.") + elif config["role"] == "node": + config["broker_url"] = _ask("Central broker URL (verified HTTPS for remote hosts; may be set later)", config.get("broker_url", ""), ask=ask) + config["tls_ca"] = _ask("Central broker CA file (path only; blank uses system trust)", config.get("tls_ca", ""), ask=ask) + print("Review this profile. TLS paths refer to node transport files; they are not ChatGPT API keys.") + _show(config) + if certificate_plan is not None: + _show({"new_lan_certificate": certificate_plan}) + if not _yes("Save this configuration?", True, ask=ask): + print("Configuration was not saved.") + return 0 + save_config(state, config) + if certificate_plan is not None: + _show(_module("tls").create_certificates(state, **certificate_plan)) + print("Configuration saved. No service or ChatGPT connection was started.") + if config["role"] == "node": + print("Next: framebridge doctor, framebridge nodes enroll, ask the central operator to approve, then framebridge nodes poll.") + print("After approval is confirmed, run fb service start.") + else: + print("Next: fb connect. This starts the managed tools and connection.") + print("Use the same --state-dir option with every command when using a custom profile.") + return 0 + + +def connect_chatgpt(state, *, foreground=False, replace=False): + config_path = Path(state) / "config.json" + config = load_config(state) if config_path.exists() or config_path.is_symlink() else default_config() + if config["role"] == "node": + raise RuntimeError("A node profile does not publish an MCP server. Connect ChatGPT from the central profile.") + runtime = _module("runtime") + port = config["ports"]["mcp"] + expected_url = "http://127.0.0.1:" + str(port) + "/mcp" + binary = Path(runtime.PREFIX) / "original/geranas/FrameBridge-MCP-Private/bin/tunnel-client" + if not binary.is_file(): + raise RuntimeError("The packaged tunnel client is missing.") + manifest = Path(state) / "autostart/installation.json" + if not foreground and not replace and (manifest.exists() or manifest.is_symlink()): + return tunnel.resume_managed_connection(state, _module("autostart").resume_connection) + if foreground: + current = runtime.status(state) + if current.get("running") is not True or current.get("ready") is not True or current.get("mcp_url") != expected_url: + raise RuntimeError("This profile's MCP service is not ready. Run fb service start for this profile first.") + def save_public(settings): + config.update(settings) + save_config(state, config) + return tunnel.connect_chatgpt(binary, state, expected_url, config, save_public, foreground=True) + def configure_connection(profile, settings, runtime_key): + current = runtime.status(state) if config_path.is_file() else {"running": False} + if current.get("running") and current.get("mcp_url") != expected_url: + raise RuntimeError("This profile's MCP endpoint does not match its configuration. Run fb doctor.") + if not current.get("running"): + for name in ("mcp", "hub") + (("broker",) if config["role"] == "central" else ()): + try: + with socket.socket() as probe: + probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + probe.bind(("127.0.0.1", config["ports"][name])) + except OSError: + raise RuntimeError("The local " + name.upper() + " port " + str(config["ports"][name]) + " is already in use. Choose an available port with fb setup.") from None + return _module("autostart").configure_connection(profile, settings, runtime_key) + return tunnel.connect_chatgpt(binary, state, expected_url, config, configure_connection=configure_connection) + + +def nodes_command(args, state): + config = load_config(state) + if args.node_command == "list": + broker = _module("broker") + broker.configure(None, state_dir=state) + previous = os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") + try: + os.environ["FRAMEBRIDGE_EXPANDED_STATE"] = str(state) + _show(_module("fleet").discover_nodes()) + finally: + if previous is None: + os.environ.pop("FRAMEBRIDGE_EXPANDED_STATE", None) + else: + os.environ["FRAMEBRIDGE_EXPANDED_STATE"] = previous + return 0 + if args.node_command in {"pending", "approve"}: + broker = _module("broker") + broker.configure(None, state_dir=state) + result = broker.list_pending() if args.node_command == "pending" else broker.approve(args.request_id, args.node_id, state_dir=state) + else: + node = _module("node") + if args.node_command == "enroll": + result = node.enroll(args.central or config.get("broker_url", ""), args.name or config["name"], state_dir=state) + elif args.node_command == "status": + result = node.status(state_dir=state) + elif args.node_command == "poll": + result = node.poll_enrollment(state_dir=state) + else: + result = node.run(state_dir=state, once=args.once) + if result is not None: + _show(result) + return 0 + + +def _target_tty(state, target, payload=None, service=None): + """Use the existing authenticated node transport; never guess SSH identities.""" + if target == "LOCAL": + tty = _module("tty") + return getattr(tty, service)(state) if service else tty.request(state, payload) + previous = os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") + os.environ["FRAMEBRIDGE_EXPANDED_STATE"] = str(state) + try: + fleet = _module("fleet") + wire = _module("tty_mcp") + def execute(**kwargs): + alias = kwargs.pop("target") + return fleet.execute_node(alias, **kwargs) + if service: + return wire._exec_json(execute, target, [service]) + return wire._remote_request(execute, target, payload) + finally: + if previous is None: + os.environ.pop("FRAMEBRIDGE_EXPANDED_STATE", None) + else: + os.environ["FRAMEBRIDGE_EXPANDED_STATE"] = previous + + +def tty_command(args, state): + action = getattr(args, "tty_service_action", None) if args.tty_command == "service" else args.tty_command + target = getattr(args, "node", "LOCAL") + if action in {"start", "stop", "status"}: + result = _target_tty(state, target, service=action) + else: + payload = {"authorized": True} + if action == "list": + payload["op"] = "status" + elif action == "open": + payload.update(op="open", program=args.program, rows=args.rows, cols=args.cols) + elif action == "sudo": + payload.update(op="sudo", session_id=args.session_id, mode=args.sudo_mode) + elif action == "observe": + payload.update(op="observe", session_id=args.session_id) + elif action == "key": + payload.update(op="key.send", session_id=args.session_id, key=args.key) + elif action == "type": + text = args.text if args.text is not None else input("Text to send: ") + payload.update(op="text.type", session_id=args.session_id, text=text) + elif action == "resize": + payload.update(op="resize", session_id=args.session_id, rows=args.rows, cols=args.cols) + else: + payload.update(op="close", session_id=args.session_id) + result = _target_tty(state, target, payload=payload) + _show(result) + return 1 if isinstance(result, dict) and result.get("ok") is False else 0 + + +def tutorial(): + runtime = _module("runtime") + candidates = [Path(runtime.PREFIX) / "docs/DEBIAN-TUTORIAL.md", + Path(__file__).resolve().parents[2] / "docs/DEBIAN-TUTORIAL.md"] + for path in candidates: + if path.is_file(): + print(path.read_text(encoding="utf-8")) + return 0 + raise RuntimeError("The tutorial is not installed. Run framebridge --help.") + + + +_OUTPUT = None + + +def _structured_result(value): + """Versioned envelope used only when --json was explicitly requested.""" + return {"schema_version": 1, "product": "FrameBridge", "version": "0.3.1", + "command": _OUTPUT.get("command") if _OUTPUT else None, + "ok": not isinstance(value, dict) or value.get("ok") is not False, + "result": value} + + +def _result_code(value): + return 1 if isinstance(value, dict) and value.get("ok") is False else 0 + + +def _scoped_action(action): + if _OUTPUT and _OUTPUT.get("explicit_state") and action.startswith("fb "): + return "fb --state-dir " + shlex.quote(_OUTPUT["explicit_state"]) + " " + action[3:] + return action + + +def _interaction_required(action): + action = _scoped_action(action) + result = {"ok": False, "error": "INTERACTION_REQUIRED", + "message": "Complete this action in a normal interactive terminal. Do not send credentials through chat.", + "next_action": action} + if _OUTPUT and _OUTPUT.get("json"): + _show(result) + else: + print("An interactive terminal is required. Next: " + action, file=sys.stderr) + return 2 + + +def _saved_noninteractive_ready(state): + value = _module("lifecycle").status(state) + boot = value.get("boot", {}) + return all(boot.get(name) is True for name in + ("configured", "encrypted_credential_present", "linger")) + + +def _runtime_label(value): + if value.get("status_unavailable") or "running" not in value: + return "unavailable" + return "ready" if value.get("ready") is True else ("running" if value.get("running") is True else "stopped") + + +def _boot_label(value): + if value.get("configured") is False: + return "not configured" + if value.get("configured") is not True or type(value.get("enabled")) is not bool: + return "unavailable" + return "enabled" if value["enabled"] else "disabled" + + +def _human_status(value, scope=None): + if scope == "connection": + conn = value + print("ChatGPT connection: " + ("connected (verified)" if conn.get("verified") is True else str(conn.get("state", "unavailable")))) + if conn.get("desired_connection"): + print("Connection intent: " + str(conn["desired_connection"])) + return + if scope == "boot": + boot = value + policy = _boot_label(boot) + print("Automatic startup: " + policy) + print("Current service: " + str(boot.get("service_state", "unavailable"))) + print("Saved connection material: " + ("present" if boot.get("encrypted_credential_present") is True else ("absent" if boot.get("encrypted_credential_present") is False else "unavailable"))) + return + if scope == "runtime": + runtime = value + print("Local tools: " + _runtime_label(runtime)) + print("Role: " + str(runtime.get("role", "unavailable"))) + return + if scope == "console": + print("Console: " + str(value.get("status", value.get("state", "available" if value.get("panes") else "not open")))) + return + runtime = value.get("runtime", {}) + runtime = runtime if isinstance(runtime, dict) else {} + connection = value.get("connection", {}) + connection = connection if isinstance(connection, dict) else {} + boot = value.get("boot", {}) + boot = boot if isinstance(boot, dict) else {} + print("FrameBridge 0.3.1") + print("Role: " + str(runtime.get("role", "unavailable"))) + print("Local tools: " + _runtime_label(runtime)) + if runtime.get("role") == "node": + print("ChatGPT connection: provided by the central computer") + else: + print("ChatGPT connection: " + ("connected (verified)" if connection.get("verified") is True else str(connection.get("state", "unavailable")))) + print("Connection intent: " + str(value.get("desired_connection", "unavailable"))) + print("Automatic startup: " + _boot_label(boot)) + print("Current service: " + str(boot.get("service_state", "unavailable"))) + if value.get("ok") is False: + print("Some status information is unavailable. Run fb doctor or inspect fb status --json.") + + +def _status_command(args, state): + scope = args.scope + if scope == "console": + value = _module("console").status(state, "LOCAL") + else: + aggregate = _module("lifecycle").status(state) + value = aggregate.get(scope, {}) if scope else aggregate + if scope == "connection": + value = dict(value, desired_connection=aggregate.get("desired_connection", "unavailable")) + if args.json: + _show(value) + else: + _human_status(value, scope) + return _result_code(value) + + +def _doctor_command(args, state): + value = _module("runtime").doctor(state) + if args.json: + _show(value) + else: + print("Diagnostics: " + ("passed" if value.get("ok") else "needs attention")) + print("Debian 13 amd64: " + ("compatible" if value.get("compatible_debian_13_amd64") else "not verified compatible")) + print("Desktop: " + str(value.get("display", "unavailable"))) + for item in value.get("missing_commands", []): + print("Missing command: " + str(item)) + for item in value.get("issues", []): + print("Issue: " + str(item)) + print("This checks prerequisites; use fb status to inspect the current connection.") + return _result_code(value) + + +def _service_command(args, state): + action = args.service_action + lifecycle = _module("lifecycle") + if action == "credentials": + if not args.confirm: + result = {"ok": False, "error": "CONFIRMATION_REQUIRED", + "message": "This stops and removes the managed connection setup and saved credential. The profile is retained.", + "next_action": _scoped_action("fb service credentials forget --confirm")} + _show(result) + return 2 + result = lifecycle.forget_credentials(state) + elif action == "boot": + if args.boot_action == "enable" and (args.json or args.non_interactive): + boot = lifecycle.status(state).get("boot", {}) + if not boot.get("configured") or not boot.get("linger"): + return _interaction_required("fb service boot enable") + result = getattr(lifecycle, "boot_" + args.boot_action)(state) + else: + if action == "start" and (args.json or args.non_interactive): + config_path = state / "config.json" + if config_path.is_file() and load_config(state)["role"] == "node": + if not lifecycle.status(state).get("boot", {}).get("configured"): + return _interaction_required("fb service start") + result = getattr(lifecycle, "service_" + action)(state) + if args.json: + _show(result) + elif result.get("ok") is False: + print("Managed operation failed: " + str(result.get("error", result.get("message", "inspect fb status --json"))), file=sys.stderr) + else: + print("Managed " + ("startup policy" if action == "boot" else "service") + ": " + str(result.get("state", result.get("operation", "request completed")))) + if result.get("initial_configuration") is True: + print("Initial node service setup: automatic startup enabled.") + if action == "boot": + print("Current execution and saved connection material are retained.") + elif action == "credentials": + print("The profile and user service persistence are retained.") + if isinstance(result.get("runtime"), dict) and isinstance(result.get("boot"), dict): + _human_status(result) + elif result.get("message"): + print(str(result["message"])) + return _result_code(result) + + +def _help_command(args): + guide = _module("guide") + topic = "all" if args.command == "tutorial" else args.topic + if args.json: + value = guide.get_guide(topic) + _show(value) + return _result_code(value) + text = guide.render_text(topic) + if sys.stdout.isatty() and sys.stdin.isatty() and not args.non_interactive: + import pydoc + pydoc.pager(text) + else: + print(text) + return 0 + + +def _add_common_options(parser): + # SUPPRESS preserves an option supplied before a nested subcommand. + if not any("--state-dir" in item.option_strings for item in parser._actions): + parser.add_argument("--state-dir", default=argparse.SUPPRESS, help=argparse.SUPPRESS) + parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, + help="Emit one versioned JSON result; never request interactive input.") + parser.add_argument("--non-interactive", action="store_true", default=argparse.SUPPRESS, + help="Never request interactive input; report when a terminal action is needed.") + for item in parser._actions: + if isinstance(item, argparse._SubParsersAction): + for child in item.choices.values(): + _add_common_options(child) + +def build_parser(): + parser = argparse.ArgumentParser(prog="framebridge", + description="One command-line interface for human operators and authorized agents. fb is the short name for framebridge.", + epilog="Normal first use: fb connect. Advanced setup is optional. Read fb help for the guide and fb help compatibility for older commands.") + parser.add_argument("--state-dir", help="Profile directory; defaults to ~/.local/share/framebridge-expanded.") + parser.set_defaults(json=False, non_interactive=False) + sub = parser.add_subparsers(dest="command", metavar="COMMAND") + connect = sub.add_parser("connect", help="Connect or resume ChatGPT; normal first use needs no separate setup/start.") + connect.add_argument("connection", nargs="?", choices=("chatgpt",), default=None, help=argparse.SUPPRESS) + modes = connect.add_mutually_exclusive_group() + modes.add_argument("--foreground", action="store_true", help="Advanced temporary connection; this terminal remains open.") + modes.add_argument("--replace", action="store_true", help="Replace saved connection settings through hidden local prompts.") + disconnect = sub.add_parser("disconnect", help="Pause ChatGPT until the next connect, including after restart; keep local tools.") + disconnect.add_argument("disconnection", nargs="?", choices=("chatgpt",), default=None, help=argparse.SUPPRESS) + status = sub.add_parser("status", help="Observe local tools, ChatGPT connection and startup policy without changing them.") + status.add_argument("scope", nargs="?", choices=("connection", "runtime", "boot", "console")) + sub.add_parser("doctor", help="Diagnose prerequisites and problems; does not repair or connect.") + sub.add_parser("setup", help="Optional advanced role/profile setup; does not start services or connect.") + help_parser = sub.add_parser("help", help="Read the installed, versioned English guide.") + help_parser.add_argument("topic", nargs="?", default="start", help="Guide topic; use all for the complete reference.") + service = sub.add_parser("service", help="Manage current services separately from future startup policy.") + services = service.add_subparsers(dest="service_action", required=True) + services.add_parser("start", help="Start services; the first node start also prepares automatic startup.") + services.add_parser("stop", help="Stop this profile's managed stack now without changing boot policy.") + boot = services.add_parser("boot", help="Change future startup policy; retain current execution and saved material.") + boot_actions = boot.add_subparsers(dest="boot_action", required=True) + for action in ("enable", "disable"): + boot_actions.add_parser(action) + credentials = services.add_parser("credentials", help="Explicit destructive management of saved connection setup.") + forget = credentials.add_subparsers(dest="credential_action", required=True).add_parser("forget", + help="Stop/remove managed connection setup and saved credential; retain profile and user persistence.") + forget.add_argument("--confirm", action="store_true", help="Explicitly confirm this destructive operation.") + screen = sub.add_parser("screen-access", help="Explicit persistent read-only access to the real local framebuffer.") + ss = screen.add_subparsers(dest="screen_access_command", required=True) + ss.add_parser("status", help="Read the access policy without changing permissions.") + enable = ss.add_parser("enable", help="Administrator opt-in: sudo fb screen-access enable --user NAME.") + enable.add_argument("--user", required=True) + ss.add_parser("disable", help="Revoke only the exact owned unchanged framebuffer ACL.") + certificates = sub.add_parser("certificates", help="Manage profile-scoped private LAN TLS certificates.") + create = certificates.add_subparsers(dest="certificate_command", required=True).add_parser("create", + help="Create a private CA/server certificate without overwriting existing material.") + create.add_argument("--host", required=True, help="Certificate DNS name; localhost is included automatically.") + create.add_argument("--address", required=True, help="Broker IP included in the certificate and advertised by default.") + create.add_argument("--listen-host", help="Explicit bind IP; preserve the saved setting when omitted.") + create.add_argument("--advertise-host", help="Advertise the certificate DNS name instead of its IP.") + nodes = sub.add_parser("nodes", help="Manage node enrollment, approval and agent state.") + ns = nodes.add_subparsers(dest="node_command", required=True) + for name in ("list", "pending", "status", "poll"): + ns.add_parser(name) + approve = ns.add_parser("approve", help="Approve after checking the node identity.") + approve.add_argument("request_id") + approve.add_argument("--node-id", required=True, help="Expected UUID from the pending request.") + enroll = ns.add_parser("enroll", help="Submit enrollment; this does not start the agent.") + enroll.add_argument("--central") + enroll.add_argument("--name") + run = ns.add_parser("run", help="Advanced foreground node agent.") + run.add_argument("--once", action="store_true", help="Process at most one polling cycle.") + console = sub.add_parser("console", help="Open or reattach the shared text Bash/FBTERM work view.") + console.add_argument("--node", default="LOCAL", help="Local alias; remote interactive attachment requires explicit SSH.") + console.add_argument("--mode", choices=("auto", "gui", "tty"), default="auto") + console.add_argument("--inside", action="store_true", help=argparse.SUPPRESS) + console.add_argument("--status", action="store_true", help=argparse.SUPPRESS) + login_flags = console.add_mutually_exclusive_group() + login_flags.add_argument("--enable-login", action="store_const", const="enable", dest="login_action", help=argparse.SUPPRESS) + login_flags.add_argument("--disable-login", action="store_const", const="disable", dest="login_action", help=argparse.SUPPRESS) + login_flags.add_argument("--login-status", action="store_const", const="status", dest="login_action", help=argparse.SUPPRESS) + con_sub = console.add_subparsers(dest="console_command") + login = con_sub.add_parser("login", help="Optional work-view opening after authenticated tty1 login; not service startup.") + login.add_argument("console_login_action", choices=("enable", "status", "disable")) + tty = sub.add_parser("tty", help="Manage persistent Bash sessions; persistence is between calls, not reboots.") + ts = tty.add_subparsers(dest="tty_command", required=True, metavar="ACTION") + tty_service = ts.add_parser("service", help="Manage the terminal daemon.") + tss = tty_service.add_subparsers(dest="tty_service_action", required=True) + for name in ("start", "stop", "status"): + item = tss.add_parser(name) + item.add_argument("--node", default="LOCAL") + for name in ("start", "stop", "status", "list"): + item = ts.add_parser(name, help="List session state." if name == "list" else argparse.SUPPRESS) + item.add_argument("--node", default="LOCAL") + sudo = ts.add_parser("sudo", help="Per-session sudo eligibility; changing mode recreates Bash and loses shell variables.") + modes = sudo.add_subparsers(dest="sudo_mode", required=True) + for mode in ("on", "status", "off"): + item = modes.add_parser(mode) + item.add_argument("session_id") + item.add_argument("--node", default="LOCAL") + op = ts.add_parser("open", help="Create a real Bash session and return its ID.") + op.add_argument("--node", default="LOCAL") + op.add_argument("--program", choices=("bash",), default="bash") + op.add_argument("--rows", type=int, default=24) + op.add_argument("--cols", type=int, default=80) + for name in ("observe", "key", "type", "resize", "close"): + item = ts.add_parser(name, help={"observe":"Read terminal text, not a real screen capture.", + "key":"Send a named key such as ENTER or CTRL_C.", "type":"Type text without adding Enter.", + "resize":"Change terminal dimensions.", "close":"Close this session and its processes; not detachment."}[name]) + item.add_argument("session_id") + item.add_argument("--node", default="LOCAL") + if name == "key": + item.add_argument("key") + elif name == "type": + item.add_argument("text", nargs="?") + elif name == "resize": + item.add_argument("--rows", type=int, required=True) + item.add_argument("--cols", type=int, required=True) + # Compatibility parsers retain their actual historical scope. + sub.add_parser("start", help=argparse.SUPPRESS, description="Compatibility: starts only local runtime; use fb service start for the managed stack.") + sub.add_parser("stop", help=argparse.SUPPRESS, description="Compatibility: pauses this boot's managed worker and stops local runtime; startup policy is retained.") + sub.add_parser("tutorial", help=argparse.SUPPRESS, description="Compatibility: read the complete versioned guide; prefer fb help.") + connection = sub.add_parser("connection", help=argparse.SUPPRESS) + connection.add_subparsers(dest="connection_action", required=True).add_parser("status") + autostart = sub.add_parser("autostart", help=argparse.SUPPRESS, + description="Compatibility controls. WARNING: disable removes managed setup and saved connection material. Prefer fb service boot disable for policy only.") + auto = autostart.add_subparsers(dest="autostart_action", required=True) + for action in ("enable", "status", "disable"): + auto.add_parser(action) + # Do not advertise duplicate compatibility spellings in ordinary help. + for group in (sub, ts): + group._choices_actions[:] = [a for a in group._choices_actions if a.help is not argparse.SUPPRESS] + _add_common_options(parser) + return parser +def main(argv=None): + global _OUTPUT + parser = build_parser() + args = parser.parse_args(argv) + previous = _OUTPUT + _OUTPUT = {"json": args.json, "command": args.command, + "explicit_state": str(resolve_state(args.state_dir)) if args.command != "screen-access" and (args.state_dir or os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") or os.environ.get("FRAMEBRIDGE_STATE_DIR")) else None} + try: + return _dispatch(parser, args) + finally: + _OUTPUT = previous + + +def _dispatch(parser, args): + state = None if args.command == "screen-access" else resolve_state(args.state_dir) + if args.command is None: + if args.json or args.non_interactive or not sys.stdin.isatty(): + if args.json: + return _help_command(argparse.Namespace(command="help", topic="start", json=True, non_interactive=True)) + parser.print_help() + return 0 + print("FrameBridge\n1. Connect\n2. Status\n3. Doctor\n4. Open console\n5. Help\n6. Advanced setup\n0. Exit") + try: + choice = input("Choose an action: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nCancelled.", file=sys.stderr) + return 130 + action = {"1":["connect"], "2":["status"], "3":["doctor"], "4":["console"], + "5":["help"], "6":["setup"]}.get(choice) + if choice == "0": + return 0 + if action is None: + print("Choose one of the listed actions.", file=sys.stderr) + return 2 + return main(["--state-dir", str(state)] + action) + try: + if args.command == "screen-access": + value = _module("screen_access").command(args) + _show(value) + return _result_code(value) + if args.command in {"help", "tutorial"}: + return _help_command(args) + if args.command == "setup": + if args.json or args.non_interactive or not sys.stdin.isatty(): + return _interaction_required("fb setup") + return setup(state) + if args.command == "connect": + if args.json or args.non_interactive: + config_path = state / "config.json" + if config_path.is_file() and load_config(state)["role"] == "node": + raise RuntimeError("A node profile does not publish an MCP server. Connect ChatGPT from the central profile.") + manifest = state / "autostart/installation.json" + if args.foreground or args.replace or not manifest.is_file() or not _saved_noninteractive_ready(state): + return _interaction_required("fb connect" + (" --foreground" if args.foreground else (" --replace" if args.replace else ""))) + if args.json: + import contextlib + with contextlib.redirect_stdout(sys.stderr): + code = connect_chatgpt(state, foreground=False, replace=False) + value = _module("lifecycle").status(state) + if code: + value = dict(value, ok=False, error="CONNECTION_NOT_READY") + _show(value) + return code + return connect_chatgpt(state, foreground=args.foreground, replace=args.replace) + if args.command == "status": + return _status_command(args, state) + if args.command == "doctor": + return _doctor_command(args, state) + if args.command == "service": + return _service_command(args, state) + if args.command == "connection": + value = _module("connection_background").status(state) + _show(value) + return _result_code(value) + if args.command == "disconnect": + module = "connection_background" if args.disconnection == "chatgpt" else "lifecycle" + result = _module(module).disconnect(state) + if args.json or args.disconnection == "chatgpt": + _show(result) + elif result.get("ok") is False: + print("Connection pause failed: " + str(result.get("error", "inspect fb status connection --json")), file=sys.stderr) + else: + print("ChatGPT connection paused until fb connect. Local tools and saved connection material are retained.") + return _result_code(result) + if args.command == "autostart": + autostart = _module("autostart") + if args.autostart_action == "enable" and (args.json or args.non_interactive): + value = autostart.status(state) + if not value.get("configured") or not value.get("linger"): + return _interaction_required("fb service boot enable") + result = getattr(autostart, args.autostart_action)(state) + if args.json or args.autostart_action == "status": + _show(result) + return _result_code(result) + if args.autostart_action == "enable": + if result.get("state") == "cancelled": + print("Automatic startup was not enabled.") + return 130 + return _show_autostart_enabled(result) + print("Automatic startup disabled." if result.get("state") == "disabled" else "No managed automatic startup configuration was found.") + try: + current = autostart.status(state) + except (OSError, ValueError, RuntimeError): + current = {} + service = current.get("service_state", "unavailable") + if service not in {"active", "activating", "deactivating", "inactive", "failed", "reloading", "maintenance", "refreshing"}: + service = "unavailable" + print("Service: " + service) + if result.get("desktop_setting_restored") is False: + print("Current profile settings were preserved; no previous desktop setting was restored.") + if current.get("linger") is True: + print("User service persistence remains enabled.") + return _result_code(result) + if args.command == "certificates": + value = _module("tls").create_certificates(state, args.host, args.address, listen_host=args.listen_host, advertise_host=args.advertise_host) + _show(value) + return _result_code(value) + if args.command == "nodes": + if args.json and args.node_command == "run" and not args.once: + return _interaction_required("fb nodes run") + return nodes_command(args, state) + if args.command == "console": + action = getattr(args, "console_login_action", None) or args.login_action + if getattr(args, "console_login_action", None) and args.login_action: + raise ValueError("Choose one console login command form.") + if args.status and action: + raise ValueError("Choose console status or a login action, not both.") + if action: + if args.node != "LOCAL": + raise ValueError("Console login setup is local; run it on the requested node.") + result = getattr(_module("console_startup"), action)(state) + _show(result) + return _result_code(result) + console = _module("console") + if args.status: + _show(console.status(state, args.node)) + return 0 + if args.json or args.non_interactive: + return _interaction_required("fb console") + return console.run(state, args.node, inside=args.inside, mode=args.mode) + if args.command == "tty": + if args.tty_command == "type" and args.text is None and (args.json or args.non_interactive): + return _interaction_required("fb tty type SESSION_ID TEXT") + return tty_command(args, state) + if args.command == "stop": + _module("autostart").stop_for_current_boot(state) + value = getattr(_module("runtime"), args.command)(state) + _show(value) + return _result_code(value) + except (EOFError, KeyboardInterrupt): + if args.json: + _show({"ok":False, "error":"CANCELLED"}) + else: + print("\nCancelled.", file=sys.stderr) + return 130 + except (OSError, ValueError, RuntimeError) as exc: + if args.json: + _show({"ok":False, "error":"COMMAND_FAILED", "message":str(exc)}) + else: + print("Error: " + str(exc), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/config.py b/source/expanded/framebridge_expanded/config.py new file mode 100644 index 0000000..5764f6f --- /dev/null +++ b/source/expanded/framebridge_expanded/config.py @@ -0,0 +1,99 @@ +"""Validated, private profiles for FrameBridge Expanded.""" +from __future__ import annotations +import json +import os +import re +import socket +import stat +import tempfile +import uuid +from pathlib import Path +from urllib.parse import urlsplit + +SCHEMA_VERSION = 1 +ROLES = ("standalone", "central", "node") +DEFAULT_STATE = Path(os.environ.get("XDG_DATA_HOME", str(Path.home() / ".local/share"))) / "framebridge-expanded" + +def state_path(state=None): + return Path(state or os.environ.get("FRAMEBRIDGE_EXPANDED_STATE", DEFAULT_STATE)).expanduser().absolute() + +def private_dir(path): + path = Path(path) + if path.is_symlink(): + raise ValueError("Profile directories must not be symbolic links.") + path.mkdir(mode=0o700, parents=True, exist_ok=True) + mode = path.stat() + if mode.st_uid != os.getuid() or stat.S_IMODE(mode.st_mode) & 0o077: + raise ValueError("Profile directories must belong to the current user and have mode 0700.") + return path + +def default_config(role="standalone", name=None): + if role not in ROLES: + raise ValueError("Choose standalone, central, or node.") + name = name or re.sub(r"[^a-z0-9-]", "-", socket.gethostname().lower()).strip("-") or "computer" + return { + "schema_version": SCHEMA_VERSION, "role": role, "name": name, + "node_id": str(uuid.uuid4()), + "ports": {"mcp": 20865, "hub": 20877, "broker": 20878}, + "headless": "auto", "broker_listen_host": "127.0.0.1", + "broker_url": "", "tls_ca": "", "tls_cert": "", "tls_key": "", + "account_label": "", "tunnel_id": "" + } + +def validate(config): + if not isinstance(config, dict) or config.get("schema_version") != SCHEMA_VERSION: + raise ValueError("Unsupported profile format.") + if config.get("role") not in ROLES: + raise ValueError("Choose standalone, central, or node.") + if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,62}", config.get("name", "")): + raise ValueError("The node name must use 1-63 letters, numbers, hyphens, or underscores.") + uuid.UUID(config["node_id"]) + ports = config.get("ports", {}) + if set(ports) != {"mcp", "hub", "broker"} or any(type(p) is not int or not 1024 <= p <= 65535 for p in ports.values()): + raise ValueError("Ports must be distinct integers between 1024 and 65535.") + if len(set(ports.values())) != len(ports): + raise ValueError("Choose a different port for each service.") + if config.get("headless", "auto") not in ("auto", "yes", "no", True, False): + raise ValueError("Headless mode must be auto, yes, or no.") + for key in ("api_key", "runtime_key", "password", "token", "secret"): + if key in config: + raise ValueError("Runtime credentials must not be stored in profile configuration.") + broker_url = config.get("broker_url", "") + if broker_url: + url = urlsplit(broker_url) + if url.username or url.password or url.query or url.fragment or url.scheme not in ("https", "http") or not url.hostname: + raise ValueError("Use a broker URL without credentials, query parameters, or fragments.") + if url.scheme == "http" and url.hostname not in ("localhost", "127.0.0.1", "::1"): + raise ValueError("A remote broker requires verified HTTPS.") + return config + +def load_config(state=None): + state = state_path(state) + path = state / "config.json" + if not path.is_file(): + raise ValueError("This profile is not configured. Run framebridge setup first.") + if path.is_symlink(): + raise ValueError("Profile configuration must not be a symbolic link.") + st = path.stat() + if st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) & 0o077: + raise ValueError("Profile configuration must belong to the current user and have mode 0600.") + return validate(json.loads(path.read_text(encoding="utf-8"))) + +def save_config(state, config): + validate(config) + state = private_dir(state_path(state)) + target = state / "config.json" + if target.is_symlink(): + raise ValueError("Profile configuration must not be a symbolic link.") + fd, temporary = tempfile.mkstemp(prefix=".config-", dir=state) + try: + with os.fdopen(fd, "w", encoding="utf-8") as stream: + json.dump(config, stream, indent=2, sort_keys=True) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, target) + finally: + if os.path.exists(temporary): + os.unlink(temporary) + return config diff --git a/source/expanded/framebridge_expanded/connection_background.py b/source/expanded/framebridge_expanded/connection_background.py new file mode 100644 index 0000000..7c4a498 --- /dev/null +++ b/source/expanded/framebridge_expanded/connection_background.py @@ -0,0 +1,358 @@ +"""Profile-scoped detached ChatGPT supervision without a persistent credential.""" +from __future__ import annotations +import fcntl +import json +import os +import resource +from pathlib import Path +import signal +import stat +import subprocess +import sys +import tempfile +import threading +import time +from .config import private_dir, state_path + +_BOOTSTRAP = "import sys;sys.path.insert(0,sys.argv[1]);from framebridge_expanded.connection_background import worker_main;worker_main(sys.argv[2],int(sys.argv[3]))" +_PACKAGE_PARENT = str(Path(__file__).resolve().parents[1]) +_STATES = {"starting", "checking", "access", "ready", "retrying", "rejected", "unverified", "error", "stopped", "closed"} +_MESSAGES = { + "starting": "[1/3] Starting the background tunnel client.", + "checking": "[2/3] Verifying the local MCP service.", + "access": "[3/3] Verifying tunnel access and authenticated polling.", + "ready": "[OK] Secure tunnel connected; local MCP service verified.", + "retrying": "[RETRYING] Waiting for current verified readiness; check the private connection log.", + "rejected": "[ERROR] Tunnel access was rejected; check the runtime key and tunnel permissions.", + "unverified": "[NOT READY] The connection is not verified yet; it continues retrying in the background.", + "error": "[ERROR] Background tunnel stopped or could not be verified; check the private connection log.", + "stopped": "[STOPPED] No background ChatGPT connection is running.", + "closed": "[CLOSED] The background tunnel client stopped.", +} + + + +def _directory(state, create=True): + root = state_path(state) + if create: + private_dir(root) + return private_dir(root / "connection") + path = root / "connection" + if not path.exists() and not path.is_symlink(): + return None + for item in (root, path): + st = item.lstat() + if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) & 0o077: + raise RuntimeError("The connection directory is not private and owned by this user.") + return path + + +def _checked_fd(path, flags, create=False): + fd = os.open(path, flags | os.O_NOFOLLOW | os.O_NONBLOCK | (os.O_CREAT if create else 0), 0o600) + try: + st = os.fstat(fd) + if not stat.S_ISREG(st.st_mode) or st.st_nlink != 1 or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o600: + raise RuntimeError("The connection state file is not a private regular file.") + return fd + except BaseException: + os.close(fd) + raise + + +def _read(path): + try: + fd = _checked_fd(path, os.O_RDONLY) + except FileNotFoundError: + return {} + try: + raw = os.read(fd, 32769) + if len(raw) > 32768: + raise RuntimeError("The connection status is too large.") + data = json.loads(raw) + if not isinstance(data, dict): + raise ValueError() + return data + except (ValueError, UnicodeError): + raise RuntimeError("The connection status cannot be read safely.") from None + finally: + os.close(fd) + + +def _write(path, data): + if path.exists() or path.is_symlink(): + fd = _checked_fd(path, os.O_RDONLY) + os.close(fd) + fd, name = tempfile.mkstemp(prefix=".status-", dir=path.parent) + try: + os.fchmod(fd, 0o600) + with os.fdopen(fd, "w", encoding="utf-8") as stream: + json.dump(data, stream, sort_keys=True) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + os.replace(name, path) + finally: + try: + os.unlink(name) + except FileNotFoundError: + pass + + +def _supervisor_command(state, lock_fd): + return [sys.executable, "-c", _BOOTSTRAP, _PACKAGE_PARENT, str(state_path(state)), str(lock_fd)] + + +def _identity(pid, state, lock_fd): + proc = Path("/proc") / str(pid) + fields = (proc / "stat").read_text().split(") ", 1)[1].split() + return {"pid": pid, "uid": proc.stat().st_uid, "start_ticks": int(fields[19]), + "boot_id": Path("/proc/sys/kernel/random/boot_id").read_text().strip(), + "session_id": int(fields[3]), "process_group": int(fields[2]), + "state_dir": str(state_path(state)), "lock_fd": lock_fd} + + +def _valid_identity(record, state): + try: + pid = record["pid"] + lock_fd = record["lock_fd"] + if type(pid) is not int or pid < 2 or type(lock_fd) is not int or lock_fd < 3: + return False + current = _identity(pid, state, lock_fd) + if any(current[k] != record.get(k) for k in current): + return False + if current["uid"] != os.getuid() or current["session_id"] != pid or current["process_group"] != pid: + return False + proc = Path("/proc") / str(pid) + if (proc / "stat").read_text().split(") ", 1)[1].split()[0] == "Z": + return False + if os.path.realpath(proc / "exe") != os.path.realpath(sys.executable): + return False + argv = (proc / "cmdline").read_bytes().split(b"\0")[:-1] + if argv != [os.fsencode(x) for x in _supervisor_command(state, lock_fd)]: + return False + lock = _directory(state, False) / "owner.lock" + return os.stat(proc / "fd" / str(lock_fd)).st_ino == lock.stat().st_ino and os.stat(proc / "fd" / str(lock_fd)).st_dev == lock.stat().st_dev + except (OSError, ValueError, KeyError, TypeError, AttributeError): + return False + + +def status(state): + directory = _directory(state, False) + record = _read(directory / "status.json") if directory else {} + alive = bool(record) and _valid_identity(record, state) + phase = record.get("state", "stopped") + if not isinstance(phase, str) or phase not in _STATES: + phase = "error" + if not alive and phase not in {"error", "closed", "stopped"}: + phase = "stopped" + checked = record.get("verified_at") + verified = alive and phase == "ready" and type(checked) in (int, float) and 0 <= time.time() - checked <= 15 + if alive and phase == "ready" and not verified: + phase = "retrying" + return {"ok": True, "alive": alive, "verified": verified, "state": phase, + "message": _MESSAGES[phase], + "pid": record.get("pid") if alive else None, + "log": str(state_path(state) / "logs/chatgpt/connection.log")} + + +def disconnect(state, *, timeout=16): + directory = _directory(state, False) + record = _read(directory / "status.json") if directory else {} + if not record or not _valid_identity(record, state): + return {"ok": True, "alive": False, "state": "stopped", "message": "No owned background tunnel is running."} + if not hasattr(os, "pidfd_open") or not hasattr(signal, "pidfd_send_signal"): + return {"ok": False, "alive": True, "state": "error", "message": "Safe process signaling is unavailable; the connection was left unchanged."} + try: + fd = os.pidfd_open(record["pid"]) + except ProcessLookupError: + return {"ok": True, "alive": False, "state": "stopped"} + try: + if not _valid_identity(record, state): + return {"ok": False, "alive": False, "state": "error", "message": "Connection identity changed; no signal was sent."} + try: + signal.pidfd_send_signal(fd, signal.SIGINT) + except ProcessLookupError: + pass + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if not _valid_identity(record, state): + return {"ok": True, "alive": False, "state": "stopped", "message": "Background ChatGPT connection stopped."} + time.sleep(0.1) + return {"ok": False, "alive": True, "state": "error", "message": "Connection cleanup is still running; local services were left unchanged."} + finally: + os.close(fd) + + +class _StatusOutput: + """Progress destination owned by the worker, with no parent pipe or terminal.""" + def __init__(self, path, identity): + self.path = path + self.record = dict(identity) + self.pending = "" + self.change("starting", "[1/3] Starting the background tunnel client.") + + def isatty(self): + return False + + def flush(self): + pass + + def change(self, state, message): + self.record.update(state=state, message=message, updated_at=time.time()) + _write(self.path, self.record) + + def observe(self, state): + self.record["checked_at"] = time.time() + self.record["verified_at"] = time.time() if state == "ready" else None + _write(self.path, self.record) + + def write(self, text): + self.pending += text + while "\n" in self.pending: + line, self.pending = self.pending.split("\n", 1) + phase = next((phase for token, phase in [("[1/3]", "starting"), ("[2/3]", "checking"), ("[3/3]", "access"), ("[OK]", "ready"), ("[RETRYING]", "retrying"), ("[NOT READY]", "unverified"), ("[ERROR]", "error"), ("[STOPPED]", "stopped"), ("[CLOSED]", "closed")] if line.startswith(token)), None) + if phase: + self.change(phase, line) + if len(self.pending) > 4096: + self.pending = "" + return len(text) + + +def worker_main(state, lock_fd): + """Read one bounded anonymous-pipe payload; the credential is never a file.""" + os.umask(0o077) + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + payload = credential = None + sink = None + rc = 1 + try: + directory = _directory(state) + held = os.fstat(lock_fd) + owned = (directory / "owner.lock").stat() + if (held.st_dev, held.st_ino) != (owned.st_dev, owned.st_ino): + return + fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + raw = sys.stdin.buffer.read(65537) + if len(raw) > 65536: + return + payload = json.loads(raw) + raw = None + credential = payload.pop("credential") + command = payload["command"] + if not isinstance(credential, str) or not credential or any(c.isspace() for c in credential) or len(credential) > 16384: + return + if not isinstance(command, list) or not command or any(not isinstance(v, str) or "\0" in v for v in command): + return + sink = _StatusOutput(directory / "status.json", _identity(os.getpid(), state, lock_fd)) + from .tunnel import run_foreground + rc = run_foreground(command, credential, state=state, output=sink, observe=sink.observe) + except BaseException: + rc = 1 + finally: + credential = payload = None + if sink is not None: + try: + sink.change("stopped" if rc in (0, 130) else "error", "[STOPPED] Background ChatGPT connection stopped." if rc in (0, 130) else "[ERROR] Background tunnel stopped; check the private connection log.") + except Exception: + pass + try: + os.close(lock_fd) + except OSError: + pass + + +def start(command, credential, state, expected_tunnel=None, expected_mcp=None, *, output=None, wait_timeout=95): + """Detach the owned supervisor, then wait for verified readiness or a clear result.""" + output = output or sys.stdout + directory = _directory(state) + lock_fd = _checked_fd(directory / "owner.lock", os.O_RDWR, create=True) + child = None + try: + try: + fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + current = status(state) + output.write("A ChatGPT connection already owns this profile. Use framebridge connection status.\n") + return 0 if current["verified"] else 1 + # No earlier status may be mistaken for this new supervisor. + old = directory / "status.json" + if old.exists() or old.is_symlink(): + check = _checked_fd(old, os.O_RDONLY) + os.close(check) + old.unlink() + env = dict(os.environ) + from .tunnel import KEY_ENV, _RUNTIME_PREFIXES, _RUNTIME_NAMES + for name in list(env): + if name.startswith(_RUNTIME_PREFIXES) or name in _RUNTIME_NAMES: + env.pop(name, None) + child = subprocess.Popen(_supervisor_command(state, lock_fd), stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + pass_fds=(lock_fd,), close_fds=True, start_new_session=True, env=env) + threading.Thread(target=child.wait, daemon=True, name="framebridge-tunnel-reaper").start() + try: + payload = json.dumps({"credential": credential, "command": list(command)}).encode() + if len(payload) > 65536: + raise ValueError("The connection input is too large.") + child.stdin.write(payload) + child.stdin.close() + payload = None + except (OSError, ValueError): + child.stdin.close() + output.write("[ERROR] The background connection could not receive its private input.\n") + return 1 + finally: + credential = None + # Closing, rather than LOCK_UN, preserves the child's inherited flock. + os.close(lock_fd) + lock_fd = None + deadline = time.monotonic() + wait_timeout + last = None + while time.monotonic() < deadline: + current = status(state) + if current["alive"] and current["message"] != last: + output.write(current["message"] + "\n") + output.flush() + last = current["message"] + if current["verified"]: + output.write("The connection is running in the background; this terminal can be closed.\n") + output.write("Use framebridge connection status or framebridge disconnect chatgpt.\n") + output.write("Select this tunnel in ChatGPT. A successful tool call verifies that connection.\n") + return 0 + if child.poll() is not None: + output.write("[ERROR] The background tunnel stopped before verified readiness. Check the private connection log.\n") + return 1 + time.sleep(0.2) + output.write("[NOT READY] Verification is still pending. The connection continues retrying in the background; use framebridge connection status.\n") + return 1 + except KeyboardInterrupt: + if child is not None: + result = disconnect(state) + # The Popen child remains ours even before it has published status. + if child.poll() is None: + try: + child.send_signal(signal.SIGINT) + child.wait(timeout=16) + except (OSError, subprocess.TimeoutExpired): + result = {"ok": False} + if not result["ok"]: + output.write("[ERROR] Check framebridge connection status before reconnecting.\n") + return 130 + finally: + credential = None + if lock_fd is not None: + os.close(lock_fd) + + +def run_foreground(command, credential, state, *, output=None): + """Retain explicit diagnostic mode, exclusive with detached connections.""" + directory = _directory(state) + fd = _checked_fd(directory / "owner.lock", os.O_RDWR, create=True) + try: + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise RuntimeError("A ChatGPT connection already owns this profile. Disconnect it before using foreground mode.") from None + from .tunnel import run_foreground as run + return run(command, credential, state=state, output=output) + finally: + os.close(fd) diff --git a/source/expanded/framebridge_expanded/connection_monitor.py b/source/expanded/framebridge_expanded/connection_monitor.py new file mode 100644 index 0000000..77c9f1c --- /dev/null +++ b/source/expanded/framebridge_expanded/connection_monitor.py @@ -0,0 +1,358 @@ +"""Private connection logs and evidence-based terminal status. + +This module monitors the packaged tunnel client. A ready tunnel is not evidence +that the intended ChatGPT account has completed consent or made a tool call. +""" +from __future__ import annotations + +import datetime +import fcntl +import http.client +import ipaddress +import json +import math +import os +from pathlib import Path +import re +import selectors +import stat +import threading +import time +import uuid +import unicodedata +from urllib.parse import urlsplit + +MAX_LINE = 64 * 1024 +MAX_LOG = 2 * 1024 * 1024 +LOG_COPIES = 3 +POLL_FRESH_SECONDS = 75 +_ANSI = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))") + + +class ConnectionMonitorError(RuntimeError): + """A safe public error; details belong only in sanitized logs.""" + + +def _private_directory(path): + path = Path(path).absolute() + for part in [path, *path.parents]: + if part.is_symlink(): + raise ConnectionMonitorError("The private connection directory is unsafe.") + try: + path.mkdir(mode=0o700) + except FileExistsError: + pass + info = path.lstat() + if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() or info.st_mode & 0o077: + raise ConnectionMonitorError("The private connection directory is unsafe.") + return path + + +def redact(text, secret): + """Normalize terminal controls before redacting and persisting a record.""" + text = _ANSI.sub("", str(text)) + text = "".join(c for c in text if c in "\n\t" or not unicodedata.category(c).startswith("C")) + if secret: + text = text.replace(secret, "[REDACTED]") + # A child may exit after emitting only a credential prefix. + tail = text.rstrip("\r\n") + suffix = text[len(tail):] + for length in range(min(len(secret) - 1, len(tail)), 7, -1): + if tail.endswith(secret[:length]): + text = tail[:-length] + "[REDACTED]" + suffix + break + text = re.sub(r"\bsk-[A-Za-z0-9_-]{8,}", "[REDACTED]", text) + # Preserve a useful proxy/server location without credential-bearing userinfo. + text = re.sub(r"(?i)(\b[a-z][a-z0-9+.-]*://)[^/\s?#]*@", r"\1[REDACTED]@", text) + # Conservative whole-tail redaction prevents partial Cookie/password leaks. + text = re.sub(r"(?im)(\b(?:authorization|proxy-authorization|api[_-]?key|access[_-]?token|" + r"refresh[_-]?token|password|passwd|cookie|set-cookie)\b[\"']?\s*[:=]\s*).*$", + r"\1[REDACTED]", text) + return text + + +class PrivateLog: + """A bounded rotating log, protected against symlinks and shared files.""" + def __init__(self, state, secret, *, max_bytes=MAX_LOG): + root = _private_directory(state) + root = _private_directory(root / "logs") + self.directory = _private_directory(root / "chatgpt") + self.path = self.directory / "connection.log" + self.secret = secret + self.max_bytes = max_bytes + self.directory_fd = os.open(self.directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) + try: + self.lock_fd = self._open("connection.lock", os.O_RDWR | os.O_CREAT) + except Exception: + os.close(self.directory_fd) + raise + self.mutex = threading.Lock() + + def _open(self, name, flags): + fd = os.open(name, flags | os.O_NOFOLLOW | os.O_CLOEXEC | os.O_NONBLOCK, 0o600, dir_fd=self.directory_fd) + info = os.fstat(fd) + if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() + or info.st_nlink != 1 or info.st_mode & 0o077): + os.close(fd) + raise ConnectionMonitorError("The private connection log is unsafe.") + return fd + + def _validate_existing(self, name): + try: + fd = self._open(name, os.O_RDONLY) + except FileNotFoundError: + return False + os.close(fd) + return True + + def write(self, text): + clean = redact(str(text), self.secret) + # All callers provide a complete bounded logical record. + if len(clean.encode("utf-8")) > MAX_LINE * 4: + clean = "[Oversized log record omitted]" + stamp = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds") + data = (stamp + " " + clean.rstrip("\r\n") + "\n").encode("utf-8", errors="replace") + with self.mutex: + deadline = time.monotonic() + 1.5 + while True: + try: + fcntl.flock(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + break + except BlockingIOError: + if time.monotonic() >= deadline: + raise ConnectionMonitorError("The private connection log is busy.") from None + time.sleep(0.02) + try: + fd = self._open("connection.log", os.O_WRONLY | os.O_APPEND | os.O_CREAT) + try: + size = os.fstat(fd).st_size + finally: + os.close(fd) + if size + len(data) > self.max_bytes: + existing = {i: self._validate_existing("connection.log" + ("." + str(i) if i else "")) + for i in range(LOG_COPIES)} + if existing[LOG_COPIES - 1]: + os.unlink("connection.log." + str(LOG_COPIES - 1), dir_fd=self.directory_fd) + for i in range(LOG_COPIES - 2, -1, -1): + if existing[i]: + os.rename("connection.log" + ("." + str(i) if i else ""), + "connection.log." + str(i + 1), + src_dir_fd=self.directory_fd, dst_dir_fd=self.directory_fd) + fd = self._open("connection.log", os.O_WRONLY | os.O_APPEND | os.O_CREAT) + try: + view = memoryview(data) + while view: + count = os.write(fd, view) + if count <= 0: + raise OSError("Short private log write") + view = view[count:] + finally: + os.close(fd) + finally: + fcntl.flock(self.lock_fd, fcntl.LOCK_UN) + + def close(self): + self.secret = None + for name in ("lock_fd", "directory_fd"): + fd = getattr(self, name, None) + if fd is not None: + os.close(fd) + setattr(self, name, None) + + +class Attempt: + def __init__(self, state): + root = _private_directory(state) + self.root = _private_directory(root / "connection-runtime") + self.directory = _private_directory(self.root / uuid.uuid4().hex) + self.health_file = self.directory / "health.url" + + def close(self): + # Remove only this attempt's own known file, never a symlink or unknown data. + try: + info = self.health_file.lstat() + if stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid() and info.st_nlink == 1: + self.health_file.unlink() + except FileNotFoundError: + pass + try: + self.directory.rmdir() + except OSError: + pass + + +def health_endpoint(path): + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1 + or info.st_mode & 0o022 or not 0 < info.st_size <= 512): + raise ConnectionMonitorError("The tunnel health endpoint is unsafe.") + text = os.read(fd, 513).decode("ascii").strip() + finally: + os.close(fd) + value = urlsplit(text) + try: + address = ipaddress.ip_address(value.hostname or "") + port = value.port + except ValueError: + raise ConnectionMonitorError("The tunnel health endpoint is unsafe.") from None + if (value.scheme != "http" or not address.is_loopback or not port or "%" in text + or value.username or value.password or value.path not in ("", "/") + or value.query or value.fragment): + raise ConnectionMonitorError("The tunnel health endpoint is unsafe.") + return str(address), port + + +def _get(endpoint, path, limit): + # Direct literal-loopback HTTP: no DNS, inherited proxy, or redirects. + connection = http.client.HTTPConnection(*endpoint, timeout=0.6) + try: + connection.request("GET", path, headers={"Connection": "close"}) + response = connection.getresponse() + body = response.read(limit + 1) + if len(body) > limit: + raise ConnectionMonitorError("The tunnel health response is too large.") + return response.status, body + finally: + connection.close() + + +def probe(health_file, expected_tunnel, expected_mcp, started, *, now=None): + """Return a public state and private detail from the pinned client contract.""" + now = time.time() if now is None else now + try: + endpoint = health_endpoint(health_file) + except FileNotFoundError: + return "starting", "Waiting for the tunnel health endpoint." + except (OSError, ValueError, ConnectionMonitorError): + return "checking", "The tunnel health endpoint is not available or valid." + try: + code, body = _get(endpoint, "/readyz", 8192) + message = body.decode("utf-8", errors="replace").strip() + if code != 200 or message != "ready": + return "checking", "MCP readiness is pending: " + message + code, body = _get(endpoint, "/api/status", 65536) + if code != 200: + return "checking", "Tunnel status is not available." + status = json.loads(body) + if not isinstance(status, dict): + return "checking", "Tunnel status has an unexpected format." + if (status.get("control_plane_tunnel_id") != expected_tunnel + or status.get("mcp_server_url") != expected_mcp): + return "checking", "Tunnel status does not match this profile." + created = datetime.datetime.fromisoformat(status["started_at"].replace("Z", "+00:00")).timestamp() + if not started - 5 <= created <= now + 5: + return "checking", "Tunnel status belongs to a different startup." + metadata = status.get("tunnel_metadata") or {} + if not isinstance(metadata, dict): + return "checking", "Tunnel metadata has an unexpected format." + if status.get("tunnel_metadata_error"): + detail = str(status["tunnel_metadata_error"]) + if re.search(r"\b(?:401|403|unauthorized|forbidden)\b", detail, re.I): + return "rejected", "Tunnel access was rejected: " + detail + return "access", "Tunnel metadata request failed: " + detail + if metadata.get("ID") != expected_tunnel: + return "access", "Waiting for authenticated tunnel metadata." + code, body = _get(endpoint, "/metrics", 256 * 1024) + if code != 200: + return "access", "Waiting for authenticated polling evidence." + values = [] + for line in body.decode("ascii", errors="replace").splitlines(): + if line.startswith("#"): + continue + match = re.fullmatch(r"commands_poll_last_successful_timestamp_seconds(?:\{[^\n]*\})?\s+([0-9.eE+-]+)(?:\s+\d+)?", line) + if match: + values.append(float(match.group(1))) + if not values or not all(math.isfinite(v) for v in values): + return "access", "Waiting for authenticated polling evidence." + recent = max(values) + if recent < started - 5 or recent > now + 5 or now - recent > POLL_FRESH_SECONDS: + return "access", "A successful authenticated poll has not been confirmed recently." + return "ready", "Local MCP initialization, tunnel identity, metadata and recent authenticated polling verified." + except (OSError, ValueError, KeyError, TypeError, http.client.HTTPException, ConnectionMonitorError): + return "checking", "The tunnel health check is temporarily unavailable." + + +class Progress: + def __init__(self, output): + self.output = output + self.tty = bool(getattr(output, "isatty", lambda: False)()) + self.state = None + self.message = "" + self.width = 0 + self.index = 0 + + def change(self, state, message): + if self.state == state and self.message == message: + return + self.clear() + self.state, self.message = state, message + self.output.write(message + "\n") + self.output.flush() + + def clear(self): + if self.tty and self.width: + self.output.write("\r" + " " * self.width + "\r") + self.width = 0 + + def tick(self): + if self.tty and self.state not in {"ready", "closed", "error", "rejected", "unverified"}: + text = "Connecting " + "|/-\\"[self.index % 4] + " Press Ctrl+C to cancel." + self.index += 1 + self.width = max(self.width, len(text)) + self.output.write("\r" + text.ljust(self.width)) + self.output.flush() + + +def capture_output(stream, log, errors, stop): + """Drain binary output without ever forwarding it to the terminal.""" + buffer = bytearray() + dropping = False + try: + fd = stream.fileno() + selector = selectors.DefaultSelector() + selector.register(fd, selectors.EVENT_READ) + while True: + if not selector.select(timeout=0.1): + if stop.is_set(): + break + continue + chunk = os.read(fd, 4096) + if not chunk: + break + for part in chunk.splitlines(keepends=True): + ending = part.endswith(b"\n") or part.endswith(b"\r") + if not dropping: + if len(buffer) + len(part) > MAX_LINE: + buffer.clear() + dropping = True + else: + buffer.extend(part) + if ending: + if dropping: + log.write("[Oversized client output line omitted]") + else: + log.write(buffer.decode("utf-8", errors="replace")) + buffer.clear() + dropping = False + if buffer and not dropping: + log.write(buffer.decode("utf-8", errors="replace")) + elif dropping: + log.write("[Oversized client output line omitted]") + except Exception: + errors.append("Connection output could not be written to its private log.") + finally: + if "selector" in locals(): + selector.close() + if stop.is_set(): + # If the main thread timed out waiting for draining, this thread + # remains the owner of its live stream/log until it finishes. + try: + stream.close() + except Exception: + errors.append("Connection output stream cleanup failed.") + try: + log.close() + except Exception: + errors.append("Private connection log cleanup failed.") diff --git a/source/expanded/framebridge_expanded/console.py b/source/expanded/framebridge_expanded/console.py new file mode 100644 index 0000000..d8c93c5 --- /dev/null +++ b/source/expanded/framebridge_expanded/console.py @@ -0,0 +1,256 @@ +"""Local authenticated Bash/FBTERM console, shared across desktop and TTY clients.""" +from __future__ import annotations +import argparse +import hashlib +import os +from pathlib import Path +import pwd +import re +import shutil +import socket +import stat +import subprocess +import sys + +from .config import load_config, private_dir, state_path + +_NODE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,79}\Z") + +def _node(state, value): + value = value or "LOCAL" + if type(value) is not str or _NODE.fullmatch(value) is None: + raise ValueError("CONSOLE_NODE_INVALID") + host = socket.gethostname() + names = {"local", "localhost", host.lower(), host.split(".")[0].lower()} + try: + config = load_config(state) + except (ValueError, OSError): + config = {} + name = config.get("name") + if isinstance(name, str): + names.add(name.lower()) + if value.lower() not in names: + raise RuntimeError( + "REMOTE_CONSOLE_SSH_CONFIGURATION_REQUIRED: run fb console on the target node, " + "or use your explicitly configured SSH destination. Remote EXEC, CUA and TTY " + "operations remain available through the enrolled broker.") + return re.sub(r"[^A-Za-z0-9_-]", "_", host)[:64] or "NODE" + +def _private_runtime(path): + path = Path(path) + info = path.lstat() + if (not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode) + or info.st_uid != os.getuid() or info.st_mode & 0o077): + raise RuntimeError("CONSOLE_RUNTIME_DIRECTORY_UNTRUSTED") + return path + +def context(state, node="LOCAL", *, create=False): + state = state_path(state) + label = _node(state, node) + key = hashlib.sha256(str(state).encode()).hexdigest()[:20] + runtime = _private_runtime(Path("/run/user") / str(os.getuid())) + root = runtime / "framebridge-expanded-console" + profile = root / key + if create: + private_dir(state) + for path in (root, profile): + path.mkdir(mode=0o700, exist_ok=True) + _private_runtime(path) + else: + for path in (root, profile): + if path.exists() or path.is_symlink(): + _private_runtime(path) + return { + "state": state, "node": label, "runtime": runtime, + "socket": profile / "console.sock", + "session": "FB-" + label + "-" + key[:10], + "jobs_root": state / "home/.local/share/framebridge/fbterm", + } + +def _properties(text): + return dict(line.split("=", 1) for line in text.splitlines() if "=" in line) + +def _active_graphical_display(env=None): + """Auto GUI requires the actual active local X11 session, never a guessed Xvfb.""" + env = os.environ if env is None else env + display = env.get("DISPLAY", "") + sid = env.get("XDG_SESSION_ID", "") + if (re.fullmatch(r":[0-9]+(?:\.[0-9]+)?", display) is None + or re.fullmatch(r"[A-Za-z0-9_-]{1,80}", sid) is None): + return False + try: + result = subprocess.run([ + "loginctl", "show-session", sid, "--no-pager", + "-p", "User", "-p", "Type", "-p", "Active", "-p", "Remote", + "-p", "Display", "-p", "VTNr", + ], capture_output=True, text=True, timeout=5) + if result.returncode: + return False + fields = _properties(result.stdout) + vt = fields.get("VTNr", "") + if not vt.isdigit() or int(vt) < 1: + return False + active = Path("/sys/class/tty/tty0/active").read_text().strip() + return (fields.get("User") == str(os.getuid()) + and fields.get("Type") == "x11" + and fields.get("Active") == "yes" + and fields.get("Remote") == "no" + and fields.get("Display", "").split(".")[0] == display.split(".")[0] + and active == "tty" + str(int(vt))) + except (OSError, subprocess.SubprocessError): + return False + +def _environment(ctx): + env = os.environ.copy() + real_home = pwd.getpwuid(os.getuid()).pw_dir + env.update({ + "HOME": real_home, + "XDG_RUNTIME_DIR": str(ctx["runtime"]), + "FRAMEBRIDGE_EXPANDED_STATE": str(ctx["state"]), + "FRAMEBRIDGE_FBTERM_ROOT": str(ctx["jobs_root"]), + "FRAMEBRIDGE_FBTERM_ROOT_OVERRIDE": str(ctx["jobs_root"]), + "FBTERM_ROOT": str(ctx["jobs_root"]), + "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8", + }) + # The installed command adjusts sys.path only in its own process. Make the + # new graphical-terminal process resolve this same installed module. + package_root = str(Path(__file__).resolve().parent.parent) + env["PYTHONPATH"] = os.pathsep.join( + [package_root] + ([env["PYTHONPATH"]] if env.get("PYTHONPATH") else [])) + env.pop("TMUX", None) + env.pop("TMUX_PANE", None) + return env + +def _socket_exists(sock): + try: + info = sock.lstat() + except FileNotFoundError: + return False + if (not stat.S_ISSOCK(info.st_mode) or stat.S_ISLNK(info.st_mode) + or info.st_uid != os.getuid() or info.st_mode & 0o077): + raise RuntimeError("CONSOLE_SOCKET_UNTRUSTED") + return True + +def status(state, node="LOCAL"): + ctx = context(state, node) + sock = ctx["socket"] + value = {"ok": True, "node": ctx["node"], "socket": str(sock), + "session": ctx["session"], "jobs_root": str(ctx["jobs_root"]), + "exists": False, "panes": []} + if not _socket_exists(sock): + return value + try: + result = subprocess.run([ + "/usr/bin/tmux", "-S", str(sock), "list-panes", + "-t", "=" + ctx["session"], + "-F", "#{pane_id}|#{pane_pid}|#{pane_current_command}|#{pane_width}|#{pane_height}|#{@framebridge_role}", + ], capture_output=True, text=True, timeout=5) + except (OSError, subprocess.SubprocessError): + value.update(ok=False, error="CONSOLE_TMUX_STATUS_FAILED") + return value + if result.returncode: + value.update(ok=False, error="CONSOLE_TMUX_STATUS_FAILED") + return value + value["exists"] = True + for line in result.stdout.splitlines(): + fields = line.split("|") + if len(fields) != 6: + raise RuntimeError("CONSOLE_TMUX_STATUS_INVALID") + pane, pid, command, width, height, role = fields + if not re.fullmatch(r"%[0-9]+", pane) or not all(x.isdigit() for x in [pid, width, height]): + raise RuntimeError("CONSOLE_TMUX_STATUS_INVALID") + value["panes"].append({"pane": pane, "pid": int(pid), "command": command, + "cols": int(width), "rows": int(height), "role": role}) + return value + + +def _prevent_recursive_attach(ctx, env): + """Reject attaching a console from one of its own panes before reflow.""" + marker = os.environ.get("TMUX", "") + if not marker: + return + parts = marker.rsplit(",", 2) + try: + same_socket = Path(parts[0]).resolve() == ctx["socket"].resolve() + except (OSError, RuntimeError): + raise RuntimeError("CONSOLE_TMUX_CONTEXT_UNVERIFIED") from None + if not same_socket: + return + pane = os.environ.get("TMUX_PANE", "") + if (len(parts) != 3 or not parts[1].isdigit() or not parts[2].isdigit() + or re.fullmatch(r"%[0-9]+", pane) is None): + raise RuntimeError("CONSOLE_TMUX_CONTEXT_UNVERIFIED") + try: + result = subprocess.run([ + "/usr/bin/tmux", "-S", str(ctx["socket"]), "list-panes", "-s", + "-t", "=" + ctx["session"], "-F", "#{pane_id}", + ], env=env, capture_output=True, text=True, timeout=5) + except (OSError, subprocess.SubprocessError): + raise RuntimeError("CONSOLE_TMUX_CONTEXT_UNVERIFIED") from None + panes = result.stdout.splitlines() if isinstance(result.stdout, str) else [] + if (result.returncode or not panes + or any(re.fullmatch(r"%[0-9]+", value) is None for value in panes)): + raise RuntimeError("CONSOLE_TMUX_CONTEXT_UNVERIFIED") + if pane in panes: + raise RuntimeError( + "CONSOLE_ALREADY_ATTACHED: This terminal is already inside this " + "FrameBridge console. Use its existing shell or open a separate terminal.") + +def run(state, node="LOCAL", *, inside=False, mode="auto"): + if mode not in {"auto", "gui", "tty"}: + raise ValueError("CONSOLE_MODE_INVALID") + ctx = context(state, node, create=True) + env = _environment(ctx) + _socket_exists(ctx["socket"]) + ui = Path(__file__).resolve().parent / "console_ui/framebridge_console.py" + if not ui.is_file(): + raise RuntimeError("CONSOLE_COMPONENT_NOT_INSTALLED") + graphical = _active_graphical_display(env) + if not inside and mode != "tty" and (mode == "gui" or graphical): + if not graphical: + raise RuntimeError("CONSOLE_ACTIVE_X11_DESKTOP_REQUIRED") + terminal = shutil.which("gnome-terminal") + if not terminal: + raise RuntimeError("CONSOLE_GRAPHICAL_TERMINAL_UNAVAILABLE") + try: + result = subprocess.run([ + terminal, "--window", "--maximize", + "--title=FrameBridge Console | " + ctx["node"], "--", + sys.executable, "-m", "framebridge_expanded.console", + "--state-dir", str(ctx["state"]), "--node", node, "--inside", "--mode", "gui", + ], env=env, capture_output=True, text=True, timeout=15) + except (OSError, subprocess.SubprocessError): + raise RuntimeError("CONSOLE_GRAPHICAL_TERMINAL_FAILED") from None + if result.returncode: + raise RuntimeError("CONSOLE_GRAPHICAL_TERMINAL_FAILED") + return 0 + if not os.isatty(0): + raise RuntimeError("CONSOLE_INTERACTIVE_TERMINAL_REQUIRED") + _prevent_recursive_attach(ctx, env) + try: + result = subprocess.run([ + sys.executable, str(ui), "--node", ctx["node"], + "--socket", str(ctx["socket"]), "--session", ctx["session"] , + "--layout", "horizontal" if inside else "vertical", "--apply", + ], env=env, capture_output=True, text=True, timeout=20) + except (OSError, subprocess.SubprocessError): + raise RuntimeError("CONSOLE_LAYOUT_FAILED") from None + if result.returncode: + raise RuntimeError("CONSOLE_LAYOUT_FAILED") + os.execve("/usr/bin/tmux", [ + "tmux", "-S", str(ctx["socket"]), "attach-session", "-f", "ignore-size", + "-t", "=" + ctx["session"], + ], env) + return 0 + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--state-dir") + parser.add_argument("--node", default="LOCAL") + parser.add_argument("--inside", action="store_true") + parser.add_argument("--mode", choices=["auto", "gui", "tty"], default="auto") + args = parser.parse_args(argv) + return run(state_path(args.state_dir), args.node, inside=args.inside, mode=args.mode) + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/console_startup.py b/source/expanded/framebridge_expanded/console_startup.py new file mode 100644 index 0000000..6bdfbef --- /dev/null +++ b/source/expanded/framebridge_expanded/console_startup.py @@ -0,0 +1,390 @@ +"""Explicit, reversible console startup after an authenticated tty1 Bash login. + +Only the caller's owned Bash startup files are changed. No login, sudo policy, +system service, desktop autostart, or authentication configuration is altered. +""" +from contextlib import contextmanager +from datetime import datetime, timezone +import errno +import fcntl +import hashlib +import json +import os +from pathlib import Path +import pwd +import re +import shlex +import stat +import tempfile +import uuid + +STARTUP_NAMES = (".bash_profile", ".bash_login", ".profile") +BEGIN = b"# BEGIN FRAMEBRIDGE CONSOLE LOGIN v1 " +END = b"# END FRAMEBRIDGE CONSOLE LOGIN v1 " +FB_EXECUTABLE = "/usr/bin/fb" + + +class ConsoleStartupError(ValueError): + pass + + +def _need(value, code): + if not value: + raise ConsoleStartupError(code) + + +def _sha(data): + return hashlib.sha256(data).hexdigest() + + +def _absolute(value): + try: + return Path(os.path.abspath(os.path.expanduser(os.fspath(value)))) + except (TypeError, ValueError): + raise ConsoleStartupError("CONSOLE_LOGIN_PATH_INVALID") from None + + +def _no_symlink_parents(path): + for part in [path, *path.parents]: + if part.is_symlink(): + raise ConsoleStartupError("CONSOLE_LOGIN_SYMLINK_REFUSED") + + +def _paths(state, home): + uid = os.getuid() + _need(uid != 0 and os.geteuid() == uid, "CONSOLE_LOGIN_USER_REQUIRED") + home = _absolute(home if home is not None else pwd.getpwuid(uid).pw_dir) + state = _absolute(state) + _no_symlink_parents(home) + _no_symlink_parents(state) + try: + info = home.lstat() + except OSError: + raise ConsoleStartupError("CONSOLE_LOGIN_HOME_UNAVAILABLE") from None + _need(stat.S_ISDIR(info.st_mode) and info.st_uid == uid + and not info.st_mode & 0o022, "CONSOLE_LOGIN_HOME_NOT_OWNED") + if state.exists(): + _private_dir(state, create=False) + return state, home + + +def _private_dir(path, create=True): + _no_symlink_parents(path) + if create: + path.mkdir(mode=0o700, parents=True, exist_ok=True) + info = path.lstat() + _need(stat.S_ISDIR(info.st_mode) and info.st_uid == os.getuid() + and stat.S_IMODE(info.st_mode) == 0o700, + "CONSOLE_LOGIN_STATE_NOT_PRIVATE") + + +def _snapshot(path): + try: + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + except FileNotFoundError: + return None + except OSError as error: + if error.errno == errno.ELOOP: + raise ConsoleStartupError("CONSOLE_LOGIN_SYMLINK_REFUSED") from None + raise + try: + info = os.fstat(fd) + _need(stat.S_ISREG(info.st_mode), "CONSOLE_LOGIN_FILE_NOT_REGULAR") + _need(info.st_uid == os.getuid(), "CONSOLE_LOGIN_FILE_NOT_OWNED") + with os.fdopen(fd, "rb") as stream: + fd = None + data = stream.read() + return {"data": data, "sha256": _sha(data), + "mode": stat.S_IMODE(info.st_mode), "gid": info.st_gid, + "atime_ns": info.st_atime_ns, "mtime_ns": info.st_mtime_ns} + finally: + if fd is not None: + os.close(fd) + + +def _startup_files(home): + return {name: _snapshot(home / name) for name in STARTUP_NAMES} + + +def _effective(snapshots): + return next((name for name in STARTUP_NAMES if snapshots[name] is not None), ".profile") + + +def _markers(snapshots): + found = [] + for name, snapshot in snapshots.items(): + if snapshot is None: + continue + for line in snapshot["data"].splitlines(): + if line.startswith((b"# BEGIN FRAMEBRIDGE CONSOLE LOGIN", + b"# END FRAMEBRIDGE CONSOLE LOGIN")): + found.append((name, line)) + return found + + +@contextmanager +def _home_lock(home, create): + path = home / ".framebridge-console-login.lock" + flags = os.O_RDWR | os.O_NOFOLLOW | (os.O_CREAT if create else 0) + try: + fd = os.open(path, flags, 0o600) + except FileNotFoundError: + yield + return + except OSError as error: + if error.errno == errno.ELOOP: + raise ConsoleStartupError("CONSOLE_LOGIN_SYMLINK_REFUSED") from None + raise + try: + info = os.fstat(fd) + _need(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid() + and stat.S_IMODE(info.st_mode) == 0o600, "CONSOLE_LOGIN_LOCK_UNTRUSTED") + fcntl.flock(fd, fcntl.LOCK_EX if create else fcntl.LOCK_SH) + yield + finally: + os.close(fd) + + +def _atomic_write(path, data, mode=0o600, gid=None, new=False): + fd, temporary = tempfile.mkstemp(prefix=".fb-console-login-", dir=path.parent) + try: + with os.fdopen(fd, "wb") as stream: + if gid is not None and os.fstat(stream.fileno()).st_gid != gid: + os.fchown(stream.fileno(), -1, gid) + os.fchmod(stream.fileno(), mode) + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + if new: + os.link(temporary, path, follow_symlinks=False) + else: + os.replace(temporary, path) + finally: + try: + os.unlink(temporary) + except FileNotFoundError: + pass + + +def _profile_id(state): + return _sha(os.fsencode(str(state))) + + +def _read_manifest(state, home): + directory = state / "console-login" + path = directory / "manifest.json" + if not path.exists() and not path.is_symlink(): + return None + _private_dir(state, create=False) + _private_dir(directory, create=False) + snapshot = _snapshot(path) + _need(snapshot is not None and snapshot["mode"] == 0o600, + "CONSOLE_LOGIN_MANIFEST_UNTRUSTED") + try: + value = json.loads(snapshot["data"]) + valid = (type(value) is dict and value["schema"] == 1 + and value["state"] == str(state) and value["home"] == str(home) + and value["profile_id"] == _profile_id(state) + and value["startup_name"] in STARTUP_NAMES + and value["phase"] in {"PREPARED", "ENABLED", "DISABLED"} + and type(value["existed_before"]) is bool + and type(value["before_mode"]) is int + and 0 <= value["before_mode"] <= 0o7777 + and type(value["before_gid"]) is int + and value["before_gid"] >= 0 + and re.fullmatch(r"[a-f0-9]{32}", value["transaction"]) is not None + and re.fullmatch(r"[a-f0-9]{64}", value["after_sha256"]) is not None) + if value["existed_before"]: + valid = (valid and re.fullmatch(r"[a-f0-9]{64}", value["before_sha256"]) is not None + and value["backup_name"] == "backup-" + value["transaction"] + ".bin") + else: + valid = valid and value["before_sha256"] is None and value["backup_name"] is None + _need(valid, "CONSOLE_LOGIN_MANIFEST_INVALID") + return value + except (KeyError, TypeError, ValueError): + raise ConsoleStartupError("CONSOLE_LOGIN_MANIFEST_INVALID") from None + + +def _write_manifest(directory, record): + _atomic_write(directory / "manifest.json", + (json.dumps(record, indent=2) + "\n").encode()) + + +def _known_original(snapshot, record): + if not record["existed_before"]: + return snapshot is None + return (snapshot is not None and snapshot["sha256"] == record["before_sha256"] + and snapshot["mode"] == record["before_mode"] + and snapshot["gid"] == record["before_gid"]) + + +def _known_installed(snapshot, record): + return (snapshot is not None and snapshot["sha256"] == record["after_sha256"] + and snapshot["mode"] == record["before_mode"] + and snapshot["gid"] == record["before_gid"]) + + +def _hook(state, profile_id): + # Adjacent literals keep shell parameter expansion in the generated script. + guard = ( + 'if [ -n "$' '{BASH_VERSION-}" ] && (shopt -q login_shell 2>/dev/null) ' + '&& [ -z "$' '{SSH_TTY-}" ] && [ -z "$' '{TMUX-}" ] ' + '&& [ "$(tty 2>/dev/null)" = /dev/tty1 ] ' + '&& [ -x ' + shlex.quote(FB_EXECUTABLE) + ' ]; then\n' + ) + return (BEGIN.decode() + profile_id + "\n" + guard + + " " + shlex.quote(FB_EXECUTABLE) + " --state-dir " + shlex.quote(str(state)) + + " console --mode tty || printf '%s\\n' " + + "'FrameBridge console did not start; continuing this login.' >&2\n" + + "fi\n" + END.decode() + profile_id + "\n").encode() + + +def _result(record, *, enabled, changed=False, **extra): + return dict({"ok": True, "enabled": enabled, "changed": changed, + "startup_file": str(Path(record["home"]) / record["startup_name"]), + "profile_id": record["profile_id"], "phase": record["phase"], + "before_sha256": record["before_sha256"], + "after_sha256": record["after_sha256"]}, **extra) + + +def _enable(state, home): + state, home = _paths(state, home) + with _home_lock(home, create=True): + snapshots = _startup_files(home) + record = _read_manifest(state, home) + if record and record["phase"] != "DISABLED": + installed = snapshots[record["startup_name"]] + if _known_installed(installed, record): + _need(_effective(snapshots) == record["startup_name"], + "CONSOLE_LOGIN_STARTUP_PRIORITY_CHANGED") + expected = [(record["startup_name"], BEGIN + record["profile_id"].encode()), + (record["startup_name"], END + record["profile_id"].encode())] + _need(_markers(snapshots) == expected, "CONSOLE_LOGIN_FOREIGN_BLOCK") + if record["phase"] == "PREPARED": + record["phase"] = "ENABLED" + _write_manifest(state / "console-login", record) + return _result(record, enabled=True) + _need(record["phase"] == "PREPARED" and _known_original(installed, record), + "CONSOLE_LOGIN_STARTUP_CHANGED") + record["phase"] = "DISABLED" + _write_manifest(state / "console-login", record) + markers = _markers(snapshots) + if markers: + own = _profile_id(state).encode() + code = ("CONSOLE_LOGIN_UNTRACKED_BLOCK" + if any(own in line for _, line in markers) + else "CONSOLE_LOGIN_OTHER_PROFILE_ENABLED") + raise ConsoleStartupError(code) + _private_dir(state) + directory = state / "console-login" + _private_dir(directory) + if record: + history = directory / ("history-" + record["transaction"] + ".json") + _atomic_write(history, (json.dumps(record, indent=2) + "\n").encode(), new=True) + name = _effective(snapshots) + before = snapshots[name] + data = before["data"] if before else b"" + profile_id = _profile_id(state) + after = data + (b"\n" if data and not data.endswith(b"\n") else b"") + _hook(state, profile_id) + transaction = uuid.uuid4().hex + record = { + "schema": 1, "transaction": transaction, "phase": "PREPARED", + "created_utc": datetime.now(timezone.utc).isoformat(), + "state": str(state), "home": str(home), "profile_id": profile_id, + "startup_name": name, "existed_before": before is not None, + "before_sha256": before["sha256"] if before else None, + "before_mode": before["mode"] if before else 0o600, + "before_gid": before["gid"] if before else os.getgid(), + "before_atime_ns": before["atime_ns"] if before else None, + "before_mtime_ns": before["mtime_ns"] if before else None, + "backup_name": "backup-" + transaction + ".bin" if before else None, + "after_sha256": _sha(after), + } + if before: + _atomic_write(directory / record["backup_name"], data, new=True) + backup = _snapshot(directory / record["backup_name"]) + _need(backup["sha256"] == record["before_sha256"], "CONSOLE_LOGIN_BACKUP_MISMATCH") + _write_manifest(directory, record) + _need(_known_original(_snapshot(home / name), record), "CONSOLE_LOGIN_STARTUP_CHANGED") + _atomic_write(home / name, after, record["before_mode"], record["before_gid"], new=before is None) + _need(_known_installed(_snapshot(home / name), record), "CONSOLE_LOGIN_WRITE_MISMATCH") + record["phase"] = "ENABLED" + _write_manifest(directory, record) + return _result(record, enabled=True, changed=True) + + +def _disable(state, home): + state, home = _paths(state, home) + with _home_lock(home, create=True): + record = _read_manifest(state, home) + if record is None: + return {"ok": True, "enabled": False, "changed": False, "phase": "NOT_CONFIGURED"} + if record["phase"] == "DISABLED": + return _result(record, enabled=False) + path = home / record["startup_name"] + current = _snapshot(path) + directory = state / "console-login" + if record["phase"] == "PREPARED" and _known_original(current, record): + record["phase"] = "DISABLED" + _write_manifest(directory, record) + return _result(record, enabled=False) + _need(_known_installed(current, record), "CONSOLE_LOGIN_STARTUP_CHANGED") + if record["existed_before"]: + backup = _snapshot(directory / record["backup_name"]) + _need(backup is not None and backup["mode"] == 0o600 + and backup["sha256"] == record["before_sha256"], + "CONSOLE_LOGIN_BACKUP_MISMATCH") + _need(_known_installed(_snapshot(path), record), "CONSOLE_LOGIN_STARTUP_CHANGED") + _atomic_write(path, backup["data"], record["before_mode"], record["before_gid"]) + os.utime(path, ns=(record["before_atime_ns"], record["before_mtime_ns"])) + _need(_known_original(_snapshot(path), record), "CONSOLE_LOGIN_RESTORE_MISMATCH") + else: + _need(_known_installed(_snapshot(path), record), "CONSOLE_LOGIN_STARTUP_CHANGED") + path.unlink() + record["phase"] = "DISABLED" + _write_manifest(directory, record) + return _result(record, enabled=False, changed=True) + + +def _status(state, home): + state, home = _paths(state, home) + with _home_lock(home, create=False): + snapshots = _startup_files(home) + record = _read_manifest(state, home) + if record is None: + return {"ok": True, "enabled": False, "changed": False, + "phase": "NOT_CONFIGURED", "startup_file": str(home / _effective(snapshots)), + "other_hook_present": bool(_markers(snapshots))} + if record["phase"] == "DISABLED": + return _result(record, enabled=False) + current = snapshots[record["startup_name"]] + if record["phase"] == "PREPARED" and _known_original(current, record): + return _result(record, enabled=False, recovery_pending=True) + _need(_known_installed(current, record), "CONSOLE_LOGIN_STARTUP_CHANGED") + _need(_effective(snapshots) == record["startup_name"], + "CONSOLE_LOGIN_STARTUP_PRIORITY_CHANGED") + expected = [(record["startup_name"], BEGIN + record["profile_id"].encode()), + (record["startup_name"], END + record["profile_id"].encode())] + _need(_markers(snapshots) == expected, "CONSOLE_LOGIN_FOREIGN_BLOCK") + return _result(record, enabled=True, recovery_pending=record["phase"] == "PREPARED") + + +def _call(function, state, home): + try: + return function(state, home) + except ConsoleStartupError as error: + return {"ok": False, "error": str(error)} + except (OSError, ValueError, TypeError, KeyError): + return {"ok": False, "error": "CONSOLE_LOGIN_IO_ERROR"} + + +def enable(state, *, home=None): + return _call(_enable, state, home) + + +def disable(state, *, home=None): + return _call(_disable, state, home) + + +def status(state, *, home=None): + return _call(_status, state, home) diff --git a/source/expanded/framebridge_expanded/console_ui/__init__.py b/source/expanded/framebridge_expanded/console_ui/__init__.py new file mode 100644 index 0000000..0bc7924 --- /dev/null +++ b/source/expanded/framebridge_expanded/console_ui/__init__.py @@ -0,0 +1 @@ +"""Accepted two-pane console display components.""" diff --git a/source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py b/source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py new file mode 100644 index 0000000..7cee86f --- /dev/null +++ b/source/expanded/framebridge_expanded/console_ui/fbterm_dashboard.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +"""Read-only FBTERM dashboard for a real tmux pane (Python standard library). + +The display is an operational summary, not a screenshot or an execution engine. +Job files remain authoritative. No lifecycle recovery or job mutation is done. +""" +from __future__ import annotations + +import argparse +from collections import OrderedDict +from contextlib import nullcontext +from dataclasses import dataclass +from datetime import datetime +import curses +import heapq +import json +import math +import os +from pathlib import Path +import re +import socket +import stat +import textwrap +import time + +VERSION = "1.0" +MAX_FILE = 32768 +MAX_TAIL = 12288 +RECENT_LIMIT = 40 +CACHE_LIMIT = 2048 +ANSI = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))") +SENSITIVE = re.compile( + r"(?i)(password|passwd|passphrase|secret|token|bearer|authorization|cookie|" + r"api[ _.-]?key|private[ _.-]?key|client[ _.-]?secret|oauth|" + r"ssh-(?:rsa|ed25519)|BEGIN [A-Z ]*PRIVATE KEY|://[^ /]+:[^ /]+@)" +) +BLOB = re.compile(r"[A-Za-z0-9+/_=-]{160,}") +ENCODED_LINE = re.compile(r"\s*[A-Za-z0-9+/]{48,}={0,2}\s*\Z") +IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,159}\Z") +TRANSPORT = "[transport payload omitted; raw output retained]" +REDACTED = "[potentially sensitive output hidden]" + + +def ascii_text(value): + """Discard terminal controls and non-ASCII glyphs for the physical VT.""" + text = ANSI.sub("", str(value)).replace("\r", " ") + return "".join(c for c in text if 32 <= ord(c) < 127) + + +def safe_line(value): + text = ascii_text(value) + if SENSITIVE.search(text): + return REDACTED + low = text.lower() + if (BLOB.search(text) or ENCODED_LINE.fullmatch(text) + or "data:image/" in low or "base64" in low + or '"capture_id"' in low or '"data"' in low and '"encoding"' in low): + return TRANSPORT + return text[:400] + (" [truncated]" if len(text) > 400 else "") + + +def read_small(path, limit=MAX_FILE): + """Bound reads and reject symlinks/special files (including stdout pipes).""" + fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_size > limit: + raise ValueError("not a bounded regular file") + return os.read(fd, limit + 1) + finally: + os.close(fd) + + +def read_exit(job): + try: + if job.is_symlink(): + return None + value = read_small(job / "exit.code", 32).decode("ascii").strip() + return int(value) if re.fullmatch(r"-?[0-9]{1,4}", value) else None + except (OSError, ValueError, UnicodeError): + return None + + +def worker_alive(token, proc_root=Path("/proc")): + """Three-valued PID identity check: true, false or unavailable.""" + if not isinstance(token, dict) or type(token.get("pid")) is not int or token["pid"] <= 0: + return None + try: + raw = (proc_root / str(token["pid"]) / "stat").read_text() + fields = raw[raw.rfind(")") + 2:].split() + boot = (proc_root / "sys/kernel/random/boot_id").read_text().strip() + if not all(k in token for k in ("start_ticks", "boot")): + return None + return (fields[0] != "Z" and fields[19] == str(token["start_ticks"]) + and boot == token["boot"]) + except FileNotFoundError: + return False + except (OSError, IndexError, ValueError): + return None + + +def number(value): + return float(value) if type(value) in (int, float) and math.isfinite(value) else None + + +@dataclass(frozen=True) +class Job: + name: str + updated: float + state: str + exit_code: int | None = None + started: float | None = None + finished: float | None = None + recorded: bool = False + + def duration(self, now): + if self.started is None: + return "--" + stop = self.finished if self.finished is not None else now if self.state == "RUNNING" else None + if stop is None or stop < self.started: + return "--" + seconds = int(stop - self.started) + if seconds >= 3600: + return f"{seconds // 3600}h{seconds % 3600 // 60:02}m" + if seconds >= 60: + return f"{seconds // 60}m{seconds % 60:02}s" + return f"{seconds}s" + + +def describe(name, receipt, job_path, updated, proc_root=Path("/proc")): + rc = read_exit(job_path) + if receipt is None: + return Job(name, updated, "EXIT" if rc is not None else "UNKNOWN", rc) + if (not isinstance(receipt, dict) or receipt.get("schema") != 4 + or receipt.get("job_id") != name): + return Job(name, updated, "INVALID", rc) + phase = receipt.get("phase") + started = number(receipt.get("started_at")) + finished = number(receipt.get("finished_at")) + if phase == "COMPLETED": + recorded = (rc is not None and type(receipt.get("exit_code")) is int + and rc == receipt["exit_code"] and finished is not None) + return Job(name, updated, ("DONE" if rc == 0 else "FAILED") if recorded else "MISMATCH", + rc, started, finished, recorded) + if phase == "RUNNING": + live = worker_alive(receipt.get("worker"), proc_root) + state = "RUNNING" if live is True else "STALE" if live is False else "UNCONFIRMED" + return Job(name, updated, state, rc, started) + if phase in ("RESERVED", "ARMED"): + return Job(name, updated, phase, rc, started, finished) + if phase == "INTERRUPTED": + return Job(name, updated, "INTERRUPTED", rc, started, finished) + return Job(name, updated, "INVALID", rc, started, finished) + + +class Catalogue: + """Stream the full census; keep bounded metadata and bounded output tails.""" + def __init__(self, root, proc_root=Path("/proc")): + self.root = Path(root) + self.proc_root = Path(proc_root) + self.cache = OrderedDict() + self.recent = [] + self.counts = {} + self.total = 0 + self.errors = 0 + self.last_scan = None + self.output_cache = (None, []) + + def receipt(self, path): + info = path.lstat() + stamp = (info.st_ino, info.st_mtime_ns, info.st_size) + key = str(path) + if key in self.cache and self.cache[key][0] == stamp: + self.cache.move_to_end(key) + return self.cache[key][1] + try: + data = json.loads(read_small(path)) + except (OSError, ValueError, UnicodeError): + data = {} + self.cache[key] = (stamp, data) + self.cache.move_to_end(key) + while len(self.cache) > CACHE_LIMIT: + self.cache.popitem(last=False) + return data + + def scan(self): + counts, newest, total, errors = {}, [], 0, 0 + receipts = self.root / "receipts" + jobs = self.root / "jobs" + + def accept(job): + nonlocal total + total += 1 + counts[job.state] = counts.get(job.state, 0) + 1 + # A stable tie breaker means equal mtimes cannot compare Job objects. + key = (job.updated, job.name, job) + if len(newest) < RECENT_LIMIT: + heapq.heappush(newest, key) + elif key[:2] > newest[0][:2]: + heapq.heapreplace(newest, key) + + # Enumerate every receipt, not only recent directories. Older running + # jobs are counted using PID identity even when newer jobs have arrived. + try: + try: + receipt_entries = os.scandir(receipts) + except FileNotFoundError: + # Legacy producers need no receipts directory. Only opening + # this optional source may be absent; entry errors still count. + receipt_entries = nullcontext(()) + with receipt_entries as entries: + for entry in entries: + if not entry.name.endswith(".json") or not entry.is_file(follow_symlinks=False): + continue + name = entry.name[:-5] + if not IDENTIFIER.fullmatch(name): + continue + try: + path = Path(entry.path) + info = entry.stat(follow_symlinks=False) + accept(describe(name, self.receipt(path), jobs / name, info.st_mtime, + self.proc_root)) + except OSError: + errors += 1 + except OSError: + errors += 1 + # Legacy jobs with an exit file are useful, but never assumed running. + try: + with os.scandir(jobs) as entries: + for entry in entries: + if not IDENTIFIER.fullmatch(entry.name) or not entry.is_dir(follow_symlinks=False): + continue + if (receipts / (entry.name + ".json")).exists(): + continue + try: + accept(describe(entry.name, None, Path(entry.path), + entry.stat(follow_symlinks=False).st_mtime, self.proc_root)) + except OSError: + errors += 1 + except OSError: + errors += 1 + self.recent = [row[2] for row in sorted(newest, reverse=True)] + self.counts, self.total, self.errors = counts, total, errors + self.last_scan = time.time() + + def output(self, name): + if not IDENTIFIER.fullmatch(name): + return [] + job = self.root / "jobs" / name + if job.is_symlink(): + return [] + stamps = [] + for stream in ("stdout.bin", "stderr.bin"): + try: + info = (job / stream).lstat() + stamps.append((info.st_ino, info.st_mtime_ns, info.st_ctime_ns, info.st_size)) + except OSError: + stamps.append(None) + cache_key = (name, *stamps) + if self.output_cache[0] == cache_key: + return self.output_cache[1] + lines = [] + transport_seen = False + for stream, prefix in (("stdout.bin", "OUT"), ("stderr.bin", "ERR")): + try: + fd = os.open(job / stream, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode): + continue + start = max(0, info.st_size - MAX_TAIL) + os.lseek(fd, start, os.SEEK_SET) + data = os.read(fd, MAX_TAIL) + finally: + os.close(fd) + chunks = data.decode("utf-8", errors="replace").splitlines() + if start and chunks: + chunks.pop(0) # Never expose a partial potentially sensitive line. + # Multiline credential blocks must not leak short base64 lines. + secret_block = False + for text in chunks[-80:]: + upper = text.upper() + if "-----BEGIN " in upper and "KEY-----" in upper: + secret_block = True + if secret_block: + line = REDACTED + if "-----END " in upper and "KEY-----" in upper: + secret_block = False + else: + line = safe_line(text) + if line == TRANSPORT: + if transport_seen: + continue + transport_seen = True + if line.strip() and (not lines or lines[-1] != (prefix, line)): + lines.append((prefix, line)) + except (OSError, ValueError): + continue + lines = lines[-100:] + self.output_cache = (cache_key, lines) + return lines + + +def put(screen, row, text, attr=0): + h, w = screen.getmaxyx() + if 0 <= row < h and w > 1: + try: + screen.addnstr(row, 0, ascii_text(text).ljust(w - 1), w - 1, attr) + except (curses.error, ValueError): + pass + + +def recent_label(job, width): + # Date-heavy execution IDs are shortened from the left; their unique suffix + # and observed time remain visible, while the complete ID is shown below. + name = job.name if len(job.name) <= width else ".." + job.name[-max(1, width - 2):] + return name + + +def draw(screen, catalogue, node, selection=0, follow=True, help_open=False): + screen.erase() + h, w = screen.getmaxyx() + now = time.time() + title = curses.A_REVERSE | curses.A_BOLD + accent = curses.color_pair(1) | curses.A_BOLD if curses.has_colors() else curses.A_BOLD + good = curses.color_pair(2) if curses.has_colors() else curses.A_NORMAL + warning = curses.color_pair(3) if curses.has_colors() else curses.A_BOLD + bad = curses.color_pair(4) if curses.has_colors() else curses.A_BOLD + put(screen, 0, f" FBTERM / ACTIVITY | {node}", title) + clock = datetime.now().astimezone().strftime("%H:%M:%S %Z") + running = catalogue.counts.get("RUNNING", 0) + pending = sum(catalogue.counts.get(k, 0) for k in ("RESERVED", "ARMED")) + uncertain = sum(catalogue.counts.get(k, 0) for k in ("UNKNOWN", "STALE", "UNCONFIRMED", "INVALID", "MISMATCH")) + put(screen, 1, f" {clock} | RUN {running} WAIT {pending} CHECK {uncertain}", accent) + if h < 10 or w < 35: + put(screen, 2, f" {catalogue.total} jobs | {'LIVE' if follow else 'REVIEW'}", good) + put(screen, 3, " Ctrl+B z: zoom | Ctrl+B o: focus") + return screen.refresh() + if help_open: + help_lines = [ + " MONITOR HELP", "", " j/k or Up/Down : review recent jobs", + " f : follow newest job", " ? : close this help", "", + " TMUX", " Ctrl+B then o : switch pane", + " Ctrl+B then arrow: choose pane", " Ctrl+B then z : zoom / restore", "", + " RUNNING: worker PID, birth and boot match.", + " DONE/FAILED: receipt and exit agree.", + " EXIT: legacy exit file, no receipt.", + " STALE/CHECK: activity cannot be confirmed.", "", + " Output is a filtered, bounded tail.", + " Raw stdout/stderr remain in job files.", + " Output hashes are not checked by this UI.", + " q does not close the monitor.", + ] + for row, text in enumerate(help_lines, 3): + if row < h - 2: + put(screen, row, text) + put(screen, h - 1, " ?: close help | Ctrl+B o: Bash", title) + return screen.refresh() + put(screen, 2, f" {'LIVE' if follow else 'REVIEW'} | {catalogue.total} jobs | receipts + process identity", good) + if catalogue.errors: + put(screen, 3, f" Source unavailable/read errors: {catalogue.errors}", warning) + else: + put(screen, 3, " Recent jobs / recorded results", accent) + list_count = min(8 if h >= 40 else 5 if h >= 25 else 2, max(1, h // 6)) + start_index = max(0, selection - list_count + 1) + rows = catalogue.recent[start_index:start_index + list_count] + for index, job in enumerate(rows): + selected = start_index + index == selection + rc = f" rc={job.exit_code}" if job.exit_code is not None else "" + state = f"{job.state}{rc} {job.duration(now)}" + name_width = max(8, w - len(state) - 5) + line = f"{'>' if selected else ' '} {recent_label(job, name_width):<{name_width}} {state}" + color = bad if job.state in ("FAILED", "MISMATCH", "INVALID") else accent if selected else good + put(screen, 4 + index, line, color) + detail_row = 5 + list_count + put(screen, detail_row - 1, " " + "-" * max(0, w - 3)) + if catalogue.recent: + job = catalogue.recent[min(selection, len(catalogue.recent) - 1)] + wrapped = textwrap.wrap(" Job: " + job.name, width=max(1, w - 2))[:2] + for line in wrapped: + put(screen, detail_row, line, accent) + detail_row += 1 + lines = catalogue.output(job.name) + available = max(0, h - detail_row - 4) + wrapped_output = [] + for prefix, line in lines: + for part in textwrap.wrap(prefix + " " + line, width=max(1, w - 3)) or [prefix]: + wrapped_output.append((prefix, " " + part)) + if not wrapped_output: + wrapped_output = [("OUT", " No readable output recorded.")] + for index, (prefix, text) in enumerate(wrapped_output[-available:] if available else []): + put(screen, detail_row + index, text, warning if prefix == "ERR" else curses.A_NORMAL) + else: + put(screen, detail_row, " Waiting for recorded FBTERM jobs.", good) + put(screen, h - 3, " " + "-" * max(0, w - 3)) + put(screen, h - 2, " j/k: review | f: live | ?: help", good) + put(screen, h - 1, " Ctrl+B o: focus | Ctrl+B z: zoom", title) + screen.refresh() + + +def main_screen(screen, root, node): + try: + curses.curs_set(0) + except curses.error: + pass + screen.timeout(200) + screen.keypad(True) + if curses.has_colors(): + curses.start_color() + try: + curses.use_default_colors() + except curses.error: + pass + for pair, fg in ((1, curses.COLOR_CYAN), (2, curses.COLOR_GREEN), + (3, curses.COLOR_YELLOW), (4, curses.COLOR_RED)): + try: + curses.init_pair(pair, fg, -1) + except curses.error: + pass + catalogue = Catalogue(root) + previous = 0.0 + selection, follow, help_open = 0, True, False + selected_name = None + while True: + now = time.monotonic() + if now - previous >= 1.5: + catalogue.scan() + previous = now + if follow: + selection = 0 + elif selected_name: + selection = next((i for i, j in enumerate(catalogue.recent) if j.name == selected_name), + min(selection, max(0, len(catalogue.recent) - 1))) + draw(screen, catalogue, node, selection, follow, help_open) + ch = screen.getch() + if ch in (ord("j"), curses.KEY_DOWN): + selection = min(selection + 1, max(0, len(catalogue.recent) - 1)) + follow = False + elif ch in (ord("k"), curses.KEY_UP): + selection = max(0, selection - 1) + follow = False + elif ch in (ord("f"), ord("F")): + selection, follow = 0, True + elif ch == ord("?"): + help_open = not help_open + selected_name = catalogue.recent[selection].name if catalogue.recent else None + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path(os.environ.get( + "FRAMEBRIDGE_FBTERM_ROOT", str(Path.home() / ".local/share/framebridge/fbterm")))) + parser.add_argument("--node", default=os.environ.get("FRAMEBRIDGE_NODE", socket.gethostname())) + parser.add_argument("--version", action="version", version=VERSION) + args = parser.parse_args() + node = ascii_text(args.node)[:64] + try: + curses.wrapper(main_screen, args.root, node) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/source/expanded/framebridge_expanded/console_ui/framebridge_console.py b/source/expanded/framebridge_expanded/console_ui/framebridge_console.py new file mode 100644 index 0000000..ea8c8ab --- /dev/null +++ b/source/expanded/framebridge_expanded/console_ui/framebridge_console.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""FrameBridge's two-pane physical console, using the existing user's tmux. + +No login, privilege, service or desktop configuration is changed here. +The normal entry point is called by an existing authenticated tty1 login. +""" +from __future__ import annotations + +import argparse +import fcntl +import getpass +import json +import os +from pathlib import Path +import re +import shlex +import socket +import stat +import subprocess +import sys +import time + + +HERE = Path(__file__).resolve().parent + + +def arguments(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--node', default=os.environ.get('FRAMEBRIDGE_NODE', socket.gethostname())) + p.add_argument('--socket') + p.add_argument('--session') + p.add_argument('--apply', action='store_true', help='Set up without attaching a client') + p.add_argument('--probe', action='store_true', help='Read current pane metadata only') + p.add_argument('--reflow', action='store_true', help='Resize existing panes without taking focus') + p.add_argument('--refresh-monitor', action='store_true') + p.add_argument('--layout', choices=('auto','horizontal','vertical'), default='auto') + p.add_argument('--monitor', action='store_true', help=argparse.SUPPRESS) + a = p.parse_args() + a.node = re.sub(r'[^A-Za-z0-9_-]', '_', a.node)[:64] or 'NODE' + a.socket = a.socket or str(Path('/run/user') / str(os.getuid()) / (a.node.lower() + '-tty1') / 'console.sock') + a.session = a.session or a.node.upper() + '-FBTERM' + if not re.fullmatch(r'[A-Za-z0-9_-]{1,90}', a.session): + p.error('Session name must contain only letters, digits, underscore and hyphen') + return a + + +class Console: + def __init__(self, args): + self.a = args + self.session_target = args.session + self.window = '=' + args.session + ':0' + self.env = {k:v for k,v in os.environ.items() if k not in ('TMUX', 'TMUX_PANE')} + self.env['LC_ALL'] = 'C.UTF-8' + + def tmux(self, *args, check=True): + r = subprocess.run(['/usr/bin/tmux', '-S', self.a.socket, *args], + env=self.env, capture_output=True, text=True, timeout=8) + if check and r.returncode: + raise RuntimeError('tmux ' + args[0] + ': ' + r.stderr.strip()) + return r.stdout.strip() + + def exists(self): + r = subprocess.run(['/usr/bin/tmux', '-S', self.a.socket, 'has-session', '-t', '=' + self.a.session], + env=self.env, capture_output=True, timeout=5) + if r.returncode == 0: + matches=[line.rsplit('|',1)[1] for line in self.tmux('list-sessions','-F','#{session_name}|#{session_id}').splitlines() + if line.rsplit('|',1)[0]==self.a.session] + if len(matches)!=1: + raise RuntimeError('Console session identity is ambiguous') + self.session_target=matches[0] + windows=self.tmux('list-windows','-t',self.session_target,'-F','#{window_id}').splitlines() + if len(windows)!=1: + raise RuntimeError('Use a dedicated console session containing exactly one window') + self.window=windows[0] + return True + return False + + def panes(self): + fmt = '|'.join('#{' + f + '}' for f in ('pane_id','pane_pid','pane_current_command', + 'pane_left','pane_top','pane_width','pane_height','pane_active','pane_dead', + '@framebridge_role')) + rows = [] + for line in self.tmux('list-panes', '-t', self.window, '-F', fmt).splitlines(): + ident,pid,command,left,top,width,height,active,dead,role = line.split('|') + rows.append(dict(id=ident,pid=int(pid),command=command,left=int(left),top=int(top), + width=int(width),height=int(height),active=active=='1',dead=dead=='1',role=role)) + return rows + + def monitor_command(self): + return shlex.join(['/usr/bin/python3', str(HERE / 'framebridge_console.py'), '--monitor', '--node', self.a.node]) + + def locate(self, rows): + shell = next((p for p in rows if p['role']=='shell'), None) + monitor = next((p for p in rows if p['role']=='monitor'), None) + # Adopt only the known original dashboard, never an arbitrary Python pane. + for p in rows: + if p['role']: + continue + try: + cmd = Path('/proc', str(p['pid']), 'cmdline').read_bytes().split(b'\0') + names = {Path(x.decode(errors='replace')).name for x in cmd if x} + except OSError: + names = set() + if not monitor and ('fbterm_live.py' in names or 'fbterm_dashboard.py' in names): + monitor = p + elif not shell and p['command']=='bash' and not p['dead']: + shell = p + return shell, monitor + + def layout(self, shell, monitor): + active=next((p['id'] for p in self.panes() if p['active']),None) + width,height = map(int, self.tmux('display-message','-p','-t',self.window, + '#{window_width} #{window_height}').split()) + mode=getattr(self.a,'layout','auto') + horizontal=mode=='horizontal' or (mode=='auto' and width>=160) + if horizontal: + self.tmux('select-layout','-t',self.window,'even-horizontal') + current={p['id']:p for p in self.panes()} + if current[monitor['id']]['left']>current[shell['id']]['left']: + self.tmux('swap-pane','-d','-s',shell['id'],'-t',monitor['id']) + monitor_width=max(2,min(max(2,width-21),max(24,round((width-1)*0.30)))) + self.tmux('resize-pane','-t',monitor['id'],'-x',str(monitor_width)) + else: + self.tmux('select-layout','-t',self.window,'even-vertical') + current={p['id']:p for p in self.panes()} + if current[monitor['id']]['top']>current[shell['id']]['top']: + self.tmux('swap-pane','-d','-s',shell['id'],'-t',monitor['id']) + self.tmux('resize-pane','-t',monitor['id'],'-y',str(max(2,min(8,(height-3)//3)))) + if active: + self.tmux('select-pane','-t',active) + + def apply(self): + existed=self.exists() + if not existed: + parent=Path(self.a.socket).parent + parent.mkdir(mode=0o700,parents=True,exist_ok=True) + st=parent.stat() + if st.st_uid!=os.getuid() or st.st_mode & 0o022: + raise RuntimeError('Console socket directory is not private to this user') + size=os.get_terminal_size(0) if os.isatty(0) else os.terminal_size((240,67)) + self.session_target,self.window=self.tmux('new-session','-d','-P','-F','#{session_id}|#{window_id}', + '-x',str(size.columns),'-y',str(size.lines), + '-s',self.a.session,'-n','Console','-c',str(Path.home()),'/bin/bash --login').split('|') + rows=self.panes() + shell,monitor=self.locate(rows) + if self.a.reflow: + if shell and monitor and len(rows)==2: + # Zoom is an explicit user choice; never undo it in a resize hook. + if self.tmux('display-message','-p','-t',self.window,'#{window_zoomed_flag}')!='1': + self.layout(shell,monitor) + return + if not shell or shell['dead']: + raise RuntimeError('Normal Bash pane is absent; refusing to replace another program') + if len(rows)>2 or (len(rows)==2 and not monitor): + raise RuntimeError('Unexpected console panes; refusing to modify them') + original_pid=shell['pid'] + previous_focus=next((p['id'] for p in rows if p['active']),shell['id']) + previous_zoom=self.tmux('display-message','-p','-t',self.window,'#{window_zoomed_flag}')=='1' + if not monitor: + ident=self.tmux('split-window','-d','-h','-l','30%','-t',shell['id'],'-P','-F','#{pane_id}',self.monitor_command()) + monitor=next(p for p in self.panes() if p['id']==ident) + else: + try: + cmd=Path('/proc',str(monitor['pid']),'cmdline').read_bytes() + except OSError: + cmd=b'' + if self.a.refresh_monitor or monitor['dead'] or b'framebridge_console.py' not in cmd: + self.tmux('respawn-pane','-k','-t',monitor['id'],self.monitor_command()) + for role,pane in [('shell',shell),('monitor',monitor)]: + self.tmux('set-option','-p','-t',pane['id'],'@framebridge_role',role) + self.tmux('set-option','-p','-t',monitor['id'],'remain-on-exit','on') + self.tmux('select-pane','-t',shell['id'],'-T','SHELL | '+self.a.node+' | '+getpass.getuser()) + self.tmux('select-pane','-t',monitor['id'],'-T','FBTERM | ACTIVITY') + self.tmux('set-window-option','-t',self.window,'automatic-rename','off') + self.tmux('rename-window','-t',self.window,'Console') + for key,value in [('pane-border-status','top'),('pane-border-style','fg=blue'), + ('pane-active-border-style','fg=cyan'), + ('pane-border-format',' #{?pane_active,>, } #{pane_title} '), + ('window-status-format',''),('window-status-current-format','')]: + self.tmux('set-window-option','-t',self.window,key,value) + for key,value in [('status','on'),('status-style','bg=blue,fg=white'), + ('status-left','#[bold] FRAMEBRIDGE #[default] | '+self.a.node+' | '), + ('status-left-length','80'),('status-right-length','110'), + ('status-right',' Ctrl+B, then o: switch | z: zoom | %H:%M %Z ')]: + self.tmux('set-option','-t',self.session_target,key,value) + self.tmux('set-option','-t',self.session_target,'mouse','off') + # This server is dedicated to FrameBridge; do not alter global tmux configuration. + for key in ('z','C-z','Z'): + self.tmux('bind-key','-T','prefix',key,'resize-pane','-Z') + # Reattachment must preserve the user's zoom and pane focus. + if not previous_zoom: + self.layout(shell,monitor) + command=shlex.join(['/usr/bin/python3',str(HERE/'framebridge_console.py'),'--node',self.a.node, + '--socket',self.a.socket,'--session',self.a.session, + '--layout',getattr(self.a,'layout','auto'),'--reflow']) + self.tmux('set-hook','-t',self.session_target,'client-resized[90]','run-shell -b '+shlex.quote(command)) + self.tmux('set-hook','-t',self.session_target,'client-attached[90]','run-shell -b '+shlex.quote(command)) + self.tmux('select-window','-t',self.window) + selected=previous_focus if existed else shell['id'] + self.tmux('select-pane','-t',selected) + if previous_zoom and self.tmux('display-message','-p','-t',self.window,'#{window_zoomed_flag}')!='1': + self.tmux('resize-pane','-Z','-t',selected) + current=next(p for p in self.panes() if p['id']==shell['id']) + if current['pid']!=original_pid: + raise RuntimeError('Bash identity changed unexpectedly') + + +def supervise(node): + while True: + try: + result=subprocess.run(['/usr/bin/python3','-u',str(HERE/'fbterm_dashboard.py'),'--node',node]) + print('\r\nFBTERM monitor stopped (exit '+str(result.returncode)+'). Restarting in 2 seconds.',flush=True) + print('Ctrl+B o switches to the shell.',flush=True) + time.sleep(2) + except KeyboardInterrupt: + print('\r\nFBTERM monitor restarting. Ctrl+B o switches to the shell.',flush=True) + time.sleep(1) + + +def main(): + args=arguments() + if args.monitor: + supervise(args.node) + return 0 + console=Console(args) + if args.probe: + print(json.dumps({'node':args.node,'socket':args.socket,'session':args.session, + 'panes':console.panes() if console.exists() else [],'login':'existing-authenticated-login'})) + return 0 + if not (args.apply or args.reflow): + if not os.isatty(0) or os.ttyname(0)!='/dev/tty1' or os.environ.get('TMUX') or os.environ.get('SSH_TTY'): + print('Open this console from an authenticated tty1 login. --probe is read-only.',file=sys.stderr) + return 1 + parent=Path(args.socket).parent + parent.mkdir(mode=0o700,parents=True,exist_ok=True) + info=parent.stat() + if info.st_uid!=os.getuid() or info.st_mode & 0o022: + raise RuntimeError('Console socket directory is not private to this user') + fd=os.open(parent/'console-layout.lock',os.O_CREAT|os.O_RDWR|os.O_CLOEXEC|os.O_NOFOLLOW,0o600) + try: + info=os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_uid!=os.getuid(): + raise RuntimeError('Invalid console layout lock') + fcntl.flock(fd,fcntl.LOCK_EX) + console.apply() + finally: + os.close(fd) + if args.apply: + print(json.dumps({'node':args.node,'status':'READY','panes':console.panes()})) + elif not args.reflow: + os.execve('/usr/bin/tmux',['tmux','-S',args.socket,'attach-session','-t',console.session_target],console.env) + return 0 + + +if __name__=='__main__': + try: + raise SystemExit(main()) + except (RuntimeError,OSError,subprocess.TimeoutExpired) as e: + print('FrameBridge console: '+str(e),file=sys.stderr) + raise SystemExit(1) diff --git a/source/expanded/framebridge_expanded/display_context.py b/source/expanded/framebridge_expanded/display_context.py new file mode 100644 index 0000000..a1b4662 --- /dev/null +++ b/source/expanded/framebridge_expanded/display_context.py @@ -0,0 +1,156 @@ +"""Bind graphical CUA to a verified physical X11 session or labelled virtual display.""" +from __future__ import annotations +import json +import shutil +from .real_screen.screen_capture import CaptureError, foreground, _x11_context + +BINDING = "FRAMEBRIDGE_DISPLAY_BINDING" +GRAPHICAL_COMMANDS = ("wmctrl", "xdotool", "xwininfo", "xclip") +VIRTUAL_COMMANDS = ("Xvfb", "openbox", "xauth") + + +def missing_graphical_commands(env): + path = env.get("PATH") + missing = [name for name in GRAPHICAL_COMMANDS if not shutil.which(name, path=path)] + if not (shutil.which("magick", path=path) or shutil.which("convert", path=path)): + missing.append("imagemagick") + return missing + + +def bind_text(env, profile, *, reason="PHYSICAL_X11_SESSION_UNAVAILABLE"): + context = {"kind": "text", "physical": False, "explicit": False, + "selection": "auto", "profile": str(profile), "display": "", "reason": reason} + for name in ("DISPLAY", "XAUTHORITY", "GDK_BACKEND"): + env.pop(name, None) + env["XDG_SESSION_TYPE"] = "tty" + env[BINDING] = json.dumps(context, sort_keys=True, separators=(",", ":")) + return context + +FIELDS = ("session_id", "uid", "leader", "active_vt", "seat", "vtnr", "display") + +class DisplayContextError(RuntimeError): + pass + +def _physical(): + current = foreground(allow_graphics=True) + if current.session_type != "x11": + raise DisplayContextError("PHYSICAL_X11_SESSION_UNAVAILABLE") + display, authority = _x11_context(current, None, None) + return { + "kind": "physical-x11", "physical": True, + "session_id": current.session_id, "uid": current.uid, + "leader": current.leader, "active_vt": current.active_vt, + "seat": current.seat, "vtnr": current.vtnr, "display": display, + "authority": authority, + } + +def describe(env): + try: + context = json.loads(env.get(BINDING, "{}")) + except (TypeError, ValueError): + return {"kind": "unavailable", "physical": False, "error": "DISPLAY_BINDING_INVALID"} + if not isinstance(context, dict): + return {"kind": "unavailable", "physical": False, "error": "DISPLAY_BINDING_INVALID"} + return {k: v for k, v in context.items() if k != "authority"} + +def configure(env, headless, profile, *, refresh=False): + profile = str(profile) + try: + previous = json.loads(env.get(BINDING, "{}")) + except (TypeError, ValueError): + previous = {} + if not isinstance(previous, dict): + previous = {} + # The manager and worker share the same immutable initial binding. + mode_compatible = ((headless in ("yes", True) and previous.get("kind") == "virtual-x11" and previous.get("explicit") is True) + or (headless in ("no", False) and previous.get("kind") == "physical-x11") + or (headless == "auto" and previous.get("kind") in ("physical-x11", "text") and previous.get("selection") == "auto")) + if not refresh and previous.get("profile") == profile and previous.get("kind") in ("physical-x11", "virtual-x11", "text") and mode_compatible: + return env + explicit = headless in ("yes", True) + if explicit: + context = {"kind": "virtual-x11", "physical": False, "explicit": True, + "display": env.get("DISPLAY", "")} + else: + try: + context = _physical() + if headless == "auto" and missing_graphical_commands(env): + bind_text(env, profile, reason="GRAPHICAL_CUA_TOOLS_UNAVAILABLE") + return env + env["DISPLAY"] = context["display"] + env["XDG_SESSION_TYPE"] = "x11" + env["GDK_BACKEND"] = "x11" + if context.get("authority"): + env["XAUTHORITY"] = context["authority"] + else: + env.pop("XAUTHORITY", None) + except (CaptureError, DisplayContextError, OSError) as exc: + code = getattr(exc, "code", str(exc)) or "PHYSICAL_X11_SESSION_UNAVAILABLE" + if headless == "auto": + bind_text(env, profile, reason=code) + return env + context = {"kind": "unavailable", "physical": False, "explicit": False, + "display": "", "reason": code} + context["selection"] = "yes" if explicit else ("auto" if headless == "auto" else "no") + context["profile"] = profile + env[BINDING] = json.dumps(context, sort_keys=True, separators=(",", ":")) + return env + +def bind_virtual(env, profile, *, explicit): + context = {"kind": "virtual-x11", "physical": False, "explicit": bool(explicit), + "profile": str(profile), "display": env.get("DISPLAY", "")} + env[BINDING] = json.dumps(context, sort_keys=True, separators=(",", ":")) + return context + +def prepare(env): + """Select the current real desktop once per request; guards then pin it.""" + try: + previous = json.loads(env.get(BINDING, "{}")) + except (TypeError, ValueError): + return + if isinstance(previous, dict) and previous.get("selection") == "auto": + configure(env, "auto", previous.get("profile", ""), refresh=True) + current = json.loads(env[BINDING]) + changed = current.get("kind") != previous.get("kind") or any(current.get(k) != previous.get(k) for k in FIELDS) + if previous.get("needs_observation") or changed: + current["needs_observation"] = True + env[BINDING] = json.dumps(current, sort_keys=True, separators=(",", ":")) + + +def observed(env): + current = check(env) + if current.get("needs_observation"): + current = dict(current) + current.pop("needs_observation", None) + env[BINDING] = json.dumps(current, sort_keys=True, separators=(",", ":")) + + +def check(env): + try: + expected = json.loads(env.get(BINDING, "{}")) + except (TypeError, ValueError): + raise DisplayContextError("DISPLAY_BINDING_INVALID") from None + if not isinstance(expected, dict): + raise DisplayContextError("DISPLAY_BINDING_INVALID") + if expected.get("kind") == "physical-x11": + try: + current = _physical() + except (CaptureError, OSError, DisplayContextError): + raise DisplayContextError("CUA_FOREGROUND_SESSION_CHANGED") from None + if any(current.get(k) != expected.get(k) for k in FIELDS): + raise DisplayContextError("CUA_FOREGROUND_SESSION_CHANGED") + if env.get("DISPLAY") != expected.get("display"): + raise DisplayContextError("CUA_DISPLAY_BINDING_MISMATCH") + elif expected.get("kind") == "virtual-x11": + if not expected.get("display") or env.get("DISPLAY") != expected["display"]: + raise DisplayContextError("VIRTUAL_DISPLAY_BINDING_MISMATCH") + else: + raise DisplayContextError("CUA_DISPLAY_UNAVAILABLE") + return expected + + +def private_display_required(env, headless, available): + physical = describe(env).get("kind") == "physical-x11" + if headless in ("no", False) and (not physical or not available): + raise DisplayContextError("PHYSICAL_X11_UNAVAILABLE_NO_VIRTUAL_FALLBACK") + return headless in ("yes", True) diff --git a/source/expanded/framebridge_expanded/fleet.py b/source/expanded/framebridge_expanded/fleet.py new file mode 100644 index 0000000..6a17b8f --- /dev/null +++ b/source/expanded/framebridge_expanded/fleet.py @@ -0,0 +1,340 @@ +"""Original Fleet providers and generic confirmed-node routing.""" +from __future__ import annotations + +import argparse +import hashlib +import os +from pathlib import Path +import re +import socket +import sys +import time +import uuid +from typing import Any + +JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +READ_CAPABILITIES = frozenset({ + "system.status", "hardware.summary", "storage.inspect", + "network.inspect", "service.status", +}) + + +class FleetError(ValueError): + """Stable public error codes only.""" + + +def _require(condition: object, code: str) -> None: + if not condition: + raise FleetError(code) + + +def original_paths() -> tuple[Path, Path]: + root = Path(__file__).resolve().parents[2] + choices = [ + ("FRAMEBRIDGE_BROKER_SOURCE", "FrameBridge-Broker-RC1/src"), + ("FRAMEBRIDGE_FLEET_SOURCE", "FrameBridge-Fleet-Hub/app"), + ] + result = [] + for variable, suffix in choices: + configured = os.environ.get(variable) + candidates = [Path(configured)] if configured else [ + root / "original/geranas" / suffix, + root / "sources/original-geranas" / suffix, + ] + normalized = [] + for candidate in candidates: + nested = candidate / ("src" if variable == "FRAMEBRIDGE_BROKER_SOURCE" else "app") + if (candidate / "framebridge_linux_chatgpt").is_dir(): + normalized.append(candidate) + elif (nested / "framebridge_linux_chatgpt").is_dir(): + normalized.append(nested) + path = next(iter(normalized), None) + _require(path is not None, "ORIGINAL_MODULES_UNAVAILABLE") + result.append(path) + return result[0], result[1] + + +def bootstrap_original() -> None: + """Extend the original package search path without copying or merging it.""" + broker_source, fleet_source = original_paths() + for path in (fleet_source, broker_source): + if str(path) in sys.path: + sys.path.remove(str(path)) + sys.path.insert(0, str(path)) + import framebridge_linux_chatgpt + package_file = Path(framebridge_linux_chatgpt.__file__).resolve() + _require(package_file.is_relative_to(broker_source.resolve()), "ORIGINAL_PACKAGE_SHADOWED") + for root in (broker_source, fleet_source): + path = str(root / "framebridge_linux_chatgpt") + if path not in framebridge_linux_chatgpt.__path__: + framebridge_linux_chatgpt.__path__.append(path) + + +def state_directory() -> Path: + raw = os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") or os.environ.get("FRAMEBRIDGE_STATE_DIR") + _require(bool(raw), "EXPANDED_STATE_REQUIRED") + return Path(raw).expanduser() + + +def local_config() -> dict[str, Any]: + from .config import load_config + config = load_config(state_directory()) + _require(type(config) is dict, "PROFILE_CONFIG_INVALID") + _require(config.get("role") in ("standalone", "central", "node"), "PROFILE_ROLE_INVALID") + _require(type(config.get("name")) is str and JOB_RE.fullmatch(config["name"]), "PROFILE_NAME_INVALID") + try: + uuid.UUID(str(config["node_id"])) + except (KeyError, ValueError, TypeError): + raise FleetError("PROFILE_NODE_ID_INVALID") from None + return config + + +def _local_descriptor(config: dict[str, Any]) -> dict[str, Any]: + bootstrap_original() + from framebridge_linux_chatgpt.seed_capabilities import execute_capability + _require(callable(execute_capability), "LOCAL_READ_PROVIDER_UNAVAILABLE") + return { + "alias": config["name"], "node_id": str(config["node_id"]), + "hostname": socket.gethostname(), "transport": "local", + "target": "LOCAL", "capability_profile": "original-readonly", + "source": "local", "operational_transport_ready": True, + } + + +def _remote_descriptors(config: dict[str, Any]) -> list[dict[str, Any]]: + if config["role"] != "central": + return [] + from . import broker + items = broker.list_confirmed_nodes() + _require(type(items) is list, "BROKER_NODE_LIST_INVALID") + result = [] + for item in items: + _require(type(item) is dict, "BROKER_NODE_INVALID") + alias = item.get("alias") + node_id = item.get("node_id") + _require(type(alias) is str and JOB_RE.fullmatch(alias), "BROKER_NODE_ALIAS_INVALID") + try: + uuid.UUID(str(node_id)) + except (ValueError, TypeError): + raise FleetError("BROKER_NODE_ID_INVALID") from None + result.append({ + "alias": alias, "node_id": str(node_id), + "hostname": str(item.get("hostname") or alias), + "transport": "broker_outbound", "target": alias, + "capability_profile": "original-readonly", + "source": "confirmed_pairing", + "operational_transport_ready": item.get("ready") is True, + }) + return result + + +def descriptors() -> list[dict[str, Any]]: + config = local_config() + result = [_local_descriptor(config), *_remote_descriptors(config)] + namespace: set[str] = set() + for item in result: + for value in (item["alias"], item["node_id"]): + _require(value not in namespace and (value != "LOCAL" or item["transport"] == "local"), + "FLEET_IDENTITY_COLLISION") + namespace.add(value) + return result + + +def discover_nodes() -> dict[str, Any]: + try: + nodes = descriptors() + return {"ok": True, "nodes": sorted(nodes, key=lambda item: item["alias"]), + "sources": {"local": 1, "confirmed_pairing": len(nodes) - 1}} + except Exception as exc: + return {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "FLEET_DISCOVERY_FAILED"} + + +def resolve_node(alias: str) -> dict[str, Any]: + _require(type(alias) is str and JOB_RE.fullmatch(alias), "NODE_ALIAS_INVALID") + nodes = descriptors() + if alias == "LOCAL": + return next(item for item in nodes if item["transport"] == "local") + matches = [item for item in nodes if alias in (item["alias"], item["node_id"])] + _require(len(matches) == 1, "NODE_NOT_FOUND") + return matches[0] + + +def _normalize_read(capability: str, args: dict[str, Any]) -> dict[str, Any]: + bootstrap_original() + from framebridge_linux_chatgpt.fleet_routing import ReadCapability, normalize_read_args + _require(capability in READ_CAPABILITIES, "FLEET_READ_CAPABILITY_DENIED") + try: + return normalize_read_args(ReadCapability(capability), args) + except (TypeError, ValueError): + raise FleetError("FLEET_READ_ARGUMENTS_INVALID") from None + + +def read_local(capability: str, args: dict[str, Any]) -> dict[str, Any]: + normalized = _normalize_read(capability, args) + from framebridge_linux_chatgpt.seed_capabilities import execute_capability + data = execute_capability(capability, normalized) + _require(type(data) is dict, "LOCAL_READ_RESPONSE_INVALID") + if data.get("ok") is False: + return data + return {"ok": True, "capability": capability, "result": data} + + +def node_read(alias: str, capability: str, args: dict[str, Any]) -> dict[str, Any]: + try: + normalized = _normalize_read(capability, args) + node = resolve_node(alias) + if node["transport"] == "local": + result = read_local(capability, normalized) + else: + _require(node["operational_transport_ready"], "NODE_OFFLINE") + from . import broker + result = broker.request(node["node_id"], capability, normalized, timeout_s=30) + _require(type(result) is dict, "NODE_READ_RESPONSE_INVALID") + response = dict(result) + response.update(node=node["alias"], hostname=node["hostname"], transport=node["transport"]) + return response + except Exception as exc: + return {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_READ_FAILED"} + + +def _exec_payload(script: str, authorized: bool, timeout_s: int, job_id: str, + sha256: str, include_stdout: bool) -> dict[str, Any]: + _require(authorized is True, "AUTHORIZED_TRUE_REQUIRED") + _require(type(script) is str and bool(script), "SCRIPT_REQUIRED") + data = script.encode("utf-8") + _require(len(data) <= 262144, "SCRIPT_TOO_LARGE") + digest = hashlib.sha256(data).hexdigest() + _require(not sha256 or (type(sha256) is str and sha256.lower() == digest), "SCRIPT_SHA256_MISMATCH") + try: + timeout = int(timeout_s) + except (TypeError, ValueError): + raise FleetError("INVALID_TIMEOUT") from None + _require(1 <= timeout <= 600, "INVALID_TIMEOUT") + job = job_id or "node-mcp-" + time.strftime("%Y%m%d-%H%M%S") + "-" + uuid.uuid4().hex[:12] + _require(type(job) is str and JOB_RE.fullmatch(job), "INVALID_JOB_ID") + return {"version": 1, "mode": "EXEC", "authorized": True, "script": script, + "timeout_s": timeout, "job_id": job, "sha256": digest, + "include_stdout": include_stdout is True} + + +def execute_node(alias: str, script: str, authorized: bool, timeout_s: int = 120, + job_id: str = "", sha256: str = "", include_stdout: bool = False) -> dict[str, Any]: + try: + payload = _exec_payload(script, authorized, timeout_s, job_id, sha256, include_stdout) + node = resolve_node(alias) + if node["transport"] == "local": + from .runtime import adapter_request + result = adapter_request(payload) + else: + _require(node["operational_transport_ready"], "NODE_OFFLINE") + from . import broker + result = broker.request(node["node_id"], "admin.exec", payload, timeout_s=payload["timeout_s"]) + _require(type(result) is dict, "NODE_EXEC_RESPONSE_INVALID") + response = dict(result) + if response.get("ok") is True: + _require(response.get("mcp_job_id", response.get("job_id")) == payload["job_id"], + "NODE_EXEC_JOB_BINDING_INVALID") + _require(response.get("mcp_script_sha256", response.get("command_sha256")) == payload["sha256"], + "NODE_EXEC_HASH_BINDING_INVALID") + response.update(target="LOCAL" if node["transport"] == "local" else node["alias"], + node_id=node["node_id"], mcp_job_id=payload["job_id"], + mcp_script_sha256=payload["sha256"]) + if include_stdout is not True: + response.pop("stdout", None) + return response + except Exception as exc: + return {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_EXEC_FAILED"} + + +def execute_screen_node(alias: str, script: str, authorized: bool, timeout_s: int = 30, + job_id: str = "", sha256: str = "", include_stdout: bool = False) -> dict[str, Any]: + """Use the same confirmed-node authority with an explicit non-shell screen operation.""" + from .real_screen.screen_transport import REQUEST_TYPE, validate_payload, ScreenTransportError + try: + _require(type(timeout_s) is int and 1 <= timeout_s <= 30, "INVALID_SCREEN_TIMEOUT") + payload = _exec_payload(script, authorized, timeout_s, job_id, sha256, include_stdout) + payload["request_type"] = REQUEST_TYPE + validate_payload(payload) + node = resolve_node(alias) + if node["transport"] == "local": + from .runtime import adapter_request + result = adapter_request(payload) + else: + _require(node["operational_transport_ready"], "NODE_OFFLINE") + from . import broker + result = broker.request(node["node_id"], "admin.exec", payload, timeout_s=payload["timeout_s"]) + _require(type(result) is dict, "NODE_EXEC_RESPONSE_INVALID") + response = dict(result) + if response.get("ok") is True: + _require(response.get("mcp_job_id", response.get("job_id")) == payload["job_id"], + "NODE_EXEC_JOB_BINDING_INVALID") + _require(response.get("mcp_script_sha256", response.get("command_sha256")) == payload["sha256"], + "NODE_EXEC_HASH_BINDING_INVALID") + if response.get("adapter_error") == "ValueError:UNSUPPORTED_REQUEST_TYPE": + # An old adapter rejects this type before EXEC. Never retry it as shell code. + from .real_screen.screen_rpc import encode + response.update(ok=False, exit_code="1", stdout_truncated=False, + stdout=encode({"ok": False, "error": {"code": "SCREEN_RPC_UNSUPPORTED_ON_NODE"}}).decode("ascii") + "\n") + response.update(target="LOCAL" if node["transport"] == "local" else node["alias"], + node_id=node["node_id"], mcp_job_id=payload["job_id"], + mcp_script_sha256=payload["sha256"]) + return response + except Exception as exc: + return {"ok": False, "error": str(exc) if isinstance(exc, (FleetError, ScreenTransportError)) else "SCREEN_RPC_FAILED"} + + +def cua_node(alias: str, payload: dict[str, Any], authorized: bool, + timeout_s: int = 60, job_id: str = "") -> dict[str, Any]: + try: + _require(authorized is True, "AUTHORIZED_TRUE_REQUIRED") + _require(type(payload) is dict, "CUA_PAYLOAD_REQUIRED") + _require(payload.get("action") in {"OBSERVE", "CLICK", "DOUBLE_CLICK", "TYPE", "KEY", "SCROLL", "DRAG"}, + "CUA_ACTION_UNSUPPORTED") + try: + timeout = int(timeout_s) + except (TypeError, ValueError): + raise FleetError("INVALID_CUA_TIMEOUT") from None + _require(1 <= timeout <= 180, "INVALID_CUA_TIMEOUT") + job = job_id or "cua-node-" + uuid.uuid4().hex + _require(type(job) is str and JOB_RE.fullmatch(job), "INVALID_JOB_ID") + request = {"version": 1, "mode": "EXEC", "request_type": "cua", + "authorized": True, "timeout_s": timeout, "job_id": job, "cua": dict(payload)} + node = resolve_node(alias) + if node["transport"] == "local": + from .runtime import adapter_request + result = adapter_request(request) + else: + _require(node["operational_transport_ready"], "NODE_OFFLINE") + from . import broker + result = broker.request(node["node_id"], "admin.cua", request, timeout_s=timeout) + _require(type(result) is dict, "NODE_CUA_RESPONSE_INVALID") + response = dict(result) + if response.get("ok") is True: + _require(response.get("job_id") == job, "NODE_CUA_JOB_BINDING_INVALID") + response.update(target="LOCAL" if node["transport"] == "local" else node["alias"], node_id=node["node_id"]) + return response + except Exception as exc: + return {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_CUA_FAILED"} + + +def main() -> None: + parser = argparse.ArgumentParser(description="Original six-tool Fleet READ Hub") + parser.add_argument("command", choices=("serve",), nargs="?", default="serve") + parser.parse_args() + bootstrap_original() + from mcp.server.fastmcp import FastMCP + from framebridge_linux_chatgpt.fleet_hub import FleetHub + from framebridge_linux_chatgpt.fleet_mcp_surface import register_fleet_read_tools + host = os.environ.get("FRAMEBRIDGE_FLEET_HUB_HOST", "127.0.0.1") + _require(host in ("127.0.0.1", "::1"), "FLEET_HUB_HOST_INVALID") + port = int(os.environ.get("FRAMEBRIDGE_FLEET_HUB_PORT", "20877")) + _require(1024 <= port <= 65535, "FLEET_HUB_PORT_INVALID") + server = FastMCP("FrameBridge Original Expanded Fleet", host=host, port=port, + streamable_http_path="/mcp") + hub = FleetHub(discover_nodes, node_read) + register_fleet_read_tools(server, hub) + server.run(transport="streamable-http") + + +if __name__ == "__main__": + main() diff --git a/source/expanded/framebridge_expanded/guide.py b/source/expanded/framebridge_expanded/guide.py new file mode 100644 index 0000000..c41911f --- /dev/null +++ b/source/expanded/framebridge_expanded/guide.py @@ -0,0 +1,63 @@ +"""Versioned, read-only English guide shared by CLI, MCP and website documentation.""" +from __future__ import annotations +import copy +import hashlib +import json +from . import __version__ + +PRODUCT = "FrameBridge" +GUIDE_SCHEMA = 1 +from .guide_data import GUIDE as _GUIDE + +def topics(): + """Return the bounded public guide-topic names.""" + return tuple(_GUIDE) + +def _digest(value): + raw = json.dumps(value, sort_keys=True, ensure_ascii=True, separators=(",", ":")).encode() + return hashlib.sha256(raw).hexdigest() + +def get_guide(topic="start"): + """Read static help without touching profiles, credentials, processes or network.""" + if not isinstance(topic, str) or len(topic) > 64: + return {"ok": False, "error": "HELP_TOPIC_INVALID", "topics": list(topics())} + topic = topic.strip().lower() + if topic == "all": + sections = [] + for name, entry in _GUIDE.items(): + sections.append({"heading": entry["title"], "body": "Guide topic: " + name, "commands": []}) + sections.extend(copy.deepcopy(entry["sections"])) + title = "FrameBridge complete command guide" + elif topic in _GUIDE: + entry = _GUIDE[topic] + title, sections = entry["title"], copy.deepcopy(entry["sections"]) + else: + return {"ok": False, "error": "HELP_TOPIC_UNKNOWN", "topic": topic, "topics": list(topics()), + "product": PRODUCT, "version": __version__, "guide_schema": GUIDE_SCHEMA} + value = {"ok": True, "product": PRODUCT, "version": __version__, "guide_schema": GUIDE_SCHEMA, + "topic": topic, "title": title, "sections": sections, "topics": list(topics()), + "read_only": True, "capabilities_verified": False} + value["guide_sha256"] = _digest(value) + return value + +def render_text(topic="start"): + """Render the same guide as a navigable offline text reference.""" + guide = get_guide(topic) + if not guide["ok"]: + raise ValueError("Unknown help topic. Choose: " + ", ".join(topics())) + lines = [guide["title"], "FrameBridge " + guide["version"] + " | guide schema " + str(GUIDE_SCHEMA), ""] + for section in guide["sections"]: + lines.extend([section["heading"], section["body"], ""]) + lines.extend(" " + command for command in section["commands"]) + if section["commands"]: + lines.append("") + if topic != "all": + lines.append("More help: fb help " + " | ".join(topics())) + return "\n".join(lines).rstrip() + "\n" + +def export_guide(): + """Return the versioned static export used to generate website documentation.""" + value = {"product": PRODUCT, "version": __version__, "guide_schema": GUIDE_SCHEMA, + "topics": {name: get_guide(name) for name in topics()}} + value["guide_sha256"] = _digest(value) + return value diff --git a/source/expanded/framebridge_expanded/guide_data.py b/source/expanded/framebridge_expanded/guide_data.py new file mode 100644 index 0000000..262f904 --- /dev/null +++ b/source/expanded/framebridge_expanded/guide_data.py @@ -0,0 +1,430 @@ +"""Shared English guide content. Regenerate command docs after editing.""" + +GUIDE = {'start': {'title': 'Start with FrameBridge', + 'sections': [{'heading': 'One interface for people and agents', + 'body': 'FrameBridge provides tools and a shared command-line interface ' + 'for human operators and authorized agents. fb is the short name ' + 'for framebridge; both run the same program. The tools run on ' + 'your Linux computers. The ChatGPT model remains hosted. Read ' + 'this guide for the installed version before operating; confirm ' + 'the receiver, target and available capabilities. Use the ' + 'installed CLI and tools for normal work. Edit software only when ' + 'the owner explicitly requests development, using a separate ' + 'source clone and reviewable changes rather than rewriting the ' + 'installed program.', + 'commands': ['fb help', 'fb status', 'fb --json status']}, + {'heading': 'Install and connect', + 'body': 'On a supported Debian system, install the downloaded package ' + 'with the package manager. Normal first connection does not ' + 'require a separate setup, doctor or start sequence. Run connect ' + 'in a normal interactive terminal. Advanced setup is optional for ' + 'selecting central/node roles and public settings.', + 'commands': ['sudo apt install ./FrameBridge-0.3.1.deb', 'fb connect']}, + {'heading': 'Updating and removal', + 'body': 'Install an official update through the normal package manager. ' + 'Normal package removal stops package-owned managed processes and ' + 'retains the profile and saved encrypted connection material for ' + 'deliberate reuse; it is not a credential-forgetting operation. ' + 'Use the separately documented explicit forget action first if ' + 'that is your intended outcome. Do not assume purge erases ' + 'user-profile data.', + 'commands': ['sudo dpkg --remove framebridge-original-expanded']}, + {'heading': 'Human authentication', + 'body': 'The normal connection asks for an optional account label, a ' + 'hidden tunnel ID and a hidden MCP server access key. The label ' + 'is metadata, not proof of ChatGPT login. Browser login/consent ' + 'and normal operating-system authentication remain human actions ' + 'in the normal interface. Never put credentials in chat, command ' + 'arguments, documentation or example files. A successful managed ' + 'connection returns to the prompt and can run independently of ' + 'that terminal. Reboot only when an actual system change requires ' + 'it.', + 'commands': []}, + {'heading': 'Choose the right guide', + 'body': 'Use commands for the reference, connection and lifecycle for ' + 'state changes, nodes for central/node work, terminals for ' + 'persistent Bash, and screens for authentic CUA evidence. Query ' + 'success alone does not mean the connection is ready. The guide ' + 'describes registered operations; use status/doctor to ' + 'investigate current state.', + 'commands': ['fb help commands', + 'fb help connection', + 'fb help lifecycle']}]}, + 'commands': {'title': 'Command reference', + 'sections': [{'heading': 'Everyday commands', + 'body': 'connect establishes or resumes the managed ChatGPT ' + 'connection. disconnect pauses that connection until an ' + 'explicit connect, including across recovery and reboot. ' + 'status reports runtime, connection, intended connection state ' + 'and boot policy. doctor diagnoses prerequisites/problems. ' + 'setup edits advanced stopped-profile settings. console opens ' + 'the text work view. help reads this versioned reference.', + 'commands': ['fb connect', + 'fb disconnect', + 'fb status', + 'fb status connection', + 'fb status runtime', + 'fb status boot', + 'fb status console', + 'fb doctor', + 'fb setup', + 'fb console', + 'fb help']}, + {'heading': 'Services and boot', + 'body': 'For an already configured managed profile, service start/stop ' + 'control the current stack symmetrically. Boot enable/disable ' + 'changes future startup policy without stopping current work ' + 'or forgetting the saved credential. First node provisioning ' + 'is an explicit exception described in lifecycle. Without ' + 'managed setup, non-node service start starts local tools ' + 'only; normal first connection should use connect. Read status ' + 'before and after changes. The separate credentials forget ' + 'action is destructive and requires explicit confirmation.', + 'commands': ['fb service start', + 'fb service stop', + 'fb service boot enable', + 'fb service boot disable']}, + {'heading': 'Console and specialized operations', + 'body': 'console login manages optional work-view opening after an ' + 'authenticated tty1 login; it does not enable automatic login. ' + 'tty service manages the terminal daemon; tty list lists ' + 'sessions. nodes, certificates and screen-access retain their ' + 'domain-specific subcommands. Use --help at the relevant ' + 'command for its exact arguments. Avoid compatibility aliases ' + 'in new automation.', + 'commands': ['fb console login status', + 'fb tty service status', + 'fb tty list', + 'fb nodes --help', + 'fb certificates --help', + 'fb screen-access --help']}, + {'heading': 'Human and structured output', + 'body': 'Use explicit commands in automation rather than the ' + 'interactive no-argument menu. --json selects structured ' + 'output for an agent; examine operation success, readiness and ' + 'state separately. Read errors and the exit status. If human ' + 'interaction is required, complete it in the normal ' + 'terminal/interface rather than attempting to supply a secret ' + 'through the agent. CLI JSON uses an envelope with ' + 'schema_version, product, version, command, ok and result. ' + 'Read runtime, connection and boot truth inside result; ' + 'envelope ok denotes request success. Exit 0 means successful ' + 'query/operation, not necessarily a connected transport; exit ' + '1 reports operation/diagnostic failure, exit 2 invalid ' + 'arguments or required human interaction/confirmation, and ' + 'exit 130 cancellation.', + 'commands': ['framebridge --json status', + 'fb --json status connection']}]}, + 'connection': {'title': 'Connect and disconnect ChatGPT', + 'sections': [{'heading': 'Normal managed connection', + 'body': 'connect creates the default standalone profile when absent ' + 'and establishes managed startup. It asks for initial human ' + 'input in the terminal, checks progress, reports readiness ' + 'or an actionable failure, and returns to the prompt. Valid ' + 'saved setup can be resumed without repeating the initial ' + 'fields. The MCP access key is not an OpenAI API key. Keep ' + "the browser's ChatGPT account login and consent separate " + 'from the optional local account label.', + 'commands': ['fb connect', 'fb status connection']}, + {'heading': 'Saved credentials and next boot', + 'body': 'Managed startup uses protected, host-bound encrypted ' + 'connection material. The normal terminal does not have to ' + 'remain open. This is a persistence feature, not a promise ' + 'that an account, network or tunnel service can never fail. ' + 'Connection intent and boot policy are separate: enabled ' + 'startup runs the stack at boot, while an intentional ' + 'disconnected state remains disconnected until connect.', + 'commands': ['fb status boot', 'fb status']}, + {'heading': 'Intentional disconnect', + 'body': 'disconnect keeps local tools and saved material, but ' + 'records the intention to stay disconnected across worker ' + 'recovery and reboot. Explicit connect clears that pause. ' + 'Check the returned state rather than interpreting a ' + 'successful query as an active transport.', + 'commands': ['fb disconnect', 'fb status connection']}, + {'heading': 'Replacement and temporary foreground mode', + 'body': 'Use connect --replace only when deliberately replacing the ' + 'account label/tunnel/access configuration. The advanced ' + '--foreground mode is temporary, needs a ready local runtime ' + 'and occupies its terminal until interrupted; it is not the ' + 'normal managed workflow. Do not use advanced mode merely to ' + 'recover a normal saved connection.', + 'commands': []}]}, + 'lifecycle': {'title': 'Services, connection intent and boot policy', + 'sections': [{'heading': 'Separate states', + 'body': 'FrameBridge distinguishes actual local runtime, desired ' + 'connection, verified transport, boot enablement and saved ' + 'credential presence. A healthy runtime can be intentionally ' + 'disconnected; a service can be active while disabled for the ' + 'next boot. Read the labelled status fields together.', + 'commands': ['fb status', 'fb --json status']}, + {'heading': 'Current managed stack', + 'body': 'For an already configured managed profile, service start and ' + 'service stop use the same current-stack boundary without ' + 'changing boot enablement. Stop ends current managed work and ' + 'transport but keeps profile, boot policy and saved material. ' + 'Startup on the next boot follows the retained policy and ' + 'intended connection state. A non-node profile without ' + 'managed setup starts only local tools. Stopping a terminal ' + 'service or session can interrupt its running work.', + 'commands': ['fb service start', 'fb service stop']}, + {'heading': 'Future boot policy', + 'body': 'Once managed setup exists, service boot enable/disable ' + 'changes unit enablement only. It does not immediately stop ' + 'or start current work and does not delete saved connection ' + 'material. Initial node provisioning is described separately ' + 'below. Explicitly forgetting saved material is a separate ' + 'destructive operation. Legacy autostart disable has its old ' + 'broader destructive behavior; use the canonical service boot ' + 'commands for new workflows.', + 'commands': ['fb service boot enable', + 'fb service boot disable', + 'fb status boot']}, + {'heading': 'First managed startup for a node', + 'body': 'After advanced node setup and the required ' + 'enrollment/approval, a node without a managed installation ' + 'can run service start in a normal terminal. This first call ' + 'creates managed startup, enables future boot and starts the ' + 'node now; the result explicitly reports ' + 'initial_configuration and boot_policy_changed. ' + 'Alternatively, first service boot enable prepares and ' + 'enables the node unit without starting current work. Initial ' + 'preparation can require normal owner authentication and is ' + 'not silently performed by an unattended JSON request. Once ' + 'configured, the ordinary start/stop and boot-policy-only ' + 'contracts apply. A node does not need its own ChatGPT ' + 'tunnel.', + 'commands': ['fb service start', + 'fb service boot enable', + 'fb status boot']}, + {'heading': 'Console opening', + 'body': 'Opening console is a view operation, separate from Linux ' + 'service startup. Optional console login enable/disable ' + 'changes the owned startup hook after an authenticated tty1 ' + 'Bash login. This hook does not bypass normal login. Review ' + "the command's scope before changing it.", + 'commands': ['fb console login status']}]}, + 'nodes': {'title': 'Central and node operation', + 'sections': [{'heading': 'Roles', + 'body': 'Standalone provides local tools. Central adds the broker for ' + 'enrolled nodes. A node joins one selected central instance and ' + 'uses its ChatGPT connection; it does not need another ChatGPT ' + 'tunnel or phone pairing. Advanced setup selects the role. ' + 'Preserve explicit authenticated node transport and real identity ' + 'approval.', + 'commands': ['fb setup', 'fb nodes --help']}, + {'heading': 'Enrollment is not availability', + 'body': 'Enrollment submission, central approval, node receipt of ' + 'authorization and a running node agent are separate stages. ' + 'status reports what it actually knows; an unchecked agent is not ' + 'evidence that the node is online. Central list/pending/approve ' + 'and node enroll/poll/status/run have role-specific purposes. ' + 'poll can complete saved authorization; it is not a purely ' + 'observational operation. After enrollment, consult the lifecycle ' + 'topic for first managed node startup and its explicit initial ' + 'boot-policy effect.', + 'commands': ['fb nodes list', 'fb nodes pending', 'fb nodes status']}, + {'heading': 'Targets', + 'body': 'LOCAL means the actual receiving machine. Use enrolled, ' + 'confirmed aliases or IDs and inspect the selected target before ' + 'work. Do not guess SSH identities or use historical lab ' + 'hostnames as defaults. CLI tty --node can use the authenticated ' + 'node transport. The interactive console command is local; run it ' + 'at the destination or use an explicitly configured SSH ' + 'destination.', + 'commands': []}, + {'heading': 'Transport trust', + 'body': 'certificates create manages a private central/node transport CA, ' + 'not a public website certificate or ChatGPT account ' + 'authentication. Private keys stay protected on the central host; ' + 'distribute only the required public trust material. Off-loopback ' + 'broker use requires correctly configured verified TLS. Do not ' + 'recreate trust merely to work around a connection symptom.', + 'commands': ['fb certificates --help']}]}, + 'terminals': {'title': 'Persistent Bash terminals', + 'sections': [{'heading': 'Service and sessions', + 'body': "tty service controls the profile's terminal daemon. tty open " + 'creates a real Bash session and returns its identifier. Use ' + 'that identifier for subsequent observations and input. The ' + 'same Bash preserves directory, variables and work between ' + 'requests while the session/service remains alive. It does ' + 'not preserve running processes across an operating-system ' + "reboot. Tool jobs use a profile-specific HOME; a job's home " + 'directory can therefore differ from the human login home. ' + "Commands retain the connecting account's normal " + 'operating-system permissions.', + 'commands': ['fb tty service status', 'fb tty list', 'fb tty open']}, + {'heading': 'Observe, type and keys', + 'body': 'tty observe returns terminal transcript text, cursor, ' + 'dimensions and process state; it is not an authentic ' + 'full-screen image. tty type sends text without automatically ' + 'adding Enter, though supplied newlines can affect execution. ' + 'tty key accepts exact uppercase names such as ENTER and ' + 'CTRL_C. Replace session arguments with an identifier ' + 'returned by open/list; never guess one.', + 'commands': ['fb tty key --help', + 'fb tty type --help', + 'fb tty observe --help']}, + {'heading': 'Lifetime and closing', + 'body': 'Default real idle expiry is disabled, but a profile can ' + 'configure a timeout. Global status/list does not refresh ' + "each session's per-session activity. close destroys a " + 'session rather than merely separating a viewer. Graceful ' + 'service stop attempts to close its sessions; elevated or ' + 'unverifiable process state can prevent cleanup. Inspect the ' + 'outcome instead of assuming that all child processes ' + 'disappeared.', + 'commands': ['fb tty close --help', 'fb tty resize --help']}, + {'heading': 'Sudo mode', + 'body': "A session's sudo on/off controls normal privilege " + 'eligibility; it does not log in as root or bypass ' + 'operating-system authentication. Changing the mode recreates ' + 'Bash, preserving directory and dimensions but not shell ' + 'variables or internal state. Authentication stays in the ' + 'normal human terminal/interface. Never send a password ' + 'through type or chat.', + 'commands': ['fb tty sudo --help']}, + {'heading': 'Work console panels', + 'body': 'console combines an ordinary Bash pane with a read-only ' + 'FBTERM activity dashboard. The dashboard is not a second ' + 'interactive shell. The tmux prefix is Ctrl+B: release both ' + 'keys, then press lowercase z to enlarge/restore a pane, or ' + 'lowercase o to switch panes. The release accepts common ' + 'zoom-key variants within its dedicated console. Closing a ' + 'viewer normally leaves its tmux session alive; leaving Bash, ' + 'killing its server or rebooting is different. In a desktop ' + 'terminal the shared work view is horizontal with FBTERM on ' + 'the left and Bash on the right. Explicit TTY mode is ' + 'vertical with a short FBTERM monitor above Bash. The layout ' + 'belongs to the shared tmux window and follows the last ' + 'requested view mode; it is not isolated per viewer.', + 'commands': ['fb console', 'fb status console']}]}, + 'screens': {'title': 'Authentic screen observation and input', + 'sections': [{'heading': 'Text transcripts and pixels', + 'body': 'A CLI tty transcript describes a terminal and explicitly does ' + 'not claim authentic pixels. MCP real-screen CUA-TTY and ' + 'graphical screen tools operate through the real screen client ' + 'and capture-linked interaction. Use those supported ' + 'observation tools when authentic complete screen evidence is ' + 'required. Never present a reconstructed terminal, animation or ' + 'partial invented view as a real screenshot.', + 'commands': []}, + {'heading': 'Automatic graphical discovery', + 'body': 'The installer and control console stay text interfaces in both ' + 'TTY and a desktop terminal. Compatible graphical CUA is ' + 'discovered automatically; availability depends on the actual ' + 'display/session and permissions. A loaded module is not proof ' + 'that real pixel capture or input succeeded. New profiles use ' + 'adaptive auto mode: without a verified active X11 session the ' + 'runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, ' + 'without requiring a graphical login or automatically creating ' + 'a private desktop. Explicit virtual-desktop settings remain ' + 'advanced options and are labelled separately from ' + 'physical-screen evidence.', + 'commands': ['fb status', 'fb doctor']}, + {'heading': 'Frame-buffer read access', + 'body': 'screen-access is a narrow, explicit administrative permission ' + 'for reading the real frame buffer. It does not by itself ' + 'enable graphical CUA, keyboard/mouse control or arbitrary VT ' + 'switching. Persistent read access can remain effective after ' + "logout or a VT switch; review the command's displayed scope " + 'and use normal local administrator authentication.', + 'commands': ['fb screen-access status']}, + {'heading': 'Shared desktop', + 'body': 'A graphical desktop shares focus, keyboard and mouse. Use one ' + 'controlling agent per desktop at a time and avoid overlapping ' + 'work. This is an operating recommendation, not a claim of ' + 'certified automatic isolation. Independent non-overlapping ' + 'execution jobs may be possible; do not infer isolation or ' + 'physical parallel execution from that alone.', + 'commands': []}]}, + 'diagnostics': {'title': 'Status and diagnosis', + 'sections': [{'heading': 'Inspect before acting', + 'body': 'status summarizes current runtime, connection, desired ' + 'state and boot policy. A query completing successfully is ' + 'distinct from the components being ready. Connection ' + 'verification has a freshness boundary; retrying or unknown ' + 'must not be treated as connected. Focused status uses the ' + 'same underlying state model.', + 'commands': ['fb status', + 'fb status connection', + 'fb status runtime', + 'fb status boot']}, + {'heading': 'Doctor', + 'body': 'doctor diagnoses prerequisites and reports findings with ' + 'suggested next steps. It is not an implicit repair, and it ' + 'is not a substitute for checking actual live connection ' + 'state. Inspect the diagnosis and relevant private ' + 'application logs before considering configuration changes.', + 'commands': ['fb doctor']}, + {'heading': 'Logs and progress', + 'body': 'Normal connection presents concise progress and a final ' + 'result, then returns to the prompt. Detailed diagnostics ' + 'belong in protected application logs. Share only the ' + 'minimum relevant redacted result; never expose ' + 'credentials, private keys, browser session data or ' + 'unrelated personal paths. Avoid rerunning setup, ' + 'enrollment or broad lifecycle tests merely because a view ' + 'is confusing.', + 'commands': ['fb help connection', 'fb help lifecycle']}]}, + 'security': {'title': 'Authorization, distribution and safe operation', + 'sections': [{'heading': 'Authorized scope', + 'body': 'An agent can use the documented CLI and MCP tools within the ' + "user's authorized scope. Reading this guide does not grant " + 'permission. Verify the receiver and selected target. Normal ' + 'human terminal/browser authentication remains separate; do ' + 'not request credentials in chat or invent phone/Owner-TTY ' + 'enrollment requirements. Normal use should operate through ' + 'the installed CLI and tools, not edit or rebuild installed ' + 'FrameBridge program files. If the owner explicitly requests ' + 'development, use a separate source clone, make reviewable ' + 'changes, and do not present a modified build as an official ' + 'release. This operating guidance does not restrict GNU GPL ' + 'freedoms or require private forks to submit changes upstream.', + 'commands': []}, + {'heading': 'Official releases', + 'body': 'The source is distributed under GNU GPLv3-only terms and the ' + 'applicable third-party notices. Verify official release ' + 'checksums and published signatures/repository metadata before ' + 'installing. Signature verification identifies the publisher ' + 'and detects alteration; it does not prohibit lawful modified ' + 'versions. Official releases are reviewed through the ' + "project's release process.", + 'commands': []}, + {'heading': 'Side effects', + 'body': 'Read status before service stop, credential forgetting, node ' + 'approval, startup-hook changes or administrative screen ' + 'permission changes. Explain material scope and consequences ' + "and follow the user's authorization. A sensitive operation " + 'must report the actual result, not just that a query ' + 'succeeded. Keep real captures complete and distinguish ' + 'observations from hypotheses.', + 'commands': []}]}, + 'compatibility': {'title': 'Compatibility with earlier commands', + 'sections': [{'heading': 'Preferred spelling', + 'body': 'Use the short canonical interface in new documentation ' + 'and automation. fb and framebridge remain equivalent. ' + 'Older connect chatgpt, disconnect chatgpt and connection ' + 'status spellings are compatibility routes. New status ' + 'scopes replace the need to learn an extra connection ' + 'noun.', + 'commands': ['fb connect', + 'fb disconnect', + 'fb status connection']}, + {'heading': 'Legacy lifecycle behavior', + 'body': 'Older root start/stop and autostart commands are ' + 'retained for compatibility where documented, but their ' + 'historical scopes can differ from the canonical service ' + 'controls. In particular, legacy autostart disable ' + 'removes managed setup and saved encrypted material; it ' + 'is not an alias for boot-policy-only disable. Read the ' + "command's help before using a legacy script.", + 'commands': ['fb help lifecycle']}, + {'heading': 'Versioned help', + 'body': 'This guide describes the installed product and guide ' + 'schema. Read the matching release notes for any semantic ' + 'migration. The CLI help, MCP guide and static web ' + 'documentation are generated from the same reviewed guide ' + 'data; a cached manual from an earlier build can describe ' + 'a different workflow.', + 'commands': ['fb help compatibility']}]}} diff --git a/source/expanded/framebridge_expanded/lifecycle.py b/source/expanded/framebridge_expanded/lifecycle.py new file mode 100644 index 0000000..0a09424 --- /dev/null +++ b/source/expanded/framebridge_expanded/lifecycle.py @@ -0,0 +1,202 @@ +"""Explicit lifecycle scopes shared by human and agent command interfaces.""" +from __future__ import annotations + +import hashlib +import os +import pwd +import time + +from . import autostart, connection_background, runtime +from .config import load_config, state_path + + +def _owned_setup(state): + state, directory, unit = autostart._paths(state) + record = autostart._manifest(directory) + if record and (record.get("unit") != unit.name or hashlib.sha256(autostart._read(unit)).hexdigest() != record.get("unit_sha256")): + raise RuntimeError("The managed service changed outside FrameBridge; no lifecycle change was made.") + return state, directory, unit, record + + +def _runtime_stopped(result): + if type(result) is not dict or result.get("ok") is False or result.get("status_unavailable") or result.get("running") is not False: + return False + processes = result.get("processes", []) + return type(processes) is list and all(type(item) is dict and item.get("alive") is False for item in processes) + + +def _settled_runtime_stop(state, result): + """Observe bounded asynchronous cleanup without treating query-ok as stopped.""" + if _runtime_stopped(result): + return result + deadline = time.monotonic() + 2 + while time.monotonic() < deadline: + time.sleep(0.05) + result = runtime.status(state) + if _runtime_stopped(result): + break + return result + + +def status(state): + """One observation model: actual tools/transport, intended transport and boot.""" + state = state_path(state) + configured = (state / "config.json").is_file() + config = load_config(state) if configured else {} + local = autostart._safe_runtime_status(runtime, state) if configured else {"running": False, "ready": False, "configured": False} + connection = connection_background.status(state) + directory = state / "autostart" + if directory.exists() or directory.is_symlink(): + startup = autostart.status(state) + else: + startup = {"configured": False, "enabled": False, "service_active": False, "service_state": "not_configured", "linger": None, "encrypted_credential_present": False} + boot = {key: startup.get(key) for key in ("configured", "enabled", "service_active", "service_state", "linger", "encrypted_credential_present", "unit")} + boot["desired_enabled"] = startup.get("boot_desired_enabled") + if config.get("role") == "node": + desired = "not_applicable" + elif not boot["configured"]: + desired = "not_configured" + else: + desired = "connected" if autostart.connection_requested(state) else "disconnected" + return {"ok": True, "configured": configured, "role": config.get("role"), "name": config.get("name"), + "runtime": local, "connection": connection, "boot": boot, + "desired_connection": desired, + "disconnect_scope": "until_explicit_connect", + "next_boot_connection": "paused" if desired == "disconnected" else ("requested" if desired == "connected" else "not_applicable"), + "worker": startup.get("worker", {"state": "not_configured"})} + + +def service_start(state): + """Start the configured stack now without changing its boot enablement.""" + def action(value): + state, directory, unit, record = _owned_setup(value) + if not record and load_config(state)["role"] == "node": + autostart._enable(state, start_now=True, confirm=False) + return {"ok": True, "state": "start_requested", "scope": "managed_stack", + "initial_configuration": True, "boot_policy_changed": True, + "boot_default_enabled": True, "status": status(state)} + if not record: + result = runtime.start(state) + return {"ok": bool(result.get("running") and result.get("ready")), "state": "started" if result.get("running") and result.get("ready") else "not_ready", + "scope": "local_tools", "boot_policy_changed": False, "runtime": result} + autostart._clear_manual_stop(state) + autostart._systemctl("start", unit.name) + return {"ok": True, "state": "start_requested", "scope": "managed_stack", "boot_policy_changed": False, "status": status(state)} + return autostart._configuration_action(state, action) + + +def service_stop(state): + """Stop current tools and transport; retain desired transport and boot policy.""" + def action(value): + state, directory, unit, record = _owned_setup(value) + if record: + autostart.mark_manual_stop(state) + autostart._systemctl("stop", unit.name) + connection = connection_background.disconnect(state) + local = _settled_runtime_stop(state, runtime.stop(state)) + success = connection.get("ok") is not False and not connection.get("alive") and _runtime_stopped(local) + return {"ok": success, "state": "stopped" if success else "stop_incomplete", + "scope": "managed_stack" if record else "local_tools", "boot_policy_changed": False, "saved_material_preserved": True, + "connection_intent_preserved": True, "connection": connection, "runtime": local} + return autostart._configuration_action(state, action) + + +def _boot(state, enabled): + def action(value): + state, directory, unit, record = _owned_setup(value) + initialized = False + if not record: + if enabled and load_config(state)["role"] == "node": + autostart._enable(state, start_now=False, confirm=False) + state, directory, unit, record = _owned_setup(state) + initialized = True + else: + raise RuntimeError("Managed startup is not configured. Use fb connect, or fb service start for a node profile.") + if enabled and not autostart._linger(pwd.getpwuid(os.getuid()).pw_name): + raise RuntimeError("OWNER_AUTHENTICATION_REQUIRED: run fb connect in a normal terminal, or fb autostart enable for a node, to enable startup after logout.") + policy = directory / "boot-policy.json" + before = autostart._optional_read(policy) + autostart._set_boot_requested(state, enabled) + installed = autostart._read(policy) + try: + autostart._systemctl("enable" if enabled else "disable", unit.name) + except BaseException: + if autostart._optional_read(policy) == installed: + if before is None: + policy.unlink() + else: + autostart._write(policy, before, replace=True) + raise + return {"ok": True, "state": "boot_enabled" if enabled else "boot_disabled", + "boot_policy_changed": True, "current_execution_unchanged": True, "initial_configuration": initialized, + "saved_material_preserved": True, "status": status(state)} + return autostart._configuration_action(state, action) + + +def boot_enable(state): + return _boot(state, True) + + +def boot_disable(state): + return _boot(state, False) + + +def disconnect(state): + """Persist a pause until explicit connect, including worker recovery/reboot.""" + def action(value): + def pause(inner): + autostart._set_connection_requested(inner, False) + result = connection_background.disconnect(inner) + return {"ok": result.get("ok") is not False and not result.get("alive"), + "state": "disconnected" if result.get("ok") is not False and not result.get("alive") else "disconnect_incomplete", + "desired_connection": "disconnected", + "scope": "until_explicit_connect", "local_tools_preserved": True, + "saved_material_preserved": True, "boot_policy_changed": False, + "connection": result} + return autostart._connection_action(value, pause) + return autostart._configuration_action(state, action) + + +def forget_credentials(state): + """Remove the managed connection setup/material; retain profile and linger. + + The command front end requires explicit confirmation before invoking this + destructive operation. Legacy autostart disable retains its old contract. + """ + def action(value): + state, directory, unit, record = _owned_setup(value) + if not record: + return {"ok": True, "state": "already_forgotten", "profile_preserved": True} + if record.get("node"): + raise RuntimeError("This node profile has no ChatGPT credential to forget.") + blob = directory / "credential.cred" + if hashlib.sha256(autostart._read(blob)).hexdigest() != record.get("credential_sha256"): + raise RuntimeError("The encrypted credential changed outside FrameBridge; nothing was forgotten.") + backup = directory / "config-before.json" + if record.get("headless_changed"): + if hashlib.sha256(autostart._read(backup)).hexdigest() != record.get("config_before_sha256"): + raise RuntimeError("The profile backup changed outside FrameBridge; nothing was forgotten.") + owned_metadata = [directory / "connection-intent.json", directory / "manual-stop.json", directory / "boot-policy.json"] + for path in owned_metadata: + if path.exists() or path.is_symlink(): + autostart._read(path) + autostart.mark_manual_stop(state) + autostart._systemctl("disable", "--now", unit.name) + connection = connection_background.disconnect(state) + if connection.get("ok") is False or connection.get("alive"): + raise RuntimeError("The connection is still stopping; its saved material was retained.") + local = _settled_runtime_stop(state, runtime.stop(state)) + if not _runtime_stopped(local): + raise RuntimeError("Local tools are still stopping; the saved connection material was retained.") + blob.unlink() + unit.unlink() + if record.get("headless_changed"): + backup.unlink() + for path in owned_metadata: + path.unlink(missing_ok=True) + (directory / "installation.json").unlink() + autostart._systemctl("daemon-reload") + return {"ok": True, "state": "forgotten", "encrypted_credential_removed": True, + "managed_configuration_removed": True, "profile_preserved": True, + "linger_preserved": True, "boot_enabled": False} + return autostart._configuration_action(state, action) diff --git a/source/expanded/framebridge_expanded/native_host.py b/source/expanded/framebridge_expanded/native_host.py new file mode 100644 index 0000000..1a33ebe --- /dev/null +++ b/source/expanded/framebridge_expanded/native_host.py @@ -0,0 +1,111 @@ +"""Preserve the captured native protocol while guarding actual X11 dispatch.""" +from __future__ import annotations +import importlib.util +import json +import os +from pathlib import Path +import sys +from . import display_context as display + +META = ("input_may_have_been_sent", "do_not_replay", "performed", "display_context") + +def _input_primitive(argv): + args = list(argv) + if len(args) < 2 or Path(str(args[0])).name != "xdotool": + return False + return args[1] not in {"getactivewindow", "getdisplaygeometry", "getmouselocation", + "getwindowgeometry", "getwindowname", "getwindowpid", + "search", "version", "help"} + +def install_cua_guard(cua, *, env=None, checker=None): + env = os.environ if env is None else env + checker = display.check if checker is None else checker + original_run = cua._run + original_handle = cua.handle_cua_message + state = {"active": False, "input_started": False, "failed_code": ""} + + def verify(): + try: + checker(env) + except display.DisplayContextError as exc: + state["failed_code"] = str(exc) + raise cua.CuaTransportError(str(exc), str(exc)) from None + + def guard_run(run, track_input): + def guarded(argv, timeout=15): + # The captured pointer helper calls its base runner after an internal + # query. Guard that final runner too, immediately before subprocess. + verify() + if track_input and _input_primitive(argv): + state["input_started"] = True + return run(argv, timeout=timeout) + return guarded + + primitive_run = getattr(cua, "_run_v3_base", None) + if primitive_run is not None: + cua._run_v3_base = guard_run(primitive_run, True) + guarded_run = guard_run(original_run, primitive_run is None) + + def guarded_handle(message): + state.update(active=True, input_started=False, failed_code="") + verify() + result = original_handle(message) + verify() + return result + + cua._run = guarded_run + cua.handle_cua_message = guarded_handle + return state + +def decorate_response(response, state, env): + if not state.get("active") or not isinstance(response, dict): + return response + response = dict(response) + may_have_sent = bool(state.get("input_started")) + response["display_context"] = display.describe(env) + response["input_may_have_been_sent"] = may_have_sent + response["do_not_replay"] = bool(may_have_sent and + (state.get("failed_code") or not response.get("executed"))) + if state.get("failed_code"): + details = {"ok": False, "error_code": state["failed_code"], + "input_may_have_been_sent": may_have_sent, + "do_not_replay": may_have_sent, "result_discarded": True} + if not may_have_sent: + details["performed"] = False + response.update(executed=False, exit_code=52, output=json.dumps(details), + error=json.dumps(details)) + if may_have_sent: + response.pop("performed", None) + else: + response["performed"] = False + elif response.get("executed"): + response["performed"] = may_have_sent + elif not may_have_sent: + response["performed"] = False + state["active"] = False + return response + +def _load(name, path): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + +def main(): + prefix = Path(os.environ.get("FRAMEBRIDGE_EXPANDED_PREFIX", Path(__file__).resolve().parents[2])) + host = prefix / "original/asus/framebridge-native/host/framebridge_host.py" + if not host.is_file(): + host = prefix / "sources/original-asus/framebridge-native/host/framebridge_host.py" + if not host.is_file(): + raise SystemExit("CAPTURED_NATIVE_HOST_NOT_FOUND") + sys.path.insert(0, str(host.parent)) + cua = _load("framebridge_cua_linux", host.with_name("framebridge_cua_linux.py")) + state = install_cua_guard(cua) + original = _load("_framebridge_original_native_host", host) + send = original.send_message + original.send_message = lambda response: send(decorate_response(response, state, os.environ)) + original.main() + +if __name__ == "__main__": + main() diff --git a/source/expanded/framebridge_expanded/network.py b/source/expanded/framebridge_expanded/network.py new file mode 100644 index 0000000..886de2d --- /dev/null +++ b/source/expanded/framebridge_expanded/network.py @@ -0,0 +1,166 @@ +"""Read-only network diagnostics, independent of a desktop applet's manager view.""" +from __future__ import annotations +import json +import os +import re +import stat +from pathlib import Path +import shutil +import subprocess + +_IFACE = re.compile(r"[A-Za-z0-9_.:@-]{1,100}\Z") + +def _command(argv, *, timeout=4): + try: + result = subprocess.run(argv, capture_output=True, text=True, timeout=timeout, + env={**os.environ, "LC_ALL": "C", "LANG": "C"}) + return result.returncode, result.stdout + except (OSError, subprocess.SubprocessError): + return None, "" + +def _json(argv): + code, text = _command(argv) + if code != 0: + return None + try: + value = json.loads(text) + except (ValueError, TypeError): + return None + return value if type(value) is list else None + +def _active(service): + code, text = _command(["systemctl", "is-active", service]) + return code == 0 and text.strip() == "active" + +def _available(name): + return bool(shutil.which(name) or any( + (Path(directory) / name).is_file() and os.access(Path(directory) / name, os.X_OK) + for directory in ["/usr/sbin", "/sbin", "/usr/bin", "/bin"])) + +def _ifupdown(state_file=Path("/run/network/ifstate")): + """Read active interface names from bounded state, never credential/config files.""" + try: + fd = os.open(state_file, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if (not stat.S_ISREG(info.st_mode) or info.st_uid not in {0, os.getuid()} + or info.st_mode & 0o022 or info.st_size > 65536): + return set() + raw = os.read(fd, 65537) + finally: + os.close(fd) + if len(raw) > 65536: + return set() + text = raw.decode("ascii") + except (OSError, UnicodeError): + return set() + names = set() + for line in text.splitlines(): + if "=" not in line: + continue + name, mapped = line.split("=", 1) + if _IFACE.fullmatch(name) and _IFACE.fullmatch(mapped): + names.add(name) + return names + +def _nm_devices(): + """Only device metadata, never connection profiles, SSIDs or saved secrets.""" + code, text = _command([ + "nmcli", "-t", "-f", "GENERAL.DEVICE,GENERAL.STATE,GENERAL.NM-MANAGED", "device", "show", + ]) + if code != 0: + return None + devices = {} + current = None + for line in text.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + # nmcli escapes colons/backslashes in terse output. Linux interface + # names containing either are excluded rather than guessed. + if key == "GENERAL.DEVICE": + current = value if _IFACE.fullmatch(value) and "\\" not in value else None + if current: + devices[current] = {"managed": None, "state": ""} + elif current and key == "GENERAL.STATE": + devices[current]["state"] = value[:120] + elif current and key == "GENERAL.NM-MANAGED": + devices[current]["managed"] = {"yes": True, "no": False}.get(value.strip().lower()) + return devices + +def summarize(addresses, routes, nm_devices=None, ifupdown=None, managers=None): + """Separate observed kernel connectivity from network-manager ownership.""" + nm_devices = {} if nm_devices is None else nm_devices + ifupdown = set() if ifupdown is None else set(ifupdown) + managers = {} if managers is None else managers + defaults = [] + for family, rows in routes.items(): + for row in rows or []: + name = row.get("dev") + if row.get("dst") != "default" or not isinstance(name, str) or _IFACE.fullmatch(name) is None: + continue + defaults.append({"family": family, "interface": name, + "gateway": row.get("gateway"), "metric": row.get("metric")}) + interfaces = [] + for row in addresses or []: + name = row.get("ifname") + if not isinstance(name, str) or _IFACE.fullmatch(name) is None: + continue + flags = row.get("flags") if isinstance(row.get("flags"), list) else [] + addresses_public = [] + for address in row.get("addr_info", []): + if (isinstance(address, dict) and address.get("family") in {"inet", "inet6"} + and isinstance(address.get("local"), str)): + addresses_public.append({"family": address["family"], "address": address["local"], + "prefixlen": address.get("prefixlen"), + "scope": address.get("scope")}) + nm = nm_devices.get(name, {}) + managed = nm.get("managed") + if managed is True: + owner = "NetworkManager" + elif name in ifupdown: + owner = "ifupdown" + else: + owner = "unverified" + default = any(route["interface"] == name for route in defaults) + link = name != "lo" and "UP" in flags and row.get("operstate") in {"UP", "UNKNOWN"} + usable_address = any(a["scope"] == "global" for a in addresses_public) + interfaces.append({ + "name": name, "link_up": link, "operstate": row.get("operstate"), + "addresses": addresses_public, "has_default_route": default, + "manager": owner, "networkmanager_managed": managed, + "networkmanager_state": nm.get("state", ""), + "local_route_ready": bool(link and usable_address and default), + }) + ready = any(item["local_route_ready"] for item in interfaces) + warnings = [] + for item in interfaces: + if item["local_route_ready"] and item["networkmanager_managed"] is False: + warnings.append({ + "code": "CONNECTED_INTERFACE_NOT_MANAGED_BY_NETWORKMANAGER", + "interface": item["name"], + "message": "A NetworkManager desktop applet may not represent this working interface.", + }) + return {"ok": True, "interfaces": interfaces, "default_routes": defaults, + "managers": managers, "connectivity": "LINK_ADDRESS_DEFAULT_ROUTE" if ready else "NOT_ESTABLISHED", + "internet_verified": False, + "internet_note": "Link, address and route checks do not verify DNS or Internet access.", + "warnings": warnings} + +def inspect(): + addresses = _json(["ip", "-j", "address", "show"]) + routes4 = _json(["ip", "-j", "-4", "route", "show", "default"]) + routes6 = _json(["ip", "-j", "-6", "route", "show", "default"]) + nm = _nm_devices() if shutil.which("nmcli") else None + managers = { + "NetworkManager": {"available": bool(shutil.which("nmcli")), "active": _active("NetworkManager.service")}, + "ifupdown": {"available": _available("ifquery"), "active": _active("networking.service")}, + "systemd-networkd": {"active": _active("systemd-networkd.service")}, + } + active = _ifupdown() if managers["ifupdown"]["active"] else set() + value = summarize(addresses, {"ipv4": routes4, "ipv6": routes6}, nm, active, managers) + if addresses is None or (routes4 is None and routes6 is None): + value.update(ok=False, error="NETWORK_KERNEL_STATUS_UNAVAILABLE") + if nm is None and managers["NetworkManager"]["available"]: + value["warnings"].append({"code": "NETWORKMANAGER_STATUS_UNAVAILABLE"}) + return value diff --git a/source/expanded/framebridge_expanded/node.py b/source/expanded/framebridge_expanded/node.py new file mode 100644 index 0000000..473110a --- /dev/null +++ b/source/expanded/framebridge_expanded/node.py @@ -0,0 +1,205 @@ +"""Original FBPAIR identity and outbound protocol for an explicitly enrolled node.""" +from __future__ import annotations + +import hashlib +import importlib +import json +import os +import socket +import time +import uuid +from pathlib import Path + +from . import broker + + +class NodeError(ValueError): + pass + + +def _original(name): + broker.original_modules() + return importlib.import_module("framebridge_linux_chatgpt." + name) + + +def _write_json(path, value): + path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + temporary = path.with_name("." + path.name + "." + uuid.uuid4().hex + ".tmp") + fd = os.open(str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + try: + with os.fdopen(fd, "w") as stream: + json.dump(value, stream, indent=2, sort_keys=True, allow_nan=False) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + finally: + if temporary.exists(): + temporary.unlink() + + +def _context(state_dir=None, create=False, node_label=None): + state = broker.state_path(state_dir) + config = broker._config(state) + label = node_label or config.get("name") or socket.gethostname() + if not isinstance(label, str) or broker._LABEL.fullmatch(label) is None or label.casefold() == "local": + raise NodeError("NODE_LABEL_INVALID") + ctx = _original("seed_core").SeedContext(state_dir=state / "node", config_dir=state / "node/config") + identity = ctx.load_identity() + if identity: + node_id = identity.get("node_id") + if config.get("node_id") and config["node_id"] != node_id: + raise NodeError("NODE_IDENTITY_CONFIG_MISMATCH") + if create and identity.get("hostname") != label: + raise NodeError("NODE_IDENTITY_LABEL_MISMATCH") + elif create: + node_id = config.get("node_id") or str(uuid.uuid4()) + try: + if str(uuid.UUID(node_id)) != node_id: + raise ValueError + except (ValueError, TypeError, AttributeError): + raise NodeError("NODE_ID_INVALID") from None + crypto = _original("node_identity").ensure_node_keypair(ctx.state_dir, + comment="framebridge-" + node_id[:8]) + identity = {"schema": 2, "node_id": node_id, "node_id_short": node_id.replace("-", "")[:8], + "node_host_id": crypto["host_id"], "node_public_jwk": crypto["public_jwk"], + "node_public_key_sha256": crypto["public_key_sha256"], "hostname": label, + "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())} + _write_json(ctx.identity_path, identity) + else: + raise NodeError("NODE_IDENTITY_MISSING") + if create and (config.get("node_id") != node_id or config.get("name") != label): + config.update(schema=1, node_id=node_id, name=label) + _write_json(state / "config.json", config) + return state, config, ctx, identity + + +def _poster(state): + def post(url, value, *, timeout=10): + return broker.post_json(url, value, state_dir=state, timeout=timeout) + return post + + +def enroll(broker_url, node_label, state_dir=None): + url = broker.validate_endpoint(broker_url) + state, config, ctx, identity = _context(state_dir, create=True, node_label=node_label) + store = _original("pairing").PairingStore(ctx.state_dir) + existing = store.load() + if existing.get("state") in {"pair_requested", "paired"}: + raise NodeError("NODE_ENROLLMENT_ALREADY_EXISTS") + manifest = _original("seed_capabilities").list_capabilities() + digest = hashlib.sha256(json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode()).hexdigest() + result = _original("pairing_client").start_pairing( + state_dir=ctx.state_dir, node_id=identity["node_id"], node_label=node_label, + node_host_id=identity["node_host_id"], public_jwk=identity["node_public_jwk"], + provider="chatgpt", capability_manifest_sha256=digest, broker_base_url=url, + allow_insecure_dev=url.startswith("http://"), poster=_poster(state)) + config["broker_url"] = url + _write_json(state / "config.json", config) + return dict(result, node_id=identity["node_id"], node_label=node_label, + fingerprint=identity["node_public_key_sha256"]) + + +def poll_enrollment(state_dir=None): + state, config, ctx, identity = _context(state_dir) + url = broker.validate_endpoint(config.get("broker_url", "")) + store = _original("pairing").PairingStore(ctx.state_dir) + pairing = store.load() + if pairing.get("broker_base_url") and pairing["broker_base_url"].rstrip("/") != url: + raise NodeError("PAIRING_BROKER_CONFIG_MISMATCH") + if pairing.get("state") == "paired" and pairing.get("broker_confirm_required") is not True: + return status(state) + result = _original("pairing_client").poll_pairing( + state_dir=ctx.state_dir, allow_insecure_dev=url.startswith("http://"), poster=_poster(state)) + pairing = store.load() + if pairing.get("state") == "paired": + _write_json(ctx.config_path, {"schema": 1, "provider": "chatgpt", "pairing": { + "state": "paired", "broker_base_url": url, + "broker_confirm_required": pairing.get("broker_confirm_required") is True}}) + return dict(result, node_id=identity["node_id"]) + + +def status(state_dir=None): + try: + state, config, ctx, identity = _context(state_dir) + except NodeError as exc: + return {"ok": False, "error": str(exc), "state": "not_enrolled"} + pairing = _original("pairing").PairingStore(ctx.state_dir).load() + return {"ok": True, "node_id": identity["node_id"], "node_label": identity["hostname"], + "fingerprint": identity["node_public_key_sha256"], + "state": pairing.get("state", "not_paired"), + "broker_confirm_required": pairing.get("broker_confirm_required") is True, + "broker_url": config.get("broker_url"), "agent_state": "not_checked"} + + +def dispatch_request(request, adapter_request=None, state_dir=None): + """Recheck binding and authorization before invoking the real local Original adapter.""" + if not isinstance(request, dict): + return {"ok": False, "error": "REQUEST_INVALID"} + try: + _, _, _, identity = _context(state_dir) + if request.get("node_id") != identity["node_id"]: + raise NodeError("REQUEST_NODE_MISMATCH") + expires = request.get("expires_at") + if isinstance(expires, bool) or not isinstance(expires, (int, float)) or not expires > time.time(): + raise NodeError("REQUEST_EXPIRED") + rid = request.get("request_id") + if not isinstance(rid, str) or not rid or len(rid) > 80: + raise NodeError("REQUEST_ID_INVALID") + operation = request.get("operation") + if operation: + payload = broker._admin_payload(operation, request.get("payload")) + payload = dict(payload, version=1, mode="EXEC") + if operation == "admin.cua": + payload["request_type"] = "cua" + if adapter_request is None: + from .runtime import adapter_request + result = adapter_request(payload) + if not isinstance(result, dict): + raise NodeError("ADAPTER_RESPONSE_INVALID") + result = dict(result, mcp_job_id=payload["job_id"]) + if operation == "admin.exec": + result["mcp_script_sha256"] = payload["sha256"] + if payload.get("include_stdout") is not True: + result.update(stdout="", stdout_sanitized=False, stdout_truncated=False, + stdout_omitted_reason="NOT_REQUESTED") + return result + cap, args = request.get("capability"), request.get("args") + if cap not in broker.original_modules().READ_CAPS or not isinstance(args, dict): + raise NodeError("CAPABILITY_NOT_ALLOWED") + value = _original("seed_capabilities").execute_capability(cap, args) + if isinstance(value, dict) and value.get("ok") is False: + return {"ok": False, "capability": cap, "error": "READ_CAPABILITY_FAILED"} + return {"ok": True, "capability": cap, "result": value} + except (NodeError, broker.BrokerError) as exc: + return {"ok": False, "error": str(exc)} + except Exception: + return {"ok": False, "error": "NODE_DISPATCH_FAILED"} + + +def run(state_dir=None, once=False): + state, config, ctx, identity = _context(state_dir) + url = broker.validate_endpoint(config.get("broker_url", "")) + pairing = _original("pairing").PairingStore(ctx.state_dir).load() + if pairing.get("state") != "paired" or pairing.get("broker_confirm_required") is True: + raise NodeError("NODE_NOT_CONFIRMED") + if pairing.get("node_id") != identity["node_id"] or pairing.get("broker_base_url", "").rstrip("/") != url: + raise NodeError("PAIRING_BINDING_MISMATCH") + outbound = _original("outbound_agent") + while True: + poll = broker.post_json(url + "/fbpair/v1/node/poll", + outbound._signed_envelope(ctx, "node.poll", {}), state_dir=state) + if poll.get("ok") is not True: + raise NodeError("NODE_POLL_REJECTED") + req = poll.get("request") + result = {"ok": True, "idle": True} + if isinstance(req, dict): + value = dispatch_request(req, state_dir=state) + posted = broker.post_json(url + "/fbpair/v1/node/result", outbound._signed_envelope( + ctx, "node.result", {"request_id": req.get("request_id"), "result": value}), state_dir=state) + if posted.get("ok") is not True: + raise NodeError("NODE_RESULT_REJECTED") + result = {"ok": True, "idle": False, "request_id": req.get("request_id")} + if once: + return result + time.sleep(1) diff --git a/source/expanded/framebridge_expanded/real_screen/__init__.py b/source/expanded/framebridge_expanded/real_screen/__init__.py new file mode 100644 index 0000000..415b57b --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/__init__.py @@ -0,0 +1 @@ +"""Actual active-screen capture and bound visible terminal input.""" diff --git a/source/expanded/framebridge_expanded/real_screen/screen_capture.py b/source/expanded/framebridge_expanded/real_screen/screen_capture.py new file mode 100644 index 0000000..3d1598e --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_capture.py @@ -0,0 +1,575 @@ +#!/usr/bin/env python3 +"""Capture actual Linux VT or X11 root pixels, never rendered terminal text. + +Active X11 sessions use XGetImage, and Wayland fails explicitly. The framebuffer +backend is never used as a substitute for an active graphical session. +""" +from __future__ import annotations + +import array +import base64 +import ctypes +import ctypes.util +from dataclasses import asdict, dataclass +from datetime import datetime, timezone +import fcntl +import hashlib +import json +import math +import os +from pathlib import Path +import pwd +import re +import socket +import stat +import struct +import subprocess +import sys +import threading +import uuid +import zlib + +MAX_PIXELS = 4096 * 2160 +MAX_FRAMEBUFFER_BYTES = 128 * 1024 * 1024 +MAX_IMAGE_BYTES = 32 * 1024 * 1024 +MAX_CHUNK_BYTES = 2400 +CAPTURE_ID = re.compile(r"[0-9a-f]{32}\Z") +SESSION_ID = re.compile(r"[A-Za-z0-9_-]{1,64}\Z") +DISPLAY_ID = re.compile(r":[0-9]{1,5}(?:\.[0-9]{1,3})?\Z") +_X11_LOCK = threading.RLock() +FBIOGET_VSCREENINFO = 0x4600 +FBIOGET_FSCREENINFO = 0x4602 + + +class CaptureError(RuntimeError): + def __init__(self, code: str): + self.code = code + super().__init__(code) + + +def require(condition: bool, code: str) -> None: + if not condition: + raise CaptureError(code) + + +@dataclass(frozen=True) +class Foreground: + active_vt: str + session_id: str + uid: int + leader: int + session_type: str + tty: str + seat: str + vtnr: int + display: str = "" + + +def choose_foreground(active_vt: str, sessions: list[dict], seat: str) -> Foreground: + """Require an identifiable active text session on the actual foreground VT.""" + require(re.fullmatch(r"tty[1-9][0-9]*", active_vt) is not None, "ACTIVE_VT_UNVERIFIED") + active = [s for s in sessions if s.get("Seat") == seat and s.get("Active") == "yes" + and s.get("Remote") == "no"] + require(not any(s.get("Type") in {"x11", "wayland"} for s in active), + "GRAPHICAL_CAPTURE_REQUIRED") + matching = [s for s in active if s.get("Type") == "tty" and s.get("TTY") == active_vt + and s.get("VTNr") == active_vt[3:]] + require(len(matching) == 1, "FOREGROUND_SESSION_UNVERIFIED") + s = matching[0] + try: + result = Foreground(active_vt, s["Id"], int(s["User"]), int(s["Leader"]), + s["Type"], s["TTY"], s["Seat"], int(s["VTNr"])) + except (KeyError, TypeError, ValueError): + raise CaptureError("FOREGROUND_SESSION_INVALID") from None + require(SESSION_ID.fullmatch(result.session_id) is not None and result.leader > 0, + "FOREGROUND_SESSION_INVALID") + return result + + +def choose_display_foreground(active_vt: str, sessions: list[dict], seat: str) -> Foreground: + active = [s for s in sessions if s.get("Seat") == seat and s.get("Active") == "yes" + and s.get("Remote") == "no"] + require(len(active) == 1, "FOREGROUND_SESSION_UNVERIFIED") + s = active[0] + if s.get("Type") == "tty": + return choose_foreground(active_vt, sessions, seat) + require(s.get("Type") != "wayland", "WAYLAND_CAPTURE_UNSUPPORTED") + require(s.get("Type") == "x11", "SCREEN_TYPE_UNSUPPORTED") + # LightDM may leave TTY empty while supplying the actual numeric VT. + # Missing metadata or an explicit contradictory TTY is still rejected. + require(re.fullmatch(r"tty[1-9][0-9]*", active_vt) is not None + and s.get("VTNr") == active_vt[3:] and s.get("TTY") in ("", active_vt), + "GRAPHICAL_VT_UNVERIFIED") + require(DISPLAY_ID.fullmatch(s.get("Display", "")) is not None, "X11_DISPLAY_UNVERIFIED") + try: + f = Foreground(active_vt, s["Id"], int(s["User"]), int(s["Leader"]), "x11", + s["TTY"], s["Seat"], int(s["VTNr"]), s["Display"]) + except (KeyError, ValueError, TypeError): + raise CaptureError("FOREGROUND_SESSION_INVALID") from None + require(f.uid == os.getuid() and f.leader > 0 and SESSION_ID.fullmatch(f.session_id) is not None, + "X11_SESSION_IDENTITY_MISMATCH") + return f + + +def foreground(seat: str = "seat0", *, allow_graphics: bool = False) -> Foreground: + def run(args: list[str]) -> str: + try: + p = subprocess.run(args, check=False, capture_output=True, timeout=4, + text=True, env={**os.environ, "LC_ALL": "C"}) + except (OSError, subprocess.TimeoutExpired): + raise CaptureError("SESSION_QUERY_FAILED") from None + require(p.returncode == 0 and len(p.stdout) <= 65536, "SESSION_QUERY_FAILED") + return p.stdout + ids = [] + for line in run(["loginctl", "list-sessions", "--no-legend", "--no-pager"]).splitlines(): + if line.strip(): + sid = line.split()[0] + require(SESSION_ID.fullmatch(sid) is not None, "SESSION_QUERY_INVALID") + ids.append(sid) + require(len(ids) <= 32, "SESSION_COUNT_EXCEEDED") + fields = ["Id", "Active", "Type", "Seat", "VTNr", "TTY", "User", "Leader", "Remote", "Display"] + sessions = [] + for sid in ids: + args = ["loginctl", "show-session", sid, "--no-pager"] + for key in fields: + args += ["-p", key] + sessions.append(dict(line.split("=", 1) for line in run(args).splitlines() if "=" in line)) + try: + active_vt = Path("/sys/class/tty/tty0/active").read_text().strip() + except OSError: + raise CaptureError("ACTIVE_VT_UNAVAILABLE") from None + return (choose_display_foreground(active_vt, sessions, seat) if allow_graphics + else choose_foreground(active_vt, sessions, seat)) + + +class _Fix(ctypes.Structure): + _fields_ = [("id", ctypes.c_char * 16), ("smem_start", ctypes.c_ulong), + ("smem_len", ctypes.c_uint32), ("type", ctypes.c_uint32), + ("type_aux", ctypes.c_uint32), ("visual", ctypes.c_uint32), + ("xpanstep", ctypes.c_uint16), ("ypanstep", ctypes.c_uint16), + ("ywrapstep", ctypes.c_uint16), ("line_length", ctypes.c_uint32), + ("mmio_start", ctypes.c_ulong), ("mmio_len", ctypes.c_uint32), + ("accel", ctypes.c_uint32), ("capabilities", ctypes.c_uint16), + ("reserved", ctypes.c_uint16 * 2)] + + +@dataclass(frozen=True) +class Geometry: + width: int + height: int + virtual_width: int + virtual_height: int + xoffset: int + yoffset: int + stride: int + bpp: int + red: tuple[int, int] + green: tuple[int, int] + blue: tuple[int, int] + memory_bytes: int + driver: str = "" + byteorder: str = sys.byteorder + + +def validate_geometry(g: Geometry) -> None: + require(0 < g.width <= 4096 and 0 < g.height <= 2160 and + g.width * g.height <= MAX_PIXELS, "SCREEN_SIZE_UNSUPPORTED") + require(g.bpp in {16, 24, 32} and g.byteorder in {"little", "big"}, "PIXEL_FORMAT_UNSUPPORTED") + pixel_bytes = g.bpp // 8 + require(g.virtual_width >= g.width and g.virtual_height >= g.height and + 0 <= g.xoffset <= g.virtual_width - g.width and + 0 <= g.yoffset <= g.virtual_height - g.height, "VIEWPORT_INVALID") + require(g.stride >= g.virtual_width * pixel_bytes and + 0 < g.memory_bytes <= MAX_FRAMEBUFFER_BYTES and + (g.yoffset + g.height) * g.stride <= g.memory_bytes, "FRAMEBUFFER_LAYOUT_INVALID") + masks = [] + for offset, length in (g.red, g.green, g.blue): + require(1 <= length <= 8 and 0 <= offset and offset + length <= g.bpp, + "COLOR_LAYOUT_UNSUPPORTED") + masks.append(((1 << length) - 1) << offset) + require(not any(a & b for i, a in enumerate(masks) for b in masks[i + 1:]), + "COLOR_LAYOUT_OVERLAP") + + +def read_geometry(fd: int) -> Geometry: + var = array.array("I", [0] * 40) + require(var.itemsize == 4, "HOST_ABI_UNSUPPORTED") + fixed_buffer = bytearray(ctypes.sizeof(_Fix)) + try: + fcntl.ioctl(fd, FBIOGET_VSCREENINFO, var, True) + fcntl.ioctl(fd, FBIOGET_FSCREENINFO, fixed_buffer, True) + except OSError: + raise CaptureError("FRAMEBUFFER_INFO_UNAVAILABLE") from None + fixed = _Fix.from_buffer_copy(fixed_buffer) + require(fixed.type == 0 and fixed.visual == 2 and var[7] == 0 and var[20] == 0 + and var[34] == 0 and all(var[i] == 0 for i in (10, 13, 16)), + "FRAMEBUFFER_FORMAT_UNSUPPORTED") + g = Geometry(var[0], var[1], var[2], var[3], var[4], var[5], fixed.line_length, + var[6], (var[8], var[9]), (var[11], var[12]), (var[14], var[15]), + fixed.smem_len, bytes(fixed.id).decode("ascii", "replace")) + validate_geometry(g) + return g + + +def decode_framebuffer(raw: bytes, g: Geometry) -> bytes: + """Losslessly select visible pixels and decode the kernel-reported RGB fields.""" + validate_geometry(g) + end = (g.yoffset + g.height - 1) * g.stride + (g.xoffset + g.width) * (g.bpp // 8) + require(len(raw) >= end, "FRAMEBUFFER_READ_SHORT") + pixel_bytes = g.bpp // 8 + output = bytearray(g.width * g.height * 3) + byte_fields = g.bpp in {24, 32} and all(n == 8 and off % 8 == 0 + for off, n in (g.red, g.green, g.blue)) + for y in range(g.height): + start = (y + g.yoffset) * g.stride + g.xoffset * pixel_bytes + row = raw[start:start + g.width * pixel_bytes] + dst = y * g.width * 3 + if byte_fields: + for component, (off, _) in enumerate((g.red, g.green, g.blue)): + byte = off // 8 if g.byteorder == "little" else pixel_bytes - 1 - off // 8 + output[dst + component:dst + g.width * 3:3] = row[byte::pixel_bytes] + else: + for x in range(g.width): + value = int.from_bytes(row[x * pixel_bytes:(x + 1) * pixel_bytes], g.byteorder) + for component, (off, n) in enumerate((g.red, g.green, g.blue)): + mask = (1 << n) - 1 + output[dst + x * 3 + component] = (((value >> off) & mask) * 255 + mask // 2) // mask + return bytes(output) + + +def encode_png(rgb: bytes, width: int, height: int, *, prefer_indexed: bool = False) -> bytes: + require(len(rgb) == width * height * 3 and width > 0 and height > 0, "PIXEL_LENGTH_INVALID") + def chunk(kind: bytes, data: bytes) -> bytes: + return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xffffffff) + # A palette changes only PNG storage, never pixels. Stop the palette attempt + # as soon as the 257th exact RGB value appears, retaining true RGB for photos. + palette = {} + indexes = None + if prefer_indexed: + indexes = bytearray(width * height) + for pixel, start in enumerate(range(0, len(rgb), 3)): + color = rgb[start:start + 3] + index = palette.get(color) + if index is None: + if len(palette) == 256: + indexes = None + break + index = len(palette) + palette[color] = index + indexes[pixel] = index + if indexes is not None: + depth = 1 if len(palette) <= 2 else 2 if len(palette) <= 4 else 4 if len(palette) <= 16 else 8 + per_byte = 8 // depth + compressor = zlib.compressobj(9) + rows = [] + for y in range(height): + source = indexes[y * width:(y + 1) * width] + if depth == 8: + packed = source + else: + packed = bytearray((width * depth + 7) // 8) + for x, value in enumerate(source): + packed[x // per_byte] |= value << (8 - depth * ((x % per_byte) + 1)) + rows.append(compressor.compress(b"\0" + packed)) + rows.append(compressor.flush()) + return (b"\x89PNG\r\n\x1a\n" + + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, depth, 3, 0, 0, 0)) + + chunk(b"PLTE", b"".join(palette)) + chunk(b"IDAT", b"".join(rows)) + + chunk(b"IEND", b"")) + # RGB pixels are stored exactly; no fonts, labels, scaling or other overlays. + compressor = zlib.compressobj(9) + rows = [] + for y in range(height): + rows.append(compressor.compress(b"\0" + rgb[y * width * 3:(y + 1) * width * 3])) + rows.append(compressor.flush()) + return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)) + + chunk(b"IDAT", b"".join(rows)) + chunk(b"IEND", b"")) + + +def _directory(output_dir: Path | str) -> Path: + path = Path(output_dir).absolute() + path.mkdir(parents=True, mode=0o700, exist_ok=True) + s = path.lstat() + require(stat.S_ISDIR(s.st_mode) and not path.is_symlink() and s.st_uid == os.getuid() + and s.st_mode & 0o077 == 0, "CAPTURE_DIRECTORY_UNTRUSTED") + return path + + +def _write_new(path: Path, data: bytes) -> None: + try: + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600) + except OSError: + raise CaptureError("CAPTURE_ARTIFACT_CREATE_FAILED") from None + with os.fdopen(fd, "wb") as stream: + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + + +def require_unchanged(before: Foreground, after: Foreground) -> None: + require(before == after, "FOREGROUND_CHANGED_DURING_CAPTURE") + + +class _XImage(ctypes.Structure): + _fields_ = [("width", ctypes.c_int), ("height", ctypes.c_int), ("xoffset", ctypes.c_int), + ("format", ctypes.c_int), ("data", ctypes.c_void_p), ("byte_order", ctypes.c_int), + ("bitmap_unit", ctypes.c_int), ("bitmap_bit_order", ctypes.c_int), + ("bitmap_pad", ctypes.c_int), ("depth", ctypes.c_int), + ("bytes_per_line", ctypes.c_int), ("bits_per_pixel", ctypes.c_int), + ("red_mask", ctypes.c_ulong), ("green_mask", ctypes.c_ulong), + ("blue_mask", ctypes.c_ulong), ("obdata", ctypes.c_void_p), + ("functions", ctypes.c_void_p * 6)] + + +def _mask_field(mask: int) -> tuple[int, int]: + require(mask > 0, "X11_COLOR_MASK_INVALID") + offset = (mask & -mask).bit_length() - 1 + shifted = mask >> offset + require(shifted & (shifted + 1) == 0, "X11_COLOR_MASK_INVALID") + return offset, shifted.bit_length() + + +def _x11_leader_environment(f: Foreground, display: str) -> dict[str, str]: + """Read the user leader's two context fields, never a display manager's env.""" + try: + leader = Path("/proc") / str(f.leader) + owner = leader.stat().st_uid + require(owner in {0, f.uid}, "X11_LEADER_IDENTITY_MISMATCH") + if owner != f.uid: + # LightDM's session leader belongs to root. It is not the desktop + # user and its authentication context must not be read or reused. + return {} + environment = (leader / "environ").read_bytes() + require(len(environment) <= 1024 * 1024, "X11_CONTEXT_TOO_LARGE") + values = {} + for item in environment.split(b"\0"): + if b"=" not in item: + continue + name, value = item.split(b"=", 1) + if name in {b"DISPLAY", b"XAUTHORITY"}: + values[name.decode()] = value.decode("utf-8", "strict") + require(values.get("DISPLAY", display) == display, "X11_DISPLAY_IDENTITY_MISMATCH") + return values + except (OSError, UnicodeDecodeError): + return {} + + +def _x11_context(f: Foreground, display: str | None, xauthority: str | None) -> tuple[str, str | None]: + require(f.session_type == "x11" and f.uid == os.getuid(), "X11_SESSION_IDENTITY_MISMATCH") + display = display or f.display + require(isinstance(display, str) and DISPLAY_ID.fullmatch(display) is not None + and display == f.display, "X11_DISPLAY_IDENTITY_MISMATCH") + if xauthority is None: + # Use the matching process context or the user leader's whitelist. + # Authentication bytes are never inspected, logged or persisted here. + if os.environ.get("DISPLAY") == display: + xauthority = os.environ.get("XAUTHORITY") + if xauthority is None: + xauthority = _x11_leader_environment(f, display).get("XAUTHORITY") + if xauthority is None: + # Resolve the verified user's canonical home, not a caller's HOME. + # Xlib's normal fallback is made explicit and validated below. + try: + account = pwd.getpwuid(f.uid) + except KeyError: + raise CaptureError("X11_AUTHORITY_UNAVAILABLE") from None + require(account.pw_uid == f.uid and Path(account.pw_dir).is_absolute(), + "X11_AUTHORITY_PATH_INVALID") + xauthority = str(Path(account.pw_dir) / ".Xauthority") + if xauthority is not None: + require(isinstance(xauthority, str) and len(xauthority) <= 4096 and "\0" not in xauthority, + "X11_AUTHORITY_PATH_INVALID") + path = Path(xauthority) + try: + s = path.lstat() + except OSError: + raise CaptureError("X11_AUTHORITY_UNAVAILABLE") from None + require(path.is_absolute() and stat.S_ISREG(s.st_mode) and s.st_uid == f.uid + and s.st_mode & 0o077 == 0, "X11_AUTHORITY_UNTRUSTED") + return display, xauthority + + +def capture_x11_pixels(f: Foreground, *, display: str | None = None, + xauthority: str | None = None) -> tuple[bytes, Geometry, dict]: + display, xauthority = _x11_context(f, display, xauthority) + library = ctypes.util.find_library("X11") + require(library is not None, "X11_LIBRARY_UNAVAILABLE") + with _X11_LOCK: + try: + x = ctypes.CDLL(library) + x.XOpenDisplay.argtypes = [ctypes.c_char_p]; x.XOpenDisplay.restype = ctypes.c_void_p + x.XCloseDisplay.argtypes = [ctypes.c_void_p] + x.XDefaultRootWindow.argtypes = [ctypes.c_void_p]; x.XDefaultRootWindow.restype = ctypes.c_ulong + x.XGetGeometry.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong), + ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), + *([ctypes.POINTER(ctypes.c_uint)] * 4)] + x.XGetGeometry.restype = ctypes.c_int + x.XGetImage.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.c_int, ctypes.c_int, + ctypes.c_uint, ctypes.c_uint, ctypes.c_ulong, ctypes.c_int] + x.XGetImage.restype = ctypes.POINTER(_XImage) + x.XDestroyImage.argtypes = [ctypes.POINTER(_XImage)] + x.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int] + except (OSError, AttributeError): + raise CaptureError("X11_LIBRARY_UNAVAILABLE") from None + old_authority = os.environ.get("XAUTHORITY") + try: + if xauthority is not None: + os.environ["XAUTHORITY"] = xauthority + else: + os.environ.pop("XAUTHORITY", None) + connection = x.XOpenDisplay(display.encode("ascii")) + finally: + if old_authority is None: + os.environ.pop("XAUTHORITY", None) + else: + os.environ["XAUTHORITY"] = old_authority + require(bool(connection), "X11_DISPLAY_UNREADABLE") + image = None + try: + root = x.XDefaultRootWindow(connection) + def dimensions() -> tuple[int, int, int]: + rr = ctypes.c_ulong(); px = ctypes.c_int(); py = ctypes.c_int() + width = ctypes.c_uint(); height = ctypes.c_uint(); border = ctypes.c_uint(); depth = ctypes.c_uint() + ok = x.XGetGeometry(connection, root, ctypes.byref(rr), ctypes.byref(px), ctypes.byref(py), + ctypes.byref(width), ctypes.byref(height), ctypes.byref(border), ctypes.byref(depth)) + require(bool(ok), "X11_GEOMETRY_UNAVAILABLE") + require(0 < width.value <= 4096 and 0 < height.value <= 2160 + and width.value * height.value <= MAX_PIXELS, "SCREEN_SIZE_UNSUPPORTED") + return width.value, height.value, depth.value + width, height, depth = dimensions() + x.XSync(connection, 0) + image = x.XGetImage(connection, root, 0, 0, width, height, ctypes.c_ulong(-1).value, 2) + require(bool(image), "X11_IMAGE_UNAVAILABLE") + info = image.contents + require(info.width == width and info.height == height and info.xoffset == 0 + and info.format == 2 and info.byte_order in {0, 1} and bool(info.data), + "X11_IMAGE_FORMAT_UNSUPPORTED") + count = info.bytes_per_line * height + g = Geometry(width, height, width, height, 0, 0, info.bytes_per_line, info.bits_per_pixel, + _mask_field(info.red_mask), _mask_field(info.green_mask), _mask_field(info.blue_mask), + count, "X11-XGetImage", "little" if info.byte_order == 0 else "big") + validate_geometry(g) + raw = ctypes.string_at(info.data, count) + require(dimensions() == (width, height, depth), "GEOMETRY_CHANGED_DURING_CAPTURE") + return decode_framebuffer(raw, g), g, {"display": display, "root_window": root, "depth": depth} + finally: + if image: + x.XDestroyImage(image) + x.XCloseDisplay(connection) + + +def capture_screen(output_dir: Path | str, *, device: str = "/dev/fb0", seat: str = "seat0", + display: str | None = None, xauthority: str | None = None) -> dict: + """Capture immutable real screen pixels or fail; never substitute a text render.""" + output_dir = _directory(output_dir) + before = foreground(seat, allow_graphics=True) + started = datetime.now(timezone.utc).isoformat() + if before.session_type == "x11": + rgb, g, backend = capture_x11_pixels(before, display=display, xauthority=xauthority) + require_unchanged(before, foreground(seat, allow_graphics=True)) + png = encode_png(rgb, g.width, g.height) + source = "x11-root-XGetImage" + else: + png, g = _capture_framebuffer(before, device, seat) + backend = {} + source = "linux-framebuffer" + require(len(png) <= MAX_IMAGE_BYTES, "CAPTURE_IMAGE_TOO_LARGE") + require_unchanged(before, foreground(seat, allow_graphics=True)) + capture_id = uuid.uuid4().hex + image_path = output_dir / (capture_id + ".png") + manifest = {"version": 1, "capture_id": capture_id, "source": source, + "screen_type": before.session_type, "node": socket.gethostname(), "uid": os.getuid(), + "width": g.width, "height": g.height, + "sha256": hashlib.sha256(png).hexdigest(), "bytes": len(png), + "path": str(image_path), "timestamp": started, + "completed_at": datetime.now(timezone.utc).isoformat(), + "active_vt": before.active_vt, "session": asdict(before), + "geometry": asdict(g), "backend": backend, + "cursor": ("pixels-present-in-framebuffer" if before.session_type == "tty" + else "hardware-cursor-not-included-by-XGetImage"), + "pixel_source_verified": True, "synthetic": False} + _write_new(image_path, png) + try: + _write_new(output_dir / (capture_id + ".json"), + json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode()) + except BaseException: + image_path.unlink(missing_ok=True) + raise + return manifest + + +def _capture_framebuffer(before: Foreground, device: str, seat: str) -> tuple[bytes, Geometry]: + require(re.fullmatch(r"/dev/fb[0-9]+", device) is not None, "FRAMEBUFFER_DEVICE_INVALID") + try: + fd = os.open(device, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + except OSError: + raise CaptureError("FRAMEBUFFER_UNREADABLE") from None + try: + require(stat.S_ISCHR(os.fstat(fd).st_mode), "FRAMEBUFFER_DEVICE_INVALID") + g = read_geometry(fd) + count = (g.yoffset + g.height) * g.stride + raw = os.pread(fd, count, 0) + require(len(raw) == count, "FRAMEBUFFER_READ_SHORT") + require(read_geometry(fd) == g, "GEOMETRY_CHANGED_DURING_CAPTURE") + require_unchanged(before, foreground(seat, allow_graphics=True)) + except OSError: + raise CaptureError("FRAMEBUFFER_READ_FAILED") from None + finally: + os.close(fd) + png = encode_png(decode_framebuffer(raw, g), g.width, g.height, prefer_indexed=True) + return png, g + + +def _read_private(path: Path, max_bytes: int) -> bytes: + try: + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + s = os.fstat(fd) + require(stat.S_ISREG(s.st_mode) and s.st_uid == os.getuid() and s.st_mode & 0o077 == 0 + and s.st_nlink == 1 and s.st_size <= max_bytes, "CAPTURE_ARTIFACT_UNTRUSTED") + with os.fdopen(fd, "rb", closefd=False) as f: + b = f.read(max_bytes + 1) + require(len(b) == s.st_size, "CAPTURE_ARTIFACT_CHANGED") + return b + finally: + os.close(fd) + except OSError: + raise CaptureError("CAPTURE_ARTIFACT_UNAVAILABLE") from None + + +def read_chunk(output_dir: Path | str, capture_id: str, index: int, *, chunk_bytes: int = 2100, + expected_sha256: str | None = None) -> dict: + require(isinstance(capture_id, str) and CAPTURE_ID.fullmatch(capture_id) is not None, + "CAPTURE_ID_INVALID") + require(type(index) is int and index >= 0 and type(chunk_bytes) is int + and 1 <= chunk_bytes <= MAX_CHUNK_BYTES, "CHUNK_BOUNDS_INVALID") + directory = _directory(output_dir) + try: + m = json.loads(_read_private(directory / (capture_id + ".json"), 16384)) + except (json.JSONDecodeError, UnicodeDecodeError): + raise CaptureError("CAPTURE_MANIFEST_INVALID") from None + require(isinstance(m, dict) and m.get("capture_id") == capture_id and m.get("synthetic") is False + and m.get("pixel_source_verified") is True, "CAPTURE_MANIFEST_INVALID") + png = _read_private(directory / (capture_id + ".png"), MAX_IMAGE_BYTES) + digest = hashlib.sha256(png).hexdigest() + require(digest == m.get("sha256") and len(png) == m.get("bytes"), "CAPTURE_SHA256_MISMATCH") + require(expected_sha256 is None or expected_sha256 == digest, "EXPECTED_SHA256_MISMATCH") + total = math.ceil(len(png) / chunk_bytes) + require(index < total, "CHUNK_INDEX_OUT_OF_RANGE") + data = png[index * chunk_bytes:(index + 1) * chunk_bytes] + return {"capture_id": capture_id, "index": index, "total_chunks": total, + "chunk_bytes": len(data), "bytes": len(png), "sha256": digest, + "encoding": "base64", "data": base64.b64encode(data).decode("ascii"), + "more": index + 1 < total} + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("output_dir") + args = parser.parse_args() + try: + print(json.dumps({"ok": True, "result": capture_screen(args.output_dir)}, sort_keys=True)) + except CaptureError as exc: + print(json.dumps({"ok": False, "error": exc.code}, sort_keys=True)) + raise SystemExit(1) diff --git a/source/expanded/framebridge_expanded/real_screen/screen_control.py b/source/expanded/framebridge_expanded/real_screen/screen_control.py new file mode 100644 index 0000000..bb5fb3d --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_control.py @@ -0,0 +1,301 @@ +"""Bind real framebuffer observations to the visible, owned tmux terminal. + +This module never selects a window, pane or VT. Input is permitted only to the +pane that was active in the client on the observed foreground VT. Capture files +and manifests are stored by the caller in its private, immutable capture store. +""" +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import stat +import subprocess +import time +from datetime import datetime, timezone + + +class ScreenControlError(RuntimeError): + def __init__(self, code, **details): + super().__init__(code) + self.code = code + self.details = details + + +ControlError = ScreenControlError + + +KEYS = { + "ENTER": "Enter", "RETURN": "Enter", "TAB": "Tab", "SHIFT_TAB": "BTab", + "ESC": "Escape", "BACKSPACE": "BSpace", "DELETE": "DC", "INSERT": "IC", + "UP": "Up", "DOWN": "Down", "LEFT": "Left", "RIGHT": "Right", + "HOME": "Home", "END": "End", "PAGE_UP": "PPage", "PAGE_DOWN": "NPage", + "SPACE": "Space", "CTRL_C": "C-c", "CTRL_D": "C-d", "CTRL_Z": "C-z", + "CTRL_A": "C-a", "CTRL_E": "C-e", "CTRL_L": "C-l", "CTRL_U": "C-u", + "CTRL_K": "C-k", "CTRL_R": "C-r", "CTRL_W": "C-w", + **{"F" + str(n): "F" + str(n) for n in range(1, 13)}, +} + + +def _active_vt(): + value = Path("/sys/class/tty/tty0/active").read_text().strip() + if re.fullmatch(r"tty[1-9][0-9]*", value) is None: + raise ScreenControlError("SCREEN_ACTIVE_VT_INVALID") + return value + + +def _proc(pid, *, tmux=False): + p = Path("/proc") / str(int(pid)) + if p.stat().st_uid != os.getuid(): + raise ScreenControlError("SCREEN_PROCESS_OWNER_MISMATCH") + raw = (p / "stat").read_text() + fields = raw[raw.rfind(")") + 2:].split() + if tmux and os.path.realpath(p / "exe") != os.path.realpath("/usr/bin/tmux"): + raise ScreenControlError("SCREEN_TMUX_PROCESS_MISMATCH") + return {"pid": int(pid), "start_ticks": int(fields[19]), + "uid": p.stat().st_uid} + + +def _socket_identity(path): + """Reject symlinks and paths outside the owned runtime directories.""" + p = Path(path) + if not p.is_absolute() or ".." in p.parts: + raise ScreenControlError("SCREEN_SOCKET_PATH_UNTRUSTED") + roots = [Path("/run/user") / str(os.getuid()), + Path("/tmp") / ("tmux-" + str(os.getuid()))] + root = next((r for r in roots if p.is_relative_to(r)), None) + if root is None: + raise ScreenControlError("SCREEN_SOCKET_PATH_UNTRUSTED") + current = root + for part in ("", *p.relative_to(root).parts): + if part: + current /= part + st = current.lstat() + if stat.S_ISLNK(st.st_mode) or st.st_uid != os.getuid(): + raise ScreenControlError("SCREEN_SOCKET_OWNER_MISMATCH") + if current != p: + if not stat.S_ISDIR(st.st_mode) or stat.S_IMODE(st.st_mode) & 0o022: + raise ScreenControlError("SCREEN_SOCKET_PARENT_UNTRUSTED") + elif not stat.S_ISSOCK(st.st_mode): + raise ScreenControlError("SCREEN_SOCKET_NOT_SOCKET") + return {"path": str(p), "device": st.st_dev, "inode": st.st_ino, + "uid": st.st_uid} + + +def _server_sockets(): + """Only probe sockets actually held by a same-UID tmux server process.""" + unix = {} + for line in Path("/proc/net/unix").read_text().splitlines()[1:]: + fields = line.split(maxsplit=7) + if len(fields) == 8 and fields[7].startswith("/"): + unix[fields[6]] = fields[7] + found = [] + for p in Path("/proc").iterdir(): + if not p.name.isdigit(): + continue + try: + if p.stat().st_uid != os.getuid() or (p / "comm").read_text().strip() != "tmux: server": + continue + process = _proc(int(p.name), tmux=True) + for fd in (p / "fd").iterdir(): + target = os.readlink(fd) + match = re.fullmatch(r"socket:\[([0-9]+)\]", target) + if match and match[1] in unix: + try: + identity = _socket_identity(unix[match[1]]) + except (OSError, ScreenControlError): + continue + found.append((identity, process)) + if _proc(int(p.name), tmux=True) != process: + raise ScreenControlError("SCREEN_TMUX_SERVER_CHANGED") + except (FileNotFoundError, PermissionError, ProcessLookupError): + continue + unique = {(s[0]["path"], s[1]["pid"]): s for s in found} + if len(unique) > 32: + raise ScreenControlError("SCREEN_TMUX_SERVER_LIMIT") + return list(unique.values()) + + +def _tmux(socket_path, *args): + env = {k: v for k, v in os.environ.items() if k not in ("TMUX", "TMUX_PANE")} + env.update({"LC_ALL": "C.UTF-8", "TERM": "xterm-256color"}) + try: + result = subprocess.run(["/usr/bin/tmux", "-S", socket_path, *args], + capture_output=True, text=True, env=env, timeout=4) + except (OSError, subprocess.TimeoutExpired) as exc: + raise ScreenControlError("SCREEN_TMUX_COMMAND_FAILED") from exc + if result.returncode or len(result.stdout.encode()) > 65536: + raise ScreenControlError("SCREEN_TMUX_COMMAND_FAILED") + return result.stdout + + +def _rows(value, size): + out = [] + for line in value.splitlines(): + fields = line.split("|") + if len(fields) != size: + raise ScreenControlError("SCREEN_TMUX_METADATA_INVALID") + out.append(fields) + return out + + +def snapshot_binding(): + """Read-only snapshot of the active visible terminal; never fall back hidden.""" + vt = _active_vt() + tty = "/dev/" + vt + matches = [] + client_format = "|".join(["#{client_pid}", "#{client_tty}", "#{session_id}", + "#{client_width}", "#{client_height}", "#{client_control_mode}"]) + pane_format = "|".join(["#{session_id}", "#{window_id}", "#{window_active}", + "#{pane_id}", "#{pane_pid}", "#{pane_tty}", "#{pane_active}", + "#{pane_left}", "#{pane_top}", "#{pane_width}", "#{pane_height}", + "#{window_width}", "#{window_height}", "#{pane_dead}", + "#{window_zoomed_flag}", "#{pane_in_mode}"]) + for socket_identity, server in _server_sockets(): + path = socket_identity["path"] + try: + clients = [c for c in _rows(_tmux(path, "list-clients", "-F", client_format), 6) + if c[1] == tty and c[5] == "0"] + if not clients: + continue + panes = _rows(_tmux(path, "list-panes", "-a", "-F", pane_format), 16) + for client in clients: + visible = [p for p in panes if p[0] == client[2] and p[2] == "1" + and p[6] == "1" and p[13] == "0"] + if len(visible) != 1: + raise ScreenControlError("SCREEN_VISIBLE_PANE_AMBIGUOUS") + p = visible[0] + binding = {"version": 1, "backend": "visible-tmux", "node": os.uname().nodename, + "uid": os.getuid(), "active_vt": vt, "client_tty": tty, + "socket": socket_identity, "server": server, + "client": _proc(int(client[0]), tmux=True), + "session_id": p[0], "window_id": p[1], "pane_id": p[3], + "pane": _proc(int(p[4])), "pane_tty": p[5], + "client_size": [int(client[3]), int(client[4])], + "window_size": [int(p[11]), int(p[12])], + "pane_geometry": [int(x) for x in p[7:11]], + "window_zoomed": p[14] == "1", "pane_in_mode": p[15] == "1"} + if _socket_identity(path) != socket_identity or _proc(server["pid"], tmux=True) != server: + raise ScreenControlError("SCREEN_TMUX_SERVER_CHANGED") + matches.append(binding) + except ScreenControlError as exc: + if exc.code == "SCREEN_TMUX_COMMAND_FAILED": + continue + raise + if _active_vt() != vt: + raise ScreenControlError("SCREEN_ACTIVE_VT_CHANGED") + if not matches: + raise ScreenControlError("SCREEN_VISIBLE_TMUX_NOT_FOUND") + if len(matches) != 1: + raise ScreenControlError("SCREEN_VISIBLE_CLIENT_AMBIGUOUS") + return matches[0] + + +def validate_binding(expected): + if not isinstance(expected, dict) or expected.get("backend") != "visible-tmux": + raise ScreenControlError("SCREEN_BINDING_REQUIRED") + current = snapshot_binding() + if current != expected: + raise ScreenControlError("SCREEN_BINDING_CHANGED") + return current + + +def bind_screen(manifest): + if not isinstance(manifest, dict) or not isinstance(manifest.get("capture_id"), str) or not manifest["capture_id"]: + raise ScreenControlError("SCREEN_CAPTURE_ID_REQUIRED") + if manifest.get("source") != "linux-framebuffer" or manifest.get("screen_type") != "tty": + raise ScreenControlError("SCREEN_CONTROL_MODE_UNSUPPORTED") + binding = snapshot_binding() + if manifest.get("active_vt") != binding["active_vt"]: + raise ScreenControlError("SCREEN_CAPTURE_VT_MISMATCH") + if manifest.get("node") != binding["node"] or manifest.get("uid") != binding["uid"]: + raise ScreenControlError("SCREEN_CAPTURE_NODE_MISMATCH") + return {**manifest, "binding": binding} + + +def _verify_capture(manifest, max_age_s): + if not isinstance(manifest, dict) or not manifest.get("capture_id"): + raise ScreenControlError("SCREEN_CAPTURE_ID_REQUIRED") + if manifest.get("source") != "linux-framebuffer" or manifest.get("screen_type") != "tty": + raise ScreenControlError("SCREEN_CONTROL_MODE_UNSUPPORTED") + if manifest.get("synthetic") is not False or manifest.get("pixel_source_verified") is not True: + raise ScreenControlError("SCREEN_REAL_CAPTURE_REQUIRED") + if max_age_s is not None and (not isinstance(max_age_s, (int, float)) or not 0 < max_age_s <= 300): + raise ScreenControlError("SCREEN_CAPTURE_AGE_LIMIT_INVALID") + try: + when = datetime.fromisoformat(manifest["completed_at"]) + if when.tzinfo is None: + raise ValueError() + age = (datetime.now(timezone.utc) - when).total_seconds() + except (KeyError, TypeError, ValueError): + raise ScreenControlError("SCREEN_CAPTURE_TIME_INVALID") from None + if age < -5 or (max_age_s is not None and age > max_age_s): + raise ScreenControlError("SCREEN_CAPTURE_EXPIRED") + path = Path(manifest.get("path", "")) + expected = manifest.get("sha256", "") + if not path.is_absolute() or re.fullmatch(r"[a-f0-9]{64}", expected) is None: + raise ScreenControlError("SCREEN_CAPTURE_FILE_INVALID") + try: + fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW) + try: + st = os.fstat(fd) + if not stat.S_ISREG(st.st_mode) or st.st_uid != os.getuid() or st.st_size > 64 * 1024 * 1024: + raise ScreenControlError("SCREEN_CAPTURE_FILE_UNTRUSTED") + with os.fdopen(fd, "rb", closefd=False) as f: + digest = hashlib.file_digest(f, "sha256").hexdigest() + if digest != expected: + raise ScreenControlError("SCREEN_CAPTURE_SHA256_MISMATCH") + finally: + os.close(fd) + except OSError as exc: + raise ScreenControlError("SCREEN_CAPTURE_FILE_UNAVAILABLE") from exc + + +def act(capture_manifest, kind, *, text=None, key=None, max_age_s=None): + """Send literal text (no controls/newline) or one explicit allowlisted key. + + The RPC caller must load this manifest by capture_id from its private store; + it must not accept a replacement manifest supplied by a remote caller. + Foreground changes are checked before and after dispatch. An independent + local user's VT switch concurrent with the actual write cannot be made + atomic with tmux; postflight failure therefore reports performed=True. + """ + if kind in ("text", "text.type", "type"): + if key is not None or not isinstance(text, str) or not text or len(text.encode()) > 16384: + raise ScreenControlError("SCREEN_TEXT_INVALID") + if any(ord(c) < 32 or ord(c) == 127 for c in text): + raise ScreenControlError("SCREEN_TEXT_CONTROL_CHARACTER") + command = ["send-keys", "-l", "--", text] + action = "text.type" + elif kind in ("key", "key.send"): + if text is not None or not isinstance(key, str) or key not in KEYS: + raise ScreenControlError("SCREEN_KEY_INVALID") + command = ["send-keys", KEYS[key]] + action = "key.send" + else: + raise ScreenControlError("SCREEN_ACTION_UNSUPPORTED") + _verify_capture(capture_manifest, max_age_s) + expected = capture_manifest.get("binding") + binding = validate_binding(expected) + if binding.get("pane_in_mode"): + raise ScreenControlError("SCREEN_TMUX_MODE_INPUT_UNSUPPORTED") + if capture_manifest.get("active_vt") != binding["active_vt"]: + raise ScreenControlError("SCREEN_CAPTURE_VT_MISMATCH") + if capture_manifest.get("node") != binding["node"] or capture_manifest.get("uid") != binding["uid"]: + raise ScreenControlError("SCREEN_CAPTURE_NODE_MISMATCH") + # A second check immediately before dispatch prevents using a stale probe. + binding = validate_binding(expected) + path, pane = binding["socket"]["path"], binding["pane_id"] + _tmux(path, command[0], "-t", pane, *command[1:]) + try: + validate_binding(expected) + except (ScreenControlError, OSError) as exc: + raise ScreenControlError("SCREEN_CHANGED_AFTER_INPUT", performed=True, + action=action, capture_id=capture_manifest["capture_id"]) from exc + return {"ok": True, "performed": True, "action": action, + "capture_id": capture_manifest["capture_id"], "binding": binding, + "input_bytes": len(text.encode()) if action == "text.type" else None, + "key": key if action == "key.send" else None, + "input_target": "observed-visible-pane"} diff --git a/source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py b/source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py new file mode 100644 index 0000000..e0fddf9 --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_frontdoor.py @@ -0,0 +1,267 @@ +"""Original FrameBridge real-screen MCP adapter. + +The node captures pixels. This adapter validates and transports those bytes; +it never renders terminal text into an image. +""" +from __future__ import annotations + +import base64 +import binascii +import hashlib +import json +import math +import re +import shlex +import struct +import uuid +from typing import Callable + +CHUNK_BYTES = 2100 +MAX_IMAGE_BYTES = 32 * 1024 * 1024 +MAX_STDOUT_BYTES = 4096 +RPC_COMMAND = '/usr/bin/python3 -B -m framebridge_expanded.real_screen.screen_rpc ' +HEX_ID = re.compile(r"[0-9a-f]{32}\Z") +SHA = re.compile(r"[0-9a-f]{64}\Z") +NODE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,79}\Z") +SID = re.compile(r"[A-Za-z0-9_-]{1,100}\Z") +ERROR = re.compile(r"[A-Z0-9_]{1,100}\Z") +READ = {"readOnlyHint": True, "destructiveHint": False, + "idempotentHint": False, "openWorldHint": False} +CHANGE = {**READ, "readOnlyHint": False} + + +class ScreenError(RuntimeError): + """A bounded public error suitable for the MCP response.""" + + def __init__(self, code, details=None): + super().__init__(code) + self.details = details or {} + + +def require(ok, code): + if not ok: + raise ScreenError(code) + + +def encode(value): + return json.dumps(value, ensure_ascii=True, sort_keys=True, + separators=(",", ":"), allow_nan=False).encode("ascii") + + +def valid_reference(value, regex, optional=False): + return type(value) is str and ((optional and value == "") or regex.fullmatch(value) is not None) + + +class _AuthenticatedReply(dict): + """A payload tied to the EXEC recipient verified by this client instance. + + The Linux hostname in the payload is observed screen identity. The route + alias and UUID come from the existing authenticated executor envelope. + A JSON object cannot supply this local proof or change its capture identity. + """ + + _IDENTITY_FIELDS = ("capture_id", "node", "sha256", "session_id", "bytes", + "width", "height", "source", "screen_type", + "full_screen", "authentic_pixels") + + def __init__(self, payload, owner, node, node_id): + super().__init__(payload) + self.__owner, self.__node, self.__node_id = owner, node, node_id + self.__identity = tuple(self.get(key) for key in self._IDENTITY_FIELDS) + + def bound_to(self, owner, node, node_id=None): + return (self.__owner is owner and self.__node == node + and (node_id is None or self.__node_id == node_id) + and self.__identity == tuple(self.get(key) for key in self._IDENTITY_FIELDS)) + + @property + def node_id(self): + return self.__node_id + + +class ScreenClient: + """Transport only; execution policy and node resolution stay in the host.""" + + def __init__(self, exec_request: Callable, image_factory: Callable, + resolve_node: Callable | None = None): + self.exec_request = exec_request + self.image_factory = image_factory + self.resolve_node = resolve_node or (lambda node: node.upper()) + self._reply_owner = object() + + def remote(self, node, request): + payload = base64.b64encode(encode(request)).decode("ascii") + script = RPC_COMMAND + shlex.quote(payload) + "\n" + digest = hashlib.sha256(script.encode("utf-8")).hexdigest() + job_id = "cua-screen-" + uuid.uuid4().hex + outer = self.exec_request(script=script, authorized=True, timeout_s=30, + job_id=job_id, sha256=digest, + include_stdout=True, target=node) + require(type(outer) is dict, "SCREEN_EXEC_FAILED") + require(type(outer.get("exit_code")) in (int, str) and + str(outer["exit_code"]) in ("0", "1"), "SCREEN_EXEC_FAILED") + require(outer.get("target") == node and outer.get("mcp_job_id") == job_id and + outer.get("mcp_script_sha256") == digest, "SCREEN_EXEC_BINDING_INVALID") + try: + node_id = outer.get("node_id") + require(type(node_id) is str, "SCREEN_EXEC_NODE_ID_INVALID") + node_id = str(uuid.UUID(node_id)) + except (ValueError, AttributeError): + raise ScreenError("SCREEN_EXEC_NODE_ID_INVALID") from None + stdout = outer.get("stdout") + require(type(stdout) is str and len(stdout.encode("utf-8")) <= MAX_STDOUT_BYTES and + outer.get("stdout_truncated") is not True, "SCREEN_STDOUT_INVALID") + try: + envelope = json.loads(stdout) + except (ValueError, UnicodeError): + raise ScreenError("SCREEN_RESPONSE_INVALID") from None + require(type(envelope) is dict and type(envelope.get("ok")) is bool, + "SCREEN_RESPONSE_INVALID") + if not envelope["ok"]: + code = envelope.get("error") + details = {} + if type(code) is dict: + for name in ("performed", "capture_consumed", "observe_again"): + if type(code.get(name)) is bool: + details[name] = code[name] + if valid_reference(code.get("capture_id"), HEX_ID): + details["capture_id"] = code["capture_id"] + if valid_reference(code.get("cause"), ERROR): + details["cause"] = code["cause"] + code = code.get("code") + raise ScreenError(code if valid_reference(code, ERROR) else "SCREEN_BACKEND_FAILED", details) + require(outer.get("ok") is True and str(outer["exit_code"]) == "0", "SCREEN_EXEC_FAILED") + require(type(envelope.get("result")) is dict, "SCREEN_RESPONSE_INVALID") + return _AuthenticatedReply(envelope["result"], self._reply_owner, node, node_id) + + def image(self, node, manifest): + capture_id = manifest.get("capture_id") + total, digest = manifest.get("bytes"), manifest.get("sha256") + require(valid_reference(capture_id, HEX_ID) and valid_reference(digest, SHA), + "SCREEN_MANIFEST_INVALID") + require(type(total) is int and 0 < total <= MAX_IMAGE_BYTES, "SCREEN_IMAGE_SIZE_INVALID") + require(isinstance(manifest, _AuthenticatedReply) and + manifest.bound_to(self._reply_owner, node), "SCREEN_IMAGE_NODE_MISMATCH") + require(valid_reference(manifest.get("node"), NODE), "SCREEN_IMAGE_HOSTNAME_INVALID") + width, height = manifest.get("width"), manifest.get("height") + require(type(width) is int and type(height) is int and + 0 < width <= 16384 and 0 < height <= 16384, + "SCREEN_DIMENSIONS_INVALID") + require(manifest.get("source") in ("linux-framebuffer", "x11-root-XGetImage"), + "SCREEN_PIXEL_SOURCE_INVALID") + require(manifest.get("full_screen") is True and manifest.get("authentic_pixels") is True, + "SCREEN_PIXEL_PROVENANCE_INVALID") + parts = [] + count = math.ceil(total / CHUNK_BYTES) + for index in range(count): + part = self.remote(node, {"op": "read_chunk", "capture_id": capture_id, + "index": index, "expected_sha256": digest}) + require(isinstance(part, _AuthenticatedReply) and + part.bound_to(self._reply_owner, node, manifest.node_id), + "SCREEN_CHUNK_NODE_MISMATCH") + require(part.get("capture_id") == capture_id and type(part.get("index")) is int and + part["index"] == index and type(part.get("total_chunks")) is int and + part["total_chunks"] == count, "SCREEN_CHUNK_BINDING_INVALID") + require(type(part.get("bytes")) is int and part["bytes"] == total and + part.get("sha256") == digest and part.get("encoding") == "base64" and + type(part.get("more")) is bool and part["more"] == (index + 1 < count), + "SCREEN_CHUNK_MANIFEST_INVALID") + text = part.get("data") + require(type(text) is str and len(text) <= 2800, "SCREEN_CHUNK_INVALID") + try: + data = base64.b64decode(text, validate=True) + except (ValueError, UnicodeError, binascii.Error): + raise ScreenError("SCREEN_CHUNK_INVALID") from None + require(len(data) == min(CHUNK_BYTES, total - index * CHUNK_BYTES), + "SCREEN_CHUNK_LENGTH_INVALID") + parts.append(data) + data = b"".join(parts) + require(len(data) == total and hashlib.sha256(data).hexdigest() == digest, + "SCREEN_IMAGE_HASH_MISMATCH") + require(len(data) >= 33 and data[:8] == b"\x89PNG\r\n\x1a\n" and + data[12:16] == b"IHDR" and struct.unpack(">II", data[16:24]) == (width, height), + "SCREEN_PNG_INVALID") + return self.image_factory(data=data, format="png") + + def call(self, op, node, authorized, **kwargs): + requested_node = node + try: + require(authorized is True, "AUTHORIZED_TRUE_REQUIRED") + require(valid_reference(node, NODE), "SCREEN_NODE_INVALID") + node = self.resolve_node(node) + require(valid_reference(node, NODE), "SCREEN_NODE_INVALID") + if kwargs.get("session_id"): + require(valid_reference(kwargs["session_id"], SID), "SCREEN_SESSION_INVALID") + else: + kwargs.pop("session_id", None) + if op in ("key", "text", "read_capture"): + require(valid_reference(kwargs.get("capture_id"), HEX_ID), "SCREEN_CAPTURE_ID_REQUIRED") + if op == "key": + require(type(kwargs.get("key")) is str and 0 < len(kwargs["key"]) <= 32, + "SCREEN_KEY_INVALID") + if op == "text": + require(type(kwargs.get("text")) is str and 0 < len(kwargs["text"].encode("utf-8")) <= 4096, + "SCREEN_TEXT_INVALID") + manifest = self.remote(node, {"op": op, **kwargs}) + if op == "read_capture": + require(manifest.get("capture_id") == kwargs["capture_id"], + "SCREEN_CAPTURE_REFERENCE_MISMATCH") + image = self.image(node, manifest) + metadata = {"ok": True, "node": node, "requested_node": requested_node, + "node_id": manifest.node_id, "hostname": manifest["node"], + "result": dict(manifest, transport_node=node, node_id=manifest.node_id)} + metadata["result"].pop("path", None) + metadata["result"].pop("image_base64", None) + metadata["result"]["image_mime"] = "image/png" + metadata["result"]["full_screen"] = True + metadata["result"]["authentic_pixels"] = True + return [metadata, image] + except ScreenError as exc: + return {"ok": False, "node": requested_node, "error": str(exc), **exc.details} + except Exception: + return {"ok": False, "node": requested_node, "error": "SCREEN_BACKEND_UNAVAILABLE"} + + +def resolve_profile_node(alias): + """Canonicalize only through the enrolled profile registry, never guessed hostnames.""" + from ..fleet import resolve_node, FleetError + try: + descriptor = resolve_node(alias) + except FleetError as exc: + raise ScreenError(str(exc)) from None + return "LOCAL" if descriptor["transport"] == "local" else descriptor["alias"] + + +def register_framebridge_screen_tools(mcp, exec_request, *, image_factory=None, + resolve_node=None): + """Register real image tools. The host supplies its existing executor.""" + if image_factory is None: + from mcp.server.fastmcp import Image + image_factory = Image + client = ScreenClient(exec_request, image_factory, resolve_node) + + @mcp.tool(annotations=dict(READ)) + def framebridge_screen_observe(node: str, authorized: bool, + session_id: str = ""): + """Capture the node's entire active screen as real pixels, without rendering text. + + To display or redisplay this original in ChatGPT, call framebridge_screen_show + with the returned node and capture_id. Do not take another capture to show it. + """ + return client.call("observe", node, authorized, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_screen_key(node: str, capture_id: str, key: str, + authorized: bool, session_id: str = ""): + """Send a key only to the visible target bound to a previous real capture.""" + return client.call("key", node, authorized, capture_id=capture_id, + key=key, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_screen_text(node: str, capture_id: str, text: str, + authorized: bool, session_id: str = ""): + """Type into the visible target bound to a previous real capture, then capture again.""" + return client.call("text", node, authorized, capture_id=capture_id, + text=text, session_id=session_id) + + return client diff --git a/source/expanded/framebridge_expanded/real_screen/screen_presentation.py b/source/expanded/framebridge_expanded/real_screen/screen_presentation.py new file mode 100644 index 0000000..692c304 --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_presentation.py @@ -0,0 +1,173 @@ +"""Display an already captured, authenticated PNG in MCP Apps-capable clients. + +No HTTP image endpoint, upload, credential, external asset, or new capture is used. +The normal ImageContent remains available to clients that do not render MCP Apps. +""" +from __future__ import annotations + +import json +from typing import Annotated, Any + +from mcp.types import CallToolResult, TextContent +from pydantic import BaseModel + +from .screen_frontdoor import READ + +TEMPLATE_URI = "ui://framebridge/screen-v1.html" +TOOL_META = { + "ui": {"resourceUri": TEMPLATE_URI}, + "openai/outputTemplate": TEMPLATE_URI, + "openai/toolInvocation/invoking": "Loading original screen capture", + "openai/toolInvocation/invoked": "Original screen capture ready", +} +RESOURCE_META = { + "ui": {"prefersBorder": True, "csp": {"connectDomains": [], "resourceDomains": []}}, + "openai/widgetDescription": ( + "FrameBridge original screen capture viewer. Shows the full saved PNG, " + "its node, capture identifier, dimensions and verified SHA-256. " + "This is a historical capture, not a live screen." + ), + "openai/ui": {"availableDisplayModes": ["inline", "fullscreen"]}, +} + + +class ScreenViewResult(BaseModel): + ok: bool + node: str + requested_node: str | None = None + node_id: str | None = None + hostname: str | None = None + result: dict[str, Any] | None = None + error: str | None = None + + +VIEWER_HTML = r""" + +FrameBridge original screen capture + +

FrameBridge original screen capture

Saved capture · read only
+

Waiting for the original PNG.

+

+
+ + +Download original PNG +
+ +""" + + +def presentation_result(response): + """Preserve the exact image for the model and pass it to the optional UI.""" + if not isinstance(response, list) or len(response) != 2: + metadata = response if isinstance(response, dict) else {"ok": False, "node": "", "error": "SCREEN_PRESENTATION_FAILED"} + return CallToolResult( + content=[TextContent(type="text", text=json.dumps(metadata, sort_keys=True))], + structuredContent=metadata, isError=True, + ) + metadata, image = response + content = image.to_image_content() + return CallToolResult( + content=[TextContent(type="text", text=json.dumps(metadata, sort_keys=True)), content], + structuredContent=metadata, + _meta={"framebridgeImage": {"mimeType": content.mimeType, "data": content.data}}, + ) + + +def register_screen_presentation(mcp, client): + """Add an explicit render/replay tool without changing observation semantics.""" + @mcp.resource(TEMPLATE_URI, name="FrameBridge original screen", + description="Offline viewer for an authenticated saved screen PNG.", + mime_type="text/html;profile=mcp-app", meta=dict(RESOURCE_META)) + def framebridge_screen_viewer(): + return VIEWER_HTML + + @mcp.tool(annotations=dict(READ), meta=dict(TOOL_META)) + def framebridge_screen_show(node: str, capture_id: str, authorized: bool) -> Annotated[CallToolResult, ScreenViewResult]: + """Display or redisplay a saved original screen PNG, without taking a new capture. + + First use framebridge_screen_observe, then pass its node and capture_id here. + This returns the same full PNG bytes and SHA-256, and an optional ChatGPT viewer. + It is a historical read only view; use a fresh observation before controlling a screen. + """ + return presentation_result(client.call("read_capture", node, authorized, capture_id=capture_id)) + + return framebridge_screen_show diff --git a/source/expanded/framebridge_expanded/real_screen/screen_rpc.py b/source/expanded/framebridge_expanded/real_screen/screen_rpc.py new file mode 100644 index 0000000..7c1e596 --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_rpc.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +"""Original FrameBridge real-screen RPC: actual pixels and bound visible input.""" +from __future__ import annotations + +import base64 +import fcntl +import hashlib +import json +import os +from pathlib import Path +import re +import stat +import sys +import time + +from .screen_capture import CaptureError, capture_screen, read_chunk +from .screen_control import ControlError, act, bind_screen, snapshot_binding + +MAX_REQUEST = 16384 +CAPTURE_ID = re.compile(r"[0-9a-f]{32}\Z") +HEX_SHA = re.compile(r"[0-9a-f]{64}\Z") + + +class ScreenRpcError(RuntimeError): + def __init__(self, code: str, **details): + self.code = code + self.details = details + super().__init__(code) + + +def require(condition, code): + if not condition: + raise ScreenRpcError(code) + + +def encode(value): + return json.dumps(value, sort_keys=True, ensure_ascii=True, separators=(",", ":"), allow_nan=False).encode("ascii") + + +def state_directory() -> Path: + parent = Path('/run/user') / str(os.getuid()) + st = parent.lstat() + require(stat.S_ISDIR(st.st_mode) and st.st_uid == os.getuid() and st.st_mode & 0o077 == 0, + 'RUNTIME_DIRECTORY_UNTRUSTED') + profile = Path(os.environ.get('FRAMEBRIDGE_EXPANDED_STATE', str(Path.home() / '.local/share/framebridge-expanded'))).expanduser().absolute() + path = parent / ('framebridge-screen-' + hashlib.sha256(str(profile).encode()).hexdigest()[:16]) + try: + path.mkdir(mode=0o700) + except FileExistsError: + pass + st = path.lstat() + require(stat.S_ISDIR(st.st_mode) and st.st_uid == os.getuid() and st.st_mode & 0o077 == 0, + 'SCREEN_DIRECTORY_UNTRUSTED') + return path + + +def checked_read(path: Path, max_bytes=32768): + fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW) + try: + st = os.fstat(fd) + require(stat.S_ISREG(st.st_mode) and st.st_uid == os.getuid() and st.st_mode & 0o077 == 0, + 'SCREEN_RECORD_UNTRUSTED') + require(st.st_size <= max_bytes, 'SCREEN_RECORD_TOO_LARGE') + data = os.read(fd, max_bytes + 1) + require(len(data) == st.st_size, 'SCREEN_RECORD_CHANGED') + return json.loads(data) + finally: + os.close(fd) + + +def write_record(path: Path, value, *, replace=False): + data = encode(value) + temporary = path.with_name(path.name + '.' + str(os.getpid()) + '.tmp') + fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_CLOEXEC | os.O_NOFOLLOW, 0o600) + try: + with os.fdopen(fd, 'wb') as stream: + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + if not replace: + require(not path.exists(), 'SCREEN_RECORD_EXISTS') + os.replace(temporary, path) + finally: + try: + temporary.unlink() + except FileNotFoundError: + pass + + +def public_manifest(manifest): + # Internal path is unnecessary to use or understand a captured screen. + return {k: v for k, v in manifest.items() if k not in {'path', '_action_consumed'}} + + +def observe(directory: Path, requested_session_id=None): + before = None + before_error = None + try: + before = snapshot_binding() + except ControlError as exc: + before_error = exc.code + manifest = capture_screen(directory) + manifest['full_screen'] = True + manifest['authentic_pixels'] = True + manifest['mime'] = 'image/png' + if manifest.get('screen_type') == 'tty' and before is not None: + manifest = bind_screen(manifest) + require(manifest.get('binding') == before, 'VISIBLE_TARGET_CHANGED_DURING_CAPTURE') + manifest['control_available'] = True + else: + manifest['control_available'] = False + manifest['control_error'] = before_error or 'USE_GRAPHICAL_CUA_FOR_INPUT' + identity = manifest.get('binding') or { + 'node': manifest['node'], 'uid': manifest['uid'], + 'active_vt': manifest.get('active_vt'), 'session': manifest.get('session'), + 'screen_type': manifest.get('screen_type'), + } + manifest['session_id'] = 'screen_' + hashlib.sha256(encode(identity)).hexdigest()[:32] + if requested_session_id: + manifest['requested_session_id'] = requested_session_id + manifest['requested_session_visible'] = requested_session_id == manifest['session_id'] + manifest['_action_consumed'] = False + write_record(directory / (manifest['capture_id'] + '.bound.json'), manifest) + return manifest + + +def load_capture(directory, capture_id): + require(type(capture_id) is str and CAPTURE_ID.fullmatch(capture_id), 'CAPTURE_ID_INVALID') + result = checked_read(directory / (capture_id + '.bound.json')) + require(type(result) is dict, 'CAPTURE_RECORD_INVALID') + require(result.get('capture_id') == capture_id, 'CAPTURE_RECORD_MISMATCH') + return result + + +def validate_request(request): + require(type(request) is dict, 'REQUEST_INVALID') + op = request.get('op') + allowed = { + 'observe': {'op', 'session_id'}, + 'status': {'op'}, + 'read_chunk': {'op', 'capture_id', 'index', 'expected_sha256'}, + 'read_capture': {'op', 'capture_id'}, + 'key': {'op', 'capture_id', 'session_id', 'key'}, + 'text': {'op', 'capture_id', 'session_id', 'text'}, + } + require(type(op) is str and op in allowed, 'OPERATION_INVALID') + require(not set(request).difference(allowed[op]), 'UNKNOWN_REQUEST_FIELDS') + if 'session_id' in request: + sid = request['session_id'] + require(type(sid) is str and len(sid) <= 96 and re.fullmatch(r'[A-Za-z0-9_-]+', sid), 'SESSION_ID_INVALID') + if op == 'read_chunk': + require(type(request.get('index')) is int and request['index'] >= 0, 'CHUNK_INDEX_INVALID') + require(type(request.get('expected_sha256')) is str and HEX_SHA.fullmatch(request['expected_sha256']), 'EXPECTED_SHA256_REQUIRED') + if op == 'key': + require(type(request.get('key')) is str and 0 < len(request['key']) <= 32, 'KEY_INVALID') + if op == 'text': + require(type(request.get('text')) is str and 0 < len(request['text'].encode('utf-8')) <= 4096, 'TEXT_INVALID') + require('\x00' not in request['text'], 'TEXT_INVALID') + return op + + +def dispatch(request, directory=None): + op = validate_request(request) + directory = directory or state_directory() + if op == 'status': + return {'ok': True, 'result': {'node': os.uname().nodename, 'uid': os.getuid(), + 'full_screen': True, 'authentic_pixels': True, + 'input_binding': 'foreground-visible-tmux-pane'}} + if op == 'observe': + return {'ok': True, 'result': public_manifest(observe(directory, request.get('session_id')))} + manifest = load_capture(directory, request.get('capture_id')) + if op == 'read_capture': + # Verify the existing artifact without observing or consuming input control. + read_chunk(directory, manifest['capture_id'], 0, expected_sha256=manifest['sha256']) + historical = public_manifest(manifest) + historical.update(historical_capture=True, control_available=False, + control_error='HISTORICAL_CAPTURE_OBSERVE_AGAIN_FOR_INPUT') + return {'ok': True, 'result': historical} + if op == 'read_chunk': + require(manifest['sha256'] == request['expected_sha256'], 'CAPTURE_SHA256_MISMATCH') + chunk = read_chunk(directory, manifest['capture_id'], request['index'], + chunk_bytes=2100, expected_sha256=request['expected_sha256']) + return {'ok': True, 'result': chunk} + if manifest.get('screen_type') == 'x11': + raise ScreenRpcError('USE_GRAPHICAL_CUA_FOR_X11_INPUT') + require(manifest.get('control_available') is True, 'VISIBLE_INPUT_UNAVAILABLE') + require(not manifest.get('_action_consumed'), 'CAPTURE_ALREADY_USED_OBSERVE_AGAIN') + if request.get('session_id'): + require(request['session_id'] == manifest['session_id'], 'SESSION_NOT_VISIBLE') + # Read and verify the immutable PNG before relying on its binding. + read_chunk(directory, manifest['capture_id'], 0, expected_sha256=manifest['sha256']) + # Mark the observation used before input: never replay after an uncertain send. + manifest['_action_consumed'] = True + write_record(directory / (manifest['capture_id'] + '.bound.json'), manifest, replace=True) + try: + result = act(manifest, op, text=request.get('text'), key=request.get('key')) + except ControlError as exc: + raise ScreenRpcError(exc.code, capture_id=manifest['capture_id'], + capture_consumed=True, observe_again=True, + performed=exc.details.get('performed')) from exc + time.sleep(0.08) + try: + after = observe(directory) + except (CaptureError, ControlError, ScreenRpcError, OSError) as exc: + raise ScreenRpcError('INPUT_SENT_OBSERVE_FAILED', performed=True, + capture_id=manifest['capture_id'], observe_again=True, + cause=getattr(exc, 'code', 'SCREEN_IO_FAILED')) from exc + summary = {k: v for k, v in result.items() if k in {'ok', 'performed', 'action', 'input_bytes', 'key', 'input_target'}} + after['action'] = {'kind': op, 'previous_capture_id': manifest['capture_id'], 'result': summary} + return {'ok': True, 'result': public_manifest(after)} + + +def main(argv=None): + argv = list(sys.argv[1:] if argv is None else argv) + try: + require(len(argv) == 1 and len(argv[0]) <= MAX_REQUEST * 2, 'ENCODED_REQUEST_INVALID') + raw = base64.b64decode(argv[0], validate=True) + require(len(raw) <= MAX_REQUEST, 'REQUEST_TOO_LARGE') + request = json.loads(raw) + directory = state_directory() + lock_fd = os.open(directory / '.lock', os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_NOFOLLOW, 0o600) + try: + st = os.fstat(lock_fd) + require(stat.S_ISREG(st.st_mode) and st.st_uid == os.getuid() and st.st_mode & 0o077 == 0, 'SCREEN_LOCK_UNTRUSTED') + fcntl.flock(lock_fd, fcntl.LOCK_EX) + reply = dispatch(request, directory) + finally: + os.close(lock_fd) + print(encode(reply).decode('ascii')) + return 0 + except (ScreenRpcError, CaptureError, ControlError) as exc: + details = {k: v for k, v in getattr(exc, 'details', {}).items() + if k in {'performed', 'capture_id', 'capture_consumed', 'observe_again', 'cause'}} + print(encode({'ok': False, 'error': {'code': exc.code, **details}}).decode('ascii')) + return 1 + except (ValueError, OSError, KeyError, TypeError): + print(encode({'ok': False, 'error': {'code': 'SCREEN_REQUEST_FAILED'}}).decode('ascii')) + return 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/real_screen/screen_transport.py b/source/expanded/framebridge_expanded/real_screen/screen_transport.py new file mode 100644 index 0000000..4510c5b --- /dev/null +++ b/source/expanded/framebridge_expanded/real_screen/screen_transport.py @@ -0,0 +1,197 @@ +"""Typed real-screen transport over the existing authenticated admin.exec channel. + +Only this explicit request type bypasses the visible administrative FBTERM engine. +The script field is canonical protocol data and a hash binding, never shell code. +""" +from __future__ import annotations + +import base64 +import binascii +import hashlib +import json +import os +import re +import selectors +import shlex +import signal +import subprocess +import time + +from .screen_frontdoor import RPC_COMMAND, MAX_STDOUT_BYTES +from .screen_rpc import MAX_REQUEST, encode, validate_request, ScreenRpcError + +REQUEST_TYPE = "screen_rpc" +MAX_TIMEOUT = 30 +MAX_STDERR_BYTES = 4096 +JOB = re.compile(r"[A-Za-z0-9._-]{1,80}\Z") +FIELDS = {"version", "mode", "authorized", "script", "timeout_s", "job_id", + "sha256", "include_stdout", "request_type"} + + +class ScreenTransportError(ValueError): + pass + + +class ScreenProcessError(RuntimeError): + def __init__(self, code, *, started=False): + super().__init__(code) + self.started = started + + +def need(condition, code): + if not condition: + raise ScreenTransportError(code) + + +def parse_script(script): + need(type(script) is str and script.startswith(RPC_COMMAND) + and script.endswith("\n"), "SCREEN_RPC_SCRIPT_INVALID") + encoded = script[len(RPC_COMMAND):-1] + need(len(encoded) <= MAX_REQUEST * 2, "SCREEN_RPC_REQUEST_TOO_LARGE") + try: + words = shlex.split(encoded, posix=True) + need(len(words) == 1, "SCREEN_RPC_SCRIPT_INVALID") + raw = base64.b64decode(words[0], validate=True) + need(len(raw) <= MAX_REQUEST, "SCREEN_RPC_REQUEST_TOO_LARGE") + request = json.loads(raw) + validate_request(request) + canonical = encode(request) + need(raw == canonical, "SCREEN_RPC_REQUEST_NONCANONICAL") + argument = base64.b64encode(canonical).decode("ascii") + need(script == RPC_COMMAND + shlex.quote(argument) + "\n", + "SCREEN_RPC_SCRIPT_NONCANONICAL") + return request, argument + except (ValueError, UnicodeError, binascii.Error, ScreenRpcError) as exc: + if isinstance(exc, ScreenTransportError): + raise + raise ScreenTransportError("SCREEN_RPC_REQUEST_INVALID") from None + + +def validate_payload(payload): + need(type(payload) is dict and set(payload) == FIELDS, "SCREEN_RPC_FIELDS_INVALID") + need(type(payload["version"]) is int and payload["version"] == 1, + "SCREEN_RPC_VERSION_INVALID") + need(payload["mode"] == "EXEC" and payload["request_type"] == REQUEST_TYPE, + "SCREEN_RPC_TYPE_INVALID") + need(payload["authorized"] is True, "AUTHORIZED_TRUE_REQUIRED") + need(type(payload["job_id"]) is str and JOB.fullmatch(payload["job_id"]), + "INVALID_JOB_ID") + need(type(payload["timeout_s"]) is int and 1 <= payload["timeout_s"] <= MAX_TIMEOUT, + "INVALID_SCREEN_TIMEOUT") + need(payload["include_stdout"] is True, "SCREEN_RPC_STDOUT_REQUIRED") + request, argument = parse_script(payload["script"]) + digest = hashlib.sha256(payload["script"].encode("utf-8")).hexdigest() + need(type(payload["sha256"]) is str and payload["sha256"] == digest, + "SCRIPT_SHA256_MISMATCH") + return request, argument + + +def _run_rpc(argument, timeout_s): + """Bound memory while draining both pipes; bound wall time and child lifetime.""" + argv = ["/usr/bin/python3", "-B", "-m", + "framebridge_expanded.real_screen.screen_rpc", argument] + process = None + completed = False + selector = selectors.DefaultSelector() + buffers = {"stdout": bytearray(), "stderr": bytearray()} + deadline = time.monotonic() + timeout_s + try: + process = subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True, + start_new_session=True) + selector.register(process.stdout, selectors.EVENT_READ, "stdout") + selector.register(process.stderr, selectors.EVENT_READ, "stderr") + while selector.get_map(): + remaining = deadline - time.monotonic() + if remaining <= 0: + raise ScreenProcessError("SCREEN_RPC_TIMEOUT", started=True) + for item, _ in selector.select(min(remaining, 0.2)): + chunk = os.read(item.fileobj.fileno(), 4096) + if not chunk: + selector.unregister(item.fileobj) + continue + name = item.data + limit = MAX_STDOUT_BYTES if name == "stdout" else MAX_STDERR_BYTES + if len(buffers[name]) + len(chunk) > limit: + raise ScreenProcessError("SCREEN_RPC_OUTPUT_LIMIT", started=True) + buffers[name].extend(chunk) + remaining = deadline - time.monotonic() + if remaining <= 0: + raise ScreenProcessError("SCREEN_RPC_TIMEOUT", started=True) + try: + code = process.wait(timeout=remaining) + except subprocess.TimeoutExpired: + raise ScreenProcessError("SCREEN_RPC_TIMEOUT", started=True) from None + completed = True + return code, bytes(buffers["stdout"]), bytes(buffers["stderr"]) + except OSError: + raise ScreenProcessError("SCREEN_RPC_PROCESS_FAILED", started=process is not None) from None + finally: + selector.close() + if process is not None: + if not completed or process.poll() is None: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + try: + process.wait(timeout=1) + except subprocess.TimeoutExpired: + pass + if process.stdout is not None: + process.stdout.close() + if process.stderr is not None: + process.stderr.close() + + +def _response(payload, envelope, code): + return {"version": 1, "ok": envelope["ok"], "mode": "EXEC", + "request_type": REQUEST_TYPE, "job_id": payload["job_id"], + "command_sha256": payload["sha256"], "exit_code": str(code), + "stdout": encode(envelope).decode("ascii") + "\n", + "stdout_truncated": False, "stdout_sanitized": False} + + +def _failure(payload, request, code, *, started=False): + uncertain = started and request["op"] in {"key", "text"} + error = {"code": code} + if uncertain: + error.update(observe_again=True) + capture = request.get("capture_id") + if type(capture) is str and re.fullmatch(r"[0-9a-f]{32}", capture): + error["capture_id"] = capture + return _response(payload, {"ok": False, "error": error}, 1) + + +def handle_request(payload, *, runner=None): + request, argument = validate_payload(payload) + runner = _run_rpc if runner is None else runner + try: + code, stdout, stderr = runner(argument, payload["timeout_s"]) + except ScreenProcessError as exc: + return _failure(payload, request, str(exc), started=exc.started) + if (type(stdout) is not bytes or type(stderr) is not bytes + or len(stdout) > MAX_STDOUT_BYTES or len(stderr) > MAX_STDERR_BYTES): + return _failure(payload, request, "SCREEN_RPC_OUTPUT_LIMIT", started=True) + try: + envelope = json.loads(stdout) + valid = (type(code) is int and type(envelope) is dict and type(envelope.get("ok")) is bool + and ((code == 0 and envelope["ok"] is True and type(envelope.get("result")) is dict) + or (code == 1 and envelope["ok"] is False and type(envelope.get("error")) is dict)) + and not stderr and stdout == encode(envelope) + b"\n") + if not valid: + raise ValueError + except (ValueError, UnicodeError, TypeError): + return _failure(payload, request, "SCREEN_RPC_RESPONSE_INVALID", started=True) + return _response(payload, envelope, code) + + +def install_adapter_route(adapter): + original = adapter.handle + + def handle(payload): + if isinstance(payload, dict) and str(payload.get("request_type", "")).strip().lower() == REQUEST_TYPE: + return handle_request(payload) + return original(payload) + + adapter.handle = handle diff --git a/source/expanded/framebridge_expanded/runtime.py b/source/expanded/framebridge_expanded/runtime.py new file mode 100644 index 0000000..a2d1807 --- /dev/null +++ b/source/expanded/framebridge_expanded/runtime.py @@ -0,0 +1,478 @@ +"""Manual process supervision for FrameBridge Expanded; no system services.""" +from __future__ import annotations +import argparse +import fcntl +import hashlib +import importlib.machinery +import importlib.util +import json +import os +import platform +import re +import secrets +import shutil +import signal +import socket +import stat +import subprocess +import sys +import time +from pathlib import Path +from types import SimpleNamespace +from .config import load_config, state_path, private_dir + +PREFIX = Path(os.environ.get("FRAMEBRIDGE_EXPANDED_PREFIX", Path(__file__).resolve().parents[2])) +_STOP = False +_LEGACY = None + +def _legacy(): + global _LEGACY + if _LEGACY is None: + path = PREFIX / "bin/framebridge-original" + loader = importlib.machinery.SourceFileLoader("framebridge_original_launcher", str(path)) + spec = importlib.util.spec_from_loader(loader.name, loader) + _LEGACY = importlib.util.module_from_spec(spec) + loader.exec_module(_LEGACY) + return _LEGACY + +def _args(state, config): + return SimpleNamespace(state_dir=str(state), port=config["ports"]["mcp"], + hub_port=config["ports"]["hub"], broker_port=config["ports"]["broker"]) + +def environment(state, config=None, prepare=False): + state = state_path(state) + config = config or load_config(state) + original = _legacy() + _, _, env = (original.prepare if prepare else original.settings)(_args(state, config)) + for key in list(env): + upper = key.upper() + if upper.startswith(("CONTROL_PLANE_", "OPENAI_", "MCP_TUNNEL_", "TUNNEL_")) or upper in ("HARPOON_API_KEY", "RUNTIMES_API_KEY"): + env.pop(key, None) + broker_source = PREFIX / "original/geranas/FrameBridge-Broker-RC1/src" + fleet_source = PREFIX / "original/geranas/FrameBridge-Fleet-Hub/app" + env.update({ + "FRAMEBRIDGE_EXPANDED_STATE": str(state), "FRAMEBRIDGE_STATE_DIR": str(state), + "FRAMEBRIDGE_EXPANDED_PREFIX": str(PREFIX), + "FRAMEBRIDGE_NATIVE_HOST": str(PREFIX / "bin/framebridge-native-expanded"), + "FRAMEBRIDGE_BROKER_SOURCE": str(broker_source), + "FRAMEBRIDGE_FLEET_SOURCE": str(fleet_source), + "FRAMEBRIDGE_FRONT_DOOR_PORT": str(config["ports"]["mcp"]), + "FRAMEBRIDGE_FLEET_HUB_PORT": str(config["ports"]["hub"]), + "FRAMEBRIDGE_HUB_MCP_URL": "http://127.0.0.1:" + str(config["ports"]["hub"]) + "/mcp", + "FRAMEBRIDGE_BROKER_CONTROL": config.get("broker_url") or "http://127.0.0.1:" + str(config["ports"]["broker"]), + "PYTHONPATH": os.pathsep.join(map(str, [PREFIX / "expanded", PREFIX / "portable", + PREFIX / "original/geranas/FrameBridge-Fleet-Hub/vendor", broker_source, fleet_source])), + "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8", "PYTHONDONTWRITEBYTECODE": "1", + "FBTERM_TITLE": "FrameBridge Expanded", "FRAMEBRIDGE_FBTERM_TITLE": "FrameBridge Expanded", + }) + from .display_context import configure + configure(env, config.get("headless", "auto"), state) + return env + +def _atomic(path, value): + temporary = path.with_name("." + path.name + "-" + secrets.token_hex(4)) + fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + try: + with os.fdopen(fd, "w") as stream: + json.dump(value, stream, indent=2) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + finally: + temporary.unlink(missing_ok=True) + +def _birth(pid): + try: + fields = Path("/proc", str(pid), "stat").read_text().rsplit(") ", 1)[1].split() + return fields[19] if fields[0] not in ("Z", "X") else None + except (OSError, IndexError): + return None + +def _alive(record): + return bool(record.get("birth") and _birth(record.get("pid", 0)) == record["birth"]) + +def _record(proc, role): + return {"pid": proc.pid, "birth": _birth(proc.pid), "role": role, "uid": os.getuid()} + +def _read(path): + try: + return json.loads(path.read_text()) + except (OSError, ValueError): + return {} + +def _port(port): + with socket.socket() as probe: + probe.settimeout(0.2) + return probe.connect_ex(("127.0.0.1", port)) == 0 + +def status(state): + state = state_path(state) + config = load_config(state) + manager = _read(state / "manager.json") + worker = _read(state / "worker.json") + running = _alive(manager) + children = [dict(r, alive=_alive(r)) for r in worker.get("processes", [])] + ready = bool(running and children and all(r["alive"] for r in children) + and _read(state / "ready.json").get("ready")) + context = _read(state / "ready.json").get("display_context", {"kind": "not_started"}) + if running and config.get("headless", "auto") == "auto": + from .display_context import describe, bind_text + try: + current_env = environment(state, config) + if not _display_ok(current_env): + bind_text(current_env, state, reason="PHYSICAL_X11_UNAVAILABLE_TEXT_TOOLS_ACTIVE") + context = describe(current_env) + except (OSError, ValueError, RuntimeError): + context = {"kind": "text", "physical": False, "reason": "DISPLAY_DETECTION_UNAVAILABLE"} + return {"ok": True, "role": config["role"], "name": config["name"], + "node_id": config["node_id"], "running": running, "ready": ready, + "state_dir": str(state), "mcp_url": None if config["role"] == "node" else + "http://127.0.0.1:" + str(config["ports"]["mcp"]) + "/mcp", + "processes": children, "manual_chatgpt_connection": True, + "display_context": context, + "capabilities": {"fbexec": ready, "fbterm": ready, "cua_tty_module": ready, + "graphical_cua": ready and context.get("kind") in ("physical-x11", "virtual-x11")}, + "error": _read(state / "last-error.json").get("error", "")} + +def _display_ok(env): + if not env.get("DISPLAY"): + return False + try: + result = subprocess.run(["xwininfo", "-root"], env=env, capture_output=True, timeout=3) + return result.returncode == 0 + except (OSError, subprocess.TimeoutExpired): + return False + +def doctor(state): + state = state_path(state) + config = load_config(state) + env = environment(state, config) + commands = ["bash", "tmux", "tee", "flock", "awk", "sha256sum", "timeout", + "dbus-run-session", "ip", "lsblk", "systemctl", "systemd-run", "sudo", "uptime"] + missing = [x for x in commands if not shutil.which(x, path=env.get("PATH"))] + from .display_context import missing_graphical_commands, VIRTUAL_COMMANDS + graphical_missing = missing_graphical_commands(env) + virtual_missing = [x for x in VIRTUAL_COMMANDS if not shutil.which(x, path=env.get("PATH"))] + console_missing = [x for x in ("gnome-terminal", "xterm") if not shutil.which(x, path=env.get("PATH"))] + if config.get("headless") in ("no", False): + missing.extend(graphical_missing) + elif config.get("headless") in ("yes", True): + missing.extend(graphical_missing + virtual_missing) + os_release = platform.freedesktop_os_release() + compatible = (sys.version_info[:2] == (3, 13) and platform.machine() == "x86_64" + and os_release.get("ID") == "debian" and os_release.get("VERSION_ID") == "13") + issues = [] + if config["role"] == "central" and config.get("broker_listen_host", "127.0.0.1") not in ("127.0.0.1", "::1", "localhost"): + if not config.get("tls_cert") or not config.get("tls_key"): + issues.append("A central listener outside loopback requires a TLS certificate and private-key reference.") + if config.get("headless") in ("no", False) and not _display_ok(env): + issues.append("No usable X11 display. Select headless auto or provide DISPLAY and XAUTHORITY.") + from .display_context import describe + from .network import inspect as inspect_network + display_context = describe(env) + if config.get("headless") in ("no", False) and display_context.get("kind") != "physical-x11": + issues.append("No verified active physical X11 session. Log in graphically or explicitly choose a virtual headless display.") + return {"ok": compatible and not missing and not issues, "compatible_debian_13_amd64": compatible, + "python": platform.python_version(), "missing_commands": missing, + "display": {"physical-x11": "existing physical X11", "virtual-x11": "explicit private virtual display", "text": "text tools"}.get(display_context.get("kind"), "unavailable"), + "display_context": display_context, + "capabilities": {"fbexec": not missing, "fbterm": not missing, "cua_tty_module": True, + "graphical_cua": display_context.get("kind") in ("physical-x11", "virtual-x11") and not graphical_missing, + "mode": "graphical" if display_context.get("kind") in ("physical-x11", "virtual-x11") else "text"}, + "optional_missing_commands": {"graphical_cua": graphical_missing, "private_desktop": virtual_missing, "graphical_terminal": console_missing}, + "network": inspect_network(), + "role": config["role"], "issues": issues} + +def adapter_request(payload): + state = state_path() + env = environment(state) + is_cua = payload.get("request_type") == "cua" or payload.get("mode") == "CUA" + if is_cua: + from .display_context import check, describe, prepare, DisplayContextError + try: + prepare(env) + check(env) + except DisplayContextError as exc: + return {"ok": False, "error": str(exc), "performed": False, "input_may_have_been_sent": False, "do_not_replay": False} + path = Path(env["FRAMEBRIDGE_TOOL_ROOT"]) / "adapter.sock" + st = path.stat() + if not stat.S_ISSOCK(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o600: + raise RuntimeError("The local adapter socket must be owned by this user with mode 0600.") + request = dict(payload) + timeout = request.get("timeout_s", 180 if request.get("mode") == "CUA" else 120) + if type(timeout) is not int or not 1 <= timeout <= 600: + return {"ok": False, "error": "INVALID_TIMEOUT"} + raw = (json.dumps(request, ensure_ascii=True) + "\n").encode() + send_attempted = False + try: + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: + client.settimeout(timeout + 15) + client.connect(str(path)) + send_attempted = True + client.sendall(raw) + client.shutdown(socket.SHUT_WR) + data = bytearray() + while True: + chunk = client.recv(65536) + if not chunk: + break + data.extend(chunk) + if len(data) > 20 * 1024 * 1024: + raise RuntimeError("The adapter response exceeded its size limit.") + result = json.loads(data) + except (OSError, TimeoutError, ValueError, RuntimeError): + if not is_cua: + raise + from .adapter import _blocked + is_input = str(payload.get("cua", {}).get("action", "")).upper() != "OBSERVE" + return _blocked("CUA_ADAPTER_RESPONSE_UNCERTAIN", send_attempted and is_input) + if not isinstance(result, dict): + if is_cua: + from .adapter import _blocked + is_input = str(payload.get("cua", {}).get("action", "")).upper() != "OBSERVE" + return _blocked("CUA_ADAPTER_RESPONSE_INVALID", is_input) + return {"ok": False, "error": "ADAPTER_RESPONSE_INVALID"} + if request.get("include_stdout") and result.get("stdout_path"): + stdout_path = Path(result["stdout_path"]).resolve() + allowed = Path(env["FBTERM_ROOT"]).resolve() / "jobs" + if stdout_path.is_relative_to(allowed) and stdout_path.is_file(): + data = stdout_path.read_bytes() + text = data[:65536].decode("utf-8", "replace") + text = re.sub(r"(?i)sk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{16,}", "[REDACTED]", text) + result["stdout"] = text + result["stdout_truncated"] = len(data) > 65536 + if is_cua: + try: + check(env) + except DisplayContextError as exc: + is_input = str(payload.get("cua", {}).get("action", "")).upper() != "OBSERVE" + may_have_sent = bool(result.get("input_may_have_been_sent", is_input)) + failure = {"ok": False, "error": str(exc), "result_discarded": True, "input_may_have_been_sent": may_have_sent, "do_not_replay": may_have_sent} + if not may_have_sent: + failure["performed"] = False + return failure + result = dict(result, display_context=describe(env)) + return result + +def _spawn(command, env, state, role): + log = (state / "logs" / (role + ".log")).open("ab") + try: + proc = subprocess.Popen(command, env=env, cwd=state, stdin=subprocess.DEVNULL, + stdout=log, stderr=subprocess.STDOUT, start_new_session=True, umask=0o077) + finally: + log.close() + return proc, _record(proc, role) + +def _terminate(proc, record): + if not _alive(record): + return + if record.get("uid") != os.getuid(): + raise RuntimeError("Refusing to stop a process owned by a different user.") + try: + os.killpg(record["pid"], signal.SIGTERM) + except ProcessLookupError: + return + deadline = time.monotonic() + 8 + while _alive(record) and time.monotonic() < deadline: + time.sleep(0.1) + if _alive(record): + os.killpg(record["pid"], signal.SIGKILL) + if proc: + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + pass + +def _signal(*_): + global _STOP + _STOP = True + +def _worker(state): + state = state_path(state) + config = load_config(state) + env = environment(state, config, prepare=True) + os.environ.update(env) + signal.signal(signal.SIGTERM, _signal) + signal.signal(signal.SIGINT, _signal) + commands = [ + ("adapter", [sys.executable, "-m", "framebridge_expanded.adapter", "serve"]), + ("tty", [sys.executable, "-m", "framebridge_expanded.tty", "serve", "--state", str(state)])] + if config["role"] == "central": + code = "from framebridge_expanded.broker import serve; serve(host=" + repr(config.get("broker_listen_host", "127.0.0.1")) + ",port=" + str(config["ports"]["broker"]) + ",state_dir=" + repr(str(state)) + ")" + commands.append(("broker", [sys.executable, "-c", code])) + if config["role"] == "node": + code = "from framebridge_expanded.node import run; run(state_dir=" + repr(str(state)) + ")" + commands.append(("node", [sys.executable, "-c", code])) + else: + commands.extend([ + ("fleet", [sys.executable, "-m", "framebridge_expanded.fleet"]), + ("frontdoor", [sys.executable, str(PREFIX / "portable/front_door_mcp.py")])]) + processes = [] + try: + for role, command in commands: + proc, rec = _spawn(command, env, state, role) + processes.append((proc, rec)) + _atomic(state / "worker.json", {"processes": [r for _, r in processes]}) + deadline = time.monotonic() + 35 + ready = False + while not _STOP and time.monotonic() < deadline: + if any(p.poll() is not None for p, _ in processes): + raise RuntimeError("A component exited during startup. Check this profile's component logs.") + adapter = Path(env["FRAMEBRIDGE_TOOL_ROOT"], "adapter.sock").is_socket() + ports = True if config["role"] == "node" else all(_port(config["ports"][k]) for k in ("mcp", "hub")) + if config["role"] == "central": + ports = ports and _port(config["ports"]["broker"]) + try: + from . import tty + tty_ready = bool(tty.status(state).get("ok")) + except Exception: + tty_ready = False + if adapter and ports and tty_ready: + ready = True + break + time.sleep(0.2) + if not ready: + raise RuntimeError("Components did not become ready before the startup deadline.") + from .display_context import describe + _atomic(state / "ready.json", {"ready": True, "display": env.get("DISPLAY", ""), "display_context": describe(env)}) + while not _STOP: + if any(p.poll() is not None for p, _ in processes): + raise RuntimeError("A component stopped unexpectedly. Check this profile's component logs.") + time.sleep(0.25) + finally: + _atomic(state / "ready.json", {"ready": False}) + try: + from . import tty + tty.stop(state) + except Exception: + pass + for proc, rec in reversed(processes): + _terminate(proc, rec) + subprocess.run(["tmux", "kill-server"], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + +def _manager(state): + state = state_path(state) + config = load_config(state) + env = environment(state, config, prepare=True) + signal.signal(signal.SIGTERM, _signal) + signal.signal(signal.SIGINT, _signal) + lock = (state / "manager.lock").open("a") + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + processes = [] + try: + from .display_context import describe, bind_virtual + from .display_context import private_display_required + display_available = _display_ok(env) + private_display = private_display_required(env, config.get("headless", "auto"), display_available) + if config.get("headless", "auto") == "auto" and not display_available: + from .display_context import bind_text + bind_text(env, state, reason="PHYSICAL_X11_UNAVAILABLE_TEXT_TOOLS_ACTIVE") + if private_display: + display = next((n for n in range(140, 240) if not Path("/tmp/.X11-unix/X" + str(n)).exists() and not Path("/tmp/.X" + str(n) + "-lock").exists()), None) + if display is None: + raise RuntimeError("No private X11 display number is available.") + env["DISPLAY"] = ":" + str(display) + env["XDG_SESSION_TYPE"] = "x11" + env["XDG_CURRENT_DESKTOP"] = "Openbox" + env["GDK_BACKEND"] = "x11" + authority = Path(env["XDG_RUNTIME_DIR"]) / "Xauthority" + env["XAUTHORITY"] = str(authority) + result = subprocess.run(["xauth", "-f", str(authority), "add", env["DISPLAY"], "MIT-MAGIC-COOKIE-1", secrets.token_hex(16)], env=env, capture_output=True, timeout=5) + if result.returncode: + raise RuntimeError("Could not create private X11 display authorization.") + proc, rec = _spawn(["Xvfb", env["DISPLAY"], "-screen", "0", "1280x800x24", "-nolisten", "tcp", "-noreset", "-auth", str(authority)], env, state, "display") + processes.append((proc, rec)) + deadline = time.monotonic() + 10 + while not _display_ok(env) and time.monotonic() < deadline: + if proc.poll() is not None: + raise RuntimeError("The private display failed to start.") + time.sleep(0.15) + if not _display_ok(env): + raise RuntimeError("The private display did not become ready.") + proc, rec = _spawn(["openbox", "--sm-disable"], env, state, "window-manager") + processes.append((proc, rec)) + if private_display: + bind_virtual(env, state, explicit=config.get("headless") in ("yes", True)) + env.pop("DBUS_SESSION_BUS_ADDRESS", None) + proc, rec = _spawn(["dbus-run-session", "--", sys.executable, "-m", "framebridge_expanded.runtime", "_worker", "--state", str(state)], env, state, "private-session") + processes.append((proc, rec)) + _atomic(state / "manager-children.json", {"processes": [r for _, r in processes]}) + while not _STOP: + if any(p.poll() is not None for p, _ in processes): + raise RuntimeError("The private runtime stopped. Check this profile's logs.") + time.sleep(0.25) + except Exception as exc: + _atomic(state / "last-error.json", {"error": str(exc)}) + raise + finally: + try: + for proc, rec in reversed(processes): + _terminate(proc, rec) + _atomic(state / "ready.json", {"ready": False}) + finally: + lock.close() + +def start(state): + state = state_path(state) + config = load_config(state) + current = status(state) + if current["running"]: + return current + checks = doctor(state) + if not checks["ok"]: + return {"ok": False, "error": "Preflight failed. Run framebridge doctor.", "doctor": checks} + for name in ("logs", "run"): + private_dir(state / name) + for name in ("ready.json", "last-error.json", "worker.json"): + (state / name).unlink(missing_ok=True) + if config["role"] != "node": + for key in ("mcp", "hub") + (("broker",) if config["role"] == "central" else ()): + with socket.socket() as probe: + # Match server restart semantics while still rejecting live listeners. + probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + probe.bind(("127.0.0.1", config["ports"][key])) + env = environment(state, config, prepare=True) + proc, rec = _spawn([sys.executable, "-m", "framebridge_expanded.runtime", "_manager", "--state", str(state)], env, state, "manager") + _atomic(state / "manager.json", rec) + deadline = time.monotonic() + 50 + while time.monotonic() < deadline and _alive(rec): + result = status(state) + if result["ready"]: + return result + if (state / "last-error.json").exists(): + break + time.sleep(0.2) + _terminate(proc, rec) + result = status(state) + result.update(ok=False, error=result["error"] or "Startup failed. Check this profile's logs.") + return result + +def stop(state): + state = state_path(state) + load_config(state) + from .connection_background import disconnect + connection = disconnect(state) + if not connection.get("ok"): + raise RuntimeError("The background ChatGPT connection could not be stopped safely; local services were left unchanged.") + from .autostart import mark_manual_stop + mark_manual_stop(state) + rec = _read(state / "manager.json") + _terminate(None, rec) + children = _read(state / "manager-children.json").get("processes", []) + for child in reversed(children): + _terminate(None, child) + for child in reversed(_read(state / "worker.json").get("processes", [])): + _terminate(None, child) + _atomic(state / "ready.json", {"ready": False}) + return status(state) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("command", choices=["_manager", "_worker"]) + parser.add_argument("--state", required=True) + args = parser.parse_args() + (_manager if args.command == "_manager" else _worker)(args.state) + +if __name__ == "__main__": + main() diff --git a/source/expanded/framebridge_expanded/screen_access.py b/source/expanded/framebridge_expanded/screen_access.py new file mode 100644 index 0000000..35f3f5b --- /dev/null +++ b/source/expanded/framebridge_expanded/screen_access.py @@ -0,0 +1,446 @@ +"""Explicit, persistent, read-only fb0 access for one local account. + +No permission changes occur on import, status, or package installation. +The administrator explicitly opts in with sudo fb screen-access enable --user NAME. +The selected UID can read framebuffer pixels even after logout or a VT switch. +Disable restores only this operation's exact unchanged ACL and retains backups. +""" +from __future__ import annotations + +import argparse +import base64 +from contextlib import contextmanager +import errno +import fcntl +import json +import os +from pathlib import Path +import pwd +import stat +import struct +import subprocess +import sys +import time +import uuid + +DEVICE = Path("/dev/fb0") +POLICY = Path("/etc/framebridge-expanded/screen-access.json") +STATE = Path("/var/lib/framebridge-expanded/screen-access") +RULE = Path("/usr/lib/udev/rules.d/70-framebridge-screen.rules") +XATTR = "system.posix_acl_access" +ENV = {"PATH": "/usr/sbin:/usr/bin:/sbin:/bin", "LC_ALL": "C"} +RISK = ("The selected account can read real /dev/fb0 pixels through its processes " + "after logout or a VT switch. No framebuffer write permission is granted.") +UNDEFINED = 0xffffffff + + +def require(value, code): + if not value: + raise RuntimeError(code) + + +def protected_stat(info, *, directory=False, private=False): + require(stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode), + "PROTECTED_PATH_TYPE_INVALID") + require(info.st_uid == 0 and not stat.S_IMODE(info.st_mode) & 0o022, + "PROTECTED_PATH_OWNERSHIP_INVALID") + if private: + require(not stat.S_IMODE(info.st_mode) & 0o077, "PRIVATE_PATH_MODE_INVALID") + + +def sync_directory(path): + fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + protected_stat(os.fstat(fd), directory=True) + os.fsync(fd) + finally: + os.close(fd) + + +def secure_directory(path, *, create=False, private=False): + path = Path(path) + require(path.is_absolute() and ".." not in path.parts, "PROTECTED_PATH_INVALID") + current = Path("/") + for part in path.parts[1:]: + current /= part + try: + info = current.lstat() + except FileNotFoundError: + require(create, "PROTECTED_DIRECTORY_MISSING") + os.mkdir(current, 0o700 if current == path and private else 0o755) + sync_directory(current.parent) + info = current.lstat() + protected_stat(info, directory=True, private=private and current == path) + return path + + +def read_json(path, *, private=False): + secure_directory(path.parent) + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + protected_stat(os.fstat(fd), private=private) + data = os.read(fd, 65537) + require(len(data) <= 65536, "PROTECTED_RECORD_TOO_LARGE") + value = json.loads(data) + require(isinstance(value, dict), "PROTECTED_RECORD_INVALID") + return value + finally: + os.close(fd) + + +def write_json(path, value, *, private=True, exclusive=False): + secure_directory(path.parent, create=True, private=path.parent == STATE) + if path.exists(): + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + protected_stat(os.fstat(fd), private=private) + finally: + os.close(fd) + require(not exclusive, "PROTECTED_RECORD_EXISTS") + temporary = path.parent / ("." + path.name + "." + uuid.uuid4().hex) + fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, + 0o600 if private else 0o644) + try: + payload = (json.dumps(value, sort_keys=True) + "\n").encode() + os.fchmod(fd, 0o600 if private else 0o644) + with os.fdopen(fd, "wb") as stream: + stream.write(payload) + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + sync_directory(path.parent) + finally: + try: + temporary.unlink() + except FileNotFoundError: + pass + + +@contextmanager +def locked(): + require(os.geteuid() == 0, "ROOT_REQUIRED_USE_SUDO") + secure_directory(STATE, create=True, private=True) + fd = os.open(STATE / "lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600) + try: + protected_stat(os.fstat(fd), private=True) + fcntl.flock(fd, fcntl.LOCK_EX) + yield + finally: + os.close(fd) + + +def account(name, expected_uid=None): + require(isinstance(name, str) and 0 < len(name) <= 256 + and not any(c.isspace() or ord(c) < 32 for c in name), "ACCOUNT_INVALID") + try: + user = pwd.getpwnam(name) + except KeyError: + raise RuntimeError("ACCOUNT_NOT_FOUND") from None + require(user.pw_uid > 0, "ROOT_ACCOUNT_NOT_ALLOWED") + require(expected_uid is None or user.pw_uid == expected_uid, "ACCOUNT_IDENTITY_CHANGED") + return {"user": user.pw_name, "uid": user.pw_uid} + + +def validate_policy(value): + require(value.get("schema") == 1 and type(value.get("enabled")) is bool + and value.get("device") == str(DEVICE) and type(value.get("uid")) is int + and value["uid"] > 0 and isinstance(value.get("user"), str) + and 0 < len(value["user"]) <= 256 + and not any(c.isspace() or ord(c) < 32 for c in value["user"]), + "SCREEN_ACCESS_POLICY_INVALID") + # Status/revocation must still work after an account is removed or its name + # is reused. The live name/UID identity is checked before every grant. + return value + + +def load_policy(): + try: + return validate_policy(read_json(POLICY)) + except FileNotFoundError: + return None + except RuntimeError as exc: + if str(exc) == "PROTECTED_DIRECTORY_MISSING" and not POLICY.parent.exists(): + return None + raise + + +def acl_entries(raw, mode): + if raw is None: + return [(1, (mode >> 6) & 7, UNDEFINED), + (4, (mode >> 3) & 7, UNDEFINED), (32, mode & 7, UNDEFINED)] + require(len(raw) >= 28 and (len(raw) - 4) % 8 == 0 + and struct.unpack(" 0, "ACCESS_BACKUP_INVALID") + meta = value["meta"] + require(set(meta) == {"dev", "inode", "rdev", "uid", "gid", "mode", "boot"} + and all(type(meta[k]) is int and meta[k] >= 0 + for k in ("dev", "inode", "rdev", "uid", "gid", "mode")) + and meta["rdev"] == os.makedev(29, 0) and meta["uid"] == 0 + and meta["mode"] <= 0o777 and isinstance(meta["boot"], str) + and 0 < len(meta["boot"]) <= 64, "ACCESS_BACKUP_IDENTITY_INVALID") + require("before_acl" in value and "expected_acl" in value, "ACCESS_BACKUP_INVALID") + decode(value["before_acl"]) + decode(value["expected_acl"]) + return value + + +def same_device(left, right): + return all(left.get(key) == right.get(key) for key in ("dev", "inode", "rdev", "boot")) + + +def restore_owned(fd, record): + meta, raw = device_snapshot(fd) + if not same_device(meta, record["meta"]): + return "DEVICE_RECREATED_NO_OLD_GRANT" + require(meta == record["meta"], "FRAMEBUFFER_METADATA_CHANGED_REVIEW_REQUIRED") + before, expected = decode(record["before_acl"]), decode(record["expected_acl"]) + if raw == before: + return "ORIGINAL_ACL_ALREADY_RESTORED" + require(raw == expected, "FRAMEBUFFER_ACL_CHANGED_REVIEW_REQUIRED") + if raw != before: + if before is None: + os.removexattr(fd, XATTR) + else: + os.setxattr(fd, XATTR, before, os.XATTR_REPLACE) + require(device_snapshot(fd) == (meta, before), "FRAMEBUFFER_RESTORE_VERIFY_FAILED") + return "ORIGINAL_ACL_RESTORED" + + +def apply_locked(policy): + account(policy["user"], policy["uid"]) + try: + fd = os.open(DEVICE, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + except FileNotFoundError: + return {"status": "ENABLED_WAITING_FOR_FB0", "changed": False} + try: + meta, before = device_snapshot(fd) + previous = load_record() + if previous and same_device(previous["meta"], meta): + require(previous["meta"] == meta, "FRAMEBUFFER_METADATA_CHANGED_REVIEW_REQUIRED") + require(previous["uid"] == policy["uid"], "ACCESS_BACKUP_IDENTITY_CHANGED") + require(before == decode(previous["expected_acl"]), "FRAMEBUFFER_ACL_CHANGED_REVIEW_REQUIRED") + probe_user(policy) + return {"status": "ENABLED_READ_ONLY", "changed": False} + expected = read_only_acl(before, meta["mode"], policy["uid"]) + record = {"schema": 1, "uid": policy["uid"], "user": policy["user"], + "meta": meta, "before_acl": encode(before), "expected_acl": encode(expected)} + backup = STATE / ("backup-" + str(time.time_ns()) + "-" + uuid.uuid4().hex + ".json") + write_json(backup, record, exclusive=True) + write_json(STATE / "active.json", record) + require(device_snapshot(fd) == (meta, before), "FRAMEBUFFER_CHANGED_BEFORE_APPLY") + if before != expected: + os.setxattr(fd, XATTR, expected, + os.XATTR_CREATE if before is None else os.XATTR_REPLACE) + try: + require(device_snapshot(fd) == (meta, expected), "FRAMEBUFFER_APPLY_VERIFY_FAILED") + probe_user(policy) + except Exception: + restore_owned(fd, record) + raise + return {"status": "ENABLED_READ_ONLY", "changed": before != expected} + finally: + os.close(fd) + + +def reload_rules(): + result = subprocess.run(["/usr/bin/udevadm", "control", "--reload-rules"], + env=ENV, capture_output=True, timeout=8) + require(result.returncode == 0, "UDEV_RULE_RELOAD_FAILED") + + +def status(): + policy = load_policy() + info = {"enabled": bool(policy and policy["enabled"]), "device": str(DEVICE), + "persistent": bool(policy and policy["enabled"]), "risk": RISK, + "status": "DISABLED" if not policy else + ("ENABLED" if policy["enabled"] else "DISABLED_REVOKE_PENDING")} + if policy: + info.update({"user": policy["user"], "uid": policy["uid"]}) + try: + info["current_account_can_read"] = os.access(DEVICE, os.R_OK) + info["current_account_can_write"] = os.access(DEVICE, os.W_OK) + info["device_present"] = DEVICE.exists() + info["udev_rule_present"] = RULE.is_file() and not RULE.is_symlink() + except OSError: + info["device_present"] = False + return info + + +def enable(user): + chosen = account(user) + with locked(): + secure_directory(RULE.parent) + fd = os.open(RULE, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + try: + protected_stat(os.fstat(fd)) + finally: + os.close(fd) + old = load_policy() + if old: + require(old["enabled"] and all(old[k] == chosen[k] for k in ("user", "uid")), + "DISABLE_EXISTING_ACCESS_POLICY_FIRST") + policy = old + else: + policy = {"schema": 1, "enabled": True, "device": str(DEVICE), **chosen} + write_json(POLICY, policy, private=False, exclusive=True) + try: + reload_rules() + answer = apply_locked(policy) + except Exception: + # Stop later udev grants. Keep the tombstone and protected backup + # for an explicit disable/recovery instead of concealing a failure. + write_json(POLICY, dict(policy, enabled=False), private=False) + raise + return dict(answer, persistent=True, user=chosen["user"], uid=chosen["uid"], risk=RISK) + + +def disable(): + with locked(): + policy = load_policy() + if policy is None: + return {"status": "DISABLED", "changed": False, "persistent": False} + write_json(POLICY, dict(policy, enabled=False), private=False) + reload_rules() + record = load_record() + result = "NO_CURRENT_OWNED_GRANT" + if record: + require(record["uid"] == policy["uid"], "ACCESS_BACKUP_IDENTITY_CHANGED") + try: + fd = os.open(DEVICE, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) + except FileNotFoundError: + result = "DEVICE_ABSENT_NO_OLD_GRANT" + else: + try: + result = restore_owned(fd, record) + finally: + os.close(fd) + # Successful revocation must not leave an active record belonging + # to an earlier enable, even when the device has been recreated. + (STATE / "active.json").unlink() + sync_directory(STATE) + POLICY.unlink() + sync_directory(POLICY.parent) + return {"status": "DISABLED", "changed": True, "persistent": False, "restore": result} + + +def apply_device(): + with locked(): + policy = load_policy() + if policy is None or not policy["enabled"]: + return {"status": "DISABLED", "changed": False} + return apply_locked(policy) + + +def command(args): + if args.screen_access_command == "status": + return status() + if args.screen_access_command == "enable": + return enable(args.user) + if args.screen_access_command == "disable": + return disable() + raise ValueError("SCREEN_ACCESS_COMMAND_INVALID") + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="action", required=True) + item = sub.add_parser("enable") + item.add_argument("--user", required=True) + sub.add_parser("disable") + sub.add_parser("status") + sub.add_parser("apply-device") + args = parser.parse_args(argv) + try: + result = (enable(args.user) if args.action == "enable" else + disable() if args.action == "disable" else + status() if args.action == "status" else apply_device()) + print(json.dumps(result, sort_keys=True)) + return 0 + except (OSError, ValueError, RuntimeError, KeyError) as exc: + # No passwords, environment, process argv, or screen pixels are read. + print("Error: " + (str(exc) if isinstance(exc, RuntimeError) + else "SCREEN_ACCESS_OPERATION_FAILED"), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/tls.py b/source/expanded/framebridge_expanded/tls.py new file mode 100644 index 0000000..b1c9440 --- /dev/null +++ b/source/expanded/framebridge_expanded/tls.py @@ -0,0 +1,162 @@ +"""Profile-scoped LAN TLS bootstrap; never changes OS trust or OpenAI credentials.""" +from __future__ import annotations + +import datetime +import ipaddress +import json +import os +from pathlib import Path +import re +import shutil +import sys +from urllib.parse import urlsplit + +from .config import load_config, save_config, state_path, private_dir + + +def _crypto(): + # The original network_security module has network policy helpers, but no + # certificate generator. Reuse the captured SDK's cryptography dependency. + from .runtime import PREFIX + for vendor in (Path(PREFIX) / "original/geranas/FrameBridge-Fleet-Hub/vendor", + Path(PREFIX) / "sources/original-geranas/FrameBridge-Fleet-Hub/vendor"): + if vendor.is_dir(): + value = str(vendor) + if value not in sys.path: + sys.path.insert(0, value) + break + from cryptography import x509 + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID + return x509, hashes, serialization, ec, NameOID, ExtendedKeyUsageOID + + +def _dns_name(value): + if not isinstance(value, str): + raise ValueError("Enter a certificate DNS name.") + try: + value = value.strip().rstrip(".").encode("idna").decode("ascii").lower() + except UnicodeError: + raise ValueError("The certificate DNS name is invalid.") from None + if not value or len(value) > 253 or any(not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?", label) for label in value.split(".")): + raise ValueError("Use a DNS name containing letters, numbers, hyphens, and dots.") + return value + + +def _address(value): + try: + address = ipaddress.ip_address(value) + except (ValueError, TypeError): + raise ValueError("Enter an IPv4 or IPv6 address for the broker certificate.") from None + if address.is_unspecified or address.is_multicast or getattr(address, "scope_id", None): + raise ValueError("The advertised address must identify a computer, not a wildcard or multicast address.") + return address + + +def _url(host, port): + return "https://" + ("[" + host + "]" if ":" in host else host) + ":" + str(port) + + +def _write(path, data, mode): + fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode) + with os.fdopen(fd, "wb") as stream: + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + path.chmod(mode) + + +def create_certificates(state, host, address, *, listen_host=None, advertise_host=None): + """Create a new private CA and server certificate in a stopped central profile.""" + state = state_path(state) + config = load_config(state) + if config["role"] != "central": + raise ValueError("Create LAN certificates in a central profile. Run framebridge setup first.") + from .runtime import status + current = status(state) + if current.get("running") or any(p.get("alive") for p in current.get("processes", [])): + raise RuntimeError("Stop this profile before creating certificates.") + directory = state / "tls" + if directory.exists() or directory.is_symlink() or any(config.get(k) for k in ("tls_cert", "tls_key", "tls_ca")): + raise FileExistsError("This profile already has TLS material or references. Use existing certificates or an explicit new profile.") + host = _dns_name(host) + address = _address(address) + listen = listen_host if listen_host is not None else config.get("broker_listen_host", "127.0.0.1") + if listen != "localhost": + try: + ipaddress.ip_address(listen) + except (ValueError, TypeError): + raise ValueError("The listen host must be an IP address or localhost.") from None + advertised = str(address) if advertise_host is None else str(advertise_host) + if advertised not in {host, str(address), "localhost", "127.0.0.1", "::1"}: + raise ValueError("The advertised host must match the certificate DNS name or one of its IP addresses.") + x509, hashes, serialization, ec, NameOID, ExtendedKeyUsageOID = _crypto() + now = datetime.datetime.now(datetime.timezone.utc) + ca_key = ec.generate_private_key(ec.SECP256R1()) + server_key = ec.generate_private_key(ec.SECP256R1()) + ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "FrameBridge " + config["name"] + " local CA")]) + ca = (x509.CertificateBuilder().subject_name(ca_name).issuer_name(ca_name) + .public_key(ca_key.public_key()).serial_number(x509.random_serial_number()) + .not_valid_before(now - datetime.timedelta(minutes=5)) + .not_valid_after(now + datetime.timedelta(days=3650)) + .add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True) + .add_extension(x509.KeyUsage(digital_signature=False, content_commitment=False, key_encipherment=False, + data_encipherment=False, key_agreement=False, key_cert_sign=True, + crl_sign=True, encipher_only=False, decipher_only=False), critical=True) + .add_extension(x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), critical=False) + .sign(ca_key, hashes.SHA256())) + dns_names = list(dict.fromkeys([host, "localhost"])) + ips = list(dict.fromkeys([address, ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("::1")])) + san = x509.SubjectAlternativeName([x509.DNSName(v) for v in dns_names] + [x509.IPAddress(v) for v in ips]) + server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, host)]) + server = (x509.CertificateBuilder().subject_name(server_name).issuer_name(ca.subject) + .public_key(server_key.public_key()).serial_number(x509.random_serial_number()) + .not_valid_before(now - datetime.timedelta(minutes=5)) + .not_valid_after(now + datetime.timedelta(days=825)) + .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) + .add_extension(san, critical=False) + .add_extension(x509.KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=False, + data_encipherment=False, key_agreement=False, key_cert_sign=False, + crl_sign=False, encipher_only=False, decipher_only=False), critical=True) + .add_extension(x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), critical=False) + .sign(ca_key, hashes.SHA256())) + state = private_dir(state) + directory.mkdir(mode=0o700) # Exclusive: never overwrite an existing CA or key. + ca_file = directory / "ca-public.pem" + ca_key_file = directory / "ca-private.key" + server_file = directory / "server.pem" + server_key_file = directory / "server-private.key" + encoding = serialization.Encoding.PEM + fmt = serialization.PrivateFormat.PKCS8 + no_password = serialization.NoEncryption() + updated = dict(config) + updated.update(tls_ca=str(ca_file), tls_cert=str(server_file), tls_key=str(server_key_file), + broker_url=_url(advertised, config["ports"]["broker"]), + broker_control_url=_url("127.0.0.1", config["ports"]["broker"]), + broker_listen_host=listen) + public = { + "ca_public_file": str(ca_file), + "ca_fingerprint_sha256": ca.fingerprint(hashes.SHA256()).hex(), + "server_certificate_file": str(server_file), + "server_fingerprint_sha256": server.fingerprint(hashes.SHA256()).hex(), + "dns_subject_alternative_names": dns_names, + "ip_subject_alternative_names": [str(v) for v in ips], + "broker_url": updated["broker_url"], + "broker_control_url": updated["broker_control_url"], + "broker_listen_host": listen, + "private_key_values_exported": False, + "openai_credentials_created_or_saved": False, + } + try: + _write(ca_key_file, ca_key.private_bytes(encoding, fmt, no_password), 0o600) + _write(server_key_file, server_key.private_bytes(encoding, fmt, no_password), 0o600) + _write(server_file, server.public_bytes(encoding) + ca.public_bytes(encoding), 0o600) + _write(ca_file, ca.public_bytes(encoding), 0o644) + _write(directory / "certificates.json", (json.dumps(public, indent=2) + "\n").encode(), 0o600) + save_config(state, updated) + except Exception: + shutil.rmtree(directory) + raise + return public diff --git a/source/expanded/framebridge_expanded/tty.py b/source/expanded/framebridge_expanded/tty.py new file mode 100644 index 0000000..b55a5db --- /dev/null +++ b/source/expanded/framebridge_expanded/tty.py @@ -0,0 +1,558 @@ +"""Portable access to the captured real FrameBridge tmux CUA-TTY engine. + +The captured Manager/LocalServer are loaded from original/gerameg, never from +an installed ALPHA. _tty_backend.py preserves RealTerminalMixin with a recorded +portability diff. The public RPC uses same-UID sockets plus authorized=True. +""" +from __future__ import annotations + +import argparse +import base64 +from dataclasses import asdict, dataclass, fields +import fcntl +import hashlib +import importlib +import importlib.util +import json +import math +import os +from pathlib import Path +import re +import shutil +import signal +import socket +import socketserver +import stat +import struct +import subprocess +import sys +import threading +import time +import uuid +import zlib + +_SESSION = re.compile(r"[a-f0-9]{32}\Z") +_ERROR = re.compile(r"[A-Z][A-Z0-9_]{0,79}\Z") +_WIRE_CHARS = 1800 +_STARTED_CHILDREN = {} + + +def _source_root(): + here = Path(__file__).resolve().parents[2] + prefix = Path(os.environ.get("FRAMEBRIDGE_EXPANDED_PREFIX", str(here))) + for p in (prefix / "original/gerameg", prefix / "sources/original-gerameg", + here / "original/gerameg", here / "sources/original-gerameg"): + if (p / "framebridge_cua_tty/core.py").is_file(): + return p + raise RuntimeError("TTY_ORIGINAL_SOURCE_NOT_FOUND") + + +def _load_captured(): + name = "framebridge_expanded._captured_cua_tty" + if name not in sys.modules: + path = _source_root() / "framebridge_cua_tty" + spec = importlib.util.spec_from_file_location( + name, path / "__init__.py", submodule_search_locations=[str(path)]) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return (importlib.import_module(name + ".core"), + importlib.import_module(name + ".server")) + + +_core, _server = _load_captured() +TerminalDenied = _core.TerminalDenied +from ._tty_backend import RealTerminalMixin +from .tty_sudo_control import SudoControlError + + +@dataclass(frozen=True) +class Limits: + max_sessions: int = 16 + min_rows: int = 2 + max_rows: int = 500 + min_cols: int = 10 + max_cols: int = 1000 + max_text_bytes: int = 65536 + max_screen_bytes: int = 262144 + history_lines: int = 10000 + idle_timeout_s: int = 0 + command_timeout_s: int = 5 + rpc_max_bytes: int = 1048576 + + +def _private(path): + path = Path(path) + if path.is_symlink(): + raise TerminalDenied("TTY_PRIVATE_PATH_IS_SYMLINK") + path.mkdir(mode=0o700, parents=True, exist_ok=True) + s = path.stat() + if s.st_uid != os.geteuid() or stat.S_IMODE(s.st_mode) != 0o700: + raise TerminalDenied("TTY_PRIVATE_DIRECTORY_REQUIRED") + return path + + +def state_path(value=None): + return Path(value or os.environ.get("FRAMEBRIDGE_EXPANDED_STATE", + str(Path.home() / ".local/state/framebridge-expanded"))).expanduser().absolute() + + +def runtime_path(state): + state = state_path(state) + tag = hashlib.sha256(str(state).encode()).hexdigest()[:16] + base = Path("/run/user") / str(os.geteuid()) + try: + safe = base.is_dir() and base.stat().st_uid == os.geteuid() and not base.is_symlink() + except OSError: + safe = False + if not safe: + base = Path("/tmp") + return base / ("fbe-tty-" + str(os.geteuid()) + "-" + tag) + + +def _config(state): + path = state / "tty.json" + data = {} + if path.exists(): + if path.is_symlink() or path.stat().st_uid != os.geteuid(): + raise TerminalDenied("TTY_CONFIG_OWNER_INVALID") + try: + data = json.loads(path.read_text()) + except (ValueError, OSError): + raise TerminalDenied("TTY_CONFIG_INVALID") from None + if type(data) is not dict or set(data) - {"limits", "cwd"}: + raise TerminalDenied("TTY_CONFIG_INVALID") + values = data.get("limits", {}) + allowed = {f.name for f in fields(Limits)} + if type(values) is not dict or set(values) - allowed: + raise TerminalDenied("TTY_LIMITS_INVALID") + limits = Limits(**values) + if any(type(getattr(limits, f.name)) is not int for f in fields(Limits)): + raise TerminalDenied("TTY_LIMITS_INVALID") + if not (1 <= limits.max_sessions <= 4096 and + 1 <= limits.min_rows <= limits.max_rows <= 65535 and + 1 <= limits.min_cols <= limits.max_cols <= 65535 and + 1 <= limits.max_text_bytes <= limits.rpc_max_bytes // 2 and + 1024 <= limits.max_screen_bytes <= limits.rpc_max_bytes - 4096 and + 0 <= limits.history_lines <= 1000000 and + 0 <= limits.idle_timeout_s and 1 <= limits.command_timeout_s <= 300 and + 65536 <= limits.rpc_max_bytes <= 16777216): + raise TerminalDenied("TTY_LIMITS_INVALID") + cwd = Path(data.get("cwd", str(state / "home"))).expanduser().absolute() + if not cwd.exists(): + cwd.mkdir(mode=0o700, parents=True) + if not cwd.is_dir(): + raise TerminalDenied("TTY_CWD_INVALID") + return limits, cwd + + +def _identity(pid): + try: + p = Path("/proc") / str(pid) + parts = (p / "stat").read_text().rsplit(")", 1)[1].split() + return {"pid": int(pid), "birth": parts[19], "uid": p.stat().st_uid, + "state": parts[0]} + except (OSError, ValueError, IndexError): + return None + + +def _alive(record): + now = _identity(record.get("pid", 0)) + return bool(now and now["birth"] == record.get("birth") + and now["uid"] == os.geteuid() and now["state"] != "Z") + + +def _write_json(path, value): + tmp = path.with_name(path.name + ".new") + tmp.write_text(json.dumps(value, ensure_ascii=True, indent=2) + "\n") + tmp.chmod(0o600) + tmp.replace(path) + + +def _safe_error(exc): + name = str(exc.args[0]) if getattr(exc, "args", ()) else "" + if name.startswith("OWNER_"): + return "TTY_INPUT_CONTEXT_INVALID" + return name if _ERROR.fullmatch(name) else "TTY_INTERNAL_ERROR" + + +class PortableManager(RealTerminalMixin, _core.Manager): + """The operative real-terminal mixin with portable runtime configuration.""" + def __init__(self, state, limits, home): + self.profile = state + self.limits = limits + self.home = home + self.tmux_socket = runtime_path(state) / "tmux.sock" + self.tmux_bin = shutil.which("tmux") + if not self.tmux_bin: + raise TerminalDenied("TMUX_NOT_AVAILABLE") + # The original constructor initializes its lock and bookkeeping. + # Session policy is supplied by this profile, not its old staging cap. + super().__init__(owner_verifier=self._local_context, max_sessions=2, + idle_timeout_s=600) + self.max_sessions = limits.max_sessions + self._last_used = {} + + @staticmethod + def _local_context(context): + return (type(context) is dict and context.get("program") == "bash" + and context.get("operation") in {"text.type", "key.send"} + and type(context.get("session_id")) is str + and _SESSION.fullmatch(context["session_id"]) is not None + and type(context.get("payload_sha256")) is str + and re.fullmatch(r"[a-f0-9]{64}", context["payload_sha256"]) is not None + and context.get("owner_assertion") == {}) + + def _size(self, rows, cols): + if (type(rows) is not int or type(cols) is not int or + not self.limits.min_rows <= rows <= self.limits.max_rows or + not self.limits.min_cols <= cols <= self.limits.max_cols): + raise TerminalDenied("SIZE_INVALID") + + def open(self, program="bash", rows=24, cols=80): + if program != "bash": + raise TerminalDenied("PROGRAM_NOT_SUPPORTED") + self._size(rows, cols) + result = super().open("bash", rows, cols) + self._last_used[result["session_id"]] = time.monotonic() + return result + + def reap(self): + super().reap() + if self.limits.idle_timeout_s: + now = time.monotonic() + for sid, last in list(self._last_used.items()): + if now - last > self.limits.idle_timeout_s and sid in self._real: + try: + super().abort(sid) + finally: + self._last_used.pop(sid, None) + + def touch(self, sid): + if sid not in self._real: + raise TerminalDenied("SESSION_NOT_FOUND") + self._last_used[sid] = time.monotonic() + + def abort(self, sid): + if sid not in self._real: + raise TerminalDenied("SESSION_NOT_FOUND") + result = super().abort(sid) + self._last_used.pop(sid, None) + return result + + def status(self): + result = super().status() + for key in ("owner_device_connected", "owner_signature_required"): + result.pop(key, None) + result.update({"input_policy": "same-uid-authorized", + "input_enabled": True, + "limits": asdict(self.limits), + "real_idle_timeout_s": self.limits.idle_timeout_s or None}) + return result + + +class PortableHandler(socketserver.StreamRequestHandler): + def send(self, value): + raw = (json.dumps(value, ensure_ascii=True, separators=(",", ":")) + "\n").encode() + if len(raw) > self.server.limits.rpc_max_bytes: + raw = b'{"ok":false,"error":"TTY_RESPONSE_TOO_LARGE"}\n' + self.wfile.write(raw) + + def handle(self): + try: + self.request.settimeout(self.server.limits.command_timeout_s + 5) + _, uid, _ = struct.unpack("3i", self.request.getsockopt( + socket.SOL_SOCKET, socket.SO_PEERCRED, 12)) + if uid != self.server.owner_uid: + self.send({"ok": False, "error": "SAME_UID_REQUIRED"}) + return + raw = self.rfile.readline(self.server.limits.rpc_max_bytes + 1) + if len(raw) > self.server.limits.rpc_max_bytes or not raw.endswith(b"\n"): + raise TerminalDenied("TTY_REQUEST_TOO_LARGE_OR_TRUNCATED") + payload = json.loads(raw) + result = dispatch(self.server.manager, payload) + self.send(result) + except (TerminalDenied, SudoControlError, KeyError) as exc: + self.send({"ok": False, "error": _safe_error(exc)}) + except (ValueError, TypeError, UnicodeError): + self.send({"ok": False, "error": "TTY_REQUEST_INVALID"}) + except Exception: + self.send({"ok": False, "error": "TTY_INTERNAL_ERROR"}) + + +class PortableServer(_server.LocalServer): + def __init__(self, path, manager): + p = Path(path).absolute() + _private(p.parent) + if p.exists() or p.is_symlink(): + raise TerminalDenied("TTY_SOCKET_ALREADY_EXISTS") + self.owner_uid = os.geteuid() + self.manager = manager + self.limits = manager.limits + self._socket_path = p + old = os.umask(0o077) + try: + socketserver.UnixStreamServer.__init__(self, str(p), PortableHandler) + finally: + os.umask(old) + p.chmod(0o600) + + +_SHAPES = { + "status": (set(), set()), + "open": (set(), {"program", "rows", "cols"}), + "observe": ({"session_id"}, set()), + "key.send": ({"session_id", "key"}, set()), + "text.type": ({"session_id", "text"}, set()), + "resize": ({"session_id", "rows", "cols"}, set()), + "close": ({"session_id"}, set()), + "sudo": ({"session_id", "mode"}, set()), +} + + +def dispatch(manager, payload): + if type(payload) is not dict: + raise TerminalDenied("TTY_REQUEST_INVALID") + if payload.get("authorized") is not True: + raise TerminalDenied("AUTHORIZED_TRUE_REQUIRED") + op = payload.get("op") + if op == "abort": + op = "close" + if type(op) is not str or op not in _SHAPES: + raise TerminalDenied("TTY_OPERATION_INVALID") + required, optional = _SHAPES[op] + keys = set(payload) - {"op", "authorized"} + if not required <= keys or keys - required - optional: + raise TerminalDenied("TTY_REQUEST_INVALID") + sid = payload.get("session_id") + if sid is not None and (type(sid) is not str or not _SESSION.fullmatch(sid)): + raise TerminalDenied("SESSION_ID_INVALID") + with manager._lock: + manager.reap() + if op not in {"status", "open"}: + manager.touch(sid) + if op == "status": + data = manager.status() + elif op == "open": + data = manager.open(payload.get("program", "bash"), + payload.get("rows", 24), payload.get("cols", 80)) + elif op == "sudo": + data = manager.sudo(sid, payload["mode"]) + if payload["mode"] != "status" and data.get("cleanup_pending"): + return {"ok": False, "error": "TTY_SUDO_TRANSITION_CLEANUP_PENDING", + "result": data, "transport": "same-uid-unix"} + elif op == "observe": + data = manager.observe(sid) + data = dict(data, observation_kind="terminal_transcript", authentic_pixels=False, full_screen=False) + elif op == "key.send": + data = manager.key(sid, payload["key"], approved_context={}) + elif op == "text.type": + data = manager.text(sid, payload["text"], approved_context={}) + elif op == "resize": + manager._size(payload["rows"], payload["cols"]) + data = manager.resize(sid, payload["rows"], payload["cols"]) + else: + data = manager.abort(sid) + return {"ok": True, "result": data, "transport": "same-uid-unix"} + + +def request(state, payload): + """Send one authorized operation to this profile's real TTY daemon.""" + try: + if type(payload) is not dict or payload.get("authorized") is not True: + return {"ok": False, "error": "AUTHORIZED_TRUE_REQUIRED"} + state = state_path(state) + path = runtime_path(state) / "agent.sock" + if not path.exists(): + return {"ok": False, "error": "TTY_NOT_RUNNING"} + s = path.lstat() + if (not stat.S_ISSOCK(s.st_mode) or s.st_uid != os.geteuid() + or stat.S_IMODE(s.st_mode) != 0o600): + return {"ok": False, "error": "TTY_SOCKET_UNTRUSTED"} + limits, _ = _config(state) + raw = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode() + if len(raw) > limits.rpc_max_bytes: + return {"ok": False, "error": "TTY_REQUEST_TOO_LARGE_OR_TRUNCATED"} + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: + client.settimeout(limits.command_timeout_s + 10) + client.connect(str(path)) + client.sendall(raw) + client.shutdown(socket.SHUT_WR) + received = bytearray() + while True: + part = client.recv(65536) + if not part: + break + received.extend(part) + if len(received) > limits.rpc_max_bytes: + return {"ok": False, "error": "TTY_RESPONSE_TOO_LARGE"} + value = json.loads(received) + return value if type(value) is dict else {"ok": False, "error": "TTY_RESPONSE_INVALID"} + except TerminalDenied as exc: + return {"ok": False, "error": _safe_error(exc)} + except (OSError, TimeoutError): + return {"ok": False, "error": "TTY_UNAVAILABLE"} + except (ValueError, TypeError, UnicodeError): + return {"ok": False, "error": "TTY_RESPONSE_INVALID"} + + +def status(state=None): + return request(state, {"op": "status", "authorized": True}) + + +def serve(state=None): + state = _private(state_path(state)) + limits, home = _config(state) + rt = _private(runtime_path(state)) + lock = (state / "tty.lock").open("a") + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + path = rt / "agent.sock" + if path.exists() or path.is_symlink(): + old = status(state) + if old.get("ok"): + raise TerminalDenied("TTY_ALREADY_RUNNING") + st = path.lstat() + if not stat.S_ISSOCK(st.st_mode) or st.st_uid != os.geteuid(): + raise TerminalDenied("TTY_SOCKET_UNTRUSTED") + path.unlink() + manager = PortableManager(state, limits, home) + def stopped(_signum, _frame): + raise SystemExit(0) + signal.signal(signal.SIGTERM, stopped) + signal.signal(signal.SIGINT, stopped) + pidfile = state / "tty-process.json" + _write_json(pidfile, {**_identity(os.getpid()), "socket": str(path), "state_dir": str(state)}) + try: + with PortableServer(path, manager) as server: + print(json.dumps({"ok": True, "ready": True, "socket": str(path), + "policy": "same-uid-authorized", "x11_required": False}), flush=True) + server.serve_forever(poll_interval=0.2) + finally: + pidfile.unlink(missing_ok=True) + + +def start(state=None): + state = _private(state_path(state)) + current = status(state) + if current.get("ok"): + return current + env = os.environ.copy() + root = str(Path(__file__).resolve().parents[1]) + env["PYTHONPATH"] = root + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "") + log = (state / "tty.log").open("ab") + proc = subprocess.Popen([sys.executable, "-m", "framebridge_expanded.tty", + "serve", "--state", str(state)], env=env, + stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, + start_new_session=True, umask=0o077) + log.close() + _STARTED_CHILDREN[str(state)] = proc + deadline = time.monotonic() + 5 + while proc.poll() is None and time.monotonic() < deadline: + current = status(state) + if current.get("ok"): + return current + time.sleep(0.1) + return {"ok": False, "error": "TTY_START_FAILED"} + + +def stop(state=None): + state = state_path(state) + p = state / "tty-process.json" + if not p.is_file() or p.is_symlink(): + return {"ok": True, "state": "STOPPED", "already_stopped": True} + try: + record = json.loads(p.read_text()) + if not _alive(record): + return {"ok": True, "state": "STOPPED", "already_stopped": True} + os.kill(record["pid"], signal.SIGTERM) + deadline = time.monotonic() + 10 + while _alive(record) and time.monotonic() < deadline: + time.sleep(0.1) + return {"ok": not _alive(record), "state": "STOPPED" if not _alive(record) else "STOP_TIMEOUT"} + except (ValueError, OSError, KeyError): + return {"ok": False, "error": "TTY_STOP_FAILED"} + finally: + child = _STARTED_CHILDREN.get(str(state)) + if child is not None: + try: + child.wait(timeout=2) + _STARTED_CHILDREN.pop(str(state), None) + except subprocess.TimeoutExpired: + pass + + +def _wire_reply(state, result): + cache = _private(state_path(state) / "tty-replies") + for old in cache.glob("*.json"): + if time.time() - old.stat().st_mtime > 3600: + old.unlink(missing_ok=True) + raw = json.dumps(result, ensure_ascii=True, separators=(",", ":")).encode() + encoded = base64.b64encode(zlib.compress(raw, 6)).decode("ascii") + chunks = [encoded[i:i + _WIRE_CHARS] for i in range(0, len(encoded), _WIRE_CHARS)] + ticket = uuid.uuid4().hex + item = {"ticket": ticket, "sha256": hashlib.sha256(raw).hexdigest(), + "bytes": len(raw), "chunks": chunks} + _write_json(cache / (ticket + ".json"), item) + return {"ok": True, "wire": "zlib-base64-v1", "ticket": ticket, + "sha256": item["sha256"], "bytes": len(raw), "chunks": len(chunks), + "index": 0, "data": chunks[0]} + + +def read_reply(state, ticket, index): + if type(ticket) is not str or not _SESSION.fullmatch(ticket) or type(index) is not int or index < 0: + return {"ok": False, "error": "TTY_REPLY_INVALID"} + p = state_path(state) / "tty-replies" / (ticket + ".json") + try: + st = p.lstat() + if not stat.S_ISREG(st.st_mode) or st.st_uid != os.geteuid() or st.st_mode & 0o077: + raise ValueError() + item = json.loads(p.read_text()) + return {"ok": True, "ticket": ticket, "index": index, "data": item["chunks"][index], + "chunks": len(item["chunks"]), "sha256": item["sha256"]} + except (OSError, ValueError, KeyError, IndexError): + return {"ok": False, "error": "TTY_REPLY_NOT_FOUND"} + + +def main(argv=None): + parser = argparse.ArgumentParser(description="Real persistent Bash CUA-TTY over a same-UID Unix socket.") + parser.add_argument("command", choices=("serve", "start", "stop", "status", "request", "read")) + parser.add_argument("--state", default=None) + parser.add_argument("--payload-b64", default=None) + parser.add_argument("--wire", action="store_true", help="Return a compressed, chunked response for bounded transports.") + parser.add_argument("--ticket") + parser.add_argument("--index", type=int, default=0) + args = parser.parse_args(argv) + try: + if args.command == "serve": + serve(args.state) + return 0 + if args.command == "start": + result = start(args.state) + elif args.command == "stop": + result = stop(args.state) + elif args.command == "status": + result = status(args.state) + elif args.command == "read": + result = read_reply(args.state, args.ticket, args.index) + else: + if args.payload_b64: + raw = base64.b64decode(args.payload_b64, validate=True) + else: + raw = sys.stdin.buffer.read(16777217) + if len(raw) > 16777216: + raise TerminalDenied("TTY_REQUEST_TOO_LARGE_OR_TRUNCATED") + result = request(args.state, json.loads(raw)) + if args.wire: + result = _wire_reply(args.state, result) + print(json.dumps(result, ensure_ascii=True, separators=(",", ":"))) + return 0 if result.get("ok") else 1 + except TerminalDenied as exc: + print(json.dumps({"ok": False, "error": _safe_error(exc)})) + return 1 + except (ValueError, TypeError, UnicodeError): + print(json.dumps({"ok": False, "error": "TTY_REQUEST_INVALID"})) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/expanded/framebridge_expanded/tty_mcp.py b/source/expanded/framebridge_expanded/tty_mcp.py new file mode 100644 index 0000000..8e26219 --- /dev/null +++ b/source/expanded/framebridge_expanded/tty_mcp.py @@ -0,0 +1,147 @@ +"""Seven local/remote MCP tools for the real persistent FrameBridge CUA-TTY.""" +from __future__ import annotations + +import base64 +import hashlib +import json +import os +import re +import shlex +import uuid +import zlib + +from . import tty + +TOOL_NAMES = ( + "cua_tty_status", "cua_tty_open", "cua_tty_observe", + "cua_tty_key_send", "cua_tty_text_type", "cua_tty_resize", "cua_tty_close", +) + + +def _exec_json(execute, target, arguments): + script = "python3 -m framebridge_expanded.tty " + " ".join(shlex.quote(str(x)) for x in arguments) + response = execute(script=script, authorized=True, timeout_s=30, + job_id="tty-rpc-" + uuid.uuid4().hex[:24], + sha256=hashlib.sha256(script.encode()).hexdigest(), + include_stdout=True, target=target) + if type(response) is not dict or response.get("ok") is not True: + return {"ok": False, "error": "TTY_REMOTE_EXEC_FAILED"} + if response.get("stdout_truncated"): + return {"ok": False, "error": "TTY_REMOTE_RESPONSE_TRUNCATED"} + output = response.get("stdout", "") + if type(output) is not str: + return {"ok": False, "error": "TTY_REMOTE_RESPONSE_INVALID"} + try: + value = json.loads(output) + except (ValueError, TypeError): + return {"ok": False, "error": "TTY_REMOTE_RESPONSE_INVALID"} + return value if type(value) is dict else {"ok": False, "error": "TTY_REMOTE_RESPONSE_INVALID"} + + +def _remote_request(execute, target, payload): + encoded = base64.b64encode(json.dumps(payload, ensure_ascii=True, separators=(",", ":")).encode()).decode("ascii") + first = _exec_json(execute, target, ["request", "--payload-b64", encoded, "--wire"]) + if first.get("ok") is not True: + return first + try: + ticket = first["ticket"] + digest = first["sha256"] + length = first["bytes"] + count = first["chunks"] + if (first.get("wire") != "zlib-base64-v1" or type(ticket) is not str + or re.fullmatch(r"[a-f0-9]{32}", ticket) is None + or type(digest) is not str or re.fullmatch(r"[a-f0-9]{64}", digest) is None + or type(length) is not int or not 0 < length <= 16777216 + or type(count) is not int or not 1 <= count <= 20000): + raise ValueError() + chunks = [] + for index in range(count): + part = first if index == 0 else _exec_json( + execute, target, ["read", "--ticket", ticket, "--index", index]) + if (part.get("ok") is not True or part.get("ticket") != ticket + or part.get("index") != index or part.get("chunks") != count + or part.get("sha256") != digest or type(part.get("data")) is not str + or len(part["data"]) > 1800): + raise ValueError() + chunks.append(part["data"]) + compressed = base64.b64decode("".join(chunks), validate=True) + decoder = zlib.decompressobj() + raw = decoder.decompress(compressed, length + 1) + if (len(raw) != length or not decoder.eof or decoder.unused_data + or decoder.unconsumed_tail or hashlib.sha256(raw).hexdigest() != digest): + raise ValueError() + result = json.loads(raw) + return result if type(result) is dict else {"ok": False, "error": "TTY_REMOTE_RESPONSE_INVALID"} + except (KeyError, ValueError, TypeError, zlib.error): + return {"ok": False, "error": "TTY_REMOTE_RESPONSE_INVALID"} + + +def register_tools(mcp, exec_request, *, screen_client=None, image_factory=None): + if screen_client is None: + from .real_screen.screen_frontdoor import ScreenClient, resolve_profile_node + if image_factory is None: + from mcp.server.fastmcp import Image + image_factory = Image + screen_client = ScreenClient(exec_request, image_factory, resolve_node=resolve_profile_node) + """Register tools; exec_request is the existing authorized fleet executor.""" + def call(payload, target): + if payload.get("authorized") is not True: + return {"ok": False, "error": "AUTHORIZED_TRUE_REQUIRED"} + if type(target) is not str or not target.strip(): + return {"ok": False, "error": "TTY_TARGET_INVALID"} + target = target.strip() + if target.upper() == "LOCAL": + result = tty.request(os.environ.get("FRAMEBRIDGE_EXPANDED_STATE"), payload) + target = "LOCAL" + else: + result = _remote_request(exec_request, target, payload) + result = dict(result) + result["target"] = target + return result + + @mcp.tool() + def cua_tty_status(authorized: bool = False, target: str = "LOCAL") -> dict: + """Report the actual CUA-TTY daemon, Bash sessions and configurable resource limits.""" + return call({"op": "status", "authorized": authorized}, target) + + @mcp.tool() + def cua_tty_open(authorized: bool = False, program: str = "bash", + rows: int = 24, cols: int = 80, target: str = "LOCAL") -> dict: + """Open a real persistent Bash console in the profile's private tmux server; no X11 required.""" + return call({"op": "open", "authorized": authorized, "program": program, + "rows": rows, "cols": cols}, target) + + @mcp.tool() + def cua_tty_observe(session_id: str, authorized: bool = False, + target: str = "LOCAL"): + """Capture the entire actual active screen and report whether this session is visible.""" + return screen_client.call("observe", target, authorized, session_id=session_id) + + @mcp.tool() + def cua_tty_key_send(session_id: str, key: str, authorized: bool = False, + target: str = "LOCAL", capture_id: str = ""): + """Send a key only to the visible TTY target bound to a real capture; use graphical CUA for X11.""" + return screen_client.call("key", target, authorized, session_id=session_id, + capture_id=capture_id, key=key) + + @mcp.tool() + def cua_tty_text_type(session_id: str, text: str, authorized: bool = False, + target: str = "LOCAL", capture_id: str = ""): + """Type only into the visible TTY target bound to a real capture; never into a hidden session.""" + return screen_client.call("text", target, authorized, session_id=session_id, + capture_id=capture_id, text=text) + + @mcp.tool() + def cua_tty_resize(session_id: str, rows: int, cols: int, + authorized: bool = False, target: str = "LOCAL") -> dict: + """Resize the real PTY/tmux console within the limits reported by cua_tty_status.""" + return call({"op": "resize", "authorized": authorized, "session_id": session_id, + "rows": rows, "cols": cols}, target) + + @mcp.tool() + def cua_tty_close(session_id: str, authorized: bool = False, + target: str = "LOCAL") -> dict: + """Close only the selected console owned by this runtime and verify tmux cleanup.""" + return call({"op": "close", "authorized": authorized, "session_id": session_id}, target) + + return TOOL_NAMES diff --git a/source/expanded/framebridge_expanded/tty_sudo_control.py b/source/expanded/framebridge_expanded/tty_sudo_control.py new file mode 100644 index 0000000..5fa4068 --- /dev/null +++ b/source/expanded/framebridge_expanded/tty_sudo_control.py @@ -0,0 +1,519 @@ +"""Per-console ordinary-sudo capability; no privilege grant or custom authentication. + +Only transient user services created by this controller are stopped. Each new +console has its own tmux server and cgroup. NoNewPrivileges is checked on both +the actual server and Bash process. Callers must hold their terminal-manager +lock while changing mode so keyboard input cannot race the transition. +""" +from __future__ import annotations +import hashlib +import os +from pathlib import Path +import pwd +import re +import shutil +import shlex +import stat +import subprocess +import threading +import time +import uuid + +class SudoControlError(RuntimeError): + def __init__(self, code): + self.code = code + super().__init__(code) + +def _fail(code): + raise SudoControlError(code) + +def _proc(pid): + try: + root = Path("/proc") / str(int(pid)) + raw = (root / "stat").read_text().rsplit(") ", 1)[1].split() + fields = {} + for line in (root / "status").read_text().splitlines(): + key, _, value = line.partition(":") + if key in {"Uid", "NoNewPrivs"}: + fields[key] = value.split() + try: + executable = os.readlink(root / "exe") + except OSError: + executable = "" + return {"pid": int(pid), "state": raw[0], "ppid": int(raw[1]), + "pgrp": int(raw[2]), "sid": int(raw[3]), "tty": int(raw[4]), + "tpgid": int(raw[5]), "birth": raw[19], "uids": tuple(map(int, fields["Uid"])), + "nnp": int(fields["NoNewPrivs"][0]), "exe": executable} + except (OSError, ValueError, KeyError, IndexError): + return None + +def _processes(): + result = {} + for path in Path("/proc").iterdir(): + if path.name.isdigit(): + item = _proc(int(path.name)) + if item and item["state"] not in {"Z", "X"}: + result[item["pid"]] = item + return result + +def _descendants(processes, pid): + found = set() + while True: + added = {p for p, info in processes.items() + if info["ppid"] == pid or info["ppid"] in found} - found + if not added: + return found + found.update(added) + +SESSION_ENVIRONMENT_KEYS = ( + "DISPLAY", "XAUTHORITY", "DBUS_SESSION_BUS_ADDRESS", "XDG_RUNTIME_DIR", + "LD_LIBRARY_PATH", "LIBGL_DRIVERS_PATH", "GBM_BACKENDS_PATH", + "__EGL_VENDOR_LIBRARY_FILENAMES", "__GLX_VENDOR_LIBRARY_NAME", + "GALLIUM_DRIVER", "MESA_LOADER_DRIVER_OVERRIDE", "VK_DRIVER_FILES", + "VK_ICD_FILENAMES", "DRI_PRIME", +) + + +class SudoConsoleController: + def __init__(self, runtime_dir, home=None, timeout=6.0, *, min_rows=2, max_rows=500, min_cols=10, max_cols=1000): + if os.geteuid() == 0: + _fail("TTY_SUDO_USER_SESSION_REQUIRED") + self.uid = os.getuid() + dimensions = (min_rows, max_rows, min_cols, max_cols) + if (not all(type(value) is int for value in dimensions) + or not 1 <= min_rows <= max_rows or not 1 <= min_cols <= max_cols): + _fail("TTY_SUDO_SIZE_LIMITS_INVALID") + self.min_rows, self.max_rows = min_rows, max_rows + self.min_cols, self.max_cols = min_cols, max_cols + # The package may run inside a profile-scoped private desktop bus. + # sudo-mode units belong to the actual logged-in user's systemd manager. + self.user_runtime = Path("/run/user") / str(self.uid) + try: + runtime_info = self.user_runtime.lstat() + bus_info = (self.user_runtime / "bus").lstat() + except OSError: + _fail("TTY_SUDO_USER_MANAGER_UNAVAILABLE") + if (not stat.S_ISDIR(runtime_info.st_mode) or runtime_info.st_uid != self.uid + or stat.S_IMODE(runtime_info.st_mode) != 0o700 + or not stat.S_ISSOCK(bus_info.st_mode) or bus_info.st_uid != self.uid): + _fail("TTY_SUDO_USER_MANAGER_UNAVAILABLE") + self.user_bus_address = "unix:path=" + str(self.user_runtime / "bus") + # Preserve only this profile's explicit graphical context in the shell + # unit. These are paths and driver selections, never authority cookies. + self.session_environment = { + key: os.environ[key] for key in SESSION_ENVIRONMENT_KEYS if key in os.environ + } + self.home = Path(home or pwd.getpwuid(self.uid).pw_dir).resolve() + self.base = Path(runtime_dir).absolute() / "sudo" + self.base.parent.mkdir(mode=0o700, parents=True, exist_ok=True) + self.base.mkdir(mode=0o700, exist_ok=True) + for path in (self.base.parent, self.base): + info = path.lstat() + if (not stat.S_ISDIR(info.st_mode) or info.st_uid != self.uid + or stat.S_IMODE(info.st_mode) != 0o700): + _fail("TTY_SUDO_RUNTIME_NOT_PRIVATE") + if len(os.fsencode(self.base / ("s-" + "a" * 26 + ".sock"))) >= 104: + _fail("TTY_SUDO_SOCKET_PATH_TOO_LONG") + self.tmux = shutil.which("tmux") + self.systemd_run = shutil.which("systemd-run") + self.systemctl = shutil.which("systemctl") + if not all((self.tmux, self.systemd_run, self.systemctl)): + _fail("TTY_SUDO_BACKEND_NOT_AVAILABLE") + self.timeout = float(timeout) + self.prefix = "framebridge-tty-" + hashlib.sha256(str(self.base).encode()).hexdigest()[:12] + "-" + self._records = {} + self._lock = threading.RLock() + + def _run(self, command, *, data=None, optional=False): + env = {k: v for k, v in os.environ.items() if k not in {"TMUX", "TMUX_PANE"}} + env["XDG_RUNTIME_DIR"] = str(self.user_runtime) + env["DBUS_SESSION_BUS_ADDRESS"] = self.user_bus_address + try: + result = subprocess.run(command, input=data, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env, timeout=self.timeout) + except subprocess.TimeoutExpired: + _fail("TTY_SUDO_BACKEND_TIMEOUT") + except OSError: + _fail("TTY_SUDO_BACKEND_UNAVAILABLE") + if result.returncode and not optional: + _fail("TTY_SUDO_BACKEND_FAILED") + return result + + def _unit(self, name): + if not re.fullmatch(re.escape(self.prefix) + r"[a-f0-9]{32}-[a-f0-9]{8}\.service", name): + _fail("TTY_SUDO_UNIT_NOT_OWNED") + result = self._run([self.systemctl, "--user", "show", name, "--no-pager", + "--property=MainPID,ActiveState,SubState,NoNewPrivileges,ControlGroup"], optional=True) + return dict(line.split("=", 1) for line in result.stdout.decode("utf-8", "replace").splitlines() + if "=" in line) + + def _tmux(self, record, *args, optional=False, data=None): + socket = record.get("tmux_socket") or record.get("legacy_socket") + if not isinstance(socket, str) or not socket.startswith("/") or "\0" in socket: + _fail("TTY_SUDO_SOCKET_INVALID") + return self._run([self.tmux, "-S", socket, "-f", "/dev/null", *map(str, args)], + optional=optional, data=data) + + def _resolve(self, session): + if isinstance(session, str): + session = {"session_id": session} + elif not isinstance(session, dict): + value = dict(vars(session)) + value.update(value.pop("_sudo_record", value.pop("sudo_record", {})) or {}) + session = value + sid = session.get("session_id") + if not isinstance(sid, str) or not re.fullmatch(r"[a-f0-9]{32}", sid): + _fail("TTY_SUDO_SESSION_INVALID") + record = dict(self._records.get(sid) or session) + if not record.get("pane") or not (record.get("tmux_socket") or record.get("legacy_socket")): + _fail("TTY_SUDO_SESSION_NOT_FOUND") + return record + + def _pane(self, record): + values = {} + for field in ("pane_pid", "pane_width", "pane_height", "pane_dead"): + value = self._tmux(record, "display-message", "-p", "-t", record["pane"], + "#{" + field + "}").stdout.decode().strip() + if not value.isdigit(): + _fail("TTY_SUDO_PANE_METADATA_INVALID") + values[field] = int(value) + if values["pane_dead"]: + _fail("TTY_SUDO_CONSOLE_EXITED") + proc = _proc(values["pane_pid"]) + if proc is None: + _fail("TTY_SUDO_PROCESS_NOT_FOUND") + if record.get("pid") and (record["pid"] != proc["pid"] or record.get("birth") != proc["birth"]): + _fail("TTY_SUDO_PROCESS_IDENTITY_CHANGED") + try: + cwd = os.readlink(Path("/proc") / str(proc["pid"]) / "cwd") + except OSError: + _fail("TTY_SUDO_CWD_UNAVAILABLE") + return proc, cwd, values["pane_height"], values["pane_width"] + + def _scope(self, group): + # cgroup v2 cgroup.procs plus nested cgroups includes reparented descendants. + if not group or not group.startswith("/") or ".." in Path(group).parts: + _fail("TTY_SUDO_CGROUP_UNAVAILABLE") + root = Path("/sys/fs/cgroup") / group.lstrip("/") + try: + files = [root / "cgroup.procs", *root.glob("**/cgroup.procs")] + return {int(line) for file in files for line in file.read_text().splitlines() if line.isdigit()} + except OSError: + _fail("TTY_SUDO_CGROUP_UNAVAILABLE") + + def _pane_group(self, pid): + try: + entries = Path("/proc", str(pid), "cgroup").read_text().splitlines() + return next(line[3:] for line in entries if line.startswith("0::")) + except (OSError, StopIteration): + _fail("TTY_SUDO_CGROUP_UNAVAILABLE") + + def _pane_scope(self, group, server_group): + if group == server_group: + return None + name = Path(group).name + if not re.fullmatch(r"tmux-spawn-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\.scope", name): + _fail("TTY_SUDO_CGROUP_UNEXPECTED") + if Path(group).parent != Path(server_group).parent: + _fail("TTY_SUDO_CGROUP_UNEXPECTED") + return name + + def _inspect(self, record): + proc, cwd, rows, cols = self._pane(record) + all_processes = _processes() + shell_pid = proc["pid"] + candidates = _descendants(all_processes, shell_pid) + candidates.update(p for p, info in all_processes.items() if p != shell_pid and ( + info["sid"] == proc["sid"] or (proc["tty"] and info["tty"] == proc["tty"]))) + unit_name = record.get("unit_name") + server = None + group = None + pane_group = None + pane_scope = None + if unit_name: + unit = self._unit(unit_name) + try: + main_pid = int(unit["MainPID"]) + except (KeyError, ValueError): + _fail("TTY_SUDO_UNIT_NOT_RUNNING") + server = _proc(main_pid) + if (unit.get("ActiveState") != "active" or server is None + or Path(server["exe"]).name != Path(self.tmux).name + or (record.get("server_pid") and record["server_pid"] != main_pid) + or (record.get("server_birth") and record["server_birth"] != server["birth"])): + _fail("TTY_SUDO_UNIT_IDENTITY_CHANGED") + group = unit.get("ControlGroup") + pane_group = self._pane_group(shell_pid) + pane_scope = self._pane_scope(pane_group, group) + if record.get("pane_control_group") and record["pane_control_group"] != pane_group: + _fail("TTY_SUDO_CGROUP_IDENTITY_CHANGED") + scoped = self._scope(group) | self._scope(pane_group) + candidates.update(scoped - {main_pid, shell_pid}) + expected = "yes" if proc["nnp"] else "no" + if unit.get("NoNewPrivileges") != expected or server["nnp"] != proc["nnp"]: + _fail("TTY_SUDO_PRIVILEGE_STATE_MISMATCH") + elevated = (any(uid != self.uid for uid in proc["uids"]) + or any(p in all_processes and any(u != self.uid for u in all_processes[p]["uids"]) + for p in candidates)) + unknown = candidates - all_processes.keys() + idle_shell = (Path(proc["exe"]).name == "bash" and proc["state"] == "S" + and proc["tpgid"] == proc["pgrp"] == proc["pid"]) + marker = record.get("prompt_marker") + try: + prompt_idle = bool(marker and Path(marker).read_text().strip() == record.get("generation", "") + ":idle") + except OSError: + prompt_idle = False + reason = "TTY_SUDO_ELEVATED_PROCESS_PRESENT" if elevated else ( + "TTY_SUDO_PROCESS_STATE_UNVERIFIED" if unknown else ( + "TTY_SUDO_CONSOLE_BUSY" if candidates or not idle_shell else ( + "TTY_SUDO_PROMPT_UNVERIFIED" if not prompt_idle else None))) + return {**record, "ok": True, "verified": True, "pid": proc["pid"], "birth": proc["birth"], + "mode": "off" if proc["nnp"] else "on", "no_new_privs": proc["nnp"], + "server_pid": server["pid"] if server else None, + "server_no_new_privs": server["nnp"] if server else None, + "server_birth": server["birth"] if server else None, "control_group": group, + "pane_control_group": pane_group, "pane_scope_unit": pane_scope, + "cwd": cwd, "rows": rows, "cols": cols, "busy": bool(reason), + "busy_reason": reason, "busy_pids": sorted(candidates), + "managed": bool(unit_name), "sudo_authentication": "system_policy", + "sudo_authorization_granted": False, + "legacy_detached_scope_complete": bool(unit_name), "prompt_verified": prompt_idle} + + def _old_cleanup_complete(self, record): + try: + old = _proc(record["pid"]) + if old and old["birth"] == record["birth"] and old["state"] not in {"Z", "X"}: + return False + unit = self._unit(record["unit_name"]) + if int(unit.get("MainPID", "0")) or unit.get("ActiveState") in {"active", "activating", "deactivating"}: + return False + for group in {record.get("control_group"), record.get("pane_control_group")}: + if not group: + return False + if (Path("/sys/fs/cgroup") / group.lstrip("/")).exists() and self._scope(group): + return False + return True + except (SudoControlError, KeyError, ValueError): + return False + + def _refresh_pending(self, record): + old = record.get("pending_old_record") + if old and self._old_cleanup_complete(old): + record = dict(record) + record.pop("pending_old_record", None) + record.update(cleanup_pending=False, transition_complete=True, cleanup_error=None) + self._records[record["session_id"]] = record + return record + + def status(self, session): + with self._lock: + record = self._refresh_pending(self._resolve(session)) + return self._inspect(record) + + def _idle(self, record): + current = self._inspect(record) + if current["busy"]: + _fail(current["busy_reason"]) + return current + + def _launch(self, session_id, cwd, rows, cols, mode): + if mode not in {"on", "off"}: + _fail("TTY_SUDO_MODE_INVALID") + if not re.fullmatch(r"[a-f0-9]{32}", str(session_id)): + _fail("TTY_SUDO_SESSION_INVALID") + if not (type(rows) is int and self.min_rows <= rows <= self.max_rows and type(cols) is int and self.min_cols <= cols <= self.max_cols): + _fail("TTY_SUDO_SIZE_INVALID") + cwd = str(Path(cwd).resolve()) + if not Path(cwd).is_dir(): + _fail("TTY_SUDO_CWD_INVALID") + nonce = uuid.uuid4().hex[:8] + socket = self.base / ("s-" + hashlib.sha256((session_id + nonce).encode()).hexdigest()[:26] + ".sock") + unit_name = self.prefix + session_id + "-" + nonce + ".service" + marker = self.base / ("p-" + nonce + ".state") + rcfile = self.base / ("p-" + nonce + ".bashrc") + record = {"session_id": session_id, "name": "fb-real-" + session_id, "pane": "", + "tmux_socket": str(socket), "unit_name": unit_name, "mode": mode, + "prompt_marker": str(marker), "rcfile": str(rcfile), "generation": nonce} + def write_private(path, text): + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + with os.fdopen(fd, "w") as stream: + stream.write(text) + quoted = shlex.quote(str(marker)) + rc = ("__fb_tty_busy() { builtin printf '%s\\n' '" + nonce + ":busy' > " + quoted + "; }\n" + "__fb_tty_prompt() { if [[ $(builtin trap -p DEBUG) == \"trap -- '__fb_tty_busy' DEBUG\" ]]; then " + "builtin printf '%s\\n' '" + nonce + ":idle' > " + quoted + "; else " + "builtin printf '%s\\n' '" + nonce + ":busy' > " + quoted + "; fi; }\n" + "set -o functrace\ntrap '__fb_tty_busy' DEBUG\nPROMPT_COMMAND=__fb_tty_prompt\nPS1='\\s-\\v\\$ '\n") + write_private(marker, nonce + ":busy\n") + write_private(rcfile, rc) + identity = pwd.getpwuid(self.uid).pw_name + command = [self.systemd_run, "--user", "--quiet", "--collect", "--unit=" + unit_name, + "--property=Type=exec", "--property=NoNewPrivileges=" + ("yes" if mode == "off" else "no"), + "--property=KillMode=control-group", "--property=TimeoutStopSec=3", + "--working-directory=" + str(self.home)] + for key, value in {"HOME": str(self.home), "USER": identity, "LOGNAME": identity, + "HISTFILE": "/dev/null", "SHELL": "/usr/bin/bash", + "LANG": "C.UTF-8", "TERM": "xterm-256color"}.items(): + command.append("--setenv=" + key + "=" + value) + for key, value in self.session_environment.items(): + command.append("--setenv=" + key + "=" + value) + command += [self.tmux, "-D", "-S", str(socket), "-f", "/dev/null"] + launched = False + try: + launched = True + self._run(command) + deadline = time.monotonic() + self.timeout + while time.monotonic() < deadline: + if socket.exists(): + break + time.sleep(0.05) + else: + _fail("TTY_SUDO_TMUX_NOT_READY") + pane = self._tmux(record, "new-session", "-d", "-s", record["name"], + "-x", cols, "-y", rows, "-c", cwd, "-P", "-F", "#{pane_id}", + shlex.join(["/usr/bin/bash", "--noprofile", "--rcfile", str(rcfile), "-i"])).stdout.decode().strip() + if not re.fullmatch(r"%[0-9]+", pane): + _fail("TTY_SUDO_PANE_METADATA_INVALID") + record["pane"] = pane + self._tmux(record, "set-option", "-p", "-t", pane, "remain-on-exit", "on") + self._tmux(record, "set-option", "-t", record["name"], "status", "off") + # Verify the real process, not merely the requested unit property. + expected = 1 if mode == "off" else 0 + deadline = time.monotonic() + self.timeout + while time.monotonic() < deadline: + current = self._inspect(record) + if current["no_new_privs"] != expected: + _fail("TTY_SUDO_PRIVILEGE_STATE_MISMATCH") + if not current["busy"]: + return current + time.sleep(0.05) + _fail("TTY_SUDO_CONSOLE_NOT_IDLE") + except BaseException: + if launched: + try: + unit = self._unit(unit_name) + proc, _, _, _ = self._pane(record) + scope = self._pane_scope(self._pane_group(proc["pid"]), unit.get("ControlGroup")) + if scope: + self._run([self.systemctl, "--user", "stop", scope], optional=True) + except SudoControlError: + pass + self._run([self.systemctl, "--user", "stop", unit_name], optional=True) + socket.unlink(missing_ok=True) + marker.unlink(missing_ok=True) + rcfile.unlink(missing_ok=True) + raise + + def launch(self, session_id, cwd=None, rows=24, cols=80, mode="off"): + with self._lock: + if session_id in self._records: + _fail("TTY_SUDO_SESSION_EXISTS") + record = self._launch(session_id, cwd or self.home, rows, cols, mode) + self._records[session_id] = record + return dict(record) + + def _terminated(self, record): + # A dead pane still owns its tmux service and may retain detached jobs. + unit = self._unit(record["unit_name"]) + server = _proc(int(unit.get("MainPID", "0"))) + if server and (server["pid"] != record.get("server_pid") + or server["birth"] != record.get("server_birth")): + _fail("TTY_SUDO_UNIT_IDENTITY_CHANGED") + groups = {record.get("control_group"), record.get("pane_control_group")} + if None in groups: + _fail("TTY_SUDO_CGROUP_UNAVAILABLE") + processes = _processes() + members = set() + for group in groups: + if (Path("/sys/fs/cgroup") / group.lstrip("/")).exists(): + members.update(self._scope(group)) + if members - processes.keys(): + _fail("TTY_SUDO_PROCESS_STATE_UNVERIFIED") + if any(any(uid != self.uid for uid in processes[pid]["uids"]) for pid in members): + _fail("TTY_SUDO_ELEVATED_PROCESS_PRESENT") + return {**record, "busy_reason": None} + + def _close(self, record, require_idle=False): + try: + current = self._idle(record) if require_idle else self._inspect(record) + except SudoControlError as error: + if (require_idle or not record.get("unit_name") + or error.code not in {"TTY_SUDO_CONSOLE_EXITED", "TTY_SUDO_PROCESS_NOT_FOUND"}): + raise + current = self._terminated(record) + if current["busy_reason"] in {"TTY_SUDO_ELEVATED_PROCESS_PRESENT", "TTY_SUDO_PROCESS_STATE_UNVERIFIED"}: + _fail(current["busy_reason"]) + if record.get("unit_name"): + if current.get("pane_scope_unit"): + self._run([self.systemctl, "--user", "stop", current["pane_scope_unit"]], optional=True) + self._run([self.systemctl, "--user", "stop", record["unit_name"]], optional=True) + deadline = time.monotonic() + self.timeout + while time.monotonic() < deadline: + remaining = set() + for group in {current["control_group"], current["pane_control_group"]}: + if (Path("/sys/fs/cgroup") / group.lstrip("/")).exists(): + remaining.update(self._scope(group)) + if not remaining and _proc(current["pid"]) is None: + break + time.sleep(0.05) + else: + _fail("TTY_SUDO_SCOPE_STILL_RUNNING") + Path(record["tmux_socket"]).unlink(missing_ok=True) + else: + self._tmux(record, "kill-session", "-t", record["name"]) + deadline = time.monotonic() + self.timeout + while time.monotonic() < deadline: + proc = _proc(current["pid"]) + if proc is None or proc["birth"] != current["birth"]: + break + time.sleep(0.05) + else: + _fail("TTY_SUDO_OLD_CONSOLE_STILL_RUNNING") + for key in ("prompt_marker", "rcfile"): + if record.get(key): + path = Path(record[key]) + if path.parent == self.base: + path.unlink(missing_ok=True) + return {"ok": True, "session_id": record["session_id"], "closed": True, + "root_process_cleanup_claimed": False} + + def close(self, session): + with self._lock: + record = self._refresh_pending(self._resolve(session)) + if record.get("cleanup_pending"): + _fail("TTY_SUDO_TRANSITION_CLEANUP_PENDING") + result = self._close(record) + self._records.pop(record["session_id"], None) + return result + + def set_mode(self, session, mode): + if mode not in {"on", "off"}: + _fail("TTY_SUDO_MODE_INVALID") + with self._lock: + old = self._refresh_pending(self._resolve(session)) + if old.get("cleanup_pending"): + _fail("TTY_SUDO_TRANSITION_CLEANUP_PENDING") + current = self._idle(old) + if old.get("unit_name") and current["mode"] == mode: + return {**current, "recreated": False} + candidate = self._launch(current["session_id"], current["cwd"], + current["rows"], current["cols"], mode) + try: + # The old console remains alive until its replacement is verified. + self._idle(old) + self._close(old, require_idle=True) + except BaseException as error: + previous = _proc(current["pid"]) + if previous and previous["birth"] == current["birth"] and previous["state"] not in {"Z", "X"}: + self._close(candidate) + raise + # The former Bash is gone. Keep the verified replacement and + # retain old ownership metadata until all old scopes are empty. + candidate = {**candidate, "cleanup_pending": True, "transition_complete": False, + "cleanup_error": getattr(error, "code", "TTY_SUDO_OLD_CLEANUP_FAILED"), + "pending_old_record": dict(current)} + self._records[current["session_id"]] = candidate + return {**candidate, "recreated": True, "previous_pid": current["pid"]} + candidate.update(cleanup_pending=False, transition_complete=True) + self._records[current["session_id"]] = candidate + return {**candidate, "recreated": True, "previous_pid": current["pid"]} diff --git a/source/expanded/framebridge_expanded/tunnel.py b/source/expanded/framebridge_expanded/tunnel.py new file mode 100644 index 0000000..94e99b8 --- /dev/null +++ b/source/expanded/framebridge_expanded/tunnel.py @@ -0,0 +1,497 @@ +"""Manual ChatGPT connection using the packaged, unmodified tunnel client.""" +from __future__ import annotations + +from dataclasses import dataclass +import getpass +import os +from pathlib import Path +import re +import shlex +import signal +import subprocess +import sys +import threading +import time +from typing import Callable +from urllib.parse import urlsplit +import warnings + +KEY_ENV = "FRAMEBRIDGE_EXPANDED_RUNTIME_KEY" +_ID = re.compile(r"tunnel_[A-Za-z0-9_-]{1,128}\Z") +_ENV_REF = re.compile(r"env:[A-Za-z_][A-Za-z0-9_]*\Z") +_RUNTIME_PREFIXES = ("TUNNEL_CLIENT_", "CONTROL_PLANE_", "OPENAI_", "MCP_", "HEALTH_", "LOG_", "CLOUDFLARED_", "HARPOON_") +_RUNTIME_NAMES = {"TUNNEL_ID", "TUNNEL_BIN", "MCP_URL", "CRED_FILE", "OPEN_WEB_UI", "PID_FILE", "ALLOW_REMOTE_UI", "ADMIN_UI_LOG_BUFFER_EVENTS", "PROXY_CHECK_INTERVAL", KEY_ENV} + + +class TunnelInputError(ValueError): + """A safe error that never includes the pasted command or credential.""" + + +@dataclass(frozen=True) +class TunnelSpec: + tunnel_id: str + organization_id: str = "" + url_path: str = "" + + def __post_init__(self): + if not isinstance(self.tunnel_id, str) or not _ID.fullmatch(self.tunnel_id): + raise TunnelInputError("A valid tunnel ID is required.") + if not isinstance(self.organization_id, str) or (self.organization_id and not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", self.organization_id)): + raise TunnelInputError("The organization identifier is invalid.") + if not isinstance(self.url_path, str) or (self.url_path and (not self.url_path.startswith("/") or self.url_path.startswith("//") or any(c in self.url_path for c in ("?", "#", "\\", "..")))): + raise TunnelInputError("The control-plane path is invalid.") + + def public_settings(self): + return {"tunnel_id": self.tunnel_id, + "control_plane_organization_id": self.organization_id, + "control_plane_url_path": self.url_path} + + +def parse_tunnel_input(text: str) -> TunnelSpec: + """Extract public setup data; never execute or retain the pasted command.""" + if not isinstance(text, str) or len(text) > 16384: + raise TunnelInputError("Paste a tunnel ID or a supported tunnel-client command.") + raw = text.strip() + if any(c in raw for c in ("\n", "\r", "\x00", ";", "|", "&", "<", ">", "`", "$(", "${", "#")): + raise TunnelInputError("Shell expressions are not accepted. Paste only the tunnel ID or one tunnel-client command.") + try: + words = shlex.split(raw, posix=True) + except ValueError: + raise TunnelInputError("The pasted command has incomplete quoting.") from None + if len(words) == 1 and _ID.fullmatch(words[0]): + return TunnelSpec(words[0]) + if not words: + raise TunnelInputError("A tunnel ID is required.") + program = Path(words.pop(0)).name + if program in {"framebridge-original", "framebridge", "fb"}: + if not words or words.pop(0) != "tunnel": + raise TunnelInputError("Paste a tunnel-client command or only its tunnel ID.") + if words[:1] == ["--"]: + words.pop(0) + elif program != "tunnel-client": + raise TunnelInputError("Only a tunnel-client command or a tunnel ID is accepted.") + if words[:2] == ["runtimes", "connect"]: + mode, words = "connect", words[2:] + elif words[:1] in (["run"], ["init"]): + mode, words = words[0], words[1:] + else: + raise TunnelInputError("Use a run, init, or runtimes connect command containing an explicit tunnel ID.") + options = { + "--control-plane.tunnel-id": "id", "--tunnel-id": "id", + "--control-plane.organization-id": "org", "--organization-id": "org", + "--control-plane.base-url": "base", "--control-plane-base-url": "base", + "--control-plane.url-path": "path", "--control-plane-url-path": "path", + "--control-plane.api-key": "credential", "--runtime-api-key": "credential", + "--control-plane-api-key-ref": "credential", + "--mcp.server-url": "mcp", "--mcp-server-url": "mcp", + "--health.listen-addr": "health", "--health-listen-addr": "health", + "--health.url-file": "health_file", + "--alias": "alias", "--name": "name", "--description": "description", + "--profile": "profile", "--profile-dir": "profile_dir", "--sample": "sample", + } + found = {} + while words: + flag = words.pop(0) + name, equals, value = flag.partition("=") + if name == "--json" and mode == "connect" and not equals: + continue + kind = options.get(name) + if kind is None: + raise TunnelInputError("That command contains an unsupported option. Paste only its tunnel ID instead.") + if not equals: + if not words or words[0].startswith("--"): + raise TunnelInputError("The pasted command has an option without a value.") + value = words.pop(0) + if not value or kind in found: + raise TunnelInputError("The pasted command contains missing or repeated setup values.") + found[kind] = value + identifier = found.get("id", "") + if not _ID.fullmatch(identifier): + raise TunnelInputError("The command must contain one explicit tunnel ID.") + if "credential" in found and not _ENV_REF.fullmatch(found["credential"]): + raise TunnelInputError("Do not include a literal key or a key file. Enter the runtime key at the separate hidden prompt.") + if "base" in found: + u = urlsplit(found["base"]) + if u.scheme != "https" or u.netloc != "api.openai.com" or u.path not in ("", "/") or u.query or u.fragment: + raise TunnelInputError("Use the OpenAI control plane. Paste only the tunnel ID if the command uses another endpoint.") + if "mcp" in found: + hint = found["mcp"] + if hint.startswith("url="): + hint = hint[4:].split(",", 1)[0] + u = urlsplit(hint) + if u.scheme not in {"http", "https"} or not u.hostname or u.username or u.password or u.query or u.fragment: + raise TunnelInputError("The copied MCP URL cannot contain credentials, a query, or a fragment.") + org = found.get("org", "") + if org and not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", org): + raise TunnelInputError("The organization identifier is invalid.") + path = found.get("path", "") + if path and (not path.startswith("/") or path.startswith("//") or any(c in path for c in ("?", "#", "\\", ".."))): + raise TunnelInputError("The control-plane path must be a plain absolute URL path.") + return TunnelSpec(identifier, org, path) + + +def child_environment(secret: str, base=None, *, state=None): + """Return a new child environment; never change os.environ.""" + env = dict(os.environ if base is None else base) + for key in list(env): + if key.startswith(_RUNTIME_PREFIXES) or key in _RUNTIME_NAMES: + env.pop(key, None) + if state is not None: + from .config import private_dir + home = private_dir(Path(state) / "chatgpt-home") + for name in (".config", ".cache", ".local/state", ".run", "tmp"): + private_dir(home / name) + env.update({"HOME": str(home), "XDG_CONFIG_HOME": str(home / ".config"), + "XDG_CACHE_HOME": str(home / ".cache"), "XDG_STATE_HOME": str(home / ".local/state"), + "XDG_RUNTIME_DIR": str(home / ".run"), "TMPDIR": str(home / "tmp")}) + env[KEY_ENV] = secret + return env + + +def build_command(binary, state, mcp_url, spec: TunnelSpec): + u = urlsplit(mcp_url) + if u.scheme != "http" or u.hostname not in {"localhost", "127.0.0.1", "::1"} or u.username or u.password or u.query or u.fragment: + raise TunnelInputError("The application MCP endpoint must be local HTTP without credentials.") + args = [str(binary), "run", + "--control-plane.base-url", "https://api.openai.com", + "--control-plane.tunnel-id", spec.tunnel_id, + "--control-plane.api-key", "env:" + KEY_ENV, + "--mcp.server-url", "url=" + mcp_url, + "--health.listen-addr", "127.0.0.1:0", + "--log.level", "info"] + if spec.organization_id: + args.extend(["--control-plane.organization-id", spec.organization_id]) + if spec.url_path: + args.extend(["--control-plane.url-path", spec.url_path]) + return args + + +def _stop_owned(process): + if process.poll() is not None: + return + for sig, seconds in ((signal.SIGINT, 5), (signal.SIGTERM, 2), (signal.SIGKILL, 2)): + try: + os.killpg(process.pid, sig) + except ProcessLookupError: + return + try: + process.wait(timeout=seconds) + return + except subprocess.TimeoutExpired: + pass + + +def run_foreground(command, secret, *, state=None, output=None, popen=None, observe=None): + """Keep the owned client in the foreground, with private logs and status.""" + from . import connection_monitor as monitor + output = output or sys.stdout + popen = popen or subprocess.Popen + progress = monitor.Progress(output) + process = reader = log = attempt = None + reader_errors = [] + stop_reader = threading.Event() + env = {} + previous = {} + ready_once = False + last_detail = None + def interrupt(signum, frame): + raise KeyboardInterrupt + try: + if state is None: + raise monitor.ConnectionMonitorError("A private profile is required for connection logs.") + log = monitor.PrivateLog(state, secret) + attempt = monitor.Attempt(state) + args = list(command) + # Never reuse an endpoint file from an earlier connection attempt. + while "--health.url-file" in args: + index = args.index("--health.url-file") + del args[index:index + 2] + args.extend(["--health.url-file", str(attempt.health_file)]) + expected_tunnel = args[args.index("--control-plane.tunnel-id") + 1] + expected_mcp = args[args.index("--mcp.server-url") + 1] + if expected_mcp.startswith("url="): + expected_mcp = expected_mcp[4:] + env = child_environment(secret, state=state) + for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): + previous[sig] = signal.signal(sig, interrupt) + progress.change("starting", "[1/3] Starting the secure tunnel client.") + output.write("Private connection log: " + str(log.path) + "\n") + output.flush() + started = time.time() + process = popen(args, env=env, stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + start_new_session=True, bufsize=0, umask=0o077) + env.pop(KEY_ENV, None) + log.write("Connection attempt started; tunnel=" + expected_tunnel + "; child_pid=" + str(process.pid)) + reader = threading.Thread(target=monitor.capture_output, + args=(process.stdout, log, reader_errors, stop_reader), daemon=True) + reader.start() + next_probe = 0.0 + waiting_since = time.monotonic() + instructions_shown = False + while True: + rc = process.poll() + if rc is not None: + log.write("Tunnel client exited; code=" + str(rc)) + if not ready_once: + progress.change("error", "[ERROR] The tunnel stopped before it was ready. Check the private connection log.") + return rc if rc > 0 else 1 + progress.change("closed" if rc == 0 else "error", + "[CLOSED] The tunnel client stopped." if rc == 0 else + "[ERROR] The tunnel connection stopped unexpectedly. Check the private connection log.") + return rc if rc >= 0 else 1 + if reader_errors: + progress.change("error", "[ERROR] Private connection logging failed. The tunnel will be stopped.") + return 1 + if time.monotonic() >= next_probe: + status, detail = monitor.probe(attempt.health_file, expected_tunnel, expected_mcp, started) + if observe is not None: + observe(status) + next_probe = time.monotonic() + 1.0 + if detail != last_detail: + log.write("Connection check: " + detail) + last_detail = detail + if process.poll() is not None: + continue + messages = { + "starting": "[1/3] Waiting for the tunnel client to start.", + "checking": "[2/3] Verifying the local MCP service.", + "access": "[3/3] Verifying tunnel access and authenticated polling.", + "ready": "[OK] Secure tunnel connected; local MCP service verified.", + "rejected": "[ERROR] Tunnel access was rejected. Press Ctrl+C and check the runtime key and tunnel permissions.", + } + if status == "ready": + ready_once = True + progress.change(status, messages[status]) + if not instructions_shown: + output.write("Complete or select this tunnel in ChatGPT. A successful tool call verifies that connection.\n") + output.write("Keep this terminal open. Press Ctrl+C to disconnect.\n") + output.flush() + instructions_shown = True + elif ready_once: + progress.change("retrying", "[RETRYING] Tunnel readiness was lost. Waiting for verified recovery; see the private log.") + elif status == "rejected": + progress.change(status, messages[status]) + elif time.monotonic() - waiting_since >= 90: + progress.change("unverified", "[NOT READY] The connection is not verified yet. The client is retrying; check the private log or press Ctrl+C.") + else: + progress.change(status, messages[status]) + progress.tick() + time.sleep(0.2) + except KeyboardInterrupt: + progress.change("closed", "[STOPPED] Disconnecting this tunnel client.") + return 130 + except Exception as error: + if log is not None: + try: + log.write("Connection wrapper failed; type=" + type(error).__name__ + "; detail=" + str(error)) + except Exception: + pass + progress.change("error", "[ERROR] The connection could not be started or monitored. Check the private connection log and profile permissions.") + return 1 + finally: + cleanup_failed = False + for sig in previous: + try: + signal.signal(sig, signal.SIG_IGN) + except Exception: + cleanup_failed = True + try: + if process is not None: + try: + _stop_owned(process) + if process.poll() is None: + cleanup_failed = True + except Exception: + cleanup_failed = True + finally: + stop_reader.set() + if reader is not None: + reader.join(timeout=3) + reader_finished = reader is None or not reader.is_alive() + if not reader_finished or reader_errors: + cleanup_failed = True + try: + if process is not None and process.stdout is not None and reader_finished: + process.stdout.close() + except Exception: + cleanup_failed = True + try: + progress.clear() + if attempt is not None: + attempt.close() + except Exception: + cleanup_failed = True + finally: + try: + # Never close descriptors out from under an active writer. + if log is not None and reader_finished: + log.close() + except Exception: + cleanup_failed = True + finally: + env.pop(KEY_ENV, None) + secret = None + for sig, handler in previous.items(): + try: + signal.signal(sig, handler) + except Exception: + cleanup_failed = True + if cleanup_failed: + progress.change("error", "[ERROR] Connection cleanup could not be fully verified. Check this client process before reconnecting.") + return 1 + + +def _hidden(prompt): + if not sys.stdin.isatty(): + raise TunnelInputError("Use an interactive terminal for the hidden setup prompts.") + with warnings.catch_warnings(): + warnings.simplefilter("error", getpass.GetPassWarning) + try: + return getpass.getpass(prompt) + except getpass.GetPassWarning: + raise TunnelInputError("A terminal with hidden input is required.") from None + + +def _public_answer(label, saved=""): + suffix = " [" + saved + "]" if saved else "" + return input(label + suffix + ": ").strip() or saved + + +def _wait_managed_connection(state, current, output, *, wait_timeout=95): + from . import autostart + from .connection_monitor import Progress + progress = Progress(output) + deadline = time.monotonic() + max(0, wait_timeout) + try: + while True: + if current.get("interrupted_after_commit"): + progress.change("closed", "Connection continues in the background.") + return 130 + connection = current.get("connection", {}) + local = current.get("runtime", {}) + if (current.get("configured") and current.get("enabled") + and current.get("service_active") and current.get("service_state") == "active" + and current.get("worker", {}).get("state") != "failed" + and local.get("running") and local.get("ready") + and connection.get("alive") and connection.get("verified")): + progress.change("ready", "[OK] Connected.") + return 0 + if (current.get("service_state") == "failed" + or current.get("worker", {}).get("state") == "failed" + or connection.get("state") in {"rejected", "error"}): + progress.change("error", "[ERROR] Connection failed. Run fb connection status for details.") + return 1 + if not current.get("configured") or not current.get("enabled"): + progress.change("error", "[ERROR] Connection setup could not be completed.") + return 1 + if time.monotonic() >= deadline: + progress.change("unverified", "[PENDING] Connection is still being established in the background.") + return 1 + progress.tick() + time.sleep(0.2) + current = autostart.status(state) + finally: + progress.clear() + + +def resume_managed_connection(state, resume_connection, *, output=None, wait_timeout=95): + output = output or sys.stdout + resumed = False + try: + output.write("Connecting...\n") + output.flush() + current = resume_connection(state) + resumed = bool(current.get("configured") and current.get("enabled")) + return _wait_managed_connection(state, current, output, wait_timeout=wait_timeout) + except (EOFError, KeyboardInterrupt): + output.write("\nConnection continues in the background.\n" if resumed else "\nConnection cancelled.\n") + return 130 + + +def _connect_managed(state, public_settings, configure_connection, output, *, wait_timeout=95): + if not sys.stdin.isatty(): + raise TunnelInputError("Use an interactive terminal for connection setup.") + runtime_key = None + committed = False + try: + saved_label = public_settings.get("account_label", "") + if saved_label and not re.fullmatch(r"[^\s@]+@[^\s@]+\.[^\s@]+", saved_label): + saved_label = "" + label = _public_answer("ChatGPT account (email label)", saved_label) + if label and not re.fullmatch(r"[^\s@]+@[^\s@]+\.[^\s@]+", label): + raise TunnelInputError("Enter an email label or leave it empty.") + saved_id = public_settings.get("tunnel_id", "") + if not _ID.fullmatch(saved_id): + saved_id = "" + raw = _hidden("Tunnel ID (hidden; Enter reuses saved ID): ") + tunnel_id = raw.strip() or saved_id + if not _ID.fullmatch(tunnel_id): + raise TunnelInputError("Enter only a valid tunnel ID beginning with tunnel_.") + spec = TunnelSpec(tunnel_id) + reuse_saved = tunnel_id == saved_id + raw = None + if reuse_saved: + spec = TunnelSpec(spec.tunnel_id, public_settings.get("control_plane_organization_id", ""), public_settings.get("control_plane_url_path", "")) + runtime_key = _hidden("MCP server access key (hidden): ") + if (not runtime_key or not runtime_key.isascii() or len(runtime_key.encode()) > 16384 + or any(ord(ch) < 33 or ord(ch) == 127 or ch.isspace() for ch in runtime_key)): + raise TunnelInputError("Enter a single nonempty MCP server access key.") + settings = dict(spec.public_settings(), account_label=label) + output.write("Connecting...\n") + output.flush() + current = configure_connection(state, settings, runtime_key) + runtime_key = None + committed = bool(current.get("configured") and current.get("enabled")) + return _wait_managed_connection(state, current, output, wait_timeout=wait_timeout) + except (EOFError, KeyboardInterrupt): + output.write("\nConnection continues in the background.\n" if committed else "\nConnection cancelled.\n") + return 130 + finally: + runtime_key = None + + +def connect_chatgpt(binary, state, mcp_url, public_settings=None, + save_public: Callable | None = None, *, output=None, foreground=False, + configure_connection: Callable | None = None, wait_timeout=95): + """Connect with one hidden key; normal setup uses protected Linux services.""" + output = output or sys.stdout + public_settings = dict(public_settings or {}) + if configure_connection is not None and not foreground: + return _connect_managed(state, public_settings, configure_connection, output, wait_timeout=wait_timeout) + from . import connection_background + current = connection_background.status(state) + if current["alive"]: + output.write("This profile already has a background ChatGPT connection. Use framebridge connection status.\n") + return 0 if current["verified"] else 1 + label = public_settings.get("account_label", "") + output.write("ChatGPT login happens in your browser. The saved email is only an account label.\n") + if label: + output.write("An account label is saved for this profile.\n") + output.write("Create or select your tunnel manually in OpenAI Platform.\n") + output.write("The runtime key needs Tunnels Read + Use. No Admin API key is needed here.\n") + output.write("The command is parsed as data; this profile's local MCP server will be used.\n") + secret = None + try: + raw = _hidden("Tunnel ID or full tunnel-client command (hidden; Enter reuses the saved ID): ") + reuse_saved = not raw.strip() + if reuse_saved: + raw = public_settings.get("tunnel_id", "") + spec = parse_tunnel_input(raw) + if reuse_saved: + spec = TunnelSpec(spec.tunnel_id, public_settings.get("control_plane_organization_id", ""), public_settings.get("control_plane_url_path", "")) + raw = None + if save_public: + save_public(spec.public_settings()) + secret = _hidden("Runtime API key (hidden; kept in memory for this connection): ").strip() + if not secret or any(c.isspace() for c in secret) or "\x00" in secret: + raise TunnelInputError("Enter a single nonempty runtime key.") + command = build_command(binary, state, mcp_url, spec) + if foreground: + return connection_background.run_foreground(command, secret, state, output=output) + return connection_background.start(command, secret, state, output=output) + except (EOFError, KeyboardInterrupt): + output.write("\nConnection cancelled.\n") + return 130 + finally: + secret = None diff --git a/source/expanded/framebridge_expanded/uninstall.py b/source/expanded/framebridge_expanded/uninstall.py new file mode 100644 index 0000000..2f2a1e0 --- /dev/null +++ b/source/expanded/framebridge_expanded/uninstall.py @@ -0,0 +1,146 @@ +"""Stop only this package's managed user services before Debian removal.""" +from pathlib import Path +import hashlib +import os +import pwd +import re +import shlex +import stat +import subprocess +import sys + +MODULE_ROOT = "/usr/lib/framebridge-expanded/expanded" +UNIT_NAME = re.compile(r"framebridge-autostart-[0-9a-f]{16}\.service\Z") + + +def managed_unit(path, uid): + """Inspect a bounded owned regular file; never execute user configuration.""" + if not UNIT_NAME.fullmatch(path.name): + return False + try: + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + info = os.fstat(fd) + if not stat.S_ISREG(info.st_mode) or info.st_uid != uid or info.st_size > 16384: + return False + raw = os.read(fd, 16385) + finally: + os.close(fd) + lines = raw.decode("utf-8").splitlines() + if not lines or lines[0] != "# FrameBridge managed autostart v1": + return False + if lines.count('Environment="PYTHONPATH=' + MODULE_ROOT + '"') != 1: + return False + starts = [line[10:] for line in lines if line.startswith("ExecStart=")] + if len(starts) != 1: + return False + args = shlex.split(starts[0]) + if len(args) != 7 or args[0] not in {"/usr/bin/python3", "/usr/bin/python3.13"}: + return False + if args[1:6] != ["-B", "-m", "framebridge_expanded.autostart", "_run", "--state"]: + return False + state = args[6].replace("%%", "%").replace("$$", "$") + if not state.startswith("/") or any(ord(c) < 32 for c in state): + return False + expected = "framebridge-autostart-" + hashlib.sha256(state.encode()).hexdigest()[:16] + ".service" + return path.name == expected + except (OSError, UnicodeError, ValueError): + return False + + +def cleanup_user(home=None, *, run=None, bus_available=None): + run = run or subprocess.run + home = Path(home) if home is not None else Path.home() + uid = os.getuid() + directory = home / ".config/systemd/user" + if not directory.is_dir(): + return 0 + units = sorted(p.name for p in directory.glob("framebridge-autostart-*.service") if managed_unit(p, uid)) + if not units: + return 0 + runtime = Path("/run/user") / str(uid) + if bus_available is None: + try: + bus_available = stat.S_ISSOCK((runtime / "bus").stat().st_mode) + except OSError: + bus_available = False + env = {"HOME": str(home), "PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "XDG_CONFIG_HOME": str(home / ".config"), "XDG_RUNTIME_DIR": str(runtime), "DBUS_SESSION_BUS_ADDRESS": "unix:path=" + str(runtime / "bus")} + failure = False + if bus_available: + try: + result = run(["/usr/bin/systemctl", "--user", "stop", *units], env=env, capture_output=True, timeout=35) + if result.returncode: + current = run(["/usr/bin/systemctl", "--user", "show", *units, "--property=ActiveState", "--value"], env=env, capture_output=True, timeout=5) + values = current.stdout.decode().split() if isinstance(current.stdout, bytes) else current.stdout.split() + failure = current.returncode != 0 or len(values) != len(units) or any(value not in {"inactive", "failed"} for value in values) + except (OSError, subprocess.TimeoutExpired): + failure = True + try: + result = run(["/usr/bin/systemctl", "--user", "--no-reload", "disable", *units], env=env, capture_output=True, timeout=10) + failure = result.returncode != 0 or failure + except (OSError, subprocess.TimeoutExpired): + failure = True + # Remove only enablement symlinks targeting these exact managed files. + # This also works offline: systemctl can delegate file operations to a live + # user manager with a different environment, and no bus exists at all for + # an offline owner. Never delete profile data, ciphertext, or unit files. + for group in directory.iterdir(): + if not group.name.endswith((".wants", ".requires")) or group.is_symlink() or not group.is_dir(): + continue + for name in units: + link = group / name + if link.is_symlink(): + try: + if link.resolve() == (directory / name).resolve(): + link.unlink() + except OSError: + failure = True + if bus_available: + try: + run(["/usr/bin/systemctl", "--user", "daemon-reload"], env=env, capture_output=True, timeout=3) + except (OSError, subprocess.TimeoutExpired): + pass # Units are stopped/disabled; an owner may have just logged out. + if failure: + raise RuntimeError("A managed FrameBridge service could not be stopped or disabled.") + return len(units) + + +def cleanup_all_users(*, run=None, accounts=None): + if os.getuid() != 0: + raise RuntimeError("Package service cleanup requires the normal package manager's root context.") + run = run or subprocess.run + accounts = pwd.getpwall() if accounts is None else accounts + seen = set() + failed = False + for account in accounts: + identity = (account.pw_uid, account.pw_dir) + if identity in seen or account.pw_uid == 65534 or not account.pw_dir.startswith("/") or account.pw_dir in {"/", "/nonexistent", "/dev/null"}: + continue + seen.add(identity) + command = ["/usr/sbin/runuser", "-u", account.pw_name, "--", "/usr/bin/env", "-i", "HOME=" + account.pw_dir, "USER=" + account.pw_name, "LOGNAME=" + account.pw_name, "PATH=/usr/bin:/bin", "LANG=C.UTF-8", "/usr/bin/python3", "-I", "-S", str(Path(__file__).resolve()), "--user"] + try: + result = run(command, capture_output=True, timeout=55) + failed = result.returncode != 0 or failed + except (OSError, subprocess.TimeoutExpired): + failed = True + if failed: + raise RuntimeError("Some FrameBridge services could not be stopped; package removal was paused.") + + +def main(argv=None): + args = list(sys.argv[1:] if argv is None else argv) + try: + if args == ["--all-users"]: + cleanup_all_users() + elif args == ["--user"]: + cleanup_user() + else: + return 2 + return 0 + except (OSError, RuntimeError, subprocess.TimeoutExpired): + print("FrameBridge service cleanup did not complete. Package data was retained.", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/source/packaging/70-framebridge-screen.rules b/source/packaging/70-framebridge-screen.rules new file mode 100644 index 0000000..cbae9c2 --- /dev/null +++ b/source/packaging/70-framebridge-screen.rules @@ -0,0 +1,3 @@ +# Inactive until an administrator explicitly enables a named user. +ACTION=="add", SUBSYSTEM=="graphics", KERNEL=="fb0", TEST=="/etc/framebridge-expanded/screen-access.json", RUN+="/usr/lib/framebridge-expanded/bin/framebridge-screen-access apply-device" +ACTION=="change", SUBSYSTEM=="graphics", KERNEL=="fb0", TEST=="/etc/framebridge-expanded/screen-access.json", RUN+="/usr/lib/framebridge-expanded/bin/framebridge-screen-access apply-device" diff --git a/source/packaging/archive_sources.py b/source/packaging/archive_sources.py new file mode 100755 index 0000000..86aedac --- /dev/null +++ b/source/packaging/archive_sources.py @@ -0,0 +1,118 @@ +#!/usr/bin/python3 +"""Archive the reviewed community source layout without runtime profiles or QA logs.""" +import calendar +import gzip +import hashlib +import io +import json +from pathlib import Path +import tarfile + +BASE = Path(__file__).resolve().parents[1] +ROOT = BASE.parent +EPOCH = calendar.timegm((2026, 10, 26, 0, 0, 0)) +ARCHIVE_ROOT = "FrameBridge-0.3.1" +ARCHIVE_NAME = "FrameBridge-0.3.1-source.tar.gz" +SOURCE_FOLDERS = ("sources", "expanded", "portable", "packaging", "docs", "tests") +ROOT_FILES = ("README.md", "LICENSE", "LICENSING.md", "COPYRIGHT.md", + "LICENSE-STATUS.md", "OFFICIAL-RELEASES.md", "CONTRIBUTING.md", "SECURITY.md", "RELEASE-NOTES.md") +ROOT_FOLDERS = ("docs", "provenance", "licensing") +BLOG_POLICY_FILES = ("README.md", "LICENSING.md", "POST-TEMPLATE.md", "LICENSE-CC-BY-SA-4.0.txt") +ARCHIVE_README_NAME = "README-SOURCE-ARCHIVE.md" +DEPENDENCY_SOURCE_ASSETS = [{'filename': 'FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz', 'bytes': 82606998, 'sha256': '64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a'}, {'filename': 'FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz', 'bytes': 26941745, 'sha256': '6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9'}] +ARCHIVE_README = "# FrameBridge 0.3.1 source archive\n\nThis archive preserves the repository layout needed by the offline builder.\nFrom this directory, run python3 source/packaging/build_expanded.py to build\nFrameBridge-0.3.1.deb. The builder writes source/build and source/release;\nit does not install or start FrameBridge. Debian's dpkg-deb and the documented\nbuild prerequisites must already be available.\n\nOriginal FrameBridge material is GNU GPL version 3 only. Read LICENSE,\nLICENSING.md, COPYRIGHT.md, LICENSE-STATUS.md and OFFICIAL-RELEASES.md for\nscope, attribution, third-party terms and the official-release trust model.\n\nThe project source asset is accompanied by these separate dependency assets:\n\n- FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz\n 82,606,998 bytes\n SHA-256 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a\n- FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz\n 26,941,745 bytes\n SHA-256 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9\n\nThe Preview1 filename deliberately preserves the identity of an immutable\nshared asset. Retain licensing/third-party/R2-SUPPLEMENTS.json and its referenced\nsource notices with the dependency sources. The source indices and\nSOURCE-AVAILABILITY.md there record scope and provenance. Do not substitute\nunrelated archives. The retained tunnel executable is not claimed to have been\nindependently rebuilt from an identified own-source revision.\n\nThe blog directory contains licensing policies and the post template.\nEditorial article drafts are omitted because they are not source build inputs.\n\nOnly source/evidence/BASELINE.json is included from technical evidence. Runtime\nprofiles, private QA logs, generated build/release files and Git history are\noutside the source archive allowlist.\n\nA completed archive or builder run is not a declaration of public readiness.\nRelease checksums, signatures and publication status are recorded separately.\n".encode("utf-8") +GPL_SHA256 = "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986" + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + +def source_files(): + selected = {} + + def add(path): + relative = path.relative_to(ROOT) + if path.is_symlink(): + raise RuntimeError("Source archives must not include symbolic links: " + str(relative)) + if "__pycache__" in relative.parts or path.suffix == ".pyc": + return + if path.is_file(): + selected[relative.as_posix()] = path + + for folder in SOURCE_FOLDERS: + directory = BASE / folder + if not directory.is_dir() or directory.is_symlink(): + raise RuntimeError("Required source directory missing: " + folder) + for path in directory.rglob("*"): + add(path) + # The builder needs this input; no other evidence, QA log, or diff is selected. + baseline = BASE / "evidence/BASELINE.json" + if not baseline.is_file(): + raise RuntimeError("Required build baseline missing.") + add(baseline) + for name in ROOT_FILES: + path = ROOT / name + if not path.is_file(): + raise RuntimeError("Required repository input missing: " + name) + add(path) + for name in ROOT_FOLDERS: + directory = ROOT / name + if not directory.is_dir() or directory.is_symlink(): + raise RuntimeError("Required repository directory missing: " + name) + for path in directory.rglob("*"): + add(path) + # Preserve policy references, but do not publish draft blog articles implicitly. + for name in BLOG_POLICY_FILES: + path = ROOT / "blog" / name + if not path.is_file(): + raise RuntimeError("Required blog licensing policy input missing: " + name) + add(path) + if sha(ROOT / "LICENSE") != GPL_SHA256: + raise RuntimeError("The GPLv3 text differs from the recorded official license.") + required_notices = ROOT / "licensing/third-party" + for name in ("THIRD-PARTY-NOTICES.md", "DEPENDENCY-SOURCES.json", "SOURCE-AVAILABILITY.md", "R2-SUPPLEMENTS.json", "NATIVE-DEPENDENCY-TERMS.json", "NATIVE-DEPENDENCY-TERMS.md", "TUNNEL-DEPENDENCY-SOURCES.json"): + path = required_notices / name + if not path.is_file() or not path.stat().st_size: + raise RuntimeError("Required third-party source/notice input missing: " + name) + return selected + +def main(): + names = source_files() + release = BASE / "release" + release.mkdir(exist_ok=True) + output = release / ARCHIVE_NAME + with output.open("wb") as raw: + with gzip.GzipFile(filename="", mode="wb", fileobj=raw, + mtime=EPOCH, compresslevel=6) as zipped: + with tarfile.open(fileobj=zipped, mode="w", format=tarfile.GNU_FORMAT) as archive: + info = tarfile.TarInfo(ARCHIVE_ROOT + "/" + ARCHIVE_README_NAME) + info.uid = info.gid = 0 + info.uname = info.gname = "" + info.mtime = EPOCH + info.mode = 0o644 + info.size = len(ARCHIVE_README) + archive.addfile(info, io.BytesIO(ARCHIVE_README)) + for relative, path in sorted(names.items()): + info = archive.gettarinfo(str(path), arcname=ARCHIVE_ROOT + "/" + relative) + info.uid = info.gid = 0 + info.uname = info.gname = "" + info.mtime = EPOCH + info.mode = 0o755 if path.stat().st_mode & 0o111 else 0o644 + with path.open("rb") as stream: + archive.addfile(info, stream) + manifest = {"status": "COMMUNITY_RELEASE_BUILD", "archive": output.name, + "bytes": output.stat().st_size, "sha256": sha(output), + "files": [{"path": ARCHIVE_README_NAME, + "sha256": hashlib.sha256(ARCHIVE_README).hexdigest(), "generated": True}] + + [{"path": relative, "sha256": sha(path)} + for relative, path in sorted(names.items())], + "draft_blog_articles_included": False, + "evidence_allowlist": ["source/evidence/BASELINE.json"], + "dependency_sources": DEPENDENCY_SOURCE_ASSETS[0]["filename"], + "dependency_source_archives": DEPENDENCY_SOURCE_ASSETS} + (release / "SOURCE-ARCHIVE-MANIFEST.json").write_text(json.dumps(manifest, indent=2) + "\n") + print(json.dumps({"archive": str(output), "bytes": output.stat().st_size, + "sha256": manifest["sha256"], "files": len(names) + 1, + "status": manifest["status"]})) + +if __name__ == "__main__": + main() diff --git a/source/packaging/build_expanded.py b/source/packaging/build_expanded.py new file mode 100755 index 0000000..83e0db9 --- /dev/null +++ b/source/packaging/build_expanded.py @@ -0,0 +1,241 @@ +#!/usr/bin/python3 +"""Reproducible Debian package builder; never installs or starts the payload.""" +import calendar +import gzip +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess + +BASE = Path(__file__).resolve().parents[1] +ROOT = BASE.parent +VERSION = "0.3.1" +PUBLIC_FILENAME = "FrameBridge-0.3.1.deb" +GPL_SHA256 = "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986" +EPOCH = calendar.timegm((2026, 10, 26, 0, 0, 0)) +PACKAGE = "framebridge-original-expanded" +RELEASE = BASE / "release" +STAGE = BASE / "build/package-root" +PREFIX = STAGE / "usr/lib/framebridge-expanded" + +DEPENDENCY_SOURCE_ASSETS = [{'filename': 'FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz', 'bytes': 82606998, 'sha256': '64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a'}, {'filename': 'FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz', 'bytes': 26941745, 'sha256': '6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9'}] + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +# These are release inputs, not files inferred from installed runtime profiles. +LEGAL_FILES = ("LICENSE", "LICENSING.md", "COPYRIGHT.md", "LICENSE-STATUS.md", "OFFICIAL-RELEASES.md") +THIRD_PARTY_REQUIRED = ("THIRD-PARTY-NOTICES.md", "DEPENDENCY-SOURCES.json", "SOURCE-AVAILABILITY.md", "R2-SUPPLEMENTS.json", "NATIVE-DEPENDENCY-TERMS.json", "NATIVE-DEPENDENCY-TERMS.md", "TUNNEL-DEPENDENCY-SOURCES.json") +BLOG_POLICY_FILES = ("README.md", "LICENSING.md", "POST-TEMPLATE.md", "LICENSE-CC-BY-SA-4.0.txt") + +def regular_files(directory): + for path in sorted(directory.rglob("*")): + if path.is_symlink(): + raise RuntimeError("Legal input must not be a symbolic link: " + str(path.relative_to(ROOT))) + if path.is_file(): + yield path + +def legal_preflight(): + for relative in LEGAL_FILES: + path = ROOT / relative + if not path.is_file() or path.is_symlink(): + raise RuntimeError("Required project legal input missing: " + relative) + if sha(ROOT / "LICENSE") != GPL_SHA256: + raise RuntimeError("The GPLv3 legal text differs from the recorded official text.") + directory = ROOT / "licensing/third-party" + for name in THIRD_PARTY_REQUIRED: + path = directory / name + if not path.is_file() or path.is_symlink() or not path.stat().st_size: + raise RuntimeError("Required third-party legal input missing: licensing/third-party/" + name) + index = json.loads((directory / "DEPENDENCY-SOURCES.json").read_text()) + if not isinstance(index, (dict, list)): + raise RuntimeError("Dependency source index must be a JSON object or array.") + if not isinstance(index, dict) or index.get("archive") != DEPENDENCY_SOURCE_ASSETS[0]: + raise RuntimeError("The immutable Python/Rust dependency source identity changed.") + native = json.loads((directory / "TUNNEL-DEPENDENCY-SOURCES.json").read_text()) + expected = DEPENDENCY_SOURCE_ASSETS[1] + if (native.get("archive"), native.get("size_bytes"), native.get("sha256")) != (expected["filename"], expected["bytes"], expected["sha256"]): + raise RuntimeError("The tunnel dependency source identity changed.") + licenses = directory / "licenses" + if not licenses.is_dir() or not any(regular_files(licenses)): + raise RuntimeError("Third-party license texts are missing.") + supplement = licenses / "wit-bindgen-rt-0.39.0-LICENSE-MIT.txt" + if not supplement.is_file() or sha(supplement) != "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3": + raise RuntimeError("The pinned wit-bindgen source notice supplement is missing or changed.") + for name in BLOG_POLICY_FILES: + if not (ROOT / "blog" / name).is_file(): + raise RuntimeError("Required referenced blog policy input missing: blog/" + name) + # Do not accidentally ship local workstation paths in newly assembled notices. + private_roots = (str(ROOT.resolve()), str(BASE.resolve()), str(Path.home())) + inputs = [ROOT / name for name in LEGAL_FILES] + list(regular_files(ROOT / "licensing")) + for path in inputs: + data = path.read_bytes() + for private_root in private_roots: + if private_root.encode() in data: + raise RuntimeError("Local path found in legal input: " + str(path.relative_to(ROOT))) + return index + +def install_legal_material(docs): + for relative in LEGAL_FILES: + shutil.copy2(ROOT / relative, docs / relative) + for directory in ("licensing", "docs", "provenance"): + source = ROOT / directory + for path in regular_files(source): + destination = docs / directory / path.relative_to(source) + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, destination) + (docs / "blog").mkdir() + for name in BLOG_POLICY_FILES: + shutil.copy2(ROOT / "blog" / name, docs / "blog" / name) + shutil.copy2(ROOT / "RELEASE-NOTES.md", docs / "RELEASE-NOTES.md") + (docs / "copyright").write_text( + "FrameBridge Original Expanded — Community Release 0.3.1\n\n" + "Original FrameBridge material: GNU GPL version 3 only (GPL-3.0-only).\n" + "The complete license is included in LICENSE. COPYRIGHT.md and LICENSING.md\n" + "identify attribution, scope and third-party exceptions.\n\n" + "Third-party material retains its applicable copyrights and terms.\n" + "See licensing/third-party/THIRD-PARTY-NOTICES.md, retained native terms\n" + "and the original notices alongside vendored components.\n\n" + "Official release signatures and public readiness are recorded separately.\n") + (docs / "THIRD-PARTY-NOTICES.md").write_text( + "# Included third-party notices\n\n" + "The assembled notices and license texts are included under\n" + "[licensing/third-party](licensing/third-party/THIRD-PARTY-NOTICES.md).\n" + "Original vendored notices are preserved with their components.\n") + lines = ["# Source availability for FrameBridge 0.3.1", "", + "Project source archive: FrameBridge-0.3.1-source.tar.gz.", "", + "The release source set also includes these separate assets:", ""] + for item in DEPENDENCY_SOURCE_ASSETS: + lines.extend(["- " + item["filename"], + " Bytes: " + str(item["bytes"]), + " SHA-256: " + item["sha256"]]) + lines.extend(["", "The Preview1 name deliberately identifies an immutable shared asset.", + "Retain the R2 source-notice supplement under licensing/third-party.", + "The tunnel dependency asset records exact dependency archives and", + "retained notices; it does not assert an independently rebuilt tunnel", + "executable or an identified exact own-source revision.", "", + "See licensing/third-party/SOURCE-AVAILABILITY.md and both dependency", + "source indices for scope and provenance. Publication readiness and", + "signature status are external release records, not builder claims.", ""]) + (docs / "SOURCE-AVAILABILITY.md").write_text("\n".join(lines)) + (docs / "COMMUNITY-RELEASE-STATUS.json").write_text(json.dumps({ + "schema_version": 1, "version": VERSION, "status": "COMMUNITY_RELEASE_BUILD", + "certified_as_RC7": False, + "public_readiness": "EXTERNAL_RELEASE_RECORD", + "signature_status": "EXTERNAL_RELEASE_RECORD", + "vm_lifecycle_validation": "NOT_ASSERTED_BY_BUILDER", + "source_archive": "FrameBridge-0.3.1-source.tar.gz", + "dependency_source_archives": DEPENDENCY_SOURCE_ASSETS + }, indent=2) + "\n") + +def main(): + legal_preflight() + baseline = json.loads((BASE / "evidence/BASELINE.json").read_text()) + for relative, expected in baseline["source_hashes"].items(): + assert sha(BASE / "sources" / relative) == expected, "Original source changed: " + relative + if STAGE.exists(): + shutil.rmtree(STAGE) + PREFIX.mkdir(parents=True) + RELEASE.mkdir(exist_ok=True) + for name, dest in [("original-asus", "asus"), ("original-geranas", "geranas"), ("original-gerameg", "gerameg")]: + source = BASE / "sources" / name + assert source.is_dir(), "Required public source snapshot missing: " + name + shutil.copytree(source, PREFIX / "original" / dest, ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) + for source in ("portable", "expanded", "docs"): + shutil.copytree(BASE / source, PREFIX / source, ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) + (PREFIX / "bin").mkdir() + for name in ("framebridge", "framebridge-original", "framebridge-native-expanded", "framebridge-screen-access"): + shutil.copy2(BASE / "packaging" / name, PREFIX / "bin" / name) + (PREFIX / "bin" / name).chmod(0o755) + rules = STAGE / "usr/lib/udev/rules.d" + rules.mkdir(parents=True) + shutil.copy2(BASE / "packaging/70-framebridge-screen.rules", rules / "70-framebridge-screen.rules") + layout = { + "adapter": "original/asus/tool-adapter/bin/framebridge_tool_adapter.py", + "adapter_bin": "original/asus/tool-adapter/bin", + "fbterm_bin": "original/asus/fbterm/bin", + "native_host": "original/asus/framebridge-native/host/framebridge_host.py", + "native_host_dir": "original/asus/framebridge-native/host" + } + (PREFIX / "layout.json").write_text(json.dumps(layout, indent=2) + "\n") + bindir = STAGE / "usr/bin" + bindir.mkdir(parents=True) + for name in ("framebridge", "fb", "framebridge-expanded"): + (bindir / name).symlink_to("../lib/framebridge-expanded/bin/framebridge") + applications = STAGE / "usr/share/applications" + applications.mkdir(parents=True) + (applications / "framebridge-console.desktop").write_text( + "[Desktop Entry]\nVersion=1.0\nType=Application\nName=FrameBridge Console\n" + "Comment=Open the persistent local Bash and FBTERM activity console\n" + "Exec=/usr/bin/fb console\nIcon=utilities-terminal\nTerminal=false\n" + "Categories=System;TerminalEmulator;\nStartupWMClass=Gnome-terminal\n") + docs = STAGE / "usr/share/doc" / PACKAGE + docs.mkdir(parents=True) + for file in sorted((BASE / "docs").glob("*")): + if file.is_file(): + shutil.copy2(file, docs / file.name) + readme = (BASE / "docs/DEBIAN-TUTORIAL.md").read_bytes() + (docs / "README.Debian.gz").write_bytes(gzip.compress(readme, mtime=EPOCH)) + install_legal_material(docs) + man = STAGE / "usr/share/man/man1" + man.mkdir(parents=True) + man_source = (BASE / "docs/framebridge.1").read_bytes() if (BASE / "docs/framebridge.1").exists() else b".TH FRAMEBRIDGE 1\n.SH NAME\nframebridge - FrameBridge Original Expanded\n.SH SYNOPSIS\nframebridge [--state-dir PATH] setup|start|stop|status|doctor\n.SH DESCRIPTION\nRun framebridge tutorial for the Debian installation and usage guide.\n" + (man / "framebridge.1.gz").write_bytes(gzip.compress(man_source, mtime=EPOCH)) + (man / "fb.1.gz").symlink_to("framebridge.1.gz") + control = STAGE / "DEBIAN" + control.mkdir() + installed = sum(p.stat().st_size for p in STAGE.rglob("*") if p.is_file() and not p.is_symlink()) + (control / "control").write_text( + "Package: " + PACKAGE + "\nVersion: " + VERSION + "\nSection: utils\nPriority: optional\nArchitecture: amd64\n" + "Maintainer: FrameBridge \n" + "Homepage: https://framebridge.betancourt.one/\n" + "Installed-Size: " + str((installed + 1023) // 1024) + "\n" + "Depends: python3 (>= 3.13), python3 (<< 3.14), libc6 (>= 2.34), libgcc-s1, python3-cryptography, sudo, libpam-systemd, dbus-user-session, udev, libx11-6, ca-certificates, iproute2, procps, systemd, bash, tmux, coreutils, util-linux, mawk | gawk, gnome-terminal, wmctrl, xdotool, x11-utils, xclip, imagemagick, xterm, xvfb, openbox, dbus-x11, xauth, x11-xkb-utils, fonts-dejavu-core\n" + "Description: FrameBridge with standalone, central and node modes\n" + " Original execution and desktop programs, a real persistent terminal,\n" + " authenticated node enrollment, and a manual ChatGPT tunnel wizard.\n" + " Includes real active-screen capture, saved original PNG viewing,\n" + " per-console sudo capability, explicit read-only framebuffer access,\n" + " a shared graphical/text console, and an English Debian tutorial.\n" + " Optional virtual displays are explicitly identified as virtual.\n" + " No phone or Owner Device pairing. Services start after connection setup.\n" + " Installing the package alone does not activate a connection.\n") + (control / "prerm").write_text( + "#!/bin/sh\nset -eu\n" + "case \"$1\" in\n" + " remove|deconfigure)\n" + " /usr/bin/python3 -I -S /usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py --all-users\n" + " if [ -e /etc/framebridge-expanded/screen-access.json ] || [ -L /etc/framebridge-expanded/screen-access.json ]; then\n" + " /usr/lib/framebridge-expanded/bin/framebridge-screen-access disable\n" + " fi\n" + " ;;\nesac\n") + (control / "prerm").chmod(0o755) + rows = [] + for path in sorted(STAGE.rglob("*")): + if path.is_file() and not path.is_symlink() and control not in path.parents: + rows.append(hashlib.md5(path.read_bytes()).hexdigest() + " " + str(path.relative_to(STAGE))) + (control / "md5sums").write_text("\n".join(rows) + "\n") + for path in STAGE.rglob("*"): + if path.is_symlink(): + continue + if path.is_dir(): + path.chmod(0o755) + elif os.access(path, os.X_OK): + path.chmod(0o755) + else: + path.chmod(0o644) + os.utime(path, (EPOCH, EPOCH), follow_symlinks=False) + manifest = [{"path": str(p.relative_to(STAGE)), "sha256": sha(p), "bytes": p.stat().st_size} + for p in sorted(STAGE.rglob("*")) if p.is_file() and not p.is_symlink()] + (RELEASE / "PACKAGE-MANIFEST.json").write_text(json.dumps(manifest, indent=2) + "\n") + output = RELEASE / ("framebridge-expanded_" + VERSION + "_amd64.deb") + env = dict(os.environ, SOURCE_DATE_EPOCH=str(EPOCH)) + subprocess.run(["dpkg-deb", "--root-owner-group", "-Zxz", "-z6", "--build", str(STAGE), str(output)], check=True, env=env) + shutil.copy2(output, RELEASE / PUBLIC_FILENAME) + print(json.dumps({"package": str(output), "bytes": output.stat().st_size, "sha256": sha(output), "payload_entries": len(manifest), "public_filename": PUBLIC_FILENAME, "status": "COMMUNITY_RELEASE_BUILD"})) + +if __name__ == "__main__": + main() diff --git a/source/packaging/build_original.py b/source/packaging/build_original.py new file mode 100755 index 0000000..bfb7734 --- /dev/null +++ b/source/packaging/build_original.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 +"""Build the original snapshot on GERAMEG; does not install or start software.""" +import calendar +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess +import tarfile +import time + +BASE = Path(__file__).resolve().parents[1] +VERSION = "0.1.0~original.20261021.1" +EPOCH = calendar.timegm((2026, 10, 21, 0, 0, 0)) +RELEASE = BASE / "release" +STAGE = BASE / "build/package-root" +PREFIX = STAGE / "usr/lib/framebridge-original" + +def sha(p): + return hashlib.sha256(p.read_bytes()).hexdigest() + +def main(): + assert (BASE / "portable/front_door_mcp.py").is_file(), "portable source missing" + assert (BASE / "sources/original-asus/SOURCE-MANIFEST.json").is_file(), "ASUS snapshot missing" + assert (BASE / "sources/original-geranas/MANIFEST.json").is_file(), "GERANAS snapshot missing" + if STAGE.exists(): + shutil.rmtree(STAGE) + RELEASE.mkdir(exist_ok=True) + PREFIX.mkdir(parents=True) + shutil.copytree(BASE / "sources/original-geranas", PREFIX / "original/geranas") + shutil.copytree(BASE / "sources/original-asus", PREFIX / "original/asus") + shutil.copytree(BASE / "portable", PREFIX / "portable", ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) + (PREFIX / "bin").mkdir() + shutil.copy2(BASE / "packaging/framebridge-original", PREFIX / "bin/framebridge-original") + (PREFIX / "bin/framebridge-original").chmod(0o755) + layout = { + "adapter": "original/asus/tool-adapter/bin/framebridge_tool_adapter.py", + "adapter_bin": "original/asus/tool-adapter/bin", + "fbterm_bin": "original/asus/fbterm/bin", + "native_host": "original/asus/framebridge-native/host/framebridge_host.py", + "native_host_dir": "original/asus/framebridge-native/host", + } + (PREFIX / "layout.json").write_text(json.dumps(layout, indent=2) + "\n") + bindir = STAGE / "usr/bin" + bindir.mkdir() + (bindir / "framebridge-original").symlink_to("../lib/framebridge-original/bin/framebridge-original") + docs = STAGE / "usr/share/doc/framebridge-original" + docs.mkdir(parents=True) + for p in [BASE / "README-ORIGINAL.md", BASE / "evidencia-mt/CONEXION-MANUAL.md"]: + if p.exists(): + shutil.copy2(p, docs / p.name) + for p in (BASE / "portable").glob("*.diff"): + shutil.copy2(p, docs / p.name) + control = STAGE / "DEBIAN" + control.mkdir() + installed = sum(p.stat().st_size for p in STAGE.rglob("*") if p.is_file() and not p.is_symlink()) + (control / "control").write_text( + "Package: framebridge-original\n" + "Version: " + VERSION + "\n" + "Section: utils\nPriority: optional\nArchitecture: amd64\n" + "Maintainer: FrameBridge \n" + "Installed-Size: " + str((installed + 1023) // 1024) + "\n" + "Depends: python3 (>= 3.13), python3 (<< 3.14), libc6 (>= 2.34), libgcc-s1, bash, tmux, coreutils, util-linux, mawk | gawk, gnome-terminal, wmctrl, xdotool, x11-utils, xclip, imagemagick, xterm\n" + "Description: FrameBridge Original with manual ChatGPT connection\n" + " Exact ASUS executor and native desktop programs with GERANAS MCP dependencies\n" + " and tunnel client. A separate profile and explicit manual startup avoid\n" + " collision with existing FrameBridge installations. No phone pairing or\n" + " Owner Device is required by this local executable path.\n") + lines = [] + for p in sorted(STAGE.rglob("*")): + if p.is_file() and not p.is_symlink() and control not in p.parents: + lines.append(hashlib.md5(p.read_bytes()).hexdigest() + " " + str(p.relative_to(STAGE))) + (control / "md5sums").write_text("\n".join(lines) + "\n") + manifest = [{"path": str(p.relative_to(STAGE)), "sha256": sha(p), "bytes": p.stat().st_size} + for p in sorted(STAGE.rglob("*")) if p.is_file() and not p.is_symlink()] + (RELEASE / "PACKAGE-MANIFEST.json").write_text(json.dumps({ + "package": "framebridge-original", "version": VERSION, "architecture": "amd64", + "source_host": "GERAMEG", "files": manifest}, indent=2) + "\n") + for p in STAGE.rglob("*"): + if not p.is_symlink(): + p.chmod(0o755 if p.is_dir() or p.stat().st_mode & 0o111 else 0o644) + for p in sorted(STAGE.rglob("*"), reverse=True): + os.utime(p, (EPOCH, EPOCH), follow_symlinks=False) + STAGE.chmod(0o755) + os.utime(STAGE, (EPOCH, EPOCH)) + output = RELEASE / ("framebridge-original_" + VERSION + "_amd64.deb") + env = os.environ.copy() + env["SOURCE_DATE_EPOCH"] = str(EPOCH) + subprocess.run(["dpkg-deb", "--root-owner-group", "--uniform-compression", "-Zxz", "-z6", + "--build", str(STAGE), str(output)], check=True, env=env) + shutil.copyfile(output, RELEASE / "framebridge.deb") + paths = [output, RELEASE / "framebridge.deb", RELEASE / "PACKAGE-MANIFEST.json"] + (RELEASE / "SHA256SUMS.txt").write_text("".join(sha(p) + " " + p.name + "\n" for p in paths)) + print(json.dumps({"package": str(output), "bytes": output.stat().st_size, "sha256": sha(output), + "files": len(manifest)}, indent=2)) +if __name__ == "__main__": + main() diff --git a/source/packaging/framebridge b/source/packaging/framebridge new file mode 100755 index 0000000..4909a55 --- /dev/null +++ b/source/packaging/framebridge @@ -0,0 +1,10 @@ +#!/usr/bin/python3 +"""FrameBridge Expanded command-line entry point.""" +import os +import sys +from pathlib import Path +prefix = Path(__file__).resolve().parents[1] +os.environ.setdefault("FRAMEBRIDGE_EXPANDED_PREFIX", str(prefix)) +sys.path.insert(0, str(prefix / "expanded")) +from framebridge_expanded.cli import main +raise SystemExit(main()) diff --git a/source/packaging/framebridge-native-expanded b/source/packaging/framebridge-native-expanded new file mode 100755 index 0000000..b66327f --- /dev/null +++ b/source/packaging/framebridge-native-expanded @@ -0,0 +1,10 @@ +#!/usr/bin/python3 +"""Private captured-host entry point with installed-prefix discovery.""" +import os +import sys +from pathlib import Path +prefix = Path(__file__).resolve().parents[1] +os.environ.setdefault("FRAMEBRIDGE_EXPANDED_PREFIX", str(prefix)) +sys.path.insert(0, str(prefix / "expanded")) +from framebridge_expanded.native_host import main +main() diff --git a/source/packaging/framebridge-original b/source/packaging/framebridge-original new file mode 100755 index 0000000..1008814 --- /dev/null +++ b/source/packaging/framebridge-original @@ -0,0 +1,258 @@ +#!/usr/bin/python3 +"""Manual launcher for the captured FrameBridge Original programs.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import signal +import stat +import socket +import subprocess +import sys +import time + +PREFIX = Path(__file__).resolve().parents[1] +REAL_HOME = Path.home() +DEFAULT_STATE = Path(os.environ.get("XDG_STATE_HOME", str(REAL_HOME / ".local/state"))) / "framebridge-original" +LAYOUT = json.loads((PREFIX / "layout.json").read_text()) +CORE_COMMANDS = ["bash", "tmux", "tee", "flock", "awk", "sha256sum", "timeout", + "gnome-terminal", "wmctrl"] + +def settings(args): + state = Path(args.state_dir).expanduser().absolute() + home = state / "home" + runtime_base = Path("/run/user") / str(os.getuid()) + if not runtime_base.is_dir() or runtime_base.stat().st_uid != os.getuid(): + runtime_base = Path("/tmp") + runtime = runtime_base / ("fbo-" + str(os.getuid()) + "-" + hashlib.sha256(str(state).encode()).hexdigest()[:16]) + env = os.environ.copy() + authority = env.get("XAUTHORITY") + if not authority and (REAL_HOME / ".Xauthority").is_file(): + authority = str(REAL_HOME / ".Xauthority") + env.update({ + "HOME": str(home), "XDG_CONFIG_HOME": str(home / ".config"), + "XDG_DATA_HOME": str(home / ".local/share"), + "XDG_STATE_HOME": str(home / ".local/state"), "XDG_CACHE_HOME": str(home / ".cache"), + "XDG_RUNTIME_DIR": str(runtime), "TMPDIR": str(runtime / "tmp"), + "TMUX_TMPDIR": str(runtime / "tmux"), "PYTHONDONTWRITEBYTECODE": "1", + "FBTERM_ROOT": str(home / ".local/share/framebridge/fbterm"), + "FRAMEBRIDGE_FBTERM_ROOT_OVERRIDE": str(home / ".local/share/framebridge/fbterm"), + "FBTERM_SESSION": "framebridge-original-fbterm", + "FBTERM_TITLE": "FrameBridge Original", + "FRAMEBRIDGE_FBTERM_ROOT": str(home / ".local/share/framebridge/fbterm"), + "FRAMEBRIDGE_FBTERM_ENGINE": str(PREFIX / LAYOUT["fbterm_bin"] / "fbterm_engine.py"), + "FRAMEBRIDGE_FBTERM_SESSION": "framebridge-original-fbterm", + "FRAMEBRIDGE_FBTERM_TITLE": "FrameBridge Original", + "FRAMEBRIDGE_EXEC_SESSION": "framebridge-original", + "FRAMEBRIDGE_RESCUE_SESSION": "framebridge-original-rescue", + "FRAMEBRIDGE_UI_STATE_DIR": str(home / ".local/share/framebridge/ui-state"), + "FRAMEBRIDGE_PROGRESS_DIR": str(home / ".local/state/framebridge/progress"), + "FRAMEBRIDGE_PROGRESS_LOG": str(home / ".local/state/framebridge/progress/framebridge-progress.log"), + "FRAMEBRIDGE_TOOL_ROOT": str(runtime / "adapter"), + "FRAMEBRIDGE_NATIVE_HOST": str(PREFIX / LAYOUT["native_host"]), + "FRAMEBRIDGE_FRONT_DOOR_HOST": "127.0.0.1", + "FRAMEBRIDGE_FRONT_DOOR_PORT": str(args.port), + "FRAMEBRIDGE_FLEET_HUB_HOST": "127.0.0.1", + "FRAMEBRIDGE_FLEET_HUB_PORT": str(args.hub_port), + "FRAMEBRIDGE_HUB_MCP_URL": "http://127.0.0.1:" + str(args.hub_port) + "/mcp", + "FRAMEBRIDGE_BROKER_CONTROL": "http://127.0.0.1:" + str(args.broker_port), + "FRAMEBRIDGE_ASUS_NODE_ID": "", "FRAMEBRIDGE_GERAMEG_NODE_ID": "", + "FRAMEBRIDGE_CLOUD_DSM_SOCKET": str(runtime / "cloud-unconfigured.sock"), + "FRAMEBRIDGE_NODE_REGISTRY": str(home / ".config/framebridge/nodes.json"), + "FRAMEBRIDGE_ACCOUNT_BINDING": str(home / ".config/framebridge/account-binding.json"), + "FRAMEBRIDGE_PAIRED_DISCOVERY": str(home / ".config/framebridge/paired-nodes.json"), + "FRAMEBRIDGE_ENABLE_GERAMEG_CUA_TTY": "0", + "PYTHONPATH": os.pathsep.join(str(PREFIX / p) for p in [ + "original/geranas/FrameBridge-Fleet-Hub/vendor", + "original/geranas/FrameBridge-Fleet-Hub/app", "portable"]), + }) + env.pop("TMUX", None) + env.pop("TMUX_PANE", None) + if authority: + env["XAUTHORITY"] = authority + return state, home, env + +def prepare(args): + state, home, env = settings(args) + runtime = Path(env["XDG_RUNTIME_DIR"]) + try: + runtime.mkdir(mode=0o700) + except FileExistsError: + pass + st = runtime.lstat() + if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700: + raise RuntimeError("Runtime directory must be private and owned by this user: " + str(runtime)) + for p in [runtime, runtime / "tmp", runtime / "tmux", runtime / "adapter", state, home, state / "run", state / "tmp", state / "tmux", + state / "logs", home / ".config/framebridge", + home / ".local/share/framebridge/fbterm", + home / ".local/share/framebridge/tool-adapter", + home / ".local/lib/framebridge-native", + home / "Descargas/framebridge_v0_1"]: + p.mkdir(parents=True, exist_ok=True) + p.chmod(0o700) + links = { + home / ".local/share/framebridge/fbterm/bin": PREFIX / LAYOUT["fbterm_bin"], + home / ".local/share/framebridge/tool-adapter/bin": PREFIX / LAYOUT["adapter_bin"], + home / ".local/lib/framebridge-native/host": PREFIX / LAYOUT["native_host_dir"], + home / "Descargas/framebridge_v0_1/host": PREFIX / LAYOUT["native_host_dir"], + } + for dst, src in links.items(): + if dst.is_symlink(): + if dst.resolve() != src.resolve(): + raise RuntimeError("Unexpected link: " + str(dst)) + elif dst.exists(): + raise RuntimeError("Runtime program path already exists: " + str(dst)) + else: + dst.symlink_to(src, target_is_directory=True) + registry = home / ".config/framebridge/nodes.json" + if not registry.exists(): + registry.write_text(json.dumps({"nodes": {}}, indent=2) + "\n") + registry.chmod(0o600) + return state, home, env + +def birth(pid): + try: + fields = Path("/proc/" + str(pid) + "/stat").read_text().rsplit(") ", 1)[1].split() + if fields[0] in {"Z", "X"}: + return None + return fields[19] + except (OSError, IndexError): + return None + +def live(record): + expected = record.get("birth") + return expected is not None and birth(record["pid"]) == expected + +def load_record(state): + p = state / "processes.json" + if not p.exists(): + return {"processes": []} + return json.loads(p.read_text()) + +def stop(args): + state, home, env = settings(args) + records = load_record(state) + for rec in reversed(records.get("processes", [])): + if live(rec): + os.killpg(rec["pid"], signal.SIGTERM) + deadline = time.monotonic() + 5 + while any(live(r) for r in records.get("processes", [])) and time.monotonic() < deadline: + time.sleep(.1) + for rec in records.get("processes", []): + if live(rec): + os.killpg(rec["pid"], signal.SIGKILL) + if Path(env["TMUX_TMPDIR"]).is_dir() and shutil.which("tmux"): + subprocess.run(["tmux", "kill-server"], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + (state / "processes.json").unlink(missing_ok=True) + print("FrameBridge Original detenido. Estado: " + str(state)) + +def start(args): + missing = [name for name in CORE_COMMANDS if not shutil.which(name)] + if missing: + raise RuntimeError("Faltan comandos para ejecutar FrameBridge Original: " + ", ".join(missing)) + state, home, env = prepare(args) + records = load_record(state) + if any(live(r) for r in records.get("processes", [])): + raise RuntimeError("Este perfil ya está ejecutándose; usa status o stop.") + for port in (args.port, args.hub_port): + with socket.socket() as probe: + probe.bind(("127.0.0.1", port)) + commands = [ + ("adapter", [sys.executable, str(PREFIX / LAYOUT["adapter"]), "serve"]), + ("fleet", [sys.executable, str(PREFIX / "original/geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py")]), + ("mcp", [sys.executable, str(PREFIX / "portable/front_door_mcp.py")]), + ] + record = {"state_dir": str(state), "mcp_url": "http://127.0.0.1:" + str(args.port) + "/mcp", + "port": args.port, "hub_port": args.hub_port, "processes": []} + try: + for role, command in commands: + log = (state / "logs" / (role + ".log")).open("ab") + proc = subprocess.Popen(command, env=env, cwd=home, stdin=subprocess.DEVNULL, + stdout=log, stderr=subprocess.STDOUT, start_new_session=True, umask=0o077) + log.close() + record["processes"].append({"role": role, "pid": proc.pid, "birth": birth(proc.pid)}) + (state / "processes.json").write_text(json.dumps(record, indent=2) + "\n") + (state / "processes.json").chmod(0o600) + deadline = time.monotonic() + 15 + while time.monotonic() < deadline: + if not all(live(r) for r in record["processes"]): + raise RuntimeError("Falló un proceso. Consulta los logs del perfil: " + str(state / "logs")) + ready = (Path(env["FRAMEBRIDGE_TOOL_ROOT"]) / "adapter.sock").is_socket() + for port in (args.port, args.hub_port): + try: + with socket.create_connection(("127.0.0.1", port), timeout=.2): + pass + except OSError: + ready = False + if ready: + print("FrameBridge Original iniciado: " + record["mcp_url"]) + print("Conexión a ChatGPT: manual; usa tunnel --help y las instrucciones del paquete.") + print("Perfil local: " + str(state)) + return + time.sleep(.15) + raise RuntimeError("No se completó el arranque; consulta los logs del perfil.") + except BaseException: + stop(args) + raise + +def status(args): + state, home, env = settings(args) + record = load_record(state) + print(json.dumps({"state_dir": str(state), "mcp_url": record.get("mcp_url"), + "processes": [{"role": x["role"], "pid": x["pid"], "running": live(x)} + for x in record.get("processes", [])]}, indent=2)) + +def doctor(args): + state, home, env = settings(args) + required = CORE_COMMANDS + gui = ["xdotool", "xwininfo", "xclip", "import", "convert", "identify", "xterm"] + imports = subprocess.run([sys.executable, "-c", "import mcp,pydantic_core,cryptography"], env=env, capture_output=True) + report = {"python": sys.version.split()[0], "python_abi_expected": "3.13", + "mcp_imports_ok": imports.returncode == 0, + "core_commands": {x: bool(shutil.which(x)) for x in required}, + "cua_commands": {x: bool(shutil.which(x)) for x in gui}, + "display": os.environ.get("DISPLAY", ""), + "tunnel_sha256": hashlib.sha256((PREFIX / "original/geranas/FrameBridge-MCP-Private/bin/tunnel-client").read_bytes()).hexdigest()} + print(json.dumps(report, indent=2)) + return 0 if sys.version_info[:2] == (3, 13) and imports.returncode == 0 and all(report["core_commands"].values()) else 1 + +def main(): + parser = argparse.ArgumentParser(description="FrameBridge Original: ejecución local, CUA y conexión manual a ChatGPT.") + parser.add_argument("--state-dir", default=str(DEFAULT_STATE)) + parser.add_argument("--port", type=int, default=19865) + parser.add_argument("--hub-port", type=int, default=19877) + parser.add_argument("--broker-port", type=int, default=19878) + sub = parser.add_subparsers(dest="command", required=True) + for name in ["start", "stop", "status", "doctor"]: + sub.add_parser(name) + tunnel = sub.add_parser("tunnel", help="Ejecuta el cliente de túnel original; configuración manual independiente.") + tunnel.add_argument("arguments", nargs=argparse.REMAINDER) + args = parser.parse_args() + if args.command == "tunnel": + state, home, env = prepare(args) + rest = args.arguments + if rest[:1] == ["--"]: + rest = rest[1:] + explicit_refs = {x[4:] for x in rest if x.startswith("env:")} + for x in rest: + if "=env:" in x: + explicit_refs.add(x.split("=env:", 1)[1]) + for key in list(env): + if (key.startswith(("TUNNEL_CLIENT_", "CONTROL_PLANE_", "OPENAI_", "MCP_", "HEALTH_", "LOG_", "CLOUDFLARED_", "HARPOON_")) + or key in {"TUNNEL_ID", "TUNNEL_BIN", "CRED_FILE", "OPEN_WEB_UI", "PID_FILE", + "ALLOW_REMOTE_UI", "ADMIN_UI_LOG_BUFFER_EVENTS", "PROXY_CHECK_INTERVAL"}): + if key not in explicit_refs: + env.pop(key, None) + os.execve(str(PREFIX / "original/geranas/FrameBridge-MCP-Private/bin/tunnel-client"), + ["tunnel-client"] + rest, env) + result = globals()[args.command](args) + return result or 0 + +if __name__ == "__main__": + try: + sys.exit(main()) + except (OSError, ValueError, RuntimeError) as exc: + print(str(exc), file=sys.stderr) + sys.exit(1) diff --git a/source/packaging/framebridge-screen-access b/source/packaging/framebridge-screen-access new file mode 100644 index 0000000..1dc0a49 --- /dev/null +++ b/source/packaging/framebridge-screen-access @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/python3 -I -S /usr/lib/framebridge-expanded/expanded/framebridge_expanded/screen_access.py "$@" diff --git a/source/portable/FRONT-DOOR-EXPANDED-PATCH.diff b/source/portable/FRONT-DOOR-EXPANDED-PATCH.diff new file mode 100644 index 0000000..ad6792c --- /dev/null +++ b/source/portable/FRONT-DOOR-EXPANDED-PATCH.diff @@ -0,0 +1,252 @@ +--- original/front_door_mcp.py ++++ portable/front_door_mcp.py +@@ -13,26 +13,28 @@ + from mcp import ClientSession + from mcp.client.streamable_http import streamablehttp_client + from mcp.server.fastmcp import FastMCP, Image ++from framebridge_expanded.fleet import execute_node as expanded_execute_node, cua_node as expanded_cua_node, bootstrap_original ++bootstrap_original() + + HOME = Path.home() +-ADAPTER_SOCKET = HOME / ".local/share/framebridge/tool-adapter/adapter.sock" ++ADAPTER_SOCKET = Path(os.environ.get("FRAMEBRIDGE_TOOL_ROOT", str(HOME / ".local/share/framebridge/tool-adapter"))) / "adapter.sock" + JOBS_ROOT = HOME / ".local/share/framebridge/fbterm/jobs" +-HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:8877/mcp") +-BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:28765") +-ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "4b4157f9-3c98-4d24-bff4-840825d50951") +-GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "76885f27-da2e-4cca-bbde-6ec981e91e68") ++HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:20877/mcp") ++BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:20878") ++ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "") ++GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "") + HOST = os.environ.get("FRAMEBRIDGE_FRONT_DOOR_HOST", "127.0.0.1") +-PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "8865")) ++PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "20865")) + JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") + SENSITIVE = re.compile(r"(?i)(secret|token|password|passwd|api[_-]?key|private[_-]?key|cookie|authorization|credential)") + KEY_BEGIN = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") + KEY_END = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + + mcp = FastMCP( +- "FrameBridge GERANAS Front Door", ++ "FrameBridge Original Front Door", + instructions=( +- "Front Door privado de FrameBridge en GERANAS. " +- "framebridge_exec enruta por target: ASUS y GERAMEG mediante broker_outbound, GERANAS via FBTERM local, autorizado explicitamente; sin target mantiene ASUS. " ++ "Front Door original en el equipo receptor. " ++ "framebridge_exec defaults to LOCAL; other targets are confirmed Fleet aliases or node IDs. " + "Las seis herramientas Fleet son READ-only y se enrutan al Hub local." + ), + host=HOST, +@@ -100,7 +102,7 @@ + mode=oct(ADAPTER_SOCKET.stat().st_mode & 0o777) if ok else None + return { + "ok":ok, +- "host":"geranas", ++ "host":socket.gethostname(), + "adapter_socket":str(ADAPTER_SOCKET), + "socket_mode":mode, + "mcp_transport":"streamable-http", +@@ -158,6 +160,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not ASUS_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { +@@ -243,6 +247,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not GERAMEG_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { +@@ -310,7 +316,7 @@ + sha256: str = "", + include_stdout: bool = False, + ) -> dict: +- """Administrative/lab exec on GERANAS local FBTERM.""" ++ """Historical alias for original local FBTERM execution on this receiver.""" + return _framebridge_exec_geranas_impl( + script=script, + authorized=authorized, +@@ -371,13 +377,13 @@ + + + def _cloud_dsm_rpc(payload: dict) -> dict: +- """Private GERANAS local socket; root-only approval never exposed through MCP.""" ++ """Optional private local socket; root-only approval never exposed through MCP.""" + import socket as _socket + import json as _json + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_STREAM) + sock.settimeout(38) + try: +- sock.connect("/run/framebridge-dsm/cloud.sock") ++ sock.connect(os.environ.get("FRAMEBRIDGE_CLOUD_DSM_SOCKET", str(HOME / ".local/share/framebridge/dsm/cloud.sock"))) + body = (_json.dumps(payload, separators=(",",":"),ensure_ascii=True) + "\n").encode("utf8") + if len(body)>12000: + return {"ok":False,"error":"CLOUD_REQUEST_TOO_LARGE","target":"CLOUD"} +@@ -428,49 +434,16 @@ + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, +- target: str = "ASUS", ++ target: str = "LOCAL", + ) -> dict: +- """Generic administrative/lab exec. target=ASUS or GERAMEG uses broker; target=GERANAS uses local FBTERM. Legacy calls without target remain ASUS. CLOUD uses only typed JSON actions, never shell commands.""" +- if target == "CLOUD": +- import json as _json +- try: +- payload = _json.loads(script) +- if not isinstance(payload,dict) or len(script)>2000: +- return {"ok":False,"error":"CLOUD_REQUIRES_JSON_ACTION"} +- except (TypeError,ValueError): +- return {"ok":False,"error":"CLOUD_ACCEPTS_ONLY_TYPED_JSON_ACTION_NOT_SHELL"} +- if payload.get("action")=="approve": +- return {"ok":False,"error":"CLOUD_OWNER_APPROVAL_LOCAL_TTY_ONLY"} +- return _cloud_dsm_rpc({"action":payload.get("action"), +- "params":payload.get("params",{}), +- "authorized":bool(authorized)}) +- if target == "GERANAS": +- result = _framebridge_exec_geranas_impl( +- script=script, +- authorized=authorized, +- timeout_s=timeout_s, +- job_id=job_id, +- sha256=sha256, +- include_stdout=include_stdout, +- ) ++ """Execute through original local FBTERM or a confirmed Fleet node alias.""" ++ if target == "LOCAL": ++ result = _framebridge_exec_geranas_impl(script, authorized, timeout_s, job_id, sha256, include_stdout) + if isinstance(result, dict): + result = dict(result) +- result["target"] = "GERANAS" ++ result["target"] = "LOCAL" + return result +- if target not in ("ASUS", "GERAMEG"): +- return {"ok": False, "error": "UNSUPPORTED_TARGET", "allowed_targets": ["ASUS", "GERANAS", "GERAMEG", "CLOUD"]} +- exec_request = asus_exec_request if target == "ASUS" else gerameg_exec_request +- result=exec_request( +- script=script, +- authorized=authorized, +- timeout_s=timeout_s, +- job_id=job_id, +- sha256=sha256, +- ) +- if include_stdout is not True and isinstance(result,dict): +- result=dict(result) +- result.pop("stdout",None) +- return result ++ return expanded_execute_node(target, script, authorized, timeout_s, job_id, sha256, include_stdout) + + + def _cua_frame_fields(frame_output: str) -> dict: +@@ -510,6 +483,54 @@ + return result + + ++def local_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: ++ if authorized is not True: ++ return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} ++ if not isinstance(cua,dict): ++ return {"ok":False,"error":"CUA_PAYLOAD_REQUIRED"} ++ action=str(cua.get("action") or "").upper().strip() ++ if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: ++ return {"ok":False,"error":"CUA_ACTION_UNSUPPORTED"} ++ try: ++ timeout_s=int(timeout_s) ++ except Exception: ++ return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} ++ if not 1<=timeout_s<=180: ++ return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} ++ if not job_id: ++ seed=json.dumps({"action":action,"time":time.time_ns()},sort_keys=True).encode() ++ digest=hashlib.sha256(seed).hexdigest() ++ job_id="cua-local-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] ++ if not JOB_RE.fullmatch(job_id): ++ return {"ok":False,"error":"INVALID_JOB_ID"} ++ ++ result=adapter_request({ ++ "version":1,"mode":"EXEC","request_type":"cua", ++ "authorized":True,"job_id":job_id,"timeout_s":timeout_s,"cua":cua, ++ }) ++ if not isinstance(result,dict): ++ return {"ok":False,"error":"CUA_LOCAL_ADAPTER_RESULT_INVALID","target":"LOCAL"} ++ value=dict(result) ++ frame_output=str(value.pop("frame_output","")) ++ if frame_output: ++ value.update(_cua_frame_fields(frame_output)) ++ value["target"]="LOCAL" ++ return value ++ ++def dispatch_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str, target: str = "LOCAL") -> dict: ++ """Reuse the original local CUA contract and route confirmed remote nodes.""" ++ if target == "LOCAL": ++ return local_cua_request(cua, authorized, timeout_s, job_id) ++ result = expanded_cua_node(target, cua, authorized, timeout_s, job_id) ++ if not isinstance(result, dict): ++ return {"ok":False,"error":"NODE_CUA_RESPONSE_INVALID"} ++ result = dict(result) ++ frame_output = str(result.pop("frame_output", "")) ++ if frame_output: ++ result.update(_cua_frame_fields(frame_output)) ++ return result ++ ++ + def asus_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} +@@ -531,6 +552,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not ASUS_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-cua", + { +@@ -606,8 +629,9 @@ + expected_source_height: int = 0, + timeout_s: int = 60, + job_id: str = "", ++ target: str = "LOCAL", + ): +- """CUA R8 on ASUS through GERANAS. Requires explicit per-use chat authorization.""" ++ """Original CUA R8 on this receiver through its local Tool Adapter.""" + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + action=str(action).upper().strip() +@@ -659,7 +683,7 @@ + "button":str(button).lower(),"steps":int(steps),"duration_ms":int(duration_ms), + }) + +- result=asus_cua_request(payload,True,timeout_s,job_id) ++ result=dispatch_cua_request(payload,True,timeout_s,job_id,target) + if bool(observe_after) and action!="OBSERVE" and bool(result.get("ok")): + post=dict(payload) + post.clear() +@@ -667,7 +691,7 @@ + if expected_window_id: + post["expected_window_id"]=str(expected_window_id) + post_id=(str(job_id or "cua")[:72]+"-post")[:80] +- post_result=asus_cua_request(post,True,timeout_s,post_id) ++ post_result=dispatch_cua_request(post,True,timeout_s,post_id,target) + result["post_ok"]=bool(post_result.get("ok")) + result["post_resultado_final"]=post_result.get("resultado_final") + for key_name in ( +@@ -742,8 +766,8 @@ + async def framebridge_fleet_node_service_status(node: str, service: str) -> dict: + return await hub_call("framebridge_fleet_node_service_status",{"node":node,"service":service}) + +-from gerameg_cua_frontdoor import register_tools as register_gerameg_cua_tools +-register_gerameg_cua_tools(mcp, gerameg_exec_request) ++from framebridge_expanded.tty_mcp import register_tools as register_tty_tools ++register_tty_tools(mcp, framebridge_exec) + + if __name__ == "__main__": + mcp.run(transport="streamable-http") diff --git a/source/portable/FRONT-DOOR-PATCH.diff b/source/portable/FRONT-DOOR-PATCH.diff new file mode 100644 index 0000000..d0d6f76 --- /dev/null +++ b/source/portable/FRONT-DOOR-PATCH.diff @@ -0,0 +1,204 @@ +--- original/front_door_mcp.py ++++ portable/front_door_mcp.py +@@ -15,24 +15,24 @@ + from mcp.server.fastmcp import FastMCP, Image + + HOME = Path.home() +-ADAPTER_SOCKET = HOME / ".local/share/framebridge/tool-adapter/adapter.sock" ++ADAPTER_SOCKET = Path(os.environ.get("FRAMEBRIDGE_TOOL_ROOT", str(HOME / ".local/share/framebridge/tool-adapter"))) / "adapter.sock" + JOBS_ROOT = HOME / ".local/share/framebridge/fbterm/jobs" +-HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:8877/mcp") +-BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:28765") +-ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "4b4157f9-3c98-4d24-bff4-840825d50951") +-GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "76885f27-da2e-4cca-bbde-6ec981e91e68") ++HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:19877/mcp") ++BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:19878") ++ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "") ++GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "") + HOST = os.environ.get("FRAMEBRIDGE_FRONT_DOOR_HOST", "127.0.0.1") +-PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "8865")) ++PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "19865")) + JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") + SENSITIVE = re.compile(r"(?i)(secret|token|password|passwd|api[_-]?key|private[_-]?key|cookie|authorization|credential)") + KEY_BEGIN = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") + KEY_END = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + + mcp = FastMCP( +- "FrameBridge GERANAS Front Door", ++ "FrameBridge Original Front Door", + instructions=( +- "Front Door privado de FrameBridge en GERANAS. " +- "framebridge_exec enruta por target: ASUS y GERAMEG mediante broker_outbound, GERANAS via FBTERM local, autorizado explicitamente; sin target mantiene ASUS. " ++ "Front Door original en el equipo receptor. " ++ "framebridge_exec ejecuta LOCAL por defecto; ASUS y GERAMEG requieren broker configurado; GERANAS es un alias historico de LOCAL. " + "Las seis herramientas Fleet son READ-only y se enrutan al Hub local." + ), + host=HOST, +@@ -100,7 +100,7 @@ + mode=oct(ADAPTER_SOCKET.stat().st_mode & 0o777) if ok else None + return { + "ok":ok, +- "host":"geranas", ++ "host":socket.gethostname(), + "adapter_socket":str(ADAPTER_SOCKET), + "socket_mode":mode, + "mcp_transport":"streamable-http", +@@ -158,6 +158,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not ASUS_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { +@@ -243,6 +245,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not GERAMEG_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { +@@ -310,7 +314,7 @@ + sha256: str = "", + include_stdout: bool = False, + ) -> dict: +- """Administrative/lab exec on GERANAS local FBTERM.""" ++ """Historical alias for original local FBTERM execution on this receiver.""" + return _framebridge_exec_geranas_impl( + script=script, + authorized=authorized, +@@ -371,13 +375,13 @@ + + + def _cloud_dsm_rpc(payload: dict) -> dict: +- """Private GERANAS local socket; root-only approval never exposed through MCP.""" ++ """Optional private local socket; root-only approval never exposed through MCP.""" + import socket as _socket + import json as _json + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_STREAM) + sock.settimeout(38) + try: +- sock.connect("/run/framebridge-dsm/cloud.sock") ++ sock.connect(os.environ.get("FRAMEBRIDGE_CLOUD_DSM_SOCKET", str(HOME / ".local/share/framebridge/dsm/cloud.sock"))) + body = (_json.dumps(payload, separators=(",",":"),ensure_ascii=True) + "\n").encode("utf8") + if len(body)>12000: + return {"ok":False,"error":"CLOUD_REQUEST_TOO_LARGE","target":"CLOUD"} +@@ -428,9 +432,15 @@ + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, +- target: str = "ASUS", ++ target: str = "LOCAL", + ) -> dict: +- """Generic administrative/lab exec. target=ASUS or GERAMEG uses broker; target=GERANAS uses local FBTERM. Legacy calls without target remain ASUS. CLOUD uses only typed JSON actions, never shell commands.""" ++ """Original execution on this receiver by default. ASUS/GERAMEG require explicit broker configuration; GERANAS remains a historical local alias. CLOUD accepts typed JSON only.""" ++ if target == "LOCAL": ++ result = _framebridge_exec_geranas_impl(script, authorized, timeout_s, job_id, sha256, include_stdout) ++ if isinstance(result, dict): ++ result = dict(result) ++ result["target"] = "LOCAL" ++ return result + if target == "CLOUD": + import json as _json + try: +@@ -458,7 +468,7 @@ + result["target"] = "GERANAS" + return result + if target not in ("ASUS", "GERAMEG"): +- return {"ok": False, "error": "UNSUPPORTED_TARGET", "allowed_targets": ["ASUS", "GERANAS", "GERAMEG", "CLOUD"]} ++ return {"ok": False, "error": "UNSUPPORTED_TARGET", "allowed_targets": ["LOCAL", "ASUS", "GERANAS", "GERAMEG", "CLOUD"]} + exec_request = asus_exec_request if target == "ASUS" else gerameg_exec_request + result=exec_request( + script=script, +@@ -510,6 +520,40 @@ + return result + + ++def local_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: ++ if authorized is not True: ++ return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} ++ if not isinstance(cua,dict): ++ return {"ok":False,"error":"CUA_PAYLOAD_REQUIRED"} ++ action=str(cua.get("action") or "").upper().strip() ++ if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: ++ return {"ok":False,"error":"CUA_ACTION_UNSUPPORTED"} ++ try: ++ timeout_s=int(timeout_s) ++ except Exception: ++ return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} ++ if not 1<=timeout_s<=180: ++ return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} ++ if not job_id: ++ seed=json.dumps({"action":action,"time":time.time_ns()},sort_keys=True).encode() ++ digest=hashlib.sha256(seed).hexdigest() ++ job_id="cua-local-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] ++ if not JOB_RE.fullmatch(job_id): ++ return {"ok":False,"error":"INVALID_JOB_ID"} ++ ++ result=adapter_request({ ++ "version":1,"mode":"EXEC","request_type":"cua", ++ "authorized":True,"job_id":job_id,"timeout_s":timeout_s,"cua":cua, ++ }) ++ if not isinstance(result,dict): ++ return {"ok":False,"error":"CUA_LOCAL_ADAPTER_RESULT_INVALID","target":"LOCAL"} ++ value=dict(result) ++ frame_output=str(value.pop("frame_output","")) ++ if frame_output: ++ value.update(_cua_frame_fields(frame_output)) ++ value["target"]="LOCAL" ++ return value ++ + def asus_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} +@@ -531,6 +575,8 @@ + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + ++ if not ASUS_NODE_ID: ++ return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-cua", + { +@@ -607,7 +653,7 @@ + timeout_s: int = 60, + job_id: str = "", + ): +- """CUA R8 on ASUS through GERANAS. Requires explicit per-use chat authorization.""" ++ """Original CUA R8 on this receiver through its local Tool Adapter.""" + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + action=str(action).upper().strip() +@@ -659,7 +705,7 @@ + "button":str(button).lower(),"steps":int(steps),"duration_ms":int(duration_ms), + }) + +- result=asus_cua_request(payload,True,timeout_s,job_id) ++ result=local_cua_request(payload,True,timeout_s,job_id) + if bool(observe_after) and action!="OBSERVE" and bool(result.get("ok")): + post=dict(payload) + post.clear() +@@ -667,7 +713,7 @@ + if expected_window_id: + post["expected_window_id"]=str(expected_window_id) + post_id=(str(job_id or "cua")[:72]+"-post")[:80] +- post_result=asus_cua_request(post,True,timeout_s,post_id) ++ post_result=local_cua_request(post,True,timeout_s,post_id) + result["post_ok"]=bool(post_result.get("ok")) + result["post_resultado_final"]=post_result.get("resultado_final") + for key_name in ( +@@ -742,8 +788,9 @@ + async def framebridge_fleet_node_service_status(node: str, service: str) -> dict: + return await hub_call("framebridge_fleet_node_service_status",{"node":node,"service":service}) + +-from gerameg_cua_frontdoor import register_tools as register_gerameg_cua_tools +-register_gerameg_cua_tools(mcp, gerameg_exec_request) ++if os.environ.get("FRAMEBRIDGE_ENABLE_GERAMEG_CUA_TTY", "") == "1": ++ from gerameg_cua_frontdoor import register_tools as register_gerameg_cua_tools ++ register_gerameg_cua_tools(mcp, gerameg_exec_request) + + if __name__ == "__main__": + mcp.run(transport="streamable-http") diff --git a/source/portable/README-PORTABILIDAD.md b/source/portable/README-PORTABILIDAD.md new file mode 100644 index 0000000..94a6165 --- /dev/null +++ b/source/portable/README-PORTABILIDAD.md @@ -0,0 +1,21 @@ +# FrameBridge Original Expanded — portability + +PROJECT=FRAMEBRIDGE_ORIGINAL_EXPANDED +STATUS=EXPANDED_PORTABILITY_AND_VALIDATION_SCOPE_DOCUMENTED +DATE=2026-10-21 + +The supported target is Debian 13 amd64 with Python 3.13. Vendored native Python extensions require that ABI. Other platforms are not certified by this package. + +Use the English first-use assistant with framebridge or fb. The full installation, role, manual ChatGPT connection and terminal workflow is in docs/DEBIAN-TUTORIAL.md; installed users can run framebridge tutorial. + +The default ports are MCP 20865, fleet hub 20877 and broker 20878. Setup can select different ports. Standalone and central profiles expose local MCP; enrolled nodes are reached through their central instance. + +Headless auto mode reuses a usable X11 display or creates a private Xvfb/Openbox/DBus environment for the original graphical tools. Original FBTERM still uses GNOME Terminal and wmctrl. Real CUA-TTY Bash/tmux sessions do not require X. + +CUA-TTY real-terminal support is included in Expanded. It uses the configured user's identity and a private local socket; there is no fixed UID or phone requirement. Fleet access retains authenticated transport and explicit node-identity approval. Central setup can generate local TLS material; nodes receive only its public CA certificate and verify the published DER fingerprint. + +ChatGPT connection is manual and runs in the foreground. A tunnel ID or supported tunnel-client command is parsed locally; the runtime API key is read with a hidden prompt and kept in process memory only. + +Live profile state, keys, enrollment identities and runtime sessions are not release inputs. Do not copy them into an installer or source archive. + +Consult docs/VALIDATION.md for actual test coverage. The historical R1 Front Door patch is provenance; it is not the Expanded implementation diff. diff --git a/source/portable/front_door_mcp.py b/source/portable/front_door_mcp.py new file mode 100644 index 0000000..10d92f2 --- /dev/null +++ b/source/portable/front_door_mcp.py @@ -0,0 +1,841 @@ +#!/usr/bin/env python3 +import base64 +import hashlib +import json +import os +import re +import socket +import time +import urllib.error +import urllib.request +from pathlib import Path + +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from mcp.server.fastmcp import FastMCP, Image +from mcp.types import ToolAnnotations +from framebridge_expanded import __version__ as PRODUCT_VERSION +from framebridge_expanded.guide import GUIDE_SCHEMA, get_guide +from framebridge_expanded.fleet import execute_node as expanded_execute_node, cua_node as expanded_cua_node, bootstrap_original +from framebridge_expanded.fleet import execute_screen_node as expanded_execute_screen_node +from framebridge_expanded.runtime import adapter_request as checked_cua_adapter_request +bootstrap_original() + +HOME = Path.home() +ADAPTER_SOCKET = Path(os.environ.get("FRAMEBRIDGE_TOOL_ROOT", str(HOME / ".local/share/framebridge/tool-adapter"))) / "adapter.sock" +JOBS_ROOT = HOME / ".local/share/framebridge/fbterm/jobs" +HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:20877/mcp") +BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:20878") +ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "") +GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "") +HOST = os.environ.get("FRAMEBRIDGE_FRONT_DOOR_HOST", "127.0.0.1") +PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "20865")) +JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +SENSITIVE = re.compile(r"(?i)(secret|token|password|passwd|api[_-]?key|private[_-]?key|cookie|authorization|credential)") +KEY_BEGIN = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") +KEY_END = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + +mcp = FastMCP( + "FrameBridge", + instructions=( + "FrameBridge provides a shared command-line interface and tools for human operators and authorized agents. " + "fb and framebridge are equivalent commands. Before the first operation, call framebridge_help(topic='start'), " + "then read the relevant topic. Confirm the receiver, selected target, installed version and available capabilities. " + "framebridge_exec defaults to LOCAL, meaning this receiver; other targets must be confirmed enrolled aliases or IDs. " + "Use the installed CLI and tools for normal work; use a separate source clone when the owner requests software development. " + "The six Fleet inspection tools are read-only. Guide instructions do not grant authorization." + ), + website_url="https://framebridge.betancourt.one", + host=HOST, + port=PORT, + streamable_http_path="/mcp", +) +# The bundled FastMCP constructor does not expose a version argument. Its +# bundled low-level server does; publish the product build, not the SDK version. +mcp._mcp_server.version = PRODUCT_VERSION + + +def _registered_operations() -> list[str]: + """Registered operations are not proof of their current execution readiness.""" + return sorted(mcp._tool_manager._tools) + + +@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, + idempotentHint=True, openWorldHint=False)) +def framebridge_help(topic: str = "start") -> dict: + """Read the versioned English guide before operating FrameBridge. + + Pure documentation: no authentication, service startup, profile changes or + terminal creation. Topics include commands, connection, lifecycle, nodes, + terminals, screens, diagnostics, security and compatibility. The guide is + shared with the human fb/framebridge CLI and installed offline help. + """ + result = get_guide(topic) + result["receiver"] = socket.gethostname() + result["registered_operations"] = _registered_operations() + result["capability_observation"] = "registered operations; execution readiness not checked" + return result + + +def adapter_request(payload: dict) -> dict: + if not ADAPTER_SOCKET.is_socket(): + return {"ok": False, "adapter_error": "ADAPTER_SOCKET_NOT_AVAILABLE"} + raw=(json.dumps(payload, ensure_ascii=True, separators=(",",":"))+"\n").encode() + s=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.settimeout(float(payload.get("timeout_s",120))+60.0) + data=bytearray() + try: + s.connect(str(ADAPTER_SOCKET)) + s.sendall(raw) + s.shutdown(socket.SHUT_WR) + while True: + chunk=s.recv(65536) + if not chunk: + break + data.extend(chunk) + if len(data)>2_000_000: + raise RuntimeError("ADAPTER_RESPONSE_TOO_LARGE") + finally: + s.close() + return json.loads(bytes(data).decode()) + +def sanitize_text(text: str) -> str: + out=[] + in_key=False + for line in text.splitlines(): + if KEY_BEGIN.search(line): + in_key=True + out.append("[REDACTED PRIVATE KEY BLOCK]") + continue + if in_key: + if KEY_END.search(line): + in_key=False + continue + if SENSITIVE.search(line) and ("=" in line or ":" in line): + out.append("[REDACTED SENSITIVE LINE]") + else: + out.append(line) + return "\n".join(out) + +def stdout_result(job_id: str) -> dict: + p=JOBS_ROOT/job_id/"stdout.bin" + if not p.is_file(): + return {"stdout":"","stdout_sanitized":False,"stdout_truncated":False,"stdout_omitted_reason":"NOT_AVAILABLE"} + b=p.read_bytes() + truncated=len(b)>1_048_576 + if truncated: + b=b[:1_048_576] + text=sanitize_text(b.decode("utf-8","replace")) + if len(text)>4096: + text=text[:4096] + truncated=True + return {"stdout":text,"stdout_sanitized":True,"stdout_truncated":truncated,"stdout_omitted_reason":None} + +@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, + idempotentHint=True, openWorldHint=False)) +def framebridge_status() -> dict: + """Inspect this MCP receiver and registration; use CLI status for lifecycle detail. + + A successful query does not certify connection or tool readiness. This query + checks only adapter-socket presence and does not start or repair anything. + """ + present = ADAPTER_SOCKET.is_socket() + return { + "ok": True, + "query_success": True, + "product": "FrameBridge", + "version": PRODUCT_VERSION, + "guide_schema": GUIDE_SCHEMA, + "receiver": socket.gethostname(), + "adapter_socket_present": present, + "adapter_ready": "not_checked", + "mcp_transport": "streamable-http", + "registered_operations": _registered_operations(), + "lifecycle_detail_command": "fb --json status", + "help_topic": "start", + } + + +def broker_post(path: str, payload: dict, timeout_s: float=10.0) -> dict: + req=urllib.request.Request( + BROKER_CONTROL.rstrip("/")+"/"+path.lstrip("/"), + data=json.dumps(payload,sort_keys=True,separators=(",",":")).encode("utf-8"), + headers={"Content-Type":"application/json","Accept":"application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req,timeout=timeout_s) as resp: + out=json.loads(resp.read(2_000_000).decode("utf-8")) + except urllib.error.HTTPError as exc: + try: + out=json.loads(exc.read(65536).decode("utf-8")) + except Exception: + out={"ok":False,"error":"BROKER_HTTP_"+str(exc.code)} + except Exception as exc: + return {"ok":False,"error":"BROKER_CALL_FAILED","detail":type(exc).__name__+":"+str(exc)[:300]} + return out if isinstance(out,dict) else {"ok":False,"error":"BROKER_RESPONSE_INVALID"} + + +def asus_exec_request( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", +) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode("utf-8") + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="asus-mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + if not ASUS_NODE_ID: + return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { + "node_id":ASUS_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "script":script, + "sha256":digest, + "timeout_s":timeout_s, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"ASUS_EXEC_QUEUE_FAILED","broker":queued} + request_id=str(queued.get("request_id") or "") + if not request_id: + return {"ok":False,"error":"ASUS_EXEC_REQUEST_ID_MISSING"} + + deadline=time.monotonic()+timeout_s+75 + while time.monotonic() dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode("utf-8") + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="gerameg-mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + if not GERAMEG_NODE_ID: + return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { + "node_id":GERAMEG_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "script":script, + "sha256":digest, + "timeout_s":timeout_s, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"GERAMEG_EXEC_QUEUE_FAILED","broker":queued} + request_id=str(queued.get("request_id") or "") + if not request_id: + return {"ok":False,"error":"GERAMEG_EXEC_REQUEST_ID_MISSING"} + + deadline=time.monotonic()+timeout_s+75 + while time.monotonic() dict: + """Historical alias for original local FBTERM execution on this receiver.""" + return _framebridge_exec_geranas_impl( + script=script, + authorized=authorized, + timeout_s=timeout_s, + job_id=job_id, + sha256=sha256, + include_stdout=include_stdout, + ) + + +def _framebridge_exec_geranas_impl( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, +) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode() + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + req={ + "version":1, + "job_id":job_id, + "mode":"EXEC", + "authorized":True, + "timeout_s":timeout_s, + "sha256":digest, + "script":script, + } + result=adapter_request(req) + result["mcp_job_id"]=job_id + result["mcp_script_sha256"]=digest + if isinstance(result.get("result_final"),str): + result["result_final"]=sanitize_text(result["result_final"]) + if include_stdout is True: + result.update(stdout_result(job_id)) + else: + result.update({"stdout":"","stdout_sanitized":False,"stdout_truncated":False,"stdout_omitted_reason":"NOT_REQUESTED"}) + return result + + +def _cloud_dsm_rpc(payload: dict) -> dict: + """Optional private local socket; root-only approval never exposed through MCP.""" + import socket as _socket + import json as _json + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_STREAM) + sock.settimeout(38) + try: + sock.connect(os.environ.get("FRAMEBRIDGE_CLOUD_DSM_SOCKET", str(HOME / ".local/share/framebridge/dsm/cloud.sock"))) + body = (_json.dumps(payload, separators=(",",":"),ensure_ascii=True) + "\n").encode("utf8") + if len(body)>12000: + return {"ok":False,"error":"CLOUD_REQUEST_TOO_LARGE","target":"CLOUD"} + sock.sendall(body) + result = b"" + while len(result)<32768 and b"\n" not in result: + part=sock.recv(8192) + if not part:break + result+=part + if b"\n" not in result: + return {"ok":False,"error":"CLOUD_RESPONSE_INCOMPLETE","target":"CLOUD"} + output = _json.loads(result.split(b"\n",1)[0]) + if not isinstance(output,dict): + return {"ok":False,"error":"CLOUD_RESPONSE_INVALID","target":"CLOUD"} + output["target"]="CLOUD" + return output + except (OSError,ValueError,UnicodeError) as e: + return {"ok":False,"error":"CLOUD_DSM_UNAVAILABLE", + "error_type":type(e).__name__,"target":"CLOUD"} + finally: + sock.close() + + +def framebridge_cloud_dsm(action: str, authorized: bool = False, params_json: str = "{}") -> dict: + """CLOUD XPEnology agentless DSM: persistent agentless DSM with temporary owner 2FA; typed reads and Owner-MFA gated writes. + action: status,system_info,file_station_info,shares,list_folder,users, + create_folder,rename_folder,pending. + params_json: JSON object (no passwords/OTP). Writes require local owner OTP approval. + """ + import json as _json + try: + params = _json.loads(params_json) + if not isinstance(params,dict) or len(params_json)>2000: + return {"ok":False,"error":"CLOUD_PARAMETERS_INVALID"} + except (ValueError,TypeError): + return {"ok":False,"error":"CLOUD_PARAMETERS_JSON_INVALID"} + if action=="approve": + return {"ok":False,"error":"OWNER_APPROVAL_LOCAL_TTY_ONLY"} + return _cloud_dsm_rpc({"action":action,"authorized":bool(authorized),"params":params}) + + +@mcp.tool() +def framebridge_exec( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, + target: str = "LOCAL", +) -> dict: + """Execute through original local FBTERM or a confirmed Fleet node alias.""" + if target == "LOCAL": + result = _framebridge_exec_geranas_impl(script, authorized, timeout_s, job_id, sha256, include_stdout) + if isinstance(result, dict): + result = dict(result) + result["target"] = "LOCAL" + return result + return expanded_execute_node(target, script, authorized, timeout_s, job_id, sha256, include_stdout) + + +def _cua_frame_fields(frame_output: str) -> dict: + result={} + safe_lines=[] + for line in str(frame_output or "").splitlines(): + if "=" not in line: + safe_lines.append(line) + continue + key,value=line.split("=",1) + key=key.strip() + if key=="BASE64": + result["image_base64"]=value.strip() + continue + if key=="MIME": + result["image_mime"]=value.strip() + if key in { + "RESULTADO_FINAL","VISUAL_CAPTURE_GATE","CUA_PROTOCOL_VERSION", + "CHAT_AUTHORIZATION_GATE","CHAT_AUTHORIZATION_MODE","ACTION","SEMANTIC", + "COORD_SPACE","COORD_SPACE_WIDTH","COORD_SPACE_HEIGHT", + "REQUESTED_X","REQUESTED_Y","REQUESTED_X1","REQUESTED_Y1", + "REQUESTED_X2","REQUESTED_Y2","ROOT_X","ROOT_Y","ROOT_X1","ROOT_Y1", + "ROOT_X2","ROOT_Y2","BUTTON","KEY","DIRECTION","CLICKS","CHAR_COUNT", + "TYPE_MODE","WIDTH","HEIGHT","SOURCE_X","SOURCE_Y","SOURCE_WIDTH", + "SOURCE_HEIGHT","DISPLAY_WIDTH","DISPLAY_HEIGHT","CAPTURE_SCOPE", + "COORD_SPACE_DEFAULT","COORD_SPACE_AVAILABLE","WINDOW_X","WINDOW_Y", + "WINDOW_WIDTH","WINDOW_HEIGHT","ACTIVE_WINDOW_ID","ACTIVE_WINDOW_ID_BEFORE", + "ACTIVE_WINDOW_ID_AFTER","WINDOW_TITLE","ACTIVE_WINDOW_TITLE_BEFORE", + "ACTIVE_WINDOW_TITLE_AFTER","CURSOR_X","CURSOR_Y","SHA256","BYTES","MIME", + "EXPECTED_WINDOW_GATE","EXPECTED_SOURCE_GEOMETRY_GATE", + }: + result[key.lower()]=sanitize_text(value.strip()) + safe_lines.append(line) + result["frame_output"]=sanitize_text("\n".join( + line for line in safe_lines if not line.startswith("BASE64=") + )) + return result + + +def local_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(cua,dict): + return {"ok":False,"error":"CUA_PAYLOAD_REQUIRED"} + action=str(cua.get("action") or "").upper().strip() + if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: + return {"ok":False,"error":"CUA_ACTION_UNSUPPORTED"} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not 1<=timeout_s<=180: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not job_id: + seed=json.dumps({"action":action,"time":time.time_ns()},sort_keys=True).encode() + digest=hashlib.sha256(seed).hexdigest() + job_id="cua-local-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + result=checked_cua_adapter_request({ + "version":1,"mode":"EXEC","request_type":"cua", + "authorized":True,"job_id":job_id,"timeout_s":timeout_s,"cua":cua, + }) + if not isinstance(result,dict): + return {"ok":False,"error":"CUA_LOCAL_ADAPTER_RESULT_INVALID","target":"LOCAL"} + value=dict(result) + frame_output=str(value.pop("frame_output","")) + if frame_output: + value.update(_cua_frame_fields(frame_output)) + value["target"]="LOCAL" + return value + +def dispatch_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str, target: str = "LOCAL") -> dict: + """Reuse the original local CUA contract and route confirmed remote nodes.""" + if target == "LOCAL": + return local_cua_request(cua, authorized, timeout_s, job_id) + result = expanded_cua_node(target, cua, authorized, timeout_s, job_id) + if not isinstance(result, dict): + return {"ok":False,"error":"NODE_CUA_RESPONSE_INVALID"} + result = dict(result) + frame_output = str(result.pop("frame_output", "")) + if frame_output: + result.update(_cua_frame_fields(frame_output)) + return result + + +def asus_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(cua,dict): + return {"ok":False,"error":"CUA_PAYLOAD_REQUIRED"} + action=str(cua.get("action") or "").upper().strip() + if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: + return {"ok":False,"error":"CUA_ACTION_UNSUPPORTED"} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not 1<=timeout_s<=180: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not job_id: + seed=json.dumps({"action":action,"time":time.time_ns()},sort_keys=True).encode() + digest=hashlib.sha256(seed).hexdigest() + job_id="cua-geranas-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + if not ASUS_NODE_ID: + return {"ok":False,"error":"REMOTE_NODE_NOT_CONFIGURED"} + queued=broker_post( + "/fbpair/v1/control/admin-cua", + { + "node_id":ASUS_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "timeout_s":timeout_s, + "cua":cua, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"ASUS_CUA_QUEUE_FAILED","broker":queued} + rid=str(queued.get("request_id") or "") + deadline=time.monotonic()+timeout_s+75 + while time.monotonic()0: payload["preview_width"]=int(preview_width) + if preview_height>0: payload["preview_height"]=int(preview_height) + if expected_window_id: payload["expected_window_id"]=str(expected_window_id) + if expected_source_width>0: payload["expected_source_width"]=int(expected_source_width) + if expected_source_height>0: payload["expected_source_height"]=int(expected_source_height) + + if action in {"CLICK","DOUBLE_CLICK"}: + if x<0 or y<0: return {"ok":False,"error":"CLICK_COORDINATES_REQUIRED"} + payload.update({"x":int(x),"y":int(y),"button":str(button).lower()}) + elif action=="TYPE": + if not text: return {"ok":False,"error":"TYPE_TEXT_REQUIRED"} + tm=str(type_mode).upper().strip() + if tm not in {"KEYS","CLIPBOARD_EXACT"}: return {"ok":False,"error":"TYPE_MODE_INVALID"} + payload.update({"text":str(text),"type_mode":tm}) + elif action=="KEY": + if not key: return {"ok":False,"error":"KEY_REQUIRED"} + payload["key"]=str(key) + elif action=="SCROLL": + payload["direction"]=str(direction).upper() + payload["steps"]=int(steps) + payload["clicks"]=int(clicks) + if x>=0 and y>=0: + payload.update({"x":int(x),"y":int(y)}) + elif x>=0 or y>=0: + return {"ok":False,"error":"SCROLL_POINT_INCOMPLETE"} + elif action=="DRAG": + if min(x1,y1,x2,y2)<0: return {"ok":False,"error":"DRAG_COORDINATES_REQUIRED"} + payload.update({ + "x1":int(x1),"y1":int(y1),"x2":int(x2),"y2":int(y2), + "button":str(button).lower(),"steps":int(steps),"duration_ms":int(duration_ms), + }) + + result=dispatch_cua_request(payload,True,timeout_s,job_id,target) + if bool(observe_after) and action!="OBSERVE" and bool(result.get("ok")): + post=dict(payload) + post.clear() + post={"action":"OBSERVE","capture_scope":observe_scope} + if expected_window_id: + post["expected_window_id"]=str(expected_window_id) + post_id=(str(job_id or "cua")[:72]+"-post")[:80] + post_result=dispatch_cua_request(post,True,timeout_s,post_id,target) + result["post_ok"]=bool(post_result.get("ok")) + result["post_resultado_final"]=post_result.get("resultado_final") + for key_name in ( + "image_base64","image_mime","source_width","source_height", + "display_width","display_height","capture_scope","active_window_id", + "window_title","sha256","bytes" + ): + if key_name in post_result: + result[key_name]=post_result[key_name] + + image_b64=result.get("image_base64") + mime=str(result.get("image_mime") or "") + if isinstance(image_b64,str) and image_b64: + try: + data=base64.b64decode(image_b64,validate=True) + fmt="jpeg" if mime=="image/jpeg" else ("png" if mime=="image/png" else None) + if fmt: + meta=dict(result) + meta.pop("image_base64",None) + return [meta, Image(data=data,format=fmt)] + except Exception: + result["image_decode_error"]="INVALID_BASE64" + return result + + +async def hub_call(tool: str, args: dict) -> dict: + try: + async with streamablehttp_client(HUB_URL) as streams: + async with ClientSession(streams[0], streams[1]) as session: + await session.initialize() + result=await session.call_tool(tool,args) + except Exception as exc: + return {"ok":False,"error":"FLEET_HUB_CALL_FAILED","detail":str(exc)[:500]} + if bool(getattr(result,"isError",False)): + return {"ok":False,"error":"FLEET_HUB_TOOL_ERROR"} + structured=getattr(result,"structuredContent",None) + if isinstance(structured,dict): + return structured + for item in getattr(result,"content",[]): + txt=getattr(item,"text",None) + if isinstance(txt,str): + try: + obj=json.loads(txt) + except Exception: + continue + if isinstance(obj,dict): + return obj + return {"ok":True,"result":obj} + return {"ok":False,"error":"FLEET_HUB_EMPTY_RESULT"} + +@mcp.tool() +async def framebridge_fleet_nodes() -> dict: + """Read enrolled node inventory from this profile hub; no enrollment or changes.""" + return await hub_call("framebridge_fleet_nodes",{}) + +@mcp.tool() +async def framebridge_fleet_node_system_status(node: str) -> dict: + """Read a confirmed node system status without changing its configuration.""" + return await hub_call("framebridge_fleet_node_system_status",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_hardware_summary(node: str) -> dict: + """Read a confirmed node hardware summary; this operation is observational.""" + return await hub_call("framebridge_fleet_node_hardware_summary",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_storage_inspect(node: str) -> dict: + """Inspect node storage metadata; never partition, format, mount or repair.""" + return await hub_call("framebridge_fleet_node_storage_inspect",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_network_inspect(node: str) -> dict: + """Inspect confirmed node network state; no address, routing or firewall changes.""" + return await hub_call("framebridge_fleet_node_network_inspect",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_service_status(node: str, service: str) -> dict: + """Read a named service status on a confirmed node; never start, stop or enable it.""" + return await hub_call("framebridge_fleet_node_service_status",{"node":node,"service":service}) + +def _screen_exec_request( + *, script: str, authorized: bool, timeout_s: int = 120, + job_id: str = "", sha256: str = "", include_stdout: bool = False, + target: str = "LOCAL", +) -> dict: + """Keep authenticated Fleet identity without opening an EXEC terminal.""" + return expanded_execute_screen_node(target, script, authorized, timeout_s, + job_id, sha256, include_stdout) + + +from framebridge_expanded.real_screen.screen_frontdoor import register_framebridge_screen_tools, resolve_profile_node +_real_screen_client = register_framebridge_screen_tools(mcp, _screen_exec_request, image_factory=Image, resolve_node=resolve_profile_node) +from framebridge_expanded.real_screen.screen_presentation import register_screen_presentation +register_screen_presentation(mcp, _real_screen_client) + +from framebridge_expanded.tty_mcp import register_tools as register_tty_tools +register_tty_tools(mcp, framebridge_exec) + +if __name__ == "__main__": + mcp.run(transport="streamable-http") diff --git a/source/portable/gerameg_cua_frontdoor.py b/source/portable/gerameg_cua_frontdoor.py new file mode 100644 index 0000000..a1dc7a4 --- /dev/null +++ b/source/portable/gerameg_cua_frontdoor.py @@ -0,0 +1,177 @@ +"""Siete herramientas CUA-TTY ligadas a GERAMEG, sobre FBEXEC original.""" +from __future__ import annotations +import base64 +import hashlib +import json +import re +import shlex +import uuid + +RPC_PATH = ".local/lib/framebridge-gerameg-original/cua_tty_rpc.py" +PROTOCOL = "FB-CUA-TTY-RPC/1" +MAX_BYTES = 65536 +CHUNK_BYTES = 2100 +SID = re.compile(r"[A-Za-z0-9_-]{12,80}\Z") +REPLY_ID = re.compile(r"[0-9a-f]{32}\Z") +SHA = re.compile(r"[0-9a-f]{64}\Z") +KEYS = frozenset({"UP", "DOWN", "LEFT", "RIGHT", "ENTER", "TAB", "ESC", "BACKSPACE", "CTRL_C"}) +READ = {"readOnlyHint": True, "destructiveHint": False, "idempotentHint": False, "openWorldHint": False} +CHANGE = {**READ, "readOnlyHint": False} +STOP = {**CHANGE, "destructiveHint": True} + + +class CuaError(RuntimeError): + pass + + +def require(ok, code): + if not ok: + raise CuaError(code) + + +def encode(value): + return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("ascii") + + +def _remote_script(argv): + """Resolve the helper under the remote execution account's home directory.""" + command = shlex.join(["/usr/bin/python3", "-B"]) + command += ' "${HOME:?}/' + RPC_PATH + '"' + if argv: + command += " " + shlex.join(argv) + return command + "\n" + + +def register_tools(mcp, exec_request): + """exec_request es DI del bootstrap original, nunca un parámetro MCP.""" + + def remote(argv): + script = _remote_script(argv) + job_id = "cua-gerameg-" + uuid.uuid4().hex + digest = hashlib.sha256(script.encode("utf-8")).hexdigest() + result = exec_request(script=script, authorized=True, timeout_s=15, job_id=job_id, sha256=digest) + require(type(result) is dict and result.get("ok") is True, "CUA_TTY_EXEC_FAILED") + exit_code = result.get("exit_code") + require(type(exit_code) in (int, str) and str(exit_code) == "0", "CUA_TTY_EXEC_FAILED") + require(result.get("target") == "GERAMEG" and result.get("mcp_job_id") == job_id and result.get("mcp_script_sha256") == digest, "CUA_TTY_EXEC_BINDING_INVALID") + stdout = result.get("stdout") + require(type(stdout) is str and len(stdout) <= 4096 and result.get("stdout_truncated") is not True, "CUA_TTY_STDOUT_INVALID") + try: + envelope = json.loads(stdout) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_ENVELOPE_INVALID") from None + require(type(envelope) is dict and envelope.get("protocol") == PROTOCOL, "CUA_TTY_ENVELOPE_INVALID") + if envelope.get("ok") is not True: + code = envelope.get("error") + if type(code) is str and re.fullmatch(r"[A-Z0-9_]{1,80}", code): + raise CuaError("CUA_TTY_" + code) + raise CuaError("CUA_TTY_RPC_FAILED") + return envelope + + def unpack(envelope): + total, digest = envelope.get("bytes"), envelope.get("sha256") + require(type(total) is int and 0 < total <= MAX_BYTES and type(digest) is str and SHA.fullmatch(digest) is not None, "CUA_TTY_MANIFEST_INVALID") + if envelope.get("kind") == "inline": + text = envelope.get("data_b64") + require(type(text) is str and len(text) <= 3000, "CUA_TTY_RESPONSE_INVALID") + try: + data = base64.b64decode(text, validate=True) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_RESPONSE_INVALID") from None + elif envelope.get("kind") == "file": + reply_id = envelope.get("reply_id") + require(type(reply_id) is str and REPLY_ID.fullmatch(reply_id) is not None, "CUA_TTY_REPLY_ID_INVALID") + pieces = [] + for offset in range(0, total, CHUNK_BYTES): + part = remote(["read", reply_id, str(offset)]) + require(part.get("kind") == "chunk" and part.get("reply_id") == reply_id and type(part.get("offset")) is int and part["offset"] == offset, "CUA_TTY_CHUNK_BINDING_INVALID") + require(type(part.get("bytes")) is int and part["bytes"] == total and part.get("sha256") == digest, "CUA_TTY_CHUNK_MANIFEST_INVALID") + text = part.get("data_b64") + require(type(text) is str and len(text) <= 2800, "CUA_TTY_CHUNK_INVALID") + try: + chunk = base64.b64decode(text, validate=True) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_CHUNK_INVALID") from None + expected = min(CHUNK_BYTES, total - offset) + require(type(part.get("length")) is int and part["length"] == expected and len(chunk) == expected, "CUA_TTY_CHUNK_LENGTH_INVALID") + pieces.append(chunk) + data = b"".join(pieces) + else: + raise CuaError("CUA_TTY_ENVELOPE_KIND_INVALID") + require(len(data) == total and hashlib.sha256(data).hexdigest() == digest, "CUA_TTY_RESPONSE_HASH_MISMATCH") + try: + reply = json.loads(data) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_RESPONSE_INVALID") from None + require(type(reply) is dict and type(reply.get("ok")) is bool, "CUA_TTY_RESPONSE_INVALID") + return reply, total, digest + + def call(op, authorized, **kwargs): + if authorized is not True: + return {"ok": False, "target": "GERAMEG", "error": "AUTHORIZED_TRUE_REQUIRED"} + try: + payload = base64.b64encode(encode({"op": op, **kwargs})).decode("ascii") + reply, total, digest = unpack(remote(["invoke", payload])) + if reply.get("ok") is not True: + error = reply.get("error") + code = error if type(error) is str and re.fullmatch(r"[A-Z0-9_]{1,80}", error) else "CUA_TTY_REQUEST_DENIED" + return {"ok": False, "target": "GERAMEG", "error": code} + require(type(reply.get("result")) is dict, "CUA_TTY_RESPONSE_INVALID") + return {"ok": True, "target": "GERAMEG", "result": reply["result"], "response_bytes": total, "response_sha256": digest} + except CuaError as exc: + return {"ok": False, "target": "GERAMEG", "error": str(exc)} + except Exception: + return {"ok": False, "target": "GERAMEG", "error": "CUA_TTY_BACKEND_UNAVAILABLE"} + + def valid_sid(session_id): + return type(session_id) is str and SID.fullmatch(session_id) is not None + + def denied(authorized, valid=True, error="CUA_TTY_ARGUMENT_INVALID"): + if authorized is not True: + return {"ok": False, "target": "GERAMEG", "error": "AUTHORIZED_TRUE_REQUIRED"} + if not valid: + return {"ok": False, "target": "GERAMEG", "error": error} + return None + + @mcp.tool(annotations=dict(READ)) + def framebridge_cua_tty_status(authorized: bool) -> dict: + """Estado CUA-TTY de GERAMEG; requiere autorización explícita del chat.""" + return call("status", authorized) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_open_demo(authorized: bool) -> dict: + """Abre demo-readonly sin privilegios en GERAMEG; no abre una shell general.""" + return call("open_demo", authorized) + + @mcp.tool(annotations=dict(READ)) + def framebridge_cua_tty_observe(session_id: str, authorized: bool) -> dict: + """Lee la pantalla textual de una sesión CUA-TTY de GERAMEG.""" + return denied(authorized, valid_sid(session_id), "CUA_TTY_SESSION_INVALID") or call("observe", authorized, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_resize(session_id: str, rows: int, cols: int, authorized: bool) -> dict: + """Redimensiona una sesión de GERAMEG dentro de 5–100 filas y 20–220 columnas.""" + valid = valid_sid(session_id) and type(rows) is int and type(cols) is int and 5 <= rows <= 100 and 20 <= cols <= 220 + return denied(authorized, valid) or call("resize", authorized, session_id=session_id, rows=rows, cols=cols) + + @mcp.tool(annotations=dict(STOP)) + def framebridge_cua_tty_abort(session_id: str, authorized: bool) -> dict: + """Cierra exclusivamente la sesión CUA-TTY indicada de GERAMEG.""" + return denied(authorized, valid_sid(session_id), "CUA_TTY_SESSION_INVALID") or call("abort", authorized, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_key_send(session_id: str, key: str, authorized: bool) -> dict: + """Envía UP/DOWN/LEFT/RIGHT/ENTER/TAB/ESC/BACKSPACE/CTRL_C en GERAMEG.""" + valid = valid_sid(session_id) and type(key) is str and key in KEYS + return denied(authorized, valid) or call("key_send", authorized, session_id=session_id, key=key) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_text_type(session_id: str, text: str, authorized: bool) -> dict: + """Escribe 1–64 caracteres sin controles en la demo de GERAMEG.""" + valid = valid_sid(session_id) and type(text) is str and 1 <= len(text) <= 64 and not any(ord(ch) < 32 or ord(ch) == 127 for ch in text) + if valid: + try: + text.encode("utf-8") + except UnicodeEncodeError: + valid = False + return denied(authorized, valid) or call("text_type", authorized, session_id=session_id, text=text) diff --git a/source/sources/original-asus/SOURCE-MANIFEST.json b/source/sources/original-asus/SOURCE-MANIFEST.json new file mode 100644 index 0000000..b4398d4 --- /dev/null +++ b/source/sources/original-asus/SOURCE-MANIFEST.json @@ -0,0 +1,125 @@ +{ + "format": "FRAMEBRIDGE_ORIGINAL_SOURCE_SNAPSHOT_V1", + "source_host": "Original desktop source capture; community derivative", + "file_count": 14, + "scan_result": "HISTORICAL_CAPTURE_CHECK_ONLY; see current community privacy review", + "scope": "PUBLIC_PROGRAM_FILES_ONLY", + "runtime_state_included": false, + "configuration_files_included": false, + "embedded_lab_route_mapping_in_host_source": false, + "files": [ + { + "archive_path": "tool-adapter/bin/framebridge_tool_adapter.py", + "source_path": "tool-adapter/bin/framebridge_tool_adapter.py", + "bytes": 10268, + "sha256": "dca9bf3dfbae7881aa6824ca79a2922fb4ec7ab1b61b5696b39cb64630c4d86b", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "tool-adapter/bin/framebridge_tool_client.py", + "source_path": "tool-adapter/bin/framebridge_tool_client.py", + "bytes": 1073, + "sha256": "1d1514f08653c31f6b67ebcdcf61686947321bec96752a692b7e46cee6285f6e", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "fbterm/bin/fbterm_engine.py", + "source_path": "fbterm/bin/fbterm_engine.py", + "bytes": 13763, + "sha256": "9408aa2dfba2ceecf1bcc4d9e6b1d52fec1f8971183f5baa7145f2d6357c7635", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "fbterm/bin/fbterm-run", + "source_path": "fbterm/bin/fbterm-run", + "bytes": 2628, + "sha256": "bbfc19c878746518e482adbe61cd6f8ae8560dca75a1e3db9921e1cb40b42e71", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "fbterm/bin/fbterm-shell", + "source_path": "fbterm/bin/fbterm-shell", + "bytes": 612, + "sha256": "eb6de30bfd6430165f7ebc982021f3565b2e80c4b82480b4e01e64e15e65957a", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "fbterm/bin/fbterm-prompt-ready", + "source_path": "fbterm/bin/fbterm-prompt-ready", + "bytes": 286, + "sha256": "276f657e23ff26c75f5e430731fdf5b095d777eabcbaad86e11c69b0919c691c", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "fbterm/bin/fbx", + "source_path": "fbterm/bin/fbx", + "bytes": 2517, + "sha256": "84589ad19ea0eab7cfc766ef4dba736be05c509c3918f572f4054585ee4c5efb", + "source_mode": "0o700", + "archive_mode": "0755" + }, + { + "archive_path": "framebridge-native/host/framebridge_host.py", + "source_path": "framebridge-native/host/framebridge_host.py", + "bytes": 45347, + "sha256": "85d165bf8875fa8f9b202b129f985ca4cf040a1f4778ef43ed4c9f5b0164b3eb", + "source_mode": "0o755", + "archive_mode": "0755" + }, + { + "archive_path": "framebridge-native/host/framebridge_cua_linux.py", + "source_path": "framebridge-native/host/framebridge_cua_linux.py", + "bytes": 45731, + "sha256": "aed73125f88b4408bad83ef4aae220fd79bd8f57e41ff15ebc9930453bdc69a1", + "source_mode": "0o600", + "archive_mode": "0644" + }, + { + "archive_path": "framebridge-native/host/framebridge_voice_command.py", + "source_path": "framebridge-native/host/framebridge_voice_command.py", + "bytes": 1127, + "sha256": "ed0aff0070799d437a5570b16dbfa0a3878e51b73166c14bebaf8df45ebdc86c", + "source_mode": "0o664", + "archive_mode": "0644" + }, + { + "archive_path": "framebridge-native/host/framebridge_voice_status.py", + "source_path": "framebridge-native/host/framebridge_voice_status.py", + "bytes": 1843, + "sha256": "f8520095e973a91249876d733143399fe40a0d72cd64c741c5cf85d8898a3ffb", + "source_mode": "0o664", + "archive_mode": "0644" + }, + { + "archive_path": "framebridge-native/host/open-terminal.sh", + "source_path": "framebridge-native/host/open-terminal.sh", + "bytes": 690, + "sha256": "facaea7a5ca6635311a4c05e18edecdec98a137ce552fce39bdbc0ee755aa34c", + "source_mode": "0o775", + "archive_mode": "0755" + }, + { + "archive_path": "framebridge-native/host/open-interactive-terminal.sh", + "source_path": "framebridge-native/host/open-interactive-terminal.sh", + "bytes": 4631, + "sha256": "e5c191c9cc354fd4d88a23c02e5eafbe454a7d3b3586265288d5f89ee9d8c602", + "source_mode": "0o775", + "archive_mode": "0755" + }, + { + "archive_path": "framebridge-native/host/open-progress-console.sh", + "source_path": "framebridge-native/host/open-progress-console.sh", + "bytes": 3488, + "sha256": "cb519186ce299e21381e4698faf5490a3e947ad01187b9674f78d4e8bc21ef65", + "source_mode": "0o775", + "archive_mode": "0755" + } + ], + "community_derivative": true +} diff --git a/source/sources/original-asus/fbterm/bin/fbterm-prompt-ready b/source/sources/original-asus/fbterm/bin/fbterm-prompt-ready new file mode 100755 index 0000000..f92d8de --- /dev/null +++ b/source/sources/original-asus/fbterm/bin/fbterm-prompt-ready @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set +e +umask 077 +ROOT="$FRAMEBRIDGE_FBTERM_ROOT" +[ -n "$ROOT" ] || ROOT="$HOME/.local/share/framebridge/fbterm" +READY="$ROOT/prompt.ready" +TMP="$ROOT/.prompt.ready.$$" +mkdir -p "$ROOT" 2>/dev/null || true +printf '%s\n' "$(date +%s%N)" > "$TMP" +mv -f "$TMP" "$READY" diff --git a/source/sources/original-asus/fbterm/bin/fbterm-run b/source/sources/original-asus/fbterm/bin/fbterm-run new file mode 100755 index 0000000..305ab0d --- /dev/null +++ b/source/sources/original-asus/fbterm/bin/fbterm-run @@ -0,0 +1,124 @@ +#!/usr/bin/env bash + +umask 077 + +ROOT="${FBTERM_ROOT:?FBTERM_ROOT no definido}" + +# R2: mientras fbterm-run esta activo, el pane no esta disponible. +rm -f "$ROOT/prompt.ready" +JOB_ID="${1:-}" + +case "$JOB_ID" in + ''|*[!A-Za-z0-9._-]*) + printf 'FBTERM_ERROR=INVALID_JOB_ID\n' >&2 + exit 64 + ;; +esac + +JOB="$ROOT/jobs/$JOB_ID" +CMD="$JOB/command.bash" +TIMEOUT_FILE="$JOB/timeout.seconds" + +if [ ! -f "$CMD" ]; then + printf 'FBTERM_ERROR=COMMAND_NOT_FOUND JOB_ID=%s\n' "$JOB_ID" >&2 + exit 66 +fi + +TIMEOUT_S='120' + +if [ -f "$TIMEOUT_FILE" ]; then + VALUE="$(cat "$TIMEOUT_FILE" 2>/dev/null || true)" + case "$VALUE" in + ''|*[!0-9]*) + ;; + *) + if [ "$VALUE" -ge 1 ] 2>/dev/null && + [ "$VALUE" -le 3600 ] 2>/dev/null + then + TIMEOUT_S="$VALUE" + fi + ;; + esac +fi + +OUT_FIFO="$JOB/stdout.fifo" +ERR_FIFO="$JOB/stderr.fifo" + +STDOUT="$JOB/stdout.bin" +STDERR="$JOB/stderr.bin" + +rm -f "$OUT_FIFO" "$ERR_FIFO" + +mkfifo "$OUT_FIFO" "$ERR_FIFO" + +: > "$STDOUT" +: > "$STDERR" + +date -Ins > "$JOB/started.at" + +sha256sum "$CMD" | + awk '{print $1}' > "$JOB/command.sha256" + +tee "$STDOUT" < "$OUT_FIFO" & +OUT_TEE_PID=$! + +tee "$STDERR" < "$ERR_FIFO" >&2 & +ERR_TEE_PID=$! + +/usr/bin/timeout \ + --signal=TERM \ + --kill-after=2s \ + "${TIMEOUT_S}s" \ + bash --noprofile --norc "$CMD" \ + > "$OUT_FIFO" \ + 2> "$ERR_FIFO" + +RC=$? + +wait "$OUT_TEE_PID" +OUT_TEE_RC=$? + +wait "$ERR_TEE_PID" +ERR_TEE_RC=$? + +printf '%s\n' "$RC" > "$JOB/exit.code" +printf '%s\n' "$OUT_TEE_RC" > "$JOB/stdout-tee.exit" +printf '%s\n' "$ERR_TEE_RC" > "$JOB/stderr-tee.exit" + +sha256sum "$STDOUT" | + awk '{print $1}' > "$JOB/stdout.sha256" + +sha256sum "$STDERR" | + awk '{print $1}' > "$JOB/stderr.sha256" + +stat -c '%s' "$STDOUT" > "$JOB/stdout.bytes" +stat -c '%s' "$STDERR" > "$JOB/stderr.bytes" + +date -Ins > "$JOB/finished.at" + +{ + printf 'JOB_ID=%s\n' "$JOB_ID" + printf 'EXIT=%s\n' "$RC" + printf 'COMMAND_SHA256=%s\n' \ + "$(cat "$JOB/command.sha256")" + printf 'STDOUT_SHA256=%s\n' \ + "$(cat "$JOB/stdout.sha256")" + printf 'STDERR_SHA256=%s\n' \ + "$(cat "$JOB/stderr.sha256")" + printf 'STDOUT_BYTES=%s\n' \ + "$(cat "$JOB/stdout.bytes")" + printf 'STDERR_BYTES=%s\n' \ + "$(cat "$JOB/stderr.bytes")" + printf 'STDOUT_TEE_EXIT=%s\n' "$OUT_TEE_RC" + printf 'STDERR_TEE_EXIT=%s\n' "$ERR_TEE_RC" +} > "$JOB/result.tmp" + +mv "$JOB/result.tmp" "$JOB/result.env" +touch "$JOB/DONE" + +rm -f "$OUT_FIFO" "$ERR_FIFO" + +printf 'FBTERM_JOB_COMPLETE JOB_ID=%s EXIT=%s\n' \ + "$JOB_ID" "$RC" + +exit "$RC" diff --git a/source/sources/original-asus/fbterm/bin/fbterm-shell b/source/sources/original-asus/fbterm/bin/fbterm-shell new file mode 100755 index 0000000..c0de2e8 --- /dev/null +++ b/source/sources/original-asus/fbterm/bin/fbterm-shell @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +umask 077 + +export HISTFILE=/dev/null +set +o history 2>/dev/null || true + +printf '\n' +printf '========================================\n' +printf ' FrameBridge Terminal R1\n' +printf ' PTY persistente / comandos controlados\n' +printf '========================================\n' +printf '\n' + +export FRAMEBRIDGE_FBTERM_ROOT="$HOME/.local/share/framebridge/fbterm" +if [ -n "$FRAMEBRIDGE_FBTERM_ROOT_OVERRIDE" ]; then + export FRAMEBRIDGE_FBTERM_ROOT="$FRAMEBRIDGE_FBTERM_ROOT_OVERRIDE" +fi +export PROMPT_COMMAND='"$FRAMEBRIDGE_FBTERM_ROOT/bin/fbterm-prompt-ready"' + +exec bash --noprofile --norc diff --git a/source/sources/original-asus/fbterm/bin/fbterm_engine.py b/source/sources/original-asus/fbterm/bin/fbterm_engine.py new file mode 100755 index 0000000..bfb93f9 --- /dev/null +++ b/source/sources/original-asus/fbterm/bin/fbterm_engine.py @@ -0,0 +1,497 @@ +#!/usr/bin/env python3 + +import fcntl +import hashlib +import os +from pathlib import Path +import re +import subprocess +import time + + +class FBTermError(RuntimeError): + pass + + +class FBTermBusy(FBTermError): + pass + + +class FBTerm: + JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,128}$") + + def __init__(self, root, session, title): + self.root = Path(root) + self.session = session + self.title = title + + self.bin = self.root / "bin" + self.jobs = self.root / "jobs" + + self.runner = self.bin / "fbterm-run" + self.shell = self.bin / "fbterm-shell" + self.lock_path = self.root / "controller.lock" + + @staticmethod + def _run(args, **kwargs): + return subprocess.run( + args, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ) + + def has_session(self): + p = self._run( + ["tmux", "has-session", "-t", self.session] + ) + return p.returncode == 0 + + def ensure(self, visible=True): + self.root.mkdir(mode=0o700, parents=True, exist_ok=True) + self.jobs.mkdir(mode=0o700, parents=True, exist_ok=True) + + if not self.has_session(): + env = os.environ.copy() + env["FBTERM_ROOT"] = str(self.root) + env["FBTERM_SESSION"] = self.session + env["PATH"] = str(self.bin) + os.pathsep + env.get("PATH", "") + + p = subprocess.run( + [ + "tmux", + "new-session", + "-d", + "-s", + self.session, + str(self.shell), + ], + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + if p.returncode != 0: + raise FBTermError( + "tmux new-session fallo: " + p.stderr.strip() + ) + + subprocess.run( + [ + "tmux", + "set-environment", + "-t", + self.session, + "FBTERM_ROOT", + str(self.root), + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.run( + [ + "tmux", + "set-environment", + "-t", + self.session, + "FBTERM_SESSION", + self.session, + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.run( + [ + "tmux", + "set-option", + "-t", + self.session, + "allow-rename", + "off", + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.run( + [ + "tmux", + "set-option", + "-t", + self.session, + "set-titles", + "off", + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.run( + [ + "tmux", + "send-keys", + "-t", + self.session + ":0.0", + ( + "export FBTERM_ROOT=" + + subprocess.list2cmdline([str(self.root)]) + + "; export FBTERM_SESSION=" + + subprocess.list2cmdline([self.session]) + + "; export PATH=" + + subprocess.list2cmdline( + [str(self.bin) + ":$PATH"] + ) + ), + "C-m", + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + time.sleep(0.5) + + if visible and not self.visible_window(): + subprocess.Popen( + [ + "gnome-terminal", + "--title=" + self.title, + "--", + "bash", + "-lc", + ( + "exec tmux attach-session -r -t " + + subprocess.list2cmdline([self.session]) + ), + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True, + ) + + for _ in range(40): + if self.visible_window(): + break + time.sleep(0.1) + + return self.status() + + def visible_window(self): + p = self._run(["wmctrl", "-l"]) + return ( + p.returncode == 0 + and self.title in p.stdout + ) + + def status(self): + if not self.has_session(): + return { + "session": False, + "visible": False, + "pane_pid": None, + "pane_tty": None, + "pane_command": None, + "clients": 0, + "readonly_clients": 0, + } + + fmt = ( + "#{pane_pid}|#{pane_tty}|" + "#{pane_current_command}" + ) + + p = self._run( + [ + "tmux", + "display-message", + "-p", + "-t", + self.session + ":0.0", + fmt, + ] + ) + + parts = p.stdout.strip().split("|", 2) + + pane_pid = parts[0] if len(parts) > 0 else "" + pane_tty = parts[1] if len(parts) > 1 else "" + pane_command = parts[2] if len(parts) > 2 else "" + + clients = self._run( + [ + "tmux", + "list-clients", + "-t", + self.session, + "-F", + "#{client_readonly}", + ] + ) + + values = [ + x.strip() + for x in clients.stdout.splitlines() + if x.strip() + ] + + readonly = sum( + 1 + for x in values + if x in ("1", "on", "true") + ) + + return { + "session": True, + "visible": self.visible_window(), + "pane_pid": pane_pid, + "pane_tty": pane_tty, + "pane_command": pane_command, + "clients": len(values), + "readonly_clients": readonly, + } + + def execute(self, job_id, command, timeout_s=120): + if not self.JOB_RE.fullmatch(job_id): + raise FBTermError("JOB_ID invalido") + + if not isinstance(command, str) or not command: + raise FBTermError("command vacio") + + timeout_s = int(timeout_s) + + if timeout_s < 1 or timeout_s > 3600: + raise FBTermError("timeout fuera de rango") + + self.ensure(visible=True) + + lock_fd = os.open( + self.lock_path, + os.O_CREAT | os.O_RDWR, + 0o600, + ) + + try: + try: + fcntl.flock( + lock_fd, + fcntl.LOCK_EX | fcntl.LOCK_NB, + ) + except BlockingIOError: + raise FBTermBusy("FBTERM ocupado") + + # R2: fail-closed si existe evidencia durable incompleta. + # La reconciliacion conserva esos directorios fuera de jobs. + incomplete_jobs = [] + + try: + for prior in self.jobs.iterdir(): + if ( + prior.is_dir() + and ( + not (prior / "DONE").exists() + or not (prior / "result.env").exists() + ) + ): + incomplete_jobs.append(prior.name) + except OSError as exc: + raise FBTermError( + "no se pudo auditar jobs previos: " + str(exc) + ) + + if incomplete_jobs: + sample = ", ".join(sorted(incomplete_jobs)[:3]) + raise FBTermBusy( + "FBTERM tiene job incompleto: " + sample + ) + + job = self.jobs / job_id + + if job.exists(): + raise FBTermError("JOB_ID ya existe") + + job.mkdir(mode=0o700) + + command_file = job / "command.bash" + command_file.write_text( + command, + encoding="utf-8", + ) + os.chmod(command_file, 0o600) + + (job / "timeout.seconds").write_text( + str(timeout_s) + "\n", + encoding="ascii", + ) + + expected_command_sha = hashlib.sha256( + command.encode("utf-8") + ).hexdigest() + + # R2 idle gate: prompt.ready existe solo cuando el shell + # persistente esta realmente esperando el siguiente comando. + ready_path = self.jobs.parent / "prompt.ready" + ready_deadline = time.monotonic() + 3.0 + + while time.monotonic() < ready_deadline: + if ready_path.exists(): + break + time.sleep(0.02) + else: + raise FBTermBusy( + "FBTERM pane no esta en prompt" + ) + + try: + ready_path.unlink() + except FileNotFoundError: + raise FBTermBusy( + "FBTERM prompt cambio antes del envio" + ) + except OSError as exc: + raise FBTermError( + "no se pudo reservar prompt: " + str(exc) + ) + + send = self._run( + [ + "tmux", + "send-keys", + "-t", + self.session + ":0.0", + "fbterm-run " + job_id, + "C-m", + ] + ) + + if send.returncode != 0: + raise FBTermError( + "tmux send-keys fallo: " + + send.stderr.strip() + ) + + deadline = time.monotonic() + timeout_s + 8 + + while time.monotonic() < deadline: + if (job / "DONE").exists(): + break + time.sleep(0.05) + + if not (job / "DONE").exists(): + raise FBTermError( + "resultado durable no aparecio" + ) + + # R2: no liberar el lock hasta que bash haya vuelto realmente + # al prompt. fbterm-shell actualiza prompt.ready mediante + # PROMPT_COMMAND despues de que fbterm-run haya terminado. + done_path = job / "DONE" + prompt_deadline = time.monotonic() + 5.0 + prompt_ready = False + + while time.monotonic() < prompt_deadline: + try: + if ( + ready_path.exists() + and ready_path.stat().st_mtime_ns + > done_path.stat().st_mtime_ns + ): + prompt_ready = True + break + except OSError: + pass + + time.sleep(0.02) + + if not prompt_ready: + raise FBTermError( + "prompt durable no aparecio" + ) + + result = {} + + for line in (job / "result.env").read_text( + encoding="utf-8" + ).splitlines(): + if "=" in line: + k, v = line.split("=", 1) + result[k] = v + + result["EXPECTED_COMMAND_SHA256"] = ( + expected_command_sha + ) + + result["COMMAND_HASH_GATE"] = ( + "PASS" + if result.get("COMMAND_SHA256") + == expected_command_sha + else "FAIL" + ) + + result["STDOUT_PATH"] = str( + job / "stdout.bin" + ) + + result["STDERR_PATH"] = str( + job / "stderr.bin" + ) + + return result + + finally: + try: + fcntl.flock(lock_fd, fcntl.LOCK_UN) + except Exception: + pass + + os.close(lock_fd) + + +def file_text(path): + return Path(path).read_text( + encoding="utf-8", + errors="replace", + ) + + +if __name__ == "__main__": + import json + import sys + + root = os.environ["FBTERM_ROOT"] + session = os.environ["FBTERM_SESSION"] + title = os.environ["FBTERM_TITLE"] + + term = FBTerm(root, session, title) + + action = sys.argv[1] + + if action == "ensure": + print(json.dumps(term.ensure(), sort_keys=True)) + + elif action == "status": + print(json.dumps(term.status(), sort_keys=True)) + + elif action == "exec": + job_id = sys.argv[2] + timeout_s = int(sys.argv[3]) + command_path = sys.argv[4] + + try: + r = term.execute( + job_id, + file_text(command_path), + timeout_s, + ) + print(json.dumps(r, sort_keys=True)) + except FBTermBusy as e: + print(json.dumps({ + "ERROR": "BUSY", + "MESSAGE": str(e), + })) + sys.exit(75) + + else: + raise SystemExit(64) diff --git a/source/sources/original-asus/fbterm/bin/fbx b/source/sources/original-asus/fbterm/bin/fbx new file mode 100755 index 0000000..42a1e0d --- /dev/null +++ b/source/sources/original-asus/fbterm/bin/fbx @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +umask 077 + +ROOT="${FBTERM_ROOT:-$HOME/.local/share/framebridge/fbterm}" +LIB="$ROOT/library" +JOBS="$ROOT/jobs" + +valid() { + [[ "$1" =~ ^[A-Za-z0-9._-]+$ ]] +} + +secret_guard() { + grep -Eqi \ + 'BEGIN .*PRIVATE KEY|rclone\.conf|client_secret|access_token|refresh_token|password[[:space:]]*=|token[[:space:]]*=|cookie[[:space:]]*=' \ + "$1" +} + +case "${1:-}" in + list) + mkdir -p "$LIB" + find "$LIB" -maxdepth 1 -type f -name '*.sh' -printf '%f\n' | + sed 's/\.sh$//' | sort + ;; + + adopt) + job="${2:-}" + name="${3:-}" + + valid "$job" && valid "$name" || { + echo 'FBX_ERROR=INVALID_NAME' + exit 64 + } + + src="$JOBS/$job/command.bash" + dst="$LIB/$name.sh" + + [ -f "$src" ] || { + echo 'FBX_ERROR=JOB_COMMAND_NOT_FOUND' + exit 66 + } + + [ ! -e "$dst" ] || { + echo 'FBX_ERROR=LIBRARY_NAME_EXISTS' + exit 73 + } + + cp -p "$src" "$dst" + chmod 600 "$dst" + + sha256sum "$dst" | awk '{print $1}' > "$LIB/$name.sha256" + printf '%s\n' "$job" > "$LIB/$name.source-job" + + echo "FBX_ADOPT=PASS NAME=$name JOB=$job SHA256=$(cat "$LIB/$name.sha256")" + ;; + + hash) + name="${2:-}" + valid "$name" || exit 64 + file="$LIB/$name.sh" + [ -f "$file" ] || exit 66 + sha256sum "$file" + ;; + + show|run) + action="$1" + name="${2:-}" + + valid "$name" || { + echo 'FBX_ERROR=INVALID_NAME' + exit 64 + } + + file="$LIB/$name.sh" + hashfile="$LIB/$name.sha256" + + [ -f "$file" ] && [ -f "$hashfile" ] || { + echo 'FBX_ERROR=LIBRARY_ITEM_NOT_FOUND' + exit 66 + } + + expected="$(cat "$hashfile")" + actual="$(sha256sum "$file" | awk '{print $1}')" + + [ "$actual" = "$expected" ] || { + echo 'FBX_ERROR=HASH_MISMATCH' + exit 74 + } + + if secret_guard "$file"; then + echo 'FBX_ERROR=POSSIBLE_SECRET_REFUSE_DISPLAY' + exit 77 + fi + + echo + echo "========== FBX SCRIPT: $name ==========" + echo "SHA256=$actual" + echo + cat "$file" + echo + echo "========== END FBX SCRIPT ==========" + echo + + [ "$action" = show ] && exit 0 + + bash --noprofile --norc "$file" + rc=$? + + echo + echo "========== FBX RESULT: $name EXIT=$rc ==========" + exit "$rc" + ;; + + status) + echo "FBX_ROOT=$ROOT" + echo "FBX_LIBRARY=$LIB" + echo "FBX_ITEMS=$(find "$LIB" -maxdepth 1 -type f -name '*.sh' | wc -l)" + ;; + + *) + echo 'USAGE: fbx {status|list|adopt JOB NAME|show NAME|run NAME|hash NAME}' + exit 64 + ;; +esac diff --git a/source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py b/source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py new file mode 100644 index 0000000..358f18f --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/framebridge_cua_linux.py @@ -0,0 +1,2104 @@ +from __future__ import annotations + +import base64 +import hashlib +import os +from pathlib import Path +import re +import subprocess +import threading +import tempfile +import time + + +class CuaTransportError(RuntimeError): + def __init__(self, code, message=None): + self.code = str(code) + self.message = str(message if message is not None else code) + super().__init__(self.message) + + +CUA_PROTOCOL_VERSION = "2" + +DANGEROUS = { + "DELETE", + "SEND", + "PURCHASE", + "AUTH_CONFIRM", + "SYSTEM_CHANGE", + "IRREVERSIBLE", +} + +SECRET_SEMANTICS = { + "SECRET_ENTRY", + "PASSWORD", + "PRIVATE_KEY", + "TOKEN", +} + +BUTTON_MAP = { + "left": "1", + "middle": "2", + "right": "3", + "1": "1", + "2": "2", + "3": "3", +} + +SCROLL_BUTTON = { + "UP": "4", + "DOWN": "5", + "LEFT": "6", + "RIGHT": "7", +} + +KEY_RE = re.compile(r"^[A-Za-z0-9_+:-]{1,64}$") + + +def _run(argv, timeout=15): + try: + p = subprocess.run( + argv, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=timeout, + check=False, + ) + except Exception as exc: + raise CuaTransportError( + "CUA_BACKEND_ERROR:" + type(exc).__name__ + ) + + return p + + +def _require_x11(): + if os.environ.get("XDG_SESSION_TYPE", "").lower() != "x11": + raise CuaTransportError("X11_SESSION_REQUIRED") + + if not os.environ.get("DISPLAY"): + raise CuaTransportError("DISPLAY_NOT_AVAILABLE") + + +def _geometry(): + p = _run( + ["xdotool", "getdisplaygeometry"], + timeout=5, + ) + + if p.returncode != 0: + raise CuaTransportError("DISPLAY_GEOMETRY_FAILED") + + parts = p.stdout.strip().split() + + if len(parts) != 2: + raise CuaTransportError("DISPLAY_GEOMETRY_INVALID") + + try: + width = int(parts[0]) + height = int(parts[1]) + except Exception: + raise CuaTransportError("DISPLAY_GEOMETRY_INVALID") + + if width <= 0 or height <= 0: + raise CuaTransportError("DISPLAY_GEOMETRY_INVALID") + + return width, height + + +def _cursor(): + p = _run( + ["xdotool", "getmouselocation", "--shell"], + timeout=5, + ) + + values = {} + + if p.returncode == 0: + for line in p.stdout.splitlines(): + if "=" in line: + k, v = line.split("=", 1) + values[k.strip()] = v.strip() + + try: + return ( + int(values.get("X", "-1")), + int(values.get("Y", "-1")), + ) + except Exception: + return -1, -1 + + +def _safe_title(value): + return " ".join(str(value).split())[:300] + + +def _active_window(): + p = _run( + ["xdotool", "getactivewindow"], + timeout=5, + ) + + if p.returncode != 0: + return -1, "" + + try: + window_id = int(p.stdout.strip(), 0) + except Exception: + return -1, "" + + q = _run( + [ + "xdotool", + "getwindowname", + str(window_id), + ], + timeout=5, + ) + + title = "" + if q.returncode == 0: + title = _safe_title(q.stdout) + + return window_id, title + + +def _preview_geometry(width, height): + display_w = 960 if width > 960 else width + display_h = round(height * display_w / width) + return display_w, display_h + + +def _semantic(msg): + return str( + msg.get( + "semantic_action", + msg.get("semantic", "GENERIC"), + ) + ).upper().strip() or "GENERIC" + + +def _require_chat_authorization(msg, semantic): + if semantic in SECRET_SEMANTICS: + raise CuaTransportError("SECRET_ENTRY_FORBIDDEN") + + if semantic in DANGEROUS: + raise CuaTransportError("SPECIFIC_CONFIRMATION_REQUIRED") + + authorized = str( + msg.get("authorized", "NO") + ).upper().strip() + + if authorized != "YES": + raise CuaTransportError("CHAT_AUTHORIZATION_REQUIRED") + + +def _parse_window_id(value): + if value is None or value == "": + return None + + try: + if isinstance(value, str): + return int(value.strip(), 0) + return int(value) + except Exception: + raise CuaTransportError("EXPECTED_WINDOW_ID_INVALID") + + +def _guard_expected_window(msg, current_window_id): + expected = _parse_window_id( + msg.get("expected_window_id") + ) + + if expected is None: + return "NOT_REQUESTED" + + if current_window_id != expected: + raise CuaTransportError("ACTIVE_WINDOW_CHANGED") + + return "PASS" + + +def _guard_expected_geometry(msg, width, height): + ew = msg.get("expected_source_width") + eh = msg.get("expected_source_height") + + if ew in (None, "") and eh in (None, ""): + return "NOT_REQUESTED" + + try: + ew_i = width if ew in (None, "") else int(ew) + eh_i = height if eh in (None, "") else int(eh) + except Exception: + raise CuaTransportError( + "EXPECTED_SOURCE_GEOMETRY_INVALID" + ) + + if ew_i != width or eh_i != height: + raise CuaTransportError("DISPLAY_GEOMETRY_CHANGED") + + return "PASS" + + +def _coord_space(msg): + space = str( + msg.get("coord_space", "ROOT") + ).upper().strip() + + if space not in {"ROOT", "PREVIEW"}: + raise CuaTransportError( + "COORDINATE_SPACE_INVALID" + ) + + return space + + +def _int_coord(msg, key): + try: + return int(msg.get(key)) + except Exception: + raise CuaTransportError( + key.upper() + "_COORDINATE_INVALID" + ) + + +def _resolve_point( + msg, + width, + height, + x_key="x", + y_key="y", +): + x = _int_coord(msg, x_key) + y = _int_coord(msg, y_key) + space = _coord_space(msg) + + if space == "ROOT": + if not (0 <= x < width and 0 <= y < height): + raise CuaTransportError( + "COORDINATES_OUT_OF_BOUNDS" + ) + return x, y, x, y, space, width, height + + default_pw, default_ph = _preview_geometry( + width, + height, + ) + + try: + preview_w = int( + msg.get("preview_width", default_pw) + ) + preview_h = int( + msg.get("preview_height", default_ph) + ) + except Exception: + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if preview_w <= 0 or preview_h <= 0: + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if not ( + 0 <= x < preview_w + and 0 <= y < preview_h + ): + raise CuaTransportError( + "PREVIEW_COORDINATES_OUT_OF_BOUNDS" + ) + + root_x = min( + width - 1, + int(x * width / preview_w), + ) + root_y = min( + height - 1, + int(y * height / preview_h), + ) + + return ( + x, + y, + root_x, + root_y, + space, + preview_w, + preview_h, + ) + + +def _action_context(msg): + _require_x11() + + semantic = _semantic(msg) + _require_chat_authorization( + msg, + semantic, + ) + + width, height = _geometry() + geometry_gate = _guard_expected_geometry( + msg, + width, + height, + ) + + before_id, before_title = _active_window() + window_gate = _guard_expected_window( + msg, + before_id, + ) + + return { + "semantic": semantic, + "width": width, + "height": height, + "before_id": before_id, + "before_title": before_title, + "geometry_gate": geometry_gate, + "window_gate": window_gate, + } + + +def _evidence_lines(ctx): + after_id, after_title = _active_window() + cx, cy = _cursor() + + return [ + "CUA_PROTOCOL_VERSION=" + CUA_PROTOCOL_VERSION, + "CHAT_AUTHORIZATION_GATE=PASS", + "SEMANTIC=" + ctx["semantic"], + "SOURCE_WIDTH=" + str(ctx["width"]), + "SOURCE_HEIGHT=" + str(ctx["height"]), + "EXPECTED_SOURCE_GEOMETRY_GATE=" + + ctx["geometry_gate"], + "EXPECTED_WINDOW_GATE=" + ctx["window_gate"], + "ACTIVE_WINDOW_ID_BEFORE=" + + str(ctx["before_id"]), + "ACTIVE_WINDOW_TITLE_BEFORE=" + + _safe_title(ctx["before_title"]), + "ACTIVE_WINDOW_ID_AFTER=" + + str(after_id), + "ACTIVE_WINDOW_TITLE_AFTER=" + + _safe_title(after_title), + "CURSOR_X=" + str(cx), + "CURSOR_Y=" + str(cy), + ] + + +def _capture_visual_jpeg(): + _require_x11() + + width, height = _geometry() + cx, cy = _cursor() + window_id, title = _active_window() + + with tempfile.TemporaryDirectory( + prefix="framebridge-cua-" + ) as td: + td = Path(td) + png = td / "screen.png" + jpg = td / "screen.jpg" + + p = _run( + [ + "import", + "-window", + "root", + str(png), + ], + timeout=15, + ) + + if p.returncode != 0 or not png.is_file(): + raise CuaTransportError( + "VISUAL_CAPTURE_FAILED" + ) + + p = _run( + [ + "convert", + str(png), + "-resize", + "960x>", + "-quality", + "82", + str(jpg), + ], + timeout=15, + ) + + if p.returncode != 0 or not jpg.is_file(): + raise CuaTransportError( + "VISUAL_JPEG_ENCODE_FAILED" + ) + + data = jpg.read_bytes() + + encoded = base64.b64encode( + data + ).decode("ascii") + + display_w, display_h = _preview_geometry( + width, + height, + ) + + lines = [ + "RESULTADO_FINAL=CUA_VISUAL_OBSERVE_PASS", + "VISUAL_CAPTURE_GATE=PASS", + "CUA_PROTOCOL_VERSION=" + CUA_PROTOCOL_VERSION, + "CHAT_AUTHORIZATION_MODE=CHAT", + "MIME=image/jpeg", + "BYTES=" + str(len(data)), + "SHA256=" + + hashlib.sha256(data).hexdigest(), + "WIDTH=" + str(display_w), + "HEIGHT=" + str(display_h), + "SOURCE_WIDTH=" + str(width), + "SOURCE_HEIGHT=" + str(height), + "COORD_SPACE_DEFAULT=ROOT", + "COORD_SPACE_AVAILABLE=ROOT,PREVIEW", + "CURSOR_X=" + str(cx), + "CURSOR_Y=" + str(cy), + "ACTIVE_WINDOW_ID=" + str(window_id), + "WINDOW_TITLE=" + _safe_title(title), + "BASE64=" + encoded, + ] + + return { + "ok": True, + "action": "OBSERVE", + "_frame_output": "\n".join(lines), + } + + +def _button(msg): + button = str( + msg.get("button", "left") + ).lower().strip() + + if button not in BUTTON_MAP: + raise CuaTransportError( + "CLICK_BUTTON_INVALID" + ) + + return button, BUTTON_MAP[button] + + +def _click_common(msg, repeat, action_name=None): + ctx = _action_context(msg) + + ( + requested_x, + requested_y, + root_x, + root_y, + space, + space_w, + space_h, + ) = _resolve_point( + msg, + ctx["width"], + ctx["height"], + ) + + button, button_number = _button(msg) + + argv = [ + "xdotool", + "mousemove", + "--sync", + str(root_x), + str(root_y), + "click", + ] + + if repeat > 1: + argv.extend( + [ + "--repeat", + str(repeat), + "--delay", + "120", + ] + ) + + argv.append(button_number) + + p = _run( + argv, + timeout=10, + ) + + if p.returncode != 0: + raise CuaTransportError( + "CLICK_EXECUTION_FAILED" + ) + + action = action_name or ( + "DOUBLE_CLICK" + if repeat == 2 + else "CLICK" + ) + + lines = [ + "RESULTADO_FINAL=CUA_" + + action + + "_PASS", + action + "_GATE=PASS", + "ACTION=" + action, + "COORD_SPACE=" + space, + "COORD_SPACE_WIDTH=" + str(space_w), + "COORD_SPACE_HEIGHT=" + str(space_h), + "REQUESTED_X=" + str(requested_x), + "REQUESTED_Y=" + str(requested_y), + "ROOT_X=" + str(root_x), + "ROOT_Y=" + str(root_y), + "BUTTON=" + button, + ] + lines.extend(_evidence_lines(ctx)) + + return { + "ok": True, + "action": action, + "_frame_output": "\n".join(lines), + } + + +def _click(msg): + return _click_common(msg, 1) + + +def _double_click(msg): + return _click_common(msg, 2) + + +def _right_click(msg): + right_msg = dict(msg) + right_msg["button"] = "right" + return _click_common(right_msg, 1, "RIGHT_CLICK") + + +def _type(msg): + ctx = _action_context(msg) + + text = str( + msg.get("text", "") + ) + + if not text: + raise CuaTransportError( + "TYPE_TEXT_EMPTY" + ) + + if len(text) > 4096: + raise CuaTransportError( + "TYPE_TEXT_TOO_LONG" + ) + + p = _run( + [ + "xdotool", + "type", + "--clearmodifiers", + "--delay", + "1", + "--", + text, + ], + timeout=20, + ) + + if p.returncode != 0: + raise CuaTransportError( + "TYPE_EXECUTION_FAILED" + ) + + lines = [ + "RESULTADO_FINAL=CUA_TYPE_PASS", + "TYPE_GATE=PASS", + "ACTION=TYPE", + "CHAR_COUNT=" + str(len(text)), + ] + lines.extend(_evidence_lines(ctx)) + + return { + "ok": True, + "action": "TYPE", + "_frame_output": "\n".join(lines), + } + + +def _normalize_key_spec(key): + parts = [part.strip() for part in key.split("+")] + if not parts or any(not part for part in parts): + raise CuaTransportError("KEY_INVALID") + + modifier_map = { + "CTRL": "ctrl", + "CONTROL": "ctrl", + "ALT": "alt", + "SHIFT": "shift", + "SUPER": "super", + "META": "super", + } + named_key_map = { + "BACKSPACE": "BackSpace", + "ENTER": "Return", + "RETURN": "Return", + "ESC": "Escape", + "ESCAPE": "Escape", + "TAB": "Tab", + "DELETE": "Delete", + "DEL": "Delete", + "INSERT": "Insert", + "HOME": "Home", + "END": "End", + "LEFT": "Left", + "RIGHT": "Right", + "UP": "Up", + "DOWN": "Down", + "PAGEUP": "Prior", + "PAGEDOWN": "Next", + "SPACE": "space", + } + normalized = [] + explicit_shift = False + for part in parts: + upper = part.upper() + if upper in modifier_map: + mapped = modifier_map[upper] + normalized.append(mapped) + explicit_shift = explicit_shift or mapped == "shift" + continue + if upper in named_key_map: + normalized.append(named_key_map[upper]) + continue + if len(part) == 1 and part.isalpha() and not explicit_shift: + normalized.append(part.lower()) + else: + normalized.append(part) + return "+".join(normalized) + + +def _key(msg): + ctx = _action_context(msg) + + key = str( + msg.get("key", "") + ).strip() + + if not key or not KEY_RE.fullmatch(key): + raise CuaTransportError("KEY_INVALID") + + normalized_key = _normalize_key_spec(key) + + p = _run( + [ + "xdotool", + "key", + "--clearmodifiers", + normalized_key, + ], + timeout=10, + ) + + if p.returncode != 0: + raise CuaTransportError( + "KEY_EXECUTION_FAILED" + ) + + lines = [ + "RESULTADO_FINAL=CUA_KEY_PASS", + "KEY_GATE=PASS", + "ACTION=KEY", + "KEY=" + key, + "KEY_NORMALIZED=" + normalized_key, + ] + lines.extend(_evidence_lines(ctx)) + + return { + "ok": True, + "action": "KEY", + "_frame_output": "\n".join(lines), + } + + +def _scroll(msg): + ctx = _action_context(msg) + + direction = str( + msg.get("direction", "DOWN") + ).upper().strip() + + if direction not in SCROLL_BUTTON: + raise CuaTransportError( + "SCROLL_DIRECTION_INVALID" + ) + + # R8: SCROLL follows the public MCP contract: `steps` is canonical. + # `clicks` remains a backward-compatible fallback for older callers. + try: + raw_steps = msg.get("steps") + if raw_steps in (None, ""): + raw_steps = msg.get("clicks", 3) + clicks = int(raw_steps) + except Exception: + raise CuaTransportError( + "SCROLL_STEPS_INVALID" + ) + + if not (1 <= clicks <= 20): + raise CuaTransportError( + "SCROLL_STEPS_INVALID" + ) + + point_lines = [] + + x_present = msg.get("x") not in (None, "") + y_present = msg.get("y") not in (None, "") + + if x_present != y_present: + raise CuaTransportError( + "SCROLL_POINT_INCOMPLETE" + ) + + if x_present: + ( + requested_x, + requested_y, + root_x, + root_y, + space, + space_w, + space_h, + ) = _resolve_point( + msg, + ctx["width"], + ctx["height"], + ) + + p = _run( + [ + "xdotool", + "mousemove", + "--sync", + str(root_x), + str(root_y), + ], + timeout=10, + ) + + if p.returncode != 0: + raise CuaTransportError( + "SCROLL_POINTER_MOVE_FAILED" + ) + + point_lines = [ + "COORD_SPACE=" + space, + "COORD_SPACE_WIDTH=" + str(space_w), + "COORD_SPACE_HEIGHT=" + str(space_h), + "REQUESTED_X=" + str(requested_x), + "REQUESTED_Y=" + str(requested_y), + "ROOT_X=" + str(root_x), + "ROOT_Y=" + str(root_y), + ] + + p = _run( + [ + "xdotool", + "click", + "--repeat", + str(clicks), + "--delay", + "35", + SCROLL_BUTTON[direction], + ], + timeout=10, + ) + + if p.returncode != 0: + raise CuaTransportError( + "SCROLL_EXECUTION_FAILED" + ) + + lines = [ + "RESULTADO_FINAL=CUA_SCROLL_PASS", + "SCROLL_GATE=PASS", + "ACTION=SCROLL", + "DIRECTION=" + direction, + "STEPS=" + str(clicks), + "CLICKS=" + str(clicks), + ] + lines.extend(point_lines) + lines.extend(_evidence_lines(ctx)) + + return { + "ok": True, + "action": "SCROLL", + "_frame_output": "\n".join(lines), + } + + +def _drag(msg): + ctx = _action_context(msg) + + ( + req_x1, + req_y1, + root_x1, + root_y1, + space1, + space_w1, + space_h1, + ) = _resolve_point( + msg, + ctx["width"], + ctx["height"], + "x1", + "y1", + ) + + ( + req_x2, + req_y2, + root_x2, + root_y2, + space2, + space_w2, + space_h2, + ) = _resolve_point( + msg, + ctx["width"], + ctx["height"], + "x2", + "y2", + ) + + if ( + space1 != space2 + or space_w1 != space_w2 + or space_h1 != space_h2 + ): + raise CuaTransportError( + "DRAG_COORDINATE_SPACE_MISMATCH" + ) + + button, button_number = _button(msg) + + try: + steps = int( + msg.get("steps", 8) + ) + duration_ms = int( + msg.get("duration_ms", 320) + ) + except Exception: + raise CuaTransportError( + "DRAG_TIMING_INVALID" + ) + + if not (1 <= steps <= 40): + raise CuaTransportError( + "DRAG_STEPS_INVALID" + ) + + if not (0 <= duration_ms <= 5000): + raise CuaTransportError( + "DRAG_DURATION_INVALID" + ) + + p = _run( + [ + "xdotool", + "mousemove", + "--sync", + str(root_x1), + str(root_y1), + ], + timeout=10, + ) + + if p.returncode != 0: + raise CuaTransportError( + "DRAG_START_MOVE_FAILED" + ) + + p = _run( + [ + "xdotool", + "mousedown", + button_number, + ], + timeout=5, + ) + + if p.returncode != 0: + raise CuaTransportError( + "DRAG_MOUSEDOWN_FAILED" + ) + + failure = None + + try: + delay = ( + duration_ms / 1000.0 / steps + if steps + else 0.0 + ) + + for i in range(1, steps + 1): + x = round( + root_x1 + + (root_x2 - root_x1) * i / steps + ) + y = round( + root_y1 + + (root_y2 - root_y1) * i / steps + ) + + p = _run( + [ + "xdotool", + "mousemove", + "--sync", + str(x), + str(y), + ], + timeout=10, + ) + + if p.returncode != 0: + failure = "DRAG_MOVE_FAILED" + break + + if delay > 0: + time.sleep(delay) + finally: + q = _run( + [ + "xdotool", + "mouseup", + button_number, + ], + timeout=5, + ) + + if q.returncode != 0 and failure is None: + failure = "DRAG_MOUSEUP_FAILED" + + if failure: + raise CuaTransportError(failure) + + lines = [ + "RESULTADO_FINAL=CUA_DRAG_PASS", + "DRAG_GATE=PASS", + "ACTION=DRAG", + "COORD_SPACE=" + space1, + "COORD_SPACE_WIDTH=" + str(space_w1), + "COORD_SPACE_HEIGHT=" + str(space_h1), + "REQUESTED_X1=" + str(req_x1), + "REQUESTED_Y1=" + str(req_y1), + "REQUESTED_X2=" + str(req_x2), + "REQUESTED_Y2=" + str(req_y2), + "ROOT_X1=" + str(root_x1), + "ROOT_Y1=" + str(root_y1), + "ROOT_X2=" + str(root_x2), + "ROOT_Y2=" + str(root_y2), + "BUTTON=" + button, + "STEPS=" + str(steps), + "DURATION_MS=" + str(duration_ms), + ] + lines.extend(_evidence_lines(ctx)) + + return { + "ok": True, + "action": "DRAG", + "_frame_output": "\n".join(lines), + } + + +def handle_cua_message(msg): + if not isinstance(msg, dict): + raise CuaTransportError( + "CUA_MESSAGE_INVALID" + ) + + target = str( + msg.get("target", "ASUS") + ).upper() + + if target != "ASUS": + raise CuaTransportError( + "TARGET_MISMATCH" + ) + + action = str( + msg.get("action", "OBSERVE") + ).upper().strip() + + if action == "OBSERVE": + return _capture_visual_jpeg() + + if action == "CLICK": + return _click(msg) + + if action == "RIGHT_CLICK": + return _right_click(msg) + + if action == "DOUBLE_CLICK": + return _double_click(msg) + + if action == "TYPE": + return _type(msg) + + if action == "KEY": + return _key(msg) + + if action == "SCROLL": + return _scroll(msg) + + if action == "DRAG": + return _drag(msg) + + raise CuaTransportError( + "CUA_ACTION_UNSUPPORTED" + ) + + +# ================================================================== +# FRAMEBRIDGE CUA V3 CANDIDATE OVERRIDES +# Window-local coordinate contract + window scoped observation. +# Candidate only: appended definitions override v2 globals at runtime. +# ================================================================== + +CUA_PROTOCOL_VERSION = "3" + + +def _window_geometry(window_id): + if int(window_id) <= 0: + raise CuaTransportError( + "WINDOW_GEOMETRY_WINDOW_INVALID" + ) + + p = _run( + [ + "xwininfo", + "-id", + str(int(window_id)), + ], + timeout=5, + ) + + if p.returncode != 0: + raise CuaTransportError( + "WINDOW_GEOMETRY_FAILED" + ) + + values = {} + + patterns = { + "x": r"Absolute upper-left X:\s+(-?\d+)", + "y": r"Absolute upper-left Y:\s+(-?\d+)", + "width": r"Width:\s+(\d+)", + "height": r"Height:\s+(\d+)", + } + + for key, pattern in patterns.items(): + m = re.search(pattern, p.stdout) + + if not m: + raise CuaTransportError( + "WINDOW_GEOMETRY_INVALID" + ) + + values[key] = int(m.group(1)) + + if ( + values["width"] <= 0 + or values["height"] <= 0 + ): + raise CuaTransportError( + "WINDOW_GEOMETRY_INVALID" + ) + + return values + + +def _coord_space(msg): + space = str( + msg.get("coord_space", "ROOT") + ).upper().strip() + + if space not in { + "ROOT", + "PREVIEW", + "WINDOW", + "WINDOW_PREVIEW", + }: + raise CuaTransportError( + "COORDINATE_SPACE_INVALID" + ) + + return space + + +def _resolve_point( + msg, + width, + height, + x_key="x", + y_key="y", +): + x = _int_coord(msg, x_key) + y = _int_coord(msg, y_key) + space = _coord_space(msg) + + if space == "ROOT": + if not ( + 0 <= x < width + and 0 <= y < height + ): + raise CuaTransportError( + "COORDINATES_OUT_OF_BOUNDS" + ) + + return ( + x, + y, + x, + y, + space, + width, + height, + ) + + if space in { + "WINDOW", + "WINDOW_PREVIEW", + }: + rect = msg.get( + "_resolved_window_rect" + ) + + if not isinstance(rect, dict): + raise CuaTransportError( + "WINDOW_GEOMETRY_REQUIRED" + ) + + wx = int(rect["x"]) + wy = int(rect["y"]) + ww = int(rect["width"]) + wh = int(rect["height"]) + + if space == "WINDOW": + if not ( + 0 <= x < ww + and 0 <= y < wh + ): + raise CuaTransportError( + "WINDOW_COORDINATES_OUT_OF_BOUNDS" + ) + + root_x = wx + x + root_y = wy + y + space_w = ww + space_h = wh + + else: + default_pw, default_ph = ( + _preview_geometry( + ww, + wh, + ) + ) + + try: + preview_w = int( + msg.get( + "preview_width", + default_pw, + ) + ) + preview_h = int( + msg.get( + "preview_height", + default_ph, + ) + ) + except Exception: + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if ( + preview_w <= 0 + or preview_h <= 0 + ): + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if not ( + 0 <= x < preview_w + and 0 <= y < preview_h + ): + raise CuaTransportError( + "PREVIEW_COORDINATES_OUT_OF_BOUNDS" + ) + + local_x = min( + ww - 1, + int( + x + * ww + / preview_w + ), + ) + + local_y = min( + wh - 1, + int( + y + * wh + / preview_h + ), + ) + + root_x = wx + local_x + root_y = wy + local_y + space_w = preview_w + space_h = preview_h + + if not ( + 0 <= root_x < width + and 0 <= root_y < height + ): + raise CuaTransportError( + "WINDOW_COORDINATES_OFFSCREEN" + ) + + return ( + x, + y, + root_x, + root_y, + space, + space_w, + space_h, + ) + + default_pw, default_ph = ( + _preview_geometry( + width, + height, + ) + ) + + try: + preview_w = int( + msg.get( + "preview_width", + default_pw, + ) + ) + preview_h = int( + msg.get( + "preview_height", + default_ph, + ) + ) + except Exception: + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if ( + preview_w <= 0 + or preview_h <= 0 + ): + raise CuaTransportError( + "PREVIEW_GEOMETRY_INVALID" + ) + + if not ( + 0 <= x < preview_w + and 0 <= y < preview_h + ): + raise CuaTransportError( + "PREVIEW_COORDINATES_OUT_OF_BOUNDS" + ) + + root_x = min( + width - 1, + int( + x + * width + / preview_w + ), + ) + + root_y = min( + height - 1, + int( + y + * height + / preview_h + ), + ) + + return ( + x, + y, + root_x, + root_y, + space, + preview_w, + preview_h, + ) + + +def _action_context(msg): + _require_x11() + + semantic = _semantic(msg) + + _require_chat_authorization( + msg, + semantic, + ) + + width, height = _geometry() + + before_id, before_title = ( + _active_window() + ) + + window_gate = ( + _guard_expected_window( + msg, + before_id, + ) + ) + + space = _coord_space(msg) + + window_rect = None + source_width = width + source_height = height + + if space in { + "WINDOW", + "WINDOW_PREVIEW", + }: + window_rect = ( + _window_geometry( + before_id + ) + ) + + msg[ + "_resolved_window_rect" + ] = dict(window_rect) + + source_width = int( + window_rect["width"] + ) + + source_height = int( + window_rect["height"] + ) + + geometry_gate = ( + _guard_expected_geometry( + msg, + source_width, + source_height, + ) + ) + + return { + "semantic": semantic, + "width": width, + "height": height, + "source_width": source_width, + "source_height": source_height, + "before_id": before_id, + "before_title": before_title, + "geometry_gate": geometry_gate, + "window_gate": window_gate, + "window_rect": window_rect, + "coord_space": space, + } + + +def _evidence_lines(ctx): + after_id, after_title = ( + _active_window() + ) + + cx, cy = _cursor() + + lines = [ + "CUA_PROTOCOL_VERSION=" + + CUA_PROTOCOL_VERSION, + "CHAT_AUTHORIZATION_GATE=PASS", + "SEMANTIC=" + + ctx["semantic"], + "SOURCE_WIDTH=" + + str(ctx["source_width"]), + "SOURCE_HEIGHT=" + + str(ctx["source_height"]), + "DISPLAY_WIDTH=" + + str(ctx["width"]), + "DISPLAY_HEIGHT=" + + str(ctx["height"]), + "EXPECTED_SOURCE_GEOMETRY_GATE=" + + ctx["geometry_gate"], + "EXPECTED_WINDOW_GATE=" + + ctx["window_gate"], + ] + + rect = ctx.get( + "window_rect" + ) + + if rect: + lines.extend( + [ + "WINDOW_X=" + + str(rect["x"]), + "WINDOW_Y=" + + str(rect["y"]), + "WINDOW_WIDTH=" + + str(rect["width"]), + "WINDOW_HEIGHT=" + + str(rect["height"]), + ] + ) + + lines.extend( + [ + "ACTIVE_WINDOW_ID_BEFORE=" + + str(ctx["before_id"]), + "ACTIVE_WINDOW_TITLE_BEFORE=" + + _safe_title( + ctx["before_title"] + ), + "ACTIVE_WINDOW_ID_AFTER=" + + str(after_id), + "ACTIVE_WINDOW_TITLE_AFTER=" + + _safe_title( + after_title + ), + "CURSOR_X=" + + str(cx), + "CURSOR_Y=" + + str(cy), + ] + ) + + return lines + + +def _capture_visual_jpeg(msg=None): + _require_x11() + + msg = ( + msg + if isinstance(msg, dict) + else {} + ) + + display_width, display_height = ( + _geometry() + ) + + cx, cy = _cursor() + + window_id, title = ( + _active_window() + ) + + capture_scope = str( + msg.get( + "capture_scope", + "ROOT", + ) + ).upper().strip() + + if capture_scope not in { + "ROOT", + "WINDOW", + }: + raise CuaTransportError( + "CAPTURE_SCOPE_INVALID" + ) + + _guard_expected_window( + msg, + window_id, + ) + + source_x = 0 + source_y = 0 + source_width = display_width + source_height = display_height + window_rect = None + + if capture_scope == "WINDOW": + window_rect = ( + _window_geometry( + window_id + ) + ) + + source_x = int( + window_rect["x"] + ) + source_y = int( + window_rect["y"] + ) + source_width = int( + window_rect["width"] + ) + source_height = int( + window_rect["height"] + ) + + _guard_expected_geometry( + msg, + source_width, + source_height, + ) + + with tempfile.TemporaryDirectory( + prefix="framebridge-cua-" + ) as td: + td = Path(td) + + png = td / "screen.png" + jpg = td / "screen.jpg" + + capture_target = ( + "root" + if capture_scope == "ROOT" + else str(window_id) + ) + + p = _run( + [ + "import", + "-window", + capture_target, + str(png), + ], + timeout=15, + ) + + if ( + p.returncode != 0 + or not png.is_file() + ): + raise CuaTransportError( + "VISUAL_CAPTURE_FAILED" + ) + + q = _run( + [ + "identify", + "-format", + "%w %h", + str(png), + ], + timeout=5, + ) + + if q.returncode != 0: + raise CuaTransportError( + "VISUAL_CAPTURE_GEOMETRY_FAILED" + ) + + try: + actual_w, actual_h = [ + int(v) + for v + in q.stdout.strip().split() + ] + except Exception: + raise CuaTransportError( + "VISUAL_CAPTURE_GEOMETRY_INVALID" + ) + + if ( + actual_w != source_width + or actual_h != source_height + ): + raise CuaTransportError( + "VISUAL_CAPTURE_GEOMETRY_MISMATCH" + ) + + p = _run( + [ + "convert", + str(png), + "-resize", + "960x>", + "-quality", + "82", + str(jpg), + ], + timeout=15, + ) + + if ( + p.returncode != 0 + or not jpg.is_file() + ): + raise CuaTransportError( + "VISUAL_JPEG_ENCODE_FAILED" + ) + + data = jpg.read_bytes() + + encoded = base64.b64encode( + data + ).decode("ascii") + + display_w, display_h = ( + _preview_geometry( + source_width, + source_height, + ) + ) + + coord_default = ( + "ROOT" + if capture_scope == "ROOT" + else "WINDOW_PREVIEW" + ) + + coord_available = ( + "ROOT,PREVIEW" + if capture_scope == "ROOT" + else + "WINDOW,WINDOW_PREVIEW,ROOT" + ) + + lines = [ + "RESULTADO_FINAL=" + "CUA_VISUAL_OBSERVE_PASS", + "VISUAL_CAPTURE_GATE=PASS", + "CUA_PROTOCOL_VERSION=" + + CUA_PROTOCOL_VERSION, + "MIME=image/jpeg", + "BYTES=" + + str(len(data)), + "SHA256=" + + hashlib.sha256( + data + ).hexdigest(), + "WIDTH=" + + str(display_w), + "HEIGHT=" + + str(display_h), + "SOURCE_X=" + + str(source_x), + "SOURCE_Y=" + + str(source_y), + "SOURCE_WIDTH=" + + str(source_width), + "SOURCE_HEIGHT=" + + str(source_height), + "DISPLAY_WIDTH=" + + str(display_width), + "DISPLAY_HEIGHT=" + + str(display_height), + "CAPTURE_SCOPE=" + + capture_scope, + "COORD_SPACE_DEFAULT=" + + coord_default, + "COORD_SPACE_AVAILABLE=" + + coord_available, + "CURSOR_X=" + + str(cx), + "CURSOR_Y=" + + str(cy), + "ACTIVE_WINDOW_ID=" + + str(window_id), + "WINDOW_TITLE=" + + _safe_title(title), + ] + + if window_rect: + lines.extend( + [ + "WINDOW_X=" + + str( + window_rect["x"] + ), + "WINDOW_Y=" + + str( + window_rect["y"] + ), + "WINDOW_WIDTH=" + + str( + window_rect["width"] + ), + "WINDOW_HEIGHT=" + + str( + window_rect["height"] + ), + ] + ) + + lines.append( + "BASE64=" + encoded + ) + + return { + "ok": True, + "action": "OBSERVE", + "_frame_output": + "\n".join(lines), + } + + +# FRAMEBRIDGE CUA V3 DISPATCH OVERRIDE +def handle_cua_message(msg): + if not isinstance(msg, dict): + raise CuaTransportError( + "CUA_MESSAGE_INVALID" + ) + + target = str( + msg.get("target", "ASUS") + ).upper() + + if target != "ASUS": + raise CuaTransportError( + "TARGET_MISMATCH" + ) + + action = str( + msg.get("action", "OBSERVE") + ).upper().strip() + + if action == "OBSERVE": + return _capture_visual_jpeg(msg) + + if action == "CLICK": + return _click(msg) + + if action == "RIGHT_CLICK": + return _right_click(msg) + + if action == "DOUBLE_CLICK": + return _double_click(msg) + + if action == "TYPE": + return _type(msg) + + if action == "KEY": + return _key(msg) + + if action == "SCROLL": + return _scroll(msg) + + if action == "DRAG": + return _drag(msg) + + raise CuaTransportError( + "CUA_ACTION_UNSUPPORTED" + ) + + +# FRAMEBRIDGE CUA V3 POINTER-SYNC GUARD +_run_v3_base = _run + + +def _run(argv, timeout=15): + args = list(argv) + + if ( + len(args) >= 5 + and args[0] == "xdotool" + and args[1] == "mousemove" + and args[2] == "--sync" + ): + try: + target_x = int(args[3]) + target_y = int(args[4]) + except Exception: + return _run_v3_base( + args, + timeout=timeout, + ) + + q = _run_v3_base( + [ + "xdotool", + "getmouselocation", + "--shell", + ], + timeout=5, + ) + + values = {} + + if q.returncode == 0: + for line in q.stdout.splitlines(): + if "=" in line: + k, v = line.split( + "=", + 1, + ) + values[ + k.strip() + ] = v.strip() + + try: + current_x = int( + values.get( + "X", + "-999999", + ) + ) + current_y = int( + values.get( + "Y", + "-999999", + ) + ) + except Exception: + current_x = -999999 + current_y = -999999 + + if ( + current_x == target_x + and current_y == target_y + ): + remainder = args[5:] + + if remainder: + return _run_v3_base( + [ + "xdotool", + *remainder, + ], + timeout=timeout, + ) + + return subprocess.CompletedProcess( + args, + 0, + "", + "", + ) + + return _run_v3_base( + args, + timeout=timeout, + ) + + +# FRAMEBRIDGE CUA V3 TYPE EXACT OVERRIDE +def _type(msg): + ctx = _action_context(msg) + + text = str( + msg.get("text", "") + ) + + if not text: + raise CuaTransportError( + "TYPE_TEXT_EMPTY" + ) + + if len(text) > 4096: + raise CuaTransportError( + "TYPE_TEXT_TOO_LONG" + ) + + type_mode = str( + msg.get( + "type_mode", + "KEYS", + ) + ).upper().strip() + + if type_mode not in { + "KEYS", + "CLIPBOARD_EXACT", + }: + raise CuaTransportError( + "TYPE_MODE_INVALID" + ) + + if type_mode == "KEYS": + p = _run( + [ + "xdotool", + "type", + "--clearmodifiers", + "--delay", + "10", + "--", + text, + ], + timeout=20, + ) + + if p.returncode != 0: + raise CuaTransportError( + "TYPE_EXECUTION_FAILED" + ) + + else: + old_clipboard = None + had_old_clipboard = False + owner = None + + try: + old = subprocess.run( + [ + "xclip", + "-selection", + "clipboard", + "-o", + ], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + timeout=3, + check=False, + ) + + if old.returncode == 0: + old_clipboard = old.stdout + had_old_clipboard = True + + # R7: mantener un owner X11 en foreground durante todo el + # Ctrl+V. No usar -loops 1: un clipboard manager puede consumir + # la primera solicitud antes que la aplicacion destino. + owner = subprocess.Popen( + [ + "xclip", + "-quiet", + "-selection", + "clipboard", + "-i", + ], + stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + if owner.stdin is None: + raise CuaTransportError( + "TYPE_CLIPBOARD_OWNER_STDIN_FAILED" + ) + + owner.stdin.write( + text.encode("utf-8") + ) + owner.stdin.close() + + time.sleep(0.05) + + early_rc = owner.poll() + + if early_rc not in (None, 0): + raise CuaTransportError( + "TYPE_CLIPBOARD_SET_FAILED" + ) + + paste = _run( + [ + "xdotool", + "key", + "--clearmodifiers", + "ctrl+v", + ], + timeout=10, + ) + + if paste.returncode != 0: + raise CuaTransportError( + "TYPE_CLIPBOARD_PASTE_FAILED" + ) + + # Dar tiempo a la aplicacion destino para completar la + # transferencia antes de retirar el owner. + time.sleep(0.10) + + finally: + # El owner temporal nunca puede sobrevivir a TYPE. + if owner is not None: + try: + if owner.poll() is None: + owner.terminate() + try: + owner.wait(timeout=1) + except subprocess.TimeoutExpired: + owner.kill() + owner.wait(timeout=1) + except Exception: + pass + + # Si existia contenido previo, restaurarlo mediante un owner + # X11 asincrono. El hilo daemon solo hace wait/reap; xclip + # termina cuando otro cliente toma la seleccion. + if ( + had_old_clipboard + and old_clipboard is not None + ): + try: + restore = subprocess.Popen( + [ + "xclip", + "-quiet", + "-selection", + "clipboard", + "-i", + ], + stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + if restore.stdin is not None: + restore.stdin.write( + old_clipboard + ) + restore.stdin.close() + + threading.Thread( + target=restore.wait, + daemon=True, + ).start() + except Exception: + pass + + lines = [ + "RESULTADO_FINAL=CUA_TYPE_PASS", + "TYPE_GATE=PASS", + "ACTION=TYPE", + "TYPE_MODE=" + type_mode, + "CHAR_COUNT=" + str(len(text)), + ] + + lines.extend( + _evidence_lines(ctx) + ) + + return { + "ok": True, + "action": "TYPE", + "_frame_output": + "\n".join(lines), + } diff --git a/source/sources/original-asus/framebridge-native/host/framebridge_host.py b/source/sources/original-asus/framebridge-native/host/framebridge_host.py new file mode 100755 index 0000000..baf3ad2 --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/framebridge_host.py @@ -0,0 +1,1199 @@ +#!/usr/bin/env python3 +import json +import os +import re +import shlex +import struct +import subprocess +import sys +import time +from pathlib import Path +from framebridge_voice_command import start_listen_once + +EXEC_SESSION = os.environ.get("FRAMEBRIDGE_EXEC_SESSION", "framebridge") +RESCUE_SESSION = os.environ.get("FRAMEBRIDGE_RESCUE_SESSION", "framebridge-rescue") +STATE_ROOT = Path.home() / ".local" / "share" / "framebridge" +JOBS_ROOT = STATE_ROOT / "jobs-v03" +RESET_GENERATION_PATH = STATE_ROOT / "exec-reset-generation" +UI_HELPER_ROOT = Path(__file__).resolve().parent + +# No implicit laboratory destinations are shipped. +# Explicit integrator configuration may populate this compatibility map. +CLASSIC_NODE_TARGETS = {} + +NODE_TARGET_RE = re.compile(r"^[A-Z0-9._:-]{1,64}$") + +READONLY_PROGRAMS = { + "pwd", "whoami", "id", "uname", "hostname", "date", "uptime", + "free", "df", "ls", "stat", "cat", "head", "tail", "grep", "rg", + "find", "ps", "pgrep", "lspci", "lsusb", "ip", "ss", "journalctl", + "ollama", "nvidia-smi", "git", "sha256sum", "du", "file", "readlink", + "realpath", "wc", "sed", "awk", "framebridge-node-ro", +} + +DANGEROUS = re.compile( + r"(^|[\s;&|])(?:sudo|su|rm|dd|mkfs(?:\.\w+)?|fdisk|parted|sfdisk|" + r"wipefs|reboot|shutdown|poweroff)\b" + r"|\b(?:chmod|chown|chgrp)\s+-R\b" + r"|\bsystemctl\s+(?:start|stop|restart|enable|disable|mask|unmask)\b" + r"|\b(?:apt|apt-get|dnf|yum|pacman)\s+(?:remove|purge|autoremove|" + r"upgrade|dist-upgrade)\b" + r"|\bgit\s+(?:reset\s+--hard|clean\s+-[a-zA-Z]*f|push\s+--force)\b", + re.I, +) + +def read_message(): + raw = sys.stdin.buffer.read(4) + if not raw: + return None + n = struct.unpack(" 32768: + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_BODY_TOO_LARGE", "channel": "NODE-CLASSIC"} + try: + body = json.loads(body_text) + except Exception: + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_BODY_JSON_INVALID", "channel": "NODE-CLASSIC"} + if not isinstance(body, dict): + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_BODY_NOT_OBJECT", "channel": "NODE-CLASSIC"} + if any(k in body for k in ("script", "shell", "command")): + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_RAW_SHELL_FORBIDDEN", "channel": "NODE-CLASSIC"} + + canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + dedup_root = STATE_ROOT / "node-classic-dedup" + dedup_root.mkdir(parents=True, exist_ok=True) + safe_id = re.sub(r"[^A-Za-z0-9._:-]", "_", frame_id)[:128] + dedup_path = dedup_root / (target + "--" + safe_id + ".json") + signature = __import__("hashlib").sha256((target + "\n" + canonical).encode("utf-8")).hexdigest() + + claim = {"state": "pending", "signature": signature, "target": target, "id": frame_id, "created_at": time.time()} + try: + fd = os.open(str(dedup_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + with os.fdopen(fd, "w", encoding="utf-8") as fh: + json.dump(claim, fh, sort_keys=True, separators=(",", ":")) + fh.write("\n") + except FileExistsError: + try: + previous = json.loads(dedup_path.read_text(encoding="utf-8")) + except Exception: + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_DEDUP_STATE_INVALID", "channel": "NODE-CLASSIC"} + if previous.get("signature") != signature: + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_ID_REUSE_MISMATCH", "channel": "NODE-CLASSIC"} + if previous.get("state") == "done" and isinstance(previous.get("response"), dict): + cached = dict(previous["response"]) + cached["channel"] = "NODE-CLASSIC-CACHED" + return cached + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_REQUEST_IN_PROGRESS", "channel": "NODE-CLASSIC"} + + frame = "\n".join([ + "FRAMEEXEC/1", + "ID=" + frame_id, + "TARGET=" + node["seed_target"], + "MODE=EXEC", + "LANG=framebridge-cap-v1", + "", + canonical, + "FRAMEEND/1", + "", + ]) + ssh = [ + "ssh", "-p", str(node["port"]), "-i", node["identity_file"], + "-oBatchMode=yes", "-oIdentitiesOnly=yes", "-oStrictHostKeyChecking=yes", + "-oUserKnownHostsFile=" + node["known_hosts_file"], "-oConnectTimeout=5", + node["user"] + "@" + node["host"], + 'export PATH="$HOME/.local/bin:$PATH"; framebridge classic', + ] + try: + proc = subprocess.run(ssh, input=frame, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=20, check=False) + except subprocess.TimeoutExpired: + try: dedup_path.unlink() + except OSError: pass + return {"executed": False, "exit_code": 124, "output": "", "error": "CLASSIC_NODE_TIMEOUT", "channel": "NODE-CLASSIC"} + except Exception as exc: + try: dedup_path.unlink() + except OSError: pass + return {"executed": False, "exit_code": None, "output": "", "error": "CLASSIC_NODE_TRANSPORT_ERROR:" + type(exc).__name__, "channel": "NODE-CLASSIC"} + + out = (proc.stdout or "").strip() + if proc.returncode != 0: + try: dedup_path.unlink() + except OSError: pass + return {"executed": False, "exit_code": proc.returncode, "output": out, "error": "CLASSIC_NODE_TRANSPORT_FAILED", "channel": "NODE-CLASSIC"} + if not out.startswith("FRAMERESULT/1") or "CHANNEL=SEED-CLASSIC-TTY" not in out: + try: dedup_path.unlink() + except OSError: pass + return {"executed": False, "exit_code": 3, "output": out, "error": "CLASSIC_NODE_RESULT_INVALID", "channel": "NODE-CLASSIC"} + response = {"executed": True, "exit_code": 0, "output": out, "error": "", "channel": "NODE-CLASSIC"} + final_state = {"state": "done", "signature": signature, "target": target, "id": frame_id, "response": response, "completed_at": time.time()} + tmp = dedup_path.with_name(dedup_path.name + ".tmp-" + str(os.getpid())) + tmp.write_text(json.dumps(final_state, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8") + os.chmod(tmp, 0o600) + os.replace(tmp, dedup_path) + return response + + +PLAN_ID_SAFE_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +PLAN_SHA_RE = re.compile(r"^[0-9a-f]{64}$") + +def _node_ssh_argv(node, remote_command): + return [ + "ssh", "-p", str(node["port"]), "-i", node["identity_file"], + "-oBatchMode=yes", "-oIdentitiesOnly=yes", "-oStrictHostKeyChecking=yes", + "-oUserKnownHostsFile=" + node["known_hosts_file"], "-oConnectTimeout=5", + node["user"] + "@" + node["host"], remote_command, + ] + +def _node_cli_json(node, remote_command, stdin_text=None, timeout=20): + try: + proc = subprocess.run( + _node_ssh_argv(node, remote_command), + input=stdin_text, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired: + return None, "NODEPLAN_TIMEOUT", 124 + except Exception as exc: + return None, "NODEPLAN_TRANSPORT_ERROR:" + type(exc).__name__, 1 + if proc.returncode != 0: + try: + value = json.loads((proc.stdout or "").strip()) + return value, str(value.get("error") or "NODEPLAN_REMOTE_ERROR"), proc.returncode + except Exception: + return None, "NODEPLAN_TRANSPORT_FAILED", proc.returncode + try: + return json.loads((proc.stdout or "").strip()), "", 0 + except Exception: + return None, "NODEPLAN_RESULT_JSON_INVALID", 3 + +def route_classic_plan(msg): + target = str(msg.get("target", "")).upper() + node = CLASSIC_NODE_TARGETS.get(target) + if node is None: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_TARGET_UNKNOWN", "channel": "NODE-PLAN"} + if ( + str(msg.get("mode", "")).upper() != "EXEC" + or str(msg.get("lang", "")).upper() != "FRAMEBRIDGE-PLAN-V1" + or str(msg.get("authorized", "NO")).upper() != "NO" + ): + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_CONTRACT_REJECT", "channel": "NODE-PLAN"} + + frame_id = str(msg.get("id", "")) + if not re.fullmatch(r"[A-Za-z0-9._:-]{1,128}", frame_id): + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_ID_INVALID", "channel": "NODE-PLAN"} + + raw = str(msg.get("command", "")).strip() + if len(raw.encode("utf-8")) > 65536: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_BODY_TOO_LARGE", "channel": "NODE-PLAN"} + try: + body = json.loads(raw) + except Exception: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_BODY_JSON_INVALID", "channel": "NODE-PLAN"} + if not isinstance(body, dict): + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_BODY_NOT_OBJECT", "channel": "NODE-PLAN"} + + op = str(body.get("op") or "") + if op not in {"plan.import", "plan.status", "plan.apply", "plan.rollback"}: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_OP_NOT_ALLOWED", "channel": "NODE-PLAN"} + + canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + dedup_root = STATE_ROOT / "node-plan-dedup" + dedup_root.mkdir(parents=True, exist_ok=True) + safe_id = re.sub(r"[^A-Za-z0-9._:-]", "_", frame_id)[:128] + dedup_path = dedup_root / (target + "--" + safe_id + ".json") + signature = __import__("hashlib").sha256((target + "\n" + canonical).encode("utf-8")).hexdigest() + + try: + fd = os.open(str(dedup_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + with os.fdopen(fd, "w", encoding="utf-8") as fh: + json.dump( + {"state": "pending", "signature": signature, "target": target, "id": frame_id, "op": op, "created_at": time.time()}, + fh, + sort_keys=True, + separators=(",", ":"), + ) + fh.write("\n") + except FileExistsError: + try: + previous = json.loads(dedup_path.read_text(encoding="utf-8")) + except Exception: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_DEDUP_STATE_INVALID", "channel": "NODE-PLAN"} + if previous.get("signature") != signature: + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_ID_REUSE_MISMATCH", "channel": "NODE-PLAN"} + if previous.get("state") == "done" and isinstance(previous.get("response"), dict): + cached = dict(previous["response"]) + cached["channel"] = "NODE-PLAN-CACHED" + return cached + return {"executed": False, "exit_code": None, "output": "", "error": "NODEPLAN_REQUEST_IN_PROGRESS", "channel": "NODE-PLAN"} + + def fail(error, rc=2, output=""): + try: + dedup_path.unlink() + except OSError: + pass + return {"executed": False, "exit_code": rc, "output": output, "error": error, "channel": "NODE-PLAN"} + + def finish(value): + response = { + "executed": True, + "exit_code": 0, + "output": json.dumps(value, sort_keys=True, separators=(",", ":")), + "error": "", + "channel": "NODE-PLAN", + } + final_state = { + "state": "done", + "signature": signature, + "target": target, + "id": frame_id, + "op": op, + "response": response, + "completed_at": time.time(), + } + tmp = dedup_path.with_name(dedup_path.name + ".tmp-" + str(os.getpid())) + tmp.write_text(json.dumps(final_state, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8") + os.chmod(tmp, 0o600) + os.replace(tmp, dedup_path) + return response + + if op == "plan.import": + if set(body) != {"op", "plan"} or not isinstance(body.get("plan"), dict): + return fail("NODEPLAN_IMPORT_SCHEMA_INVALID") + plan = body["plan"] + steps = plan.get("steps") if isinstance(plan, dict) else None + if ( + not isinstance(steps, list) + or len(steps) != 1 + or not isinstance(steps[0], dict) + or steps[0].get("capability") not in {"workspace.write_text", "package.install"} + ): + return fail("NODEPLAN_CAPABILITY_NOT_ALLOWED") + value, error, rc = _node_cli_json( + node, + 'export PATH="$HOME/.local/bin:$PATH"; framebridge plan import-stdin', + json.dumps(plan, separators=(",", ":")) + "\n", + ) + if rc != 0 or not isinstance(value, dict) or value.get("ok") is not True: + return fail(error or "NODEPLAN_IMPORT_FAILED", rc or 2, json.dumps(value) if value else "") + return finish(value) + + plan_id = str(body.get("plan_id") or "") + if not PLAN_ID_SAFE_RE.fullmatch(plan_id): + return fail("NODEPLAN_PLAN_ID_INVALID") + + value, error, rc = _node_cli_json( + node, + 'export PATH="$HOME/.local/bin:$PATH"; framebridge plan show ' + plan_id, + ) + if rc != 0 or not isinstance(value, dict) or value.get("ok") is not True: + return fail(error or "NODEPLAN_STATUS_FAILED", rc or 2, json.dumps(value) if value else "") + + if op == "plan.status": + if set(body) != {"op", "plan_id"}: + return fail("NODEPLAN_STATUS_SCHEMA_INVALID") + return finish(value) + + if set(body) != {"op", "plan_id", "plan_sha256"}: + return fail("NODEPLAN_CHANGE_SCHEMA_INVALID") + expected = str(body.get("plan_sha256") or "") + if not PLAN_SHA_RE.fullmatch(expected): + return fail("NODEPLAN_PLAN_SHA_INVALID") + if value.get("plan_sha256") != expected: + return fail("NODEPLAN_PLAN_SHA_MISMATCH") + + plan = value.get("plan") or {} + steps = plan.get("steps") or [] + if len(steps) != 1 or steps[0].get("capability") not in {"workspace.write_text", "package.install"}: + return fail("NODEPLAN_CAPABILITY_NOT_ALLOWED") + capability = steps[0].get("capability") + + if op == "plan.apply": + approval = value.get("approval") or {} + base_ok = ( + value.get("state") == "APPROVED" + and approval.get("one_shot") is True + and approval.get("consumed") is False + and approval.get("plan_sha256") == expected + and value.get("apply") is None + ) + if capability == "workspace.write_text": + auth_ok = approval.get("source") == "LOCAL_TTY" + else: + auth_ok = ( + approval.get("source") == "REMOTE_HUMAN_TOTP" + and approval.get("auth_method") == "totp-sha1-6-30" + and isinstance(approval.get("key_id"), str) + and bool(re.fullmatch(r"[A-Za-z0-9._:-]{1,128}", approval.get("key_id") or "")) + and isinstance(approval.get("challenge_id"), str) + and bool(re.fullmatch(r"[0-9a-f]{32}", approval.get("challenge_id") or "")) + and isinstance(approval.get("totp_step"), int) + ) + if not (base_ok and auth_ok): + return fail("NODEPLAN_APPLY_PRECONDITION_FAILED") + + changed, error, rc = _node_cli_json( + node, + 'export PATH="$HOME/.local/bin:$PATH"; framebridge plan apply ' + plan_id, + ) + if rc != 0 or not isinstance(changed, dict) or changed.get("ok") is not True: + return fail(error or "NODEPLAN_APPLY_FAILED", rc or 2, json.dumps(changed) if changed else "") + return finish(changed) + + rollback = value.get("rollback") or {} + rollback_approval = value.get("rollback_approval") or {} + base_rb_ok = ( + value.get("state") == "APPLIED" + and rollback.get("available") is True + and rollback_approval.get("one_shot") is True + and rollback_approval.get("consumed") is False + and rollback_approval.get("plan_sha256") == expected + ) + if capability == "workspace.write_text": + rb_auth_ok = rollback_approval.get("source") == "LOCAL_TTY" + else: + rb_auth_ok = ( + rollback_approval.get("source") == "REMOTE_HUMAN_TOTP" + and rollback_approval.get("auth_method") == "totp-sha1-6-30" + and isinstance(rollback_approval.get("key_id"), str) + and bool(re.fullmatch(r"[A-Za-z0-9._:-]{1,128}", rollback_approval.get("key_id") or "")) + and isinstance(rollback_approval.get("challenge_id"), str) + and bool(re.fullmatch(r"[0-9a-f]{32}", rollback_approval.get("challenge_id") or "")) + and isinstance(rollback_approval.get("totp_step"), int) + ) + if not (base_rb_ok and rb_auth_ok): + return fail("NODEPLAN_ROLLBACK_PRECONDITION_FAILED") + + changed, error, rc = _node_cli_json( + node, + 'export PATH="$HOME/.local/bin:$PATH"; framebridge plan rollback ' + plan_id, + ) + if rc != 0 or not isinstance(changed, dict) or changed.get("ok") is not True: + return fail(error or "NODEPLAN_ROLLBACK_FAILED", rc or 2, json.dumps(changed) if changed else "") + return finish(changed) + + +def first_program(command): + try: + toks = shlex.split(command, posix=True) + except ValueError: + return "" + while toks and re.match(r"^[A-Za-z_][A-Za-z0-9_]*=", toks[0]): + toks.pop(0) + return os.path.basename(toks[0]) if toks else "" + +def classify(mode, lang, code, authorized): + mode = (mode or "READONLY").upper() + lang = (lang or "BASH").upper() + + if lang not in {"BASH", "PYTHON"}: + return False, "LANG no soportado" + + if DANGEROUS.search(code) and not authorized: + return False, ( + "Operacion potencialmente destructiva/privilegiada bloqueada. " + "Requiere autorizacion explicita y AUTHORIZED=YES." + ) + + if mode == "READONLY": + if lang != "BASH": + return False, "READONLY solo admite BASH" + if "\n" in code.strip(): + return False, "READONLY solo admite una orden simple" + if any(x in code for x in [";", "&&", "||", "|", "$(", "`", ">", ">>"]): + return False, "READONLY no admite composicion de shell" + prog = first_program(code) + if prog not in READONLY_PROGRAMS: + return False, f"Programa fuera de allowlist READONLY: {prog or '(vacio)'}" + return True, "READONLY permitido" + + if mode == "EXEC": + return True, "EXEC permitido" + + return False, f"MODE no soportado: {mode}" + +def safe_job_id(job_id): + return re.sub(r"[^A-Za-z0-9._-]+", "_", job_id)[:72] or "job" + +def create_job(job_id, lang, code): + JOBS_ROOT.mkdir(parents=True, exist_ok=True) + token = f"{safe_job_id(job_id)}-{os.getpid()}-{int(time.time()*1000)}" + jobdir = JOBS_ROOT / token + jobdir.mkdir(mode=0o700) + + suffix = ".py" if lang == "PYTHON" else ".sh" + code_path = jobdir / f"payload{suffix}" + output_path = jobdir / "output.txt" + rc_path = jobdir / "exit_code.txt" + done_path = jobdir / "done" + wrapper_path = jobdir / "wrapper.sh" + + code_path.write_text(code + ("" if code.endswith("\n") else "\n"), encoding="utf-8") + os.chmod(code_path, 0o700) + + runner = ( + f"python3 {shlex.quote(str(code_path))}" + if lang == "PYTHON" + else f"bash {shlex.quote(str(code_path))}" + ) + + wrapper = f'''#!/usr/bin/env bash +set +e +export PAGER=cat +export GIT_PAGER=cat +export SYSTEMD_PAGER=cat +export MANPAGER=cat +export GIT_TERMINAL_PROMPT=0 +export PYTHONUNBUFFERED=1 +cd {shlex.quote(str(Path.home()))} + +printf '\\n=== FRAMEBRIDGE JOB {token} ===\\n' +{runner} 2>&1 | tee {shlex.quote(str(output_path))} +rc=${{PIPESTATUS[0]}} +printf '%s\\n' "$rc" > {shlex.quote(str(rc_path))} +touch {shlex.quote(str(done_path))} +printf '=== FRAMEBRIDGE DONE rc=%s ===\\n' "$rc" + +exec bash --noprofile --norc +''' + wrapper_path.write_text(wrapper, encoding="utf-8") + os.chmod(wrapper_path, 0o700) + + return { + "token": token, + "jobdir": jobdir, + "output": output_path, + "rc": rc_path, + "done": done_path, + "wrapper": wrapper_path, + } + +def prune_old_windows(): + try: + out = subprocess.check_output( + ["tmux", "list-windows", "-t", EXEC_SESSION, "-F", "#{window_index}\t#{window_name}"], + text=True, + errors="replace", + ) + except Exception: + return + jobs = [] + for line in out.splitlines(): + try: + idx, name = line.split("\t", 1) + except ValueError: + continue + if name.startswith("fb-"): + jobs.append((int(idx), name)) + jobs.sort() + for idx, _ in jobs[:-8]: + subprocess.call( + ["tmux", "kill-window", "-t", f"{EXEC_SESSION}:{idx}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + +def run_isolated(job_id, lang, code, timeout_s): + ensure_infrastructure() + reset_generation = read_reset_generation() + job = create_job(job_id, lang, code) + win_name = ("fb-" + safe_job_id(job_id))[:45] + + subprocess.check_call([ + "tmux", "new-window", "-d", + "-t", f"{EXEC_SESSION}:", + "-n", win_name, + "bash", str(job["wrapper"]), + ]) + + subprocess.call( + ["tmux", "select-window", "-t", f"{EXEC_SESSION}:{win_name}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + deadline = time.time() + timeout_s + while time.time() < deadline: + current_generation = read_reset_generation() + + if current_generation != reset_generation: + out = "" + if job["output"].exists(): + out = job["output"].read_text( + encoding="utf-8", + errors="replace", + ) + return 130, ( + out[-44000:] + + "\nABORTED_BY_RESET_EXEC: trabajo aislado terminado" + ).strip(), job["token"] + + if job["done"].exists(): + rc = 1 + if job["rc"].exists(): + try: + rc = int(job["rc"].read_text(encoding="utf-8").strip()) + except Exception: + rc = 1 + out = "" + if job["output"].exists(): + out = job["output"].read_text(encoding="utf-8", errors="replace") + prune_old_windows() + return rc, out[-48000:], job["token"] + + time.sleep(0.20) + + subprocess.call( + ["tmux", "kill-window", "-t", f"{EXEC_SESSION}:{win_name}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + out = "" + if job["output"].exists(): + out = job["output"].read_text(encoding="utf-8", errors="replace") + return 124, (out[-44000:] + "\nTIMEOUT: trabajo aislado terminado por FrameBridge").strip(), job["token"] + +def control_status(): + lines = [] + for name in (EXEC_SESSION, RESCUE_SESSION): + exists = tmux_has_session(name) + lines.append(f"SESSION {name}={'UP' if exists else 'DOWN'}") + if exists: + try: + out = subprocess.check_output( + ["tmux", "list-windows", "-t", name, + "-F", "#{window_index}:#{window_name}:active=#{window_active}:panes=#{window_panes}"], + text=True, + errors="replace", + ).strip() + if out: + lines.append(out) + except Exception as exc: + lines.append(f"ERROR list-windows {name}: {exc!r}") + return "\n".join(lines) + +def control_reset_exec(): + # RESCUE is an invariant of RESET_EXEC. + ensure_session(RESCUE_SESSION) + + generation = bump_reset_generation() + + if tmux_has_session(EXEC_SESSION): + subprocess.call( + ["tmux", "kill-session", "-t", EXEC_SESSION], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + ensure_session(EXEC_SESSION) + ensure_session(RESCUE_SESSION) + + return ( + "RESET_EXEC=PASS\n" + f"RESET_GENERATION={generation}\n" + + control_status() + ) + +def control_ensure(): + ensure_infrastructure() + return "ENSURE=PASS\n" + control_status() + +def handle_control(action): + action = (action or "").upper() + if action == "STATUS": + return 0, control_status() + if action == "ENSURE": + return 0, control_ensure() + if action == "RESET_EXEC": + return 0, control_reset_exec() + return 2, f"ACTION no soportada: {action}" + +def _cua_runtime_journal(event, msg=None, rc=None, error_code=""): + try: + payload = msg if isinstance(msg, dict) else {} + base = Path.home() / ".local/state/framebridge/cua-runtime" + base.mkdir(parents=True, exist_ok=True) + path = base / "cua-runtime.log" + record = { + "time": __import__("datetime").datetime.now( + __import__("datetime").timezone.utc + ).isoformat(), + "event": str(event)[:64], + "id": str(payload.get("id", ""))[:128], + "action": str(payload.get("action", ""))[:32], + "semantic": str( + payload.get( + "semantic_action", + payload.get("semantic", "") + ) + )[:64], + "authorized": ( + "YES" + if str(payload.get("authorized", "NO")).upper() == "YES" + else "NO" + ), + "rc": rc, + "error_code": str(error_code)[:128], + } + with path.open("a", encoding="utf-8") as fh: + fh.write( + json.dumps( + record, + ensure_ascii=True, + separators=(",", ":"), + ) + + "\n" + ) + except Exception: + pass + + + +def run_ui_helper(name, action): + helper = UI_HELPER_ROOT / name + + if not helper.is_file(): + return 70, "UI_HELPER_NOT_FOUND" + + env = os.environ.copy() + env.setdefault("DISPLAY", ":0") + env.setdefault( + "XAUTHORITY", + str(Path.home() / ".Xauthority"), + ) + + try: + proc = subprocess.run( + [str(helper), str(action)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=15, + check=False, + env=env, + ) + except subprocess.TimeoutExpired: + return 71, "UI_HELPER_TIMEOUT" + except Exception as exc: + return 72, "UI_HELPER_ERROR:" + type(exc).__name__ + + out = (proc.stdout or "").strip() + err = (proc.stderr or "").strip() + detail = out if out else err + + return proc.returncode, detail[:4000] + + +def handle_ui_request(msg, op): + target = str(msg.get("target", "")) + authorized = str(msg.get("authorized", "")).upper() + action = str(msg.get("action", "")).upper() + + if target != "ASUS": + return 73, "UI_TARGET_MISMATCH" + + if authorized != "YES": + return 74, "UI_AUTHORIZATION_REQUIRED" + + if op == "progress_console": + if action not in {"ENSURE", "FOCUS", "STATUS"}: + return 75, "PROGRESS_ACTION_INVALID" + return run_ui_helper( + "open-progress-console.sh", + action, + ) + + if op == "interactive_terminal": + if action not in {"OPEN", "ENSURE", "FOCUS", "STATUS", "CLOSE", "TOGGLE"}: + return 76, "TERMINAL_ACTION_INVALID" + return run_ui_helper( + "open-interactive-terminal.sh", + action, + ) + + return 77, "UI_OPERATION_UNSUPPORTED" + +def main(): + while True: + msg = read_message() + if msg is None: + break + + op = str(msg.get("op", "exec")).lower() + + try: + if op in {"progress_console", "interactive_terminal"}: + rc, out = handle_ui_request(msg, op) + send_message({ + "executed": rc == 0, + "exit_code": rc, + "output": out, + "error": "" if rc == 0 else out, + "channel": ( + "PROGRESS-CONSOLE-DIRECT" + if op == "progress_console" + else "INTERACTIVE-TERMINAL-DIRECT" + ), + }) + continue + + if op == "control": + rc, out = handle_control(str(msg.get("action", ""))) + send_message({ + "executed": rc == 0, + "exit_code": rc, + "output": out, + "error": "" if rc == 0 else out, + "channel": "CONTROL-DIRECT", + }) + continue + if op == "voice_listen_once": + resp = start_listen_once() + send_message({ + "_fb_voice_request_id": str(msg.get("_fb_voice_request_id", ""))[:128], + "executed": bool(resp.get("ok")), + "exit_code": 0 if resp.get("ok") else 1, + "output": "", + "error": "" if resp.get("ok") else str(resp.get("error", "")), + "channel": "VOICE-LISTEN-DIRECT", + "voice_started": bool(resp.get("started")), + }) + continue + + if op == "voice_status": + request_id = str(msg.get("_fb_voice_request_id", ""))[:128] + target = str(msg.get("target", "")) + + if target != "ASUS": + send_message({ + "executed": False, + "exit_code": 53, + "output": "", + "error": "VOICE_TARGET_MISMATCH", + "channel": "VOICE-STATUS-DIRECT", + "voice_state": "listo", + "voice_available": False, + "_fb_voice_request_id": request_id, + }) + continue + + from framebridge_voice_status import read_voice_status + + voice = read_voice_status() + + send_message({ + "executed": True, + "exit_code": 0, + "output": "", + "error": "", + "channel": "VOICE-STATUS-DIRECT", + "voice_state": voice["state"], + "voice_available": bool(voice["available"]), + "voice_updated_at": voice["updated_at"], + "_fb_voice_request_id": request_id, + }) + continue + + if op == "cua": + from framebridge_cua_linux import handle_cua_message, CuaTransportError + + _cua_runtime_journal("START", msg) + + try: + cua_result = handle_cua_message(msg) + cua_rc = 0 + _cua_runtime_journal("END", msg, rc=0) + except CuaTransportError as exc: + cua_result = { + "ok": False, + "error_code": exc.code, + "error": exc.message, + } + cua_rc = 52 + _cua_runtime_journal( + "END", + msg, + rc=cua_rc, + error_code=exc.code, + ) + except Exception: + cua_result = { + "ok": False, + "error_code": "CUA_INTERNAL_ERROR", + "error": "CUA internal error", + } + cua_rc = 53 + _cua_runtime_journal( + "END", + msg, + rc=cua_rc, + error_code="CUA_INTERNAL_ERROR", + ) + + if ( + isinstance(cua_result, dict) + and cua_result.get("_frame_output") + ): + cua_out = str(cua_result["_frame_output"]) + else: + cua_out = json.dumps( + cua_result, + ensure_ascii=False, + separators=(",", ":"), + ) + + send_message({ + "executed": cua_rc == 0, + "exit_code": cua_rc, + "output": cua_out, + "error": "" if cua_rc == 0 else cua_out, + "channel": "CUA-DIRECT", + }) + continue + + if op == "term": + term_job_id = str(msg.get("id", "job")) + term_mode = str(msg.get("mode", "EXEC")).upper() + term_lang = str(msg.get("lang", "BASH")).upper() + term_code = str(msg.get("command", "")).rstrip() + term_authorized = ( + str(msg.get("authorized", "NO")).upper() == "YES" + ) + + try: + term_timeout = int(msg.get("timeout", 300)) + except Exception: + term_timeout = 300 + + term_timeout = max(1, min(term_timeout, 1800)) + + if term_lang != "BASH": + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": "FBTERM_R1_LANG_REQUIRES_BASH", + "channel": "FBTERM-PTY", + }) + continue + + allowed, reason = classify( + term_mode, + term_lang, + term_code, + term_authorized, + ) + + if not allowed: + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": reason, + "channel": "FBTERM-PTY", + }) + continue + + fbterm_root = os.environ.get( + "FRAMEBRIDGE_FBTERM_ROOT", + os.path.expanduser( + "~/.local/share/framebridge/fbterm" + ), + ) + + fbterm_engine = os.environ.get( + "FRAMEBRIDGE_FBTERM_ENGINE", + os.path.join( + fbterm_root, + "bin", + "fbterm_engine.py", + ), + ) + + fbterm_session = os.environ.get( + "FRAMEBRIDGE_FBTERM_SESSION", + "framebridge-fbterm-r1", + ) + + fbterm_title = os.environ.get( + "FRAMEBRIDGE_FBTERM_TITLE", + "FrameBridge Terminal R1", + ) + + fbterm_incoming = os.path.join( + fbterm_root, + "incoming", + ) + + os.makedirs( + fbterm_incoming, + mode=0o700, + exist_ok=True, + ) + + safe_term_id = safe_job_id(term_job_id) + + term_command_path = os.path.join( + fbterm_incoming, + safe_term_id + ".bash", + ) + + if os.path.exists(term_command_path): + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": "FBTERM_R1_JOB_ALREADY_EXISTS", + "channel": "FBTERM-PTY", + }) + continue + + with open( + term_command_path, + "x", + encoding="utf-8", + ) as term_file: + term_file.write(term_code) + term_file.write("\n") + + os.chmod(term_command_path, 0o600) + + term_env = os.environ.copy() + term_env["FBTERM_ROOT"] = fbterm_root + term_env["FBTERM_SESSION"] = fbterm_session + term_env["FBTERM_TITLE"] = fbterm_title + + try: + term_proc = subprocess.run( + [ + "python3", + fbterm_engine, + "exec", + safe_term_id, + str(term_timeout), + term_command_path, + ], + env=term_env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=term_timeout + 15, + ) + + except subprocess.TimeoutExpired: + send_message({ + "executed": False, + "exit_code": 124, + "output": "", + "error": "FBTERM_R1_ENGINE_TIMEOUT", + "channel": "FBTERM-PTY", + }) + continue + + except Exception as exc: + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": ( + "FBTERM_R1_ENGINE_ERROR:" + + type(exc).__name__ + ), + "channel": "FBTERM-PTY", + }) + continue + + if term_proc.returncode != 0: + send_message({ + "executed": False, + "exit_code": term_proc.returncode, + "output": "", + "error": "FBTERM_R1_ENGINE_FAILED", + "channel": "FBTERM-PTY", + }) + continue + + try: + term_result = json.loads( + term_proc.stdout.strip().splitlines()[-1] + ) + + except Exception: + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": "FBTERM_R1_RESULT_PARSE_FAILED", + "channel": "FBTERM-PTY", + }) + continue + + term_rc = int( + term_result.get("EXIT", 1) + ) + + compact = [] + + for key in ( + "COMMAND_SHA256", + "STDOUT_SHA256", + "STDERR_SHA256", + "STDOUT_BYTES", + "STDERR_BYTES", + "COMMAND_HASH_GATE", + ): + value = term_result.get(key) + + if value is not None: + compact.append( + key + "=" + str(value) + ) + + # FBTERM_DUPLEX_R1: return last durable RESULTADO_FINAL from stdout + duplex_final = None + stdout_path = term_result.get("STDOUT_PATH") + if stdout_path: + try: + with open(stdout_path, "r", encoding="utf-8", errors="replace") as fh: + stdout_lines = fh.read().splitlines() + for stdout_line in reversed(stdout_lines): + if stdout_line.startswith("RESULTADO_FINAL="): + final_value = stdout_line.split("=", 1)[1].strip()[:1000] + duplex_final = final_value + if final_value: + compact.append("RESULTADO_FINAL=" + final_value) + break + except OSError: + pass + + send_message({ + "executed": True, + "exit_code": term_rc, + "output": " ".join(compact) + (("\nRESULTADO_FINAL=" + duplex_final) if duplex_final else ""), # FBTERM_DUPLEX_LINEFIX_R2 + "RESULTADO_FINAL": duplex_final, # FBTERM_DUPLEX_STRUCTURED_R1 + "error": "", + "job_token": safe_term_id, + "channel": "FBTERM-PTY", + }) + continue + + target = str(msg.get("target", "ASUS")).upper() + if target != "ASUS": + if str(msg.get("lang", "")).upper() == "FRAMEBRIDGE-PLAN-V1": + send_message(route_classic_plan(msg)) + else: + send_message(route_classic_node(msg)) + continue + + job_id = str(msg.get("id", "job")) + mode = str(msg.get("mode", "READONLY")).upper() + lang = str(msg.get("lang", "BASH")).upper() + code = str(msg.get("command", "")).rstrip() + authorized = str(msg.get("authorized", "NO")).upper() == "YES" + + try: + timeout_s = int(msg.get("timeout", 300)) + except Exception: + timeout_s = 300 + timeout_s = max(5, min(timeout_s, 1800)) + + allowed, reason = classify(mode, lang, code, authorized) + if not allowed: + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": reason, + "channel": "EXEC-ISOLATED", + }) + continue + + rc, out, token = run_isolated( + job_id=job_id, + lang=lang, + code=code, + timeout_s=timeout_s, + ) + send_message({ + "executed": True, + "exit_code": rc, + "output": out, + "error": "", + "job_token": token, + "channel": "EXEC-ISOLATED", + }) + + except Exception as exc: + send_message({ + "executed": False, + "exit_code": None, + "output": "", + "error": repr(exc), + "channel": "HOST-ERROR", + }) + +if __name__ == "__main__": + main() diff --git a/source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py b/source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py new file mode 100644 index 0000000..ab10bb3 --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/framebridge_voice_command.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import json +import os +import socket +import threading +from pathlib import Path + +_lock = threading.Lock() +_active = False + +def _socket_path(): + runtime = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}")) + return runtime / "framebridge-voice-r1.sock" + +def _listen_worker(): + global _active + try: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.settimeout(30) + s.connect(str(_socket_path())) + s.sendall(b'{"cmd":"listen_once"}\n') + f = s.makefile("rb") + f.readline() # respuesta local; NO se expone + s.close() + except Exception: + pass + finally: + with _lock: + _active = False + +def start_listen_once(): + global _active + with _lock: + if _active: + return {"ok": True, "started": False, "reason": "already_active"} + if not _socket_path().is_socket(): + return {"ok": False, "error": "VOICE_DAEMON_NOT_AVAILABLE"} + _active = True + threading.Thread(target=_listen_worker, daemon=True).start() + return {"ok": True, "started": True} diff --git a/source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py b/source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py new file mode 100644 index 0000000..2ea4c55 --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/framebridge_voice_status.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import json +import os +import stat +import time +from pathlib import Path + +_ALLOWED = {"escuchando", "pensando", "trabajando", "listo", "detenido"} +_MAX_BYTES = 16384 +_MAX_AGE_SECONDS = 5.0 + +def read_voice_status(home=None): + base = Path(home).expanduser() if home else Path.home() + path = base / ".local/share/framebridge-voice/state-r1.json" + + safe = { + "state": "listo", + "available": False, + "updated_at": None, + } + + try: + flags = os.O_RDONLY + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + if hasattr(os, "O_NONBLOCK"): + flags |= os.O_NONBLOCK + + fd = os.open(path, flags) + try: + st = os.fstat(fd) + + if not stat.S_ISREG(st.st_mode): + return safe + + if st.st_uid != os.getuid(): + return safe + + if st.st_size > _MAX_BYTES: + return safe + + raw = os.read(fd, _MAX_BYTES + 1) + finally: + os.close(fd) + + if len(raw) > _MAX_BYTES: + return safe + + data = json.loads(raw.decode("utf-8")) + + state = str(data.get("status", "listo")).lower() + if state not in _ALLOWED: + state = "listo" + + updated = data.get("updated_at") + if not isinstance(updated, (int, float)): + return safe + + age = max(0.0, time.time() - float(updated)) + + if age > _MAX_AGE_SECONDS: + return safe + + if state == "detenido": + state = "listo" + + return { + "state": state, + "available": True, + "updated_at": float(updated), + } + + except ( + FileNotFoundError, + PermissionError, + OSError, + ValueError, + json.JSONDecodeError, + ): + return safe diff --git a/source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh b/source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh new file mode 100755 index 0000000..163cb5d --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/open-interactive-terminal.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +set +e + +ACTION="$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')" +[ -n "$ACTION" ] || ACTION="ENSURE" +[ "$ACTION" = "OPEN" ] && ACTION="ENSURE" + +case "$ACTION" in + ENSURE|FOCUS|STATUS|CLOSE|TOGGLE) ;; + *) + echo "INTERACTIVE_TERMINAL_GATE=FAIL" + echo "ERROR=INVALID_ACTION" + exit 2 + ;; +esac + +TITLE="FrameBridge - Terminal" +STATE_DIR="${FRAMEBRIDGE_UI_STATE_DIR:-$HOME/.local/share/framebridge/ui-state}" +STATE_FILE="$STATE_DIR/interactive-terminal.window" +DISPLAY_VALUE="$(printenv DISPLAY 2>/dev/null)" +[ -n "$DISPLAY_VALUE" ] || DISPLAY_VALUE=":0" +XAUTH_VALUE="$(printenv XAUTHORITY 2>/dev/null)" +[ -n "$XAUTH_VALUE" ] || XAUTH_VALUE="$HOME/.Xauthority" + +TERM_FG="#a8ffb5" +TERM_BG="#010402" +TERM_CURSOR="#c8ffd1" + +mkdir -p "$STATE_DIR" +chmod 700 "$STATE_DIR" 2>/dev/null || true + +window_exists() +{ + W="$1" + [ -n "$W" ] || return 1 + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool getwindowname "$W" >/dev/null 2>&1 +} + +read_state_window() +{ + if [ -r "$STATE_FILE" ]; then + head -n1 "$STATE_FILE" 2>/dev/null | tr -cd '0-9' + fi +} + +write_state_window() +{ + W="$1" + printf '%s\n' "$W" > "$STATE_FILE" + chmod 600 "$STATE_FILE" 2>/dev/null || true +} + +clear_state() +{ + rm -f "$STATE_FILE" +} + +W="$(read_state_window)" +if ! window_exists "$W"; then + W="" + clear_state +fi + +if [ "$ACTION" = "STATUS" ]; then + echo "INTERACTIVE_TERMINAL_GATE=PASS" + if [ -n "$W" ]; then + echo "STATE=ON" + echo "WINDOW_ID=$W" + else + echo "STATE=OFF" + echo "WINDOW_ID=" + fi + echo "INTERACTIVE=YES" + echo "STYLE=GREEN_MONOCHROME" + exit 0 +fi + +if [ "$ACTION" = "TOGGLE" ]; then + if [ -n "$W" ]; then + ACTION="CLOSE" + else + ACTION="ENSURE" + fi +fi + +if [ "$ACTION" = "CLOSE" ]; then + if [ -n "$W" ]; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool windowclose "$W" >/dev/null 2>&1 || true + for _ in $(seq 1 30); do + if ! window_exists "$W"; then + break + fi + sleep 0.1 + done + fi + clear_state + echo "INTERACTIVE_TERMINAL_GATE=PASS" + echo "ACTION=CLOSE" + echo "STATE=OFF" + echo "WINDOW_ID=" + echo "INTERACTIVE=YES" + echo "STYLE=GREEN_MONOCHROME" + exit 0 +fi + +if [ -z "$W" ]; then + BEFORE="$(mktemp)" + AFTER="$(mktemp)" + trap 'rm -f "$BEFORE" "$AFTER"' EXIT + + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" wmctrl -lx 2>/dev/null | awk '$3 ~ /gnome-terminal-server.Gnome-terminal/ {print $1}' | sort -u > "$BEFORE" + + export FB_TERM_TITLE="$TITLE" + export FB_TERM_FG="$TERM_FG" + export FB_TERM_BG="$TERM_BG" + export FB_TERM_CURSOR="$TERM_CURSOR" + + if command -v gnome-terminal >/dev/null 2>&1; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" gnome-terminal --title="$TITLE" -- bash -lc ' + printf "\033]0;%s\007" "$FB_TERM_TITLE" + printf "\033]10;%s\007" "$FB_TERM_FG" + printf "\033]11;%s\007" "$FB_TERM_BG" + printf "\033]12;%s\007" "$FB_TERM_CURSOR" + exec bash -l + ' >/dev/null 2>&1 & + elif command -v xfce4-terminal >/dev/null 2>&1; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xfce4-terminal --title="$TITLE" --command="bash -lc 'printf \"\\033]10;$TERM_FG\\007\\033]11;$TERM_BG\\007\"; exec bash -l'" >/dev/null 2>&1 & + elif command -v xterm >/dev/null 2>&1; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xterm -T "$TITLE" -fg "$TERM_FG" -bg "$TERM_BG" -e bash -l >/dev/null 2>&1 & + else + echo "INTERACTIVE_TERMINAL_GATE=FAIL" + echo "ERROR=NO_SUPPORTED_TERMINAL" + exit 3 + fi + + for _ in $(seq 1 50); do + TITLE_W="$(DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool search --name "^$TITLE$" 2>/dev/null | tail -n1)" + if window_exists "$TITLE_W"; then + W="$TITLE_W" + break + fi + + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" wmctrl -lx 2>/dev/null | awk '$3 ~ /gnome-terminal-server.Gnome-terminal/ {print $1}' | sort -u > "$AFTER" + + NEW_HEX="$(comm -13 "$BEFORE" "$AFTER" | tail -n1)" + if [ -n "$NEW_HEX" ]; then + W="$((NEW_HEX))" + if window_exists "$W"; then + break + fi + W="" + fi + sleep 0.1 + done + + if [ -z "$W" ]; then + echo "INTERACTIVE_TERMINAL_GATE=FAIL" + echo "ERROR=WINDOW_NOT_FOUND" + exit 4 + fi + + write_state_window "$W" +fi + +DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool windowactivate --sync "$W" >/dev/null 2>&1 || true + +echo "INTERACTIVE_TERMINAL_GATE=PASS" +echo "ACTION=$ACTION" +echo "STATE=ON" +echo "WINDOW_ID=$W" +echo "INTERACTIVE=YES" +echo "STYLE=GREEN_MONOCHROME" +echo "TERM_FG=$TERM_FG" +echo "TERM_BG=$TERM_BG" +exit 0 diff --git a/source/sources/original-asus/framebridge-native/host/open-progress-console.sh b/source/sources/original-asus/framebridge-native/host/open-progress-console.sh new file mode 100755 index 0000000..297c86c --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/open-progress-console.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +set +e + +ACTION="$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')" +[ -n "$ACTION" ] || ACTION="ENSURE" + +case "$ACTION" in + ENSURE|FOCUS|STATUS) ;; + *) + echo "PROGRESS_CONSOLE_GATE=FAIL" + echo "ERROR=INVALID_ACTION" + exit 2 + ;; +esac + +TITLE="FrameBridge - Progreso DIG" +LOG_DIR="${FRAMEBRIDGE_PROGRESS_DIR:-$HOME/.local/state/framebridge/progress}" +LOG="${FRAMEBRIDGE_PROGRESS_LOG:-$LOG_DIR/framebridge-progress.log}" +DISPLAY_VALUE="$(printenv DISPLAY 2>/dev/null)" +[ -n "$DISPLAY_VALUE" ] || DISPLAY_VALUE=":0" +XAUTH_VALUE="$(printenv XAUTHORITY 2>/dev/null)" +[ -n "$XAUTH_VALUE" ] || XAUTH_VALUE="$HOME/.Xauthority" + +mkdir -p "$LOG_DIR" +chmod 700 "$LOG_DIR" 2>/dev/null || true +touch "$LOG" +chmod 600 "$LOG" 2>/dev/null || true + +if [ ! -s "$LOG" ]; then + cat > "$LOG" <<'HEADER' +============================================================ + FRAMEBRIDGE ASUS // DIG PROGRESS + MODO: SOLO LECTURA +============================================================ +HEADER +fi + +find_window() +{ + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool search --name "^$TITLE$" 2>/dev/null | tail -n1 +} + +W="$(find_window)" + +if [ "$ACTION" = "STATUS" ]; then + echo "PROGRESS_CONSOLE_GATE=PASS" + if [ -n "$W" ]; then + echo "STATE=ON" + echo "WINDOW_ID=$W" + else + echo "STATE=OFF" + echo "WINDOW_ID=" + fi + echo "LOG=$LOG" + echo "READ_ONLY=YES" + echo "STYLE=GREEN_MONOCHROME_DIG" + exit 0 +fi + +if [ -z "$W" ]; then + export FB_PROGRESS_LOG="$LOG" + export FB_PROGRESS_TITLE="$TITLE" + + if command -v gnome-terminal >/dev/null 2>&1; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" gnome-terminal --title="$TITLE" -- bash -lc ' + printf "\033]0;%s\007" "$FB_PROGRESS_TITLE" + printf "\033]10;#a8ffb5\007" + printf "\033]11;#010402\007" + printf "\033[1;38;2;168;255;181m" + printf "============================================================\n" + printf " FRAMEBRIDGE // DIG PROGRESS // SOLO LECTURA\n" + printf "============================================================\n" + exec tail -n 200 -F "$FB_PROGRESS_LOG" + ' >/dev/null 2>&1 & + elif command -v xterm >/dev/null 2>&1; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xterm -T "$TITLE" -fg "#a8ffb5" -bg "#010402" -e bash -lc 'exec tail -n 200 -F "$FB_PROGRESS_LOG"' >/dev/null 2>&1 & + else + echo "PROGRESS_CONSOLE_GATE=FAIL" + echo "ERROR=NO_SUPPORTED_TERMINAL" + exit 3 + fi + + for _ in $(seq 1 50); do + W="$(find_window)" + [ -n "$W" ] && break + sleep 0.1 + done +fi + +if [ -z "$W" ]; then + echo "PROGRESS_CONSOLE_GATE=FAIL" + echo "ERROR=WINDOW_NOT_FOUND" + exit 4 +fi + +if command -v wmctrl >/dev/null 2>&1; then + GEOM="$(DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool getdisplaygeometry 2>/dev/null)" + SW="$(printf '%s' "$GEOM" | awk '{print $1}')" + SH="$(printf '%s' "$GEOM" | awk '{print $2}')" + if [ -n "$SW" ] && [ -n "$SH" ]; then + H=330 + Y=$((SH - H - 28)) + [ "$Y" -lt 0 ] && Y=0 + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" wmctrl -ir "$W" -e "0,0,$Y,$SW,$H" >/dev/null 2>&1 || true + fi +fi + +if [ "$ACTION" = "FOCUS" ]; then + DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool windowactivate --sync "$W" >/dev/null 2>&1 || true +fi + +echo "PROGRESS_CONSOLE_GATE=PASS" +echo "ACTION=$ACTION" +echo "STATE=ON" +echo "WINDOW_ID=$W" +echo "LOG=$LOG" +echo "READ_ONLY=YES" +echo "STYLE=GREEN_MONOCHROME_DIG" +exit 0 diff --git a/source/sources/original-asus/framebridge-native/host/open-terminal.sh b/source/sources/original-asus/framebridge-native/host/open-terminal.sh new file mode 100755 index 0000000..d5c1808 --- /dev/null +++ b/source/sources/original-asus/framebridge-native/host/open-terminal.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail +SESSION="framebridge" +tmux has-session -t "$SESSION" 2>/dev/null || tmux new-session -d -s "$SESSION" + +if command -v gnome-terminal >/dev/null 2>&1; then + exec gnome-terminal -- bash -lc "tmux attach -t '$SESSION'" +elif command -v xfce4-terminal >/dev/null 2>&1; then + exec xfce4-terminal --command="bash -lc \"tmux attach -t '$SESSION'\"" +elif command -v konsole >/dev/null 2>&1; then + exec konsole -e bash -lc "tmux attach -t '$SESSION'" +elif command -v xterm >/dev/null 2>&1; then + exec xterm -e tmux attach -t "$SESSION" +else + echo "No encontré terminal gráfica compatible." + echo "Ejecuta manualmente: tmux attach -t $SESSION" + exit 1 +fi diff --git a/source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py b/source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py new file mode 100755 index 0000000..4f89490 --- /dev/null +++ b/source/sources/original-asus/tool-adapter/bin/framebridge_tool_adapter.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 +import hashlib +import json +import os +import re +import socket +import stat +import struct +import subprocess +import sys +from pathlib import Path + +VERSION = 1 +MAX_SCRIPT_BYTES = 262144 +MAX_REQUEST_BYTES = 393216 +JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") + +HOME = Path.home() +FBROOT = HOME / ".local/share/framebridge/fbterm" +ENGINE = FBROOT / "bin/fbterm_engine.py" + +ROOT = Path( + os.environ.get( + "FRAMEBRIDGE_TOOL_ROOT", + str(HOME / ".local/share/framebridge/tool-adapter") + ) +) +SOCKET_PATH = ROOT / "adapter.sock" +INBOX = ROOT / "inbox" +PID_FILE = ROOT / "adapter.pid" +NATIVE_HOST = Path(os.environ.get("FRAMEBRIDGE_NATIVE_HOST", str(HOME / ".local/lib/framebridge-native/host/framebridge_host.py"))) + + +def prepare_root(): + ROOT.mkdir(parents=True, exist_ok=True) + INBOX.mkdir(parents=True, exist_ok=True) + os.chmod(ROOT, 0o700) + os.chmod(INBOX, 0o700) + + +def recv_request(conn): + data = bytearray() + + while True: + chunk = conn.recv(65536) + if not chunk: + break + + data.extend(chunk) + + if len(data) > MAX_REQUEST_BYTES: + raise ValueError("REQUEST_TOO_LARGE") + + if b"\n" in data: + break + + raw = bytes(data).split(b"\n", 1)[0] + + if not raw: + raise ValueError("EMPTY_REQUEST") + + return json.loads(raw.decode("utf-8")) + + +def send_response(conn, obj): + raw = ( + json.dumps( + obj, + ensure_ascii=True, + sort_keys=True, + separators=(",", ":"), + ) + + "\n" + ).encode("utf-8") + + try: + conn.sendall(raw) + except (BrokenPipeError, ConnectionResetError): + return False + return True + + +def peer_uid(conn): + fmt = "3i" + raw = conn.getsockopt( + socket.SOL_SOCKET, + socket.SO_PEERCRED, + struct.calcsize(fmt), + ) + _pid, uid, _gid = struct.unpack(fmt, raw) + return uid + + +def last_result_final(stdout_path): + try: + lines = Path(stdout_path).read_text( + encoding="utf-8", + errors="replace", + ).splitlines() + except OSError: + return None + + for line in reversed(lines): + if line.startswith("RESULTADO_FINAL="): + return line.split("=", 1)[1].strip()[:2000] + + return None + + +def engine_result(stdout): + for line in reversed(stdout.splitlines()): + line = line.strip() + + if not line.startswith("{"): + continue + + try: + obj = json.loads(line) + except json.JSONDecodeError: + continue + + if isinstance(obj, dict): + return obj + + return None + + +def native_cua_roundtrip(payload, timeout_s=60): + if not NATIVE_HOST.is_file(): + raise ValueError("FRAMEBRIDGE_NATIVE_HOST_NOT_FOUND") + + raw = json.dumps( + payload, + ensure_ascii=True, + separators=(",", ":"), + ).encode("utf-8") + + frame = struct.pack(" 2_000_000: + raise ValueError("CUA_NATIVE_RESPONSE_SIZE_INVALID") + + body = out[4:4 + size] + + if len(body) != size: + raise ValueError("CUA_NATIVE_RESPONSE_TRUNCATED") + + try: + response = json.loads(body.decode("utf-8")) + except Exception: + raise ValueError("CUA_NATIVE_RESPONSE_INVALID_JSON") + + if not isinstance(response, dict): + raise ValueError("CUA_NATIVE_RESPONSE_NOT_OBJECT") + + return response + + +def handle_cua_request(req, job_id): + timeout_s = int(req.get("timeout_s", 60)) + + if not 1 <= timeout_s <= 180: + raise ValueError("INVALID_CUA_TIMEOUT") + + cua = req.get("cua") + + if not isinstance(cua, dict): + raise ValueError("CUA_PAYLOAD_REQUIRED") + + payload = dict(cua) + payload["op"] = "cua" + payload["id"] = job_id + payload["target"] = "ASUS" + payload["authorized"] = "YES" + + response = native_cua_roundtrip( + payload, + timeout_s=timeout_s, + ) + + return { + "version": VERSION, + "ok": bool(response.get("executed")), + "job_id": job_id, + "mode": "CUA", + "action": str(payload.get("action", ""))[:32], + "channel": str(response.get("channel", "CUA-DIRECT")), + "exit_code": response.get("exit_code"), + "frame_output": str(response.get("output", "")), + "error": str(response.get("error", "")), + } + + +def handle(req): + if not isinstance(req, dict): + raise ValueError("REQUEST_NOT_OBJECT") + + if req.get("version") != VERSION: + raise ValueError("UNSUPPORTED_VERSION") + + job_id = str(req.get("job_id", "")) + + if not JOB_RE.fullmatch(job_id): + raise ValueError("INVALID_JOB_ID") + + mode = str(req.get("mode", "EXEC")).upper() + + if mode != "EXEC": + raise ValueError("UNSUPPORTED_MODE") + + if req.get("authorized") is not True: + raise ValueError("AUTHORIZED_TRUE_REQUIRED") + + request_type = str( + req.get("request_type", "exec") + ).lower().strip() + + if request_type == "cua": + return handle_cua_request(req, job_id) + + if request_type != "exec": + raise ValueError("UNSUPPORTED_REQUEST_TYPE") + + script = req.get("script") + + if not isinstance(script, str) or not script: + raise ValueError("SCRIPT_REQUIRED") + + script_bytes = script.encode("utf-8") + + if len(script_bytes) > MAX_SCRIPT_BYTES: + raise ValueError("SCRIPT_TOO_LARGE") + + timeout_s = int(req.get("timeout_s", 120)) + + if not 1 <= timeout_s <= 600: + raise ValueError("INVALID_TIMEOUT") + + digest = hashlib.sha256(script_bytes).hexdigest() + + expected = req.get("sha256") + + if expected is not None: + expected = str(expected).lower() + + if digest != expected: + raise ValueError("SCRIPT_SHA256_MISMATCH") + + command_path = INBOX / f"{job_id}.bash" + temp_path = INBOX / f".{job_id}.tmp" + + temp_path.write_bytes(script_bytes) + os.chmod(temp_path, 0o600) + temp_path.replace(command_path) + + proc = subprocess.run( + [ + sys.executable, + str(ENGINE), + "exec", + job_id, + str(timeout_s), + str(command_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=timeout_s + 45, + check=False, + env={ + **os.environ, + "FBTERM_ROOT": str(FBROOT), + "FBTERM_SESSION": os.environ.get( + "FBTERM_SESSION", + "framebridge-fbterm-r1", + ), + "FBTERM_TITLE": os.environ.get( + "FBTERM_TITLE", + "FrameBridge Terminal R1", + ), + }, + ) + + result = engine_result(proc.stdout) + + if result is None: + return { + "version": VERSION, + "ok": False, + "job_id": job_id, + "adapter_error": "ENGINE_RESULT_NOT_FOUND", + "engine_rc": proc.returncode, + "engine_stderr": proc.stderr[-2000:], + "command_sha256": digest, + } + + stdout_path = result.get("STDOUT_PATH") + stderr_path = result.get("STDERR_PATH") + + return { + "version": VERSION, + "ok": True, + "job_id": job_id, + "mode": mode, + "command_sha256": digest, + "engine_rc": proc.returncode, + "exit_code": result.get("EXIT"), + "result_final": ( + last_result_final(stdout_path) + if stdout_path + else None + ), + "stdout_path": stdout_path, + "stderr_path": stderr_path, + "stdout_sha256": result.get("STDOUT_SHA256"), + "stderr_sha256": result.get("STDERR_SHA256"), + "command_hash_gate": result.get( + "COMMAND_HASH_GATE" + ), + "pty": result.get("PTY"), + "pane_pid": result.get("PANE_PID"), + "pane_tty": result.get("PANE_TTY"), + "shell_ready": result.get("SHELL_READY"), + } + + +def serve(): + prepare_root() + + if not ENGINE.is_file(): + raise RuntimeError("FBTERM_ENGINE_NOT_FOUND") + + if SOCKET_PATH.exists(): + st = SOCKET_PATH.lstat() + + if not stat.S_ISSOCK(st.st_mode): + raise RuntimeError("SOCKET_PATH_OCCUPIED") + + SOCKET_PATH.unlink() + + server = socket.socket( + socket.AF_UNIX, + socket.SOCK_STREAM, + ) + + server.bind(str(SOCKET_PATH)) + os.chmod(SOCKET_PATH, 0o600) + server.listen(4) + + PID_FILE.write_text( + str(os.getpid()) + "\n", + encoding="ascii", + ) + os.chmod(PID_FILE, 0o600) + + try: + while True: + conn, _ = server.accept() + + with conn: + try: + if peer_uid(conn) != os.getuid(): + raise PermissionError( + "PEER_UID_MISMATCH" + ) + + req = recv_request(conn) + resp = handle(req) + + except Exception as exc: + resp = { + "version": VERSION, + "ok": False, + "adapter_error": ( + f"{type(exc).__name__}:" + f"{exc}" + )[:2000], + } + + send_response(conn, resp) + + finally: + server.close() + + try: + SOCKET_PATH.unlink() + except FileNotFoundError: + pass + + +if __name__ == "__main__": + action = sys.argv[1] if len(sys.argv) > 1 else "" + + if action == "serve": + serve() + else: + print( + json.dumps( + { + "ok": False, + "error": "USAGE: serve", + }, + sort_keys=True, + ) + ) + raise SystemExit(64) diff --git a/source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py b/source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py new file mode 100755 index 0000000..6a9f8fb --- /dev/null +++ b/source/sources/original-asus/tool-adapter/bin/framebridge_tool_client.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +import json +import os +import socket +import sys +from pathlib import Path + +HOME = Path.home() +ROOT = Path( + os.environ.get( + "FRAMEBRIDGE_TOOL_ROOT", + str(HOME / ".local/share/framebridge/tool-adapter") + ) +) +SOCKET_PATH = ROOT / "adapter.sock" + +if len(sys.argv) != 2: + print("USAGE: framebridge_tool_client.py REQUEST.json") + raise SystemExit(64) + +request_path = Path(sys.argv[1]) +request = json.loads( + request_path.read_text(encoding="utf-8") +) + +raw = ( + json.dumps( + request, + ensure_ascii=True, + separators=(",", ":"), + ) + + "\n" +).encode("utf-8") + +sock = socket.socket( + socket.AF_UNIX, + socket.SOCK_STREAM, +) + +sock.connect(str(SOCKET_PATH)) +sock.sendall(raw) +sock.shutdown(socket.SHUT_WR) + +data = bytearray() + +while True: + chunk = sock.recv(65536) + + if not chunk: + break + + data.extend(chunk) + +sock.close() + +response = json.loads( + bytes(data).decode("utf-8") +) + +print( + json.dumps( + response, + ensure_ascii=True, + sort_keys=True, + ) +) diff --git a/source/sources/original-gerameg/MANIFEST.json b/source/sources/original-gerameg/MANIFEST.json new file mode 100644 index 0000000..6a1273c --- /dev/null +++ b/source/sources/original-gerameg/MANIFEST.json @@ -0,0 +1,59 @@ +{ + "origin": "Original text-console source capture; community derivative", + "files": [ + { + "source": "gerameg_cua_runtime.py", + "path": "gerameg_cua_runtime.py", + "bytes": 5049, + "sha256": "381a7dc740227e27085e66d5f7e5d398a09f86eff66d6aefce9025e896634887" + }, + { + "source": "real_tty_manager.py", + "path": "real_tty_manager.py", + "bytes": 9647, + "sha256": "3c8fe23cf1499172d4064f04f373656f6fd6109a1c1bc01657d669aabd4121d8" + }, + { + "source": "cua_tty_rpc.py", + "path": "cua_tty_rpc.py", + "bytes": 9957, + "sha256": "e64b0b9283c8143b5fc4db2c6020c288927f0a26a9c4fd42cfc8aca8bfc36175" + }, + { + "source": "framebridge_cua_tty/__init__.py", + "path": "framebridge_cua_tty/__init__.py", + "bytes": 98, + "sha256": "a873aecc609c08bd8b703e0917370278a64e6ad24fc96b5da2fd04c9d958855c" + }, + { + "source": "framebridge_cua_tty/client.py", + "path": "framebridge_cua_tty/client.py", + "bytes": 1465, + "sha256": "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7" + }, + { + "source": "framebridge_cua_tty/core.py", + "path": "framebridge_cua_tty/core.py", + "bytes": 7850, + "sha256": "1c259afd5233f8dc4b0df58c2e69e7df346637e20f8c6fc24303bdb7a4e29590" + }, + { + "source": "framebridge_cua_tty/demo.py", + "path": "framebridge_cua_tty/demo.py", + "bytes": 1385, + "sha256": "22614e47bc2189f83fed4ef294bd48a78bea587b3619f8d333eff74570dee39d" + }, + { + "source": "framebridge_cua_tty/screen.py", + "path": "framebridge_cua_tty/screen.py", + "bytes": 5362, + "sha256": "20f47b9d21189f1e9ac679ba7a0a01d0298d7dcce459d88cfb589504be0893d7" + }, + { + "source": "framebridge_cua_tty/server.py", + "path": "framebridge_cua_tty/server.py", + "bytes": 5977, + "sha256": "7c803ca0de25e28914fd15804d0cc785c4362d38c8a29b6f7fb377c5b963674d" + } + ] +} diff --git a/source/sources/original-gerameg/cua_tty_rpc.py b/source/sources/original-gerameg/cua_tty_rpc.py new file mode 100644 index 0000000..917d3db --- /dev/null +++ b/source/sources/original-gerameg/cua_tty_rpc.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""RPC local CUA-TTY de GERAMEG, invocado por el FBEXEC original firmado.""" +from __future__ import annotations +import base64 +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import re +import socket +import stat +import sys +import uuid + +UID = 1000 +HOSTNAME = "GERAMEG" +HOME = Path.home() +SOCKET_PATH = Path("/run/user/1000/framebridge-alpha01-cua/agent.sock") +CLIENT_PATH = Path("/opt/framebridge-alpha-01/vendor/framebridge_cua_tty/client.py") +CLIENT_SHA256 = "6a1fc004f2d50944661fcce67002b04534721bc3118eaf44e3f0694e19d15fe7" +RESULTS_DIR = HOME / ".local/share/framebridge/cua-tty-results" +PROTOCOL = "FB-CUA-TTY-RPC/1" +MAX_BYTES = 65536 +INLINE_BYTES = 3000 +CHUNK_BYTES = 2100 +SID = re.compile(r"[A-Za-z0-9_-]{12,80}\Z") +REPLY_ID = re.compile(r"[0-9a-f]{32}\Z") +KEYS = frozenset(['UP', 'DOWN', 'LEFT', 'RIGHT', 'ENTER', 'RETURN', 'TAB', 'SHIFT_TAB', 'ESC', 'BACKSPACE', 'DELETE', 'INSERT', 'HOME', 'END', 'PAGE_UP', 'PAGE_DOWN', 'SPACE', 'CTRL_A', 'CTRL_B', 'CTRL_C', 'CTRL_D', 'CTRL_E', 'CTRL_F', 'CTRL_G', 'CTRL_H', 'CTRL_I', 'CTRL_J', 'CTRL_K', 'CTRL_L', 'CTRL_M', 'CTRL_N', 'CTRL_O', 'CTRL_P', 'CTRL_Q', 'CTRL_R', 'CTRL_S', 'CTRL_T', 'CTRL_U', 'CTRL_V', 'CTRL_W', 'CTRL_X', 'CTRL_Y', 'CTRL_Z', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']) +SENSITIVE = re.compile(r"(?i)(api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|passwd|secret|authorization|cookie|credential)") +KEY_BEGIN = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") +KEY_END = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + + +class RpcError(RuntimeError): + pass + + +def require(ok, code): + if not ok: + raise RpcError(code) + + +def encode(value): + return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("ascii") + + +def sanitize(value): + # Sanear ANTES de persistir o codificar en base64, igual que el stdout original. + private_key = [False] + + def clean(item): + if isinstance(item, dict): + return {key: clean(val) for key, val in item.items()} + if isinstance(item, list): + return [clean(val) for val in item] + if not isinstance(item, str): + return item + lines = [] + for line in item.splitlines(): + if KEY_BEGIN.search(line): + private_key[0] = True + lines.append("[REDACTED PRIVATE KEY BLOCK]") + elif private_key[0]: + if KEY_END.search(line): + private_key[0] = False + elif SENSITIVE.search(line) and ("=" in line or ":" in line): + lines.append("[REDACTED SENSITIVE LINE]") + else: + lines.append(line) + return "\n".join(lines) + + return clean(value) + + +def validate_request(req): + require(type(req) is dict and type(req.get("op")) is str, "REQUEST_INVALID") + op = req["op"] + shapes = { + "status": {"op"}, "open_demo": {"op"}, "open_shell": {"op"}, + "observe": {"op", "session_id"}, "abort": {"op", "session_id"}, + "resize": {"op", "session_id", "rows", "cols"}, + "key_send": {"op", "session_id", "key"}, + "text_type": {"op", "session_id", "text"}, + } + require(op in shapes and set(req) == shapes[op], "REQUEST_INVALID") + if "session_id" in req: + require(type(req["session_id"]) is str and SID.fullmatch(req["session_id"]) is not None, "SESSION_INVALID") + if op == "resize": + require(type(req["rows"]) is int and type(req["cols"]) is int and 5 <= req["rows"] <= 100 and 20 <= req["cols"] <= 220, "SIZE_INVALID") + if op == "key_send": + require(type(req["key"]) is str and req["key"] in KEYS, "KEY_NOT_ALLOWLISTED") + if op == "text_type": + text = req["text"] + require(type(text) is str and 0 < len(text.encode("utf-8")) <= 16384 and "\x00" not in text, "TEXT_NOT_ALLOWED") + try: + text.encode("utf-8") + except UnicodeEncodeError: + raise RpcError("TEXT_NOT_ALLOWED") from None + args = {key: val for key, val in req.items() if key != "op"} + if op == "open_shell": + return "open", {"program": "bash"} + if op == "open_demo": + return "open", {"program": "demo-readonly"} + if op in {"key_send", "text_type"}: + # Modo local legado autorizado por el propietario; no es una firma Owner. + args["owner_assertion"] = {} + op = {"key_send": "key.send", "text_type": "text.type"}[op] + return op, args + + +def guard_runtime(): + require(os.getuid() == UID and os.geteuid() == UID, "UID_MISMATCH") + require(socket.gethostname().upper() == HOSTNAME, "HOSTNAME_MISMATCH") + require(Path.home().resolve() == HOME.resolve(), "HOME_MISMATCH") + + +def load_client(): + require(CLIENT_PATH.is_file() and not CLIENT_PATH.is_symlink(), "CLIENT_PATH_INVALID") + source = CLIENT_PATH.read_bytes() + require(hashlib.sha256(source).hexdigest() == CLIENT_SHA256, "CLIENT_SHA256_MISMATCH") + spec = importlib.util.spec_from_file_location("_gerameg_cua_client_pinned", CLIENT_PATH) + require(spec is not None and spec.loader is not None, "CLIENT_IMPORT_INVALID") + module = importlib.util.module_from_spec(spec) + exec(compile(source, str(CLIENT_PATH), "exec"), module.__dict__) + return module.rpc + + +def backend_call(req): + op, args = validate_request(req) + try: + parent, target = SOCKET_PATH.parent.lstat(), SOCKET_PATH.lstat() + except OSError: + raise RpcError("CUA_TTY_OFFLINE") from None + require(stat.S_ISDIR(parent.st_mode) and parent.st_uid == UID and parent.st_mode & 0o077 == 0, "SOCKET_PARENT_UNTRUSTED") + require(stat.S_ISSOCK(target.st_mode) and target.st_uid == UID and target.st_mode & 0o077 == 0, "SOCKET_UNTRUSTED") + reply = load_client()(op, path=SOCKET_PATH, **args) + require(type(reply) is dict and type(reply.get("ok")) is bool, "BACKEND_RESPONSE_INVALID") + if reply.get("ok") is True: + require(type(reply.get("result")) is dict, "BACKEND_RESPONSE_INVALID") + return sanitize(reply) + + +def result_directory(create=False): + if create: + RESULTS_DIR.mkdir(mode=0o700, exist_ok=True) + require(RESULTS_DIR.resolve() == RESULTS_DIR.absolute(), "RESULT_DIRECTORY_UNSAFE") + fd = os.open(RESULTS_DIR, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) + st = os.fstat(fd) + if st.st_uid != UID or st.st_mode & 0o077: + os.close(fd) + raise RpcError("RESULT_DIRECTORY_UNSAFE") + return fd + + +def pack_reply(reply): + reply = sanitize(reply) + data = encode(reply) + require(0 < len(data) <= MAX_BYTES, "RESPONSE_TOO_LARGE") + meta = {"protocol": PROTOCOL, "ok": True, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()} + # El sobre ASCII evita que campos del backend como "authorization_mode" + # hagan que el saneador de stdout sustituya toda una línea JSON válida. + inline = {**meta, "kind": "inline", "data_b64": base64.b64encode(data).decode("ascii")} + if len(encode(inline)) <= INLINE_BYTES: + return inline + reply_id = uuid.uuid4().hex + directory = result_directory(create=True) + try: + fd = os.open(reply_id + ".json", os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=directory) + with os.fdopen(fd, "wb") as stream: + stream.write(data) + stream.flush() + os.fsync(stream.fileno()) + os.fsync(directory) + finally: + os.close(directory) + return {**meta, "kind": "file", "reply_id": reply_id} + + +def read_chunk(reply_id, offset): + require(type(reply_id) is str and REPLY_ID.fullmatch(reply_id) is not None, "REPLY_ID_INVALID") + require(type(offset) is int and 0 <= offset < MAX_BYTES and offset % CHUNK_BYTES == 0, "OFFSET_INVALID") + directory = result_directory() + try: + fd = os.open(reply_id + ".json", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=directory) + with os.fdopen(fd, "rb") as stream: + st = os.fstat(stream.fileno()) + require(stat.S_ISREG(st.st_mode) and st.st_nlink == 1 and st.st_uid == UID and st.st_mode & 0o077 == 0, "RESULT_FILE_UNSAFE") + require(0 < st.st_size <= MAX_BYTES, "RESULT_SIZE_INVALID") + data = stream.read(MAX_BYTES + 1) + require(len(data) == st.st_size, "RESULT_SIZE_CHANGED") + finally: + os.close(directory) + require(offset < len(data), "OFFSET_INVALID") + chunk = data[offset:offset + CHUNK_BYTES] + return {"protocol": PROTOCOL, "ok": True, "kind": "chunk", "reply_id": reply_id, + "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest(), + "offset": offset, "length": len(chunk), "data_b64": base64.b64encode(chunk).decode("ascii")} + + +def dispatch(argv): + require(type(argv) is list, "CLI_INVALID") + if len(argv) == 2 and argv[0] == "invoke": + require(len(argv[1]) <= 50000, "REQUEST_TOO_LARGE") + try: + raw = base64.b64decode(argv[1], validate=True) + require(len(raw) <= 40000, "REQUEST_TOO_LARGE") + request = json.loads(raw) + except (ValueError, UnicodeError): + raise RpcError("REQUEST_ENCODING_INVALID") from None + return pack_reply(backend_call(request)) + if len(argv) == 3 and argv[0] == "read": + require(re.fullmatch(r"0|[1-9][0-9]{0,4}", argv[2]) is not None, "OFFSET_INVALID") + return read_chunk(argv[1], int(argv[2])) + raise RpcError("CLI_INVALID") + + +def main(): + try: + guard_runtime() + result = dispatch(sys.argv[1:]) + except RpcError as exc: + result = {"protocol": PROTOCOL, "ok": False, "error": str(exc)} + except Exception as exc: + result = {"protocol": PROTOCOL, "ok": False, "error": "RPC_FAILED", "error_type": type(exc).__name__} + output = encode(result) + require(len(output) <= 3500, "ENVELOPE_TOO_LARGE") + sys.stdout.write(output.decode("ascii") + "\n") + + +if __name__ == "__main__": + main() diff --git a/source/sources/original-gerameg/framebridge_cua_tty/__init__.py b/source/sources/original-gerameg/framebridge_cua_tty/__init__.py new file mode 100644 index 0000000..e4127d0 --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/__init__.py @@ -0,0 +1,2 @@ +"""FrameBridge CUA-TTY prototype. No network or shell execution.""" +__version__ = "0.1.0-staging" diff --git a/source/sources/original-gerameg/framebridge_cua_tty/client.py b/source/sources/original-gerameg/framebridge_cua_tty/client.py new file mode 100644 index 0000000..2a90ec8 --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/client.py @@ -0,0 +1,39 @@ +"""Local staging inspection client; input operations cannot bypass OwnerDevice.""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import socket + + +def rpc(op, *, path, **kwargs): + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + sock.settimeout(4) + sock.connect(str(path)) + sock.sendall((json.dumps({'op':op, **kwargs})+'\n').encode()) + reply = bytearray() + while len(reply) < 65536: + chunk = sock.recv(min(8192, 65536-len(reply))) + if not chunk: break + reply.extend(chunk) + if b'\n' in chunk: break + if len(reply) >= 65536 or b'\n' not in reply: raise RuntimeError('RESPONSE_TOO_LARGE') + return json.loads(reply.split(b'\n',1)[0]) + + +def main(argv=None): + p=argparse.ArgumentParser() + p.add_argument('op', choices=('status','open','observe','resize','abort','key.send','text.type')) + p.add_argument('--session-id',default='') + p.add_argument('--socket',default=str(Path.home()/'.local/share/framebridge-cua-tty-r1/agent.sock')) + p.add_argument('--key',default='') + p.add_argument('--text',default='') + args=p.parse_args(argv) + req={} + if args.session_id: req['session_id']=args.session_id + if args.key: req['key']=args.key + if args.text: req['text']=args.text + print(json.dumps(rpc(args.op,path=args.socket,**req),ensure_ascii=False,indent=2)) + + +if __name__=='__main__':main() diff --git a/source/sources/original-gerameg/framebridge_cua_tty/core.py b/source/sources/original-gerameg/framebridge_cua_tty/core.py new file mode 100644 index 0000000..fb71fa4 --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/core.py @@ -0,0 +1,204 @@ +"""Unprivileged local PTY session engine with mandatory owner gate for inputs. + +Staging only: only fixed, no-side-effects demo program may be opened. Never +use an LLM-provided shell/argv or `authorized=True` as authority. +""" +from __future__ import annotations +from dataclasses import dataclass +import fcntl +import hashlib +import os +from pathlib import Path +import pty +import secrets +import select +import signal +import struct +import subprocess +import sys +import termios +import threading +import time +from typing import Callable +from .screen import Screen + + +class TerminalDenied(PermissionError): + pass + + +KEYS = {'UP': b'\x1b[A', 'DOWN': b'\x1b[B', 'LEFT': b'\x1b[D', + 'RIGHT': b'\x1b[C', 'ENTER': b'\r', 'TAB': b'\t', 'ESC': b'\x1b', + 'BACKSPACE': b'\x7f', 'CTRL_C': b'\x03'} + + +@dataclass +class Session: + session_id: str + proc: subprocess.Popen + fd: int + screen: Screen + born: float + last_access: float + program: str + sequence: int = 0 + + +class Manager: + """A trusted OwnerDevice verifier must implement callable(context)->bool. + + IMPORTANT: no grants can be passed in via RPC, environment, or `authorized`. + An absent verifier rejects ALL input. A production verifier must validate + signed challenge, owner identity, exact payload hash, nonce, TTL and one-use. + """ + def __init__(self, owner_verifier: Callable | None = None, max_sessions: int = 2, + idle_timeout_s: int = 600): + if not 1 <= max_sessions <= 4: raise ValueError('MAX_SESSIONS_INVALID') + self._gate = owner_verifier + self._sessions = {} + self._lock = threading.RLock() + self.max_sessions, self.idle_timeout_s = max_sessions, idle_timeout_s + + def _get(self, sid): + s = self._sessions.get(sid) + if not s: raise KeyError('SESSION_NOT_FOUND') + if time.monotonic() - s.last_access > self.idle_timeout_s: + self._terminate(s) + del self._sessions[sid] + raise TerminalDenied('SESSION_EXPIRED') + s.last_access = time.monotonic() + return s + + @staticmethod + def _terminate(s): + if s.proc.poll() is None: + try: os.killpg(s.proc.pid, signal.SIGTERM) + except ProcessLookupError: pass + try: s.proc.wait(timeout=0.7) + except subprocess.TimeoutExpired: + try: os.killpg(s.proc.pid, signal.SIGKILL) + except ProcessLookupError: pass + try: s.proc.wait(timeout=1) + except subprocess.TimeoutExpired: pass + try: os.close(s.fd) + except OSError: pass + + @staticmethod + def _child_ctty(): + # CUA does not use setuid. Child has same UID as daemon. + fcntl.ioctl(0, termios.TIOCSCTTY, 0) + + def open(self, program='demo-readonly', rows=24, cols=80): + if program != 'demo-readonly': + raise TerminalDenied('PROGRAM_NOT_ALLOWLISTED') + if os.geteuid() == 0: + raise TerminalDenied('ROOT_DAEMON_FORBIDDEN') + with self._lock: + self.reap() + if len(self._sessions) >= self.max_sessions: + raise TerminalDenied('SESSION_LIMIT') + screen = Screen(rows, cols) + master, slave = pty.openpty() + fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', rows, cols, 0, 0)) + env = {'PATH': '/usr/bin:/bin', 'TERM': 'xterm', 'LANG': 'C.UTF-8', + 'HOME': str(Path.home())} + try: + proc = subprocess.Popen([sys.executable, '-B', str(Path(__file__).with_name('demo.py'))], + stdin=slave, stdout=slave, stderr=slave, env=env, close_fds=True, + start_new_session=True, preexec_fn=self._child_ctty) + except BaseException: + os.close(master) + raise + finally: + os.close(slave) + os.set_blocking(master, False) + sid = secrets.token_hex(16) + now = time.monotonic() + self._sessions[sid] = Session(sid, proc, master, screen, now, now, program) + return {'session_id': sid, 'program': program, 'state': 'OPEN'} + + def _drain(self, s): + total = 0 + while total < 65536: + ready, _, _ = select.select([s.fd], [], [], 0) + if not ready: break + try: data = os.read(s.fd, min(4096, 65536-total)) + except (BlockingIOError, OSError): break + if not data: break + total += len(data) + s.screen.feed(data) + return total + + def observe(self, sid): + with self._lock: + s = self._get(sid) + n = self._drain(s) + s.sequence += 1 + return {'session_id': sid, 'program': s.program, 'running': s.proc.poll() is None, + 'sequence': s.sequence, 'drained_bytes': n, 'screen': s.screen.snapshot()} + + def status(self): + with self._lock: + self.reap() + return {'active_sessions': len(self._sessions), 'max_sessions': self.max_sessions, + 'owner_device_connected': self._gate is not None, + 'input_enabled': False if self._gate is None else 'per-call-only'} + + def _authorize(self, s, operation, payload: bytes, approved_context=None): + if self._gate is None: + raise TerminalDenied('OWNER_DEVICE_NOT_CONNECTED') + if approved_context is None: + raise TerminalDenied('OWNER_APPROVAL_REQUIRED') + context = {'session_id': s.session_id, 'program': s.program, 'operation': operation, + 'payload_sha256': hashlib.sha256(payload).hexdigest(), + 'owner_assertion': approved_context} + if self._gate(context) is not True: + raise TerminalDenied('OWNER_APPROVAL_INVALID_OR_REPLAYED') + + def key(self, sid, key, approved_context=None): + if key not in KEYS: + raise TerminalDenied('KEY_NOT_ALLOWLISTED') + with self._lock: + s = self._get(sid) + data = KEYS[key] + self._authorize(s, 'key.send', data, approved_context) + if s.proc.poll() is not None: raise TerminalDenied('SESSION_CLOSED') + os.write(s.fd, data) + return {'session_id': sid, 'submitted': key, 'state': 'SENT'} + + def text(self, sid, text, approved_context=None): + if not isinstance(text, str) or not 1 <= len(text) <= 64 or any(ord(c)<32 or ord(c)==127 for c in text): + raise TerminalDenied('TEXT_NOT_ALLOWED') + with self._lock: + s = self._get(sid) + data = text.encode('utf-8') + self._authorize(s, 'text.type', data, approved_context) + if s.proc.poll() is not None: raise TerminalDenied('SESSION_CLOSED') + os.write(s.fd, data) + return {'session_id': sid, 'submitted_chars': len(text), 'state': 'SENT'} + + def resize(self, sid, rows, cols): + with self._lock: + s = self._get(sid) + s.screen.resize(rows, cols) + fcntl.ioctl(s.fd, termios.TIOCSWINSZ, struct.pack('HHHH', rows, cols, 0, 0)) + return {'session_id': sid, 'rows': rows, 'cols': cols} + + def abort(self, sid): + # Emergency stop always works, but only against its own demo session. + with self._lock: + s = self._get(sid) + self._terminate(s) + del self._sessions[sid] + return {'session_id': sid, 'state': 'ABORTED'} + + def reap(self): + for sid, s in list(self._sessions.items()): + if time.monotonic() - s.last_access > self.idle_timeout_s: + self._terminate(s) + self._sessions.pop(sid, None) + + def shutdown(self): + with self._lock: + for s in self._sessions.values(): self._terminate(s) + self._sessions.clear() diff --git a/source/sources/original-gerameg/framebridge_cua_tty/demo.py b/source/sources/original-gerameg/framebridge_cua_tty/demo.py new file mode 100644 index 0000000..5aa1087 --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/demo.py @@ -0,0 +1,43 @@ +"""No-side-effects simulated interactive menu. Not a shell.""" +import os +import sys +import tty + +OPTIONS = ('Estado del sistema (demo)', 'Almacenamiento (demo)', 'Red (demo)', 'Salir') + + +def show(i): + sys.stdout.write('\x1b[2J\x1b[H') + sys.stdout.write('FrameBridge CUA-TTY - DEMOSTRACION AISLADA\r\n') + sys.stdout.write('NO ejecuta comandos; NO modifica el servidor.\r\n\r\n') + for n, label in enumerate(OPTIONS): + sys.stdout.write(('> ' if n == i else ' ') + label + '\r\n') + sys.stdout.write('\r\nUse flechas arriba/abajo, Enter y q.\r\n') + sys.stdout.flush() + + +def main(): + tty.setcbreak(sys.stdin.fileno()) + i = 0 + show(i) + while True: + b = os.read(0, 1) + if not b: break + if b in (b'q', b'Q', b'\x03'): break + if b == b'\x1b': + seq = os.read(0, 2) + if seq == b'[A': i = (i - 1) % len(OPTIONS) + elif seq == b'[B': i = (i + 1) % len(OPTIONS) + elif b in (b'j', b'J'): i = (i + 1) % len(OPTIONS) + elif b in (b'k', b'K'): i = (i - 1) % len(OPTIONS) + elif b in (b'\r', b'\n'): + if i == len(OPTIONS) - 1: break + sys.stdout.write('\r\nSeleccion simulada: ' + OPTIONS[i] + '\r\n') + sys.stdout.flush() + show(i) + sys.stdout.write('\r\nDEMOSTRACION_FINALIZADA\r\n') + sys.stdout.flush() + + +if __name__ == '__main__': + main() diff --git a/source/sources/original-gerameg/framebridge_cua_tty/screen.py b/source/sources/original-gerameg/framebridge_cua_tty/screen.py new file mode 100644 index 0000000..25d9117 --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/screen.py @@ -0,0 +1,121 @@ +"""Small bounded VT-like ASCII/UTF-8 text screen for staging. + +This is deliberately NOT advertised as a full xterm emulator. It handles common +CSI cursor/erase controls and suppresses OSC/DCS/other escape payloads. +""" +from __future__ import annotations +import codecs +import unicodedata + +class Screen: + def __init__(self, rows: int = 24, cols: int = 80): + if not 8 <= rows <= 80 or not 20 <= cols <= 200: + raise ValueError('SCREEN_DIMENSIONS_INVALID') + self.rows, self.cols = rows, cols + self.buf = [[' '] * cols for _ in range(rows)] + self.y = self.x = 0 + self.mode = 'normal' + self.control = '' + self.decoder = codecs.getincrementaldecoder('utf-8')('replace') + self.max_control = 96 + + def reset(self): + self.buf = [[' '] * self.cols for _ in range(self.rows)] + self.y = self.x = 0 + + def resize(self, rows: int, cols: int): + if not 8 <= rows <= 80 or not 20 <= cols <= 200: + raise ValueError('SCREEN_DIMENSIONS_INVALID') + new = [[' '] * cols for _ in range(rows)] + for y in range(min(rows, self.rows)): + for x in range(min(cols, self.cols)): + new[y][x] = self.buf[y][x] + self.rows, self.cols, self.buf = rows, cols, new + self.y = min(self.y, rows - 1) + self.x = min(self.x, cols - 1) + + def scroll(self): + self.buf.pop(0) + self.buf.append([' '] * self.cols) + self.y = self.rows - 1 + + def newline(self): + self.y += 1 + if self.y >= self.rows: + self.scroll() + + def put(self, c: str): + if unicodedata.combining(c): + return + # Avoid unmodeled full-width terminal-cell ambiguity in this R1 pilot. + if unicodedata.east_asian_width(c) in ('W', 'F'): + c = '?' + if self.x >= self.cols: + self.x = 0 + self.newline() + self.buf[self.y][self.x] = c + self.x += 1 + + def csi(self, final: str, args: str): + if len(args) > self.max_control: + return + if args.startswith('?'): + if final in ('h', 'l') and args in ('?1049', '?1047', '?47') and final == 'h': + self.reset() + return + nums = [int(a) if a.isdecimal() else 0 for a in args.split(';')[:8]] + n = nums[0] if nums else 0 + count = max(1, min(n, 200)) + if final == 'A': self.y = max(0, self.y - count) + elif final == 'B': self.y = min(self.rows - 1, self.y + count) + elif final == 'C': self.x = min(self.cols - 1, self.x + count) + elif final == 'D': self.x = max(0, self.x - count) + elif final in ('H', 'f'): + self.y = max(0, min(self.rows - 1, (nums[0] if nums and nums[0] else 1) - 1)) + self.x = max(0, min(self.cols - 1, (nums[1] if len(nums) > 1 and nums[1] else 1) - 1)) + elif final == 'G': self.x = max(0, min(self.cols - 1, count - 1)) + elif final == 'd': self.y = max(0, min(self.rows - 1, count - 1)) + elif final == 'J': + if n == 2: self.reset() + elif n == 0: + for y in range(self.y, self.rows): + start = self.x if y == self.y else 0 + for x in range(start, self.cols): self.buf[y][x] = ' ' + elif final == 'K': + start, end = (0, self.cols) if n == 2 else ((0, self.x + 1) if n == 1 else (self.x, self.cols)) + for x in range(start, min(end, self.cols)): self.buf[self.y][x] = ' ' + # SGR and unrecognized commands are intentionally ignored. + + def feed(self, data: bytes): + for c in self.decoder.decode(data): + if self.mode == 'normal': + if c == '\x1b': self.mode = 'esc' + elif c == '\r': self.x = 0 + elif c == '\n': self.newline() + elif c == '\b': self.x = max(0, self.x - 1) + elif c == '\t': self.x = min(self.cols - 1, (self.x // 8 + 1) * 8) + elif c.isprintable() and c not in '\x7f': self.put(c) + elif self.mode == 'esc': + if c == '[': self.mode, self.control = 'csi', '' + elif c == ']': self.mode, self.control = 'osc', '' + elif c in ('P', '^', '_'): self.mode, self.control = 'str', '' + elif c == 'c': self.reset(); self.mode = 'normal' + else: self.mode = 'normal' + elif self.mode == 'csi': + if '@' <= c <= '~': + self.csi(c, self.control); self.mode = 'normal'; self.control = '' + elif len(self.control) >= self.max_control: self.mode, self.control = 'discard_csi', '' + else: self.control += c + elif self.mode == 'discard_csi': + if '@' <= c <= '~': self.mode = 'normal' + elif self.mode in ('osc', 'str'): + if c == '\x07' and self.mode == 'osc': self.mode = 'normal' + elif c == '\x1b': self.mode = 'string_esc' + elif self.mode == 'string_esc': + if c == '\\': self.mode = 'normal' + elif c == '\x1b': self.mode = 'string_esc' + else: self.mode = 'str' + + def snapshot(self): + return {'rows': self.rows, 'cols': self.cols, 'cursor': {'row': self.y, 'col': self.x}, + 'lines': [''.join(row).rstrip() for row in self.buf]} diff --git a/source/sources/original-gerameg/framebridge_cua_tty/server.py b/source/sources/original-gerameg/framebridge_cua_tty/server.py new file mode 100644 index 0000000..015675f --- /dev/null +++ b/source/sources/original-gerameg/framebridge_cua_tty/server.py @@ -0,0 +1,129 @@ +"""Local-only JSON-lines Unix socket prototype. No TCP listener, no shell.""" +from __future__ import annotations +import argparse +import json +import os +from pathlib import Path +import socket +import socketserver +import stat +import struct +from .core import Manager, TerminalDenied + +MAX_REQUEST = 65536 + + +class Handler(socketserver.StreamRequestHandler): + def handle(self): + # On Linux: SO_PEERCRED is the connecting process's kernel UID. + try: + _, uid, _ = struct.unpack('3i', self.request.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, 12)) + if uid != self.server.owner_uid: + return + raw = self.rfile.readline(MAX_REQUEST + 1) + if len(raw) > MAX_REQUEST or not raw.endswith(b'\n'): + self.send({'ok': False, 'error': 'REQUEST_TOO_LARGE_OR_TRUNCATED'}) + return + req = json.loads(raw) + if type(req) is not dict or type(req.get('op')) is not str: + self.send({'ok': False, 'error': 'INVALID_REQUEST'}) + return + # Only specific RPC shapes. The caller cannot supply binary argv, + # host path, a shell, an approval bool, or an OwnerDevice verifier. + op = req['op'] + # Enforce exact wire shape for every operation. Untrusted clients + # may not attach arbitrary authority or additional arguments. + permitted = { + 'status': ({'op'},), + 'open': ({'op'}, {'op','program'}), + 'observe': ({'op','session_id'},), + 'resize': ({'op','session_id','rows','cols'},), + 'abort': ({'op','session_id'},), + 'key.send': ({'op','session_id','key','owner_assertion'},), + 'text.type': ({'op','session_id','text','owner_assertion'},), + } + accepted = permitted.get(op) + if accepted is None: + raise TerminalDenied('UNKNOWN_OPERATION') + if set(req) not in accepted: + raise TerminalDenied('REQUEST_INVALID') + m = self.server.manager + if op == 'status': data = m.status() + elif op == 'open': data = m.open(req.get('program', 'demo-readonly')) + elif op == 'observe': data = m.observe(req['session_id']) + elif op == 'resize': data = m.resize(req['session_id'], req['rows'], req['cols']) + elif op == 'abort': data = m.abort(req['session_id']) + elif op in ('key.send', 'text.type'): + # Server default remains Manager(owner_verifier=None) -> DENY. + # A separately deployed trusted bootstrap must bind a real + # WebAuthn/Owner verifier. Neither AUTHORIZED nor an RPC bool + # can alter the verifier. Never accept arbitrary shell/argv. + if m._gate is None: + raise TerminalDenied('OWNER_DEVICE_NOT_CONNECTED') + if type(req.get('owner_assertion')) is not dict: + raise TerminalDenied('OWNER_APPROVAL_REQUIRED') + required = ({'op', 'session_id', 'key', 'owner_assertion'} + if op == 'key.send' else + {'op', 'session_id', 'text', 'owner_assertion'}) + if set(req) != required: + raise TerminalDenied('REQUEST_INVALID') + if op == 'key.send': + data = m.key(req['session_id'], req['key'], + approved_context=req['owner_assertion']) + else: + data = m.text(req['session_id'], req['text'], + approved_context=req['owner_assertion']) + else: raise TerminalDenied('UNKNOWN_OPERATION') + self.send({'ok': True, 'result': data}) + except (ValueError, KeyError, TypeError, json.JSONDecodeError): + self.send({'ok': False, 'error': 'REQUEST_INVALID'}) + except TerminalDenied as exc: + self.send({'ok': False, 'error': str(exc)}) + except Exception: + # Never echo arbitrary error content, terminal output or secrets. + self.send({'ok': False, 'error': 'INTERNAL_ERROR'}) + + def send(self, data): + self.wfile.write((json.dumps(data, ensure_ascii=True, separators=(',', ':')) + '\n').encode()) + + +class LocalServer(socketserver.ThreadingMixIn, socketserver.UnixStreamServer): + daemon_threads = True + allow_reuse_address = False + def __init__(self, path, manager=None): + if os.geteuid() == 0: + raise TerminalDenied('ROOT_SERVER_FORBIDDEN') + p = Path(path).absolute() + p.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + # Do not operate in a shared or symlinked parent directory. + if p.parent.is_symlink() or stat.S_IMODE(p.parent.stat().st_mode) != 0o700: + raise TerminalDenied('SOCKET_PARENT_NOT_PRIVATE') + if p.exists() or p.is_symlink(): + raise TerminalDenied('SOCKET_ALREADY_EXISTS') + self.owner_uid = os.geteuid() + self.manager = manager or Manager() + self._socket_path = p + old = os.umask(0o077) + try: super().__init__(str(p), Handler) + finally: os.umask(old) + os.chmod(p, 0o600) + + def server_close(self): + self.manager.shutdown() + super().server_close() + try: self._socket_path.unlink(missing_ok=True) + except OSError: pass + + +def main(argv=None): + parser = argparse.ArgumentParser(description='FrameBridge CUA-TTY staging demo, local socket only') + parser.add_argument('--socket', default=str(Path.home()/'.local/share/framebridge-cua-tty-r1/agent.sock')) + args = parser.parse_args(argv) + with LocalServer(args.socket) as server: + print('CUA_TTY_R1_LOCAL_ONLY_SOCKET_READY', flush=True) + try: server.serve_forever(poll_interval=0.2) + except KeyboardInterrupt: pass + + +if __name__ == '__main__': + main() diff --git a/source/sources/original-gerameg/gerameg_cua_runtime.py b/source/sources/original-gerameg/gerameg_cua_runtime.py new file mode 100644 index 0000000..df0dd6f --- /dev/null +++ b/source/sources/original-gerameg/gerameg_cua_runtime.py @@ -0,0 +1,132 @@ +#!/usr/bin/python3 +"""GERAMEG CUA-TTY with the explicitly selected local-user input policy. + +Reuse the installed LocalServer and Manager without changing their source, +socket protocol, existing demos and PTY handling, or input/session limits for demos. + +Remote callers must still enter through authenticated FrameBridge. This +process authenticates LOCAL peers by the original LocalServer's SO_PEERCRED +check. It cannot distinguish an authenticated FBEXEC process from another +process running as gerabh (UID 1000). Both have the same local authority. + +The legacy owner_assertion RPC field remains mandatory for wire compatibility +but must be an empty object. It is not a grant, signature, or approval flag. +No Owner R12 signing code, private keys, or FBAUTH state is loaded. +""" +from __future__ import annotations + +import argparse +import os +from pathlib import Path +import re +import signal +import stat + + +EXPECTED_UID = 1000 +EXPECTED_HOST = "GERAMEG" +DEFAULT_SOCKET = "/run/user/1000/framebridge-alpha01-cua/agent.sock" +_SESSION_ID = re.compile(r"[a-f0-9]{32}\Z") +_PAYLOAD_HASH = re.compile(r"[a-f0-9]{64}\Z") +_CONTEXT_FIELDS = frozenset( + {"session_id", "program", "operation", "payload_sha256", "owner_assertion"} +) + + +def require_local_identity() -> None: + if ( + os.getuid() != EXPECTED_UID + or os.geteuid() != EXPECTED_UID + or os.uname().nodename.upper() != EXPECTED_HOST + ): + raise RuntimeError("CUA_LOCAL_UID_OR_HOST_INVALID") + + +class LocalUidVerifier: + """Apply the selected UID policy after LocalServer checks the peer UID. + + Manager constructs this context after resolving its own live session and + validating the input. No caller-provided boolean or assertion grants access. + """ + + def __call__(self, context: object) -> bool: + try: + require_local_identity() + if type(context) is not dict or set(context) != _CONTEXT_FIELDS: + return False + if context.get("program") not in ("demo-readonly", "bash"): + return False + if context.get("operation") not in ("key.send", "text.type"): + return False + session_id = context.get("session_id") + payload_hash = context.get("payload_sha256") + if type(session_id) is not str or _SESSION_ID.fullmatch(session_id) is None: + return False + if type(payload_hash) is not str or _PAYLOAD_HASH.fullmatch(payload_hash) is None: + return False + assertion = context.get("owner_assertion") + return type(assertion) is dict and not assertion + except (AttributeError, OSError, RuntimeError, TypeError, ValueError): + return False + + +def _stop(_signum: int, _frame: object) -> None: + # Exiting the LocalServer context closes only this runtime's own sessions + # and socket. Do not leave a stale socket after a normal systemd stop. + raise SystemExit(0) + + +def main(argv: list[str] | None = None) -> None: + parser = argparse.ArgumentParser(description="GERAMEG CUA-TTY local UID policy") + parser.add_argument("--socket", default=DEFAULT_SOCKET) + args = parser.parse_args(argv) + require_local_identity() + if args.socket != DEFAULT_SOCKET: + raise RuntimeError("CUA_LOCAL_SOCKET_INVALID") + + parent = Path(args.socket).parent + try: + parent_state = parent.lstat() + except FileNotFoundError: + # LocalServer creates its own private directory with mode 0700. + pass + else: + if ( + not stat.S_ISDIR(parent_state.st_mode) + or parent_state.st_uid != EXPECTED_UID + or stat.S_IMODE(parent_state.st_mode) != 0o700 + ): + raise RuntimeError("CUA_LOCAL_SOCKET_PARENT_UNTRUSTED") + + # Keep imports on the existing service's installed PYTHONPATH. This file + # does not copy, patch, or replace the original CUA-TTY package. + from framebridge_cua_tty.core import Manager + from framebridge_cua_tty.server import LocalServer + + from real_tty_manager import RealTerminalMixin + + class LocalPolicyManager(RealTerminalMixin, Manager): + def status(self): + value = super().status() + # A callable verifier is not evidence of an Owner device. Report + # the selected authority honestly while retaining session status. + value["owner_device_connected"] = False + value["input_enabled"] = "same-uid-1000" + value["input_policy"] = "local-uid-1000" + value["owner_signature_required"] = False + return value + + manager = LocalPolicyManager( + owner_verifier=LocalUidVerifier(), max_sessions=2, idle_timeout_s=600 + ) + signal.signal(signal.SIGTERM, _stop) + with LocalServer(args.socket, manager=manager) as server: + print("CUA_TTY_READY POLICY=LOCAL_UID_1000 OWNER_SIGNATURE_REQUIRED=NO", flush=True) + try: + server.serve_forever(poll_interval=0.2) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/source/sources/original-gerameg/real_tty_manager.py b/source/sources/original-gerameg/real_tty_manager.py new file mode 100644 index 0000000..393e6ab --- /dev/null +++ b/source/sources/original-gerameg/real_tty_manager.py @@ -0,0 +1,190 @@ +"""Real terminals for the existing FrameBridge CUA-TTY Manager. +Transport identity and same-UID socket policy are unchanged. No Owner prompts. +Only this module's tmux sessions are operated on; no existing tty is hijacked. +""" +from __future__ import annotations +from dataclasses import dataclass +import hashlib +import os +from pathlib import Path +import re +import subprocess +import time +import uuid +from framebridge_cua_tty.core import TerminalDenied + +TMUX_SOCKET = '/run/user/1000/framebridge-alpha01-cua/real-tmux.sock' +KEY_NAMES = {'UP':'Up','DOWN':'Down','LEFT':'Left','RIGHT':'Right', + 'ENTER':'Enter','RETURN':'Enter','TAB':'Tab','SHIFT_TAB':'BTab','ESC':'Escape', + 'BACKSPACE':'BSpace','DELETE':'DC','INSERT':'IC','HOME':'Home','END':'End', + 'PAGE_UP':'PPage','PAGE_DOWN':'NPage','SPACE':'Space'} +KEY_NAMES.update({'CTRL_'+c:'C-'+c.lower() for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'}) +KEY_NAMES.update({'F'+str(i):'F'+str(i) for i in range(1,13)}) + +@dataclass +class RealSession: + session_id: str + name: str + pane: str + born: float + program: str = 'bash' + sequence: int = 0 + +class RealTerminalMixin: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._real = {} + + def _tmux(self, *args, data=None, optional=False): + env={k:v for k,v in os.environ.items() if k not in ('TMUX','TMUX_PANE')} + env.update({'HOME':str(Path.home()),'USER':'gerabh','LOGNAME':'gerabh', + 'LANG':'C.UTF-8','TERM':'xterm-256color','HISTFILE':'/dev/null'}) + try: + p=subprocess.run(['/usr/bin/tmux','-S',TMUX_SOCKET,'-f','/dev/null',*args], + input=data,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,timeout=3) + except subprocess.TimeoutExpired: + raise TerminalDenied('TMUX_COMMAND_TIMEOUT') from None + except OSError: + raise TerminalDenied('TMUX_NOT_AVAILABLE') from None + if p.returncode and not optional: + raise TerminalDenied('TMUX_COMMAND_FAILED') + return p + + def _info(self, session): + keys=['pane_width','pane_height','cursor_x','cursor_y','pane_dead', + 'pane_dead_status','pane_pid'] + fmt='|'.join('#{'+key+'}' for key in keys) + p=self._tmux('display-message','-p','-t',session.pane,fmt) + parts=p.stdout.decode('utf-8','replace').rstrip('\n').split('|') + if len(parts)!=len(keys):raise TerminalDenied('TMUX_METADATA_INVALID') + info=dict(zip(keys,parts)) + for key in ['pane_current_path','pane_current_command','pane_tty']: + p=self._tmux('display-message','-p','-t',session.pane,'#{'+key+'}') + info[key]=p.stdout.decode('utf-8','replace').rstrip('\n') + return info + + def open(self, program='demo-readonly', rows=24, cols=80): + if program!='bash': + with self._lock: + self.reap() + if len(self._sessions)+len(self._real)>=self.max_sessions: + raise TerminalDenied('SESSION_LIMIT') + return super().open(program,rows,cols) + if not (5<=rows<=100 and 20<=cols<=220):raise TerminalDenied('SIZE_INVALID') + with self._lock: + self.reap() + if len(self._sessions)+len(self._real)>=self.max_sessions: + raise TerminalDenied('SESSION_LIMIT') + sid=uuid.uuid4().hex + name='fb-real-'+sid + pane=None + try: + p=self._tmux('new-session','-d','-s',name,'-x',str(cols),'-y',str(rows), + '-c',str(Path.home()),'-P','-F','#{pane_id}', + '/usr/bin/bash --noprofile --norc -i') + pane=p.stdout.decode().strip() + if not re.fullmatch(r'%[0-9]+',pane):raise TerminalDenied('PANE_ID_INVALID') + self._tmux('set-option','-p','-t',pane,'remain-on-exit','on') + self._tmux('set-option','-t',name,'status','off') + self._tmux('set-option','-t',name,'history-limit','10000') + s=RealSession(sid,name,pane,time.monotonic()) + self._real[sid]=s + info=self._info(s) + return {'session_id':sid,'program':'bash','state':'OPEN','backend':'tmux', + 'real_terminal':True,'tty':info['pane_tty'],'pid':int(info['pane_pid']), + 'cwd':info['pane_current_path'],'persistent_between_chat_calls':True, + 'attach_command':'tmux -S '+TMUX_SOCKET+' attach-session -t '+name} + except BaseException: + self._real.pop(sid,None) + self._tmux('kill-session','-t',name,optional=True) + raise + + def status(self): + with self._lock: + value=super().status() + sessions=[] + for sid,s in list(self._real.items()): + try: + info=self._info(s) + except TerminalDenied: + self._real.pop(sid,None) + continue + sessions.append({'session_id':sid,'program':'bash', + 'running':info['pane_dead']!='1','tty':info['pane_tty'], + 'cwd':info['pane_current_path'],'command':info['pane_current_command']}) + value.update({'active_sessions':value['active_sessions']+len(sessions), + 'real_sessions':sessions,'real_terminal_enabled':True, + 'real_programs':['bash'],'input_policy':'local-uid-1000', + 'owner_device_connected':False,'owner_signature_required':False, + 'input_enabled':'same-uid-1000','real_idle_timeout_s':None}) + return value + + def observe(self, sid): + with self._lock: + if sid not in self._real:return super().observe(sid) + s=self._real[sid];info=self._info(s) + text=self._tmux('capture-pane','-p','-t',s.pane).stdout.decode('utf-8','replace') + lines=text.splitlines() + # The original RPC response has a finite frame size, not an approval gate. + limited=False + import json + while len(json.dumps(lines,ensure_ascii=True).encode())>48000: + lines=lines[:-1];limited=True + return {'session_id':sid,'program':'bash','real_terminal':True,'backend':'tmux', + 'running':info['pane_dead']!='1','exit_code':int(info['pane_dead_status']) if info['pane_dead_status'] else None, + 'cwd':info['pane_current_path'],'current_command':info['pane_current_command'], + 'tty':info['pane_tty'],'pid':int(info['pane_pid']),'sequence':s.sequence, + 'screen':{'rows':int(info['pane_height']),'cols':int(info['pane_width']), + 'cursor':{'row':int(info['cursor_y']),'col':int(info['cursor_x'])},'lines':lines}, + 'screen_truncated':limited,'screen_sha256':hashlib.sha256(text.encode()).hexdigest()} + + def text(self, sid, text, approved_context=None): + with self._lock: + if sid not in self._real:return super().text(sid,text,approved_context=approved_context) + if not isinstance(text,str) or not text or '\x00' in text or len(text.encode())>16384: + raise TerminalDenied('TEXT_INVALID_OR_TOO_LARGE') + s=self._real[sid];payload=text.encode('utf-8') + self._authorize(s,'text.type',payload,approved_context) + if self._info(s)['pane_dead']=='1':raise TerminalDenied('SESSION_EXITED') + name='fb-input-'+sid + self._tmux('load-buffer','-b',name,'-',data=payload) + try:self._tmux('paste-buffer','-d','-r','-b',name,'-t',s.pane) + finally:self._tmux('delete-buffer','-b',name,optional=True) + s.sequence+=1 + return {'session_id':sid,'state':'TYPED','submitted_chars':len(text),'sequence':s.sequence} + + def key(self, sid, key, approved_context=None): + with self._lock: + if sid not in self._real:return super().key(sid,key,approved_context=approved_context) + if not isinstance(key,str) or key not in KEY_NAMES:raise TerminalDenied('KEY_NOT_ALLOWLISTED') + s=self._real[sid] + self._authorize(s,'key.send',key.encode(),approved_context) + if self._info(s)['pane_dead']=='1':raise TerminalDenied('SESSION_EXITED') + self._tmux('send-keys','-t',s.pane,KEY_NAMES[key]);s.sequence+=1 + return {'session_id':sid,'state':'KEY_SENT','submitted':key,'sequence':s.sequence} + + def resize(self, sid, rows, cols): + with self._lock: + if sid not in self._real:return super().resize(sid,rows,cols) + if type(rows) is not int or type(cols) is not int or not (5<=rows<=100 and 20<=cols<=220): + raise TerminalDenied('SIZE_INVALID') + self._tmux('resize-window','-t',self._real[sid].pane,'-x',str(cols),'-y',str(rows)) + self._real[sid].sequence+=1 + return {'session_id':sid,'rows':rows,'cols':cols} + + def abort(self, sid): + with self._lock: + if sid not in self._real:return super().abort(sid) + s=self._real[sid] + self._tmux('kill-session','-t',s.name) + self._real.pop(sid,None) + probe=self._tmux('has-session','-t',s.name,optional=True) + if probe.returncode==0:raise TerminalDenied('SESSION_CLOSE_NOT_VERIFIED') + return {'session_id':sid,'state':'CLOSED','cleanup_verified':True} + + def shutdown(self): + with self._lock: + for sid in list(self._real): + try:self.abort(sid) + except TerminalDenied:pass + super().shutdown() diff --git a/source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py b/source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py new file mode 100755 index 0000000..c4198d4 --- /dev/null +++ b/source/sources/original-geranas/.local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 +import hashlib +import json +import os +import re +import socket +import stat +import struct +import subprocess +import sys +from pathlib import Path + +VERSION = 1 +MAX_SCRIPT_BYTES = 262144 +MAX_REQUEST_BYTES = 393216 +JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") + +HOME = Path.home() +FBROOT = HOME / ".local/share/framebridge/fbterm" +ENGINE = FBROOT / "bin/fbterm_engine.py" + +ROOT = Path( + os.environ.get( + "FRAMEBRIDGE_TOOL_ROOT", + str(HOME / ".local/share/framebridge/tool-adapter") + ) +) +SOCKET_PATH = ROOT / "adapter.sock" +INBOX = ROOT / "inbox" +PID_FILE = ROOT / "adapter.pid" +NATIVE_HOST = HOME / "Descargas/framebridge_v0_1/host/framebridge_host.py" + + +def prepare_root(): + ROOT.mkdir(parents=True, exist_ok=True) + INBOX.mkdir(parents=True, exist_ok=True) + os.chmod(ROOT, 0o700) + os.chmod(INBOX, 0o700) + + +def recv_request(conn): + data = bytearray() + + while True: + chunk = conn.recv(65536) + if not chunk: + break + + data.extend(chunk) + + if len(data) > MAX_REQUEST_BYTES: + raise ValueError("REQUEST_TOO_LARGE") + + if b"\n" in data: + break + + raw = bytes(data).split(b"\n", 1)[0] + + if not raw: + raise ValueError("EMPTY_REQUEST") + + return json.loads(raw.decode("utf-8")) + + +def send_response(conn, obj): + raw = ( + json.dumps( + obj, + ensure_ascii=True, + sort_keys=True, + separators=(",", ":"), + ) + + "\n" + ).encode("utf-8") + + try: + conn.sendall(raw) + except (BrokenPipeError, ConnectionResetError): + return False + return True + + +def peer_uid(conn): + fmt = "3i" + raw = conn.getsockopt( + socket.SOL_SOCKET, + socket.SO_PEERCRED, + struct.calcsize(fmt), + ) + _pid, uid, _gid = struct.unpack(fmt, raw) + return uid + + +def last_result_final(stdout_path): + try: + lines = Path(stdout_path).read_text( + encoding="utf-8", + errors="replace", + ).splitlines() + except OSError: + return None + + for line in reversed(lines): + if line.startswith("RESULTADO_FINAL="): + return line.split("=", 1)[1].strip()[:2000] + + return None + + +def engine_result(stdout): + for line in reversed(stdout.splitlines()): + line = line.strip() + + if not line.startswith("{"): + continue + + try: + obj = json.loads(line) + except json.JSONDecodeError: + continue + + if isinstance(obj, dict): + return obj + + return None + + +def native_cua_roundtrip(payload, timeout_s=60): + if not NATIVE_HOST.is_file(): + raise ValueError("FRAMEBRIDGE_NATIVE_HOST_NOT_FOUND") + + raw = json.dumps( + payload, + ensure_ascii=True, + separators=(",", ":"), + ).encode("utf-8") + + frame = struct.pack(" 2_000_000: + raise ValueError("CUA_NATIVE_RESPONSE_SIZE_INVALID") + + body = out[4:4 + size] + + if len(body) != size: + raise ValueError("CUA_NATIVE_RESPONSE_TRUNCATED") + + try: + response = json.loads(body.decode("utf-8")) + except Exception: + raise ValueError("CUA_NATIVE_RESPONSE_INVALID_JSON") + + if not isinstance(response, dict): + raise ValueError("CUA_NATIVE_RESPONSE_NOT_OBJECT") + + return response + + +def handle_cua_request(req, job_id): + timeout_s = int(req.get("timeout_s", 60)) + + if not 1 <= timeout_s <= 180: + raise ValueError("INVALID_CUA_TIMEOUT") + + cua = req.get("cua") + + if not isinstance(cua, dict): + raise ValueError("CUA_PAYLOAD_REQUIRED") + + payload = dict(cua) + payload["op"] = "cua" + payload["id"] = job_id + payload["target"] = "ASUS" + payload["authorized"] = "YES" + + response = native_cua_roundtrip( + payload, + timeout_s=timeout_s, + ) + + return { + "version": VERSION, + "ok": bool(response.get("executed")), + "job_id": job_id, + "mode": "CUA", + "action": str(payload.get("action", ""))[:32], + "channel": str(response.get("channel", "CUA-DIRECT")), + "exit_code": response.get("exit_code"), + "frame_output": str(response.get("output", "")), + "error": str(response.get("error", "")), + } + + +def handle(req): + if not isinstance(req, dict): + raise ValueError("REQUEST_NOT_OBJECT") + + if req.get("version") != VERSION: + raise ValueError("UNSUPPORTED_VERSION") + + job_id = str(req.get("job_id", "")) + + if not JOB_RE.fullmatch(job_id): + raise ValueError("INVALID_JOB_ID") + + mode = str(req.get("mode", "EXEC")).upper() + + if mode != "EXEC": + raise ValueError("UNSUPPORTED_MODE") + + if req.get("authorized") is not True: + raise ValueError("AUTHORIZED_TRUE_REQUIRED") + + request_type = str( + req.get("request_type", "exec") + ).lower().strip() + + if request_type == "cua": + return handle_cua_request(req, job_id) + + if request_type != "exec": + raise ValueError("UNSUPPORTED_REQUEST_TYPE") + + script = req.get("script") + + if not isinstance(script, str) or not script: + raise ValueError("SCRIPT_REQUIRED") + + script_bytes = script.encode("utf-8") + + if len(script_bytes) > MAX_SCRIPT_BYTES: + raise ValueError("SCRIPT_TOO_LARGE") + + timeout_s = int(req.get("timeout_s", 120)) + + if not 1 <= timeout_s <= 600: + raise ValueError("INVALID_TIMEOUT") + + digest = hashlib.sha256(script_bytes).hexdigest() + + expected = req.get("sha256") + + if expected is not None: + expected = str(expected).lower() + + if digest != expected: + raise ValueError("SCRIPT_SHA256_MISMATCH") + + command_path = INBOX / f"{job_id}.bash" + temp_path = INBOX / f".{job_id}.tmp" + + temp_path.write_bytes(script_bytes) + os.chmod(temp_path, 0o600) + temp_path.replace(command_path) + + proc = subprocess.run( + [ + sys.executable, + str(ENGINE), + "exec", + job_id, + str(timeout_s), + str(command_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=timeout_s + 45, + check=False, + env={ + **os.environ, + "FBTERM_ROOT": str(FBROOT), + "FBTERM_SESSION": os.environ.get( + "FBTERM_SESSION", + "framebridge-fbterm-r1", + ), + "FBTERM_TITLE": os.environ.get( + "FBTERM_TITLE", + "FrameBridge Terminal R1", + ), + }, + ) + + result = engine_result(proc.stdout) + + if result is None: + return { + "version": VERSION, + "ok": False, + "job_id": job_id, + "adapter_error": "ENGINE_RESULT_NOT_FOUND", + "engine_rc": proc.returncode, + "engine_stderr": proc.stderr[-2000:], + "command_sha256": digest, + } + + stdout_path = result.get("STDOUT_PATH") + stderr_path = result.get("STDERR_PATH") + + return { + "version": VERSION, + "ok": True, + "job_id": job_id, + "mode": mode, + "command_sha256": digest, + "engine_rc": proc.returncode, + "exit_code": result.get("EXIT"), + "result_final": ( + last_result_final(stdout_path) + if stdout_path + else None + ), + "stdout_path": stdout_path, + "stderr_path": stderr_path, + "stdout_sha256": result.get("STDOUT_SHA256"), + "stderr_sha256": result.get("STDERR_SHA256"), + "command_hash_gate": result.get( + "COMMAND_HASH_GATE" + ), + "pty": result.get("PTY"), + "pane_pid": result.get("PANE_PID"), + "pane_tty": result.get("PANE_TTY"), + "shell_ready": result.get("SHELL_READY"), + } + + +def serve(): + prepare_root() + + if not ENGINE.is_file(): + raise RuntimeError("FBTERM_ENGINE_NOT_FOUND") + + if SOCKET_PATH.exists(): + st = SOCKET_PATH.lstat() + + if not stat.S_ISSOCK(st.st_mode): + raise RuntimeError("SOCKET_PATH_OCCUPIED") + + SOCKET_PATH.unlink() + + server = socket.socket( + socket.AF_UNIX, + socket.SOCK_STREAM, + ) + + server.bind(str(SOCKET_PATH)) + os.chmod(SOCKET_PATH, 0o600) + server.listen(4) + + PID_FILE.write_text( + str(os.getpid()) + "\n", + encoding="ascii", + ) + os.chmod(PID_FILE, 0o600) + + try: + while True: + conn, _ = server.accept() + + with conn: + try: + if peer_uid(conn) != os.getuid(): + raise PermissionError( + "PEER_UID_MISMATCH" + ) + + req = recv_request(conn) + resp = handle(req) + + except Exception as exc: + resp = { + "version": VERSION, + "ok": False, + "adapter_error": ( + f"{type(exc).__name__}:" + f"{exc}" + )[:2000], + } + + send_response(conn, resp) + + finally: + server.close() + + try: + SOCKET_PATH.unlink() + except FileNotFoundError: + pass + + +if __name__ == "__main__": + action = sys.argv[1] if len(sys.argv) > 1 else "" + + if action == "serve": + serve() + else: + print( + json.dumps( + { + "ok": False, + "error": "USAGE: serve", + }, + sort_keys=True, + ) + ) + raise SystemExit(64) diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py new file mode 100644 index 0000000..17b7e91 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py @@ -0,0 +1,1095 @@ +#!/usr/bin/env python3 +"""FBPAIR/1 development broker. + +DEV ONLY: +- supports real ChatGPT account authentication for local-browser lab validation; +- loopback/private-LAN bind recommended; +- used to test node identity, proof-of-possession, account binding and device-flow mechanics. +""" + +from __future__ import annotations + +import argparse +import collections +import hashlib +import ipaddress +import json +import re +import os +import secrets +import threading +import time +import urllib.parse +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + +from framebridge_linux_chatgpt.node_identity import ( + parse_ed25519_public_key_line, + verify_pairing_payload, +) +from framebridge_linux_chatgpt.pairing import ( + FBPAIR_PROTOCOL, + canonical, + pairing_signing_payload, + sha256_hex, +) +from framebridge_linux_chatgpt.chatgpt_auth import ( + ChatGPTAuthError, + authenticate_interactive, +) + + +ALPHABET="ABCDEFGHJKLMNPQRSTUVWXYZ23456789" + +READ_CAPS={ + "system.status","system.resources","system.processes","system.time", + "hardware.summary","kernel.modules", + "storage.inspect","storage.usage","storage.mounts", + "network.inspect","network.sockets","network.dns", + "package.status","package.search","package.upgradable", + "service.status","service.list","journal.service", + "user.list","group.list", + "filesystem.stat","filesystem.list","filesystem.hash","filesystem.read_text", +} + +ADMIN_EXEC_NODE_LABEL="ASUS" +ADMIN_EXEC_GERAMEG_NODE_ID="76885f27-da2e-4cca-bbde-6ec981e91e68" +ADMIN_EXEC_MAX_SCRIPT_BYTES=262144 +ADMIN_EXEC_JOB_RE=re.compile(r"^[A-Za-z0-9._-]{1,80}$") + + + +def approve_via_chatgpt(store, code: str, account_binding: dict | None=None, auth_fn=authenticate_interactive): + code=str(code or "").strip() + if not code: + return False,"user_code_missing" + with store.lock: + pid=store.by_user.get(code) + item=store.by_pair.get(pid) if pid else None + if not isinstance(item,dict): + return False,"pair_not_found" + if item.get("state")!="pending": + return False,"pair_not_pending" + request=item.get("request") or {} + host_id=str(request.get("node_host_id") or "") + if not host_id: + return False,"node_host_id_missing" + expected=account_binding if isinstance(account_binding,dict) else {} + saved_client_id=str(expected.get("client_id") or "") + kwargs={"client_id":saved_client_id} if saved_client_id else {} + try: + auth=auth_fn(host_id,**kwargs) + except ChatGPTAuthError as exc: + return False,str(exc) + except Exception: + return False,"chatgpt_auth_failed" + if not isinstance(auth,dict) or auth.get("ok") is not True: + return False,"chatgpt_auth_invalid" + binding={ + "issuer":auth.get("issuer"), + "account_binding_id":auth.get("account_binding_id"), + "client_id":auth.get("client_id"), + } + if not all(isinstance(binding[k],str) and binding[k] for k in binding): + return False,"chatgpt_auth_binding_invalid" + expected_binding=str(expected.get("account_binding_id") or "") + expected_issuer=str(expected.get("issuer") or "") + if expected_binding and binding["account_binding_id"]!=expected_binding: + return False,"chatgpt_account_binding_mismatch" + if expected_issuer and binding["issuer"]!=expected_issuer: + return False,"chatgpt_account_issuer_mismatch" + ok=store.approve_authenticated(code,binding) + return (True,None) if ok else (False,"pair_approval_failed") + + +def user_code() -> str: + raw="".join(secrets.choice(ALPHABET) for _ in range(8)) + return raw[:4]+"-"+raw[4:] + + +class Store: + def __init__(self, discovery_path: Path | None=None, public_base: str=""): + self.lock=threading.Lock() + self.by_pair={} + self.by_user={} + self.discovery_path=Path(discovery_path) if discovery_path else None + self.public_base=str(public_base).rstrip("/") + self.control_queue=collections.defaultdict(collections.deque) + self.control_requests={} + self.control_results={} + self.node_nonces=collections.defaultdict(dict) + self.node_last_seen={} + + def _persist_discovery(self, item, receipt): + if self.discovery_path is None: + return + path=self.discovery_path + path.parent.mkdir(parents=True,exist_ok=True,mode=0o700) + current={"schema":1,"nodes":[]} + if path.is_file(): + try: + loaded=json.loads(path.read_text(encoding="utf-8")) + if isinstance(loaded,dict) and isinstance(loaded.get("nodes"),list): + current=loaded + except Exception: + pass + record={ + "schema":1, + "source":"fbpair", + "state":"paired", + "node_id":receipt["node_id"], + "node_label":receipt["node_label"], + "node_host_id":receipt["node_host_id"], + "provider":receipt["provider"], + "account_binding_id":receipt["account_binding_id"], + "grant_id":receipt["grant_id"], + "scopes":receipt["scopes"], + "paired_at":receipt["paired_at"], + "revocation_id":receipt["revocation_id"], + "transport":"broker_outbound", + "capability_profile":"paired", + "broker_base_url":self.public_base, + "public_key_ssh":str((item.get("request") or {}).get("public_key_ssh") or ""), + } + nodes=[ + n for n in current.get("nodes",[]) + if n.get("node_id")!=record["node_id"] + ] + nodes.append(record) + current={"schema":1,"nodes":sorted(nodes,key=lambda n:str(n.get("node_label") or n.get("node_id")))} + tmp=path.with_name("."+path.name+".tmp") + tmp.write_text(json.dumps(current,indent=2,sort_keys=True)+"\n",encoding="utf-8") + os.chmod(tmp,0o600) + os.replace(tmp,path) + + def _remove_discovery_node(self,node_id): + if self.discovery_path is None or not self.discovery_path.is_file(): + return + path=self.discovery_path + try: + current=json.loads(path.read_text(encoding="utf-8")) + except Exception: + return + nodes=current.get("nodes") if isinstance(current,dict) else None + if not isinstance(nodes,list): + return + kept=[n for n in nodes if str(n.get("node_id") or "")!=str(node_id)] + if len(kept)==len(nodes): + return + value={"schema":1,"nodes":kept} + tmp=path.with_name("."+path.name+".tmp") + tmp.write_text(json.dumps(value,indent=2,sort_keys=True)+"\n",encoding="utf-8") + os.chmod(tmp,0o600) + os.replace(tmp,path) + + def cancel_pair(self,request): + pair_id=str((request or {}).get("pair_id") or "") + node_id=str((request or {}).get("node_id") or "") + reason=str((request or {}).get("reason") or "") + with self.lock: + item=self.by_pair.get(pair_id) + if not isinstance(item,dict): + return {"ok":False,"error":"pair_not_found"} + original=item.get("request") or {} + if str(original.get("node_id") or "")!=node_id: + return {"ok":False,"error":"node_mismatch"} + if time.time()>=item["expires"] and item.get("state") in {"pending","approved","receipt_issued"}: + item["state"]="expired" + state=str(item.get("state") or "") + if state in {"cancelled","expired","denied"}: + return {"ok":True,"state":state,"pair_id":pair_id,"idempotent":True} + if state=="paired": + return {"ok":False,"error":"pair_already_confirmed","state":state} + if state not in {"pending","approved","receipt_issued"}: + return {"ok":False,"error":"pair_not_cancellable","state":state} + item["state"]="cancelled" + item["cancel_reason"]=reason + item["cancelled_at"]=time.time() + code=str(item.get("user_code") or "") + if code: + self.by_user.pop(code,None) + return {"ok":True,"state":"cancelled","pair_id":pair_id,"idempotent":False} + + def confirm_pair(self,request): + pair_id=str((request or {}).get("pair_id") or "") + node_id=str((request or {}).get("node_id") or "") + with self.lock: + item=self.by_pair.get(pair_id) + if not isinstance(item,dict): + return {"ok":False,"error":"pair_not_found"} + original=item.get("request") or {} + if str(original.get("node_id") or "")!=node_id: + return {"ok":False,"error":"node_mismatch"} + if time.time()>=item["expires"] and item.get("state") in {"pending","approved","receipt_issued"}: + item["state"]="expired" + state=str(item.get("state") or "") + if state=="paired" and item.get("confirmed") is True: + return {"ok":True,"state":"paired","pair_id":pair_id,"idempotent":True} + if state in {"cancelled","denied","expired"}: + return {"ok":False,"error":"pair_not_confirmable","state":state} + if state!="receipt_issued" or not isinstance(item.get("receipt"),dict): + return {"ok":False,"error":"receipt_not_issued","state":state} + receipt=dict(item["receipt"]) + self._persist_discovery(item,receipt) + item["state"]="paired" + item["confirmed"]=True + item["confirmed_at"]=time.time() + return {"ok":True,"state":"paired","pair_id":pair_id,"idempotent":False} + + def create(self,request,public_base): + with self.lock: + pair_id=secrets.token_urlsafe(18) + device_code=secrets.token_urlsafe(32) + code=user_code() + now=time.time() + item={ + "pair_id":pair_id, + "device_code":device_code, + "user_code":code, + "created":now, + "expires":now+300, + "state":"pending", + "request":request, + } + self.by_pair[pair_id]=item + self.by_user[code]=pair_id + return { + "pair_id":pair_id, + "device_code":device_code, + "user_code":code, + "verification_uri":public_base.rstrip("/")+"/device", + "verification_uri_complete":public_base.rstrip("/")+"/device?user_code="+urllib.parse.quote(code), + "expires_in":300, + "interval":2, + } + + def approve(self,code): + with self.lock: + pid=self.by_user.get(code) + if not pid: + return False + item=self.by_pair[pid] + if time.time()>=item["expires"]: + if item.get("state") in {"pending","approved","receipt_issued"}: + item["state"]="expired" + return False + if item.get("state")!="pending": + return False + item["state"]="approved" + return True + + def approve_authenticated(self,code,binding): + with self.lock: + pid=self.by_user.get(code) + if not pid: + return False + item=self.by_pair[pid] + if time.time()>=item["expires"]: + if item.get("state") in {"pending","approved","receipt_issued"}: + item["state"]="expired" + return False + if item.get("state")!="pending": + return False + if not isinstance(binding,dict): + return False + issuer=str(binding.get("issuer") or "") + account_binding_id=str(binding.get("account_binding_id") or "") + client_id=str(binding.get("client_id") or "") + if issuer!="https://auth.openai.com": + return False + if not account_binding_id.startswith("fbacct_"): + return False + if not client_id.startswith("oaiapp_"): + return False + item["authenticated_binding"]={ + "issuer":issuer, + "account_binding_id":account_binding_id, + "client_id":client_id, + } + item["state"]="approved" + return True + + def _discovery_records(self): + if self.discovery_path is None or not self.discovery_path.is_file(): + return [] + try: + value=json.loads(self.discovery_path.read_text(encoding="utf-8")) + except Exception: + return [] + nodes=value.get("nodes") if isinstance(value,dict) else None + return nodes if isinstance(nodes,list) else [] + + def _discovery_node(self,node_id): + node_id=str(node_id) + for n in self._discovery_records(): + if isinstance(n,dict) and n.get("source")=="fbpair" and n.get("node_id")==node_id: + return n + return None + + def _paired_node(self,node_id): + node_id=str(node_id) + for n in self._discovery_records(): + if ( + isinstance(n,dict) + and n.get("state")=="paired" + and n.get("source")=="fbpair" + and n.get("node_id")==node_id + ): + return n + return None + + def queue_read(self,node_id,capability,args): + node=self._paired_node(node_id) + if node is None: + return None,"node_not_paired" + capability=str(capability) + if capability not in READ_CAPS: + return None,"capability_not_allowed" + if not isinstance(args,dict): + return None,"args_not_object" + request_id="fbreq_"+secrets.token_urlsafe(18) + item={ + "schema":1, + "request_id":request_id, + "node_id":str(node_id), + "capability":capability, + "args":args, + "created_at":time.time(), + "expires_at":time.time()+30, + } + with self.lock: + self.control_requests[request_id]=item + self.control_results[request_id]={"state":"pending","node_id":str(node_id)} + self.control_queue[str(node_id)].append(request_id) + return item,None + + def queue_plan(self,node_id,operation,payload): + node=self._paired_node(str(node_id)) + if node is None: + return None,"node_not_paired" + scopes={str(x) for x in (node.get("scopes") or [])} + if "node.plan" not in scopes: + return None,"plan_scope_missing" + operation=str(operation) + if operation not in {"plan.import","plan.status","plan.apply","plan.rollback"}: + return None,"operation_not_allowed" + if not isinstance(payload,dict): + return None,"payload_not_object" + request_id="fbreq_"+secrets.token_urlsafe(18) + item={ + "schema":1, + "request_id":request_id, + "node_id":str(node_id), + "operation":operation, + "payload":payload, + "created_at":time.time(), + "expires_at":time.time()+60, + } + with self.lock: + self.control_requests[request_id]=item + self.control_results[request_id]={"state":"pending","node_id":str(node_id)} + self.control_queue[str(node_id)].append(request_id) + return item,None + + def queue_admin_exec(self,node_id,payload): + node=self._paired_node(str(node_id)) + if node is None: + return None,"node_not_paired" + label=str(node.get("node_label") or "") + if not ( + label==ADMIN_EXEC_NODE_LABEL + or (str(node_id)==ADMIN_EXEC_GERAMEG_NODE_ID and label=="GERAMEG") + ): + return None,"admin_exec_node_not_allowed" + if not isinstance(payload,dict): + return None,"payload_not_object" + if payload.get("authorized") is not True: + return None,"authorized_true_required" + script=payload.get("script") + if not isinstance(script,str) or not script: + return None,"script_required" + raw=script.encode("utf-8") + if len(raw)>ADMIN_EXEC_MAX_SCRIPT_BYTES: + return None,"script_too_large" + job_id=str(payload.get("job_id") or "") + if ADMIN_EXEC_JOB_RE.fullmatch(job_id) is None: + return None,"invalid_job_id" + try: + timeout_s=int(payload.get("timeout_s")) + except Exception: + return None,"invalid_timeout" + if not 1<=timeout_s<=600: + return None,"invalid_timeout" + digest=hashlib.sha256(raw).hexdigest() + expected=str(payload.get("sha256") or "").lower() + if expected!=digest: + return None,"script_sha256_mismatch" + request_id="fbreq_"+secrets.token_urlsafe(18) + normalized={ + "authorized":True, + "job_id":job_id, + "script":script, + "sha256":digest, + "timeout_s":timeout_s, + } + item={ + "schema":1, + "request_id":request_id, + "node_id":str(node_id), + "operation":"admin.exec", + "payload":normalized, + "created_at":time.time(), + "expires_at":time.time()+timeout_s+90, + } + with self.lock: + self.control_requests[request_id]=item + self.control_results[request_id]={"state":"pending","node_id":str(node_id)} + self.control_queue[str(node_id)].append(request_id) + return item,None + + def queue_admin_cua(self,node_id,payload): + node=self._paired_node(str(node_id)) + if node is None: + return None,"node_not_paired" + if str(node.get("node_label") or "")!="ASUS": + return None,"admin_cua_node_not_allowed" + if not isinstance(payload,dict): + return None,"payload_not_object" + if payload.get("authorized") is not True: + return None,"authorized_true_required" + cua=payload.get("cua") + if not isinstance(cua,dict): + return None,"cua_payload_required" + action=str(cua.get("action") or "").upper().strip() + if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: + return None,"cua_action_not_allowed" + job_id=str(payload.get("job_id") or "") + if ADMIN_EXEC_JOB_RE.fullmatch(job_id) is None: + return None,"invalid_job_id" + try: + timeout_s=int(payload.get("timeout_s")) + except Exception: + return None,"invalid_timeout" + if not 1<=timeout_s<=180: + return None,"invalid_timeout" + request_id="fbreq_"+secrets.token_urlsafe(18) + normalized={ + "authorized":True, + "job_id":job_id, + "timeout_s":timeout_s, + "cua":cua, + } + item={ + "schema":1, + "request_id":request_id, + "node_id":str(node_id), + "operation":"admin.cua", + "payload":normalized, + "created_at":time.time(), + "expires_at":time.time()+timeout_s+90, + } + with self.lock: + self.control_requests[request_id]=item + self.control_results[request_id]={"state":"pending","node_id":str(node_id)} + self.control_queue[str(node_id)].append(request_id) + return item,None + + def next_read(self,node_id): + node_id=str(node_id) + with self.lock: + q=self.control_queue[node_id] + while q: + rid=q.popleft() + item=self.control_requests.get(rid) + if not item: + continue + if time.time()>=float(item.get("expires_at",0)): + self.control_results[rid]={"state":"expired","node_id":node_id} + continue + self.control_results[rid]={"state":"dispatched","node_id":node_id} + return item + return None + + def put_result(self,node_id,request_id,result): + node_id=str(node_id);request_id=str(request_id) + with self.lock: + req=self.control_requests.get(request_id) + if not req or req.get("node_id")!=node_id: + return False + if not isinstance(result,dict): + return False + self.control_results[request_id]={ + "state":"complete", + "node_id":node_id, + "result":result, + "completed_at":time.time(), + } + return True + + def get_result(self,request_id): + with self.lock: + value=self.control_results.get(str(request_id)) + return dict(value) if isinstance(value,dict) else None + + def verify_node_envelope(self,envelope): + if not isinstance(envelope,dict): + return False,"invalid_envelope" + node_id=str(envelope.get("node_id") or "") + node=self._paired_node(node_id) + if node is None: + known=self._discovery_node(node_id) + if isinstance(known,dict) and known.get("state")=="revoked": + return False,"node_revoked" + return False,"node_not_paired" + pub=str(node.get("public_key_ssh") or "") + if not pub.startswith("ssh-ed25519 "): + return False,"node_public_key_missing" + if str(envelope.get("node_host_id") or "")!=str(node.get("node_host_id") or ""): + return False,"host_id_mismatch" + try: + ts=float(envelope.get("timestamp")) + except Exception: + return False,"timestamp_invalid" + if abs(time.time()-ts)>60: + return False,"timestamp_window" + nonce=str(envelope.get("nonce") or "") + if len(nonce)<16: + return False,"nonce_invalid" + with self.lock: + used=self.node_nonces[node_id] + cutoff=time.time()-120 + for k,v in list(used.items()): + if v=item["expires"] and item.get("state") in {"pending","approved","receipt_issued"}: + item["state"]="expired" + state=str(item.get("state") or "") + if state=="pending": + return {"error":"authorization_pending"} + if state in {"denied","cancelled"}: + return {"error":"access_denied"} + if state=="expired": + return {"error":"expired_token"} + if state in {"receipt_issued","paired"} and isinstance(item.get("receipt"),dict): + return dict(item["receipt"]) + if state!="approved": + return {"error":"invalid_grant"} + req=item["request"] + binding=item.get("authenticated_binding") or {} + account_binding_id=str(binding.get("account_binding_id") or "") + broker_issuer=str(binding.get("issuer") or "https://dev.framebridge.local") + if not account_binding_id: + account_binding_id="fbacct_dev_"+secrets.token_urlsafe(16) + receipt={ + "protocol":FBPAIR_PROTOCOL, + "schema":1, + "state":"paired", + "pair_id":item["pair_id"], + "request_sha256":req["request_sha256"], + "node_id":req["node_id"], + "node_label":req["node_label"], + "node_host_id":req["node_host_id"], + "provider":req["provider"], + "broker_issuer":broker_issuer, + "account_binding_id":account_binding_id, + "grant_id":"fbgrant_dev_"+secrets.token_urlsafe(16), + "paired_at":time.strftime("%Y-%m-%dT%H:%M:%SZ",time.gmtime()), + "scopes":["node.read","node.plan"], + "revocation_id":"dev-"+secrets.token_urlsafe(12), + "node_grant_binding":"proof-of-possession-required", + } + item["receipt"]=receipt + item["state"]="receipt_issued" + item["receipt_issued_at"]=time.time() + return dict(receipt) + + +def verify_request(req): + if not isinstance(req,dict) or req.get("protocol")!=FBPAIR_PROTOCOL: + return False,"protocol" + proof=req.get("node_proof") or {} + pub=str(req.get("public_key_ssh") or "") + if proof.get("format")!="sshsig" or proof.get("namespace")!="framebridge-pairing": + return False,"proof-format" + try: + parsed=parse_ed25519_public_key_line(pub) + except Exception: + return False,"public-key" + if parsed["host_id"]!=req.get("node_host_id"): + return False,"host-id" + if parsed["public_jwk"]!=req.get("public_jwk"): + return False,"jwk" + base=dict(req) + base.pop("node_proof",None) + base.pop("public_key_ssh",None) + claimed=base.pop("request_sha256",None) + if claimed!=sha256_hex(base): + return False,"request-sha" + base["request_sha256"]=claimed + if not verify_pairing_payload( + pub, + canonical(base), + str(proof.get("signature_b64") or ""), + ): + return False,"signature" + return True,"ok" + + +PAIR_CANCEL_REASONS={ + "network_change","service_stop","reconcile","window_expired", + "window_closed","local_operator_cancelled", +} + + +def _verify_lifecycle_request(store,req,operation): + if not isinstance(req,dict) or req.get("protocol")!=FBPAIR_PROTOCOL or req.get("schema")!=1: + return False,"protocol" + if req.get("operation")!=operation: + return False,"operation" + pair_id=str(req.get("pair_id") or "") + node_id=str(req.get("node_id") or "") + node_host_id=str(req.get("node_host_id") or "") + proof=req.get("node_proof") or {} + pub=str(req.get("public_key_ssh") or "") + if not pair_id or not node_id or not node_host_id: + return False,"identity" + if proof.get("format")!="sshsig" or proof.get("namespace")!="framebridge-pairing": + return False,"proof-format" + with store.lock: + item=store.by_pair.get(pair_id) + if not isinstance(item,dict): + return False,"pair-not-found" + original=dict(item.get("request") or {}) + if str(original.get("node_id") or "")!=node_id: + return False,"node-id" + if str(original.get("node_host_id") or "")!=node_host_id: + return False,"host-id" + if str(original.get("public_key_ssh") or "")!=pub: + return False,"public-key-binding" + if operation=="pair.cancel" and str(req.get("reason") or "") not in PAIR_CANCEL_REASONS: + return False,"reason" + if operation=="pair.confirm" and str(req.get("request_sha256") or "")!=str(original.get("request_sha256") or ""): + return False,"request-sha" + try: + parsed=parse_ed25519_public_key_line(pub) + except Exception: + return False,"public-key" + if parsed["host_id"]!=node_host_id: + return False,"host-id" + base=dict(req) + base.pop("node_proof",None) + base.pop("public_key_ssh",None) + if not verify_pairing_payload( + pub,canonical(base),str(proof.get("signature_b64") or "") + ): + return False,"signature" + return True,"ok" + + +def verify_cancel_request(store,req): + return _verify_lifecycle_request(store,req,"pair.cancel") + + +def verify_confirm_request(store,req): + return _verify_lifecycle_request(store,req,"pair.confirm") + + +DEFAULT_CONTROL_PEERS = frozenset({'127.0.0.1'}) + + +def _control_peer_literal(value): + if not isinstance(value,str) or not value or value != value.strip() or any(c in value for c in ("/","*","%")): + raise ValueError("CONTROL_PEER_CONFIG_INVALID") + try: + address=ipaddress.ip_address(value) + except ValueError: + raise ValueError("CONTROL_PEER_CONFIG_INVALID") from None + if address.is_unspecified: + raise ValueError("CONTROL_PEER_CONFIG_INVALID") + return str(address) + + +def parse_control_peers(values=None): + """Validate explicit extra transport peers for the development entrypoint.""" + if values is None: + return DEFAULT_CONTROL_PEERS + if not isinstance(values,(list,tuple,set,frozenset)): + raise ValueError("CONTROL_PEER_CONFIG_INVALID") + return frozenset(set(DEFAULT_CONTROL_PEERS) | {_control_peer_literal(value) for value in values}) + + +def control_peer_allowed(peer,allowed): + try: + return _control_peer_literal(peer) in allowed + except ValueError: + return False + + +def make_handler(store,public_base,account_binding=None,*,control_peers=None): + allowed_control_peers=parse_control_peers(control_peers) + class Handler(BaseHTTPRequestHandler): + server_version="FrameBridgePairDev/1" + + def log_message(self,fmt,*args): + return + + def send_json(self,status,value): + data=json.dumps(value,sort_keys=True).encode() + self.send_response(status) + self.send_header("Content-Type","application/json") + self.send_header("Cache-Control","no-store") + self.send_header("Content-Length",str(len(data))) + self.end_headers() + self.wfile.write(data) + + def read_json(self): + try: + n=int(self.headers.get("Content-Length","0")) + if n<0 or n>1024*1024: + raise ValueError + value=json.loads(self.rfile.read(n).decode()) + if not isinstance(value,dict): + raise ValueError + return value + except Exception: + return None + + def do_POST(self): + if self.path=="/fbpair/v1/device_authorization": + req=self.read_json() + ok,reason=verify_request(req) + if not ok: + self.send_json(400,{"error":"invalid_node_proof","reason":reason}) + return + self.send_json(200,store.create(req,public_base)) + return + if self.path=="/fbpair/v1/cancel": + req=self.read_json() or {} + ok,reason=verify_cancel_request(store,req) + if not ok: + self.send_json(400,{"ok":False,"error":"invalid_node_cancel","reason":reason}) + return + value=store.cancel_pair(req) + self.send_json(200 if value.get("ok") else 409,value) + return + if self.path=="/fbpair/v1/confirm": + req=self.read_json() or {} + ok,reason=verify_confirm_request(store,req) + if not ok: + self.send_json(400,{"ok":False,"error":"invalid_node_confirm","reason":reason}) + return + value=store.confirm_pair(req) + self.send_json(200 if value.get("ok") else 409,value) + return + if self.path=="/fbpair/v1/token": + req=self.read_json() or {} + self.send_json( + 200, + store.token( + str(req.get("pair_id") or ""), + str(req.get("device_code") or ""), + ), + ) + return + if self.path=="/fbpair/v1/control/node-status": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + value=store.node_runtime_status(str(req.get("node_id") or "")) + if value is None: + self.send_json(404,{"ok":False,"error":"node_not_paired"}) + else: + self.send_json(200,{"ok":True,**value}) + return + + if self.path=="/fbpair/v1/control/request": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + item,error=store.queue_read( + str(req.get("node_id") or ""), + str(req.get("capability") or ""), + req.get("args") or {}, + ) + if error: + self.send_json(400,{"ok":False,"error":error}) + else: + self.send_json(200,{"ok":True,"request_id":item["request_id"]}) + return + + if self.path=="/fbpair/v1/control/admin-exec": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + item,error=store.queue_admin_exec( + str(req.get("node_id") or ""), + req.get("payload") or {}, + ) + if error: + self.send_json(400,{"ok":False,"error":error}) + else: + self.send_json(200,{"ok":True,"request_id":item["request_id"]}) + return + + if self.path=="/fbpair/v1/control/admin-cua": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + item,error=store.queue_admin_cua( + str(req.get("node_id") or ""), + req.get("payload") or {}, + ) + if error: + self.send_json(400,{"ok":False,"error":error}) + else: + self.send_json(200,{"ok":True,"request_id":item["request_id"]}) + return + + if self.path=="/fbpair/v1/control/operation": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + item,error=store.queue_plan( + str(req.get("node_id") or ""), + str(req.get("operation") or ""), + req.get("payload") or {}, + ) + if error: + self.send_json(400,{"ok":False,"error":error}) + else: + self.send_json(200,{"ok":True,"request_id":item["request_id"]}) + return + + if self.path=="/fbpair/v1/control/result": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + value=store.get_result(str(req.get("request_id") or "")) + if value is None: + self.send_json(404,{"ok":False,"error":"request_not_found"}) + else: + self.send_json(200,{"ok":True,**value}) + return + + if self.path=="/fbpair/v1/node/poll": + req=self.read_json() or {} + ok,detail=store.verify_node_envelope(req) + if not ok: + self.send_json(403,{"ok":False,"error":detail}) + return + item=store.next_read(req["node_id"]) + self.send_json(200,{"ok":True,"request":item}) + return + + if self.path=="/fbpair/v1/node/result": + req=self.read_json() or {} + ok,detail=store.verify_node_envelope(req) + if not ok: + self.send_json(403,{"ok":False,"error":detail}) + return + payload=req.get("payload") or {} + saved=store.put_result( + req["node_id"], + str(payload.get("request_id") or ""), + payload.get("result") or {}, + ) + self.send_json(200 if saved else 400,{"ok":bool(saved)}) + return + + if self.path=="/dev/authenticate-and-approve": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + ok,error=approve_via_chatgpt( + store, + str(req.get("user_code") or ""), + account_binding=account_binding, + ) + self.send_json(200 if ok else 400,{"ok":ok,"error":error}) + return + + if self.path=="/dev/approve-authenticated": + peer=str(self.client_address[0] or "") + if not control_peer_allowed(peer,allowed_control_peers): + self.send_json(403,{"ok":False,"error":"local_only"}) + return + req=self.read_json() or {} + binding={ + "issuer":req.get("issuer"), + "account_binding_id":req.get("account_binding_id"), + "client_id":req.get("client_id"), + } + ok=store.approve_authenticated( + str(req.get("user_code") or ""), + binding, + ) + self.send_json(200 if ok else 400,{"ok":ok}) + return + + if self.path=="/dev/approve": + req=self.read_json() or {} + ok=store.approve(str(req.get("user_code") or "")) + self.send_json(200 if ok else 404,{"ok":ok}) + return + if self.path=="/dev/deny": + req=self.read_json() or {} + ok=store.deny(str(req.get("user_code") or "")) + self.send_json(200 if ok else 404,{"ok":ok}) + return + self.send_json(404,{"error":"not_found"}) + + def do_GET(self): + p=urllib.parse.urlparse(self.path) + if p.path=="/health": + self.send_json(200,{"ok":True,"protocol":FBPAIR_PROTOCOL,"dev_only":True}) + return + if p.path=="/device": + code=urllib.parse.parse_qs(p.query).get("user_code",[""])[0] + pid=store.by_user.get(code) + item=store.by_pair.get(pid,{}) if pid else {} + req=item.get("request") or {} + safe_code=urllib.parse.quote(code,safe="") + body=( + "" + "FrameBridge DEV Pairing" + "

FrameBridge DEV Pairing

" + "

DEV LAB — real ChatGPT authentication, development control plane.

" + "

Node: "+str(req.get("node_label") or "")+"

" + "

Provider: "+str(req.get("provider") or "")+"

" + "

Host ID: "+str(req.get("node_host_id") or "")+"

" + "

User code: "+code+"

" + "" + "
"
+                    ""
+                    "

No ChatGPT password is collected by FrameBridge.

" + ).encode() + self.send_response(200) + self.send_header("Content-Type","text/html; charset=utf-8") + self.send_header("Content-Length",str(len(body))) + self.end_headers() + self.wfile.write(body) + return + self.send_json(404,{"error":"not_found"}) + return Handler + + +def main(): + ap=argparse.ArgumentParser() + ap.add_argument("--bind",default="127.0.0.1") + ap.add_argument("--port",type=int,default=18765) + ap.add_argument("--public-base",default="http://127.0.0.1:18765") + ap.add_argument("--discovery-file",default="") + ap.add_argument("--account-binding-file",default="") + ap.add_argument("--control-peer",action="append",metavar="IP",help="Allow an additional literal transport peer for development control endpoints") + args=ap.parse_args() + try: + control_peers=parse_control_peers(args.control_peer) + except ValueError as exc: + ap.error(str(exc)) + account_binding=None + if args.account_binding_file: + p=Path(args.account_binding_file).expanduser() + try: + value=json.loads(p.read_text(encoding="utf-8")) + except Exception: + raise SystemExit("ACCOUNT_BINDING_FILE_INVALID") + required=("issuer","account_binding_id","client_id") + if not isinstance(value,dict) or any(not isinstance(value.get(k),str) or not value.get(k) for k in required): + raise SystemExit("ACCOUNT_BINDING_FILE_INVALID") + account_binding={k:value[k] for k in required} + store=Store( + Path(args.discovery_file) if args.discovery_file else None, + public_base=args.public_base, + ) + server=ThreadingHTTPServer( + (args.bind,args.port), + make_handler(store,args.public_base,account_binding=account_binding,control_peers=control_peers), + ) + print(json.dumps({ + "ok":True, + "dev_only":True, + "bind":args.bind, + "port":args.port, + "public_base":args.public_base, + "protocol":FBPAIR_PROTOCOL, + }),flush=True) + server.serve_forever() + + +if __name__=="__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py new file mode 100644 index 0000000..f9d5b56 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0-alpha.1" diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py new file mode 100644 index 0000000..e68e8e2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py @@ -0,0 +1,276 @@ +from __future__ import annotations + +import base64 +import hashlib +import json +import secrets +import socket +import threading +import time +import urllib.parse +from http.server import BaseHTTPRequestHandler, HTTPServer +from typing import Any + +AUTHORIZATION_ENDPOINT="https://auth.openai.com/api/accounts/authorize" +TOKEN_ENDPOINT="https://auth.openai.com/api/accounts/oauth/token" +DISCOVERY_ENDPOINT="https://auth.openai.com/.well-known/openid-configuration" +RESOURCE="https://api.openai.com/v1" +DYNAMIC_CLIENT_ID="dynamic_agent_client" +DEFAULT_SCOPES=("openid","profile","email","offline_access","resource.invoke","chatgpt.tokens.use.direct") +AGENT_NAME="FrameBridge" + + +class ChatGPTAuthError(RuntimeError): + pass + + +def discover_openai(*, requests_module=None) -> dict[str,str]: + if requests_module is None: + import requests as requests_module + response=requests_module.get(DISCOVERY_ENDPOINT,timeout=15) + if response.status_code!=200: + raise ChatGPTAuthError("CHATGPT_DISCOVERY_FAILED") + value=response.json() + if not isinstance(value,dict): + raise ChatGPTAuthError("CHATGPT_DISCOVERY_INVALID") + issuer=str(value.get("issuer") or "") + auth=str(value.get("authorization_endpoint") or "") + token=str(value.get("token_endpoint") or "") + jwks=str(value.get("jwks_uri") or "") + if issuer!="https://auth.openai.com": + raise ChatGPTAuthError("CHATGPT_DISCOVERY_INVALID") + for url in (auth,token,jwks): + if not url.startswith("https://auth.openai.com/"): + raise ChatGPTAuthError("CHATGPT_DISCOVERY_INVALID") + return { + "issuer":issuer, + "authorization_endpoint":auth, + "token_endpoint":token, + "jwks_uri":jwks, + } + + +def _b64url(data: bytes) -> str: + return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=") + + +def _random(n: int=48) -> str: + return secrets.token_urlsafe(n) + + +def account_binding_id(issuer: str, subject: str) -> str: + raw=(str(issuer)+"\0"+str(subject)).encode("utf-8") + return "fbacct_"+hashlib.sha256(raw).hexdigest()[:40] + + +def build_authorization( + host_id: str, + redirect_uri: str, + *, + scopes: tuple[str,...]=DEFAULT_SCOPES, + authorization_endpoint: str=AUTHORIZATION_ENDPOINT, + client_id: str=DYNAMIC_CLIENT_ID, +) -> tuple[str,dict[str,str]]: + host_id=str(host_id).strip() + if not host_id: + raise ChatGPTAuthError("CHATGPT_HOST_ID_REQUIRED") + state=_random(32) + nonce=_random(32) + verifier=_random(64) + challenge=_b64url(hashlib.sha256(verifier.encode("ascii")).digest()) + client_id=str(client_id or DYNAMIC_CLIENT_ID) + tx={ + "state":state, + "nonce":nonce, + "code_verifier":verifier, + "redirect_uri":str(redirect_uri), + "created_at":str(time.time()), + "client_id":client_id, + } + query={ + "client_id":client_id, + "ext_agent_host_id":host_id, + "response_type":"code", + "redirect_uri":str(redirect_uri), + "scope":" ".join(scopes), + "resource":RESOURCE, + "state":state, + "nonce":nonce, + "code_challenge_method":"S256", + "code_challenge":challenge, + } + if client_id==DYNAMIC_CLIENT_ID: + query["agent_name_hint"]=AGENT_NAME + return str(authorization_endpoint)+"?"+urllib.parse.urlencode(query),tx + + +class _Callback: + def __init__(self): + self.event=threading.Event() + self.query: dict[str,str]={} + + +def _handler(callback: _Callback): + class Handler(BaseHTTPRequestHandler): + def log_message(self,fmt,*args): + return + def do_GET(self): + p=urllib.parse.urlparse(self.path) + if p.path!="/auth/callback": + self.send_response(404);self.end_headers();return + q=urllib.parse.parse_qs(p.query,keep_blank_values=True) + callback.query={k:(v[0] if v else "") for k,v in q.items()} + callback.event.set() + body=( + b"" + b"FrameBridge" + b"

FrameBridge

" + b"

ChatGPT authentication returned to FrameBridge.

" + b"

You may close this browser tab.

" + ) + self.send_response(200) + self.send_header("Content-Type","text/html; charset=utf-8") + self.send_header("Cache-Control","no-store") + self.send_header("Content-Length",str(len(body))) + self.end_headers() + self.wfile.write(body) + return Handler + + +def start_loopback_listener() -> tuple[HTTPServer,_Callback,str]: + cb=_Callback() + server=HTTPServer(("127.0.0.1",0),_handler(cb)) + port=int(server.server_address[1]) + uri=f"http://127.0.0.1:{port}/auth/callback" + thread=threading.Thread(target=server.serve_forever,daemon=True) + thread.start() + return server,cb,uri + + +def exchange_and_validate( + callback_query: dict[str,str], + tx: dict[str,str], + *, + requests_module=None, + jwt_module=None, + provider_metadata: dict[str,str] | None=None, +) -> dict[str,Any]: + if callback_query.get("state") != tx.get("state"): + raise ChatGPTAuthError("CHATGPT_STATE_MISMATCH") + if callback_query.get("error"): + raise ChatGPTAuthError("CHATGPT_AUTH_DENIED") + code=str(callback_query.get("code") or "") + requested_client_id=str(tx.get("client_id") or DYNAMIC_CLIENT_ID) + callback_client_id=str(callback_query.get("client_id") or "") + if requested_client_id==DYNAMIC_CLIENT_ID: + issued_client_id=callback_client_id + if not code or not issued_client_id or issued_client_id==DYNAMIC_CLIENT_ID: + raise ChatGPTAuthError("CHATGPT_CALLBACK_INCOMPLETE") + else: + issued_client_id=requested_client_id + if callback_client_id and callback_client_id!=requested_client_id: + raise ChatGPTAuthError("CHATGPT_AUTH_CLIENT_MISMATCH") + if not code: + raise ChatGPTAuthError("CHATGPT_CALLBACK_INCOMPLETE") + + if requests_module is None: + import requests as requests_module + if jwt_module is None: + import jwt as jwt_module + + provider_metadata=provider_metadata or discover_openai(requests_module=requests_module) + token_endpoint=provider_metadata["token_endpoint"] + issuer=provider_metadata["issuer"] + jwks_uri=provider_metadata["jwks_uri"] + + token_resp=requests_module.post( + token_endpoint, + data={ + "grant_type":"authorization_code", + "client_id":issued_client_id, + "code":code, + "code_verifier":tx["code_verifier"], + "redirect_uri":tx["redirect_uri"], + "resource":RESOURCE, + }, + timeout=20, + ) + if token_resp.status_code != 200: + raise ChatGPTAuthError("CHATGPT_TOKEN_EXCHANGE_FAILED") + tokens=token_resp.json() + id_token=str(tokens.get("id_token") or "") + if not id_token: + raise ChatGPTAuthError("CHATGPT_ID_TOKEN_MISSING") + + jwk_client=jwt_module.PyJWKClient(jwks_uri) + signing_key=jwk_client.get_signing_key_from_jwt(id_token) + alg=str(getattr(signing_key,"algorithm_name","") or "") + if not alg: + try: + alg=str(jwt_module.get_unverified_header(id_token).get("alg") or "") + except Exception: + alg="" + if alg not in {"RS256","ES256","EdDSA"}: + raise ChatGPTAuthError("CHATGPT_ID_TOKEN_ALG_INVALID") + + claims=jwt_module.decode( + id_token, + signing_key.key, + algorithms=[alg], + audience=issued_client_id, + issuer=issuer, + options={"require":["sub","exp","iat"]}, + leeway=5, + ) + if claims.get("nonce") != tx.get("nonce"): + raise ChatGPTAuthError("CHATGPT_NONCE_MISMATCH") + subject=str(claims.get("sub") or "") + if not subject: + raise ChatGPTAuthError("CHATGPT_SUBJECT_MISSING") + + granted=str(tokens.get("scope") or callback_query.get("scope") or "") + scopes=sorted({x for x in granted.split() if x}) + email=str(claims.get("email") or "") + + # No OAuth access/refresh/ID token is persisted or returned. + return { + "ok":True, + "issuer":issuer, + "client_id":issued_client_id, + "account_binding_id":account_binding_id(issuer,subject), + "subject_sha256":hashlib.sha256(subject.encode("utf-8")).hexdigest(), + "email_sha256":hashlib.sha256(email.encode("utf-8")).hexdigest() if email else None, + "scopes":scopes, + "identity_only": "chatgpt.tokens.use.direct" not in scopes, + } + + +def authenticate_interactive( + host_id: str, + *, + browser_opener=None, + timeout_seconds: int=300, + client_id: str=DYNAMIC_CLIENT_ID, +) -> dict[str,Any]: + server,cb,redirect_uri=start_loopback_listener() + try: + provider=discover_openai() + url,tx=build_authorization( + host_id, + redirect_uri, + authorization_endpoint=provider["authorization_endpoint"], + client_id=client_id, + ) + if browser_opener is None: + import webbrowser + opened=webbrowser.open(url,new=1,autoraise=True) + else: + opened=bool(browser_opener(url)) + if not opened: + raise ChatGPTAuthError("CHATGPT_BROWSER_OPEN_FAILED") + if not cb.event.wait(timeout_seconds): + raise ChatGPTAuthError("CHATGPT_AUTH_TIMEOUT") + return exchange_and_validate(cb.query,tx,provider_metadata=provider) + finally: + server.shutdown() + server.server_close() diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py new file mode 100644 index 0000000..bd6a0a1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +import hashlib +import json +import re +import time +from dataclasses import dataclass +from typing import Any + +from .product_core import ProductError +from .seed_capabilities import execute_capability, list_capabilities +from .seed_core import SeedContext + +FRAME_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") + + +@dataclass(frozen=True) +class ClassicRequest: + frame_id: str + target: str + mode: str + lang: str + body: dict[str, Any] + body_sha256: str + + +def parse_frame(text: str) -> ClassicRequest: + raw = str(text).replace("\r\n", "\n").replace("\r", "\n") + lines = raw.splitlines() + + try: + start = lines.index("FRAMEEXEC/1") + except ValueError: + raise ProductError("CLASSIC_FRAME_START_MISSING") + + try: + end = lines.index("FRAMEEND/1", start + 1) + except ValueError: + raise ProductError("CLASSIC_FRAME_END_MISSING") + + frame_lines = lines[start + 1:end] + headers: dict[str, str] = {} + body_start = None + + for i, line in enumerate(frame_lines): + if not line.strip(): + body_start = i + 1 + break + if "=" not in line: + raise ProductError("CLASSIC_HEADER_INVALID") + key, value = line.split("=", 1) + headers[key.strip().upper()] = value.strip() + + if body_start is None: + raise ProductError("CLASSIC_BODY_SEPARATOR_MISSING") + + frame_id = headers.get("ID", "") + target = headers.get("TARGET", "") + mode = headers.get("MODE", "") + lang = headers.get("LANG", "") + + if not FRAME_ID_RE.fullmatch(frame_id): + raise ProductError("CLASSIC_ID_INVALID") + if not target: + raise ProductError("CLASSIC_TARGET_REQUIRED") + if mode != "EXEC": + raise ProductError("CLASSIC_MODE_UNSUPPORTED") + if lang != "framebridge-cap-v1": + raise ProductError("CLASSIC_LANG_UNSUPPORTED") + + body_text = "\n".join(frame_lines[body_start:]).strip() + if not body_text: + raise ProductError("CLASSIC_BODY_MISSING") + if len(body_text.encode("utf-8")) > 32768: + raise ProductError("CLASSIC_BODY_TOO_LARGE") + + try: + body = json.loads(body_text) + except Exception: + raise ProductError("CLASSIC_BODY_JSON_INVALID") + + if not isinstance(body, dict): + raise ProductError("CLASSIC_BODY_NOT_OBJECT") + + canonical = json.dumps( + body, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + ).encode("utf-8") + + return ClassicRequest( + frame_id=frame_id, + target=target, + mode=mode, + lang=lang, + body=body, + body_sha256=hashlib.sha256(canonical).hexdigest(), + ) + + +def _target_matches(seed: SeedContext, target: str) -> bool: + identity = seed.load_identity() + if identity is None: + raise ProductError("SEED_NOT_INITIALIZED") + + accepted = { + str(identity.get("hostname") or ""), + str(identity.get("node_id") or ""), + str(identity.get("node_id_short") or ""), + } + return target in accepted + + +def execute_frame(seed: SeedContext, text: str) -> dict[str, Any]: + request = parse_frame(text) + + if not _target_matches(seed, request.target): + raise ProductError("CLASSIC_TARGET_MISMATCH") + + capability = str(request.body.get("capability") or "").strip() + args = request.body.get("args") or {} + + if "script" in request.body or "shell" in request.body or "command" in request.body: + raise ProductError("CLASSIC_RAW_SHELL_FORBIDDEN") + + started = time.monotonic() + try: + result = execute_capability(capability, args) + status = "PASS" + exit_code = 0 + except Exception as exc: + result = { + "ok": False, + "error": str(exc), + "error_type": type(exc).__name__, + } + status = "ERROR" + exit_code = 1 + + duration_ms = int((time.monotonic() - started) * 1000) + + seed.audit.append( + "classic." + capability, + status, + request={ + "id": request.frame_id, + "target": request.target, + "lang": request.lang, + "body_sha256": request.body_sha256, + "capability": capability, + "args": args, + }, + result={ + "exit": exit_code, + "duration_ms": duration_ms, + }, + principal="chat-manual", + ) + + return { + "id": request.frame_id, + "exit": exit_code, + "channel": "SEED-CLASSIC-TTY", + "capability": capability, + "body_sha256": request.body_sha256, + "duration_ms": duration_ms, + "result": result, + } + + +def render_result(value: dict[str, Any]) -> str: + payload = json.dumps( + value.get("result"), + indent=2, + sort_keys=True, + ensure_ascii=True, + ) + return "\n".join([ + "FRAMERESULT/1", + "ID=" + str(value.get("id")), + "EXIT=" + str(value.get("exit")), + "CHANNEL=" + str(value.get("channel")), + "CAPABILITY=" + str(value.get("capability")), + "BODY_SHA256=" + str(value.get("body_sha256")), + "DURATION_MS=" + str(value.get("duration_ms")), + "", + payload, + "FRAMEEND/1", + ]) + + +def render_snapshot(seed: SeedContext) -> str: + identity = seed.load_identity() + config = seed.load_config() + if identity is None or config is None: + raise ProductError("SEED_NOT_INITIALIZED") + + snapshot = { + "protocol": "framebridge-cap-v1", + "hostname": identity.get("hostname"), + "node_id": identity.get("node_id"), + "node_id_short": identity.get("node_id_short"), + "provider": config.get("provider"), + "connection_mode": config.get("connection_mode", "MANUAL"), + "capability_profile": config.get("capability_profile"), + "capabilities": list_capabilities(), + "instruction": ( + "Return only one FRAMEEXEC/1 frame using LANG=framebridge-cap-v1. " + "Use only a listed READONLY capability. Never include raw shell." + ), + } + + payload = json.dumps(snapshot, indent=2, sort_keys=True, ensure_ascii=True) + + return "\n".join([ + "FRAMEBRIDGE-SNAPSHOT/1", + "TARGET=" + str(identity.get("node_id_short")), + "PROVIDER=" + str(config.get("provider")), + "CONNECTION=MANUAL", + "", + payload, + "FRAMEEND/1", + ]) diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py new file mode 100644 index 0000000..ac43523 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py @@ -0,0 +1,133 @@ +from __future__ import annotations + +import json +import os +import sys +import time +from pathlib import Path +from typing import Any + +from .memory_store import MemoryStore +from .product_core import ProductError + + +BANNER = """ +============================================================== + FrameBridge Console +============================================================== +""".strip("\n") + + +HELP = """ +Commands: + help Show this help + status Show node/Memory summary + manifest Show the AI bootstrap manifest + memory Query normalized Memory + phone Show official mobile-app guidance + provider Show configured AI provider + objectives Show future objective-prompt roadmap + quit / exit Leave the console + +Anything else is treated as a chat message. +""".strip("\n") + + +PHONE_HINT = "📱 You can also continue from the official ChatGPT app on your phone." + + +def _setup(ctx) -> dict[str, Any]: + cfg = ctx.load_config() or {} + return cfg.get("setup") or {} + + +def session_header(ctx) -> str: + setup = _setup(ctx) + provider = setup.get("provider") or (ctx.load_config() or {}).get("provider") or "unknown" + memory = MemoryStore(ctx).verify() + identity = ctx.load_identity() or {} + connected = os.environ.get("FRAMEBRIDGE_AGENT_CONNECTED") == "1" + icon = "🤖" if connected else "○" + lines = [ + BANNER, + f"Node: {identity.get('hostname') or 'unknown'} ({identity.get('node_id_short') or '--------'})", + f"AI provider: {provider}", + f"Agent: {icon} {'connected' if connected else 'not connected'}", + f"Memory: {memory.get('protocol')} R/W | events={memory.get('events')}", + PHONE_HINT, + "", + "Type help for commands.", + ] + return "\n".join(lines) + + +def _chat_response(ctx, text: str) -> str: + setup = _setup(ctx) + provider = setup.get("provider") or "chatgpt" + # Chat transcripts are intentionally NOT written to technical Memory. + # Only normalized technical facts/decisions/incidents/artifacts belong in FBM. + if provider == "chatgpt": + if os.environ.get("FRAMEBRIDGE_AGENT_CONNECTED") == "1": + return ( + "A remote ChatGPT session is connected to this node. " + "Console-to-agent conversational transport is not enabled in this candidate yet. " + "Continue in the official ChatGPT app while this console remains available locally." + ) + return ( + "ChatGPT companion mode is configured, but no remote agent session is currently attached.\n" + + PHONE_HINT + "\n" + "Open ChatGPT and connect to this FrameBridge node. The local console will remain active." + ) + return ( + f"Provider '{provider}' is configured, but direct conversational AgentAdapter transport " + "is not implemented in this candidate." + ) + + +def execute_console_command(ctx, line: str) -> tuple[bool, str]: + raw = str(line).strip() + if not raw: + return True, "" + if raw in {"quit", "exit"}: + return False, "Goodbye." + if raw == "help": + return True, HELP + if raw == "phone": + return True, PHONE_HINT + if raw == "provider": + setup = _setup(ctx) + return True, json.dumps({"provider": setup.get("provider"), "mode": "companion" if setup.get("provider") == "chatgpt" else "adapter"}, indent=2) + if raw == "status": + return True, json.dumps({"status": ctx.status(), "memory": MemoryStore(ctx).stats()}, indent=2, sort_keys=True) + if raw == "manifest": + return True, json.dumps(MemoryStore(ctx).bootstrap(), indent=2, sort_keys=True) + if raw.startswith("memory "): + domain = raw.split(None, 1)[1].strip() + return True, json.dumps(MemoryStore(ctx).query(domain, 20), indent=2, sort_keys=True) + if raw == "objectives": + return True, ( + "Objective prompts are planned for a later update. Examples: install a desktop, " + "create a server, configure a workstation, prepare a NAS. They are NOT active in this candidate." + ) + return True, _chat_response(ctx, raw) + + +def run_console_chat(ctx) -> int: + try: + MemoryStore(ctx).require_ready() + except Exception as exc: + print("FrameBridge Console blocked: mandatory Memory is not healthy.") + print(str(exc)) + return 2 + print(session_header(ctx)) + while True: + try: + line = input("\nYou > ") + except (EOFError, KeyboardInterrupt): + print("\nGoodbye.") + return 0 + keep, out = execute_console_command(ctx, line) + if out: + print("\n" + ("AI > " if line.strip() not in {"help","status","manifest","phone","provider","objectives"} and not line.strip().startswith("memory ") else "") + out) + if not keep: + return 0 diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py new file mode 100644 index 0000000..67aad4a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import sys + +from .classic_tty import execute_frame, render_result, render_snapshot +from .product_core import ProductError +from .seed_core import SeedContext + + +BANNER = """FrameBridge Seed // Classic TTY +Modo: MANUAL / CHAT_ONLY +Seguridad: READONLY typed capabilities +Comandos locales: /snapshot /status /audit /help /quit +Pegue un FRAMEEXEC/1 completo para ejecutarlo. +""" + + +def _read_frame(first_line: str) -> str: + lines = [first_line.rstrip("\n")] + while True: + raw = sys.stdin.readline() + if raw == "": + raise ProductError("CLASSIC_FRAME_EOF_BEFORE_END") + line = raw.rstrip("\n") + lines.append(line) + if line == "FRAMEEND/1": + return "\n".join(lines) + + +def run_console(seed: SeedContext) -> int: + if seed.load_identity() is None: + print("ERROR: Seed no inicializado. Ejecute: framebridge init --provider chatgpt") + return 2 + + print(BANNER) + print("--- SNAPSHOT PARA SU IA ---") + print(render_snapshot(seed)) + print("--- FIN SNAPSHOT ---") + + while True: + if sys.stdin.isatty(): + print("fb> ", end="", flush=True) + + raw = sys.stdin.readline() + if raw == "": + return 0 + + line = raw.rstrip("\n") + stripped = line.strip() + + if not stripped: + continue + + if stripped == "/quit": + return 0 + if stripped == "/help": + print(BANNER) + continue + if stripped == "/snapshot": + print(render_snapshot(seed)) + continue + if stripped == "/status": + import json + print(json.dumps(seed.status(), indent=2, sort_keys=True, ensure_ascii=True)) + continue + if stripped == "/audit": + import json + print(json.dumps(seed.audit.verify(), indent=2, sort_keys=True, ensure_ascii=True)) + continue + + if stripped != "FRAMEEXEC/1": + print("ERROR: comando desconocido. Use /help o pegue FRAMEEXEC/1.") + continue + + try: + frame = _read_frame(line) + result = execute_frame(seed, frame) + print(render_result(result)) + except ProductError as exc: + print("FRAMERESULT/1") + print("ID=unknown") + print("EXIT=2") + print("CHANNEL=SEED-CLASSIC-TTY") + print("") + print('{"ok":false,"error":' + __import__("json").dumps(str(exc)) + "}") + print("FRAMEEND/1") + + return 0 diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py new file mode 100644 index 0000000..cc55695 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import json +import os +import shutil +import socket +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from .memory_store import MemoryStore +from .product_core import ProductError + + +@dataclass(frozen=True) +class SetupChoice: + privilege_mode: str + provider: str + ui_language: str + + +VALID_PRIVILEGE_MODES = {"totp", "sudo_tty"} +VALID_PROVIDERS = {"chatgpt", "ollama", "lmstudio", "other"} +VALID_LANGUAGES = {"en", "es", "zh", "fr", "de", "pt", "ja", "ko"} + + +def detect_system() -> dict[str, Any]: + from .seed_capabilities import hardware_summary, system_status, storage_inspect, network_inspect + system = system_status({}) + hardware = hardware_summary({}) + storage = storage_inspect({}) + network = network_inspect({}) + return { + "hostname": socket.gethostname(), + "system": system, + "hardware": hardware, + "storage": storage, + "network": network, + } + + +def validate_choice(choice: SetupChoice) -> SetupChoice: + pm = str(choice.privilege_mode).strip().lower() + provider = str(choice.provider).strip().lower() + lang = str(choice.ui_language).strip().lower() + if pm not in VALID_PRIVILEGE_MODES: + raise ProductError("SETUP_PRIVILEGE_MODE_INVALID") + if provider not in VALID_PROVIDERS: + raise ProductError("SETUP_PROVIDER_INVALID") + if lang not in VALID_LANGUAGES: + raise ProductError("SETUP_LANGUAGE_INVALID") + return SetupChoice(pm, provider, lang) + + +def save_setup_config(ctx, choice: SetupChoice) -> dict[str, Any]: + choice = validate_choice(choice) + config = ctx.load_config() or {} + config["provider"] = choice.provider + config["setup"] = { + "completed": True, + "privilege_mode": choice.privilege_mode, + "provider": choice.provider, + "ui_language": choice.ui_language, + "chat_mode": "companion" if choice.provider == "chatgpt" else "agent_adapter", + "pairing_state": ( + (config.get("pairing") or {}).get("state", "not_paired") + if choice.provider == "chatgpt" + else "adapter_pending" + ), + } + path = Path(ctx.config_path) + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_name(path.name + ".setup.tmp") + tmp.write_text(json.dumps(config, indent=2, sort_keys=True) + "\n", encoding="utf-8") + os.chmod(tmp, 0o600) + os.replace(tmp, path) + return config + + +def setup_summary(ctx) -> dict[str, Any]: + config = ctx.load_config() or {} + setup = config.get("setup") or {} + memory = MemoryStore(ctx).verify() if (config.get("memory") or {}).get("required") else {"ok": False, "required": False} + return { + "ok": True, + "setup": setup, + "memory": memory, + "provider": config.get("provider"), + "seed_version": config.get("seed_version"), + } + + +def render_privilege_explanation(mode: str) -> list[str]: + if mode == "totp": + return [ + "Authenticator / QR TOTP selected.", + "FrameBridge will show enrollment QR locally.", + "The TOTP secret and OTP codes never leave this machine.", + "Privileged CHANGE operations still use typed plans and one-shot approval.", + ] + return [ + "Local sudo password selected.", + "FrameBridge never stores or forwards your sudo password.", + "sudo prompts are handled directly by the local terminal.", + "The AI does not receive general sudo or raw root shell access.", + ] + + +def provider_instructions(provider: str) -> list[str]: + if provider == "chatgpt": + return [ + "ChatGPT companion mode selected.", + "Use the official ChatGPT app or web interface for your OpenAI account.", + "FrameBridge does not ask for or store your ChatGPT password.", + "After pairing, this local console remains the primary on-machine FrameBridge interface.", + ] + if provider == "ollama": + return ["Ollama selected. Local AgentAdapter integration will be configured when available."] + if provider == "lmstudio": + return ["LM Studio selected. Local AgentAdapter integration will be configured when available."] + return ["Custom provider selected. Configure an AgentAdapter after setup."] diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py new file mode 100644 index 0000000..3ab8804 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py @@ -0,0 +1,548 @@ +from __future__ import annotations + +import hashlib +import json +import os +import re +import struct +import tempfile +import time +import uuid +import zlib +from pathlib import Path +from typing import Any, Iterator + +from .product_core import ProductError, redact +from .seed_capabilities import hardware_summary, list_capabilities, network_inspect, storage_inspect, system_status + +FBAM_PROTOCOL = "FBAM/1" +FBM_PROTOCOL = "FBM/1" +FBM_SCHEMA = 1 +CODEC_ZLIB = 1 +HEADER = struct.Struct(">4sBBII32s") +MAGIC_MANIFEST = b"FBAM" +MAGIC_STATE = b"FBM1" +MAGIC_RECORD = b"FBMR" +MAGIC_INDEX = b"FBI1" +MAX_EVENT_RAW = 65536 +MAX_QUERY_EVENTS = 100 + +READ_CAPABILITIES = tuple(x["name"] for x in list_capabilities()) +CHANGE_CAPABILITIES = ( + "workspace.write_text", + "package.install", + "package.remove", + "service.manage", + "system.hostname", + "system.timezone", + "package.refresh", + "package.upgrade", + "system.file.write_text", + "system.directory.ensure", + "system.file.mode", + "system.daemon_reload", +) +MEMORY_CAPABILITIES = ("memory.bootstrap", "memory.query", "memory.append", "memory.checkpoint", "memory.stats") + +RECORD_TYPE_IDS = { + "checkpoint": 1, + "change": 2, + "incident": 3, + "decision": 4, + "artifact": 5, + "recovery": 6, + "certification": 7, + "note": 8, +} +RECORD_TYPE_NAMES = {v: k for k, v in RECORD_TYPE_IDS.items()} +SOURCE_IDS = {"system": 1, "agent": 2, "operator": 3, "import": 4} +SOURCE_NAMES = {v: k for k, v in SOURCE_IDS.items()} +FIELD_REGISTRY = { + 1: "schema", 2: "record_type", 3: "timestamp_ns", 4: "node_id", + 5: "identity", 6: "platform", 7: "hardware", 8: "storage", 9: "network", + 10: "software", 11: "capabilities", 12: "policy", 13: "connections", + 14: "plans", 15: "audit", 16: "changes", 17: "incidents", 18: "decisions", + 19: "artifacts", 20: "recovery", 21: "relationships", 22: "pending", + 23: "freshness", 24: "provenance", +} +DOMAIN_TO_FIELD = {name: fid for fid, name in FIELD_REGISTRY.items() if fid >= 5} +FORBIDDEN_KEY_RE = re.compile(r"(?i)(password|passwd|secret|token|private[_-]?key|otp|credential|client[_-]?secret|api[_-]?key)") + + +def _uint_head(major: int, value: int) -> bytes: + if value < 0: + raise ValueError("CBOR_UINT_NEGATIVE") + if value < 24: + return bytes([(major << 5) | value]) + if value <= 0xFF: + return bytes([(major << 5) | 24, value]) + if value <= 0xFFFF: + return bytes([(major << 5) | 25]) + struct.pack(">H", value) + if value <= 0xFFFFFFFF: + return bytes([(major << 5) | 26]) + struct.pack(">I", value) + if value <= 0xFFFFFFFFFFFFFFFF: + return bytes([(major << 5) | 27]) + struct.pack(">Q", value) + raise ValueError("CBOR_INTEGER_TOO_LARGE") + + +def cbor_encode(value: Any) -> bytes: + if value is None: + return b"\xf6" + if value is False: + return b"\xf4" + if value is True: + return b"\xf5" + if isinstance(value, int) and not isinstance(value, bool): + return _uint_head(0, value) if value >= 0 else _uint_head(1, -1 - value) + if isinstance(value, float): + return b"\xfb" + struct.pack(">d", value) + if isinstance(value, bytes): + return _uint_head(2, len(value)) + value + if isinstance(value, str): + raw = value.encode("utf-8") + return _uint_head(3, len(raw)) + raw + if isinstance(value, (list, tuple)): + parts = [cbor_encode(x) for x in value] + return _uint_head(4, len(parts)) + b"".join(parts) + if isinstance(value, dict): + items = [] + for key, item in value.items(): + ek = cbor_encode(key) + items.append((len(ek), ek, cbor_encode(item))) + items.sort(key=lambda x: (x[0], x[1])) + return _uint_head(5, len(items)) + b"".join(ek + ev for _, ek, ev in items) + raise TypeError("CBOR_TYPE_UNSUPPORTED:" + type(value).__name__) + + +def _read_ai(data: bytes, offset: int, ai: int) -> tuple[int, int]: + if ai < 24: + return ai, offset + if ai == 24: + if offset + 1 > len(data): raise ValueError("CBOR_TRUNCATED") + return data[offset], offset + 1 + if ai == 25: + if offset + 2 > len(data): raise ValueError("CBOR_TRUNCATED") + return struct.unpack_from(">H", data, offset)[0], offset + 2 + if ai == 26: + if offset + 4 > len(data): raise ValueError("CBOR_TRUNCATED") + return struct.unpack_from(">I", data, offset)[0], offset + 4 + if ai == 27: + if offset + 8 > len(data): raise ValueError("CBOR_TRUNCATED") + return struct.unpack_from(">Q", data, offset)[0], offset + 8 + raise ValueError("CBOR_INDEFINITE_NOT_SUPPORTED") + + +def _decode_one(data: bytes, offset: int = 0) -> tuple[Any, int]: + if offset >= len(data): + raise ValueError("CBOR_TRUNCATED") + first = data[offset]; offset += 1 + major, ai = first >> 5, first & 31 + if major in (0, 1): + n, offset = _read_ai(data, offset, ai) + return (n if major == 0 else -1 - n), offset + if major in (2, 3): + n, offset = _read_ai(data, offset, ai) + end = offset + n + if end > len(data): raise ValueError("CBOR_TRUNCATED") + raw = data[offset:end] + return (raw if major == 2 else raw.decode("utf-8")), end + if major == 4: + n, offset = _read_ai(data, offset, ai) + out = [] + for _ in range(n): + item, offset = _decode_one(data, offset); out.append(item) + return out, offset + if major == 5: + n, offset = _read_ai(data, offset, ai) + out = {} + for _ in range(n): + key, offset = _decode_one(data, offset) + val, offset = _decode_one(data, offset) + out[key] = val + return out, offset + if major == 7: + if ai == 20: return False, offset + if ai == 21: return True, offset + if ai == 22: return None, offset + if ai == 27: + if offset + 8 > len(data): raise ValueError("CBOR_TRUNCATED") + return struct.unpack_from(">d", data, offset)[0], offset + 8 + raise ValueError("CBOR_VALUE_UNSUPPORTED") + + +def cbor_decode(data: bytes) -> Any: + value, offset = _decode_one(bytes(data), 0) + if offset != len(data): + raise ValueError("CBOR_TRAILING_DATA") + return value + + +def _pack_container(magic: bytes, value: Any) -> bytes: + raw = cbor_encode(value) + comp = zlib.compress(raw, 9) + digest = hashlib.sha256(raw).digest() + return HEADER.pack(magic, FBM_SCHEMA, CODEC_ZLIB, len(raw), len(comp), digest) + comp + + +def _unpack_container(data: bytes, expected_magic: bytes) -> tuple[Any, dict[str, Any]]: + if len(data) < HEADER.size: + raise ProductError("MEMORY_CONTAINER_TRUNCATED") + magic, schema, codec, raw_len, comp_len, digest = HEADER.unpack_from(data, 0) + if magic != expected_magic: raise ProductError("MEMORY_MAGIC_INVALID") + if schema != FBM_SCHEMA: raise ProductError("MEMORY_SCHEMA_UNSUPPORTED") + if codec != CODEC_ZLIB: raise ProductError("MEMORY_CODEC_UNSUPPORTED") + if len(data) != HEADER.size + comp_len: raise ProductError("MEMORY_CONTAINER_SIZE_INVALID") + try: raw = zlib.decompress(data[HEADER.size:]) + except Exception as exc: raise ProductError("MEMORY_DECOMPRESS_FAILED") from exc + if len(raw) != raw_len: raise ProductError("MEMORY_RAW_SIZE_INVALID") + if hashlib.sha256(raw).digest() != digest: raise ProductError("MEMORY_DIGEST_MISMATCH") + try: value = cbor_decode(raw) + except Exception as exc: raise ProductError("MEMORY_CBOR_INVALID") from exc + return value, {"raw_bytes": raw_len, "compressed_bytes": comp_len, "raw_sha256": digest.hex()} + + +def _atomic_write(path: Path, data: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + os.chmod(path.parent, 0o700) + fd, tmp = tempfile.mkstemp(prefix=".fbm-", dir=str(path.parent)) + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data); handle.flush(); os.fsync(handle.fileno()) + os.chmod(tmp, 0o600) + os.replace(tmp, path) + finally: + if os.path.exists(tmp): os.unlink(tmp) + + +def _atomic_write_json(path: Path, value: dict[str, Any]) -> None: + data = (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=True) + "\n").encode("utf-8") + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(prefix=".fbm-config-", dir=str(path.parent)) + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data); handle.flush(); os.fsync(handle.fileno()) + os.chmod(tmp, 0o600) + os.replace(tmp, path) + finally: + if os.path.exists(tmp): os.unlink(tmp) + + +def _uuid_bytes(value: str) -> bytes: + try: return uuid.UUID(str(value)).bytes + except Exception: return hashlib.sha256(str(value).encode("utf-8")).digest()[:16] + + +def _uuid_text(value: bytes) -> str: + try: return str(uuid.UUID(bytes=bytes(value))) + except Exception: return bytes(value).hex() + + +def _human(value: Any) -> Any: + if isinstance(value, bytes): + return value.hex() + if isinstance(value, dict): + return {str(k): _human(v) for k, v in value.items()} + if isinstance(value, list): + return [_human(v) for v in value] + return value + + +def _safe_payload(value: Any, depth: int = 0) -> Any: + if depth > 8: raise ProductError("MEMORY_PAYLOAD_DEPTH_LIMIT") + if isinstance(value, dict): + out = {} + for key, item in value.items(): + key = str(key) + if len(key) > 80: raise ProductError("MEMORY_KEY_TOO_LONG") + out[key] = "[REDACTED]" if FORBIDDEN_KEY_RE.search(key) else _safe_payload(item, depth + 1) + return out + if isinstance(value, list): + if len(value) > 256: raise ProductError("MEMORY_LIST_TOO_LONG") + return [_safe_payload(x, depth + 1) for x in value] + if isinstance(value, str): + return str(redact(value))[:4096] + if isinstance(value, (int, float, bool)) or value is None: + return value + return str(value)[:4096] + + +class MemoryStore: + def __init__(self, seed): + self.seed = seed + self.root = Path(seed.state_dir) / "memory" + self.state_path = self.root / "node.fbm" + self.events_path = self.root / "events.fbm" + self.index_path = self.root / "index.fbi" + self.manifest_path = self.root / "manifest.fbam" + + def _identity_config(self) -> tuple[dict[str, Any], dict[str, Any]]: + identity = self.seed.load_identity() + config = self.seed.load_config() + if not isinstance(identity, dict) or not isinstance(config, dict): + raise ProductError("MEMORY_SEED_NOT_INITIALIZED") + return identity, config + + def _plans(self) -> list[dict[str, Any]]: + out = [] + plans_dir = Path(self.seed.state_dir) / "plans" + if not plans_dir.is_dir(): return out + import json + for path in sorted(plans_dir.glob("*.json")): + try: + value = json.loads(path.read_text(encoding="utf-8")) + plan = value.get("plan") or {} + out.append({1: path.stem, 2: value.get("state"), 3: value.get("plan_sha256"), 4: plan.get("title")}) + except Exception: + out.append({1: path.stem, 2: "INVALID"}) + return out + + @staticmethod + def _normalize_storage(value: dict[str, Any]) -> dict[int, Any]: + devices = [] + for item in (value.get("blockdevices") or [])[:128]: + if not isinstance(item, dict): continue + devices.append({ + 1: item.get("name"), 2: item.get("type"), 3: item.get("size"), + 4: item.get("fstype"), 5: item.get("mountpoints"), 6: item.get("rota"), 7: item.get("model"), + }) + return {1: value.get("ok") is True, 2: devices} + + @staticmethod + def _normalize_network(value: dict[str, Any]) -> dict[int, Any]: + addresses = [] + for item in (value.get("addresses") or [])[:64]: + if not isinstance(item, dict): continue + infos = [] + for a in (item.get("addr_info") or [])[:32]: + if not isinstance(a, dict): continue + infos.append({1:a.get("family"),2:a.get("local"),3:a.get("prefixlen"),4:a.get("scope")}) + addresses.append({1:item.get("ifname"),2:item.get("operstate"),3:infos}) + routes = [] + for r in (value.get("routes") or [])[:128]: + if not isinstance(r, dict): continue + routes.append({1:r.get("dst"),2:r.get("gateway"),3:r.get("dev"),4:r.get("protocol"),5:r.get("metric")}) + return {1:value.get("ok") is True,2:addresses,3:routes} + + def _collect_state(self) -> dict[int, Any]: + identity, config = self._identity_config() + doctor = self.seed.doctor() + sys_state = system_status({}) + hw = hardware_summary({}) + storage = storage_inspect({}) + network = network_inspect({}) + audit = self.seed.audit.verify() + plans = self._plans() + pending = [p for p in plans if p.get(2) not in ("ROLLED_BACK",)] + memory_cfg = config.get("memory") or {} + return { + 1: FBM_SCHEMA, + 2: 1, + 3: time.time_ns(), + 4: _uuid_bytes(identity.get("node_id", "")), + 5: { + 1: identity.get("hostname"), + 2: identity.get("node_id_short"), + 3: config.get("provider"), + 4: identity.get("node_host_id"), + 5: identity.get("node_public_key_sha256"), + }, + 6: {1: sys_state.get("os_id"), 2: (doctor.get("checks") or {}).get("os_version"), 3: sys_state.get("os_name"), 4: sys_state.get("kernel"), 5: sys_state.get("architecture"), 6: (doctor.get("checks") or {}).get("python")}, + 7: {1: hw.get("cpu_model"), 2: hw.get("logical_cpus"), 3: hw.get("memory_total_kib"), 4: hw.get("architecture")}, + 8: self._normalize_storage(storage), + 9: self._normalize_network(network), + 10: {1: config.get("seed_version"), 2: (doctor.get("checks") or {}).get("python")}, + 11: {1: list(READ_CAPABILITIES), 2: list(CHANGE_CAPABILITIES), 3: list(MEMORY_CAPABILITIES)}, + 12: {1: config.get("capability_profile"), 2: True, 3: "plan-required"}, + 13: {1: config.get("connection_name"), 2: config.get("connection_mode"), 3: (config.get("pairing") or {}).get("state"), 4: (config.get("pairing") or {}).get("transport")}, + 14: plans, + 15: {1: audit.get("ok") is True, 2: audit.get("entries"), 3: audit.get("last_hash")}, + 21: [], + 22: pending, + 23: {1: time.time_ns(), 2: "live-collection"}, + 24: {1: "framebridge-node", 2: memory_cfg.get("authority", "deployment-defined"), 3: "node-operational"}, + } + + def _manifest_object(self, state: dict[int, Any]) -> dict[int, Any]: + identity, config = self._identity_config() + return { + 1: 1, + 2: "FrameBridge", + 3: FBAM_PROTOCOL, + 4: _uuid_bytes(identity.get("node_id", "")), + 5: identity.get("node_id_short"), + 6: identity.get("hostname"), + 7: config.get("seed_version"), + 8: state.get(6), + 9: state.get(7), + 10: state.get(11), + 11: {1: config.get("capability_profile"), 2: True}, + 12: {1: True, 2: "read_write", 3: FBM_PROTOCOL, 4: list(MEMORY_CAPABILITIES)}, + 13: {1: (config.get("memory") or {}).get("authority", "deployment-defined"), 2: "node-operational"}, + 14: time.time_ns(), + } + + def _read_container(self, path: Path, magic: bytes) -> tuple[Any, dict[str, Any]]: + try: data = path.read_bytes() + except FileNotFoundError as exc: raise ProductError("MEMORY_FILE_MISSING") from exc + return _unpack_container(data, magic) + + def _load_index(self) -> dict[int, Any]: + if not self.index_path.is_file(): return {1:1,2:0,3:b"",4:[]} + value, _ = self._read_container(self.index_path, MAGIC_INDEX) + if not isinstance(value, dict): raise ProductError("MEMORY_INDEX_INVALID") + return value + + def initialize(self) -> dict[str, Any]: + _, config = self._identity_config() + desired = { + "required": True, "protocol": FBM_PROTOCOL, "manifest_protocol": FBAM_PROTOCOL, + "mode": "read_write", "authority": (config.get("memory") or {}).get("authority", "deployment-defined"), + } + if config.get("memory") != desired: + config["memory"] = desired + _atomic_write_json(Path(self.seed.config_path), config) + self.root.mkdir(parents=True, exist_ok=True); os.chmod(self.root, 0o700) + if not self.events_path.exists(): + self.events_path.touch(mode=0o600); os.chmod(self.events_path, 0o600) + if not self.index_path.exists(): + _atomic_write(self.index_path, _pack_container(MAGIC_INDEX, {1:1,2:0,3:b"",4:[]})) + checkpoint = self.checkpoint() + if self._load_index().get(2, 0) == 0: + self._append_event_raw("checkpoint", {"action":"memory.initialize","protocol":FBM_PROTOCOL}, "system") + return {"ok":True,"protocol":FBM_PROTOCOL,"manifest_protocol":FBAM_PROTOCOL,"required":True,"mode":"read_write","checkpoint":checkpoint,"verify":self.verify()} + + def checkpoint(self) -> dict[str, Any]: + state = self._collect_state() + manifest = self._manifest_object(state) + state_blob = _pack_container(MAGIC_STATE, state) + manifest_blob = _pack_container(MAGIC_MANIFEST, manifest) + _atomic_write(self.state_path, state_blob) + _atomic_write(self.manifest_path, manifest_blob) + return { + "ok": True, + "state_sha256": hashlib.sha256(state_blob).hexdigest(), + "manifest_sha256": hashlib.sha256(manifest_blob).hexdigest(), + "state_bytes": len(state_blob), + "manifest_bytes": len(manifest_blob), + } + + def ready(self) -> bool: + try: return self.verify().get("ok") is True + except Exception: return False + + def require_ready(self) -> None: + _, config = self._identity_config() + required = bool((config.get("memory") or {}).get("required")) + if required and not self.ready(): raise ProductError("MEMORY_REQUIRED_NOT_READY") + + def _append_event_raw(self, record_type: str, data: dict[str, Any], source: str) -> dict[str, Any]: + if record_type not in RECORD_TYPE_IDS: raise ProductError("MEMORY_RECORD_TYPE_INVALID") + if source not in SOURCE_IDS: raise ProductError("MEMORY_SOURCE_INVALID") + identity, _ = self._identity_config() + safe = _safe_payload(data) + if not isinstance(safe, dict): raise ProductError("MEMORY_PAYLOAD_NOT_OBJECT") + index = self._load_index() + prev = index.get(3) or b"" + record = {1:FBM_SCHEMA,2:RECORD_TYPE_IDS[record_type],3:time.time_ns(),4:_uuid_bytes(identity.get("node_id","")),5:safe,6:SOURCE_IDS[source],7:prev} + raw = cbor_encode(record) + if len(raw) > MAX_EVENT_RAW: raise ProductError("MEMORY_EVENT_TOO_LARGE") + block = _pack_container(MAGIC_RECORD, record) + self.events_path.parent.mkdir(parents=True, exist_ok=True) + offset = self.events_path.stat().st_size if self.events_path.exists() else 0 + with self.events_path.open("ab") as handle: + handle.write(block); handle.flush(); os.fsync(handle.fileno()) + os.chmod(self.events_path, 0o600) + record_hash = hashlib.sha256(raw).digest() + offsets = list(index.get(4) or []) + offsets.append(offset) + offsets = offsets[-1024:] + new_index = {1:1,2:int(index.get(2) or 0)+1,3:record_hash,4:offsets} + _atomic_write(self.index_path, _pack_container(MAGIC_INDEX, new_index)) + return {"ok":True,"record_type":record_type,"event_number":new_index[2],"event_sha256":record_hash.hex(),"source":source} + + def append(self, record_type: str, data: dict[str, Any], source: str = "agent") -> dict[str, Any]: + self.require_ready() + return self._append_event_raw(record_type, data, source) + + def _iter_events(self) -> Iterator[tuple[dict[int, Any], bytes, int]]: + if not self.events_path.exists(): return + data = self.events_path.read_bytes(); off = 0; number = 0 + while off < len(data): + if off + HEADER.size > len(data): raise ProductError("MEMORY_EVENT_STREAM_TRUNCATED") + _, _, _, _, comp_len, _ = HEADER.unpack_from(data, off) + end = off + HEADER.size + comp_len + if end > len(data): raise ProductError("MEMORY_EVENT_STREAM_TRUNCATED") + value, _ = _unpack_container(data[off:end], MAGIC_RECORD) + if not isinstance(value, dict): raise ProductError("MEMORY_EVENT_INVALID") + number += 1 + yield value, hashlib.sha256(cbor_encode(value)).digest(), off + off = end + + def verify(self) -> dict[str, Any]: + state, sm = self._read_container(self.state_path, MAGIC_STATE) + manifest, mm = self._read_container(self.manifest_path, MAGIC_MANIFEST) + index = self._load_index() + prev = b""; count = 0; offsets = [] + for record, record_hash, offset in self._iter_events(): + if record.get(7, b"") != prev: raise ProductError("MEMORY_EVENT_CHAIN_INVALID") + prev = record_hash; count += 1; offsets.append(offset) + if int(index.get(2) or 0) != count: raise ProductError("MEMORY_INDEX_COUNT_MISMATCH") + if (index.get(3) or b"") != prev: raise ProductError("MEMORY_INDEX_HASH_MISMATCH") + identity, _ = self._identity_config() + if state.get(4) != _uuid_bytes(identity.get("node_id","")): raise ProductError("MEMORY_NODE_ID_MISMATCH") + if manifest.get(4) != state.get(4): raise ProductError("MEMORY_MANIFEST_NODE_MISMATCH") + return { + "ok":True,"protocol":FBM_PROTOCOL,"manifest_protocol":FBAM_PROTOCOL,"events":count, + "event_head_sha256":prev.hex() if prev else "GENESIS", + "state_raw_bytes":sm["raw_bytes"],"state_compressed_bytes":sm["compressed_bytes"], + "manifest_raw_bytes":mm["raw_bytes"],"manifest_compressed_bytes":mm["compressed_bytes"], + } + + def bootstrap(self) -> dict[str, Any]: + self.require_ready() + manifest, _ = self._read_container(self.manifest_path, MAGIC_MANIFEST) + platform = manifest.get(8) or {}; hardware = manifest.get(9) or {}; caps = manifest.get(10) or {}; mem = manifest.get(12) or {}; authority = manifest.get(13) or {} + return { + "ok": True, + "protocol": FBAM_PROTOCOL, + "framebridge": {"product":"FrameBridge","seed_version":manifest.get(7)}, + "node": {"node_id":_uuid_text(manifest.get(4,b"")),"node_id_short":manifest.get(5),"hostname":manifest.get(6)}, + "os": {"id":platform.get(1),"version":platform.get(2),"name":platform.get(3),"kernel":platform.get(4),"architecture":platform.get(5)}, + "hardware": {"cpu_model":hardware.get(1),"logical_cpus":hardware.get(2),"memory_total_kib":hardware.get(3),"architecture":hardware.get(4)}, + "capabilities": {"read":caps.get(1) or [],"change":caps.get(2) or [],"memory":caps.get(3) or []}, + "policy": {"profile":(manifest.get(11) or {}).get(1),"change_requires_plan":True}, + "memory": {"required":mem.get(1) is True,"mode":mem.get(2),"protocol":mem.get(3),"capabilities":mem.get(4) or [],"rule":"READ_BEFORE_DEPENDENT_ACTION__WRITE_AFTER_MATERIAL_CHANGE"}, + "authority": {"mode":authority.get(1),"node_role":authority.get(2)}, + "generated_at_ns": manifest.get(14), + "manifest_sha256": hashlib.sha256(self.manifest_path.read_bytes()).hexdigest(), + } + + def query(self, domain: str = "summary", limit: int = 20) -> dict[str, Any]: + self.require_ready() + domain = str(domain).strip().lower() + limit = max(1, min(int(limit), MAX_QUERY_EVENTS)) + state, _ = self._read_container(self.state_path, MAGIC_STATE) + if domain == "summary": + verify = self.verify() + return {"ok":True,"domain":"summary","node_id":_uuid_text(state.get(4,b"")),"hostname":(state.get(5) or {}).get(1),"seed_version":(state.get(10) or {}).get(1),"audit":_human(state.get(15)),"pending":_human(state.get(22)),"memory":verify} + if domain == "events": + items = [] + for record, record_hash, _ in self._iter_events(): + items.append({"record_type":RECORD_TYPE_NAMES.get(record.get(2),"unknown"),"timestamp_ns":record.get(3),"source":SOURCE_NAMES.get(record.get(6),"unknown"),"data":_human(record.get(5)),"sha256":record_hash.hex()}) + return {"ok":True,"domain":"events","events":items[-limit:]} + if domain not in DOMAIN_TO_FIELD: raise ProductError("MEMORY_QUERY_DOMAIN_INVALID") + return {"ok":True,"domain":domain,"value":_human(state.get(DOMAIN_TO_FIELD[domain]))} + + def stats(self) -> dict[str, Any]: + self.require_ready(); verify = self.verify() + return { + "ok":True,"protocol":FBM_PROTOCOL, + "files": { + "node.fbm":self.state_path.stat().st_size, + "events.fbm":self.events_path.stat().st_size, + "index.fbi":self.index_path.stat().st_size, + "manifest.fbam":self.manifest_path.stat().st_size, + }, + "events":verify.get("events"),"event_head_sha256":verify.get("event_head_sha256"), + "state_compression_ratio":round(verify["state_compressed_bytes"] / max(1, verify["state_raw_bytes"]), 4), + } diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py new file mode 100644 index 0000000..7c1de5e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py @@ -0,0 +1,353 @@ +from __future__ import annotations + +import argparse +import json +import sys +import time +from datetime import datetime, timezone +from typing import Any, Callable + +from .memory_store import MemoryStore +from .product_core import ProductError +from .network_security import inspect_network +from .pairing import PairingError, PairingStore, public_pairing_view +from .pairing_client import PairingClientError, cancel_pairing_remote +from .pairing_window import close_pairing_window, pairing_window_status +from .seed_core import SeedContext, atomic_write_json + + +MONITOR_SCHEMA = 1 +DEFAULT_INTERVAL_SECONDS = 1.0 + + +def _candidate_binding(candidate: dict[str, Any] | None) -> dict[str, Any]: + c=candidate or {} + return { + "interface":str(c.get("interface") or ""), + "source_subnet":str(c.get("subnet") or ""), + "destination_address":str(c.get("address") or ""), + "network_fingerprint":str(c.get("network_fingerprint") or ""), + } + + +def _window_binding(window: dict[str, Any]) -> dict[str, Any]: + return { + "interface":str(window.get("interface") or ""), + "source_subnet":str(window.get("source_subnet") or ""), + "destination_address":str(window.get("destination_address") or ""), + "network_fingerprint":str(window.get("network_fingerprint") or ""), + } + + +def compare_binding(window: dict[str, Any], candidate: dict[str, Any] | None) -> dict[str, Any]: + old=_window_binding(window) + new=_candidate_binding(candidate) + reasons=[] + if not candidate: + reasons.append("NO_ELIGIBLE_PAIRING_CANDIDATE") + else: + for key,label in ( + ("interface","INTERFACE_CHANGED"), + ("source_subnet","SUBNET_CHANGED"), + ("destination_address","ADDRESS_CHANGED"), + ("network_fingerprint","NETWORK_FINGERPRINT_CHANGED"), + ): + if old.get(key)!=new.get(key): + reasons.append(label) + return { + "changed":bool(reasons), + "reasons":reasons, + "old":old, + "new":new, + } + + +def _sync_pairing_config(ctx: SeedContext, view: dict[str, Any]) -> None: + config=ctx.load_config() or {} + pairing=config.get("pairing") or {} + pairing["state"]=view.get("state","expired") + if view.get("expiration_reason"): + pairing["expiration_reason"]=view.get("expiration_reason") + pairing["requires_new_local_action"]=True + config["pairing"]=pairing + config["connection_mode"]="PAIRING" + setup=config.get("setup") + if isinstance(setup,dict): + setup["pairing_state"]=view.get("state","expired") + atomic_write_json(ctx.config_path,config) + + +def _invalidate_pending_pair(ctx: SeedContext, reason: str) -> dict[str, Any] | None: + store=PairingStore(ctx.state_dir) + current=store.load() + if current.get("state")!="pair_requested": + return None + view=store.invalidate_pending(reason) + _sync_pairing_config(ctx,view) + return view + + +def reconcile_broker_cancel( + ctx: SeedContext, + *, + cancel_func=cancel_pairing_remote, + retry_seconds: float=5.0, +) -> dict[str, Any]: + store=PairingStore(ctx.state_dir) + current=store.load() + if current.get("broker_cancel_required") is not True: + return {"ok":True,"state":"NOT_REQUIRED","cancelled":False} + pair_id=str(current.get("pair_id") or "") + reason=str(current.get("broker_cancel_reason") or current.get("expiration_reason") or "reconcile") + last=str(current.get("broker_cancel_last_attempt_at") or "") + if last: + try: + last_dt=datetime.fromisoformat(last.replace("Z","+00:00")).astimezone(timezone.utc) + age=(datetime.now(timezone.utc)-last_dt).total_seconds() + if age dict[str, Any]: + window_id=str(window.get("window_id") or "") + if window.get("state")=="expired" and window.get("rule_present") is not False: + raise ProductError("NETWORK_WINDOW_EXPIRY_UNVERIFIED") + if not window_id or window.get("state")!="expired" or window.get("close_reason")!="expired": + return {"ok":True,"synced":False} + path=_lifecycle_state_path(ctx) + prior={} + if path.is_file(): + try: + prior=json.loads(path.read_text()) + except Exception: + prior={} + if prior.get("last_expiry_window_id")==window_id: + return {"ok":True,"synced":False,"already_synced":True,"window_id":window_id} + pair_view=_invalidate_pending_pair(ctx,"window_expired") + broker_cancel=reconcile_broker_cancel(ctx) + ctx.audit.append( + "network.pairing_window.expired","PASS", + request={"window_id":window_id,"expires_at":window.get("expires_at")}, + result={ + "rule_present":window.get("rule_present"), + "pair_state":(pair_view or {}).get("state"), + "requires_new_local_action":bool(pair_view), + "broker_cancel_state":broker_cancel.get("state"), + }, + principal="network-monitor", + ) + memory=MemoryStore(ctx) + if memory.ready(): + memory.append( + "recovery", + { + "operation":"network.pairing_window.expired", + "window_id":window_id, + "rule_present":window.get("rule_present"), + "pair_state":(pair_view or {}).get("state"), + "requires_new_local_action":bool(pair_view), + "broker_cancel_state":broker_cancel.get("state"), + },source="system", + ) + atomic_write_json(path,{"schema":MONITOR_SCHEMA,"last_expiry_window_id":window_id,"last_expiry_synced_at":time.time()}) + return { + "ok":True,"synced":True,"window_id":window_id, + "pair_state":(pair_view or {}).get("state"), + "requires_new_local_action":bool(pair_view), + "broker_cancel":broker_cancel, + } + + + +def _record_invalidation( + ctx: SeedContext, + *, + reason: str, + comparison: dict[str, Any], + window: dict[str, Any], + closed: dict[str, Any], + pair_view: dict[str, Any] | None, +) -> None: + result={ + "window_id":window.get("window_id"), + "close_reason":closed.get("close_reason"), + "rule_present":closed.get("rule_present"), + "pair_state":(pair_view or {}).get("state"), + "requires_new_local_action":True, + "reasons":comparison.get("reasons") or [reason], + } + ctx.audit.append( + "network.pairing_window.invalidated", + "PASS", + request={ + "reason":reason, + "old":comparison.get("old"), + "new":comparison.get("new"), + }, + result=result, + principal="network-monitor", + ) + store=MemoryStore(ctx) + if store.ready(): + store.append( + "incident", + { + "operation":"network.pairing_window.invalidated", + "reason":reason, + "reasons":comparison.get("reasons") or [reason], + "window_id":window.get("window_id"), + "old":comparison.get("old"), + "new":comparison.get("new"), + "pair_state":(pair_view or {}).get("state"), + "requires_new_local_action":True, + }, + source="system", + ) + + +def invalidate_active_window( + *, + ctx: SeedContext | None=None, + reason: str, + comparison: dict[str, Any] | None=None, + status_func: Callable[..., dict[str, Any]]=pairing_window_status, + close_func: Callable[..., dict[str, Any]]=close_pairing_window, +) -> dict[str, Any]: + ctx=ctx or SeedContext() + window=status_func(non_interactive=True) + if window.get("active") is not True: + return {"ok":True,"schema":MONITOR_SCHEMA,"state":"NO_ACTIVE_PAIRING_WINDOW","invalidated":False} + if comparison is None: + comparison={"changed":True,"reasons":[reason.upper()],"old":_window_binding(window),"new":{}} + closed=close_func(reason,non_interactive=True) + if closed.get("ok") is not True or closed.get("rule_present") is not False or closed.get("active") is True: + raise ProductError("NETWORK_WINDOW_CLOSE_UNVERIFIED") + pair_view=_invalidate_pending_pair(ctx,reason) + broker_cancel=reconcile_broker_cancel(ctx) + _record_invalidation( + ctx,reason=reason,comparison=comparison,window=window,closed=closed,pair_view=pair_view, + ) + return { + "ok":True,"schema":MONITOR_SCHEMA,"state":"INVALIDATED","invalidated":True, + "reason":reason,"reasons":comparison.get("reasons") or [], + "window_id":window.get("window_id"),"rule_present":closed.get("rule_present"), + "pair_state":(pair_view or {}).get("state"),"requires_new_local_action":True, + "broker_cancel":broker_cancel,"old":comparison.get("old"),"new":comparison.get("new"), + } + + + +def reconcile_once( + *, + ctx: SeedContext | None=None, + snapshot: dict[str, Any] | None=None, + status_func: Callable[..., dict[str, Any]]=pairing_window_status, + close_func: Callable[..., dict[str, Any]]=close_pairing_window, +) -> dict[str, Any]: + ctx=ctx or SeedContext() + broker_cancel=reconcile_broker_cancel(ctx) + window=status_func(non_interactive=True) + if window.get("active") is not True: + expiry_sync=_sync_expired_window(ctx,window) + return { + "ok":True,"schema":MONITOR_SCHEMA, + "state":"PAIRING_WINDOW_EXPIRED_SYNCED" if expiry_sync.get("synced") else "NO_ACTIVE_PAIRING_WINDOW", + "invalidated":False,"expiry_sync":expiry_sync,"broker_cancel":broker_cancel, + } + try: + snap=snapshot if snapshot is not None else inspect_network() + if not isinstance(snap,dict): raise ValueError("NETWORK_SNAPSHOT_NOT_OBJECT") + except Exception: + return invalidate_active_window( + ctx=ctx,reason="reconcile", + comparison={"changed":True,"reasons":["NETWORK_INSPECTION_FAILED"],"old":_window_binding(window),"new":{}}, + status_func=lambda **_:window,close_func=close_func, + ) + candidate=snap.get("pairing_candidate") + comparison=compare_binding(window,candidate) + if not comparison["changed"]: + return { + "ok":True,"schema":MONITOR_SCHEMA,"state":"STABLE","invalidated":False, + "window_id":window.get("window_id"),"binding":comparison["old"],"broker_cancel":broker_cancel, + } + return invalidate_active_window( + ctx=ctx,reason="network_change",comparison=comparison, + status_func=lambda **_:window,close_func=close_func, + ) + + + +def run_forever(interval: float=DEFAULT_INTERVAL_SECONDS) -> None: + interval=max(0.25,float(interval)) + ctx=SeedContext() + while True: + try: + result=reconcile_once(ctx=ctx) + if result.get("invalidated") is True: + print(json.dumps(result,sort_keys=True),flush=True) + except Exception as exc: + print(json.dumps({ + "ok":False, + "schema":MONITOR_SCHEMA, + "state":"MONITOR_ERROR", + "error":type(exc).__name__, + "message":str(exc), + },sort_keys=True),file=sys.stderr,flush=True) + time.sleep(interval) + + +def main() -> None: + parser=argparse.ArgumentParser(prog="framebridge-network-monitor") + sub=parser.add_subparsers(dest="command",required=True) + run=sub.add_parser("run") + run.add_argument("--interval",type=float,default=DEFAULT_INTERVAL_SECONDS) + sub.add_parser("once") + close=sub.add_parser("close") + close.add_argument("--reason",choices=("service_stop","reconcile"),default="service_stop") + args=parser.parse_args() + if args.command=="run": + run_forever(args.interval) + elif args.command=="once": + print(json.dumps(reconcile_once(),sort_keys=True,indent=2)) + else: + print(json.dumps(invalidate_active_window(reason=args.reason),sort_keys=True,indent=2)) + + +if __name__=="__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py new file mode 100644 index 0000000..6511234 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py @@ -0,0 +1,513 @@ +from __future__ import annotations + +import hashlib +import ipaddress +import json +import os +from pathlib import Path +import subprocess +from typing import Any + +SCHEMA = 1 +PAIRING_WINDOW_TTL_SECONDS = 600 + +RFC1918 = ( + ipaddress.ip_network("10.0.0.0/8"), + ipaddress.ip_network("172.16.0.0/12"), + ipaddress.ip_network("192.168.0.0/16"), +) +ULA_V6 = ipaddress.ip_network("fc00::/7") + +OVERLAY_NAMES = {"tailscale0"} +VIRTUAL_PREFIXES = ( + "docker", "br-", "virbr", "veth", "lxc", "waydroid", + "tap", "tun", "zt", "wg", +) + + +def _run(argv: list[str], timeout: int = 5) -> subprocess.CompletedProcess: + return subprocess.run( + argv, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=False, + ) + + +def _run_json(argv: list[str]) -> list[dict[str, Any]]: + try: + cp = _run(argv) + except Exception: + return [] + if cp.returncode != 0: + return [] + try: + value = json.loads(cp.stdout or "[]") + except Exception: + return [] + return value if isinstance(value, list) else [] + + +def address_class(address: str) -> str: + try: + ip = ipaddress.ip_address(str(address)) + except ValueError: + return "INVALID" + if ip.is_loopback: + return "LOOPBACK" + if ip.version == 4: + if any(ip in net for net in RFC1918): + return "PRIVATE_LAN" + if ip.is_link_local: + return "LINK_LOCAL" + if ip.is_global: + return "PUBLIC" + return "SPECIAL" + if ip in ULA_V6: + return "ULA" + if ip.is_link_local: + return "LINK_LOCAL" + if ip.is_global: + return "PUBLIC" + return "SPECIAL" + + +def classify_interface( + name: str, + *, + has_device: bool = False, + wireless: bool = False, +) -> str: + n = str(name or "") + if n == "lo": + return "LOOPBACK" + if n in OVERLAY_NAMES: + return "PRIVATE_OVERLAY" + if n.startswith(VIRTUAL_PREFIXES): + return "VIRTUAL_LOCAL" + if wireless or n.startswith(("wl", "wlan")): + return "PHYSICAL_LAN" + if has_device or n.startswith(("en", "eth")): + return "PHYSICAL_LAN" + return "UNKNOWN" + + +def _sysfs_interface_kind(name: str) -> str: + base = Path("/sys/class/net") / name + return classify_interface( + name, + has_device=(base / "device").exists(), + wireless=(base / "wireless").exists(), + ) + + +def _prefix_subnet(address: str, prefixlen: int) -> str: + try: + return str(ipaddress.ip_interface(f"{address}/{int(prefixlen)}").network) + except Exception: + return "" + + +def _default_routes() -> list[dict[str, Any]]: + routes = _run_json(["ip", "-j", "-4", "route", "show", "default"]) + clean: list[dict[str, Any]] = [] + for r in routes: + if not isinstance(r, dict): + continue + clean.append( + { + "dev": str(r.get("dev") or ""), + "gateway": str(r.get("gateway") or ""), + "metric": int(r.get("metric") or 0), + "prefsrc": str(r.get("prefsrc") or ""), + } + ) + return clean + + +def _network_manager_identity(ifname: str) -> dict[str, str]: + result = {"connection": "", "connection_uuid": ""} + try: + cp = _run(["nmcli", "-g", "GENERAL.CONNECTION", "device", "show", ifname]) + except Exception: + return result + if cp.returncode != 0: + return result + connection = (cp.stdout or "").strip().splitlines() + if not connection: + return result + name = connection[0].strip() + if not name or name == "--": + return result + result["connection"] = name + try: + cp2 = _run(["nmcli", "-g", "connection.uuid", "connection", "show", name]) + except Exception: + return result + if cp2.returncode == 0: + result["connection_uuid"] = (cp2.stdout or "").strip().splitlines()[0] if (cp2.stdout or "").strip() else "" + return result + + +def collect_interfaces() -> list[dict[str, Any]]: + links = _run_json(["ip", "-j", "link", "show"]) + addrs = _run_json(["ip", "-j", "addr", "show"]) + addr_by_name = {str(x.get("ifname") or ""): x for x in addrs if isinstance(x, dict)} + out: list[dict[str, Any]] = [] + for link in links: + if not isinstance(link, dict): + continue + name = str(link.get("ifname") or "") + if not name: + continue + a = addr_by_name.get(name, {}) + ipv4 = [] + ipv6 = [] + for ai in a.get("addr_info") or []: + if not isinstance(ai, dict): + continue + local = str(ai.get("local") or "") + if not local: + continue + item = { + "address": local, + "prefixlen": int(ai.get("prefixlen") or 0), + "class": address_class(local), + "scope": str(ai.get("scope") or ""), + } + if ai.get("family") == "inet": + ipv4.append(item) + elif ai.get("family") == "inet6": + ipv6.append(item) + kind = _sysfs_interface_kind(name) + out.append( + { + "name": name, + "kind": kind, + "operstate": str(link.get("operstate") or ""), + "flags": list(link.get("flags") or []), + "ipv4": ipv4, + "ipv6": ipv6, + } + ) + return out + + +def choose_pairing_candidate( + interfaces: list[dict[str, Any]], + routes: list[dict[str, Any]], +) -> dict[str, Any] | None: + """Select only an unambiguous physical private-LAN binding. + + Metric zero is valid. Route preferred source is honored. Equal-priority + candidates that differ in interface/address/prefix/gateway are blocked, + not resolved by comparing payload dictionaries or snapshot order. + """ + route_by_dev: dict[str, list[dict[str, Any]]] = {} + for route in routes: + route_by_dev.setdefault(str(route.get("dev") or ""), []).append(route) + + choices: dict[tuple, tuple[tuple, dict[str, Any]]] = {} + for iface in interfaces: + if iface.get("kind") != "PHYSICAL_LAN": + continue + if str(iface.get("operstate") or "").upper() not in {"UP", "UNKNOWN"}: + continue + name = str(iface.get("name") or "") + if not name: + continue + dev_routes = route_by_dev.get(name, []) + if dev_routes: + minimum = min(int(r.get("metric") or 0) for r in dev_routes) + best_routes = [r for r in dev_routes if int(r.get("metric") or 0) == minimum] + else: + best_routes = [{}] + for selected_route in best_routes: + preferred = str(selected_route.get("prefsrc") or "") + metric = int(selected_route.get("metric") or 0) if dev_routes else None + for addr in iface.get("ipv4") or []: + address = str(addr.get("address") or "") + if address_class(address) != "PRIVATE_LAN": + continue + if preferred and address != preferred: + continue + prefix = addr.get("prefixlen") + if isinstance(prefix, bool) or not isinstance(prefix, int) or not 1 <= prefix <= 32: + continue + gateway = str(selected_route.get("gateway") or "") + item = { + "interface": name, + "interface_kind": "PHYSICAL_LAN", + "address": address, + "prefixlen": prefix, + "subnet": _prefix_subnet(address, prefix), + "address_class": "PRIVATE_LAN", + "gateway": gateway, + "route_metric": metric, + } + rank = (0 if dev_routes else 1, metric if metric is not None else 0, + 0 if preferred else 1) + identity = (name, address, prefix, gateway, metric) + choices[identity] = (rank, item) + if not choices: + return None + best_rank = min(rank for rank, item in choices.values()) + top = [item for rank, item in choices.values() if rank == best_rank] + if len(top) != 1: + return None + selected = dict(top[0]) + selected.update(_network_manager_identity(selected["interface"])) + binding = {key: selected.get(key) for key in ( + "interface", "address", "prefixlen", "gateway", "route_metric", "connection_uuid" + )} + selected["network_fingerprint_schema"] = 2 + raw = json.dumps(binding, sort_keys=True, separators=(",", ":")).encode("utf-8") + selected["network_fingerprint"] = hashlib.sha256(raw).hexdigest() + return selected + + +def _service_active(name: str) -> bool: + try: + cp = _run(["systemctl", "is-active", "--quiet", name]) + except Exception: + return False + return cp.returncode == 0 + + +def _ufw_config_enabled(path: Path=Path("/etc/ufw/ufw.conf")) -> bool: + try: + text=path.read_text(encoding="utf-8",errors="replace") + except OSError: + return False + for raw in text.splitlines(): + line=raw.strip() + if not line or line.startswith("#"): + continue + if line.upper().startswith("ENABLED="): + return line.split("=",1)[1].strip().lower() in {"yes","true","1"} + return False + + +def firewall_backend() -> dict[str, Any]: + ufw_systemd_active=_service_active("ufw") + ufw_config_enabled=_ufw_config_enabled() + if ufw_systemd_active or ufw_config_enabled: + return { + "backend": "ufw", + "active": True, + "systemd_active": ufw_systemd_active, + "config_enabled": ufw_config_enabled, + "managed_pairing_rules": True, + } + if _service_active("firewalld"): + return {"backend": "firewalld", "active": True, "managed_pairing_rules": False} + if _service_active("nftables"): + return {"backend": "nftables", "active": True, "managed_pairing_rules": False} + return {"backend": "none", "active": False, "managed_pairing_rules": False} + + +def ufw_pairing_plan(candidate: dict[str, Any], port: int) -> dict[str, Any]: + iface = str(candidate.get("interface") or "") + subnet = str(candidate.get("subnet") or "") + address = str(candidate.get("address") or "") + fp = str(candidate.get("network_fingerprint") or "") + if not iface or not subnet or not address or not fp: + return {"ok": False, "error": "PAIRING_CANDIDATE_INCOMPLETE"} + return { + "ok": True, + "backend": "ufw", + "execution_backend": "iptables-runtime-inside-ufw", + "chain": "ufw-user-input", + "execute": False, + "operation": "runtime_scoped_accept", + "interface": iface, + "source_subnet": subnet, + "destination_address": address, + "port": int(port), + "protocol": "tcp", + "network_fingerprint": fp, + "ttl_seconds": PAIRING_WINDOW_TTL_SECONDS, + "requires_local_tty": True, + "runtime_only": True, + "reboot_fail_closed": True, + "automatic_expiry": True, + "persistent_ufw_rule": False, + "rule_comment_template": "FrameBridge::pairing", + } + + +def inspect_network() -> dict[str, Any]: + interfaces = collect_interfaces() + routes = _default_routes() + candidate = choose_pairing_candidate(interfaces, routes) + fw = firewall_backend() + return { + "ok": True, + "schema": SCHEMA, + "interfaces": interfaces, + "default_routes": routes, + "pairing_candidate": candidate, + "firewall": fw, + "policy": { + "local_pairing_transport": "LAN_HTTP", + "tailscale_required": False, + "public_http_allowed": False, + "wildcard_pairing_bind_allowed": False, + "local_tty_required": True, + "pairing_window_ttl_seconds": PAIRING_WINDOW_TTL_SECONDS, + }, + } + + +def pairing_policy(port: int = 18765) -> dict[str, Any]: + snap = inspect_network() + candidate = snap.get("pairing_candidate") + fw = snap.get("firewall") or {} + if not candidate: + return { + "ok": False, + "schema": SCHEMA, + "state": "BLOCKED", + "reason": "NO_ELIGIBLE_PHYSICAL_PRIVATE_LAN", + "execute": False, + "snapshot": snap, + } + if not fw.get("active"): + return { + "ok": False, + "schema": SCHEMA, + "state": "BLOCKED", + "reason": "NO_ACTIVE_FIREWALL_BACKEND", + "execute": False, + "snapshot": snap, + } + if fw.get("backend") != "ufw": + return { + "ok": False, + "schema": SCHEMA, + "state": "BLOCKED", + "reason": "FIREWALL_BACKEND_NOT_YET_MANAGED", + "execute": False, + "snapshot": snap, + } + return { + "ok": True, + "schema": SCHEMA, + "state": "ELIGIBLE_REQUIRES_LOCAL_TTY", + "execute": False, + "candidate": candidate, + "firewall_plan": ufw_pairing_plan(candidate, int(port)), + "network_change_policy": "INVALIDATE_PENDING_PAIR_AND_CLOSE_OLD_WINDOW", + } + +CGNAT_V4 = ipaddress.ip_network("100.64.0.0/10") +FORWARD_PROTOCOLS = {"any", "tcp", "udp"} + + +def _valid_interface_name(value: str) -> bool: + import re + return re.fullmatch(r"[A-Za-z0-9_.:-]{1,32}", str(value or "")) is not None + + +def _normalize_ipv4_network(value: str) -> str: + try: + net = ipaddress.ip_network(str(value), strict=False) + except ValueError as exc: + raise ValueError("NETWORK_CIDR_INVALID") from exc + if net.version != 4: + raise ValueError("NETWORK_IPV4_REQUIRED") + if net.is_multicast or net.is_loopback or net.is_link_local: + raise ValueError("NETWORK_CIDR_UNSAFE") + return str(net) + + +def _source_network_allowed(value: str) -> bool: + net = ipaddress.ip_network(value, strict=False) + if net.version != 4: + return False + if net.subnet_of(CGNAT_V4): + return True + return any(net.subnet_of(x) for x in RFC1918) + + +def forwarding_policy( + *, + ingress_interface: str, + egress_interface: str, + source_subnet: str, + destination_subnet: str, + protocol: str = "any", + destination_port: int = 0, + nat: bool = False, + lifetime: str = "runtime", +) -> dict[str, Any]: + ingress_interface = str(ingress_interface or "").strip() + egress_interface = str(egress_interface or "").strip() + protocol = str(protocol or "any").strip().lower() + lifetime = str(lifetime or "runtime").strip().lower() + try: + destination_port = int(destination_port) + except Exception: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_PORT_INVALID", "execute": False} + + if not _valid_interface_name(ingress_interface) or not _valid_interface_name(egress_interface): + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_INTERFACE_INVALID", "execute": False} + if ingress_interface == egress_interface: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_INTERFACES_MUST_DIFFER", "execute": False} + + try: + source_subnet = _normalize_ipv4_network(source_subnet) + destination_subnet = _normalize_ipv4_network(destination_subnet) + except ValueError as exc: + return {"ok": False, "state": "BLOCKED", "reason": str(exc), "execute": False} + + if not _source_network_allowed(source_subnet): + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_SOURCE_NOT_PRIVATE", "execute": False} + + if protocol not in FORWARD_PROTOCOLS: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_PROTOCOL_INVALID", "execute": False} + if destination_port < 0 or destination_port > 65535: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_PORT_INVALID", "execute": False} + if protocol == "any" and destination_port != 0: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_PORT_REQUIRES_PROTOCOL", "execute": False} + if lifetime != "runtime": + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_PERSISTENCE_REQUIRES_SEPARATE_CAPABILITY", "execute": False} + + fw = firewall_backend() + if not fw.get("active"): + return {"ok": False, "state": "BLOCKED", "reason": "NO_ACTIVE_FIREWALL_BACKEND", "execute": False} + if fw.get("backend") != "ufw": + return {"ok": False, "state": "BLOCKED", "reason": "FIREWALL_BACKEND_NOT_YET_MANAGED", "execute": False} + + names = {x.get("name") for x in collect_interfaces()} + if ingress_interface not in names or egress_interface not in names: + return {"ok": False, "state": "BLOCKED", "reason": "NETWORK_FORWARD_INTERFACE_NOT_PRESENT", "execute": False} + + args = { + "ingress_interface": ingress_interface, + "egress_interface": egress_interface, + "source_subnet": source_subnet, + "destination_subnet": destination_subnet, + "protocol": protocol, + "destination_port": destination_port, + "nat": bool(nat), + "lifetime": "runtime", + } + return { + "ok": True, + "schema": SCHEMA, + "state": "ELIGIBLE_REQUIRES_TYPED_PRIVILEGED_PLAN", + "execute": False, + "capability": "network.forward", + "args": args, + "security": { + "local_tty_privileged_required": True, + "independent_rollback_approval_required": True, + "runtime_only": True, + "reboot_fail_closed": True, + "nat_explicit": bool(nat), + "wildcard_source_forbidden": True, + }, + } diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py new file mode 100644 index 0000000..345cd50 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py @@ -0,0 +1,239 @@ +from __future__ import annotations + +import base64 +import hashlib +import json +import os +import stat +import struct +import subprocess +from pathlib import Path +from typing import Any, Callable + +NODE_KEY_NAMESPACE = "framebridge-pairing" +NODE_KEY_ALGORITHM = "ssh-ed25519" + + +class NodeIdentityError(RuntimeError): + pass + + +def _b64url(data: bytes) -> str: + return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=") + + +def _ssh_string(blob: bytes, offset: int) -> tuple[bytes, int]: + if offset + 4 > len(blob): + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + size = struct.unpack(">I", blob[offset:offset + 4])[0] + offset += 4 + end = offset + size + if end > len(blob): + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + return blob[offset:end], end + + +def read_ed25519_public_key(public_path: Path) -> dict[str, Any]: + path = Path(public_path) + if not path.is_file() or path.is_symlink(): + raise NodeIdentityError("NODE_PUBLIC_KEY_MISSING") + parts = path.read_text(encoding="utf-8").strip().split() + if len(parts) < 2 or parts[0] != NODE_KEY_ALGORITHM: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + try: + blob = base64.b64decode(parts[1], validate=True) + except Exception as exc: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") from exc + alg, off = _ssh_string(blob, 0) + raw, off = _ssh_string(blob, off) + if off != len(blob) or alg != b"ssh-ed25519" or len(raw) != 32: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + x = _b64url(raw) + jwk = {"crv": "Ed25519", "kty": "OKP", "x": x} + canonical = json.dumps( + jwk, sort_keys=True, separators=(",", ":"), ensure_ascii=True + ).encode("ascii") + thumbprint = _b64url(hashlib.sha256(canonical).digest()) + host_id = ( + "urn:ietf:params:oauth:jwk-thumbprint:sha-256:" + thumbprint + ) + return { + "algorithm": "Ed25519", + "public_jwk": jwk, + "jwk_thumbprint_sha256": thumbprint, + "host_id": host_id, + "public_key_sha256": hashlib.sha256(blob).hexdigest(), + } + + +def ensure_node_keypair( + state_dir: Path, + *, + comment: str = "framebridge-node", + runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, +) -> dict[str, Any]: + root = Path(state_dir) / "node-identity" + private_path = root / "node_ed25519" + public_path = root / "node_ed25519.pub" + + if root.exists() and root.is_symlink(): + raise NodeIdentityError("NODE_KEY_DIR_SYMLINK_FORBIDDEN") + root.mkdir(parents=True, exist_ok=True, mode=0o700) + os.chmod(root, 0o700) + + private_exists = private_path.exists() + public_exists = public_path.exists() + if private_exists != public_exists: + raise NodeIdentityError("NODE_KEYPAIR_INCOMPLETE") + + if not private_exists: + proc = runner( + [ + "/usr/bin/ssh-keygen", + "-q", + "-t", + "ed25519", + "-N", + "", + "-C", + str(comment), + "-f", + str(private_path), + ], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if proc.returncode != 0: + raise NodeIdentityError("NODE_KEYGEN_FAILED") + + if ( + not private_path.is_file() + or private_path.is_symlink() + or not public_path.is_file() + or public_path.is_symlink() + ): + raise NodeIdentityError("NODE_KEYPAIR_INVALID") + + os.chmod(private_path, 0o600) + os.chmod(public_path, 0o644) + mode = stat.S_IMODE(private_path.stat().st_mode) + if mode != 0o600: + raise NodeIdentityError("NODE_PRIVATE_KEY_MODE_INVALID") + + public = read_ed25519_public_key(public_path) + return { + "ok": True, + "private_key_path": str(private_path), + "public_key_path": str(public_path), + "public_key_ssh": _safe_public_line(public_path), + **public, + } + + +def _safe_public_line(public_path: Path) -> str: + parts=Path(public_path).read_text(encoding="utf-8").strip().split() + if len(parts)<2 or parts[0] != NODE_KEY_ALGORITHM: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + return parts[0] + " " + parts[1] + + +def sign_pairing_payload( + private_path: Path, + payload: bytes, + *, + runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, +) -> str: + private_path=Path(private_path) + if not private_path.is_file() or private_path.is_symlink(): + raise NodeIdentityError("NODE_PRIVATE_KEY_MISSING") + import tempfile + with tempfile.TemporaryDirectory(prefix="framebridge-pair-sign-") as td: + msg=Path(td)/"message" + msg.write_bytes(bytes(payload)) + proc=runner( + [ + "/usr/bin/ssh-keygen","-Y","sign", + "-f",str(private_path), + "-n",NODE_KEY_NAMESPACE, + str(msg), + ], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + sig=Path(str(msg)+".sig") + if proc.returncode!=0 or not sig.is_file(): + raise NodeIdentityError("NODE_PAIR_SIGNATURE_FAILED") + return base64.b64encode(sig.read_bytes()).decode("ascii") + + +def verify_pairing_payload( + public_key_ssh: str, + payload: bytes, + signature_b64: str, + *, + runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, +) -> bool: + import tempfile + try: + signature=base64.b64decode(str(signature_b64),validate=True) + except Exception: + return False + public_key_ssh=str(public_key_ssh).strip() + if not public_key_ssh.startswith("ssh-ed25519 "): + return False + with tempfile.TemporaryDirectory(prefix="framebridge-pair-verify-") as td: + root=Path(td) + msg=root/"message" + sig=root/"message.sig" + allowed=root/"allowed_signers" + msg.write_bytes(bytes(payload)) + sig.write_bytes(signature) + allowed.write_text( + "framebridge-node " + public_key_ssh + "\n", + encoding="utf-8", + ) + proc=runner( + [ + "/usr/bin/ssh-keygen","-Y","verify", + "-f",str(allowed), + "-I","framebridge-node", + "-n",NODE_KEY_NAMESPACE, + "-s",str(sig), + ], + input=bytes(payload), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + return proc.returncode==0 + + +def parse_ed25519_public_key_line(line: str) -> dict[str, Any]: + parts=str(line).strip().split() + if len(parts)<2 or parts[0] != NODE_KEY_ALGORITHM: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + try: + blob=base64.b64decode(parts[1],validate=True) + except Exception as exc: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") from exc + alg,off=_ssh_string(blob,0) + raw,off=_ssh_string(blob,off) + if off!=len(blob) or alg!=b"ssh-ed25519" or len(raw)!=32: + raise NodeIdentityError("NODE_PUBLIC_KEY_INVALID") + x=_b64url(raw) + jwk={"crv":"Ed25519","kty":"OKP","x":x} + canonical=json.dumps( + jwk,sort_keys=True,separators=(",",":"),ensure_ascii=True + ).encode("ascii") + thumbprint=_b64url(hashlib.sha256(canonical).digest()) + return { + "algorithm":"Ed25519", + "public_jwk":jwk, + "jwk_thumbprint_sha256":thumbprint, + "host_id":"urn:ietf:params:oauth:jwk-thumbprint:sha-256:"+thumbprint, + "public_key_sha256":hashlib.sha256(blob).hexdigest(), + } diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py new file mode 100644 index 0000000..81bbaef --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py @@ -0,0 +1,287 @@ +from __future__ import annotations + +import json +import secrets +import time +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + +from .node_identity import ensure_node_keypair, sign_pairing_payload +from .pairing import PairingStore, canonical +from .pairing_client import PairingClientError, confirm_pairing_remote +from .product_core import ProductError +from .seed_capabilities import execute_capability, list_capabilities +from .seed_core import SeedContext, atomic_write_json +from .plan_engine import PlanEngine +from .memory_store import MemoryStore + +READ_CAPS={x["name"] for x in list_capabilities() if x.get("risk")=="READONLY"} + + +class OutboundAgentError(RuntimeError): + pass + + +def _post(url: str, value: dict[str,Any], timeout: float=10.0) -> dict[str,Any]: + req=urllib.request.Request( + url, + data=json.dumps(value,sort_keys=True,separators=(",",":")).encode("utf-8"), + headers={"Content-Type":"application/json","Accept":"application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req,timeout=timeout) as resp: + out=json.loads(resp.read(1024*1024).decode("utf-8")) + except urllib.error.HTTPError as exc: + detail="" + try: + body=json.loads(exc.read(1024*1024).decode("utf-8")) + if isinstance(body,dict): + detail=str(body.get("error") or "") + except Exception: + detail="" + if detail=="node_revoked": + raise OutboundAgentError("OUTBOUND_NODE_REVOKED") from exc + raise OutboundAgentError("OUTBOUND_BROKER_REJECTED") from exc + except Exception as exc: + raise OutboundAgentError("OUTBOUND_BROKER_UNREACHABLE") from exc + if not isinstance(out,dict): + raise OutboundAgentError("OUTBOUND_BROKER_RESPONSE_INVALID") + return out + + +def _signed_envelope(ctx: SeedContext, kind: str, payload: dict[str,Any]) -> dict[str,Any]: + identity=ctx.load_identity() or {} + node_id=str(identity.get("node_id") or "") + host_id=str(identity.get("node_host_id") or "") + if not node_id or not host_id: + raise OutboundAgentError("OUTBOUND_NODE_IDENTITY_MISSING") + crypto=ensure_node_keypair(ctx.state_dir,comment="framebridge-"+node_id.replace("-","")[:8]) + base={ + "schema":1, + "kind":str(kind), + "node_id":node_id, + "node_host_id":host_id, + "timestamp":time.time(), + "nonce":secrets.token_hex(16), + "payload":payload, + } + sig=sign_pairing_payload(crypto["private_key_path"],canonical(base)) + return { + **base, + "node_proof":{ + "format":"sshsig", + "namespace":"framebridge-pairing", + "signature_b64":sig, + }, + } + + +def broker_base(ctx: SeedContext) -> str: + config=ctx.load_config() or {} + pairing=config.get("pairing") or {} + base=str(pairing.get("broker_base_url") or "").rstrip("/") + if not base: + raise OutboundAgentError("OUTBOUND_BROKER_NOT_CONFIGURED") + return base + + +def sync_local_revocation(ctx: SeedContext) -> dict[str,Any]: + store=PairingStore(ctx.state_dir) + current=store.load() + if current.get("state")=="paired": + view=store.revoke() + elif current.get("state")=="revoked": + view=current + else: + raise OutboundAgentError("OUTBOUND_LOCAL_PAIRING_STATE_INVALID") + + config=ctx.load_config() or {} + pairing=config.get("pairing") or {} + pairing["state"]="revoked" + pairing["transport"]="broker_outbound" + if view.get("revoked_at"): + pairing["revoked_at"]=view.get("revoked_at") + config["pairing"]=pairing + config["connection_mode"]="PAIRING" + setup=config.get("setup") + if isinstance(setup,dict): + setup["pairing_state"]="revoked" + atomic_write_json(ctx.config_path,config) + + ctx.audit.append( + "pair.revoked", + "PASS", + request={"source":"broker"}, + result={"state":"revoked"}, + principal="pairing-broker", + ) + MemoryStore(ctx).append( + "recovery", + {"event":"pair.revoked","state":"revoked","source":"broker"}, + source="system", + ) + return {"ok":True,"state":"revoked"} + + +def execute_plan_operation(ctx: SeedContext, operation: str, payload: dict[str,Any]) -> dict[str,Any]: + if operation not in {"plan.import","plan.status","plan.apply","plan.rollback"}: + raise OutboundAgentError("OUTBOUND_PLAN_OPERATION_NOT_ALLOWED") + if not isinstance(payload,dict): + raise OutboundAgentError("OUTBOUND_PLAN_PAYLOAD_INVALID") + engine=PlanEngine(ctx) + + if operation=="plan.import": + plan=payload.get("plan") + if not isinstance(plan,dict): + raise OutboundAgentError("OUTBOUND_PLAN_DOCUMENT_INVALID") + return engine.import_document(plan) + + plan_id=str(payload.get("plan_id") or "") + if not plan_id: + raise OutboundAgentError("OUTBOUND_PLAN_ID_MISSING") + + if operation=="plan.status": + return engine.show(plan_id) + + expected=str(payload.get("plan_sha256") or "") + shown=engine.show(plan_id) + if shown.get("plan_sha256")!=expected: + raise OutboundAgentError("OUTBOUND_PLAN_SHA_MISMATCH") + + if operation=="plan.apply": + return engine.apply(plan_id) + return engine.rollback(plan_id) + + +def run_once(ctx: SeedContext | None=None) -> dict[str,Any]: + ctx=ctx or SeedContext() + store=PairingStore(ctx.state_dir) + local_pair=store.load() + if local_pair.get("state")=="paired" and local_pair.get("broker_confirm_required") is True: + try: + confirm_pairing_remote(state_dir=ctx.state_dir,record=local_pair) + except PairingClientError as exc: + raise OutboundAgentError("OUTBOUND_PAIR_CONFIRM_PENDING") from exc + local_pair=store.load() + config=ctx.load_config() or {} + pairing_cfg=config.get("pairing") or {} + pairing_cfg["broker_confirm_required"]=False + pairing_cfg["broker_confirmed_at"]=local_pair.get("broker_confirmed_at") + config["pairing"]=pairing_cfg + atomic_write_json(ctx.config_path,config) + config=ctx.load_config() or {} + pairing=config.get("pairing") or {} + if pairing.get("state")!="paired": + raise OutboundAgentError("OUTBOUND_NODE_NOT_PAIRED") + if store.load().get("broker_confirm_required") is True: + raise OutboundAgentError("OUTBOUND_PAIR_CONFIRM_PENDING") + base=broker_base(ctx) + poll=_post( + base+"/fbpair/v1/node/poll", + _signed_envelope(ctx,"node.poll",{}), + ) + if poll.get("ok") is not True: + raise OutboundAgentError("OUTBOUND_POLL_REJECTED") + req=poll.get("request") + if not isinstance(req,dict): + return {"ok":True,"idle":True} + rid=str(req.get("request_id") or "") + operation=str(req.get("operation") or "") + if operation: + payload=req.get("payload") or {} + try: + value=execute_plan_operation(ctx,operation,payload) + result={"ok":True,"operation":operation,"result":value} + ctx.audit.append( + "outbound."+operation, + "PASS", + request={"request_id":rid,"operation":operation}, + result={"ok":True}, + principal="paired-ai", + ) + except Exception as exc: + result={"ok":False,"operation":operation,"error":str(exc)} + ctx.audit.append( + "outbound."+operation, + "FAIL", + request={"request_id":rid,"operation":operation}, + result={"ok":False,"error":type(exc).__name__}, + principal="paired-ai", + ) + cap=operation + else: + cap=str(req.get("capability") or "") + args=req.get("args") or {} + if cap not in READ_CAPS: + result={"ok":False,"error":"CAPABILITY_NOT_ALLOWED","capability":cap} + elif not isinstance(args,dict): + result={"ok":False,"error":"CAPABILITY_ARGS_NOT_OBJECT","capability":cap} + else: + try: + value=execute_capability(cap,args) + result={"ok":True,"capability":cap,"result":value} + ctx.audit.append( + "outbound."+cap, + "PASS", + request={"request_id":rid,"capability":cap}, + result={"ok":True}, + principal="paired-ai", + ) + except Exception as exc: + result={"ok":False,"capability":cap,"error":str(exc)} + ctx.audit.append( + "outbound."+cap, + "FAIL", + request={"request_id":rid,"capability":cap}, + result={"ok":False,"error":type(exc).__name__}, + principal="paired-ai", + ) + response=_post( + base+"/fbpair/v1/node/result", + _signed_envelope( + ctx, + "node.result", + {"request_id":rid,"result":result}, + ), + ) + if response.get("ok") is not True: + raise OutboundAgentError("OUTBOUND_RESULT_REJECTED") + return {"ok":True,"idle":False,"request_id":rid,"capability":cap} + + +def run_forever(interval: float=1.0) -> None: + interval=max(0.5,min(float(interval),30.0)) + ctx=SeedContext() + while True: + try: + run_once(ctx) + except OutboundAgentError as exc: + if str(exc)=="OUTBOUND_NODE_REVOKED": + try: + sync_local_revocation(ctx) + except Exception: + pass + except Exception: + pass + time.sleep(interval) + + +def main() -> None: + import argparse + parser=argparse.ArgumentParser(prog="framebridge-outbound-agent") + sub=parser.add_subparsers(dest="command",required=True) + sub.add_parser("once") + run=sub.add_parser("run") + run.add_argument("--interval",type=float,default=1.0) + args=parser.parse_args() + if args.command=="once": + print(json.dumps(run_once(),sort_keys=True)) + else: + run_forever(args.interval) + + +if __name__=="__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py new file mode 100644 index 0000000..a475d2a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py @@ -0,0 +1,484 @@ +from __future__ import annotations + +import hashlib +import ipaddress +import json +import fcntl +import os +import secrets +import tempfile +from datetime import datetime, timedelta, timezone +from contextlib import contextmanager +from pathlib import Path +from typing import Any +from urllib.parse import urlparse + +FBPAIR_PROTOCOL = "FBPAIR/1" +PAIR_STATES = { + "not_paired", + "pair_requested", + "paired", + "denied", + "expired", + "revoked", +} +PROVIDERS = {"chatgpt", "ollama", "lmstudio", "other"} + + +class PairingError(RuntimeError): + pass + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def canonical(value: Any) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=True + ).encode("ascii") + + +def sha256_hex(value: Any) -> str: + return hashlib.sha256(canonical(value)).hexdigest() + + +def _is_local_ip(host: str) -> bool: + try: + addr=ipaddress.ip_address(str(host)) + except ValueError: + return False + if addr.is_loopback or addr.is_link_local: + return True + if addr.version==4: + return any(addr in net for net in ( + ipaddress.ip_network("10.0.0.0/8"), + ipaddress.ip_network("172.16.0.0/12"), + ipaddress.ip_network("192.168.0.0/16"), + )) + return addr in ipaddress.ip_network("fc00::/7") + + +def _https_or_loopback_http(uri: str, allow_insecure_dev: bool=False) -> bool: + try: + p = urlparse(str(uri)) + except Exception: + return False + if p.scheme == "https" and bool(p.netloc): + return True + if p.scheme == "http" and bool(p.netloc): + host=str(p.hostname or "") + if _is_local_ip(host): + return True + if allow_insecure_dev: + return True + return False + + +def create_pair_request( + *, + node_id: str, + node_label: str, + node_host_id: str, + public_jwk: dict[str, Any], + provider: str, + capability_manifest_sha256: str, +) -> dict[str, Any]: + provider = str(provider).strip().lower() + if provider not in PROVIDERS: + raise PairingError("PAIR_PROVIDER_UNSUPPORTED") + node_label=str(node_label).strip() + if not node_id or not node_host_id or not node_label or len(node_label)>253: + raise PairingError("PAIR_NODE_IDENTITY_REQUIRED") + manifest_sha = str(capability_manifest_sha256).strip().lower() + if len(manifest_sha) != 64 or any(c not in "0123456789abcdef" for c in manifest_sha): + raise PairingError("PAIR_CAPABILITY_MANIFEST_SHA_INVALID") + nonce = secrets.token_urlsafe(32) + request = { + "protocol": FBPAIR_PROTOCOL, + "schema": 1, + "node_id": str(node_id), + "node_label": node_label, + "node_host_id": str(node_host_id), + "public_jwk": public_jwk, + "provider": provider, + "capability_manifest_sha256": manifest_sha, + "node_nonce": nonce, + "requested_at": utc_now(), + } + request["request_sha256"] = sha256_hex(request) + return request + + +def attach_node_proof( + request: dict[str, Any], + *, + public_key_ssh: str, + signature_b64: str, +) -> dict[str, Any]: + if request.get("protocol") != FBPAIR_PROTOCOL: + raise PairingError("PAIR_REQUEST_PROTOCOL_INVALID") + if not str(public_key_ssh).startswith("ssh-ed25519 "): + raise PairingError("PAIR_PUBLIC_KEY_INVALID") + if not signature_b64: + raise PairingError("PAIR_NODE_PROOF_REQUIRED") + value=dict(request) + value["public_key_ssh"]=str(public_key_ssh) + value["node_proof"]={ + "format":"sshsig", + "namespace":"framebridge-pairing", + "signature_b64":str(signature_b64), + } + return value + + +def pairing_signing_payload(request: dict[str, Any]) -> bytes: + value=dict(request) + value.pop("node_proof",None) + value.pop("public_key_ssh",None) + return canonical(value) + + +def validate_device_authorization( + request: dict[str, Any], + response: dict[str, Any], + *, + allow_insecure_dev: bool=False, +) -> dict[str, Any]: + if not isinstance(response, dict): + raise PairingError("PAIR_DEVICE_RESPONSE_INVALID") + required = { + "pair_id", + "device_code", + "user_code", + "verification_uri", + "expires_in", + } + if not required.issubset(response): + raise PairingError("PAIR_DEVICE_RESPONSE_INCOMPLETE") + pair_id = str(response["pair_id"]) + device_code = str(response["device_code"]) + user_code = str(response["user_code"]) + verification_uri = str(response["verification_uri"]) + complete = str(response.get("verification_uri_complete") or "") + try: + expires_in = int(response["expires_in"]) + interval = int(response.get("interval", 5)) + except Exception as exc: + raise PairingError("PAIR_DEVICE_RESPONSE_INVALID") from exc + if not pair_id or len(device_code) < 24 or not user_code: + raise PairingError("PAIR_DEVICE_RESPONSE_INVALID") + if not _https_or_loopback_http(verification_uri, allow_insecure_dev): + raise PairingError("PAIR_VERIFICATION_URI_INVALID") + if complete and not _https_or_loopback_http(complete, allow_insecure_dev): + raise PairingError("PAIR_VERIFICATION_URI_COMPLETE_INVALID") + if not (30 <= expires_in <= 1800): + raise PairingError("PAIR_EXPIRES_INVALID") + if not (1 <= interval <= 60): + raise PairingError("PAIR_INTERVAL_INVALID") + try: + requested_dt=datetime.fromisoformat( + str(request["requested_at"]).replace("Z","+00:00") + ).astimezone(timezone.utc) + except Exception as exc: + raise PairingError("PAIR_REQUEST_TIME_INVALID") from exc + expires_at=(requested_dt+timedelta(seconds=expires_in)).isoformat() + + return { + "protocol": FBPAIR_PROTOCOL, + "schema": 1, + "state": "pair_requested", + "pair_id": pair_id, + "request_sha256": request["request_sha256"], + "node_id": request["node_id"], + "node_label": request["node_label"], + "node_host_id": request["node_host_id"], + "provider": request["provider"], + "device_code": device_code, + "device_code_sha256": hashlib.sha256(device_code.encode("utf-8")).hexdigest(), + "user_code": user_code, + "verification_uri": verification_uri, + "verification_uri_complete": complete or None, + "expires_in": expires_in, + "expires_at": expires_at, + "interval": interval, + "requested_at": request["requested_at"], + "last_poll_at": None, + } + + +def public_pairing_view(record: dict[str, Any]) -> dict[str, Any]: + value = dict(record) + value.pop("device_code", None) + value.pop("account_subject", None) + value.pop("provider_access_token", None) + value.pop("provider_refresh_token", None) + return value + + +def pairing_qr_payload(record: dict[str, Any]) -> str: + if record.get("state") != "pair_requested": + raise PairingError("PAIR_NOT_PENDING") + return str( + record.get("verification_uri_complete") + or record.get("verification_uri") + or "" + ) + + +def validate_pair_receipt( + pending: dict[str, Any], + receipt: dict[str, Any], +) -> dict[str, Any]: + if pending.get("state") != "pair_requested": + raise PairingError("PAIR_NOT_PENDING") + if not isinstance(receipt, dict): + raise PairingError("PAIR_RECEIPT_INVALID") + checks = { + "protocol": receipt.get("protocol") == FBPAIR_PROTOCOL, + "schema": receipt.get("schema") == 1, + "state": receipt.get("state") == "paired", + "pair_id": receipt.get("pair_id") == pending.get("pair_id"), + "request_sha256": receipt.get("request_sha256") == pending.get("request_sha256"), + "node_id": receipt.get("node_id") == pending.get("node_id"), + "node_label": receipt.get("node_label") == pending.get("node_label"), + "node_host_id": receipt.get("node_host_id") == pending.get("node_host_id"), + "provider": receipt.get("provider") == pending.get("provider"), + } + if not all(checks.values()): + raise PairingError("PAIR_RECEIPT_BINDING_MISMATCH") + broker_issuer = str(receipt.get("broker_issuer") or "") + account_binding_id = str(receipt.get("account_binding_id") or "") + grant_id = str(receipt.get("grant_id") or "") + if ( + not broker_issuer + or not account_binding_id + or len(account_binding_id)>160 + or not grant_id + or len(grant_id)>160 + ): + raise PairingError("PAIR_ACCOUNT_BINDING_MISSING") + return { + "protocol": FBPAIR_PROTOCOL, + "schema": 1, + "state": "paired", + "pair_id": pending["pair_id"], + "request_sha256": pending["request_sha256"], + "node_id": pending["node_id"], + "node_label": pending["node_label"], + "node_host_id": pending["node_host_id"], + "provider": pending["provider"], + "broker_issuer": broker_issuer, + "account_binding_id": account_binding_id, + "grant_id": grant_id, + "paired_at": str(receipt.get("paired_at") or utc_now()), + "scopes": sorted({str(x) for x in (receipt.get("scopes") or [])}), + "revocation_id": str(receipt.get("revocation_id") or ""), + "node_grant_binding": str(receipt.get("node_grant_binding") or "proof-of-possession-required"), + } + + +class PairingStore: + def __init__(self, state_dir: Path): + self.path = Path(state_dir) / "pairing.json" + self.lock_path = Path(state_dir) / ".pairing.lock" + + @contextmanager + def _exclusive(self): + self.lock_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + flags=os.O_RDWR|os.O_CREAT|getattr(os,"O_NOFOLLOW",0) + fd=os.open(self.lock_path,flags,0o600) + try: + fcntl.flock(fd,fcntl.LOCK_EX) + yield + finally: + try: + fcntl.flock(fd,fcntl.LOCK_UN) + finally: + os.close(fd) + + def _load_unlocked(self) -> dict[str, Any]: + if not self.path.is_file(): + return {"protocol": FBPAIR_PROTOCOL, "schema": 1, "state": "not_paired"} + value = json.loads(self.path.read_text(encoding="utf-8")) + if not isinstance(value, dict) or value.get("state") not in PAIR_STATES: + raise PairingError("PAIR_STATE_INVALID") + return value + + def load(self) -> dict[str, Any]: + # Atomic replacement makes a lock-free reader see old-or-new complete JSON. + return self._load_unlocked() + + def _save_unlocked(self, value: dict[str, Any]) -> None: + if value.get("state") not in PAIR_STATES: + raise PairingError("PAIR_STATE_INVALID") + self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + fd, tmp = tempfile.mkstemp(prefix=".pairing.", dir=str(self.path.parent)) + try: + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(value, handle, indent=2, sort_keys=True) + handle.write("\n") + handle.flush() + os.fsync(handle.fileno()) + os.chmod(tmp, 0o600) + os.replace(tmp, self.path) + finally: + if os.path.exists(tmp): + os.unlink(tmp) + + def save(self, value: dict[str, Any]) -> None: + with self._exclusive(): + self._save_unlocked(value) + + def begin(self, pending: dict[str, Any]) -> dict[str, Any]: + with self._exclusive(): + current = self._load_unlocked() + if current.get("state") == "paired": + raise PairingError("PAIR_ALREADY_PAIRED") + if current.get("state") == "pair_requested": + raise PairingError("PAIR_REQUEST_ALREADY_PENDING") + self._save_unlocked(pending) + return public_pairing_view(pending) + + def update_pending_fields(self, pair_id: str, fields: dict[str, Any]) -> dict[str, Any]: + pair_id=str(pair_id or "") + if not pair_id: + raise PairingError("PAIR_ID_REQUIRED") + with self._exclusive(): + current=self._load_unlocked() + if current.get("state")!="pair_requested" or str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current.update(dict(fields or {})) + self._save_unlocked(current) + return current + + def complete(self, receipt: dict[str, Any]) -> dict[str, Any]: + with self._exclusive(): + current = self._load_unlocked() + paired = validate_pair_receipt(current, receipt) + paired["broker_base_url"]=str(current.get("broker_base_url") or "") + paired["broker_confirm_required"]=True + self._save_unlocked(paired) + return public_pairing_view(paired) + + def invalidate_unconfirmed_pair(self, pair_id: str, broker_state: str) -> dict[str, Any]: + pair_id=str(pair_id or "") + broker_state=str(broker_state or "unknown").lower() + with self._exclusive(): + current=self._load_unlocked() + if current.get("state")!="paired" or current.get("broker_confirm_required") is not True or str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current["state"]="expired" + current["expired_at"]=utc_now() + current["expiration_reason"]="broker_confirm_"+broker_state + current["requires_new_local_action"]=True + current["broker_confirm_required"]=False + current["broker_confirm_state"]=broker_state + self._save_unlocked(current) + return current + + def mark_broker_confirmed(self, pair_id: str, result: dict[str, Any]) -> dict[str, Any]: + pair_id=str(pair_id or "") + with self._exclusive(): + current=self._load_unlocked() + if current.get("state")!="paired" or str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current["broker_confirm_required"]=False + current["broker_confirmed_at"]=utc_now() + current["broker_confirm_state"]=str((result or {}).get("state") or "paired") + self._save_unlocked(current) + return current + + def transition_pending(self, pair_id: str, new_state: str, fields: dict[str, Any] | None=None) -> dict[str, Any]: + pair_id=str(pair_id or "") + if new_state not in {"denied","expired"}: + raise PairingError("PAIR_TRANSITION_STATE_INVALID") + with self._exclusive(): + current=self._load_unlocked() + if current.get("state")!="pair_requested" or str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current.pop("device_code",None) + current["state"]=new_state + current.update(dict(fields or {})) + self._save_unlocked(current) + return current + + def cancel_pending(self) -> dict[str, Any]: + with self._exclusive(): + current = self._load_unlocked() + if current.get("state") != "pair_requested": + raise PairingError("PAIR_NOT_PENDING") + current=dict(current) + current.pop("device_code", None) + current["state"] = "revoked" + current["revoked_at"] = utc_now() + current["revocation_reason"] = "local_operator_cancelled" + if current.get("pair_id") and current.get("broker_base_url"): + current["broker_cancel_required"]=True + current["broker_cancel_reason"]="local_operator_cancelled" + self._save_unlocked(current) + return public_pairing_view(current) + + def invalidate_pending(self, reason: str) -> dict[str, Any]: + reason=str(reason or "").strip().lower() + if reason not in {"network_change","service_stop","reconcile","window_expired","window_closed"}: + raise PairingError("PAIR_INVALIDATION_REASON_INVALID") + with self._exclusive(): + current=self._load_unlocked() + if current.get("state") != "pair_requested": + raise PairingError("PAIR_NOT_PENDING") + current=dict(current) + current.pop("device_code",None) + current["state"]="expired" + current["expired_at"]=utc_now() + current["expiration_reason"]=reason + current["requires_new_local_action"]=True + if current.get("pair_id") and current.get("broker_base_url"): + current["broker_cancel_required"]=True + current["broker_cancel_reason"]=reason + self._save_unlocked(current) + return public_pairing_view(current) + + def mark_broker_cancelled(self, pair_id: str, result: dict[str, Any]) -> dict[str, Any]: + pair_id=str(pair_id or "") + with self._exclusive(): + current=self._load_unlocked() + if str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current["broker_cancel_required"]=False + current["broker_cancel_state"]=str((result or {}).get("state") or "cancelled") + current["broker_cancelled_at"]=utc_now() + current.pop("broker_cancel_last_error",None) + self._save_unlocked(current) + return current + + def note_broker_cancel_error(self, pair_id: str, error: str) -> dict[str, Any]: + pair_id=str(pair_id or "") + with self._exclusive(): + current=self._load_unlocked() + if str(current.get("pair_id") or "")!=pair_id: + return current + current=dict(current) + current["broker_cancel_required"]=True + current["broker_cancel_last_error"]=str(error or "PAIR_BROKER_CANCEL_FAILED")[:128] + current["broker_cancel_attempts"]=int(current.get("broker_cancel_attempts") or 0)+1 + current["broker_cancel_last_attempt_at"]=utc_now() + self._save_unlocked(current) + return current + + def revoke(self) -> dict[str, Any]: + with self._exclusive(): + current = self._load_unlocked() + if current.get("state") != "paired": + raise PairingError("PAIR_NOT_PAIRED") + current=dict(current) + current["state"] = "revoked" + current["revoked_at"] = utc_now() + self._save_unlocked(current) + return public_pairing_view(current) + diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py new file mode 100644 index 0000000..49a8d26 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py @@ -0,0 +1,380 @@ +from __future__ import annotations + +import hashlib +import ipaddress +import json +import secrets +import ssl +import time +from datetime import datetime, timezone +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any, Callable + +from .node_identity import ensure_node_keypair, sign_pairing_payload +from .pairing import ( + PairingError, + PairingStore, + attach_node_proof, + create_pair_request, + pairing_signing_payload, + public_pairing_view, + validate_device_authorization, +) + + +class PairingClientError(RuntimeError): + pass + + +def _is_local_ip(host: str) -> bool: + try: + addr=ipaddress.ip_address(str(host)) + except ValueError: + return False + if addr.is_loopback or addr.is_link_local: + return True + if addr.version==4: + return any(addr in net for net in ( + ipaddress.ip_network("10.0.0.0/8"), + ipaddress.ip_network("172.16.0.0/12"), + ipaddress.ip_network("192.168.0.0/16"), + )) + return addr in ipaddress.ip_network("fc00::/7") + + +def _broker_base_ok(url: str, allow_insecure_dev: bool) -> bool: + try: + p=urllib.parse.urlparse(str(url)) + except Exception: + return False + if p.scheme=="https" and p.netloc: + return True + if p.scheme=="http" and p.netloc: + host=str(p.hostname or "") + if _is_local_ip(host): + return True + if allow_insecure_dev: + return True + return False + + +def _endpoint(base: str, path: str) -> str: + return str(base).rstrip("/") + "/" + path.lstrip("/") + + +def post_json( + url: str, + value: dict[str, Any], + *, + timeout: float=10.0, + opener: Callable[..., Any] | None=None, +) -> dict[str, Any]: + data=json.dumps( + value, sort_keys=True, separators=(",",":"), ensure_ascii=True + ).encode("utf-8") + req=urllib.request.Request( + url, + data=data, + method="POST", + headers={ + "Content-Type":"application/json", + "Accept":"application/json", + "User-Agent":"FrameBridge-Linux-Core/0.5", + }, + ) + try: + if opener is None: + with urllib.request.urlopen(req,timeout=timeout) as resp: + raw=resp.read(1024*1024) + else: + raw=opener(req,timeout=timeout) + if hasattr(raw,"read"): + raw=raw.read(1024*1024) + result=json.loads(bytes(raw).decode("utf-8")) + except urllib.error.HTTPError as exc: + try: + raw=exc.read(1024*1024) + result=json.loads(bytes(raw).decode("utf-8")) + except Exception as parse_exc: + raise PairingClientError("PAIR_BROKER_RESPONSE_INVALID") from parse_exc + if not isinstance(result,dict): + raise PairingClientError("PAIR_BROKER_RESPONSE_INVALID") + return result + except (urllib.error.URLError,TimeoutError,OSError) as exc: + raise PairingClientError("PAIR_BROKER_UNREACHABLE") from exc + except Exception as exc: + raise PairingClientError("PAIR_BROKER_RESPONSE_INVALID") from exc + if not isinstance(result,dict): + raise PairingClientError("PAIR_BROKER_RESPONSE_INVALID") + return result + + +def start_pairing( + *, + state_dir: Path, + node_id: str, + node_label: str, + node_host_id: str, + public_jwk: dict[str, Any], + provider: str, + capability_manifest_sha256: str, + broker_base_url: str, + allow_insecure_dev: bool=False, + poster: Callable[..., dict[str,Any]]=post_json, +) -> dict[str,Any]: + if not _broker_base_ok(broker_base_url,allow_insecure_dev): + raise PairingClientError("PAIR_BROKER_URL_INVALID") + crypto=ensure_node_keypair(Path(state_dir),comment="framebridge-"+str(node_id)[:8]) + request=create_pair_request( + node_id=node_id, + node_label=node_label, + node_host_id=node_host_id, + public_jwk=public_jwk, + provider=provider, + capability_manifest_sha256=capability_manifest_sha256, + ) + signature=sign_pairing_payload( + crypto["private_key_path"], + pairing_signing_payload(request), + ) + signed=attach_node_proof( + request, + public_key_ssh=crypto["public_key_ssh"], + signature_b64=signature, + ) + response=poster( + _endpoint(broker_base_url,"fbpair/v1/device_authorization"), + signed, + ) + pending=validate_device_authorization( + request,response,allow_insecure_dev=allow_insecure_dev + ) + pending["broker_base_url"]=str(broker_base_url).rstrip("/") + PairingStore(Path(state_dir)).begin(pending) + return public_pairing_view(pending) + + +def confirm_pairing_remote( + *, + state_dir: Path, + allow_insecure_dev: bool=False, + poster: Callable[..., dict[str,Any]]=post_json, + record: dict[str,Any] | None=None, +) -> dict[str,Any]: + store=PairingStore(Path(state_dir)) + current=dict(record) if isinstance(record,dict) else store.load() + if current.get("state")!="paired": + raise PairingClientError("PAIR_CONFIRM_STATE_INVALID") + pair_id=str(current.get("pair_id") or "") + node_id=str(current.get("node_id") or "") + node_host_id=str(current.get("node_host_id") or "") + request_sha256=str(current.get("request_sha256") or "") + broker=str(current.get("broker_base_url") or "") + if not pair_id or not node_id or not node_host_id or len(request_sha256)!=64: + raise PairingClientError("PAIR_CONFIRM_STATE_INCOMPLETE") + if not _broker_base_ok(broker,allow_insecure_dev): + raise PairingClientError("PAIR_BROKER_URL_INVALID") + crypto=ensure_node_keypair(Path(state_dir),comment="framebridge-"+node_id.replace("-","")[:8]) + base={ + "protocol":"FBPAIR/1", + "schema":1, + "operation":"pair.confirm", + "pair_id":pair_id, + "request_sha256":request_sha256, + "node_id":node_id, + "node_host_id":node_host_id, + "confirm_nonce":secrets.token_urlsafe(24), + "confirmed_at":datetime.now(timezone.utc).isoformat(), + } + signature=sign_pairing_payload(crypto["private_key_path"],pairing_signing_payload(base)) + signed=attach_node_proof( + base,public_key_ssh=crypto["public_key_ssh"],signature_b64=signature + ) + response=poster(_endpoint(broker,"fbpair/v1/confirm"),signed) + if isinstance(response,dict) and response.get("ok") is not True and str(response.get("state") or "") in {"cancelled","denied","expired"}: + store.invalidate_unconfirmed_pair(pair_id,str(response.get("state"))) + return response + if not isinstance(response,dict) or response.get("ok") is not True or response.get("state")!="paired": + error=str((response or {}).get("error") or "pair_confirm_failed").upper() if isinstance(response,dict) else "PAIR_CONFIRM_RESPONSE_INVALID" + raise PairingClientError("PAIR_BROKER_"+error) + store.mark_broker_confirmed(pair_id,response) + return response + + +def cancel_pairing_remote( + *, + state_dir: Path, + reason: str, + allow_insecure_dev: bool=False, + poster: Callable[..., dict[str,Any]]=post_json, + record: dict[str,Any] | None=None, +) -> dict[str,Any]: + reason=str(reason or "").strip().lower() + if reason not in { + "network_change","service_stop","reconcile","window_expired", + "window_closed","local_operator_cancelled", + }: + raise PairingClientError("PAIR_CANCEL_REASON_INVALID") + store=PairingStore(Path(state_dir)) + current=dict(record) if isinstance(record,dict) else store.load() + pair_id=str(current.get("pair_id") or "") + node_id=str(current.get("node_id") or "") + node_host_id=str(current.get("node_host_id") or "") + broker=str(current.get("broker_base_url") or "") + if not pair_id or not node_id or not node_host_id: + raise PairingClientError("PAIR_CANCEL_STATE_INCOMPLETE") + if not _broker_base_ok(broker,allow_insecure_dev): + raise PairingClientError("PAIR_BROKER_URL_INVALID") + crypto=ensure_node_keypair(Path(state_dir),comment="framebridge-"+node_id.replace("-","")[:8]) + base={ + "protocol":"FBPAIR/1", + "schema":1, + "operation":"pair.cancel", + "pair_id":pair_id, + "node_id":node_id, + "node_host_id":node_host_id, + "reason":reason, + "cancel_nonce":secrets.token_urlsafe(24), + "cancelled_at":datetime.now(timezone.utc).isoformat(), + } + signature=sign_pairing_payload(crypto["private_key_path"],pairing_signing_payload(base)) + signed=attach_node_proof( + base, + public_key_ssh=crypto["public_key_ssh"], + signature_b64=signature, + ) + response=poster(_endpoint(broker,"fbpair/v1/cancel"),signed) + if not isinstance(response,dict): + raise PairingClientError("PAIR_CANCEL_RESPONSE_INVALID") + if response.get("ok") is True and str(response.get("state") or "") in { + "cancelled","expired","denied", + }: + return response + error=str(response.get("error") or "pair_cancel_failed").upper() + raise PairingClientError("PAIR_BROKER_"+error) + + +def poll_pairing( + *, + state_dir: Path, + allow_insecure_dev: bool=False, + poster: Callable[..., dict[str,Any]]=post_json, +) -> dict[str,Any]: + store=PairingStore(Path(state_dir)) + pending=store.load() + if pending.get("state")=="paired" and pending.get("broker_confirm_required") is True: + try: + confirm_pairing_remote( + state_dir=Path(state_dir),allow_insecure_dev=allow_insecure_dev, + poster=poster,record=pending, + ) + except PairingClientError: + result=public_pairing_view(store.load()) + result["broker_status"]="confirm_pending" + return result + return public_pairing_view(store.load()) + if pending.get("state")!="pair_requested": + raise PairingClientError("PAIR_NOT_PENDING") + pair_id=str(pending.get("pair_id") or "") + broker=str(pending.get("broker_base_url") or "") + if not _broker_base_ok(broker,allow_insecure_dev): + raise PairingClientError("PAIR_BROKER_URL_INVALID") + + now=datetime.now(timezone.utc) + try: + expires=datetime.fromisoformat( + str(pending.get("expires_at") or "").replace("Z","+00:00") + ).astimezone(timezone.utc) + except Exception as exc: + raise PairingClientError("PAIR_EXPIRY_STATE_INVALID") from exc + if now>=expires: + current=store.transition_pending( + pair_id,"expired", + {"expired_at":now.isoformat(),"expiration_reason":"pairing_timeout"}, + ) + return public_pairing_view(current) + + last=pending.get("last_poll_at") + interval=max(1,int(pending.get("interval") or 5)) + if last: + try: + last_dt=datetime.fromisoformat( + str(last).replace("Z","+00:00") + ).astimezone(timezone.utc) + except Exception as exc: + raise PairingClientError("PAIR_POLL_STATE_INVALID") from exc + remaining=interval-(now-last_dt).total_seconds() + if remaining>0: + result=public_pairing_view(pending) + result["broker_status"]="local_poll_interval" + result["retry_after"]=max(1,int(remaining+0.999)) + return result + + pending=store.update_pending_fields(pair_id,{"last_poll_at":now.isoformat()}) + if pending.get("state")!="pair_requested": + return public_pairing_view(pending) + device_code=str(pending.get("device_code") or "") + if not device_code: + return public_pairing_view(pending) + + response=poster( + _endpoint(broker,"fbpair/v1/token"), + { + "protocol":"FBPAIR/1", + "pair_id":pair_id, + "device_code":device_code, + }, + ) + + # A network monitor may have invalidated this pair while the POST was in flight. + current=store.load() + if current.get("state")!="pair_requested" or str(current.get("pair_id") or "")!=pair_id: + return public_pairing_view(current) + + error=str(response.get("error") or "") + if error in {"authorization_pending","slow_down"}: + if error=="slow_down": + current=store.update_pending_fields( + pair_id,{"interval":min(60,int(current.get("interval") or 5)+5)} + ) + result=public_pairing_view(current) + result["broker_status"]=error + return result + if error=="access_denied": + current=store.transition_pending( + pair_id,"denied",{"denied_at":datetime.now(timezone.utc).isoformat()} + ) + return public_pairing_view(current) + if error in {"expired_token","expired"}: + current=store.transition_pending( + pair_id,"expired", + {"expired_at":datetime.now(timezone.utc).isoformat(),"expiration_reason":"broker_expired"}, + ) + return public_pairing_view(current) + if error: + raise PairingClientError("PAIR_BROKER_"+error.upper()) + try: + store.complete(response) + except PairingError as exc: + # If local invalidation won the race, do not revive the pairing. + current=store.load() + if current.get("state")!="pair_requested": + return public_pairing_view(current) + raise PairingClientError(str(exc)) from exc + paired=store.load() + try: + confirm_pairing_remote( + state_dir=Path(state_dir), + allow_insecure_dev=allow_insecure_dev, + poster=poster, + record=paired, + ) + except PairingClientError: + current=public_pairing_view(store.load()) + current["broker_status"]="confirm_pending" + return current + return public_pairing_view(store.load()) + diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py new file mode 100644 index 0000000..e25b0b5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from typing import Any + +from .network_security import PAIRING_WINDOW_TTL_SECONDS, pairing_policy +from .product_core import ProductError + +SUDO = "/usr/bin/sudo" +PRIV_HELPER = "/usr/local/libexec/framebridge-seed-priv" +MIN_TTL = 30 +MAX_TTL = PAIRING_WINDOW_TTL_SECONDS + + +def _decode_helper(proc: subprocess.CompletedProcess[str]) -> dict[str, Any]: + try: + value=json.loads(proc.stdout or "{}") + except Exception as exc: + raise ProductError("PAIRING_WINDOW_HELPER_INVALID_JSON") from exc + if not isinstance(value,dict): + raise ProductError("PAIRING_WINDOW_HELPER_INVALID_RESPONSE") + if proc.returncode!=0 or value.get("ok") is not True: + raise ProductError(str(value.get("error") or "PAIRING_WINDOW_HELPER_FAILED")) + return value + + +def _run_helper(args: list[str], *, interactive: bool=True) -> dict[str, Any]: + argv=[SUDO] + if not interactive: + argv.append("-n") + argv.extend([PRIV_HELPER,*args]) + proc=subprocess.run( + argv, + text=True, + stdout=subprocess.PIPE, + stderr=None, + check=False, + shell=False, + ) + return _decode_helper(proc) + + +def open_pairing_window(port: int=18765, ttl_seconds: int=PAIRING_WINDOW_TTL_SECONDS) -> dict[str, Any]: + if not sys.stdin.isatty(): + raise ProductError("PAIRING_WINDOW_LOCAL_TTY_REQUIRED") + if isinstance(ttl_seconds,bool) or not isinstance(ttl_seconds,int) or not (MIN_TTL <= ttl_seconds <= MAX_TTL): + raise ProductError("PAIRING_WINDOW_TTL_INVALID") + policy=pairing_policy(port) + if policy.get("ok") is not True: + raise ProductError(str(policy.get("reason") or "PAIRING_WINDOW_POLICY_BLOCKED")) + candidate=policy.get("candidate") or {} + required=("interface","subnet","address","network_fingerprint") + if not all(candidate.get(k) for k in required): + raise ProductError("PAIRING_WINDOW_CANDIDATE_INCOMPLETE") + result=_run_helper([ + "pairing-window-open", + "--interface",str(candidate["interface"]), + "--source-subnet",str(candidate["subnet"]), + "--destination-address",str(candidate["address"]), + "--port",str(int(port)), + "--network-fingerprint",str(candidate["network_fingerprint"]), + "--ttl",str(int(ttl_seconds)), + ]) + result["policy_state"]=policy.get("state") + result["transport"]="LAN_HTTP" + result["listen_url_base"]=f"http://{candidate['address']}:{int(port)}" + return result + + +def pairing_window_status(*, non_interactive: bool=False) -> dict[str, Any]: + return _run_helper(["pairing-window-status"],interactive=not non_interactive) + + +def close_pairing_window(reason: str="user", *, non_interactive: bool=False) -> dict[str, Any]: + return _run_helper(["pairing-window-close","--reason",str(reason)],interactive=not non_interactive) diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py new file mode 100644 index 0000000..a30054e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py @@ -0,0 +1,1388 @@ +from __future__ import annotations + +import hashlib +import ipaddress +import json +import os +import re +import subprocess +import sys +import tempfile +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from .product_core import ProductError +from .seed_core import SeedContext +from .memory_store import MemoryStore + +PLAN_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +STEP_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +PACKAGE_RE = re.compile(r"^[a-z0-9][a-z0-9+.-]{0,127}$") +REMOTE_APPROVAL_SOURCE = "REMOTE_HUMAN" +TOTP_APPROVAL_SOURCE = "REMOTE_HUMAN_TOTP" +REMOTE_CHALLENGE_RE = re.compile(r"^[a-f0-9]{32}$") +REMOTE_KEY_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +SERVICE_RE = re.compile(r"^[A-Za-z0-9@_.-]{1,128}$") +TIMEZONE_RE = re.compile(r"^[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*$") +SERVICE_ACTIONS = {"start", "stop", "restart", "enable", "disable"} +IFACE_RE = re.compile(r"^[A-Za-z0-9_.:-]{1,32}$") +FORWARD_PROTOCOLS = {"any", "tcp", "udp"} +FORWARD_SOURCE_NETS = ( + ipaddress.ip_network("10.0.0.0/8"), + ipaddress.ip_network("172.16.0.0/12"), + ipaddress.ip_network("192.168.0.0/16"), + ipaddress.ip_network("100.64.0.0/10"), +) +ADMIN_R3_CORE_CAPABILITIES = { + "package.refresh", + "package.upgrade", + "system.file.write_text", + "system.directory.ensure", + "system.file.mode", + "system.daemon_reload", + "network.forward", +} +PRIVILEGED_CHANGE_CAPABILITIES = { + "package.install", + "package.remove", + "service.manage", + "system.hostname", + "system.timezone", +} | ADMIN_R3_CORE_CAPABILITIES +ALLOWED_CHANGE_CAPABILITIES = {"workspace.write_text"} | PRIVILEGED_CHANGE_CAPABILITIES +MAX_STEPS = 20 +MAX_TEXT_BYTES = 65536 +PRIV_HELPER = "/usr/local/libexec/framebridge-seed-priv" +SUDO = "/usr/bin/sudo" + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def canonical(value: Any) -> bytes: + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + ).encode("utf-8") + + +def sha256(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _valid_hostname(value: str) -> bool: + value = str(value or "").strip() + if not value or len(value) > 253 or value.endswith(".") or ".." in value: + return False + for label in value.split("."): + if not (1 <= len(label) <= 63): + return False + if label[0] == "-" or label[-1] == "-": + return False + if re.fullmatch(r"[A-Za-z0-9-]+", label) is None: + return False + return True + +def _valid_timezone(value: str) -> bool: + value = str(value or "").strip() + if not value or len(value) > 128 or value.startswith("/"): + return False + if any(part in ("", ".", "..") for part in Path(value).parts): + return False + return TIMEZONE_RE.fullmatch(value) is not None + +def _args_sha(args: dict[str, Any]) -> str: + return sha256(canonical(args)) + + +def _normalize_forward_ipv4_network(value: str, *, source: bool=False) -> str: + try: + net=ipaddress.ip_network(str(value), strict=False) + except ValueError: + raise ProductError("PLAN_NETWORK_CIDR_INVALID") from None + if net.version != 4: + raise ProductError("PLAN_NETWORK_IPV4_REQUIRED") + if net.is_multicast or net.is_loopback or net.is_link_local: + raise ProductError("PLAN_NETWORK_CIDR_UNSAFE") + if source and not any(net.subnet_of(allowed) for allowed in FORWARD_SOURCE_NETS): + raise ProductError("PLAN_NETWORK_FORWARD_SOURCE_NOT_PRIVATE") + return str(net) + + +def _normalize_network_forward_args(args: dict[str, Any]) -> dict[str, Any]: + required={ + "ingress_interface","egress_interface","source_subnet","destination_subnet", + "protocol","destination_port","nat","lifetime", + } + if set(args) != required: + raise ProductError("PLAN_NETWORK_FORWARD_ARGS_INVALID") + ingress=str(args.get("ingress_interface") or "").strip() + egress=str(args.get("egress_interface") or "").strip() + if not IFACE_RE.fullmatch(ingress) or not IFACE_RE.fullmatch(egress): + raise ProductError("PLAN_NETWORK_FORWARD_INTERFACE_INVALID") + if ingress == egress: + raise ProductError("PLAN_NETWORK_FORWARD_INTERFACES_MUST_DIFFER") + source=_normalize_forward_ipv4_network(str(args.get("source_subnet") or ""),source=True) + destination=_normalize_forward_ipv4_network(str(args.get("destination_subnet") or ""),source=False) + protocol=str(args.get("protocol") or "").lower() + if protocol not in FORWARD_PROTOCOLS: + raise ProductError("PLAN_NETWORK_FORWARD_PROTOCOL_INVALID") + port=args.get("destination_port") + if isinstance(port,bool) or not isinstance(port,int) or not (0 <= port <= 65535): + raise ProductError("PLAN_NETWORK_FORWARD_PORT_INVALID") + if protocol=="any" and port!=0: + raise ProductError("PLAN_NETWORK_FORWARD_PORT_REQUIRES_PROTOCOL") + nat=args.get("nat") + if not isinstance(nat,bool): + raise ProductError("PLAN_NETWORK_FORWARD_NAT_INVALID") + lifetime=str(args.get("lifetime") or "") + if lifetime!="runtime": + raise ProductError("PLAN_NETWORK_FORWARD_LIFETIME_INVALID") + return { + "ingress_interface":ingress, + "egress_interface":egress, + "source_subnet":source, + "destination_subnet":destination, + "protocol":protocol, + "destination_port":port, + "nat":nat, + "lifetime":"runtime", + } + + +MODE_RE = re.compile(r"^[0-7]{3,4}$") +SYSTEM_PATH_FORBIDDEN_PREFIXES = ( + "/proc", "/sys", "/dev", "/run", "/boot", + "/var/lib/framebridge-seed", +) +SYSTEM_PATH_FORBIDDEN_EXACT = { + "/etc/shadow", "/etc/gshadow", "/etc/sudoers", +} + + +def _valid_system_path_arg(value: str) -> bool: + value = str(value or "").strip() + if not value or len(value) > 4096 or not value.startswith("/"): + return False + path = Path(value) + if any(part in ("", ".", "..") for part in path.parts[1:]): + return False + normalized = os.path.normpath(value) + if normalized in SYSTEM_PATH_FORBIDDEN_EXACT: + return False + if normalized.startswith("/etc/sudoers.d/"): + return False + if normalized.startswith("/etc/ssl/private/"): + return False + if normalized.startswith("/root/.ssh/"): + return False + if normalized.startswith("/etc/ssh/ssh_host_"): + return False + for prefix in SYSTEM_PATH_FORBIDDEN_PREFIXES: + if normalized == prefix or normalized.startswith(prefix + "/"): + return False + return True + + +def _valid_mode_arg(value: str) -> bool: + value = str(value or "").strip() + if MODE_RE.fullmatch(value) is None: + return False + try: + mode = int(value, 8) + except ValueError: + return False + return 0 <= mode <= 0o7777 + + +class PlanEngine: + def __init__(self, seed: SeedContext): + self.seed = seed + self.plans_dir = seed.state_dir / "plans" + self.rollback_dir = seed.state_dir / "rollback" + self.workspace = seed.state_dir / "workspace" + self.plans_dir.mkdir(parents=True, exist_ok=True) + self.rollback_dir.mkdir(parents=True, exist_ok=True) + self.workspace.mkdir(parents=True, exist_ok=True) + + def _plan_path(self, plan_id: str) -> Path: + if not PLAN_ID_RE.fullmatch(plan_id): + raise ProductError("PLAN_ID_INVALID") + return self.plans_dir / (plan_id + ".json") + + def _load_record(self, plan_id: str) -> dict[str, Any]: + path = self._plan_path(plan_id) + if not path.is_file(): + raise ProductError("PLAN_NOT_FOUND") + try: + value = json.loads(path.read_text(encoding="utf-8")) + except Exception: + raise ProductError("PLAN_RECORD_INVALID_JSON") + if not isinstance(value, dict): + raise ProductError("PLAN_RECORD_NOT_OBJECT") + return value + + def _write_record(self, plan_id: str, record: dict[str, Any]) -> None: + path = self._plan_path(plan_id) + tmp = path.with_name("." + path.name + ".tmp") + tmp.write_text( + json.dumps(record, indent=2, sort_keys=True, ensure_ascii=True) + "\n", + encoding="utf-8", + ) + os.chmod(tmp, 0o600) + os.replace(tmp, path) + + def _validate_workspace_path(self, raw: str) -> Path: + raw = str(raw or "") + if not raw or len(raw) > 240: + raise ProductError("WORKSPACE_PATH_INVALID") + p = Path(raw) + if p.is_absolute(): + raise ProductError("WORKSPACE_PATH_ABSOLUTE_FORBIDDEN") + if any(part in ("", ".", "..") for part in p.parts): + raise ProductError("WORKSPACE_PATH_TRAVERSAL") + target = (self.workspace / p).resolve(strict=False) + root = self.workspace.resolve(strict=False) + try: + if os.path.commonpath([str(root), str(target)]) != str(root): + raise ProductError("WORKSPACE_PATH_ESCAPE") + except ValueError: + raise ProductError("WORKSPACE_PATH_ESCAPE") + cur = root + for part in p.parts[:-1]: + cur = cur / part + if cur.exists() and cur.is_symlink(): + raise ProductError("WORKSPACE_SYMLINK_PARENT_BLOCKED") + if target.exists() and target.is_symlink(): + raise ProductError("WORKSPACE_SYMLINK_TARGET_BLOCKED") + return target + + def validate_document(self, plan: dict[str, Any]) -> dict[str, Any]: + if not isinstance(plan, dict): + raise ProductError("PLAN_NOT_OBJECT") + if plan.get("schema") != 1: + raise ProductError("PLAN_SCHEMA_UNSUPPORTED") + + plan_id = str(plan.get("plan_id") or "") + if not PLAN_ID_RE.fullmatch(plan_id): + raise ProductError("PLAN_ID_INVALID") + + title = str(plan.get("title") or "").strip() + if not title or len(title) > 200: + raise ProductError("PLAN_TITLE_INVALID") + + steps = plan.get("steps") + if not isinstance(steps, list) or not (1 <= len(steps) <= MAX_STEPS): + raise ProductError("PLAN_STEPS_INVALID") + + seen: set[str] = set() + normalized_steps: list[dict[str, Any]] = [] + + for step in steps: + if not isinstance(step, dict): + raise ProductError("PLAN_STEP_NOT_OBJECT") + step_id = str(step.get("id") or "") + if not STEP_ID_RE.fullmatch(step_id) or step_id in seen: + raise ProductError("PLAN_STEP_ID_INVALID") + seen.add(step_id) + + capability = str(step.get("capability") or "") + if capability not in ALLOWED_CHANGE_CAPABILITIES: + raise ProductError("PLAN_CAPABILITY_NOT_ALLOWED") + + args = step.get("args") + if not isinstance(args, dict): + raise ProductError("PLAN_ARGS_NOT_OBJECT") + + if capability == "workspace.write_text": + allowed_keys = {"path", "text"} + if set(args) - allowed_keys: + raise ProductError("PLAN_ARGS_UNKNOWN_KEY") + path = str(args.get("path") or "") + text = str(args.get("text") if args.get("text") is not None else "") + self._validate_workspace_path(path) + if len(text.encode("utf-8")) > MAX_TEXT_BYTES: + raise ProductError("PLAN_TEXT_TOO_LARGE") + normalized_args = {"path": path, "text": text} + + elif capability in ("package.install", "package.remove"): + if set(args) != {"package"}: + raise ProductError("PLAN_PACKAGE_ARGS_INVALID") + package = str(args.get("package") or "") + if not PACKAGE_RE.fullmatch(package): + raise ProductError("PLAN_PACKAGE_NAME_INVALID") + normalized_args = {"package": package} + + elif capability == "service.manage": + if set(args) != {"service", "action"}: + raise ProductError("PLAN_SERVICE_ARGS_INVALID") + service = str(args.get("service") or "") + action = str(args.get("action") or "") + if not SERVICE_RE.fullmatch(service): + raise ProductError("PLAN_SERVICE_NAME_INVALID") + if action not in SERVICE_ACTIONS: + raise ProductError("PLAN_SERVICE_ACTION_INVALID") + normalized_args = {"service": service, "action": action} + + elif capability == "system.hostname": + if set(args) != {"hostname"}: + raise ProductError("PLAN_HOSTNAME_ARGS_INVALID") + hostname = str(args.get("hostname") or "").strip() + if not _valid_hostname(hostname): + raise ProductError("PLAN_HOSTNAME_INVALID") + normalized_args = {"hostname": hostname} + + elif capability == "system.timezone": + if set(args) != {"timezone"}: + raise ProductError("PLAN_TIMEZONE_ARGS_INVALID") + timezone_name = str(args.get("timezone") or "").strip() + if not _valid_timezone(timezone_name): + raise ProductError("PLAN_TIMEZONE_INVALID") + normalized_args = {"timezone": timezone_name} + + elif capability == "package.refresh": + if set(args): + raise ProductError("PLAN_PACKAGE_REFRESH_ARGS_INVALID") + normalized_args = {} + + elif capability == "package.upgrade": + if set(args) != {"package"}: + raise ProductError("PLAN_PACKAGE_ARGS_INVALID") + package = str(args.get("package") or "") + if not PACKAGE_RE.fullmatch(package): + raise ProductError("PLAN_PACKAGE_NAME_INVALID") + normalized_args = {"package": package} + + elif capability == "system.file.write_text": + if set(args) != {"path", "text"}: + raise ProductError("PLAN_SYSTEM_FILE_ARGS_INVALID") + path = str(args.get("path") or "") + text = str(args.get("text") if args.get("text") is not None else "") + if not _valid_system_path_arg(path): + raise ProductError("PLAN_SYSTEM_PATH_INVALID") + if len(text.encode("utf-8")) > MAX_TEXT_BYTES: + raise ProductError("PLAN_TEXT_TOO_LARGE") + normalized_args = {"path": os.path.normpath(path), "text": text} + + elif capability == "system.directory.ensure": + if set(args) != {"path", "mode"}: + raise ProductError("PLAN_SYSTEM_DIRECTORY_ARGS_INVALID") + path = str(args.get("path") or "") + mode = str(args.get("mode") or "") + if not _valid_system_path_arg(path): + raise ProductError("PLAN_SYSTEM_PATH_INVALID") + if not _valid_mode_arg(mode): + raise ProductError("PLAN_MODE_INVALID") + normalized_args = {"path": os.path.normpath(path), "mode": mode} + + elif capability == "system.file.mode": + if set(args) != {"path", "mode"}: + raise ProductError("PLAN_SYSTEM_FILE_MODE_ARGS_INVALID") + path = str(args.get("path") or "") + mode = str(args.get("mode") or "") + if not _valid_system_path_arg(path): + raise ProductError("PLAN_SYSTEM_PATH_INVALID") + if not _valid_mode_arg(mode): + raise ProductError("PLAN_MODE_INVALID") + normalized_args = {"path": os.path.normpath(path), "mode": mode} + + elif capability == "system.daemon_reload": + if set(args): + raise ProductError("PLAN_DAEMON_RELOAD_ARGS_INVALID") + normalized_args = {} + + elif capability == "network.forward": + normalized_args = _normalize_network_forward_args(args) + + else: + raise ProductError("PLAN_CAPABILITY_NOT_ALLOWED") + + normalized_steps.append({ + "id": step_id, + "capability": capability, + "risk": "CHANGE", + "args": normalized_args, + }) + + privileged_steps = [ + x for x in normalized_steps + if x["capability"] in PRIVILEGED_CHANGE_CAPABILITIES + ] + if privileged_steps and (len(normalized_steps) != 1 or len(privileged_steps) != 1): + raise ProductError("PLAN_PRIVILEGED_MUST_BE_SINGLE_STEP") + + normalized = { + "schema": 1, + "plan_id": plan_id, + "title": title, + "steps": normalized_steps, + } + return normalized + + def import_file(self, path: str) -> dict[str, Any]: + src = Path(path).expanduser() + if not src.is_file(): + raise ProductError("PLAN_IMPORT_FILE_NOT_FOUND") + if src.stat().st_size > 262144: + raise ProductError("PLAN_IMPORT_FILE_TOO_LARGE") + try: + value = json.loads(src.read_text(encoding="utf-8")) + except Exception: + raise ProductError("PLAN_IMPORT_JSON_INVALID") + return self.import_document(value) + + def import_document(self, value: dict[str, Any]) -> dict[str, Any]: + plan = self.validate_document(value) + plan_id = plan["plan_id"] + dst = self._plan_path(plan_id) + if dst.exists(): + raise ProductError("PLAN_ALREADY_EXISTS") + + plan_sha = sha256(canonical(plan)) + record = { + "plan": plan, + "plan_sha256": plan_sha, + "state": "IMPORTED", + "approval": None, + "apply": None, + "rollback": None, + "rollback_approval": None, + "imported_at": utc_now(), + } + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.import", + "PASS", + request={"plan_id": plan_id, "plan_sha256": plan_sha}, + result={"steps": len(plan["steps"]), "risk": "CHANGE"}, + principal="local-operator", + ) + return self.show(plan_id) + + def show(self, plan_id: str) -> dict[str, Any]: + record = self._load_record(plan_id) + return { + "ok": True, + "plan_id": plan_id, + "plan_sha256": record.get("plan_sha256"), + "state": record.get("state"), + "plan": record.get("plan"), + "approval": record.get("approval"), + "apply": record.get("apply"), + "rollback": record.get("rollback"), + "rollback_approval": record.get("rollback_approval"), + "workspace": str(self.workspace), + } + + def validate(self, plan_id: str) -> dict[str, Any]: + record = self._load_record(plan_id) + plan = self.validate_document(record.get("plan")) + current_sha = sha256(canonical(plan)) + expected_sha = str(record.get("plan_sha256") or "") + ok = current_sha == expected_sha + result = { + "ok": ok, + "plan_id": plan_id, + "expected_sha256": expected_sha, + "current_sha256": current_sha, + "risk": "CHANGE", + "steps": len(plan["steps"]), + } + if not ok: + result["error"] = "PLAN_HASH_MISMATCH" + return result + + def _plan_capability(self, record: dict[str, Any]) -> str: + steps = ((record.get("plan") or {}).get("steps") or []) + if len(steps) != 1: + return "mixed" + return str(steps[0].get("capability") or "") + + def _remote_approval_metadata_valid(self, approval: dict[str, Any]) -> bool: + return ( + approval.get("source") == REMOTE_APPROVAL_SOURCE + and approval.get("auth_method") == "ecdsa-p256" + and REMOTE_CHALLENGE_RE.fullmatch(str(approval.get("challenge_id") or "")) is not None + and REMOTE_KEY_ID_RE.fullmatch(str(approval.get("key_id") or "")) is not None + ) + + def _totp_approval_metadata_valid(self, approval: dict[str, Any]) -> bool: + return ( + approval.get("source") == TOTP_APPROVAL_SOURCE + and approval.get("auth_method") == "totp-sha1-6-30" + and REMOTE_CHALLENGE_RE.fullmatch(str(approval.get("challenge_id") or "")) is not None + and REMOTE_KEY_ID_RE.fullmatch(str(approval.get("key_id") or "")) is not None + and type(approval.get("totp_step")) is int + ) + + def _privilege_mode(self) -> str: + seed = getattr(self, "seed", None) + if seed is None: + return "totp" + config = seed.load_config() or {} + setup = config.get("setup") or {} + mode = str(setup.get("privilege_mode") or "totp").strip().lower() + return mode if mode in ("totp", "sudo_tty") else "totp" + + def _run_priv_helper(self, args: list[str], input_text: str | None = None) -> dict[str, Any]: + # Exit status alone must never authorize a privileged state transition. + mode = self._privilege_mode() + if mode == "sudo_tty" and not (sys.stdin.isatty() and sys.stderr.isatty()): + raise ProductError("LOCAL_TTY_REQUIRED_FOR_SUDO_PASSWORD") + sudo_argv = [SUDO, PRIV_HELPER, *args] if mode == "sudo_tty" else [SUDO, "-n", PRIV_HELPER, *args] + try: + kwargs={"text":True,"stdout":subprocess.PIPE,"stderr":subprocess.PIPE,"check":False,"shell":False} + if input_text is not None: + kwargs["input"]=input_text + proc = subprocess.run(sudo_argv, **kwargs) + except OSError: + raise ProductError("PRIV_HELPER_EXEC_FAILED") from None + try: + value = json.loads(proc.stdout) + except (json.JSONDecodeError, TypeError): + raise ProductError("PRIV_HELPER_INVALID_JSON") from None + + local_kinds={"approve-install":"package.install","approve-rollback":"package.rollback"} + op=args[0] if args else "" + if op in local_kinds and isinstance(value,dict) and "ok" not in value: + if proc.returncode != 0: raise ProductError("PRIV_HELPER_FAILED") + fields={"schema","kind","plan_id","plan_sha256","package","source","one_shot","consumed","approved_at"} + valid=(len(args)==7 and args[1::2]==["--plan-id","--plan-sha256","--package"] and set(value)==fields + and type(value.get("schema")) is int and value["schema"]==1 + and value.get("kind")==local_kinds[op] and value.get("plan_id")==args[2] + and value.get("plan_sha256")==args[4] and value.get("package")==args[6] + and value.get("source")=="LOCAL_TTY" and value.get("one_shot") is True + and value.get("consumed") is False and isinstance(value.get("approved_at"),str)) + if not valid: raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + try: + approved_at=datetime.fromisoformat(value["approved_at"].replace("Z","+00:00")) + if approved_at.tzinfo is None or approved_at.utcoffset() is None: raise ValueError("TIMEZONE_REQUIRED") + except (ValueError,TypeError,OverflowError): + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") from None + return value + + if op=="approve-remote-challenge" and isinstance(value,dict) and "ok" not in value: + if proc.returncode != 0: raise ProductError("PRIV_HELPER_FAILED") + fields={"schema","kind","plan_id","plan_sha256","package","source","auth_method","key_id","challenge_id","one_shot","consumed","approved_at"} + valid=(set(value)==fields and value.get("schema")==2 and value.get("kind") in ("package.install","package.rollback") + and PLAN_ID_RE.fullmatch(str(value.get("plan_id") or "")) is not None + and re.fullmatch(r"[a-f0-9]{64}",str(value.get("plan_sha256") or "")) is not None + and PACKAGE_RE.fullmatch(str(value.get("package") or "")) is not None + and value.get("source")==REMOTE_APPROVAL_SOURCE and value.get("auth_method")=="ecdsa-p256" + and REMOTE_KEY_ID_RE.fullmatch(str(value.get("key_id") or "")) is not None + and REMOTE_CHALLENGE_RE.fullmatch(str(value.get("challenge_id") or "")) is not None + and value.get("one_shot") is True and value.get("consumed") is False + and isinstance(value.get("approved_at"),str)) + if not valid: raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + try: + approved_at=datetime.fromisoformat(value["approved_at"].replace("Z","+00:00")) + if approved_at.tzinfo is None or approved_at.utcoffset() is None: raise ValueError("TIMEZONE_REQUIRED") + except (ValueError,TypeError,OverflowError): + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") from None + return value + + if op=="approve-totp-challenge" and isinstance(value,dict) and "ok" not in value: + if proc.returncode != 0: raise ProductError("PRIV_HELPER_FAILED") + fields={"schema","kind","plan_id","plan_sha256","package","source","auth_method","key_id","challenge_id","totp_step","one_shot","consumed","approved_at"} + valid=(set(value)==fields and value.get("schema")==3 and value.get("kind") in ("package.install","package.rollback") + and PLAN_ID_RE.fullmatch(str(value.get("plan_id") or "")) is not None + and re.fullmatch(r"[a-f0-9]{64}",str(value.get("plan_sha256") or "")) is not None + and PACKAGE_RE.fullmatch(str(value.get("package") or "")) is not None + and value.get("source")==TOTP_APPROVAL_SOURCE and value.get("auth_method")=="totp-sha1-6-30" + and REMOTE_KEY_ID_RE.fullmatch(str(value.get("key_id") or "")) is not None + and REMOTE_CHALLENGE_RE.fullmatch(str(value.get("challenge_id") or "")) is not None + and type(value.get("totp_step")) is int + and value.get("one_shot") is True and value.get("consumed") is False + and isinstance(value.get("approved_at"),str)) + if not valid: raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + return value + + generic_approval_kinds = { + "approve-change": "system.change", + "approve-change-rollback": "system.change.rollback", + } + if args and args[0] in generic_approval_kinds and isinstance(value, dict) and "ok" not in value: + if proc.returncode != 0: + raise ProductError("PRIV_HELPER_FAILED") + fields = { + "schema", "kind", "plan_id", "plan_sha256", "capability", + "args_sha256", "source", "one_shot", "consumed", "approved_at", + } + valid = ( + len(args) == 9 + and args[1::2] == ["--plan-id","--plan-sha256","--capability","--args-sha256"] + and set(value) == fields + and type(value.get("schema")) is int and value["schema"] == 2 + and value.get("kind") == generic_approval_kinds[args[0]] + and value.get("plan_id") == args[2] + and value.get("plan_sha256") == args[4] + and value.get("capability") == args[6] + and value.get("args_sha256") == args[8] + and value.get("source") == "LOCAL_TTY" + and value.get("one_shot") is True + and value.get("consumed") is False + and isinstance(value.get("approved_at"), str) + ) + if not valid: + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + try: + approved_at=datetime.fromisoformat(value["approved_at"].replace("Z","+00:00")) + if approved_at.tzinfo is None or approved_at.utcoffset() is None: + raise ValueError("TIMEZONE_REQUIRED") + except (ValueError,TypeError,OverflowError): + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") from None + return value + + if not isinstance(value,dict) or type(value.get("ok")) is not bool: + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + if proc.returncode != 0 or value["ok"] is not True: + error=value.get("error") + if not isinstance(error,str) or re.fullmatch(r"[A-Z][A-Z0-9_]{0,127}",error) is None: error="PRIV_HELPER_FAILED" + raise ProductError(error) + if value.get("error") not in (None,""): raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + if op in local_kinds or op in generic_approval_kinds or op in ("approve-remote-challenge","approve-totp-challenge"): + raise ProductError("PRIV_HELPER_INVALID_RESPONSE") + return value + + def remote_challenge(self, plan_id: str, rollback: bool=False, ttl_seconds: int=300) -> dict[str, Any]: + record=self._load_record(plan_id) + validation=self.validate(plan_id) + if not validation["ok"]: raise ProductError("PLAN_HASH_MISMATCH") + if self._plan_capability(record)!="package.install": raise ProductError("PLAN_NOT_PRIVILEGED_PACKAGE") + step=record["plan"]["steps"][0]; package=step["args"]["package"] + if rollback: + if record.get("state")!="APPLIED" or (record.get("rollback") or {}).get("available") is not True: + raise ProductError("ROLLBACK_NOT_AVAILABLE") + kind="package.rollback" + else: + if record.get("state") not in ("IMPORTED","APPROVED"): raise ProductError("PLAN_STATE_NOT_APPROVABLE") + kind="package.install" + value=self._run_priv_helper(["remote-challenge","--kind",kind,"--plan-id",plan_id,"--plan-sha256",record["plan_sha256"],"--package",package,"--ttl",str(int(ttl_seconds))]) + required={"ok","schema","kind","plan_id","plan_sha256","package","challenge_id","created_at","expires_at","state","message_b64"} + valid=(set(value)==required and value.get("ok") is True and value.get("schema")==1 and value.get("kind")==kind + and value.get("plan_id")==plan_id and value.get("plan_sha256")==record["plan_sha256"] and value.get("package")==package + and REMOTE_CHALLENGE_RE.fullmatch(str(value.get("challenge_id") or "")) is not None and value.get("state")=="PENDING" + and isinstance(value.get("message_b64"),str)) + if not valid: raise ProductError("REMOTE_CHALLENGE_INVALID_RESPONSE") + self.seed.audit.append("plan.remote.challenge","PASS",request={"plan_id":plan_id,"plan_sha256":record["plan_sha256"],"kind":kind},result={"challenge_id":value["challenge_id"],"expires_at":value["expires_at"]},principal="remote-human-broker") + return value + + def approve_remote(self, plan_id: str, challenge_id: str, key_id: str, signature_b64: str, rollback: bool=False) -> dict[str, Any]: + record=self._load_record(plan_id) + validation=self.validate(plan_id) + if not validation["ok"]: raise ProductError("PLAN_HASH_MISMATCH") + if self._plan_capability(record)!="package.install": raise ProductError("PLAN_NOT_PRIVILEGED_PACKAGE") + if REMOTE_CHALLENGE_RE.fullmatch(str(challenge_id)) is None: raise ProductError("REMOTE_CHALLENGE_ID_INVALID") + if REMOTE_KEY_ID_RE.fullmatch(str(key_id)) is None: raise ProductError("REMOTE_KEY_ID_INVALID") + step=record["plan"]["steps"][0]; package=step["args"]["package"] + expected_kind="package.rollback" if rollback else "package.install" + if rollback: + if record.get("state")!="APPLIED" or (record.get("rollback") or {}).get("available") is not True: raise ProductError("ROLLBACK_NOT_AVAILABLE") + else: + if record.get("state") not in ("IMPORTED","APPROVED"): raise ProductError("PLAN_STATE_NOT_APPROVABLE") + receipt=self._run_priv_helper(["approve-remote-challenge","--challenge-id",str(challenge_id),"--key-id",str(key_id),"--signature-b64",str(signature_b64)]) + if receipt.get("kind")!=expected_kind or receipt.get("plan_id")!=plan_id or receipt.get("plan_sha256")!=record.get("plan_sha256") or receipt.get("package")!=package: + raise ProductError("REMOTE_APPROVAL_BINDING_MISMATCH") + approval={"source":REMOTE_APPROVAL_SOURCE,"plan_sha256":record["plan_sha256"],"approved_at":receipt["approved_at"],"one_shot":True,"consumed":False,"package":package,"auth_method":"ecdsa-p256","key_id":receipt["key_id"],"challenge_id":receipt["challenge_id"]} + if rollback: + record["rollback_approval"]=approval; event="plan.rollback.approve.remote" + else: + record["approval"]=approval; record["state"]="APPROVED"; event="plan.approve.remote" + self._write_record(plan_id,record) + self.seed.audit.append(event,"PASS",request={"plan_id":plan_id,"plan_sha256":record["plan_sha256"],"source":REMOTE_APPROVAL_SOURCE,"challenge_id":receipt["challenge_id"],"key_id":receipt["key_id"]},result={"package":package,"one_shot":True},principal="remote-human") + return self.show(plan_id) + + def totp_enroll(self, key_id: str, node_label: str, issuer: str='FrameBridge') -> dict[str, Any]: + if REMOTE_KEY_ID_RE.fullmatch(str(key_id)) is None: raise ProductError("REMOTE_KEY_ID_INVALID") + if not isinstance(node_label,str) or not node_label.strip() or len(node_label.strip())>128: raise ProductError("TOTP_NODE_LABEL_INVALID") + if not isinstance(issuer,str) or not issuer.strip() or len(issuer.strip())>64: raise ProductError("TOTP_ISSUER_INVALID") + value=self._run_priv_helper(["totp-enroll","--key-id",str(key_id),"--node-label",node_label.strip(),"--issuer",issuer.strip()]) + required={"ok","schema","key_id","node_label","issuer","otpauth_uri","secret_sha256","enrolled_at"} + valid=(set(value)==required and value.get("ok") is True and value.get("schema")==1 and value.get("key_id")==key_id and value.get("node_label")==node_label.strip() and value.get("issuer")==issuer.strip() and isinstance(value.get("otpauth_uri"),str) and value["otpauth_uri"].startswith("otpauth://totp/") and re.fullmatch(r"[a-f0-9]{64}",str(value.get("secret_sha256") or "")) is not None) + if not valid: raise ProductError("TOTP_ENROLL_INVALID_RESPONSE") + self.seed.audit.append("totp.enroll","PASS",request={"key_id":key_id,"node_label":node_label.strip(),"issuer":issuer.strip()},result={"secret_sha256":value["secret_sha256"]},principal="local-operator") + return value + + def totp_challenge(self, plan_id: str, key_id: str, rollback: bool=False, ttl_seconds: int=120) -> dict[str, Any]: + record=self._load_record(plan_id); validation=self.validate(plan_id) + if not validation["ok"]: raise ProductError("PLAN_HASH_MISMATCH") + if self._plan_capability(record)!="package.install": raise ProductError("PLAN_NOT_PRIVILEGED_PACKAGE") + step=record["plan"]["steps"][0]; package=step["args"]["package"] + kind="package.rollback" if rollback else "package.install" + if rollback and (record.get("state")!="APPLIED" or (record.get("rollback") or {}).get("available") is not True): raise ProductError("ROLLBACK_NOT_AVAILABLE") + if not rollback and record.get("state") not in ("IMPORTED","APPROVED"): raise ProductError("PLAN_STATE_NOT_APPROVABLE") + value=self._run_priv_helper(["totp-challenge","--kind",kind,"--plan-id",plan_id,"--plan-sha256",record["plan_sha256"],"--package",package,"--key-id",str(key_id),"--ttl",str(int(ttl_seconds))]) + if value.get("ok") is not True or value.get("plan_id")!=plan_id or value.get("plan_sha256")!=record["plan_sha256"] or value.get("totp_key_id")!=key_id: raise ProductError("TOTP_CHALLENGE_INVALID_RESPONSE") + return value + + def approve_totp(self, plan_id: str, challenge_id: str, key_id: str, code: str, rollback: bool=False) -> dict[str, Any]: + record=self._load_record(plan_id); validation=self.validate(plan_id) + if not validation["ok"]: raise ProductError("PLAN_HASH_MISMATCH") + if not (isinstance(code,str) and len(code)==6 and code.isdigit()): raise ProductError("TOTP_CODE_INVALID") + step=record["plan"]["steps"][0]; package=step["args"]["package"]; expected="package.rollback" if rollback else "package.install" + receipt=self._run_priv_helper(["approve-totp-challenge","--challenge-id",str(challenge_id),"--key-id",str(key_id)],input_text=code+"\n") + if receipt.get("kind")!=expected or receipt.get("plan_id")!=plan_id or receipt.get("plan_sha256")!=record.get("plan_sha256") or receipt.get("package")!=package: raise ProductError("TOTP_APPROVAL_BINDING_MISMATCH") + approval={"source":TOTP_APPROVAL_SOURCE,"plan_sha256":record["plan_sha256"],"approved_at":receipt["approved_at"],"one_shot":True,"consumed":False,"package":package,"auth_method":"totp-sha1-6-30","key_id":receipt["key_id"],"challenge_id":receipt["challenge_id"],"totp_step":receipt["totp_step"]} + if rollback: record["rollback_approval"]=approval + else: record["approval"]=approval; record["state"]="APPROVED" + self._write_record(plan_id,record); return self.show(plan_id) + + def approve_system(self, plan_id: str) -> dict[str, Any]: + record = self._load_record(plan_id) + validation = self.validate(plan_id) + if not validation["ok"]: + raise ProductError("PLAN_HASH_MISMATCH") + if record.get("state") not in ("IMPORTED", "APPROVED"): + raise ProductError("PLAN_STATE_NOT_APPROVABLE") + + cap = self._plan_capability(record) + if cap not in PRIVILEGED_CHANGE_CAPABILITIES: + raise ProductError("PLAN_NOT_PRIVILEGED_CHANGE") + + step = record["plan"]["steps"][0] + args = step["args"] + + approval = { + "source": "LOCAL_TTY_PRIVILEGED", + "plan_sha256": record["plan_sha256"], + "approved_at": utc_now(), + "one_shot": True, + "consumed": False, + "capability": cap, + } + + if cap == "package.install": + package = args["package"] + self._run_priv_helper([ + "approve-install", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--package", package, + ]) + approval["package"] = package + audit_result = {"capability": cap, "package": package, "one_shot": True} + else: + args_sha = _args_sha(args) + self._run_priv_helper([ + "approve-change", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--capability", cap, + "--args-sha256", args_sha, + ]) + approval["args_sha256"] = args_sha + audit_result = {"capability": cap, "args_sha256": args_sha, "one_shot": True} + + record["approval"] = approval + record["state"] = "APPROVED" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.approve.system", "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record["plan_sha256"], + "source": "LOCAL_TTY_PRIVILEGED", + "capability": cap, + }, + result=audit_result, + principal="local-operator", + ) + return self.show(plan_id) + + def approve(self, plan_id: str) -> dict[str, Any]: + record = self._load_record(plan_id) + cap = self._plan_capability(record) + if cap == "package.install": + raise ProductError("USE_APPROVE_SYSTEM_FOR_PACKAGE") + if cap in PRIVILEGED_CHANGE_CAPABILITIES: + raise ProductError("USE_APPROVE_SYSTEM_FOR_PRIVILEGED") + validation = self.validate(plan_id) + if not validation["ok"]: + raise ProductError("PLAN_HASH_MISMATCH") + if record.get("state") not in ("IMPORTED", "APPROVED"): + raise ProductError("PLAN_STATE_NOT_APPROVABLE") + + approval = { + "source": "LOCAL_TTY", + "plan_sha256": record["plan_sha256"], + "approved_at": utc_now(), + "one_shot": True, + "consumed": False, + } + record["approval"] = approval + record["state"] = "APPROVED" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.approve", + "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record["plan_sha256"], + "source": "LOCAL_TTY", + }, + result={"one_shot": True}, + principal="local-operator", + ) + return self.show(plan_id) + + def _backup_step(self, plan_id: str, step_id: str, target: Path) -> dict[str, Any]: + rb_root = self.rollback_dir / plan_id + rb_root.mkdir(mode=0o700, parents=True, exist_ok=True) + meta: dict[str, Any] = { + "step_id": step_id, + "target": str(target), + "existed": target.exists(), + } + if target.exists(): + if not target.is_file(): + raise ProductError("WORKSPACE_TARGET_NOT_REGULAR_FILE") + data = target.read_bytes() + backup = rb_root / (step_id + ".bak") + backup.write_bytes(data) + os.chmod(backup, 0o600) + meta["backup"] = str(backup) + meta["mode"] = target.stat().st_mode & 0o777 + meta["sha256_before"] = sha256(data) + return meta + + def _atomic_write(self, target: Path, text: str) -> dict[str, Any]: + target.parent.mkdir(parents=True, exist_ok=True) + root = self.workspace.resolve(strict=False) + try: + if os.path.commonpath([str(root), str(target.parent.resolve(strict=False))]) != str(root): + raise ProductError("WORKSPACE_PARENT_ESCAPE") + except ValueError: + raise ProductError("WORKSPACE_PARENT_ESCAPE") + + data = text.encode("utf-8") + fd, tmp_name = tempfile.mkstemp(prefix=".framebridge-plan-", dir=str(target.parent)) + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.replace(tmp_name, target) + finally: + if os.path.exists(tmp_name): + os.unlink(tmp_name) + return {"sha256_after": sha256(data), "bytes": len(data)} + + def apply(self, plan_id: str) -> dict[str, Any]: + memory = MemoryStore(self.seed) + memory.require_ready() + record = self._load_record(plan_id) + validation = self.validate(plan_id) + if not validation["ok"]: + raise ProductError("PLAN_HASH_MISMATCH") + if record.get("state") != "APPROVED": + raise ProductError("PLAN_NOT_APPROVED") + approval = record.get("approval") or {} + cap = self._plan_capability(record) + if cap == "package.install": + if approval.get("source") not in ("LOCAL_TTY_PRIVILEGED", REMOTE_APPROVAL_SOURCE, TOTP_APPROVAL_SOURCE): + raise ProductError("PLAN_APPROVAL_SOURCE_INVALID") + if approval.get("source") == REMOTE_APPROVAL_SOURCE and not self._remote_approval_metadata_valid(approval): + raise ProductError("PLAN_REMOTE_APPROVAL_INVALID") + if approval.get("source") == TOTP_APPROVAL_SOURCE and not self._totp_approval_metadata_valid(approval): + raise ProductError("PLAN_TOTP_APPROVAL_INVALID") + elif cap in PRIVILEGED_CHANGE_CAPABILITIES: + if approval.get("source") != "LOCAL_TTY_PRIVILEGED": + raise ProductError("PLAN_APPROVAL_SOURCE_INVALID") + elif approval.get("source") != "LOCAL_TTY": + raise ProductError("PLAN_APPROVAL_SOURCE_INVALID") + if approval.get("plan_sha256") != record.get("plan_sha256"): + raise ProductError("PLAN_APPROVAL_HASH_MISMATCH") + if approval.get("one_shot") is not True or approval.get("consumed") is True: + raise ProductError("PLAN_APPROVAL_CONSUMED") + + plan = record["plan"] + applied: list[dict[str, Any]] = [] + rollback_meta: list[dict[str, Any]] = [] + + if self._plan_capability(record) == "package.install": + if approval.get("source") not in ("LOCAL_TTY_PRIVILEGED", REMOTE_APPROVAL_SOURCE, TOTP_APPROVAL_SOURCE): + raise ProductError("PLAN_PRIVILEGED_APPROVAL_SOURCE_INVALID") + if approval.get("source") == REMOTE_APPROVAL_SOURCE and not self._remote_approval_metadata_valid(approval): + raise ProductError("PLAN_REMOTE_APPROVAL_INVALID") + if approval.get("source") == TOTP_APPROVAL_SOURCE and not self._totp_approval_metadata_valid(approval): + raise ProductError("PLAN_TOTP_APPROVAL_INVALID") + step = plan["steps"][0] + package = step["args"]["package"] + result = self._run_priv_helper([ + "apply-install", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--package", package, + ]) + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["approval"] = approval + applied = [{ + "step_id": step["id"], + "capability": "package.install", + "package": package, + "version": result.get("version"), + "verified": result.get("ok") is True, + }] + record["apply"] = {"applied_at": utc_now(), "steps": applied} + record["rollback"] = {"available": result.get("rollback_available") is True, "kind": "package.remove"} + record["state"] = "APPLIED" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.apply", "PASS", + request={"plan_id":plan_id,"plan_sha256":record["plan_sha256"]}, + result={"steps":1,"verified":True,"rollback_available":record["rollback"]["available"]}, + principal="local-operator", + ) + memory.append("change", { + "action":"plan.apply", "plan_id":plan_id, "plan_sha256":record["plan_sha256"], + "capability":"package.install", "package":package, "version":result.get("version"), + "verified":True, "rollback_available":record["rollback"]["available"], + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + + if cap in PRIVILEGED_CHANGE_CAPABILITIES and cap != "package.install": + if approval.get("source") != "LOCAL_TTY_PRIVILEGED": + raise ProductError("PLAN_PRIVILEGED_APPROVAL_SOURCE_INVALID") + step = plan["steps"][0] + args = step["args"] + expected_args_sha = _args_sha(args) + if approval.get("args_sha256") != expected_args_sha: + raise ProductError("PLAN_APPROVAL_ARGS_HASH_MISMATCH") + + if cap == "package.remove": + result = self._run_priv_helper([ + "apply-remove", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--package", args["package"], + ]) + elif cap == "service.manage": + result = self._run_priv_helper([ + "apply-service", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--service", args["service"], + "--action", args["action"], + ]) + elif cap == "system.hostname": + result = self._run_priv_helper([ + "apply-hostname", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--hostname", args["hostname"], + ]) + elif cap == "system.timezone": + result = self._run_priv_helper([ + "apply-timezone", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--timezone", args["timezone"], + ]) + elif cap in ADMIN_R3_CORE_CAPABILITIES: + result = self._run_priv_helper([ + "apply-admin", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--capability", cap, + "--args-sha256", expected_args_sha, + ], input_text=json.dumps(args, sort_keys=True, separators=(",", ":"))) + else: + raise ProductError("PLAN_CAPABILITY_NOT_ALLOWED") + + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["approval"] = approval + applied = [{ + "step_id": step["id"], + "capability": cap, + "args_sha256": expected_args_sha, + "verified": result.get("ok") is True, + }] + if cap.startswith("package."): + applied[0]["package"] = args["package"] + if cap == "service.manage": + applied[0]["service"] = args["service"] + applied[0]["action"] = args["action"] + if cap == "system.hostname": + applied[0]["hostname"] = args["hostname"] + if cap == "system.timezone": + applied[0]["timezone"] = args["timezone"] + + record["apply"] = {"applied_at": utc_now(), "steps": applied} + record["rollback"] = { + "available": result.get("rollback_available") is True, + "kind": cap + ".rollback", + } + record["state"] = "APPLIED" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.apply", "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record["plan_sha256"], + "capability": cap, + "args_sha256": expected_args_sha, + }, + result={ + "steps": 1, + "verified": result.get("ok") is True, + "rollback_available": record["rollback"]["available"], + }, + principal="local-operator", + ) + memory.append("change", { + "action":"plan.apply", + "plan_id":plan_id, + "plan_sha256":record["plan_sha256"], + "capability":cap, + "args_sha256":expected_args_sha, + "verified":True, + "rollback_available":record["rollback"]["available"], + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + + try: + for step in plan["steps"]: + if step["capability"] != "workspace.write_text": + raise ProductError("PLAN_CAPABILITY_NOT_ALLOWED") + target = self._validate_workspace_path(step["args"]["path"]) + backup = self._backup_step(plan_id, step["id"], target) + write_meta = self._atomic_write(target, step["args"]["text"]) + verify = target.read_bytes() + if sha256(verify) != write_meta["sha256_after"]: + raise ProductError("PLAN_VERIFY_HASH_MISMATCH") + applied.append({ + "step_id": step["id"], + "capability": step["capability"], + "target": str(target), + **write_meta, + "verified": True, + }) + rollback_meta.append(backup) + except Exception: + for meta in reversed(rollback_meta): + self._restore_meta(meta) + raise + + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["approval"] = approval + record["apply"] = { + "applied_at": utc_now(), + "steps": applied, + } + record["rollback"] = { + "available": True, + "steps": rollback_meta, + } + record["state"] = "APPLIED" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.apply", + "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record["plan_sha256"], + }, + result={ + "steps": len(applied), + "verified": True, + "rollback_available": True, + }, + principal="local-operator", + ) + memory.append("change", { + "action":"plan.apply", "plan_id":plan_id, "plan_sha256":record["plan_sha256"], + "capability":"workspace.write_text", "steps":len(applied), "verified":True, "rollback_available":True, + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + def approve_rollback(self, plan_id: str) -> dict[str, Any]: + record = self._load_record(plan_id) + if record.get("state") != "APPLIED": + raise ProductError("PLAN_NOT_APPLIED") + rollback = record.get("rollback") or {} + if rollback.get("available") is not True: + raise ProductError("ROLLBACK_NOT_AVAILABLE") + + cap = self._plan_capability(record) + step = record["plan"]["steps"][0] if cap != "mixed" else None + approval = { + "plan_sha256": record.get("plan_sha256"), + "approved_at": utc_now(), + "one_shot": True, + "consumed": False, + } + + if cap == "package.install": + package = step["args"]["package"] + self._run_priv_helper([ + "approve-rollback", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--package", package, + ]) + source = "LOCAL_TTY_PRIVILEGED" + approval["package"] = package + approval["capability"] = cap + audit_result = {"one_shot": True, "capability": cap, "package": package} + elif cap in PRIVILEGED_CHANGE_CAPABILITIES: + args_sha = _args_sha(step["args"]) + self._run_priv_helper([ + "approve-change-rollback", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--capability", cap, + "--args-sha256", args_sha, + ]) + source = "LOCAL_TTY_PRIVILEGED" + approval["capability"] = cap + approval["args_sha256"] = args_sha + audit_result = {"one_shot": True, "capability": cap, "args_sha256": args_sha} + else: + source = "LOCAL_TTY" + audit_result = {"one_shot": True, "capability": cap} + + approval["source"] = source + record["rollback_approval"] = approval + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.rollback.approve", + "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record.get("plan_sha256"), + "source": source, + "capability": cap, + }, + result=audit_result, + principal="local-operator", + ) + return self.show(plan_id) + + def _restore_meta(self, meta: dict[str, Any]) -> None: + target = Path(str(meta["target"])) + if meta.get("existed"): + backup = Path(str(meta.get("backup") or "")) + if not backup.is_file(): + raise ProductError("ROLLBACK_BACKUP_MISSING") + target.parent.mkdir(parents=True, exist_ok=True) + data = backup.read_bytes() + fd, tmp_name = tempfile.mkstemp(prefix=".framebridge-rb-", dir=str(target.parent)) + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.replace(tmp_name, target) + os.chmod(target, int(meta.get("mode") or 0o600)) + finally: + if os.path.exists(tmp_name): + os.unlink(tmp_name) + else: + try: + target.unlink() + except FileNotFoundError: + pass + + def rollback(self, plan_id: str) -> dict[str, Any]: + memory = MemoryStore(self.seed) + memory.require_ready() + record = self._load_record(plan_id) + if record.get("state") != "APPLIED": + raise ProductError("PLAN_NOT_APPLIED") + rollback = record.get("rollback") or {} + if rollback.get("available") is not True: + raise ProductError("ROLLBACK_NOT_AVAILABLE") + + approval = record.get("rollback_approval") or {} + cap = self._plan_capability(record) + cap = self._plan_capability(record) + if cap == "package.install": + if approval.get("source") not in ("LOCAL_TTY_PRIVILEGED", REMOTE_APPROVAL_SOURCE, TOTP_APPROVAL_SOURCE): + raise ProductError("ROLLBACK_APPROVAL_SOURCE_INVALID") + if approval.get("source") == REMOTE_APPROVAL_SOURCE and not self._remote_approval_metadata_valid(approval): + raise ProductError("ROLLBACK_REMOTE_APPROVAL_INVALID") + if approval.get("source") == TOTP_APPROVAL_SOURCE and not self._totp_approval_metadata_valid(approval): + raise ProductError("ROLLBACK_TOTP_APPROVAL_INVALID") + elif cap in PRIVILEGED_CHANGE_CAPABILITIES: + if approval.get("source") != "LOCAL_TTY_PRIVILEGED": + raise ProductError("ROLLBACK_APPROVAL_SOURCE_INVALID") + elif approval.get("source") != "LOCAL_TTY": + raise ProductError("ROLLBACK_APPROVAL_SOURCE_INVALID") + if approval.get("plan_sha256") != record.get("plan_sha256"): + raise ProductError("ROLLBACK_APPROVAL_HASH_MISMATCH") + if approval.get("one_shot") is not True or approval.get("consumed") is True: + raise ProductError("ROLLBACK_APPROVAL_CONSUMED") + + if cap == "package.install": + step = record["plan"]["steps"][0] + package = step["args"]["package"] + result = self._run_priv_helper([ + "apply-rollback", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--package", package, + ]) + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["rollback_approval"] = approval + rollback["available"] = False + rollback["completed_at"] = utc_now() + rollback["verified"] = result.get("rollback_verified") is True + rollback["verification"] = [{ + "package": package, + "absent_after_rollback": result.get("absent_after_rollback") is True, + "verified": result.get("rollback_verified") is True, + }] + record["rollback"] = rollback + record["state"] = "ROLLED_BACK" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.rollback", "PASS", request={"plan_id":plan_id,"plan_sha256":record["plan_sha256"]}, + result={"steps":1,"rollback_approval_consumed":True,"verified":rollback["verified"]}, principal="local-operator", + ) + memory.append("recovery", { + "action":"plan.rollback", "plan_id":plan_id, "plan_sha256":record["plan_sha256"], + "capability":"package.install", "package":package, "verified":rollback["verified"], + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + + if cap in PRIVILEGED_CHANGE_CAPABILITIES and cap != "package.install": + step = record["plan"]["steps"][0] + args_sha = _args_sha(step["args"]) + if approval.get("args_sha256") != args_sha: + raise ProductError("ROLLBACK_APPROVAL_ARGS_HASH_MISMATCH") + result = self._run_priv_helper([ + "apply-change-rollback", "--plan-id", plan_id, + "--plan-sha256", record["plan_sha256"], + "--capability", cap, + "--args-sha256", args_sha, + ]) + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["rollback_approval"] = approval + rollback["available"] = False + rollback["completed_at"] = utc_now() + rollback["verified"] = result.get("rollback_verified") is True + verification = result.get("verification") + rollback["verification"] = [verification] if isinstance(verification, dict) else [] + record["rollback"] = rollback + record["state"] = "ROLLED_BACK" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.rollback", "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record["plan_sha256"], + "capability": cap, + "args_sha256": args_sha, + }, + result={ + "steps": 1, + "rollback_approval_consumed": True, + "verified": rollback["verified"], + }, + principal="local-operator", + ) + memory.append("recovery", { + "action":"plan.rollback", + "plan_id":plan_id, + "plan_sha256":record["plan_sha256"], + "capability":cap, + "rollback_verified":True, + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + + metas = rollback.get("steps") or [] + applied_steps = (record.get("apply") or {}).get("steps") or [] + if len(applied_steps) != len(metas): + raise ProductError("ROLLBACK_METADATA_MISMATCH") + for step in applied_steps: + target = Path(str(step.get("target") or "")) + expected_after = str(step.get("sha256_after") or "") + if not target.is_file(): + raise ProductError("ROLLBACK_TARGET_MISSING_OR_INVALID") + current_sha = sha256(target.read_bytes()) + if current_sha != expected_after: + raise ProductError("ROLLBACK_TARGET_CHANGED") + + for meta in reversed(metas): + self._restore_meta(meta) + + verification: list[dict[str, Any]] = [] + for meta in metas: + target = Path(str(meta.get("target") or "")) + if meta.get("existed"): + if not target.is_file(): + raise ProductError("ROLLBACK_VERIFY_TARGET_MISSING") + current_sha = sha256(target.read_bytes()) + expected_before = str(meta.get("sha256_before") or "") + if current_sha != expected_before: + raise ProductError("ROLLBACK_VERIFY_HASH_MISMATCH") + verification.append({ + "step_id": meta.get("step_id"), + "target": str(target), + "restored_existing": True, + "sha256_restored": current_sha, + "verified": True, + }) + else: + if target.exists(): + raise ProductError("ROLLBACK_VERIFY_TARGET_STILL_PRESENT") + verification.append({ + "step_id": meta.get("step_id"), + "target": str(target), + "restored_existing": False, + "absent_after_rollback": True, + "verified": True, + }) + + approval["consumed"] = True + approval["consumed_at"] = utc_now() + record["rollback_approval"] = approval + rollback["available"] = False + rollback["completed_at"] = utc_now() + rollback["verified"] = True + rollback["verification"] = verification + record["rollback"] = rollback + record["state"] = "ROLLED_BACK" + self._write_record(plan_id, record) + self.seed.audit.append( + "plan.rollback", + "PASS", + request={ + "plan_id": plan_id, + "plan_sha256": record.get("plan_sha256"), + }, + result={ + "steps": len(metas), + "rollback_approval_consumed": True, + "verified": True, + }, + principal="local-operator", + ) + memory.append("recovery", { + "action":"plan.rollback", "plan_id":plan_id, "plan_sha256":record.get("plan_sha256"), + "capability":"workspace.write_text", "steps":len(metas), "verified":True, + }, source="system") + memory.checkpoint() + return self.show(plan_id) + + def list(self) -> dict[str, Any]: + items = [] + for path in sorted(self.plans_dir.glob("*.json")): + try: + record = json.loads(path.read_text(encoding="utf-8")) + items.append({ + "plan_id": path.stem, + "state": record.get("state"), + "title": (record.get("plan") or {}).get("title"), + "plan_sha256": record.get("plan_sha256"), + }) + except Exception: + items.append({"plan_id": path.stem, "state": "INVALID"}) + return {"ok": True, "plans": items} diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py new file mode 100644 index 0000000..77a4cb0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py @@ -0,0 +1,1540 @@ +#!/usr/bin/python3 +from __future__ import annotations + +import argparse +import base64 +import hashlib +import hmac +import ipaddress +import json +import os +import re +import secrets +import struct +import subprocess +import time +import sys +import tempfile +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any, Callable +from urllib.parse import quote + +PLAN_ID_RE = re.compile(r'^[A-Za-z0-9._-]{1,80}$') +SHA256_RE = re.compile(r'^[a-f0-9]{64}$') +PACKAGE_RE = re.compile(r'^[a-z0-9][a-z0-9+.-]{0,127}$') +SERVICE_RE = re.compile(r'^[A-Za-z0-9@_.-]{1,128}$') +ARGS_SHA_RE = re.compile(r'^[a-f0-9]{64}$') +SERVICE_ACTIONS = {'start','stop','restart','enable','disable'} +IFACE_RE = re.compile(r'^[A-Za-z0-9_.:-]{1,32}$') +FORWARD_PROTOCOLS = {'any','tcp','udp'} +FORWARD_SOURCE_NETS = ( + ipaddress.ip_network('10.0.0.0/8'), + ipaddress.ip_network('172.16.0.0/12'), + ipaddress.ip_network('192.168.0.0/16'), + ipaddress.ip_network('100.64.0.0/10'), +) +IPTABLES = '/usr/sbin/iptables' +SYSCTL = '/usr/sbin/sysctl' +SYSTEMCTL = '/usr/bin/systemctl' +IP = '/usr/sbin/ip' +SYSTEMD_RUN = '/usr/bin/systemd-run' +PRIV_HELPER_PATH = '/usr/local/libexec/framebridge-seed-priv' +PAIRING_WINDOW_STATE = Path('/run/framebridge-seed/pairing-window.json') +PAIRING_WINDOW_MIN_TTL = 30 +PAIRING_WINDOW_MAX_TTL = 600 +R3_CORE_CAPABILITIES = { + 'package.refresh','package.upgrade','system.file.write_text', + 'system.directory.ensure','system.file.mode','system.daemon_reload', + 'network.forward', +} +CHANGE_CAPABILITIES = {'package.remove','service.manage','system.hostname','system.timezone'} | R3_CORE_CAPABILITIES +ZONEINFO_ROOT = Path('/usr/share/zoneinfo') +MODE_RE = re.compile(r'^[0-7]{3,4}$') +MAX_SYSTEM_TEXT_BYTES = 65536 +SYSTEM_PATH_FORBIDDEN_PREFIXES = ( + '/proc','/sys','/dev','/run','/boot','/var/lib/framebridge-seed', +) +SYSTEM_PATH_FORBIDDEN_EXACT = {'/etc/shadow','/etc/gshadow','/etc/sudoers'} +CHALLENGE_RE = re.compile(r'^[a-f0-9]{32}$') +KEY_ID_RE = re.compile(r'^[A-Za-z0-9._-]{1,80}$') +REMOTE_SOURCE = 'REMOTE_HUMAN' +TOTP_SOURCE = 'REMOTE_HUMAN_TOTP' + +STATE_ROOT = Path('/var/lib/framebridge-seed') +APPROVAL_DIR = STATE_ROOT / 'priv-approvals' +TX_DIR = STATE_ROOT / 'priv-transactions' +REMOTE_CHALLENGE_DIR = STATE_ROOT / 'remote-human-challenges' +REMOTE_KEY_DIR = STATE_ROOT / 'remote-human-keys' +TOTP_KEY_DIR = STATE_ROOT / 'totp-keys' +TOTP_USED_DIR = STATE_ROOT / 'totp-used' + + +class PrivError(RuntimeError): + pass + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def _validate_plan_id(v: str) -> str: + v=str(v).strip() + if not PLAN_ID_RE.fullmatch(v): raise PrivError('PLAN_ID_INVALID') + return v + + +def _validate_sha(v: str) -> str: + v=str(v).strip().lower() + if not SHA256_RE.fullmatch(v): raise PrivError('PLAN_SHA256_INVALID') + return v + + +def _validate_package(v: str) -> str: + v=str(v).strip() + if not PACKAGE_RE.fullmatch(v): raise PrivError('PACKAGE_NAME_INVALID') + return v + + +def _validate_args_sha(v: str) -> str: + v=str(v).strip().lower() + if not ARGS_SHA_RE.fullmatch(v): raise PrivError('ARGS_SHA256_INVALID') + return v + +def _validate_capability(v: str) -> str: + v=str(v).strip() + if v not in CHANGE_CAPABILITIES: raise PrivError('CHANGE_CAPABILITY_INVALID') + return v + +def _validate_service(v: str) -> str: + v=str(v).strip() + if not SERVICE_RE.fullmatch(v): raise PrivError('SERVICE_NAME_INVALID') + return v + +def _validate_service_action(v: str) -> str: + v=str(v).strip() + if v not in SERVICE_ACTIONS: raise PrivError('SERVICE_ACTION_INVALID') + return v + +def _validate_interface(v: str) -> str: + v=str(v).strip() + if not IFACE_RE.fullmatch(v): raise PrivError('NETWORK_INTERFACE_INVALID') + return v + + +def _validate_forward_network(v: str, source: bool=False) -> str: + try: + net=ipaddress.ip_network(str(v),strict=False) + except ValueError: + raise PrivError('NETWORK_CIDR_INVALID') from None + if net.version!=4: raise PrivError('NETWORK_IPV4_REQUIRED') + if net.is_multicast or net.is_loopback or net.is_link_local: + raise PrivError('NETWORK_CIDR_UNSAFE') + if source and not any(net.subnet_of(x) for x in FORWARD_SOURCE_NETS): + raise PrivError('NETWORK_FORWARD_SOURCE_NOT_PRIVATE') + return str(net) + + +def _validate_network_forward_args(args: dict[str, Any]) -> dict[str, Any]: + required={'ingress_interface','egress_interface','source_subnet','destination_subnet','protocol','destination_port','nat','lifetime'} + if set(args)!=required: raise PrivError('NETWORK_FORWARD_ARGS_INVALID') + ingress=_validate_interface(args.get('ingress_interface','')) + egress=_validate_interface(args.get('egress_interface','')) + if ingress==egress: raise PrivError('NETWORK_FORWARD_INTERFACES_MUST_DIFFER') + source=_validate_forward_network(args.get('source_subnet',''),source=True) + destination=_validate_forward_network(args.get('destination_subnet',''),source=False) + protocol=str(args.get('protocol') or '').lower() + if protocol not in FORWARD_PROTOCOLS: raise PrivError('NETWORK_FORWARD_PROTOCOL_INVALID') + port=args.get('destination_port') + if isinstance(port,bool) or not isinstance(port,int) or not (0<=port<=65535): raise PrivError('NETWORK_FORWARD_PORT_INVALID') + if protocol=='any' and port!=0: raise PrivError('NETWORK_FORWARD_PORT_REQUIRES_PROTOCOL') + nat=args.get('nat') + if not isinstance(nat,bool): raise PrivError('NETWORK_FORWARD_NAT_INVALID') + lifetime=str(args.get('lifetime') or '') + if lifetime!='runtime': raise PrivError('NETWORK_FORWARD_LIFETIME_INVALID') + return {'ingress_interface':ingress,'egress_interface':egress,'source_subnet':source,'destination_subnet':destination,'protocol':protocol,'destination_port':port,'nat':nat,'lifetime':'runtime'} + + +def _forward_rule_specs(plan_id: str, args: dict[str, Any]) -> tuple[list[str], list[str] | None]: + args=_validate_network_forward_args(args) + comment='FrameBridge:'+_validate_plan_id(plan_id)+':forward' + common=['-i',args['ingress_interface'],'-o',args['egress_interface'],'-s',args['source_subnet'],'-d',args['destination_subnet']] + proto=[] + if args['protocol']!='any': + proto+=['-p',args['protocol']] + if args['destination_port']>0: + proto+=['--dport',str(args['destination_port'])] + filt=common+proto+['-m','comment','--comment',comment,'-j','ACCEPT'] + nat=None + if args['nat']: + nat=['-s',args['source_subnet'],'-d',args['destination_subnet'],'-o',args['egress_interface']]+proto+['-m','comment','--comment',comment+':nat','-j','MASQUERADE'] + return filt,nat + + +def _run_cmd(runner, argv: list[str]): + return runner(argv,text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,timeout=15) + + +def _ip_forward_state(runner) -> int: + p=_run_cmd(runner,[SYSCTL,'-n','net.ipv4.ip_forward']) + v=(p.stdout or '').strip() + if p.returncode!=0 or v not in ('0','1'): raise PrivError('NETWORK_IP_FORWARD_READ_FAILED') + return int(v) + + +def _set_ip_forward(runner, value: int) -> None: + p=_run_cmd(runner,[SYSCTL,'-w','net.ipv4.ip_forward='+str(int(value))]) + if p.returncode!=0: raise PrivError('NETWORK_IP_FORWARD_SET_FAILED') + if _ip_forward_state(runner)!=int(value): raise PrivError('NETWORK_IP_FORWARD_VERIFY_FAILED') + + +def _iptables_exists(runner, table: str | None, chain: str, spec: list[str]) -> bool: + prefix=[IPTABLES,'-w','5'] + if table: prefix+=['-t',table] + result=_run_cmd(runner,[*prefix,'-C',chain,*spec]) + if result.returncode==0: + return True + if result.returncode!=1: + raise PrivError('FIREWALL_RULE_QUERY_FAILED') + # A missing chain/backend is not evidence that a particular rule is absent. + probe=_run_cmd(runner,[*prefix,'-S',chain]) + if probe.returncode!=0: + raise PrivError('FIREWALL_CHAIN_QUERY_FAILED') + return False + + +def _iptables_add(runner, table: str | None, chain: str, spec: list[str], insert: bool=True) -> None: + argv=[IPTABLES,'-w','5'] + if table: argv+=['-t',table] + argv+=(['-I',chain,'1',*spec] if insert else ['-A',chain,*spec]) + if _run_cmd(runner,argv).returncode!=0: raise PrivError('NETWORK_FORWARD_RULE_ADD_FAILED') + + +def _iptables_delete_if_present(runner, table: str | None, chain: str, spec: list[str]) -> None: + if not _iptables_exists(runner,table,chain,spec): return + argv=[IPTABLES,'-w','5'] + if table: argv+=['-t',table] + argv+=['-D',chain,*spec] + if _run_cmd(runner,argv).returncode!=0: raise PrivError('NETWORK_FORWARD_RULE_DELETE_FAILED') + + +def _ufw_config_enabled_priv(path: Path=Path('/etc/ufw/ufw.conf')) -> bool: + try: + text=path.read_text(encoding='utf-8',errors='replace') + except OSError: + return False + for raw in text.splitlines(): + line=raw.strip() + if not line or line.startswith('#'): + continue + if line.upper().startswith('ENABLED='): + return line.split('=',1)[1].strip().lower() in {'yes','true','1'} + return False + + +def _serialize_pairing_window(fn): + import functools, inspect + @functools.wraps(fn) + def wrapped(*args,**kwargs): + import fcntl + signature=inspect.signature(fn) + bound=signature.bind(*args,**kwargs); bound.apply_defaults() + state=Path(bound.arguments['state_path']) + state.parent.mkdir(parents=True,exist_ok=True,mode=0o700) + lock=state.with_name(state.name+'.lock') + flags=os.O_RDWR|os.O_CREAT|getattr(os,'O_NOFOLLOW',0) + fd=os.open(lock,flags,0o600) + try: + # Timer and monitor serialize with an opening in progress. + fcntl.flock(fd,fcntl.LOCK_EX) + return fn(*args,**kwargs) + finally: + os.close(fd) + return wrapped + + +def _validate_pairing_window_args(interface: str, source_subnet: str, destination_address: str, port: int, network_fingerprint: str, ttl_seconds: int) -> dict[str, Any]: + interface=str(interface or '').strip() + if not IFACE_RE.fullmatch(interface): raise PrivError('PAIRING_WINDOW_INTERFACE_INVALID') + try: source=ipaddress.ip_network(str(source_subnet),strict=False) + except ValueError: raise PrivError('PAIRING_WINDOW_SOURCE_INVALID') from None + if source.version!=4 or source.is_loopback or source.is_link_local or source.is_multicast: + raise PrivError('PAIRING_WINDOW_SOURCE_INVALID') + if not any(source.subnet_of(allowed) for allowed in FORWARD_SOURCE_NETS): + raise PrivError('PAIRING_WINDOW_SOURCE_NOT_PRIVATE') + try: dest=ipaddress.ip_address(str(destination_address)) + except ValueError: raise PrivError('PAIRING_WINDOW_DESTINATION_INVALID') from None + if dest.version!=4 or dest.is_loopback or dest.is_link_local or dest.is_multicast or dest not in source: + raise PrivError('PAIRING_WINDOW_DESTINATION_INVALID') + if isinstance(port,bool) or not isinstance(port,int) or not (1 <= port <= 65535): + raise PrivError('PAIRING_WINDOW_PORT_INVALID') + fp=str(network_fingerprint or '').strip().lower() + if not SHA256_RE.fullmatch(fp): raise PrivError('PAIRING_WINDOW_FINGERPRINT_INVALID') + if isinstance(ttl_seconds,bool) or not isinstance(ttl_seconds,int) or not (PAIRING_WINDOW_MIN_TTL <= ttl_seconds <= PAIRING_WINDOW_MAX_TTL): + raise PrivError('PAIRING_WINDOW_TTL_INVALID') + return { + 'interface':interface, + 'source_subnet':str(source), + 'destination_address':str(dest), + 'port':port, + 'network_fingerprint':fp, + 'ttl_seconds':ttl_seconds, + } + + +def _pairing_window_rule_spec(window_id: str, args: dict[str, Any]) -> list[str]: + comment=f'FrameBridge:{window_id}:pairing' + return [ + '-i',args['interface'], + '-s',args['source_subnet'], + '-d',args['destination_address']+'/32', + '-p','tcp','--dport',str(args['port']), + '-m','comment','--comment',comment, + '-j','ACCEPT', + ] + + +def _interface_has_ipv4(runner, interface: str, address: str) -> bool: + p=_run_cmd(runner,[IP,'-4','-j','addr','show','dev',interface]) + if p.returncode!=0: return False + try: value=json.loads(p.stdout or '[]') + except Exception: return False + for item in value if isinstance(value,list) else []: + for ai in item.get('addr_info') or []: + if ai.get('family')=='inet' and str(ai.get('local') or '')==address: + return True + return False + + +def _pairing_window_rule_present(runner, state: dict[str, Any]) -> bool: + spec=state.get('rule_spec') or [] + if not isinstance(spec,list) or not all(isinstance(x,str) for x in spec): return False + return _iptables_exists(runner,None,'ufw-user-input',spec) + + +def _pairing_window_rule_add(runner, spec: list[str]) -> None: + argv=[IPTABLES,'-w','5','-I','ufw-user-input','1',*spec] + if _run_cmd(runner,argv).returncode!=0: raise PrivError('PAIRING_WINDOW_RULE_ADD_FAILED') + + +def _pairing_window_rule_delete_if_present(runner, spec: list[str]) -> None: + if not _iptables_exists(runner,None,'ufw-user-input',spec): return + argv=[IPTABLES,'-w','5','-D','ufw-user-input',*spec] + if _run_cmd(runner,argv).returncode!=0: raise PrivError('PAIRING_WINDOW_RULE_DELETE_FAILED') + + +@_serialize_pairing_window +def pairing_window_status(state_path: Path=PAIRING_WINDOW_STATE, runner=subprocess.run) -> dict[str, Any]: + if not state_path.exists(): + return {'ok':True,'schema':1,'state':'closed','active':False,'rule_present':False} + state=_read_json(state_path) + rule_present=_pairing_window_rule_present(runner,state) + result=dict(state) + result.update({'ok':True,'rule_present':rule_present,'active':state.get('state')=='active' and rule_present}) + expires=str(state.get('expires_at') or '') + try: + exp=datetime.fromisoformat(expires) + if exp.tzinfo is None: exp=exp.replace(tzinfo=timezone.utc) + result['expired']=datetime.now(timezone.utc)>=exp.astimezone(timezone.utc) + except Exception: + result['expired']=False + return result + + +@_serialize_pairing_window +def pairing_window_open( + interface: str, source_subnet: str, destination_address: str, port: int, + network_fingerprint: str, ttl_seconds: int, + state_path: Path=PAIRING_WINDOW_STATE, runner=subprocess.run, + stdin_isatty: bool | None=None, +) -> dict[str, Any]: + require_interactive_tty(stdin_isatty) + args=_validate_pairing_window_args(interface,source_subnet,destination_address,port,network_fingerprint,ttl_seconds) + previous=_read_json(state_path) if state_path.exists() else None + if previous and previous.get('state') in {'active','opening'}: + raise PrivError('PAIRING_WINDOW_ALREADY_ACTIVE') + if not _ufw_config_enabled_priv(): + raise PrivError('PAIRING_WINDOW_UFW_NOT_ENABLED') + if _run_cmd(runner,[IPTABLES,'-w','5','-S','ufw-user-input']).returncode!=0: + raise PrivError('PAIRING_WINDOW_UFW_CHAIN_MISSING') + if not _interface_has_ipv4(runner,args['interface'],args['destination_address']): + raise PrivError('PAIRING_WINDOW_DESTINATION_NOT_ON_INTERFACE') + window_id=secrets.token_hex(16) + spec=_pairing_window_rule_spec(window_id,args) + if _iptables_exists(runner,None,'ufw-user-input',spec): + raise PrivError('PAIRING_WINDOW_RULE_ALREADY_EXISTS') + now=datetime.now(timezone.utc) + expires=now+timedelta(seconds=args['ttl_seconds']) + unit='framebridge-pairing-window-'+window_id[:12] + state={ + 'schema':1,'window_id':window_id,'state':'opening',**args, + 'opened_at':now.isoformat(),'expires_at':expires.isoformat(), + 'timer_unit':unit,'rule_spec':spec,'rule_present':False,'reboot_fail_closed':True, + } + # A storage failure here happens BEFORE any exposure is created. + _atomic_json(state_path,state) + try: + timer=_run_cmd(runner,[ + SYSTEMD_RUN,'--quiet',f'--unit={unit}',f'--on-active={args["ttl_seconds"]}s', + '--timer-property=AccuracySec=1s',PRIV_HELPER_PATH, + 'pairing-window-expire','--window-id',window_id, + ]) + if timer.returncode!=0: + raise PrivError('PAIRING_WINDOW_TIMER_SCHEDULE_FAILED') + _pairing_window_rule_add(runner,spec) + if not _iptables_exists(runner,None,'ufw-user-input',spec): + raise PrivError('PAIRING_WINDOW_RULE_VERIFY_FAILED') + state.update(state='active',rule_present=True) + _atomic_json(state_path,state) + except Exception as original: + # Every catchable failure attempts exact rollback. Never report a clean + # result if the read or deletion fails. Keep timer/state for recovery. + try: + _pairing_window_rule_delete_if_present(runner,spec) + if _iptables_exists(runner,None,'ufw-user-input',spec): + raise PrivError('PAIRING_WINDOW_RULE_DELETE_FAILED') + except Exception as cleanup_error: + raise PrivError('PAIRING_WINDOW_CLEANUP_UNVERIFIED') from cleanup_error + # No exposure remains. A late timer sees closed/no state, never reopens. + try: + _run_cmd(runner,[SYSTEMCTL,'stop',unit+'.timer']) + except Exception: + pass + try: + if previous is not None: _atomic_json(state_path,previous) + else: state_path.unlink(missing_ok=True) + except Exception as state_error: + raise PrivError('PAIRING_WINDOW_STATE_RECOVERY_FAILED') from state_error + raise original + return { + 'ok':True,'schema':1,'state':'active','active':True, + 'window_id':window_id,**args,'opened_at':state['opened_at'], + 'expires_at':state['expires_at'],'timer_unit':unit, + 'rule_present':True,'reboot_fail_closed':True, + } + + +@_serialize_pairing_window +def _pairing_window_close_internal(window_id: str | None, reason: str, state_path: Path, runner) -> dict[str, Any]: + if not state_path.exists(): + return {'ok':True,'schema':1,'state':'closed','active':False,'rule_present':False,'close_reason':'already_absent'} + state=_read_json(state_path) + current_id=str(state.get('window_id') or '') + if window_id is not None and current_id!=str(window_id): raise PrivError('PAIRING_WINDOW_ID_MISMATCH') + if state.get('state') not in {'active','opening'}: + result=dict(state); result.update({'ok':True,'active':False,'rule_present':_pairing_window_rule_present(runner,state)}) + return result + + spec=state.get('rule_spec') or [] + if not isinstance(spec,list) or not all(isinstance(x,str) for x in spec): raise PrivError('PAIRING_WINDOW_STATE_INVALID') + _pairing_window_rule_delete_if_present(runner,spec) + if _iptables_exists(runner,None,'ufw-user-input',spec): raise PrivError('PAIRING_WINDOW_RULE_DELETE_FAILED') + + unit=str(state.get('timer_unit') or '') + if unit and reason!='expired': + _run_cmd(runner,[SYSTEMCTL,'stop',unit+'.timer']) + + state['state']='expired' if reason=='expired' else 'closed' + state['closed_at']=datetime.now(timezone.utc).isoformat() + state['close_reason']=reason + state['rule_present']=False + _atomic_json(state_path,state) + result=dict(state); result.update({'ok':True,'active':False,'rule_present':False}) + return result + + +def pairing_window_close(reason: str='user', state_path: Path=PAIRING_WINDOW_STATE, runner=subprocess.run) -> dict[str, Any]: + reason=str(reason or '').strip().lower() + if reason not in {'user','cancelled','paired','denied','network_change','service_stop','reconcile'}: + raise PrivError('PAIRING_WINDOW_CLOSE_REASON_INVALID') + return _pairing_window_close_internal(None,reason,state_path,runner) + + +def pairing_window_expire(window_id: str, state_path: Path=PAIRING_WINDOW_STATE, runner=subprocess.run) -> dict[str, Any]: + window_id=str(window_id or '').strip().lower() + if not re.fullmatch(r'[a-f0-9]{32}',window_id): raise PrivError('PAIRING_WINDOW_ID_INVALID') + return _pairing_window_close_internal(window_id,'expired',state_path,runner) + + +def _active_forward_transaction_exists(tx_dir: Path) -> bool: + if not tx_dir.exists(): return False + for path in tx_dir.glob('*.json'): + try: tx=_read_json(path) + except Exception: continue + if tx.get('capability')=='network.forward' and tx.get('rollback_available') is True: return True + return False + + +def _apply_network_forward_runtime(plan_id: str, args: dict[str, Any], runner=subprocess.run, interface_root: Path=Path('/sys/class/net')) -> tuple[dict[str, Any],dict[str, Any],dict[str, Any]]: + args=_validate_network_forward_args(args) + for name in (args['ingress_interface'],args['egress_interface']): + if not (interface_root/name).exists(): raise PrivError('NETWORK_FORWARD_INTERFACE_NOT_PRESENT') + if not _ufw_config_enabled_priv(): raise PrivError('NETWORK_FORWARD_UFW_NOT_ENABLED') + if _run_cmd(runner,[IPTABLES,'-w','5','-S','ufw-user-forward']).returncode!=0: raise PrivError('NETWORK_FORWARD_UFW_CHAIN_MISSING') + filt,nat=_forward_rule_specs(plan_id,args) + if _iptables_exists(runner,None,'ufw-user-forward',filt): raise PrivError('NETWORK_FORWARD_RULE_ALREADY_EXISTS') + if nat is not None and _iptables_exists(runner,'nat','POSTROUTING',nat): raise PrivError('NETWORK_FORWARD_NAT_RULE_ALREADY_EXISTS') + before_ip=_ip_forward_state(runner) + added_filter=False; added_nat=False; changed_ip=False + try: + _iptables_add(runner,None,'ufw-user-forward',filt,insert=True); added_filter=True + if nat is not None: + _iptables_add(runner,'nat','POSTROUTING',nat,insert=False); added_nat=True + if before_ip!=1: + _set_ip_forward(runner,1); changed_ip=True + if not _iptables_exists(runner,None,'ufw-user-forward',filt): raise PrivError('NETWORK_FORWARD_RULE_VERIFY_FAILED') + if nat is not None and not _iptables_exists(runner,'nat','POSTROUTING',nat): raise PrivError('NETWORK_FORWARD_NAT_VERIFY_FAILED') + if _ip_forward_state(runner)!=1: raise PrivError('NETWORK_IP_FORWARD_VERIFY_FAILED') + except Exception: + if added_nat: _iptables_delete_if_present(runner,'nat','POSTROUTING',nat or []) + if added_filter: _iptables_delete_if_present(runner,None,'ufw-user-forward',filt) + if changed_ip: _set_ip_forward(runner,before_ip) + raise + before={'ip_forward':before_ip,'filter_rule':filt,'nat_rule':nat,'runtime_only':True} + after={'ip_forward':1,'filter_rule_present':True,'nat_rule_present':nat is not None} + verification={'ip_forward':1,'filter_rule_present':True,'nat_rule_present':nat is not None,'runtime_only':True} + return before,after,verification + + +def _rollback_network_forward_runtime(before: dict[str, Any], runner=subprocess.run) -> dict[str, Any]: + filt=list(before.get('filter_rule') or []) + nat_raw=before.get('nat_rule'); nat=list(nat_raw) if isinstance(nat_raw,list) else None + prior=int(before.get('ip_forward',0)) + if nat is not None: _iptables_delete_if_present(runner,'nat','POSTROUTING',nat) + if filt: _iptables_delete_if_present(runner,None,'ufw-user-forward',filt) + if _ip_forward_state(runner)!=prior: _set_ip_forward(runner,prior) + filter_present=_iptables_exists(runner,None,'ufw-user-forward',filt) if filt else False + nat_present=_iptables_exists(runner,'nat','POSTROUTING',nat) if nat is not None else False + current_ip=_ip_forward_state(runner) + return {'verified':(not filter_present and not nat_present and current_ip==prior),'ip_forward':current_ip,'filter_rule_present':filter_present,'nat_rule_present':nat_present} + + +def _validate_hostname(v: str) -> str: + v=str(v).strip() + if not v or len(v)>253 or v.endswith('.') or '..' in v: + raise PrivError('HOSTNAME_INVALID') + for label in v.split('.'): + if not (1 <= len(label) <= 63): + raise PrivError('HOSTNAME_INVALID') + if label[0]=='-' or label[-1]=='-' or re.fullmatch(r'[A-Za-z0-9-]+',label) is None: + raise PrivError('HOSTNAME_INVALID') + return v + +def _validate_timezone(v: str, zoneinfo_root: Path=ZONEINFO_ROOT) -> str: + v=str(v).strip() + if not v or len(v)>128 or v.startswith('/') or '\x00' in v: + raise PrivError('TIMEZONE_INVALID') + if any(part in ('','.','..') for part in Path(v).parts): + raise PrivError('TIMEZONE_INVALID') + if re.fullmatch(r'[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*',v) is None: + raise PrivError('TIMEZONE_INVALID') + root=zoneinfo_root.resolve(strict=False) + candidate=(root/v).resolve(strict=False) + try: + if os.path.commonpath([str(root),str(candidate)]) != str(root): + raise PrivError('TIMEZONE_INVALID') + except ValueError: + raise PrivError('TIMEZONE_INVALID') from None + if not candidate.is_file(): + raise PrivError('TIMEZONE_NOT_FOUND') + return v + +def canonical_args_sha(args: dict[str, Any]) -> str: + raw=json.dumps(args,sort_keys=True,separators=(',',':'),ensure_ascii=True).encode('utf-8') + return hashlib.sha256(raw).hexdigest() + + +def _validate_mode(v: str) -> str: + v=str(v or '').strip() + if MODE_RE.fullmatch(v) is None: raise PrivError('MODE_INVALID') + mode=int(v,8) + if mode<0 or mode>0o7777: raise PrivError('MODE_INVALID') + return v + + +def _validate_system_path(v: str) -> Path: + v=str(v or '').strip() + if not v or len(v)>4096 or not v.startswith('/'): raise PrivError('SYSTEM_PATH_INVALID') + p=Path(v) + if any(part in ('','.','..') for part in p.parts[1:]): raise PrivError('SYSTEM_PATH_INVALID') + norm=os.path.normpath(v) + if norm in SYSTEM_PATH_FORBIDDEN_EXACT: raise PrivError('SYSTEM_PATH_FORBIDDEN') + if norm.startswith('/etc/sudoers.d/') or norm.startswith('/etc/ssl/private/') or norm.startswith('/root/.ssh/') or norm.startswith('/etc/ssh/ssh_host_'): + raise PrivError('SYSTEM_PATH_FORBIDDEN') + for prefix in SYSTEM_PATH_FORBIDDEN_PREFIXES: + if norm==prefix or norm.startswith(prefix+'/'): raise PrivError('SYSTEM_PATH_FORBIDDEN') + path=Path(norm) + if path.is_symlink(): raise PrivError('SYSTEM_PATH_SYMLINK_FORBIDDEN') + parent=path.parent + while parent != Path('/'): + if parent.exists() and parent.is_symlink(): raise PrivError('SYSTEM_PATH_PARENT_SYMLINK_FORBIDDEN') + parent=parent.parent + return path + + +def package_candidate(package: str, runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> str | None: + package=_validate_package(package) + p=runner(['/usr/bin/apt-cache','policy',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: return None + for line in p.stdout.splitlines(): + m=re.match(r'^\s*Candidate:\s*(\S+)\s*$',line) + if m: + value=m.group(1) + return None if value=='(none)' else value + return None + + +def _file_state(path: Path) -> dict[str, Any]: + if not path.exists(): + return {'exists':False} + if path.is_symlink() or not path.is_file(): raise PrivError('SYSTEM_FILE_NOT_REGULAR') + data=path.read_bytes() + if len(data)>MAX_SYSTEM_TEXT_BYTES: raise PrivError('SYSTEM_FILE_BACKUP_TOO_LARGE') + st=path.stat() + return { + 'exists':True, + 'content_b64':base64.b64encode(data).decode('ascii'), + 'sha256':hashlib.sha256(data).hexdigest(), + 'mode':st.st_mode & 0o7777, + 'uid':st.st_uid, + 'gid':st.st_gid, + } + + +def _atomic_write_system_file(path: Path, data: bytes, before: dict[str, Any]) -> None: + if not path.parent.is_dir(): raise PrivError('SYSTEM_FILE_PARENT_MISSING') + fd,tmp=tempfile.mkstemp(prefix='.'+path.name+'.framebridge-',dir=str(path.parent)) + try: + with os.fdopen(fd,'wb') as f: + f.write(data); f.flush(); os.fsync(f.fileno()) + if before.get('exists'): + os.chmod(tmp,int(before['mode'])) + os.chown(tmp,int(before['uid']),int(before['gid'])) + else: + os.chmod(tmp,0o644) + os.chown(tmp,os.geteuid(),os.getegid()) + os.replace(tmp,path) + finally: + if os.path.exists(tmp): os.unlink(tmp) + + +def _directory_state(path: Path) -> dict[str, Any]: + if not path.exists(): return {'exists':False} + if path.is_symlink() or not path.is_dir(): raise PrivError('SYSTEM_DIRECTORY_NOT_DIRECTORY') + st=path.stat() + return {'exists':True,'mode':st.st_mode & 0o7777,'uid':st.st_uid,'gid':st.st_gid} + + +def _atomic_json(path: Path, value: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + tmp=path.with_name('.'+path.name+'.tmp') + tmp.write_text(json.dumps(value,sort_keys=True,indent=2)+'\n') + os.chmod(tmp,0o600) + os.replace(tmp,path) + + +def _read_json(path: Path) -> dict[str, Any]: + try: v=json.loads(path.read_text()) + except FileNotFoundError: raise PrivError('STATE_NOT_FOUND') + except Exception: raise PrivError('STATE_INVALID_JSON') + if not isinstance(v,dict): raise PrivError('STATE_NOT_OBJECT') + return v + + +def require_root() -> None: + if os.geteuid()!=0: raise PrivError('ROOT_REQUIRED') + + +def require_interactive_tty(stdin_isatty: bool | None = None) -> None: + ok=sys.stdin.isatty() if stdin_isatty is None else bool(stdin_isatty) + if not ok: raise PrivError('INTERACTIVE_TTY_REQUIRED') + + +def _now(value: datetime | None = None) -> datetime: + result=value or datetime.now(timezone.utc) + if result.tzinfo is None or result.utcoffset() is None: raise PrivError('TIMEZONE_REQUIRED') + return result.astimezone(timezone.utc) + + +def _validate_challenge(v: str) -> str: + v=str(v).strip().lower() + if not CHALLENGE_RE.fullmatch(v): raise PrivError('REMOTE_CHALLENGE_ID_INVALID') + return v + + +def _validate_key_id(v: str) -> str: + v=str(v).strip() + if not KEY_ID_RE.fullmatch(v): raise PrivError('REMOTE_KEY_ID_INVALID') + return v + + +def _approval_record_valid(ap: dict[str, Any], kind: str, plan_sha256: str, package: str) -> bool: + common=(ap.get('kind')==kind and ap.get('one_shot') is True and ap.get('consumed') is False and ap.get('plan_sha256')==plan_sha256 and ap.get('package')==package) + if not common: return False + if ap.get('source')=='LOCAL_TTY': return ap.get('schema')==1 + if ap.get('source')==REMOTE_SOURCE: + return (ap.get('schema')==2 and ap.get('auth_method')=='ecdsa-p256' and CHALLENGE_RE.fullmatch(str(ap.get('challenge_id') or '')) is not None and KEY_ID_RE.fullmatch(str(ap.get('key_id') or '')) is not None) + if ap.get('source')==TOTP_SOURCE: + return (ap.get('schema')==3 and ap.get('auth_method')=='totp-sha1-6-30' and CHALLENGE_RE.fullmatch(str(ap.get('challenge_id') or '')) is not None and KEY_ID_RE.fullmatch(str(ap.get('key_id') or '')) is not None and type(ap.get('totp_step')) is int) + return False + + +def package_status(package: str, runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> dict[str, Any]: + package=_validate_package(package) + p=runner(['/usr/bin/dpkg-query','-W','-f=${Status}|${Version}',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: return {'installed':False,'version':None} + out=p.stdout.strip() + if not out.startswith('install ok installed|'): return {'installed':False,'version':None} + return {'installed':True,'version':out.split('|',1)[1] if '|' in out else None} + + +def approve_install(plan_id: str, plan_sha256: str, package: str, approval_dir: Path=APPROVAL_DIR, stdin_isatty: bool | None=None) -> dict[str, Any]: + require_interactive_tty(stdin_isatty) + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + rec={'schema':1,'kind':'package.install','plan_id':plan_id,'plan_sha256':plan_sha256,'package':package,'source':'LOCAL_TTY','one_shot':True,'consumed':False,'approved_at':utc_now()} + _atomic_json(approval_dir/(plan_id+'.install.json'),rec) + return rec + + +def apply_install(plan_id: str, plan_sha256: str, package: str, approval_dir: Path=APPROVAL_DIR, tx_dir: Path=TX_DIR, runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> dict[str, Any]: + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + ap_path=approval_dir/(plan_id+'.install.json'); ap=_read_json(ap_path) + checks={ + 'kind':ap.get('kind')=='package.install', + 'approval':_approval_record_valid(ap,'package.install',plan_sha256,package), + 'plan_sha':ap.get('plan_sha256')==plan_sha256, + 'package':ap.get('package')==package, + } + if not all(checks.values()): raise PrivError('INSTALL_APPROVAL_PRECONDITION_FAILED') + before=package_status(package,runner) + if before['installed']: raise PrivError('PACKAGE_ALREADY_INSTALLED') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + p=runner(['/usr/bin/apt-get','-y','--no-install-recommends','install',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if p.returncode!=0: raise PrivError('APT_INSTALL_FAILED') + after=package_status(package,runner) + if not after['installed']: raise PrivError('PACKAGE_INSTALL_VERIFY_FAILED') + ap['consumed']=True; ap['consumed_at']=utc_now(); _atomic_json(ap_path,ap) + tx={'schema':1,'plan_id':plan_id,'plan_sha256':plan_sha256,'package':package,'before':before,'after':after,'applied_at':utc_now(),'rollback_available':True} + _atomic_json(tx_dir/(plan_id+'.json'),tx) + return {'ok':True,'package':package,'version':after['version'],'approval_consumed':True,'rollback_available':True} + + +def approve_rollback(plan_id: str, plan_sha256: str, package: str, approval_dir: Path=APPROVAL_DIR, tx_dir: Path=TX_DIR, stdin_isatty: bool | None=None) -> dict[str, Any]: + require_interactive_tty(stdin_isatty) + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + tx=_read_json(tx_dir/(plan_id+'.json')) + if tx.get('plan_sha256')!=plan_sha256 or tx.get('package')!=package or tx.get('rollback_available') is not True: raise PrivError('ROLLBACK_TRANSACTION_PRECONDITION_FAILED') + rec={'schema':1,'kind':'package.rollback','plan_id':plan_id,'plan_sha256':plan_sha256,'package':package,'source':'LOCAL_TTY','one_shot':True,'consumed':False,'approved_at':utc_now()} + _atomic_json(approval_dir/(plan_id+'.rollback.json'),rec) + return rec + + +def apply_rollback(plan_id: str, plan_sha256: str, package: str, approval_dir: Path=APPROVAL_DIR, tx_dir: Path=TX_DIR, runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> dict[str, Any]: + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + ap_path=approval_dir/(plan_id+'.rollback.json'); ap=_read_json(ap_path); tx_path=tx_dir/(plan_id+'.json'); tx=_read_json(tx_path) + checks={ + 'approval':_approval_record_valid(ap,'package.rollback',plan_sha256,package), + 'plan_sha':ap.get('plan_sha256')==plan_sha256==tx.get('plan_sha256'),'package':ap.get('package')==package==tx.get('package'), + 'rollback_available':tx.get('rollback_available') is True,'before_absent':(tx.get('before') or {}).get('installed') is False, + } + if not all(checks.values()): raise PrivError('ROLLBACK_APPROVAL_PRECONDITION_FAILED') + current=package_status(package,runner) + expected_version=(tx.get('after') or {}).get('version') + if not current['installed']: raise PrivError('PACKAGE_NOT_INSTALLED') + if current.get('version')!=expected_version: raise PrivError('PACKAGE_VERSION_CHANGED') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + p=runner(['/usr/bin/apt-get','-y','remove',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if p.returncode!=0: raise PrivError('APT_REMOVE_FAILED') + after=package_status(package,runner) + if after['installed']: raise PrivError('PACKAGE_ROLLBACK_VERIFY_FAILED') + ap['consumed']=True; ap['consumed_at']=utc_now(); _atomic_json(ap_path,ap) + tx['rollback_available']=False; tx['rolled_back_at']=utc_now(); tx['rollback_verified']=True; _atomic_json(tx_path,tx) + return {'ok':True,'package':package,'absent_after_rollback':True,'rollback_approval_consumed':True,'rollback_verified':True} + +def enroll_totp_key(key_id: str, node_label: str, issuer: str='FrameBridge', key_dir: Path=TOTP_KEY_DIR, stdin_isatty: bool | None=None) -> dict[str,Any]: + require_interactive_tty(stdin_isatty) + key_id=_validate_key_id(key_id) + node_label=str(node_label).strip(); issuer=str(issuer).strip() + if not node_label or len(node_label)>128 or any(c in node_label for c in "\r\n\x00"): raise PrivError('TOTP_NODE_LABEL_INVALID') + if not issuer or len(issuer)>64 or any(c in issuer for c in "\r\n\x00"): raise PrivError('TOTP_ISSUER_INVALID') + key_dir.mkdir(parents=True,exist_ok=True,mode=0o700) + os.chmod(key_dir,0o700) + path=key_dir/(key_id+'.b32') + if path.exists(): raise PrivError('TOTP_KEY_ALREADY_EXISTS') + secret=secrets.token_bytes(20) + secret_b32=base64.b32encode(secret).decode('ascii').rstrip('=') + tmp=path.with_name('.'+path.name+'.tmp') + tmp.write_text(secret_b32+'\n'); os.chmod(tmp,0o600); os.replace(tmp,path) + label=quote(f'{issuer}:{node_label}',safe='') + uri=f'otpauth://totp/{label}?secret={secret_b32}&issuer={quote(issuer,safe="")}&algorithm=SHA1&digits=6&period=30' + return {'ok':True,'schema':1,'key_id':key_id,'node_label':node_label,'issuer':issuer,'otpauth_uri':uri,'secret_sha256':hashlib.sha256(secret).hexdigest(),'enrolled_at':utc_now()} + + +def _b32decode_nopad(value: str) -> bytes: + s=''.join(str(value).strip().upper().split()) + if not s or any(c not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' for c in s): raise PrivError('TOTP_SECRET_INVALID') + s += '='*((8-len(s)%8)%8) + try: return base64.b32decode(s,casefold=True) + except Exception: raise PrivError('TOTP_SECRET_INVALID') from None + + +def _load_totp_secret(key_id: str, key_dir: Path=TOTP_KEY_DIR) -> bytes: + key_id=_validate_key_id(key_id); p=key_dir/(key_id+'.b32') + if not p.is_file() or p.is_symlink(): raise PrivError('TOTP_KEY_NOT_FOUND') + secret=_b32decode_nopad(p.read_text()) + if len(secret)<20: raise PrivError('TOTP_SECRET_TOO_SHORT') + return secret + + +def _hotp(secret: bytes, counter: int) -> str: + digest=hmac.new(secret,struct.pack('>Q',int(counter)),hashlib.sha1).digest(); off=digest[-1]&0x0f + value=((digest[off]&0x7f)<<24)|((digest[off+1]&0xff)<<16)|((digest[off+2]&0xff)<<8)|(digest[off+3]&0xff) + return str(value%1000000).zfill(6) + + +def _verify_totp(secret: bytes, code: str, when: float|None=None, window: int=1) -> int|None: + if not isinstance(code,str) or len(code)!=6 or not code.isdigit(): return None + step=int((time.time() if when is None else float(when))//30) + for s in range(step-window,step+window+1): + if hmac.compare_digest(_hotp(secret,s),code): return s + return None + + +def _consume_totp_step(key_id: str, step: int, challenge_id: str, used_dir: Path=TOTP_USED_DIR) -> None: + used_dir.mkdir(parents=True,exist_ok=True,mode=0o700); p=used_dir/(f'{key_id}-{step}.used') + try: fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) + except FileExistsError: raise PrivError('TOTP_REPLAY') from None + with os.fdopen(fd,'w') as f: f.write(challenge_id+'\n') + + +def create_totp_challenge(kind: str, plan_id: str, plan_sha256: str, package: str, key_id: str, challenge_dir: Path=REMOTE_CHALLENGE_DIR, tx_dir: Path=TX_DIR, key_dir: Path=TOTP_KEY_DIR, ttl_seconds: int=120, now: datetime|None=None) -> dict[str,Any]: + key_id=_validate_key_id(key_id); _load_totp_secret(key_id,key_dir) + if kind not in ('package.install','package.rollback'): raise PrivError('REMOTE_CHALLENGE_KIND_INVALID') + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + if not (30<=int(ttl_seconds)<=300): raise PrivError('REMOTE_CHALLENGE_TTL_INVALID') + if kind=='package.rollback': + tx=_read_json(tx_dir/(plan_id+'.json')) + if tx.get('plan_sha256')!=plan_sha256 or tx.get('package')!=package or tx.get('rollback_available') is not True: raise PrivError('ROLLBACK_TRANSACTION_PRECONDITION_FAILED') + created=_now(now); challenge_dir.mkdir(parents=True,exist_ok=True,mode=0o700) + for path in challenge_dir.glob('*.json'): + try: existing=_read_json(path) + except PrivError: continue + if existing.get('totp_key_id')==key_id and existing.get('state')=='PENDING': + try: exp=datetime.fromisoformat(str(existing.get('expires_at')).replace('Z','+00:00')) + except Exception: continue + if exp.tzinfo is not None and created < exp.astimezone(timezone.utc): raise PrivError('PENDING_TOTP_CHALLENGE_EXISTS') + cid=secrets.token_hex(16) + rec={'schema':1,'kind':kind,'plan_id':plan_id,'plan_sha256':plan_sha256,'package':package,'challenge_id':cid,'totp_key_id':key_id,'created_at':created.isoformat(),'expires_at':(created+timedelta(seconds=int(ttl_seconds))).isoformat(),'state':'PENDING'} + _atomic_json(challenge_dir/(cid+'.json'),rec) + return {'ok':True,**rec} + + +def approve_totp_challenge(challenge_id: str, key_id: str, code: str, challenge_dir: Path=REMOTE_CHALLENGE_DIR, key_dir: Path=TOTP_KEY_DIR, used_dir: Path=TOTP_USED_DIR, approval_dir: Path=APPROVAL_DIR, now: datetime|None=None) -> dict[str,Any]: + challenge_id=_validate_challenge(challenge_id); key_id=_validate_key_id(key_id); ch_path=challenge_dir/(challenge_id+'.json'); ch=_read_json(ch_path) + if ch.get('schema')!=1 or ch.get('challenge_id')!=challenge_id or ch.get('state')!='PENDING' or ch.get('totp_key_id')!=key_id: raise PrivError('TOTP_CHALLENGE_STATE_INVALID') + try: expires=datetime.fromisoformat(str(ch.get('expires_at')).replace('Z','+00:00')) + except Exception: raise PrivError('REMOTE_CHALLENGE_INVALID') from None + current=_now(now) + if current>=expires.astimezone(timezone.utc): raise PrivError('REMOTE_CHALLENGE_EXPIRED') + secret=_load_totp_secret(key_id,key_dir); step=_verify_totp(secret,code,current.timestamp(),window=1) + if step is None: raise PrivError('TOTP_INVALID') + _consume_totp_step(key_id,step,challenge_id,used_dir) + kind=str(ch.get('kind')); suffix='.install.json' if kind=='package.install' else '.rollback.json' + rec={'schema':3,'kind':kind,'plan_id':ch.get('plan_id'),'plan_sha256':ch.get('plan_sha256'),'package':ch.get('package'),'source':TOTP_SOURCE,'auth_method':'totp-sha1-6-30','key_id':key_id,'challenge_id':challenge_id,'totp_step':step,'one_shot':True,'consumed':False,'approved_at':utc_now()} + _atomic_json(approval_dir/(str(ch.get('plan_id'))+suffix),rec) + ch['state']='APPROVED'; ch['approved_at']=rec['approved_at']; ch['key_id']=key_id; ch['totp_step']=step; _atomic_json(ch_path,ch) + return rec + + +def _challenge_message(challenge: dict[str, Any]) -> bytes: + fields={ + 'schema':1, + 'kind':challenge.get('kind'), + 'plan_id':challenge.get('plan_id'), + 'plan_sha256':challenge.get('plan_sha256'), + 'package':challenge.get('package'), + 'challenge_id':challenge.get('challenge_id'), + 'expires_at':challenge.get('expires_at'), + } + return json.dumps(fields,sort_keys=True,separators=(',',':'),ensure_ascii=True).encode('utf-8') + + +def create_remote_challenge(kind: str, plan_id: str, plan_sha256: str, package: str, challenge_dir: Path=REMOTE_CHALLENGE_DIR, tx_dir: Path=TX_DIR, ttl_seconds: int=300, now: datetime | None=None) -> dict[str, Any]: + if kind not in ('package.install','package.rollback'): raise PrivError('REMOTE_CHALLENGE_KIND_INVALID') + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256); package=_validate_package(package) + if not (30 <= int(ttl_seconds) <= 900): raise PrivError('REMOTE_CHALLENGE_TTL_INVALID') + if kind=='package.rollback': + tx=_read_json(tx_dir/(plan_id+'.json')) + if tx.get('plan_sha256')!=plan_sha256 or tx.get('package')!=package or tx.get('rollback_available') is not True: + raise PrivError('ROLLBACK_TRANSACTION_PRECONDITION_FAILED') + created=_now(now) + challenge_id=secrets.token_hex(16) + rec={ + 'schema':1,'kind':kind,'plan_id':plan_id,'plan_sha256':plan_sha256,'package':package, + 'challenge_id':challenge_id,'created_at':created.isoformat(), + 'expires_at':(created+timedelta(seconds=int(ttl_seconds))).isoformat(),'state':'PENDING', + } + _atomic_json(challenge_dir/(challenge_id+'.json'),rec) + result=dict(rec) + result['message_b64']=base64.b64encode(_challenge_message(rec)).decode('ascii') + result['ok']=True + return result + + +def _load_public_key(key_id: str, key_dir: Path) -> Path: + key_id=_validate_key_id(key_id) + p=key_dir/(key_id+'.pem') + if not p.is_file() or p.is_symlink(): raise PrivError('REMOTE_KEY_NOT_FOUND') + return p + + +def _verify_signature(public_key: Path, message: bytes, signature: bytes, runner: Callable[..., subprocess.CompletedProcess[bytes]]=subprocess.run) -> bool: + if len(signature)<8 or len(signature)>256: return False + with tempfile.TemporaryDirectory(prefix='framebridge-remote-verify-') as td: + root=Path(td) + msg=root/'message.bin'; sig=root/'signature.der' + msg.write_bytes(message); sig.write_bytes(signature) + p=runner(['/usr/bin/openssl','dgst','-sha256','-verify',str(public_key),'-signature',str(sig),str(msg)],stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False) + return p.returncode==0 + + +def approve_remote_challenge(challenge_id: str, key_id: str, signature_b64: str, challenge_dir: Path=REMOTE_CHALLENGE_DIR, key_dir: Path=REMOTE_KEY_DIR, approval_dir: Path=APPROVAL_DIR, now: datetime | None=None, runner: Callable[..., subprocess.CompletedProcess[bytes]]=subprocess.run) -> dict[str, Any]: + challenge_id=_validate_challenge(challenge_id); key_id=_validate_key_id(key_id) + ch_path=challenge_dir/(challenge_id+'.json'); ch=_read_json(ch_path) + if ch.get('schema')!=1 or ch.get('challenge_id')!=challenge_id or ch.get('state')!='PENDING': + raise PrivError('REMOTE_CHALLENGE_STATE_INVALID') + try: + expires=datetime.fromisoformat(str(ch.get('expires_at')).replace('Z','+00:00')) + except Exception: + raise PrivError('REMOTE_CHALLENGE_INVALID') from None + if expires.tzinfo is None or expires.utcoffset() is None: raise PrivError('REMOTE_CHALLENGE_INVALID') + if _now(now) >= expires.astimezone(timezone.utc): raise PrivError('REMOTE_CHALLENGE_EXPIRED') + try: + signature=base64.b64decode(str(signature_b64),validate=True) + except Exception: + raise PrivError('REMOTE_SIGNATURE_INVALID') from None + public_key=_load_public_key(key_id,key_dir) + if not _verify_signature(public_key,_challenge_message(ch),signature,runner): + raise PrivError('REMOTE_SIGNATURE_INVALID') + kind=str(ch.get('kind')) + suffix='.install.json' if kind=='package.install' else '.rollback.json' + rec={ + 'schema':2,'kind':kind,'plan_id':ch.get('plan_id'),'plan_sha256':ch.get('plan_sha256'), + 'package':ch.get('package'),'source':REMOTE_SOURCE,'auth_method':'ecdsa-p256', + 'key_id':key_id,'challenge_id':challenge_id,'one_shot':True,'consumed':False,'approved_at':utc_now(), + } + _atomic_json(approval_dir/(str(ch.get('plan_id'))+suffix),rec) + ch['state']='APPROVED'; ch['approved_at']=rec['approved_at']; ch['key_id']=key_id + _atomic_json(ch_path,ch) + return rec + + +def install_remote_public_key(key_id: str, public_key_pem: str, key_dir: Path=REMOTE_KEY_DIR, allow_replace: bool=False) -> dict[str, Any]: + key_id=_validate_key_id(key_id) + if not isinstance(public_key_pem,str) or len(public_key_pem)>4096: raise PrivError('REMOTE_KEY_INVALID') + if 'BEGIN PUBLIC KEY' not in public_key_pem or 'PRIVATE KEY' in public_key_pem: raise PrivError('REMOTE_KEY_INVALID') + key_dir.mkdir(parents=True,exist_ok=True,mode=0o700) + path=key_dir/(key_id+'.pem') + if path.exists() and not allow_replace: raise PrivError('REMOTE_KEY_ALREADY_EXISTS') + tmp=path.with_name('.'+path.name+'.tmp') + tmp.write_text(public_key_pem if public_key_pem.endswith('\n') else public_key_pem+'\n') + os.chmod(tmp,0o600) + check=subprocess.run(['/usr/bin/openssl','pkey','-pubin','-in',str(tmp),'-noout'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False) + if check.returncode!=0: + tmp.unlink(missing_ok=True) + raise PrivError('REMOTE_KEY_INVALID') + os.replace(tmp,path) + import hashlib + return {'ok':True,'key_id':key_id,'public_key_sha256':hashlib.sha256(path.read_bytes()).hexdigest()} + + +def _change_approval_path(plan_id: str, rollback: bool, approval_dir: Path=APPROVAL_DIR) -> Path: + suffix='.change-rollback.json' if rollback else '.change.json' + return approval_dir/(plan_id+suffix) + +def approve_change( + plan_id: str, + plan_sha256: str, + capability: str, + args_sha256: str, + approval_dir: Path=APPROVAL_DIR, + stdin_isatty: bool | None=None, +) -> dict[str, Any]: + require_interactive_tty(stdin_isatty) + plan_id=_validate_plan_id(plan_id) + plan_sha256=_validate_sha(plan_sha256) + capability=_validate_capability(capability) + args_sha256=_validate_args_sha(args_sha256) + rec={ + 'schema':2,'kind':'system.change','plan_id':plan_id, + 'plan_sha256':plan_sha256,'capability':capability,'args_sha256':args_sha256, + 'source':'LOCAL_TTY','one_shot':True,'consumed':False,'approved_at':utc_now(), + } + _atomic_json(_change_approval_path(plan_id,False,approval_dir),rec) + return rec + +def _require_change_approval( + plan_id: str, + plan_sha256: str, + capability: str, + args: dict[str, Any], + approval_dir: Path=APPROVAL_DIR, +) -> tuple[Path, dict[str, Any], str]: + plan_id=_validate_plan_id(plan_id) + plan_sha256=_validate_sha(plan_sha256) + capability=_validate_capability(capability) + args_sha=canonical_args_sha(args) + ap_path=_change_approval_path(plan_id,False,approval_dir) + ap=_read_json(ap_path) + checks={ + 'schema':ap.get('schema')==2, + 'kind':ap.get('kind')=='system.change', + 'source':ap.get('source')=='LOCAL_TTY', + 'one_shot':ap.get('one_shot') is True, + 'not_consumed':ap.get('consumed') is False, + 'plan_sha':ap.get('plan_sha256')==plan_sha256, + 'capability':ap.get('capability')==capability, + 'args_sha':ap.get('args_sha256')==args_sha, + } + if not all(checks.values()): raise PrivError('CHANGE_APPROVAL_PRECONDITION_FAILED') + return ap_path,ap,args_sha + +def _consume_approval(path: Path, ap: dict[str, Any]) -> None: + ap['consumed']=True + ap['consumed_at']=utc_now() + _atomic_json(path,ap) + +def package_version_available( + package: str, + version: str, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> bool: + package=_validate_package(package) + version=str(version or '').strip() + if not version: return False + p=runner(['/usr/bin/apt-cache','policy',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: return False + for line in p.stdout.splitlines(): + m=re.match(r'^\s*(?:\*{3}\s*)?(\S+)\s+\d+\s*$',line) + if m and m.group(1)==version: + return True + return False + +def apply_remove( + plan_id: str, + plan_sha256: str, + package: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> dict[str, Any]: + package=_validate_package(package) + args={'package':package} + ap_path,ap,args_sha=_require_change_approval(plan_id,plan_sha256,'package.remove',args,approval_dir) + before=package_status(package,runner) + if not before['installed']: raise PrivError('PACKAGE_NOT_INSTALLED') + if not package_version_available(package,str(before.get('version') or ''),runner): + raise PrivError('PACKAGE_ROLLBACK_VERSION_UNAVAILABLE') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + p=runner(['/usr/bin/apt-get','-y','remove',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if p.returncode!=0: raise PrivError('APT_REMOVE_FAILED') + after=package_status(package,runner) + if after['installed']: raise PrivError('PACKAGE_REMOVE_VERIFY_FAILED') + _consume_approval(ap_path,ap) + tx={ + 'schema':2,'plan_id':plan_id,'plan_sha256':plan_sha256, + 'capability':'package.remove','args_sha256':args_sha,'args':args, + 'before':before,'after':after,'applied_at':utc_now(),'rollback_available':True, + } + _atomic_json(tx_dir/(plan_id+'.json'),tx) + return {'ok':True,'package':package,'removed':True,'approval_consumed':True,'rollback_available':True} + +def service_state( + service: str, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> dict[str, Any]: + service=_validate_service(service) + a=runner(['/usr/bin/systemctl','is-active',service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + e=runner(['/usr/bin/systemctl','is-enabled',service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + return { + 'active':a.stdout.strip() or ('inactive' if a.returncode else 'active'), + 'active_rc':a.returncode, + 'enabled':e.stdout.strip() or 'unknown', + 'enabled_rc':e.returncode, + } + +def apply_service( + plan_id: str, + plan_sha256: str, + service: str, + action: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> dict[str, Any]: + service=_validate_service(service); action=_validate_service_action(action) + args={'service':service,'action':action} + ap_path,ap,args_sha=_require_change_approval(plan_id,plan_sha256,'service.manage',args,approval_dir) + before=service_state(service,runner) + p=runner(['/usr/bin/systemctl',action,service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('SYSTEMCTL_ACTION_FAILED') + after=service_state(service,runner) + if action in ('start','restart') and after['active']!='active': raise PrivError('SERVICE_ACTIVE_VERIFY_FAILED') + if action=='stop' and after['active']=='active': raise PrivError('SERVICE_STOP_VERIFY_FAILED') + if action=='enable' and after['enabled']!='enabled': raise PrivError('SERVICE_ENABLE_VERIFY_FAILED') + if action=='disable' and after['enabled']=='enabled': raise PrivError('SERVICE_DISABLE_VERIFY_FAILED') + _consume_approval(ap_path,ap) + tx={ + 'schema':2,'plan_id':plan_id,'plan_sha256':plan_sha256, + 'capability':'service.manage','args_sha256':args_sha,'args':args, + 'before':before,'after':after,'applied_at':utc_now(),'rollback_available':True, + } + _atomic_json(tx_dir/(plan_id+'.json'),tx) + return {'ok':True,'service':service,'action':action,'after':after,'approval_consumed':True,'rollback_available':True} + +def hostname_state(runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> str: + p=runner(['/usr/bin/hostnamectl','--static'],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0 or not p.stdout.strip(): raise PrivError('HOSTNAME_READ_FAILED') + return p.stdout.strip() + +def apply_hostname( + plan_id: str, + plan_sha256: str, + hostname: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> dict[str, Any]: + hostname=_validate_hostname(hostname) + args={'hostname':hostname} + ap_path,ap,args_sha=_require_change_approval(plan_id,plan_sha256,'system.hostname',args,approval_dir) + before=hostname_state(runner) + p=runner(['/usr/bin/hostnamectl','set-hostname',hostname],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('HOSTNAME_SET_FAILED') + after=hostname_state(runner) + if after!=hostname: raise PrivError('HOSTNAME_VERIFY_FAILED') + _consume_approval(ap_path,ap) + tx={ + 'schema':2,'plan_id':plan_id,'plan_sha256':plan_sha256, + 'capability':'system.hostname','args_sha256':args_sha,'args':args, + 'before':{'hostname':before},'after':{'hostname':after}, + 'applied_at':utc_now(),'rollback_available':True, + } + _atomic_json(tx_dir/(plan_id+'.json'),tx) + return {'ok':True,'hostname':after,'approval_consumed':True,'rollback_available':True} + +def timezone_state(runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run) -> str: + p=runner(['/usr/bin/timedatectl','show','-p','Timezone','--value'],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0 or not p.stdout.strip(): raise PrivError('TIMEZONE_READ_FAILED') + return p.stdout.strip() + +def apply_timezone( + plan_id: str, + plan_sha256: str, + timezone: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, + zoneinfo_root: Path=ZONEINFO_ROOT, +) -> dict[str, Any]: + timezone=_validate_timezone(timezone,zoneinfo_root) + args={'timezone':timezone} + ap_path,ap,args_sha=_require_change_approval(plan_id,plan_sha256,'system.timezone',args,approval_dir) + before=timezone_state(runner) + p=runner(['/usr/bin/timedatectl','set-timezone',timezone],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('TIMEZONE_SET_FAILED') + after=timezone_state(runner) + if after!=timezone: raise PrivError('TIMEZONE_VERIFY_FAILED') + _consume_approval(ap_path,ap) + tx={ + 'schema':2,'plan_id':plan_id,'plan_sha256':plan_sha256, + 'capability':'system.timezone','args_sha256':args_sha,'args':args, + 'before':{'timezone':before},'after':{'timezone':after}, + 'applied_at':utc_now(),'rollback_available':True, + } + _atomic_json(tx_dir/(plan_id+'.json'),tx) + return {'ok':True,'timezone':after,'approval_consumed':True,'rollback_available':True} + +def apply_admin( + plan_id: str, + plan_sha256: str, + capability: str, + args: dict[str, Any], + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, +) -> dict[str, Any]: + plan_id=_validate_plan_id(plan_id) + plan_sha256=_validate_sha(plan_sha256) + capability=_validate_capability(capability) + if capability not in R3_CORE_CAPABILITIES: raise PrivError('CHANGE_CAPABILITY_INVALID') + if not isinstance(args,dict): raise PrivError('CHANGE_ARGS_INVALID') + ap_path,ap,args_sha=_require_change_approval(plan_id,plan_sha256,capability,args,approval_dir) + tx_path=tx_dir/(plan_id+'.json') + if tx_path.exists(): raise PrivError('CHANGE_TRANSACTION_EXISTS') + before: dict[str, Any] = {} + after: dict[str, Any] = {} + rollback_available=False + verification: dict[str, Any] = {} + + if capability=='package.refresh': + if args: raise PrivError('PACKAGE_REFRESH_ARGS_INVALID') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + proc=runner(['/usr/bin/apt-get','update'],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if proc.returncode!=0: raise PrivError('APT_UPDATE_FAILED') + after={'refreshed':True} + verification={'refreshed':True} + + elif capability=='package.upgrade': + if set(args)!={'package'}: raise PrivError('PACKAGE_UPGRADE_ARGS_INVALID') + package=_validate_package(args.get('package','')) + before=package_status(package,runner) + if not before.get('installed'): raise PrivError('PACKAGE_NOT_INSTALLED') + old=str(before.get('version') or '') + candidate=package_candidate(package,runner) + if not candidate or candidate==old: raise PrivError('PACKAGE_NO_UPGRADE_AVAILABLE') + if not package_version_available(package,old,runner): raise PrivError('PACKAGE_ROLLBACK_VERSION_UNAVAILABLE') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + proc=runner(['/usr/bin/apt-get','-y','--no-install-recommends','--only-upgrade','install',package],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if proc.returncode!=0: raise PrivError('APT_UPGRADE_FAILED') + after=package_status(package,runner) + if not after.get('installed') or after.get('version')==old: raise PrivError('PACKAGE_UPGRADE_VERIFY_FAILED') + rollback_available=True + verification={'package':package,'version':after.get('version')} + + elif capability=='system.file.write_text': + if set(args)!={'path','text'}: raise PrivError('SYSTEM_FILE_ARGS_INVALID') + path=_validate_system_path(args.get('path','')) + text=args.get('text') + if not isinstance(text,str): raise PrivError('SYSTEM_FILE_TEXT_INVALID') + data=text.encode('utf-8') + if len(data)>MAX_SYSTEM_TEXT_BYTES: raise PrivError('SYSTEM_FILE_TEXT_TOO_LARGE') + before=_file_state(path) + _atomic_write_system_file(path,data,before) + after=_file_state(path) + wanted=hashlib.sha256(data).hexdigest() + if after.get('sha256')!=wanted: raise PrivError('SYSTEM_FILE_VERIFY_FAILED') + rollback_available=True + verification={'path':str(path),'sha256':wanted} + + elif capability=='system.directory.ensure': + if set(args)!={'path','mode'}: raise PrivError('SYSTEM_DIRECTORY_ARGS_INVALID') + path=_validate_system_path(args.get('path','')) + mode_s=_validate_mode(args.get('mode','')); mode=int(mode_s,8) + before=_directory_state(path) + if not before.get('exists'): + if not path.parent.is_dir(): raise PrivError('SYSTEM_DIRECTORY_PARENT_MISSING') + os.mkdir(path,mode=mode) + os.chmod(path,mode) + else: + os.chmod(path,mode) + after=_directory_state(path) + if not after.get('exists') or int(after.get('mode',-1))!=mode: raise PrivError('SYSTEM_DIRECTORY_VERIFY_FAILED') + rollback_available=(not before.get('exists')) or int(before.get('mode',mode))!=mode + verification={'path':str(path),'mode':after.get('mode')} + + elif capability=='system.file.mode': + if set(args)!={'path','mode'}: raise PrivError('SYSTEM_FILE_MODE_ARGS_INVALID') + path=_validate_system_path(args.get('path','')) + if not path.exists() or path.is_symlink(): raise PrivError('SYSTEM_FILE_NOT_FOUND') + mode_s=_validate_mode(args.get('mode','')); mode=int(mode_s,8) + st=path.stat(); before={'exists':True,'mode':st.st_mode & 0o7777,'uid':st.st_uid,'gid':st.st_gid} + os.chmod(path,mode) + st2=path.stat(); after={'exists':True,'mode':st2.st_mode & 0o7777,'uid':st2.st_uid,'gid':st2.st_gid} + if after['mode']!=mode: raise PrivError('SYSTEM_FILE_MODE_VERIFY_FAILED') + rollback_available=before['mode']!=mode + verification={'path':str(path),'mode':after['mode']} + + elif capability=='system.daemon_reload': + if args: raise PrivError('DAEMON_RELOAD_ARGS_INVALID') + proc=runner(['/usr/bin/systemctl','daemon-reload'],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if proc.returncode!=0: raise PrivError('SYSTEMD_DAEMON_RELOAD_FAILED') + after={'reloaded':True} + verification={'reloaded':True} + + elif capability=='network.forward': + if _active_forward_transaction_exists(tx_dir): raise PrivError('NETWORK_FORWARD_TRANSACTION_ALREADY_ACTIVE') + before,after,verification=_apply_network_forward_runtime(plan_id,args,runner) + rollback_available=True + + else: + raise PrivError('CHANGE_CAPABILITY_INVALID') + + _consume_approval(ap_path,ap) + tx={ + 'schema':3,'plan_id':plan_id,'plan_sha256':plan_sha256, + 'capability':capability,'args_sha256':args_sha,'args':args, + 'before':before,'after':after,'applied_at':utc_now(), + 'rollback_available':rollback_available, + } + _atomic_json(tx_path,tx) + return { + 'ok':True,'capability':capability,'approval_consumed':True, + 'rollback_available':rollback_available,'verification':verification, + } + + +def approve_change_rollback( + plan_id: str, + plan_sha256: str, + capability: str, + args_sha256: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + stdin_isatty: bool | None=None, +) -> dict[str, Any]: + require_interactive_tty(stdin_isatty) + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256) + capability=_validate_capability(capability); args_sha256=_validate_args_sha(args_sha256) + tx=_read_json(tx_dir/(plan_id+'.json')) + checks={ + 'plan_sha':tx.get('plan_sha256')==plan_sha256, + 'capability':tx.get('capability')==capability, + 'args_sha':tx.get('args_sha256')==args_sha256, + 'rollback_available':tx.get('rollback_available') is True, + } + if not all(checks.values()): raise PrivError('CHANGE_ROLLBACK_TRANSACTION_PRECONDITION_FAILED') + rec={ + 'schema':2,'kind':'system.change.rollback','plan_id':plan_id, + 'plan_sha256':plan_sha256,'capability':capability,'args_sha256':args_sha256, + 'source':'LOCAL_TTY','one_shot':True,'consumed':False,'approved_at':utc_now(), + } + _atomic_json(_change_approval_path(plan_id,True,approval_dir),rec) + return rec + +def _restore_service( + before: dict[str, Any], + service: str, + action: str, + runner: Callable[..., subprocess.CompletedProcess[str]], +) -> dict[str, Any]: + if action in ('enable','disable'): + prior=before.get('enabled') + if prior=='enabled': + p=runner(['/usr/bin/systemctl','enable',service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('SERVICE_ROLLBACK_ENABLE_FAILED') + elif prior=='disabled': + p=runner(['/usr/bin/systemctl','disable',service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('SERVICE_ROLLBACK_DISABLE_FAILED') + prior_active=before.get('active') + desired='start' if prior_active=='active' else 'stop' + p=runner(['/usr/bin/systemctl',desired,service],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('SERVICE_ROLLBACK_ACTIVE_FAILED') + return service_state(service,runner) + +def apply_change_rollback( + plan_id: str, + plan_sha256: str, + capability: str, + args_sha256: str, + approval_dir: Path=APPROVAL_DIR, + tx_dir: Path=TX_DIR, + runner: Callable[..., subprocess.CompletedProcess[str]]=subprocess.run, + zoneinfo_root: Path=ZONEINFO_ROOT, +) -> dict[str, Any]: + plan_id=_validate_plan_id(plan_id); plan_sha256=_validate_sha(plan_sha256) + capability=_validate_capability(capability); args_sha256=_validate_args_sha(args_sha256) + ap_path=_change_approval_path(plan_id,True,approval_dir); ap=_read_json(ap_path) + tx_path=tx_dir/(plan_id+'.json'); tx=_read_json(tx_path) + checks={ + 'schema':ap.get('schema')==2,'kind':ap.get('kind')=='system.change.rollback', + 'source':ap.get('source')=='LOCAL_TTY','one_shot':ap.get('one_shot') is True, + 'not_consumed':ap.get('consumed') is False, + 'plan_sha':ap.get('plan_sha256')==plan_sha256==tx.get('plan_sha256'), + 'capability':ap.get('capability')==capability==tx.get('capability'), + 'args_sha':ap.get('args_sha256')==args_sha256==tx.get('args_sha256'), + 'rollback_available':tx.get('rollback_available') is True, + } + if not all(checks.values()): raise PrivError('CHANGE_ROLLBACK_APPROVAL_PRECONDITION_FAILED') + + args=tx.get('args') or {} + before=tx.get('before') or {} + + if capability=='package.remove': + package=_validate_package(args.get('package','')) + version=str(before.get('version') or '') + if not version: raise PrivError('PACKAGE_ROLLBACK_VERSION_MISSING') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + p=runner(['/usr/bin/apt-get','-y','--no-install-recommends','install',package+'='+version],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if p.returncode!=0: raise PrivError('PACKAGE_REINSTALL_FAILED') + current=package_status(package,runner) + verified=current['installed'] and current.get('version')==version + verification={'package':package,'installed':current['installed'],'version':current.get('version')} + elif capability=='service.manage': + service=_validate_service(args.get('service','')) + action=_validate_service_action(args.get('action','')) + current=_restore_service(before,service,action,runner) + verified=(current.get('active')==before.get('active')) + if action in ('enable','disable') and before.get('enabled') in ('enabled','disabled'): + verified=verified and current.get('enabled')==before.get('enabled') + verification={'service':service,'state':current} + elif capability=='system.hostname': + old=_validate_hostname(before.get('hostname','')) + p=runner(['/usr/bin/hostnamectl','set-hostname',old],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('HOSTNAME_ROLLBACK_FAILED') + current=hostname_state(runner); verified=current==old + verification={'hostname':current} + elif capability=='system.timezone': + old=_validate_timezone(before.get('timezone',''),zoneinfo_root) + p=runner(['/usr/bin/timedatectl','set-timezone',old],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False) + if p.returncode!=0: raise PrivError('TIMEZONE_ROLLBACK_FAILED') + current=timezone_state(runner); verified=current==old + verification={'timezone':current} + elif capability=='package.upgrade': + package=_validate_package(args.get('package','')) + version=str(before.get('version') or '') + if not version: raise PrivError('PACKAGE_ROLLBACK_VERSION_MISSING') + env=dict(os.environ); env['DEBIAN_FRONTEND']='noninteractive' + p=runner(['/usr/bin/apt-get','-y','--allow-downgrades','--no-install-recommends','install',package+'='+version],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False,shell=False,env=env) + if p.returncode!=0: raise PrivError('PACKAGE_DOWNGRADE_FAILED') + current=package_status(package,runner) + verified=current.get('installed') is True and current.get('version')==version + verification={'package':package,'version':current.get('version')} + elif capability=='system.file.write_text': + path=_validate_system_path(args.get('path','')) + if before.get('exists'): + data=base64.b64decode(str(before.get('content_b64') or ''),validate=True) + _atomic_write_system_file(path,data,before) + current=_file_state(path) + verified=current.get('sha256')==before.get('sha256') + verification={'path':str(path),'sha256':current.get('sha256')} + else: + if path.exists(): + if path.is_symlink() or not path.is_file(): raise PrivError('SYSTEM_FILE_ROLLBACK_TARGET_INVALID') + path.unlink() + verified=not path.exists() + verification={'path':str(path),'exists':path.exists()} + elif capability=='system.directory.ensure': + path=_validate_system_path(args.get('path','')) + if before.get('exists'): + if not path.is_dir() or path.is_symlink(): raise PrivError('SYSTEM_DIRECTORY_ROLLBACK_TARGET_INVALID') + os.chmod(path,int(before['mode'])) + st=path.stat() + verified=(st.st_mode & 0o7777)==int(before['mode']) + verification={'path':str(path),'mode':st.st_mode & 0o7777} + else: + if path.exists(): + try: os.rmdir(path) + except OSError: raise PrivError('SYSTEM_DIRECTORY_ROLLBACK_NOT_EMPTY') from None + verified=not path.exists() + verification={'path':str(path),'exists':path.exists()} + elif capability=='system.file.mode': + path=_validate_system_path(args.get('path','')) + if not path.exists() or path.is_symlink(): raise PrivError('SYSTEM_FILE_ROLLBACK_TARGET_INVALID') + os.chmod(path,int(before['mode'])) + st=path.stat() + verified=(st.st_mode & 0o7777)==int(before['mode']) + verification={'path':str(path),'mode':st.st_mode & 0o7777} + elif capability=='network.forward': + verification=_rollback_network_forward_runtime(before,runner) + verified=verification.get('verified') is True + else: + raise PrivError('CHANGE_CAPABILITY_INVALID') + + if not verified: raise PrivError('CHANGE_ROLLBACK_VERIFY_FAILED') + _consume_approval(ap_path,ap) + tx['rollback_available']=False + tx['rolled_back_at']=utc_now() + tx['rollback_verified']=True + _atomic_json(tx_path,tx) + return {'ok':True,'capability':capability,'rollback_verified':True,'rollback_approval_consumed':True,'verification':verification} + + +def emit(v: Any) -> None: print(json.dumps(v,sort_keys=True,indent=2)) + + +def main() -> int: + require_root() + p=argparse.ArgumentParser(prog='framebridge-seed-priv') + sp=p.add_subparsers(dest='cmd',required=True) + for name in ('approve-install','apply-install','approve-rollback','apply-rollback'): + x=sp.add_parser(name); x.add_argument('--plan-id',required=True); x.add_argument('--plan-sha256',required=True); x.add_argument('--package',required=True) + c=sp.add_parser('remote-challenge') + c.add_argument('--kind',required=True,choices=('package.install','package.rollback')); c.add_argument('--plan-id',required=True); c.add_argument('--plan-sha256',required=True); c.add_argument('--package',required=True); c.add_argument('--ttl',type=int,default=300) + r=sp.add_parser('approve-remote-challenge') + r.add_argument('--challenge-id',required=True); r.add_argument('--key-id',required=True); r.add_argument('--signature-b64',required=True) + te=sp.add_parser('totp-enroll') + te.add_argument('--key-id',required=True); te.add_argument('--node-label',required=True); te.add_argument('--issuer',default='FrameBridge') + tc=sp.add_parser('totp-challenge') + tc.add_argument('--kind',required=True,choices=('package.install','package.rollback')); tc.add_argument('--plan-id',required=True); tc.add_argument('--plan-sha256',required=True); tc.add_argument('--package',required=True); tc.add_argument('--key-id',required=True); tc.add_argument('--ttl',type=int,default=120) + ta=sp.add_parser('approve-totp-challenge') + ta.add_argument('--challenge-id',required=True); ta.add_argument('--key-id',required=True) + for name in ('approve-change','approve-change-rollback','apply-change-rollback'): + x=sp.add_parser(name) + x.add_argument('--plan-id',required=True) + x.add_argument('--plan-sha256',required=True) + x.add_argument('--capability',required=True) + x.add_argument('--args-sha256',required=True) + + x=sp.add_parser('apply-remove') + x.add_argument('--plan-id',required=True); x.add_argument('--plan-sha256',required=True); x.add_argument('--package',required=True) + + x=sp.add_parser('apply-service') + x.add_argument('--plan-id',required=True); x.add_argument('--plan-sha256',required=True) + x.add_argument('--service',required=True); x.add_argument('--action',required=True) + + x=sp.add_parser('apply-hostname') + x.add_argument('--plan-id',required=True); x.add_argument('--plan-sha256',required=True); x.add_argument('--hostname',required=True) + + x=sp.add_parser('apply-timezone') + x.add_argument('--plan-id',required=True); x.add_argument('--plan-sha256',required=True); x.add_argument('--timezone',required=True) + + x=sp.add_parser('apply-admin') + x.add_argument('--plan-id',required=True) + x.add_argument('--plan-sha256',required=True) + x.add_argument('--capability',required=True) + x.add_argument('--args-sha256',required=True) + + pw=sp.add_parser('pairing-window-open') + pw.add_argument('--interface',required=True) + pw.add_argument('--source-subnet',required=True) + pw.add_argument('--destination-address',required=True) + pw.add_argument('--port',required=True,type=int) + pw.add_argument('--network-fingerprint',required=True) + pw.add_argument('--ttl',required=True,type=int) + + sp.add_parser('pairing-window-status') + pwc=sp.add_parser('pairing-window-close') + pwc.add_argument('--reason',default='user') + pwe=sp.add_parser('pairing-window-expire') + pwe.add_argument('--window-id',required=True) + + a=p.parse_args() + try: + if a.cmd=='approve-install': emit(approve_install(a.plan_id,a.plan_sha256,a.package)) + elif a.cmd=='apply-install': emit(apply_install(a.plan_id,a.plan_sha256,a.package)) + elif a.cmd=='approve-rollback': emit(approve_rollback(a.plan_id,a.plan_sha256,a.package)) + elif a.cmd=='apply-rollback': emit(apply_rollback(a.plan_id,a.plan_sha256,a.package)) + elif a.cmd=='apply-admin': + try: + args_value=json.loads(sys.stdin.read() or '{}') + except Exception: + raise PrivError('CHANGE_ARGS_INVALID_JSON') + if not isinstance(args_value,dict): raise PrivError('CHANGE_ARGS_INVALID') + if canonical_args_sha(args_value)!=_validate_args_sha(a.args_sha256): + raise PrivError('CHANGE_ARGS_HASH_MISMATCH') + emit(apply_admin(a.plan_id,a.plan_sha256,a.capability,args_value)) + elif a.cmd=='pairing-window-open': emit(pairing_window_open(a.interface,a.source_subnet,a.destination_address,a.port,a.network_fingerprint,a.ttl)) + elif a.cmd=='pairing-window-status': emit(pairing_window_status()) + elif a.cmd=='pairing-window-close': emit(pairing_window_close(a.reason)) + elif a.cmd=='pairing-window-expire': emit(pairing_window_expire(a.window_id)) + elif a.cmd=='approve-change': emit(approve_change(a.plan_id,a.plan_sha256,a.capability,a.args_sha256)) + elif a.cmd=='approve-change-rollback': emit(approve_change_rollback(a.plan_id,a.plan_sha256,a.capability,a.args_sha256)) + elif a.cmd=='apply-change-rollback': emit(apply_change_rollback(a.plan_id,a.plan_sha256,a.capability,a.args_sha256)) + elif a.cmd=='apply-remove': emit(apply_remove(a.plan_id,a.plan_sha256,a.package)) + elif a.cmd=='apply-service': emit(apply_service(a.plan_id,a.plan_sha256,a.service,a.action)) + elif a.cmd=='apply-hostname': emit(apply_hostname(a.plan_id,a.plan_sha256,a.hostname)) + elif a.cmd=='apply-timezone': emit(apply_timezone(a.plan_id,a.plan_sha256,a.timezone)) + elif a.cmd=='remote-challenge': emit(create_remote_challenge(a.kind,a.plan_id,a.plan_sha256,a.package,ttl_seconds=a.ttl)) + elif a.cmd=='approve-remote-challenge': emit(approve_remote_challenge(a.challenge_id,a.key_id,a.signature_b64)) + elif a.cmd=='totp-enroll': emit(enroll_totp_key(a.key_id,a.node_label,a.issuer)) + elif a.cmd=='totp-challenge': emit(create_totp_challenge(a.kind,a.plan_id,a.plan_sha256,a.package,a.key_id,ttl_seconds=a.ttl)) + elif a.cmd=='approve-totp-challenge': + code=sys.stdin.readline().strip() + emit(approve_totp_challenge(a.challenge_id,a.key_id,code)) + return 0 + except PrivError as e: + emit({'ok':False,'error':str(e)}); return 2 + + +if __name__=='__main__': raise SystemExit(main()) diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py new file mode 100644 index 0000000..b047b0c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py @@ -0,0 +1,349 @@ +from __future__ import annotations + +import hashlib +import json +import os +import re +import sqlite3 +import tempfile +import threading +import uuid +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from . import __version__ + +SECRET_KEY_RE = re.compile(r"(secret|token|password|passwd|api[_-]?key|private[_-]?key|credential|authorization|cookie)", re.I) +TOKEN_RE = re.compile(r"(?i)\\b(?:sk-[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9_]{10,}|github_pat_[A-Za-z0-9_]{10,}|xox[baprs]-[A-Za-z0-9-]{10,})\\b") + +class ProductError(RuntimeError): + pass + +class PolicyDenied(ProductError): + pass + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + +def sha256_text(text: str) -> str: + return sha256_bytes(text.encode("utf-8")) + +def canonical_json(value: Any) -> str: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + +def redact(value: Any, depth: int = 0) -> Any: + if depth > 8: + return "[DEPTH_LIMIT]" + if isinstance(value, dict): + out = {} + for key, item in value.items(): + out[str(key)] = "[REDACTED]" if SECRET_KEY_RE.search(str(key)) else redact(item, depth + 1) + return out + if isinstance(value, list): + return [redact(x, depth + 1) for x in value[:200]] + if isinstance(value, tuple): + return [redact(x, depth + 1) for x in value[:200]] + if isinstance(value, str): + clean = TOKEN_RE.sub("[REDACTED TOKEN]", value) + return clean[:8192] + ("[TRUNCATED]" if len(clean) > 8192 else "") + if isinstance(value, (int, float, bool)) or value is None: + return value + return redact(str(value), depth + 1) + +@dataclass(frozen=True) +class PolicyDecision: + allowed: bool + mode: str + reason: str + +class PolicyEngine: + VALID = {"allow", "deny", "require_authorized"} + def __init__(self, policy: dict[str, Any] | None = None): + policy = policy or {} + self.default = str(policy.get("default", "deny")).strip().lower() + if self.default not in self.VALID: + self.default = "deny" + self.tools = {} + for key, value in (policy.get("tools") or {}).items(): + mode = str(value).strip().lower() + self.tools[str(key)] = mode if mode in self.VALID else "deny" + def decide(self, tool: str, authorized: bool = False) -> PolicyDecision: + mode = self.tools.get(tool, self.default) + if mode == "allow": + return PolicyDecision(True, mode, "POLICY_ALLOW") + if mode == "require_authorized" and authorized is True: + return PolicyDecision(True, mode, "AUTHORIZED_TRUE") + if mode == "require_authorized": + return PolicyDecision(False, mode, "AUTHORIZED_TRUE_REQUIRED") + return PolicyDecision(False, mode, "POLICY_DENY") + +class AuditLog: + def __init__(self, path: Path): + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._lock = threading.Lock() + def verify(self) -> dict[str, Any]: + previous = "GENESIS" + count = 0 + if not self.path.exists(): + return {"ok": True, "entries": 0, "last_hash": previous} + try: + with self.path.open("r", encoding="utf-8") as handle: + for number, raw in enumerate(handle, 1): + raw = raw.strip() + if not raw: + continue + item = json.loads(raw) + event_hash = str(item.pop("event_hash")) + if item.get("prev_hash") != previous: + return {"ok": False, "entries": count, "line": number, "error": "PREV_HASH_MISMATCH"} + expected = sha256_text(previous + canonical_json(item)) + if event_hash != expected: + return {"ok": False, "entries": count, "line": number, "error": "EVENT_HASH_MISMATCH"} + previous = event_hash + count += 1 + except Exception as exc: + return {"ok": False, "entries": count, "error": type(exc).__name__} + return {"ok": True, "entries": count, "last_hash": previous} + def append(self, tool: str, status: str, request: Any = None, result: Any = None, principal: str = "chatgpt") -> dict[str, Any]: + with self._lock: + current = self.verify() + if not current.get("ok"): + raise ProductError("AUDIT_CHAIN_INVALID") + previous = str(current.get("last_hash", "GENESIS")) + event = {"event_id": uuid.uuid4().hex, "timestamp": utc_now(), "principal": str(principal), "tool": str(tool), "status": str(status), "request": redact(request), "result": redact(result), "prev_hash": previous} + event_hash = sha256_text(previous + canonical_json(event)) + stored = dict(event) + stored["event_hash"] = event_hash + with self.path.open("a", encoding="utf-8") as handle: + handle.write(canonical_json(stored) + "\n") + handle.flush() + os.fsync(handle.fileno()) + return {"event_id": event["event_id"], "event_hash": event_hash} + def status(self) -> dict[str, Any]: + value = self.verify() + value["path"] = str(self.path) + return value + + def tail(self, limit: int = 20) -> dict[str, Any]: + limit = max(1, min(int(limit), 100)) + if not self.path.exists(): + return {"ok": True, "events": []} + events = [] + with self.path.open("r", encoding="utf-8") as handle: + for raw in handle: + raw = raw.strip() + if not raw: + continue + try: + item = json.loads(raw) + except Exception: + continue + events.append({ + "timestamp": item.get("timestamp"), + "tool": item.get("tool"), + "status": item.get("status"), + "event_hash": item.get("event_hash"), + "request": item.get("request"), + "result": item.get("result"), + }) + return {"ok": True, "events": events[-limit:]} + + +class SafeFiles: + def __init__(self, roots: list[str]): + self.roots = [Path(x).expanduser().resolve(strict=False) for x in roots] + def _resolve(self, value: str) -> Path: + if not self.roots: + raise ProductError("NO_ALLOWED_ROOTS") + candidate = Path(value).expanduser() + if not candidate.is_absolute(): + candidate = self.roots[0] / candidate + resolved = candidate.resolve(strict=False) + for root in self.roots: + try: + if os.path.commonpath([str(root), str(resolved)]) == str(root): + return resolved + except ValueError: + pass + raise ProductError("PATH_OUTSIDE_ALLOWED_ROOTS") + def list_dir(self, path: str = ".", limit: int = 200) -> dict[str, Any]: + target = self._resolve(path) + if not target.is_dir(): + raise ProductError("DIRECTORY_NOT_FOUND") + limit = max(1, min(int(limit), 500)) + items = [] + for entry in sorted(target.iterdir(), key=lambda p: p.name.lower())[:limit]: + info = entry.lstat() + items.append({"name": entry.name, "type": "symlink" if entry.is_symlink() else ("dir" if entry.is_dir() else "file"), "size": info.st_size}) + return {"path": str(target), "items": items, "count": len(items)} + def read_text(self, path: str, max_bytes: int = 65536) -> dict[str, Any]: + target = self._resolve(path) + if not target.is_file(): + raise ProductError("FILE_NOT_FOUND") + max_bytes = max(1, min(int(max_bytes), 262144)) + size = target.stat().st_size + if size > max_bytes: + raise ProductError("FILE_TOO_LARGE") + data = target.read_bytes() + if b"\\x00" in data: + raise ProductError("BINARY_FILE_NOT_ALLOWED") + text = data.decode("utf-8", errors="strict") + return {"path": str(target), "size": len(data), "sha256": sha256_bytes(data), "text": text} + def write_text(self, path: str, text: str, expected_sha256: str = "", max_bytes: int = 262144) -> dict[str, Any]: + target = self._resolve(path) + parent = target.parent + if not parent.is_dir(): + raise ProductError("PARENT_DIRECTORY_NOT_FOUND") + if target.exists() and target.is_symlink(): + raise ProductError("SYMLINK_WRITE_BLOCKED") + data = str(text).encode("utf-8") + if len(data) > int(max_bytes): + raise ProductError("WRITE_TOO_LARGE") + before_sha = "" + if target.exists(): + before_sha = sha256_bytes(target.read_bytes()) + if expected_sha256 and before_sha != expected_sha256: + raise ProductError("EXPECTED_SHA256_MISMATCH") + elif expected_sha256: + raise ProductError("EXPECTED_SHA256_WITHOUT_EXISTING_FILE") + fd, tmp_name = tempfile.mkstemp(prefix=".framebridge-", dir=str(parent)) + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.replace(tmp_name, target) + finally: + if os.path.exists(tmp_name): + os.unlink(tmp_name) + return {"path": str(target), "size": len(data), "sha256": sha256_bytes(data), "previous_sha256": before_sha} + +class MemoryIndex: + ALLOWED_SUFFIXES = {".md", ".txt", ".json", ".yaml", ".yml"} + BLOCKED_NAMES = {"rclone.conf", "runtime.env", ".env"} + BLOCKED_SUFFIXES = {".pem", ".key", ".p12", ".pfx"} + def __init__(self, db_path: Path, sources: list[str]): + self.db_path = Path(db_path) + self.db_path.parent.mkdir(parents=True, exist_ok=True) + self.sources = [Path(x).expanduser().resolve(strict=False) for x in sources] + self._init_db() + def _connect(self): + con = sqlite3.connect(self.db_path) + con.row_factory = sqlite3.Row + return con + def _init_db(self): + with self._connect() as con: + con.execute("CREATE TABLE IF NOT EXISTS documents (source_path TEXT PRIMARY KEY, title TEXT NOT NULL, sha256 TEXT NOT NULL, mtime REAL NOT NULL, body TEXT NOT NULL, indexed_at TEXT NOT NULL)") + con.execute("CREATE VIRTUAL TABLE IF NOT EXISTS docs_fts USING fts5(source_path UNINDEXED, title, body)") + def _safe_file(self, path: Path, root: Path) -> bool: + try: + resolved = path.resolve(strict=True) + if os.path.commonpath([str(root), str(resolved)]) != str(root): + return False + except Exception: + return False + if path.is_symlink() or not path.is_file(): + return False + if path.name in self.BLOCKED_NAMES or path.suffix.lower() in self.BLOCKED_SUFFIXES: + return False + if path.suffix.lower() not in self.ALLOWED_SUFFIXES: + return False + try: + return path.stat().st_size <= 2_000_000 + except OSError: + return False + def refresh(self) -> dict[str, Any]: + docs = [] + for root in self.sources: + if not root.is_dir(): + continue + for path in sorted(root.rglob("*")): + if not self._safe_file(path, root): + continue + try: + body = path.read_text(encoding="utf-8") + except Exception: + continue + data = body.encode("utf-8") + lines = body.splitlines() + title = lines[0].lstrip("# ").strip() if lines else path.name + docs.append({"source_path": str(path.resolve()), "title": title[:500] or path.name, "sha256": sha256_bytes(data), "mtime": path.stat().st_mtime, "body": body, "indexed_at": utc_now()}) + with self._connect() as con: + con.execute("DELETE FROM documents") + con.execute("DELETE FROM docs_fts") + for item in docs: + con.execute("INSERT INTO documents(source_path,title,sha256,mtime,body,indexed_at) VALUES (?,?,?,?,?,?)", (item["source_path"], item["title"], item["sha256"], item["mtime"], item["body"], item["indexed_at"])) + con.execute("INSERT INTO docs_fts(source_path,title,body) VALUES (?,?,?)", (item["source_path"], item["title"], item["body"])) + return self.status() + def status(self) -> dict[str, Any]: + with self._connect() as con: + row = con.execute("SELECT COUNT(*) AS n, MAX(indexed_at) AS updated FROM documents").fetchone() + return {"db_path": str(self.db_path), "documents": int(row["n"]), "last_indexed_at": row["updated"], "sources": [str(x) for x in self.sources]} + def search(self, query: str, limit: int = 8) -> dict[str, Any]: + tokens = re.findall(r"[A-Za-z0-9_]{2,}", str(query)) + if not tokens: + return {"query": str(query), "results": []} + expression = " AND ".join('"' + token.replace('"', '') + '"' for token in tokens[:12]) + limit = max(1, min(int(limit), 20)) + with self._connect() as con: + rows = con.execute("SELECT source_path,title,snippet(docs_fts,2,'[',']','...',24) AS snippet,bm25(docs_fts) AS rank FROM docs_fts WHERE docs_fts MATCH ? ORDER BY rank LIMIT ?", (expression, limit)).fetchall() + return {"query": str(query), "results": [{"source_path": row["source_path"], "title": row["title"], "snippet": row["snippet"], "rank": row["rank"]} for row in rows]} + def read(self, source_path: str, start_line: int = 1, max_lines: int = 120) -> dict[str, Any]: + start_line = max(1, int(start_line)) + max_lines = max(1, min(int(max_lines), 400)) + with self._connect() as con: + row = con.execute("SELECT source_path,title,sha256,body,indexed_at FROM documents WHERE source_path=?", (str(source_path),)).fetchone() + if row is None: + raise ProductError("MEMORY_DOCUMENT_NOT_FOUND") + lines = row["body"].splitlines() + selected = lines[start_line - 1:start_line - 1 + max_lines] + return {"source_path": row["source_path"], "title": row["title"], "sha256": row["sha256"], "indexed_at": row["indexed_at"], "start_line": start_line, "end_line": start_line + len(selected) - 1 if selected else start_line - 1, "text": "\n".join(selected)} + +DEFAULT_POLICY = {"default": "deny", "tools": {"framebridge_product_status": "allow", "framebridge_files_list": "allow", "framebridge_files_read": "allow", "framebridge_files_write": "require_authorized", "framebridge_memory_status": "allow", "framebridge_memory_refresh": "require_authorized", "framebridge_memory_search": "allow", "framebridge_memory_read": "allow", "framebridge_audit_verify": "allow", "framebridge_exec": "require_authorized", "framebridge_cua": "require_authorized"}} + +def load_config(path: Path) -> dict[str, Any]: + if path.is_file(): + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise ProductError("CONFIG_NOT_OBJECT") + return value + return {"node_name": os.uname().nodename, "state_dir": "~/.local/share/framebridge-linux-chatgpt", "allowed_roots": ["~/FrameBridge-Workspace"], "memory_sources": [], "policy": DEFAULT_POLICY} + +class AlphaContext: + def __init__(self, config_path: Path): + self.config_path = Path(config_path).expanduser() + self.config = load_config(self.config_path) + self.node_name = str(self.config.get("node_name") or os.uname().nodename) + self.state_dir = Path(str(self.config.get("state_dir") or "~/.local/share/framebridge-linux-chatgpt")).expanduser() + self.state_dir.mkdir(parents=True, exist_ok=True) + self.policy = PolicyEngine(self.config.get("policy") or DEFAULT_POLICY) + self.files = SafeFiles(list(self.config.get("allowed_roots") or [])) + self.memory = MemoryIndex(self.state_dir / "memory.sqlite3", list(self.config.get("memory_sources") or [])) + self.audit = AuditLog(self.state_dir / "audit.jsonl") + @classmethod + def from_environment(cls) -> "AlphaContext": + raw = os.environ.get("FRAMEBRIDGE_ALPHA_CONFIG", "~/.config/framebridge-linux-chatgpt/config.json") + return cls(Path(raw).expanduser()) + def status(self) -> dict[str, Any]: + return {"ok": True, "version": __version__, "node_name": self.node_name, "config_path": str(self.config_path), "state_dir": str(self.state_dir), "allowed_roots": [str(x) for x in self.files.roots], "memory": self.memory.status(), "audit": self.audit.status(), "policy_default": self.policy.default} + def guarded(self, tool: str, authorized: bool, request: dict[str, Any], fn): + decision = self.policy.decide(tool, authorized) + if not decision.allowed: + result = {"ok": False, "error": decision.reason, "policy_mode": decision.mode} + self.audit.append(tool, "DENY", request=request, result=result) + return result + try: + value = fn() + result = {"ok": True, "policy_mode": decision.mode, "result": value} + self.audit.append(tool, "PASS", request=request, result=result) + return result + except Exception as exc: + result = {"ok": False, "error": str(exc), "error_type": type(exc).__name__} + self.audit.append(tool, "ERROR", request=request, result=result) + return result diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py new file mode 100644 index 0000000..8cc5724 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py @@ -0,0 +1,67 @@ +from __future__ import annotations +import sys +from .vendor.qrcodegen import QrCode + +class TerminalQrError(RuntimeError): + pass + +def _prepare(text, border, stream, require_tty): + if not isinstance(text,str) or not text: raise TerminalQrError('QR_TEXT_INVALID') + if not (2 <= int(border) <= 8): raise TerminalQrError('QR_BORDER_INVALID') + out=stream or sys.stdout + if require_tty and not getattr(out,'isatty',lambda:False)(): raise TerminalQrError('INTERACTIVE_TTY_REQUIRED') + return QrCode.encode_text(text,QrCode.Ecc.MEDIUM),out + +def _dark(qr,border,x,y): + size=qr.get_size() + if x=border+size or y>=border+size: return False + return qr.get_module(x-border,y-border) + +def render_qr_compact(text, *, border=4, stream=None, require_tty=True): + qr,out=_prepare(text,border,stream,require_tty) + total=qr.get_size()+border*2 + for y in range(0,total,2): + row=[] + for x in range(total): + top=_dark(qr,border,x,y) + bottom=_dark(qr,border,x,y+1) if y+1 dict[str, Any]: + try: + proc = subprocess.run( + args, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=False, + shell=False, + ) + return { + "ok": proc.returncode == 0, + "rc": proc.returncode, + "stdout": proc.stdout[:MAX_OUTPUT], + "stderr": proc.stderr[:4096], + } + except FileNotFoundError: + return {"ok": False, "error": "COMMAND_NOT_FOUND"} + except subprocess.TimeoutExpired: + return {"ok": False, "error": "COMMAND_TIMEOUT"} + except Exception as exc: + return {"ok": False, "error": type(exc).__name__} + + +def _os_release() -> dict[str, str]: + path = Path("/etc/os-release") + out: dict[str, str] = {} + if not path.is_file(): + return out + for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + if "=" not in line: + continue + key, value = line.split("=", 1) + out[key] = value.strip().strip('"') + return out + + +def system_status(args: dict[str, Any]) -> dict[str, Any]: + osr = _os_release() + uptime = _run(["uptime", "-p"]) if shutil.which("uptime") else {"ok": False} + return { + "hostname": socket.gethostname(), + "kernel": platform.release(), + "architecture": platform.machine(), + "os_id": osr.get("ID"), + "os_name": osr.get("PRETTY_NAME"), + "uptime": uptime.get("stdout", "").strip(), + } + + +def hardware_summary(args: dict[str, Any]) -> dict[str, Any]: + cpu_model = "" + cpuinfo = Path("/proc/cpuinfo") + if cpuinfo.is_file(): + for line in cpuinfo.read_text(errors="replace").splitlines(): + if line.lower().startswith("model name") and ":" in line: + cpu_model = line.split(":", 1)[1].strip() + break + + mem_total_kib = None + meminfo = Path("/proc/meminfo") + if meminfo.is_file(): + for line in meminfo.read_text(errors="replace").splitlines(): + if line.startswith("MemTotal:"): + parts = line.split() + if len(parts) >= 2 and parts[1].isdigit(): + mem_total_kib = int(parts[1]) + break + + return { + "cpu_model": cpu_model or None, + "logical_cpus": os.cpu_count(), + "memory_total_kib": mem_total_kib, + "architecture": platform.machine(), + } + + +def storage_inspect(args: dict[str, Any]) -> dict[str, Any]: + if not shutil.which("lsblk"): + return {"ok": False, "error": "LSBLK_NOT_FOUND"} + result = _run([ + "lsblk", + "-J", + "-o", + "NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL", + ]) + if not result.get("ok"): + return result + try: + return { + "ok": True, + "blockdevices": json.loads(result["stdout"]).get("blockdevices", []), + } + except Exception: + return {"ok": False, "error": "LSBLK_JSON_INVALID"} + + +def network_inspect(args: dict[str, Any]) -> dict[str, Any]: + if not shutil.which("ip"): + return {"ok": False, "error": "IP_COMMAND_NOT_FOUND"} + addresses = _run(["ip", "-j", "address"]) + routes = _run(["ip", "-j", "route"]) + out: dict[str, Any] = { + "ok": bool(addresses.get("ok") and routes.get("ok")), + } + try: + out["addresses"] = json.loads(addresses.get("stdout", "[]")) + except Exception: + out["addresses"] = [] + out["ok"] = False + try: + out["routes"] = json.loads(routes.get("stdout", "[]")) + except Exception: + out["routes"] = [] + out["ok"] = False + return out + + +def service_status(args: dict[str, Any]) -> dict[str, Any]: + service = str(args.get("service") or "").strip() + if not service: + raise ProductError("SERVICE_REQUIRED") + if len(service) > 128: + raise ProductError("SERVICE_NAME_TOO_LONG") + allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.-") + if any(ch not in allowed for ch in service): + raise ProductError("SERVICE_NAME_INVALID") + if not shutil.which("systemctl"): + return {"ok": False, "error": "SYSTEMCTL_NOT_FOUND"} + + active = _run(["systemctl", "is-active", service]) + enabled = _run(["systemctl", "is-enabled", service]) + return { + "service": service, + "active": active.get("stdout", "").strip(), + "active_rc": active.get("rc"), + "enabled": enabled.get("stdout", "").strip(), + "enabled_rc": enabled.get("rc"), + } + + + +PACKAGE_RE = re.compile(r"^[a-z0-9][a-z0-9+.-]{0,127}$") +SIMPLE_NAME_RE = re.compile(r"^[A-Za-z0-9@_.-]{1,128}$") +SAFE_TEXT_ROOTS = ( + Path("/etc"), + Path("/usr/local/etc"), + Path("/var/log"), + Path("/var/lib/framebridge-seed"), +) +SAFE_TEXT_FILES = { + Path("/usr/lib/os-release"), +} +SENSITIVE_PATH_PARTS = ( + "/etc/shadow", + "/etc/gshadow", + "/etc/sudoers", + "/etc/sudoers.d/", + "/etc/ssl/private/", + "/etc/wireguard/", + "/etc/openvpn/", + "/etc/NetworkManager/system-connections/", + "/etc/apt/auth.conf", + "/.ssh/", +) +SENSITIVE_NAME_RE = re.compile( + r"(?i)(?:^|[._-])(?:secret|token|password|passwd|credential|private[_-]?key|api[_-]?key)(?:$|[._-])" +) +SENSITIVE_ASSIGNMENT_RE = re.compile( + r"(?i)^\s*([A-Za-z0-9_.-]*(?:secret|token|password|passwd|api[_-]?key|" + r"private[_-]?key|cookie|authorization|credential)[A-Za-z0-9_.-]*)\s*[:=]" +) +BEARER_RE = re.compile(r"(?i)\bBearer\s+[^\s,;]+") +URL_USERINFO_RE = re.compile(r"(?i)(https?://)[^/@\s]+:[^/@\s]+@") +PRIVATE_KEY_BEGIN_RE = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") +PRIVATE_KEY_END_RE = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + + +def _bounded_int(value: Any, *, default: int, low: int, high: int, error: str) -> int: + if value is None: + return default + if type(value) is not int or not (low <= value <= high): + raise ProductError(error) + return value + + +def _simple_name(value: Any, error: str) -> str: + name = str(value or "").strip() + if not SIMPLE_NAME_RE.fullmatch(name): + raise ProductError(error) + return name + + +def _package_name(value: Any) -> str: + package = str(value or "").strip() + if not PACKAGE_RE.fullmatch(package): + raise ProductError("PACKAGE_NAME_INVALID") + return package + + +def _sanitize_text(text: str) -> str: + out: list[str] = [] + in_key = False + for line in str(text).splitlines(): + if PRIVATE_KEY_BEGIN_RE.search(line): + in_key = True + out.append("[REDACTED PRIVATE KEY BLOCK]") + continue + if in_key: + if PRIVATE_KEY_END_RE.search(line): + in_key = False + continue + m = SENSITIVE_ASSIGNMENT_RE.search(line) + if m: + out.append(f"{m.group(1)}=[REDACTED]") + continue + line = BEARER_RE.sub("Bearer [REDACTED]", line) + line = URL_USERINFO_RE.sub(r"\1[REDACTED]@", line) + out.append(line) + return "\n".join(out) + + +def _safe_text_path(raw: Any) -> Path: + value = str(raw or "").strip() + if not value or len(value) > 4096 or "\x00" in value or "\n" in value: + raise ProductError("PATH_INVALID") + src = Path(value) + if not src.is_absolute(): + raise ProductError("PATH_ABSOLUTE_REQUIRED") + try: + resolved = src.resolve(strict=True) + except FileNotFoundError: + raise ProductError("PATH_NOT_FOUND") from None + except OSError: + raise ProductError("PATH_INVALID") from None + + allowed = False + for root in SAFE_TEXT_ROOTS: + try: + if os.path.commonpath([str(root), str(resolved)]) == str(root): + allowed = True + break + except ValueError: + pass + if not allowed and resolved not in SAFE_TEXT_FILES: + raise ProductError("PATH_OUTSIDE_SAFE_READ_ROOTS") + + lower = str(resolved).lower() + if any(part.lower() in lower for part in SENSITIVE_PATH_PARTS): + raise ProductError("SENSITIVE_PATH_FORBIDDEN") + if SENSITIVE_NAME_RE.search(resolved.name): + raise ProductError("SENSITIVE_PATH_FORBIDDEN") + if not resolved.is_file(): + raise ProductError("PATH_NOT_REGULAR_FILE") + return resolved + + +def system_resources(args: dict[str, Any]) -> dict[str, Any]: + mem: dict[str, int] = {} + p = Path("/proc/meminfo") + if p.is_file(): + for line in p.read_text(errors="replace").splitlines(): + if ":" not in line: + continue + key, rest = line.split(":", 1) + fields = rest.strip().split() + if fields and fields[0].isdigit(): + mem[key] = int(fields[0]) + + load = os.getloadavg() if hasattr(os, "getloadavg") else (None, None, None) + process_count = 0 + proc = Path("/proc") + if proc.is_dir(): + process_count = sum(1 for x in proc.iterdir() if x.name.isdigit()) + + return { + "load_1": load[0], + "load_5": load[1], + "load_15": load[2], + "memory_total_kib": mem.get("MemTotal"), + "memory_available_kib": mem.get("MemAvailable"), + "swap_total_kib": mem.get("SwapTotal"), + "swap_free_kib": mem.get("SwapFree"), + "process_count": process_count, + } + + +def system_processes(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=40, low=1, high=200, error="PROCESS_LIMIT_INVALID") + result = _run([ + "ps", "-eo", + "pid=,ppid=,user=,stat=,pcpu=,pmem=,etime=,comm=", + "--sort=-pcpu", + ]) + if not result.get("ok"): + return result + rows = [] + for raw in result.get("stdout", "").splitlines(): + parts = raw.split(None, 7) + if len(parts) != 8: + continue + pid, ppid, user, state, cpu, mem, etime, comm = parts + rows.append({ + "pid": int(pid) if pid.isdigit() else pid, + "ppid": int(ppid) if ppid.isdigit() else ppid, + "user": user, + "state": state, + "cpu_pct": cpu, + "mem_pct": mem, + "etime": etime, + "comm": comm, + }) + if len(rows) >= limit: + break + return {"ok": True, "processes": rows, "limit": limit} + + +def system_time(args: dict[str, Any]) -> dict[str, Any]: + timezone = None + if shutil.which("timedatectl"): + r = _run(["timedatectl", "show", "-p", "Timezone", "--value"]) + if r.get("ok"): + timezone = r.get("stdout", "").strip() or None + if timezone is None: + tz = Path("/etc/timezone") + if tz.is_file(): + timezone = tz.read_text(errors="replace").strip() or None + return { + "local_time": datetime.now().astimezone().isoformat(), + "timezone": timezone, + } + + +def kernel_modules(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=100, low=1, high=500, error="MODULE_LIMIT_INVALID") + if not shutil.which("lsmod"): + return {"ok": False, "error": "LSMOD_NOT_FOUND"} + r = _run(["lsmod"]) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines()[1:]: + parts = line.split() + if len(parts) < 3: + continue + rows.append({ + "module": parts[0], + "size": int(parts[1]) if parts[1].isdigit() else parts[1], + "used_by_count": int(parts[2]) if parts[2].isdigit() else parts[2], + }) + if len(rows) >= limit: + break + return {"ok": True, "modules": rows, "limit": limit} + + +def storage_usage(args: dict[str, Any]) -> dict[str, Any]: + r = _run(["df", "-B1", "-P", "-T"]) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines()[1:]: + parts = line.split() + if len(parts) < 7: + continue + fs, fstype, total, used, avail, pct = parts[:6] + mount = " ".join(parts[6:]) + rows.append({ + "filesystem": fs, + "fstype": fstype, + "bytes_total": int(total) if total.isdigit() else total, + "bytes_used": int(used) if used.isdigit() else used, + "bytes_available": int(avail) if avail.isdigit() else avail, + "use_percent": pct, + "mountpoint": mount, + }) + return {"ok": True, "filesystems": rows} + + +def storage_mounts(args: dict[str, Any]) -> dict[str, Any]: + if not shutil.which("findmnt"): + return {"ok": False, "error": "FINDMNT_NOT_FOUND"} + r = _run(["findmnt", "-J", "-o", "TARGET,SOURCE,FSTYPE"]) + if not r.get("ok"): + return r + try: + return {"ok": True, "filesystems": json.loads(r["stdout"]).get("filesystems", [])} + except Exception: + return {"ok": False, "error": "FINDMNT_JSON_INVALID"} + + +def network_sockets(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=100, low=1, high=500, error="SOCKET_LIMIT_INVALID") + if not shutil.which("ss"): + return {"ok": False, "error": "SS_NOT_FOUND"} + r = _run(["ss", "-H", "-lntu"]) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + parts = line.split() + if len(parts) < 5: + continue + rows.append({ + "netid": parts[0], + "state": parts[1], + "local": parts[4] if len(parts) > 4 else None, + "peer": parts[5] if len(parts) > 5 else None, + }) + if len(rows) >= limit: + break + return {"ok": True, "listeners": rows, "limit": limit} + + +def network_dns(args: dict[str, Any]) -> dict[str, Any]: + path = Path("/etc/resolv.conf") + if not path.exists(): + return {"ok": False, "error": "RESOLV_CONF_NOT_FOUND"} + try: + resolved = path.resolve(strict=True) + except Exception: + resolved = path + lines = [] + try: + text = path.read_text(errors="replace") + except Exception as exc: + return {"ok": False, "error": type(exc).__name__} + for line in text.splitlines(): + stripped = line.strip() + if stripped.startswith(("nameserver ", "search ", "domain ", "options ")): + lines.append(stripped) + return { + "ok": True, + "path": str(path), + "resolved_path": str(resolved), + "directives": lines, + } + + +def package_status_read(args: dict[str, Any]) -> dict[str, Any]: + package = _package_name(args.get("package")) + r = _run(["dpkg-query", "-W", "-f=${Status}|${Version}|${Architecture}", package]) + if not r.get("ok"): + return {"ok": True, "package": package, "installed": False, "version": None, "architecture": None} + out = r.get("stdout", "").strip() + parts = out.split("|") + installed = len(parts) >= 1 and parts[0] == "install ok installed" + return { + "ok": True, + "package": package, + "installed": installed, + "version": parts[1] if installed and len(parts) > 1 else None, + "architecture": parts[2] if installed and len(parts) > 2 else None, + } + + +def package_search(args: dict[str, Any]) -> dict[str, Any]: + query = str(args.get("query") or "").strip() + if not query or len(query) > 80: + raise ProductError("PACKAGE_QUERY_INVALID") + if not re.fullmatch(r"[A-Za-z0-9+._ -]+", query): + raise ProductError("PACKAGE_QUERY_INVALID") + limit = _bounded_int(args.get("limit"), default=30, low=1, high=100, error="PACKAGE_LIMIT_INVALID") + if not shutil.which("apt-cache"): + return {"ok": False, "error": "APT_CACHE_NOT_FOUND"} + r = _run(["apt-cache", "search", "--names-only", re.escape(query)], timeout=15.0) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + if " - " in line: + name, desc = line.split(" - ", 1) + else: + name, desc = line, "" + rows.append({"package": name.strip(), "description": desc.strip()}) + if len(rows) >= limit: + break + return {"ok": True, "query": query, "results": rows, "limit": limit} + + +def package_upgradable(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=100, low=1, high=500, error="PACKAGE_LIMIT_INVALID") + if not shutil.which("apt"): + return {"ok": False, "error": "APT_NOT_FOUND"} + r = _run(["apt", "list", "--upgradable"], timeout=20.0) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + line = line.strip() + if not line or line.startswith("Listing...") or "/" not in line: + continue + first, *rest = line.split() + name, suite = first.split("/", 1) + version = rest[0] if rest else None + rows.append({"package": name, "suite": suite, "candidate_version": version}) + if len(rows) >= limit: + break + return {"ok": True, "packages": rows, "limit": limit} + + +def service_list(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=100, low=1, high=500, error="SERVICE_LIMIT_INVALID") + if not shutil.which("systemctl"): + return {"ok": False, "error": "SYSTEMCTL_NOT_FOUND"} + r = _run([ + "systemctl", "list-units", "--type=service", "--all", + "--no-legend", "--no-pager", "--plain", + ], timeout=10.0) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + parts = line.split(None, 4) + if len(parts) < 4: + continue + rows.append({ + "unit": parts[0], + "load": parts[1], + "active": parts[2], + "sub": parts[3], + "description": parts[4] if len(parts) > 4 else "", + }) + if len(rows) >= limit: + break + return {"ok": True, "services": rows, "limit": limit} + + +def journal_service(args: dict[str, Any]) -> dict[str, Any]: + service = _simple_name(args.get("service"), "SERVICE_NAME_INVALID") + lines = _bounded_int(args.get("lines"), default=80, low=1, high=500, error="JOURNAL_LINES_INVALID") + if not shutil.which("journalctl"): + return {"ok": False, "error": "JOURNALCTL_NOT_FOUND"} + r = _run([ + "journalctl", "-u", service, "-n", str(lines), + "--no-pager", "--output=short-iso-precise", + ], timeout=10.0) + if not r.get("ok"): + return r + return { + "ok": True, + "service": service, + "lines": lines, + "text": _sanitize_text(r.get("stdout", ""))[:MAX_OUTPUT], + } + + +def user_list(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=200, low=1, high=1000, error="USER_LIMIT_INVALID") + r = _run(["getent", "passwd"]) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + parts = line.split(":") + if len(parts) != 7: + continue + name, _pw, uid, gid, gecos, home, shell = parts + rows.append({ + "name": name, + "uid": int(uid) if uid.isdigit() else uid, + "gid": int(gid) if gid.isdigit() else gid, + "gecos": gecos, + "home": home, + "shell": shell, + }) + if len(rows) >= limit: + break + return {"ok": True, "users": rows, "limit": limit} + + +def group_list(args: dict[str, Any]) -> dict[str, Any]: + limit = _bounded_int(args.get("limit"), default=200, low=1, high=1000, error="GROUP_LIMIT_INVALID") + r = _run(["getent", "group"]) + if not r.get("ok"): + return r + rows = [] + for line in r.get("stdout", "").splitlines(): + parts = line.split(":") + if len(parts) != 4: + continue + name, _pw, gid, members = parts + rows.append({ + "name": name, + "gid": int(gid) if gid.isdigit() else gid, + "members": [x for x in members.split(",") if x], + }) + if len(rows) >= limit: + break + return {"ok": True, "groups": rows, "limit": limit} + + +def filesystem_stat(args: dict[str, Any]) -> dict[str, Any]: + raw = str(args.get("path") or "").strip() + if not raw or len(raw) > 4096 or "\x00" in raw or "\n" in raw: + raise ProductError("PATH_INVALID") + path = Path(raw) + if not path.is_absolute(): + raise ProductError("PATH_ABSOLUTE_REQUIRED") + try: + st = path.lstat() + except FileNotFoundError: + return {"ok": True, "path": raw, "exists": False} + return { + "ok": True, + "path": raw, + "exists": True, + "is_file": path.is_file(), + "is_dir": path.is_dir(), + "is_symlink": path.is_symlink(), + "size": st.st_size, + "mode": oct(st.st_mode & 0o7777), + "uid": st.st_uid, + "gid": st.st_gid, + "mtime_ns": st.st_mtime_ns, + } + + +def filesystem_list(args: dict[str, Any]) -> dict[str, Any]: + raw = str(args.get("path") or "").strip() + if not raw or len(raw) > 4096 or "\x00" in raw or "\n" in raw: + raise ProductError("PATH_INVALID") + path = Path(raw) + if not path.is_absolute(): + raise ProductError("PATH_ABSOLUTE_REQUIRED") + limit = _bounded_int(args.get("limit"), default=100, low=1, high=500, error="FILE_LIST_LIMIT_INVALID") + if not path.is_dir(): + raise ProductError("DIRECTORY_NOT_FOUND") + rows = [] + try: + children = sorted(path.iterdir(), key=lambda x: x.name) + except PermissionError: + raise ProductError("DIRECTORY_PERMISSION_DENIED") from None + for child in children[:limit]: + try: + st = child.lstat() + rows.append({ + "name": child.name, + "is_file": child.is_file(), + "is_dir": child.is_dir(), + "is_symlink": child.is_symlink(), + "size": st.st_size, + "mode": oct(st.st_mode & 0o7777), + }) + except OSError: + rows.append({"name": child.name, "error": "STAT_FAILED"}) + return {"ok": True, "path": raw, "entries": rows, "limit": limit} + + +def filesystem_hash(args: dict[str, Any]) -> dict[str, Any]: + raw = str(args.get("path") or "").strip() + if not raw or len(raw) > 4096 or "\x00" in raw or "\n" in raw: + raise ProductError("PATH_INVALID") + path = Path(raw) + if not path.is_absolute(): + raise ProductError("PATH_ABSOLUTE_REQUIRED") + if not path.is_file(): + raise ProductError("PATH_NOT_REGULAR_FILE") + size = path.stat().st_size + if size > 16 * 1024 * 1024: + raise ProductError("FILE_TOO_LARGE_TO_HASH") + h = hashlib.sha256() + try: + with path.open("rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + h.update(chunk) + except PermissionError: + raise ProductError("FILE_PERMISSION_DENIED") from None + return {"ok": True, "path": raw, "size": size, "sha256": h.hexdigest()} + + +def filesystem_read_text(args: dict[str, Any]) -> dict[str, Any]: + path = _safe_text_path(args.get("path")) + max_bytes = _bounded_int(args.get("max_bytes"), default=16384, low=1, high=32768, error="MAX_BYTES_INVALID") + try: + data = path.read_bytes() + except PermissionError: + raise ProductError("FILE_PERMISSION_DENIED") from None + if len(data) > max_bytes: + data = data[:max_bytes] + truncated = True + else: + truncated = False + if b"\x00" in data: + raise ProductError("BINARY_FILE_NOT_ALLOWED") + text = data.decode("utf-8", errors="replace") + return { + "ok": True, + "path": str(path), + "bytes_returned": len(data), + "truncated": truncated, + "text": _sanitize_text(text), + } + + +CAPABILITIES = { + "system.status": system_status, + "system.resources": system_resources, + "system.processes": system_processes, + "system.time": system_time, + "hardware.summary": hardware_summary, + "kernel.modules": kernel_modules, + "storage.inspect": storage_inspect, + "storage.usage": storage_usage, + "storage.mounts": storage_mounts, + "network.inspect": network_inspect, + "network.sockets": network_sockets, + "network.dns": network_dns, + "package.status": package_status_read, + "package.search": package_search, + "package.upgradable": package_upgradable, + "service.status": service_status, + "service.list": service_list, + "journal.service": journal_service, + "user.list": user_list, + "group.list": group_list, + "filesystem.stat": filesystem_stat, + "filesystem.list": filesystem_list, + "filesystem.hash": filesystem_hash, + "filesystem.read_text": filesystem_read_text, +} + + +def list_capabilities() -> list[dict[str, Any]]: + return [ + {"name": "system.status", "risk": "READONLY"}, + {"name": "system.resources", "risk": "READONLY"}, + {"name": "system.processes", "risk": "READONLY", "args": {"limit": "int:1..200"}}, + {"name": "system.time", "risk": "READONLY"}, + {"name": "hardware.summary", "risk": "READONLY"}, + {"name": "kernel.modules", "risk": "READONLY", "args": {"limit": "int:1..500"}}, + {"name": "storage.inspect", "risk": "READONLY"}, + {"name": "storage.usage", "risk": "READONLY"}, + {"name": "storage.mounts", "risk": "READONLY"}, + {"name": "network.inspect", "risk": "READONLY"}, + {"name": "network.sockets", "risk": "READONLY", "args": {"limit": "int:1..500"}}, + {"name": "network.dns", "risk": "READONLY"}, + {"name": "package.status", "risk": "READONLY", "args": {"package": "debian-package-name"}}, + {"name": "package.search", "risk": "READONLY", "args": {"query": "string", "limit": "int:1..100"}}, + {"name": "package.upgradable", "risk": "READONLY", "args": {"limit": "int:1..500"}}, + {"name": "service.status", "risk": "READONLY", "args": {"service": "systemd-unit"}}, + {"name": "service.list", "risk": "READONLY", "args": {"limit": "int:1..500"}}, + {"name": "journal.service", "risk": "READONLY", "args": {"service": "systemd-unit", "lines": "int:1..500"}}, + {"name": "user.list", "risk": "READONLY", "args": {"limit": "int:1..1000"}}, + {"name": "group.list", "risk": "READONLY", "args": {"limit": "int:1..1000"}}, + {"name": "filesystem.stat", "risk": "READONLY", "args": {"path": "absolute-path"}}, + {"name": "filesystem.list", "risk": "READONLY", "args": {"path": "absolute-directory", "limit": "int:1..500"}}, + {"name": "filesystem.hash", "risk": "READONLY", "args": {"path": "absolute-file<=16MiB"}}, + {"name": "filesystem.read_text", "risk": "READONLY", "args": {"path": "safe-absolute-text-path", "max_bytes": "int:1..32768"}}, + ] + + +def execute_capability(name: str, args: dict[str, Any] | None = None) -> dict[str, Any]: + name = str(name).strip() + fn = CAPABILITIES.get(name) + if fn is None: + raise ProductError("CAPABILITY_NOT_ALLOWED") + args = args or {} + if not isinstance(args, dict): + raise ProductError("CAPABILITY_ARGS_NOT_OBJECT") + return fn(args) diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py new file mode 100644 index 0000000..90e5270 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py @@ -0,0 +1,572 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +import time + +from .product_core import ProductError +from .seed_core import SEED_VERSION, SeedContext, atomic_write_json +from .classic_tty import execute_frame, render_result, render_snapshot +from .console_tty import run_console +from .plan_engine import PlanEngine +from .memory_store import MemoryStore +from .first_run import SetupChoice, detect_system, provider_instructions, render_privilege_explanation, save_setup_config, setup_summary +from .console_chat import run_console_chat +from .qr_terminal import render_qr_compact, render_qr_mono, render_qr_ansi, render_qr_unicode, TerminalQrError +from .pairing import FBPAIR_PROTOCOL, PairingStore, pairing_qr_payload, public_pairing_view +from .pairing_client import PairingClientError, poll_pairing, start_pairing +from .network_security import inspect_network, pairing_policy, forwarding_policy +from .pairing_window import open_pairing_window, pairing_window_status, close_pairing_window +from .network_lifecycle import reconcile_once, reconcile_broker_cancel + + +def emit(value) -> None: + print(json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False)) + + +def _sync_pairing_config(ctx: SeedContext, view: dict) -> dict: + config=ctx.load_config() or {} + pairing=config.get("pairing") or {} + pairing.update({ + "protocol":FBPAIR_PROTOCOL, + "state":view.get("state","not_paired"), + "transport":"broker_outbound" if view.get("state")!="not_paired" else "unpaired", + }) + for key in ( + "pair_id","broker_base_url","account_issuer", + "account_subject_sha256","revocation_id","paired_at", + "broker_confirm_required","broker_confirmed_at", + "broker_cancel_required","broker_cancelled_at","broker_cancel_state", + ): + if view.get(key) is not None: + pairing[key]=view.get(key) + config["pairing"]=pairing + config["connection_mode"]="PAIRED" if view.get("state")=="paired" else "PAIRING" + setup=config.get("setup") + if isinstance(setup,dict): + setup["pairing_state"]=view.get("state","not_paired") + atomic_write_json(ctx.config_path,config) + return config + + +def _render_pair_qr(view: dict, mode: str) -> None: + payload=pairing_qr_payload(view) + if not sys.stdout.isatty(): + return + if mode=="compact": + render_qr_compact(payload) + elif mode=="mono": + render_qr_mono(payload) + elif mode=="unicode": + render_qr_unicode(payload) + else: + render_qr_ansi(payload) + + +def main() -> None: + parser = argparse.ArgumentParser( + prog="framebridge", + description="FrameBridge Seed - Linux text-mode bootstrap", + ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {SEED_VERSION}", + ) + sub = parser.add_subparsers(dest="command", required=True) + + init = sub.add_parser("init", help="Initialize node identity and provider") + init.add_argument("--provider", default="chatgpt") + init.add_argument("--hostname", default="") + + sub.add_parser("identity", help="Show persistent node identity") + sub.add_parser("doctor", help="Run READONLY bootstrap diagnostics") + sub.add_parser("status", help="Show Seed status") + sub.add_parser("fingerprint", help="Show non-secret node fingerprint") + sub.add_parser("snapshot", help="Generate Classic AI context snapshot") + sub.add_parser("console", help="Open FrameBridge Classic in the current TTY") + classic = sub.add_parser("classic", help="Process one Classic frame from stdin or file") + classic.add_argument("--file", default="-") + setup = sub.add_parser("setup", help="Run the FrameBridge first-run setup wizard") + setup.add_argument("--provider", choices=("chatgpt","ollama","lmstudio","other"), default="") + setup.add_argument("--privilege-mode", choices=("totp","sudo_tty"), default="") + setup.add_argument("--language", default="en") + setup.add_argument("--non-interactive", action="store_true") + sub.add_parser("setup-status", help="Show first-run setup state") + + net = sub.add_parser("net", help="Inspect FrameBridge network/firewall policy") + net_sub = net.add_subparsers(dest="net_command", required=True) + net_sub.add_parser("status", help="Show READONLY FBNET interface/firewall status") + p_net_policy = net_sub.add_parser("pairing-policy", help="Render pairing-window policy without applying changes") + p_net_policy.add_argument("--port", type=int, default=18765) + p_net_forward = net_sub.add_parser("forwarding-policy", help="Render typed runtime forwarding policy without applying changes") + p_net_forward.add_argument("--in-interface", required=True) + p_net_forward.add_argument("--out-interface", required=True) + p_net_forward.add_argument("--source-subnet", required=True) + p_net_forward.add_argument("--destination-subnet", required=True) + p_net_forward.add_argument("--protocol", choices=("any","tcp","udp"), default="any") + p_net_forward.add_argument("--destination-port", type=int, default=0) + p_net_forward.add_argument("--nat", action="store_true") + p_net_forward.add_argument("--lifetime", choices=("runtime",), default="runtime") + + p_window = net_sub.add_parser("pairing-window", help="Manage the short-lived local LAN pairing firewall window") + p_window_sub = p_window.add_subparsers(dest="pairing_window_command", required=True) + p_window_open = p_window_sub.add_parser("open", help="Open a scoped runtime pairing window after local authorization") + p_window_open.add_argument("--port", type=int, default=18765) + p_window_open.add_argument("--ttl", type=int, default=600) + p_window_sub.add_parser("status", help="Show the current pairing-window transaction") + p_window_close = p_window_sub.add_parser("close", help="Close the active pairing window") + p_window_close.add_argument("--reason", choices=("user","cancelled","paired","denied","network_change","service_stop","reconcile"), default="user") + + net_sub.add_parser("reconcile", help="Re-evaluate an active pairing window against current network identity") + + pair = sub.add_parser("pair", help="Pair this Linux node with an authorized AI account") + pair_sub = pair.add_subparsers(dest="pair_command", required=True) + p_pair_start = pair_sub.add_parser("start", help="Start FBPAIR/1 device-style pairing") + p_pair_start.add_argument("--broker", required=True) + p_pair_start.add_argument("--provider", default="") + p_pair_start.add_argument("--mode", choices=("compact","mono","ansi","unicode"), default="compact") + p_pair_start.add_argument("--dev-insecure", action="store_true") + pair_sub.add_parser("status", help="Show public pairing status") + pair_sub.add_parser("cancel", help="Cancel the current pending pairing request") + p_pair_poll = pair_sub.add_parser("poll", help="Poll pairing broker once") + p_pair_poll.add_argument("--dev-insecure", action="store_true") + p_pair_wait = pair_sub.add_parser("wait", help="Wait for human account authorization") + p_pair_wait.add_argument("--dev-insecure", action="store_true") + p_pair_wait.add_argument("--max-wait", type=int, default=600) + p_pair_qr = pair_sub.add_parser("qr", help="Render the current pairing QR") + p_pair_qr.add_argument("--mode", choices=("compact","mono","ansi","unicode"), default="compact") + + sub.add_parser("console-chat", help="Open the primary FrameBridge conversational console") + + sub.add_parser("agent-manifest", help="Return the mandatory compact AI bootstrap manifest") + memory = sub.add_parser("memory", help="Manage mandatory FrameBridge binary technical memory") + memory_sub = memory.add_subparsers(dest="memory_command", required=True) + memory_sub.add_parser("init", help="Initialize or repair node binary memory") + memory_sub.add_parser("bootstrap", help="Return compact mandatory AI memory bootstrap") + p_mem_query = memory_sub.add_parser("query", help="Query a normalized memory domain") + p_mem_query.add_argument("--domain", default="summary") + p_mem_query.add_argument("--limit", type=int, default=20) + p_mem_append = memory_sub.add_parser("append", help="Append one normalized memory record") + p_mem_append.add_argument("--record-type", required=True, choices=("change","incident","decision","artifact","recovery","certification","note")) + p_mem_append.add_argument("--json-stdin", action="store_true", required=True) + memory_sub.add_parser("checkpoint", help="Refresh the compact current-state checkpoint") + memory_sub.add_parser("stats", help="Show binary memory statistics") + + plan = sub.add_parser("plan", help="Manage local CHANGE plans") + plan_sub = plan.add_subparsers(dest="plan_command", required=True) + p_import = plan_sub.add_parser("import") + p_import.add_argument("--file", required=True) + plan_sub.add_parser("import-stdin") + p_show = plan_sub.add_parser("show") + p_show.add_argument("plan_id") + p_validate = plan_sub.add_parser("validate") + p_validate.add_argument("plan_id") + p_approve = plan_sub.add_parser("approve") + p_approve.add_argument("plan_id") + p_approve_system = plan_sub.add_parser("approve-system") + p_approve_system.add_argument("plan_id") + p_apply = plan_sub.add_parser("apply") + p_apply.add_argument("plan_id") + p_approve_rollback = plan_sub.add_parser("approve-rollback") + p_approve_rollback.add_argument("plan_id") + p_remote_challenge = plan_sub.add_parser("remote-challenge") + p_remote_challenge.add_argument("plan_id") + p_remote_challenge.add_argument("--rollback", action="store_true") + p_remote_challenge.add_argument("--ttl", type=int, default=300) + p_remote_approve = plan_sub.add_parser("remote-approve") + p_remote_approve.add_argument("plan_id") + p_remote_approve.add_argument("--challenge-id", required=True) + p_remote_approve.add_argument("--key-id", required=True) + p_remote_approve.add_argument("--signature-b64", required=True) + p_remote_approve.add_argument("--rollback", action="store_true") + p_totp_enroll = plan_sub.add_parser("totp-enroll") + p_totp_enroll.add_argument("--key-id", required=True) + p_totp_enroll.add_argument("--node-label", required=True) + p_totp_enroll.add_argument("--issuer", default="FrameBridge") + p_totp_enroll.add_argument("--mode", choices=("compact","mono","ansi","unicode"), default="compact") + p_totp_challenge = plan_sub.add_parser("totp-challenge") + p_totp_challenge.add_argument("plan_id") + p_totp_challenge.add_argument("--key-id", required=True) + p_totp_challenge.add_argument("--rollback", action="store_true") + p_totp_challenge.add_argument("--ttl", type=int, default=120) + p_totp_approve = plan_sub.add_parser("totp-approve") + p_totp_approve.add_argument("plan_id") + p_totp_approve.add_argument("--challenge-id", required=True) + p_totp_approve.add_argument("--key-id", required=True) + p_totp_approve.add_argument("--rollback", action="store_true") + p_rollback = plan_sub.add_parser("rollback") + p_rollback.add_argument("plan_id") + plan_sub.add_parser("list") + sub.add_parser("audit", help="Verify the audit chain") + + args = parser.parse_args() + ctx = SeedContext() + + try: + if args.command == "init": + emit(ctx.init(args.provider, args.hostname)) + elif args.command == "identity": + emit(ctx.identity()) + elif args.command == "doctor": + emit(ctx.doctor()) + elif args.command == "status": + emit(ctx.status()) + elif args.command == "fingerprint": + emit(ctx.fingerprint()) + elif args.command == "snapshot": + print(render_snapshot(ctx)) + elif args.command == "setup": + if not (ctx.load_identity() and ctx.load_config()): + emit(ctx.init("chatgpt", "")) + detected = detect_system() + provider = args.provider + privilege = args.privilege_mode + language = args.language or "en" + if args.non_interactive: + if not provider or not privilege: + raise ProductError("SETUP_NON_INTERACTIVE_OPTIONS_REQUIRED") + else: + print("\nFrameBridge First-Run Setup\n") + print("System detected:") + sysinfo = detected.get("system") or {} + hw = detected.get("hardware") or {} + print(f" OS: {sysinfo.get('os_name')} / kernel {sysinfo.get('kernel')}") + print(f" CPU: {hw.get('cpu_model')}") + print(f" RAM: {hw.get('memory_total_kib')} KiB") + print("\nPrivileged authorization:") + print(" 1) Authenticator / QR TOTP") + print(" 2) Local sudo password") + if not privilege: + answer=input("Select [1/2]: ").strip() + privilege="totp" if answer=="1" else "sudo_tty" if answer=="2" else "" + print("\nAI provider:") + print(" 1) ChatGPT") + print(" 2) Ollama") + print(" 3) LM Studio") + print(" 4) Other") + if not provider: + answer=input("Select [1-4]: ").strip() + provider={"1":"chatgpt","2":"ollama","3":"lmstudio","4":"other"}.get(answer,"") + choice=SetupChoice(privilege,provider,language) + config=save_setup_config(ctx,choice) + mem=MemoryStore(ctx).initialize() + print("\n"+"\n".join(render_privilege_explanation(choice.privilege_mode))) + print("\n"+"\n".join(provider_instructions(choice.provider))) + print("\nFrameBridge setup complete.") + print("Run: framebridge console-chat") + emit({"ok":True,"setup":config.get("setup"),"memory":mem,"detected":detected}) + elif args.command == "setup-status": + emit(setup_summary(ctx)) + elif args.command == "net": + if args.net_command == "status": + emit(inspect_network()) + elif args.net_command == "pairing-policy": + emit(pairing_policy(args.port)) + elif args.net_command == "forwarding-policy": + emit(forwarding_policy( + ingress_interface=args.in_interface, + egress_interface=args.out_interface, + source_subnet=args.source_subnet, + destination_subnet=args.destination_subnet, + protocol=args.protocol, + destination_port=args.destination_port, + nat=args.nat, + lifetime=args.lifetime, + )) + elif args.net_command == "pairing-window": + if args.pairing_window_command == "open": + MemoryStore(ctx).require_ready() + result=open_pairing_window(args.port,args.ttl) + ctx.audit.append( + "network.pairing_window.open","PASS", + request={"port":args.port,"ttl_seconds":args.ttl}, + result={ + "window_id":result.get("window_id"), + "interface":result.get("interface"), + "source_subnet":result.get("source_subnet"), + "destination_address":result.get("destination_address"), + "expires_at":result.get("expires_at"), + }, + principal="local-operator", + ) + MemoryStore(ctx).append( + "change", + { + "operation":"network.pairing_window.open", + "window_id":result.get("window_id"), + "interface":result.get("interface"), + "source_subnet":result.get("source_subnet"), + "destination_address":result.get("destination_address"), + "port":result.get("port"), + "expires_at":result.get("expires_at"), + }, + source="operator", + ) + emit(result) + elif args.pairing_window_command == "status": + emit(pairing_window_status()) + elif args.pairing_window_command == "close": + MemoryStore(ctx).require_ready() + before=pairing_window_status() + result=close_pairing_window(args.reason) + ctx.audit.append( + "network.pairing_window.close","PASS", + request={"reason":args.reason,"window_id":before.get("window_id")}, + result={"state":result.get("state"),"rule_present":result.get("rule_present")}, + principal="local-operator", + ) + MemoryStore(ctx).append( + "recovery", + { + "operation":"network.pairing_window.close", + "reason":args.reason, + "window_id":before.get("window_id"), + "state":result.get("state"), + }, + source="operator", + ) + emit(result) + elif args.net_command == "reconcile": + emit(reconcile_once(ctx=ctx)) + elif args.command == "pair": + if not (ctx.load_identity() and ctx.load_config()): + raise ProductError("PAIR_NODE_NOT_INITIALIZED") + pair_store=PairingStore(ctx.state_dir) + if args.pair_command == "status": + emit(public_pairing_view(pair_store.load())) + elif args.pair_command == "start": + MemoryStore(ctx).require_ready() + identity=ctx.load_identity() or {} + config=ctx.load_config() or {} + manifest_path=ctx.state_dir/"memory"/"manifest.fbam" + if not manifest_path.is_file(): + raise ProductError("PAIR_CAPABILITY_MANIFEST_MISSING") + manifest_sha=hashlib.sha256(manifest_path.read_bytes()).hexdigest() + provider=(args.provider or config.get("provider") or "chatgpt").strip().lower() + try: + view=start_pairing( + state_dir=ctx.state_dir, + node_id=str(identity.get("node_id") or ""), + node_label=str(identity.get("hostname") or ""), + node_host_id=str(identity.get("node_host_id") or ""), + public_jwk=identity.get("node_public_jwk") or {}, + provider=provider, + capability_manifest_sha256=manifest_sha, + broker_base_url=args.broker, + allow_insecure_dev=args.dev_insecure, + ) + except PairingClientError as exc: + raise ProductError(str(exc)) from exc + _sync_pairing_config(ctx,view) + ctx.audit.append( + "pair.request","PASS", + request={ + "protocol":FBPAIR_PROTOCOL, + "pair_id":view.get("pair_id"), + "provider":provider, + "node_host_id":identity.get("node_host_id"), + }, + result={"state":view.get("state")}, + principal="local-operator", + ) + print("\nFrameBridge AI pairing") + print("Open: "+str(view.get("verification_uri_complete") or view.get("verification_uri") or "")) + print("Code: "+str(view.get("user_code"))) + print("Expires in: "+str(view.get("expires_in"))+" seconds") + _render_pair_qr(view,args.mode) + emit(view) + elif args.pair_command == "cancel": + try: + view=pair_store.cancel_pending() + except Exception as exc: + raise ProductError(str(exc)) from exc + _sync_pairing_config(ctx,view) + broker_cancel=reconcile_broker_cancel(ctx) + view=public_pairing_view(pair_store.load()) + _sync_pairing_config(ctx,view) + ctx.audit.append( + "pair.cancel","PASS", + request={ + "protocol":FBPAIR_PROTOCOL, + "pair_id":view.get("pair_id"), + }, + result={"state":view.get("state"),"broker_cancel_state":broker_cancel.get("state")}, + principal="local-operator", + ) + emit(view) + elif args.pair_command == "poll": + before=pair_store.load() + try: + view=poll_pairing( + state_dir=ctx.state_dir, + allow_insecure_dev=args.dev_insecure, + ) + except PairingClientError as exc: + raise ProductError(str(exc)) from exc + _sync_pairing_config(ctx,view) + if view.get("state") != before.get("state"): + ctx.audit.append( + "pair."+str(view.get("state")),"PASS", + request={ + "protocol":FBPAIR_PROTOCOL, + "pair_id":view.get("pair_id"), + }, + result={ + "state":view.get("state"), + "provider":view.get("provider"), + }, + principal="pairing-broker", + ) + if view.get("state")=="paired": + MemoryStore(ctx).checkpoint() + emit(view) + elif args.pair_command == "wait": + max_wait=int(args.max_wait) + if max_wait<1 or max_wait>1800: + raise ProductError("PAIR_WAIT_TIMEOUT_INVALID") + started=time.monotonic() + view=public_pairing_view(pair_store.load()) + while ( + view.get("state")=="pair_requested" + or (view.get("state")=="paired" and view.get("broker_confirm_required") is True) + ): + if time.monotonic()-started>=max_wait: + raise ProductError("PAIR_WAIT_TIMEOUT") + try: + view=poll_pairing( + state_dir=ctx.state_dir, + allow_insecure_dev=args.dev_insecure, + ) + except PairingClientError as exc: + raise ProductError(str(exc)) from exc + if view.get("state")=="paired" and view.get("broker_confirm_required") is not True: + break + if view.get("state") not in {"pair_requested","paired"}: + break + retry=int(view.get("retry_after") or view.get("interval") or 2) + time.sleep(max(1,min(60,retry))) + _sync_pairing_config(ctx,view) + ctx.audit.append( + "pair."+str(view.get("state")),"PASS", + request={ + "protocol":FBPAIR_PROTOCOL, + "pair_id":view.get("pair_id"), + }, + result={ + "state":view.get("state"), + "provider":view.get("provider"), + }, + principal="pairing-broker", + ) + if view.get("state")=="paired": + MemoryStore(ctx).checkpoint() + emit(view) + if view.get("state")!="paired" or view.get("broker_confirm_required") is True: + raise SystemExit(3) + elif args.pair_command == "qr": + view=public_pairing_view(pair_store.load()) + _render_pair_qr(view,args.mode) + emit(view) + elif args.command == "console-chat": + raise SystemExit(run_console_chat(ctx)) + elif args.command == "agent-manifest": + emit(MemoryStore(ctx).bootstrap()) + elif args.command == "memory": + store = MemoryStore(ctx) + if args.memory_command == "init": + value = store.initialize() + ctx.audit.append("memory.init", "PASS", request={"protocol":"FBM/1"}, result={"required":True,"mode":"read_write"}, principal="local-operator") + emit(value) + elif args.memory_command == "bootstrap": + emit(store.bootstrap()) + elif args.memory_command == "query": + emit(store.query(args.domain, args.limit)) + elif args.memory_command == "append": + try: + value = json.load(sys.stdin) + except Exception: + raise ProductError("MEMORY_APPEND_JSON_INVALID") + if not isinstance(value, dict): + raise ProductError("MEMORY_APPEND_JSON_NOT_OBJECT") + result = store.append(args.record_type, value, source="agent") + ctx.audit.append("memory.append", "PASS", request={"record_type":args.record_type}, result={"event_number":result.get("event_number"),"event_sha256":result.get("event_sha256")}, principal="ai-agent") + store.checkpoint() + emit(result) + elif args.memory_command == "checkpoint": + value = store.checkpoint() + ctx.audit.append("memory.checkpoint", "PASS", request={}, result={"state_sha256":value.get("state_sha256"),"manifest_sha256":value.get("manifest_sha256")}, principal="local-operator") + emit(value) + elif args.memory_command == "stats": + emit(store.stats()) + elif args.command == "console": + raise SystemExit(run_console(ctx)) + elif args.command == "classic": + if args.file == "-": + text = sys.stdin.read() + else: + with open(args.file, "r", encoding="utf-8") as handle: + text = handle.read() + print(render_result(execute_frame(ctx, text))) + elif args.command == "plan": + engine = PlanEngine(ctx) + if args.plan_command == "import": + emit(engine.import_file(args.file)) + elif args.plan_command == "import-stdin": + try: + value = json.load(sys.stdin) + except Exception: + raise ProductError("PLAN_IMPORT_JSON_INVALID") + emit(engine.import_document(value)) + elif args.plan_command == "show": + emit(engine.show(args.plan_id)) + elif args.plan_command == "validate": + emit(engine.validate(args.plan_id)) + elif args.plan_command == "approve": + emit(engine.approve(args.plan_id)) + elif args.plan_command == "approve-system": + emit(engine.approve_system(args.plan_id)) + elif args.plan_command == "apply": + emit(engine.apply(args.plan_id)) + elif args.plan_command == "approve-rollback": + emit(engine.approve_rollback(args.plan_id)) + elif args.plan_command == "remote-challenge": + emit(engine.remote_challenge(args.plan_id, rollback=args.rollback, ttl_seconds=args.ttl)) + elif args.plan_command == "remote-approve": + emit(engine.approve_remote(args.plan_id, args.challenge_id, args.key_id, args.signature_b64, rollback=args.rollback)) + elif args.plan_command == "totp-enroll": + if not sys.stdin.isatty() or not sys.stdout.isatty(): + raise ProductError("INTERACTIVE_TTY_REQUIRED") + value=engine.totp_enroll(args.key_id,args.node_label,args.issuer) + print("\nFrameBridge TOTP enrollment") + print("Scan this QR with your authenticator app.\n") + if args.mode == "compact": render_qr_compact(value["otpauth_uri"]) + elif args.mode == "mono": render_qr_mono(value["otpauth_uri"]) + elif args.mode == "unicode": render_qr_unicode(value["otpauth_uri"]) + else: render_qr_ansi(value["otpauth_uri"]) + print("Key ID: "+value["key_id"]) + print("Enrollment complete. Do not share the secret or a QR screenshot.\n") + emit({"ok":True,"key_id":value["key_id"],"secret_sha256":value["secret_sha256"],"enrolled_at":value["enrolled_at"]}) + elif args.plan_command == "totp-challenge": + emit(engine.totp_challenge(args.plan_id, args.key_id, rollback=args.rollback, ttl_seconds=args.ttl)) + elif args.plan_command == "totp-approve": + code=sys.stdin.readline().strip() + emit(engine.approve_totp(args.plan_id, args.challenge_id, args.key_id, code, rollback=args.rollback)) + elif args.plan_command == "rollback": + emit(engine.rollback(args.plan_id)) + elif args.plan_command == "list": + emit(engine.list()) + elif args.command == "audit": + emit(ctx.audit.verify()) + except ProductError as exc: + emit({ + "ok": False, + "error": str(exc), + }) + raise SystemExit(2) + + +if __name__ == "__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py new file mode 100644 index 0000000..c62cc54 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import hashlib +import json +import os +import platform +import shutil +import socket +import ssl +import subprocess +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from .product_core import AuditLog, ProductError +from .node_identity import NodeIdentityError, ensure_node_keypair +from .pairing import FBPAIR_PROTOCOL + +SUPPORTED_PROVIDERS = {"chatgpt"} +SEED_VERSION = "0.5.0-candidate.1" + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def atomic_write_json(path: Path, value: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_name("." + path.name + ".tmp") + data = json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n" + tmp.write_text(data, encoding="utf-8") + os.chmod(tmp, 0o600) + os.replace(tmp, path) + + +def read_os_release() -> dict[str, str]: + path = Path("/etc/os-release") + result: dict[str, str] = {} + if not path.is_file(): + return result + for raw in path.read_text(encoding="utf-8", errors="replace").splitlines(): + if "=" not in raw: + continue + key, value = raw.split("=", 1) + result[key] = value.strip().strip('"') + return result + + +def run_readonly(args: list[str], timeout: float = 3.0) -> dict[str, Any]: + try: + proc = subprocess.run( + args, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=False, + ) + return { + "ok": proc.returncode == 0, + "rc": proc.returncode, + "stdout": proc.stdout.strip()[:8192], + "stderr": proc.stderr.strip()[:2048], + } + except Exception as exc: + return { + "ok": False, + "error": type(exc).__name__, + } + + +class SeedContext: + def __init__( + self, + state_dir: Path | None = None, + config_dir: Path | None = None, + ): + self.state_dir = Path( + state_dir + or os.environ.get( + "FRAMEBRIDGE_SEED_STATE_DIR", + "~/.local/share/framebridge-seed", + ) + ).expanduser() + self.config_dir = Path( + config_dir + or os.environ.get( + "FRAMEBRIDGE_SEED_CONFIG_DIR", + "~/.config/framebridge-seed", + ) + ).expanduser() + + self.identity_path = self.state_dir / "identity.json" + self.config_path = self.config_dir / "config.json" + self.audit = AuditLog(self.state_dir / "audit.jsonl") + + def detect_hostname(self) -> str: + value = socket.gethostname().strip() + return value or "linux-node" + + def load_identity(self) -> dict[str, Any] | None: + if not self.identity_path.is_file(): + return None + value = json.loads(self.identity_path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise ProductError("SEED_IDENTITY_INVALID") + return value + + def load_config(self) -> dict[str, Any] | None: + if not self.config_path.is_file(): + return None + value = json.loads(self.config_path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise ProductError("SEED_CONFIG_INVALID") + return value + + def init( + self, + provider: str = "chatgpt", + hostname: str = "", + ) -> dict[str, Any]: + provider = str(provider).strip().lower() + if provider not in SUPPORTED_PROVIDERS: + raise ProductError("SEED_PROVIDER_UNSUPPORTED") + + existing = self.load_identity() + host = str(hostname).strip() or self.detect_hostname() + + if existing: + node_id = str(existing.get("node_id") or "") + if not node_id: + raise ProductError("SEED_IDENTITY_NODE_ID_MISSING") + created_at = str(existing.get("created_at") or utc_now()) + else: + node_id = str(uuid.uuid4()) + created_at = utc_now() + + try: + crypto_identity = ensure_node_keypair( + self.state_dir, + comment="framebridge-" + node_id.replace("-", "")[:8], + ) + except NodeIdentityError as exc: + raise ProductError(str(exc)) from exc + + identity = { + "schema": 2, + "node_id": node_id, + "node_id_short": node_id.replace("-", "")[:8], + "node_host_id": crypto_identity["host_id"], + "node_public_jwk": crypto_identity["public_jwk"], + "node_public_key_sha256": crypto_identity["public_key_sha256"], + "hostname": host, + "created_at": created_at, + "updated_at": utc_now(), + } + + config = { + "schema": 1, + "seed_version": SEED_VERSION, + "provider": provider, + "connection_name": f"FrameBridge {host}", + "pairing": { + "protocol": FBPAIR_PROTOCOL, + "state": "not_paired", + "transport": "unpaired", + }, + "connection_mode": "PAIRING", + "capability_profile": "seed-readonly", + "memory": { + "required": True, + "protocol": "FBM/1", + "manifest_protocol": "FBAM/1", + "mode": "read_write", + "authority": os.environ.get("FRAMEBRIDGE_MEMORY_AUTHORITY", "deployment-defined"), + }, + "hostname": host, + "node_id": node_id, + "updated_at": utc_now(), + } + + atomic_write_json(self.identity_path, identity) + atomic_write_json(self.config_path, config) + + self.audit.append( + "seed.init", + "PASS", + request={ + "provider": provider, + "hostname": host, + }, + result={ + "node_id_short": identity["node_id_short"], + "pairing_state": "not_paired", + "capability_profile": "seed-readonly", + "memory_required": True, + }, + principal="local-operator", + ) + + from .memory_store import MemoryStore + memory = MemoryStore(self).initialize() + + return { + "ok": True, + "seed_version": SEED_VERSION, + "identity": identity, + "config": config, + "memory": memory, + } + + def identity(self) -> dict[str, Any]: + identity = self.load_identity() + if identity is None: + return { + "ok": False, + "error": "SEED_NOT_INITIALIZED", + } + return { + "ok": True, + "identity": identity, + } + + def doctor(self) -> dict[str, Any]: + os_release = read_os_release() + route = ( + run_readonly(["ip", "route", "show", "default"]) + if shutil.which("ip") + else {"ok": False, "error": "IP_COMMAND_MISSING"} + ) + dns_path = Path("/etc/resolv.conf") + dns_present = dns_path.exists() and dns_path.stat().st_size > 0 + ca = ssl.get_default_verify_paths() + ca_present = bool( + (ca.cafile and Path(ca.cafile).is_file()) + or (ca.capath and Path(ca.capath).is_dir()) + ) + + checks = { + "kernel": platform.release(), + "architecture": platform.machine(), + "python": platform.python_version(), + "hostname": self.detect_hostname(), + "os_id": os_release.get("ID"), + "os_version": os_release.get("VERSION_ID"), + "shell": os.environ.get("SHELL"), + "network_default_route": bool(route.get("ok") and route.get("stdout")), + "dns_config_present": dns_present, + "ca_certificates_present": ca_present, + "systemd_present": shutil.which("systemctl") is not None, + "ip_command_present": shutil.which("ip") is not None, + "curl_present": shutil.which("curl") is not None, + "python_venv_available": self._venv_available(), + } + + required = [ + checks["network_default_route"], + checks["dns_config_present"], + checks["ca_certificates_present"], + checks["ip_command_present"], + checks["python_venv_available"], + ] + + return { + "ok": all(bool(x) for x in required), + "seed_version": SEED_VERSION, + "checks": checks, + "pairing_ready": all(bool(x) for x in required), + "readonly_only": True, + } + + def _venv_available(self) -> bool: + try: + import venv # noqa: F401 + return True + except Exception: + return False + + def status(self) -> dict[str, Any]: + identity = self.load_identity() + config = self.load_config() + doctor = self.doctor() + + if identity is None or config is None: + return { + "ok": False, + "initialized": False, + "seed_version": SEED_VERSION, + "doctor": doctor, + } + + from .memory_store import MemoryStore + memory_required = bool((config.get("memory") or {}).get("required")) + try: + memory = MemoryStore(self).verify() if memory_required else {"ok": True, "required": False} + except Exception as exc: + memory = {"ok": False, "required": memory_required, "error": str(exc)} + + return { + "ok": bool(memory.get("ok")) if memory_required else True, + "initialized": True, + "seed_version": SEED_VERSION, + "node": { + "hostname": identity.get("hostname"), + "node_id": identity.get("node_id"), + "node_id_short": identity.get("node_id_short"), + }, + "provider": config.get("provider"), + "connection_name": config.get("connection_name"), + "pairing": config.get("pairing"), + "capability_profile": config.get("capability_profile"), + "memory": memory, + "doctor": doctor, + "audit": self.audit.verify(), + } + + def fingerprint(self) -> dict[str, Any]: + identity = self.load_identity() + config = self.load_config() + if identity is None or config is None: + raise ProductError("SEED_NOT_INITIALIZED") + + body = json.dumps( + { + "node_id": identity.get("node_id"), + "hostname": identity.get("hostname"), + "provider": config.get("provider"), + "connection_name": config.get("connection_name"), + }, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + + return { + "ok": True, + "sha256": hashlib.sha256(body).hexdigest(), + "node_id_short": identity.get("node_id_short"), + "hostname": identity.get("hostname"), + "provider": config.get("provider"), + } diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py new file mode 100644 index 0000000..8e248b8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py @@ -0,0 +1,41 @@ +from __future__ import annotations +import base64,hashlib,hmac,os,struct,time +from pathlib import Path + +class TotpError(RuntimeError): pass + +def b32decode_nopad(value:str)->bytes: + s=''.join(str(value).strip().upper().split()) + if not s or any(c not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' for c in s): raise TotpError('TOTP_SECRET_INVALID') + s += '='*((8-len(s)%8)%8) + try:return base64.b32decode(s,casefold=True) + except Exception: raise TotpError('TOTP_SECRET_INVALID') from None + +def hotp(secret:bytes,counter:int,digits:int=6)->str: + digest=hmac.new(secret,struct.pack('>Q',int(counter)),hashlib.sha1).digest() + off=digest[-1]&0x0f + code=((digest[off]&0x7f)<<24)|((digest[off+1]&0xff)<<16)|((digest[off+2]&0xff)<<8)|(digest[off+3]&0xff) + return str(code%(10**digits)).zfill(digits) + +def verify_code(secret:bytes,code:str,when:float|None=None,window:int=1,period:int=30)->int|None: + if not isinstance(code,str) or len(code)!=6 or not code.isdigit(): return None + now=time.time() if when is None else float(when) + step=int(now//period) + for s in range(step-window,step+window+1): + if hmac.compare_digest(hotp(secret,s),code): return s + return None + +def load_secret(path:Path)->bytes: + p=Path(path) + if not p.is_file() or p.is_symlink(): raise TotpError('TOTP_KEY_NOT_FOUND') + raw=p.read_text().strip() + secret=b32decode_nopad(raw) + if len(secret)<20: raise TotpError('TOTP_SECRET_TOO_SHORT') + return secret + +def consume_step(used_dir:Path,key_id:str,step:int,challenge_id:str)->None: + d=Path(used_dir);d.mkdir(parents=True,exist_ok=True,mode=0o700) + p=d/(f'{key_id}-{step}.used') + try:fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) + except FileExistsError: raise TotpError('TOTP_REPLAY') from None + with os.fdopen(fd,'w') as f:f.write(str(challenge_id)+'\n') diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py new file mode 100644 index 0000000..3434155 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py @@ -0,0 +1 @@ +"""Vendored third-party libraries used by FrameBridge.""" diff --git a/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py new file mode 100644 index 0000000..0eb24ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py @@ -0,0 +1,907 @@ +# +# QR Code generator library (Python) +# +# Copyright (c) Project Nayuki. (MIT License) +# https://www.nayuki.io/page/qr-code-generator-library +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# - The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# - The Software is provided "as is", without warranty of any kind, express or +# implied, including but not limited to the warranties of merchantability, +# fitness for a particular purpose and noninfringement. In no event shall the +# authors or copyright holders be liable for any claim, damages or other +# liability, whether in an action of contract, tort or otherwise, arising from, +# out of or in connection with the Software or the use or other dealings in the +# Software. +# + +from __future__ import annotations +import collections, itertools, re +from collections.abc import Sequence +from typing import Optional, Union + + +# ---- QR Code symbol class ---- + +class QrCode: + """A QR Code symbol, which is a type of two-dimension barcode. + Invented by Denso Wave and described in the ISO/IEC 18004 standard. + Instances of this class represent an immutable square grid of dark and light cells. + The class provides static factory functions to create a QR Code from text or binary data. + The class covers the QR Code Model 2 specification, supporting all versions (sizes) + from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + + Ways to create a QR Code object: + - High level: Take the payload data and call QrCode.encode_text() or QrCode.encode_binary(). + - Mid level: Custom-make the list of segments and call QrCode.encode_segments(). + - Low level: Custom-make the array of data codeword bytes (including + segment headers and final padding, excluding error correction codewords), + supply the appropriate version number, and call the QrCode() constructor. + (Note that all ways require supplying the desired error correction level.)""" + + # ---- Static factory functions (high level) ---- + + @staticmethod + def encode_text(text: str, ecl: QrCode.Ecc) -> QrCode: + """Returns a QR Code representing the given Unicode text string at the given error correction level. + As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer + Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible + QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the + ecl argument if it can be done without increasing the version.""" + segs: list[QrSegment] = QrSegment.make_segments(text) + return QrCode.encode_segments(segs, ecl) + + + @staticmethod + def encode_binary(data: Union[bytes,Sequence[int]], ecl: QrCode.Ecc) -> QrCode: + """Returns a QR Code representing the given binary data at the given error correction level. + This function always encodes using the binary segment mode, not any text mode. The maximum number of + bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.""" + return QrCode.encode_segments([QrSegment.make_bytes(data)], ecl) + + + # ---- Static factory functions (mid level) ---- + + @staticmethod + def encode_segments(segs: Sequence[QrSegment], ecl: QrCode.Ecc, minversion: int = 1, maxversion: int = 40, mask: int = -1, boostecl: bool = True) -> QrCode: + """Returns a QR Code representing the given segments with the given encoding parameters. + The smallest possible QR Code version within the given range is automatically + chosen for the output. Iff boostecl is true, then the ECC level of the result + may be higher than the ecl argument if it can be done without increasing the + version. The mask number is either between 0 to 7 (inclusive) to force that + mask, or -1 to automatically choose an appropriate mask (which may be slow). + This function allows the user to create a custom sequence of segments that switches + between modes (such as alphanumeric and byte) to encode text in less space. + This is a mid-level API; the high-level API is encode_text() and encode_binary().""" + + if not (QrCode.MIN_VERSION <= minversion <= maxversion <= QrCode.MAX_VERSION) or not (-1 <= mask <= 7): + raise ValueError("Invalid value") + + # Find the minimal version number to use + for version in range(minversion, maxversion + 1): + datacapacitybits: int = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available + datausedbits: Optional[int] = QrSegment.get_total_bits(segs, version) + if (datausedbits is not None) and (datausedbits <= datacapacitybits): + break # This version number is found to be suitable + if version >= maxversion: # All versions in the range could not fit the given data + msg: str = "Segment too long" + if datausedbits is not None: + msg = f"Data length = {datausedbits} bits, Max capacity = {datacapacitybits} bits" + raise DataTooLongError(msg) + assert datausedbits is not None + + # Increase the error correction level while the data still fits in the current version number + for newecl in (QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH): # From low to high + if boostecl and (datausedbits <= QrCode._get_num_data_codewords(version, newecl) * 8): + ecl = newecl + + # Concatenate all segments to create the data bit string + bb = _BitBuffer() + for seg in segs: + bb.append_bits(seg.get_mode().get_mode_bits(), 4) + bb.append_bits(seg.get_num_chars(), seg.get_mode().num_char_count_bits(version)) + bb.extend(seg._bitdata) + assert len(bb) == datausedbits + + # Add terminator and pad up to a byte if applicable + datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8 + assert len(bb) <= datacapacitybits + bb.append_bits(0, min(4, datacapacitybits - len(bb))) + bb.append_bits(0, -len(bb) % 8) # Note: Python's modulo on negative numbers behaves better than C family languages + assert len(bb) % 8 == 0 + + # Pad with alternating bytes until data capacity is reached + for padbyte in itertools.cycle((0xEC, 0x11)): + if len(bb) >= datacapacitybits: + break + bb.append_bits(padbyte, 8) + + # Pack bits into bytes in big endian + datacodewords = bytearray([0] * (len(bb) // 8)) + for (i, bit) in enumerate(bb): + datacodewords[i >> 3] |= bit << (7 - (i & 7)) + + # Create the QR Code object + return QrCode(version, ecl, datacodewords, mask) + + + # ---- Private fields ---- + + # The version number of this QR Code, which is between 1 and 40 (inclusive). + # This determines the size of this barcode. + _version: int + + # The width and height of this QR Code, measured in modules, between + # 21 and 177 (inclusive). This is equal to version * 4 + 17. + _size: int + + # The error correction level used in this QR Code. + _errcorlvl: QrCode.Ecc + + # The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + # Even if a QR Code is created with automatic masking requested (mask = -1), + # the resulting object still has a mask value between 0 and 7. + _mask: int + + # The modules of this QR Code (False = light, True = dark). + # Immutable after constructor finishes. Accessed through get_module(). + _modules: list[list[bool]] + + # Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + _isfunction: list[list[bool]] + + + # ---- Constructor (low level) ---- + + def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: Union[bytes,Sequence[int]], msk: int) -> None: + """Creates a new QR Code with the given version number, + error correction level, data codeword bytes, and mask number. + This is a low-level API that most users should not use directly. + A mid-level API is the encode_segments() function.""" + + # Check scalar arguments and set fields + if not (QrCode.MIN_VERSION <= version <= QrCode.MAX_VERSION): + raise ValueError("Version value out of range") + if not (-1 <= msk <= 7): + raise ValueError("Mask value out of range") + + self._version = version + self._size = version * 4 + 17 + self._errcorlvl = errcorlvl + + # Initialize both grids to be size*size arrays of Boolean false + self._modules = [[False] * self._size for _ in range(self._size)] # Initially all light + self._isfunction = [[False] * self._size for _ in range(self._size)] + + # Compute ECC, draw modules + self._draw_function_patterns() + allcodewords: bytes = self._add_ecc_and_interleave(bytearray(datacodewords)) + self._draw_codewords(allcodewords) + + # Do masking + if msk == -1: # Automatically choose best mask + minpenalty: int = 1 << 32 + for i in range(8): + self._apply_mask(i) + self._draw_format_bits(i) + penalty = self._get_penalty_score() + if penalty < minpenalty: + msk = i + minpenalty = penalty + self._apply_mask(i) # Undoes the mask due to XOR + assert 0 <= msk <= 7 + self._mask = msk + self._apply_mask(msk) # Apply the final choice of mask + self._draw_format_bits(msk) # Overwrite old format bits + + del self._isfunction + + + # ---- Accessor methods ---- + + def get_version(self) -> int: + """Returns this QR Code's version number, in the range [1, 40].""" + return self._version + + def get_size(self) -> int: + """Returns this QR Code's size, in the range [21, 177].""" + return self._size + + def get_error_correction_level(self) -> QrCode.Ecc: + """Returns this QR Code's error correction level.""" + return self._errcorlvl + + def get_mask(self) -> int: + """Returns this QR Code's mask, in the range [0, 7].""" + return self._mask + + def get_module(self, x: int, y: int) -> bool: + """Returns the color of the module (pixel) at the given coordinates, which is False + for light or True for dark. The top left corner has the coordinates (x=0, y=0). + If the given coordinates are out of bounds, then False (light) is returned.""" + return (0 <= x < self._size) and (0 <= y < self._size) and self._modules[y][x] + + + # ---- Private helper methods for constructor: Drawing function modules ---- + + def _draw_function_patterns(self) -> None: + """Reads this object's version field, and draws and marks all function modules.""" + # Draw horizontal and vertical timing patterns + for i in range(self._size): + self._set_function_module(6, i, i % 2 == 0) + self._set_function_module(i, 6, i % 2 == 0) + + # Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + self._draw_finder_pattern(3, 3) + self._draw_finder_pattern(self._size - 4, 3) + self._draw_finder_pattern(3, self._size - 4) + + # Draw numerous alignment patterns + alignpatpos: list[int] = self._get_alignment_pattern_positions() + numalign: int = len(alignpatpos) + skips: Sequence[tuple[int,int]] = ((0, 0), (0, numalign - 1), (numalign - 1, 0)) + for i in range(numalign): + for j in range(numalign): + if (i, j) not in skips: # Don't draw on the three finder corners + self._draw_alignment_pattern(alignpatpos[i], alignpatpos[j]) + + # Draw configuration data + self._draw_format_bits(0) # Dummy mask value; overwritten later in the constructor + self._draw_version() + + + def _draw_format_bits(self, mask: int) -> None: + """Draws two copies of the format bits (with its own error correction code) + based on the given mask and this object's error correction level field.""" + # Calculate error correction code and pack bits + data: int = self._errcorlvl.formatbits << 3 | mask # errCorrLvl is uint2, mask is uint3 + rem: int = data + for _ in range(10): + rem = (rem << 1) ^ ((rem >> 9) * 0x537) + bits: int = (data << 10 | rem) ^ 0x5412 # uint15 + assert bits >> 15 == 0 + + # Draw first copy + for i in range(0, 6): + self._set_function_module(8, i, _get_bit(bits, i)) + self._set_function_module(8, 7, _get_bit(bits, 6)) + self._set_function_module(8, 8, _get_bit(bits, 7)) + self._set_function_module(7, 8, _get_bit(bits, 8)) + for i in range(9, 15): + self._set_function_module(14 - i, 8, _get_bit(bits, i)) + + # Draw second copy + for i in range(0, 8): + self._set_function_module(self._size - 1 - i, 8, _get_bit(bits, i)) + for i in range(8, 15): + self._set_function_module(8, self._size - 15 + i, _get_bit(bits, i)) + self._set_function_module(8, self._size - 8, True) # Always dark + + + def _draw_version(self) -> None: + """Draws two copies of the version bits (with its own error correction code), + based on this object's version field, iff 7 <= version <= 40.""" + if self._version < 7: + return + + # Calculate error correction code and pack bits + rem: int = self._version # version is uint6, in the range [7, 40] + for _ in range(12): + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) + bits: int = self._version << 12 | rem # uint18 + assert bits >> 18 == 0 + + # Draw two copies + for i in range(18): + bit: bool = _get_bit(bits, i) + a: int = self._size - 11 + i % 3 + b: int = i // 3 + self._set_function_module(a, b, bit) + self._set_function_module(b, a, bit) + + + def _draw_finder_pattern(self, x: int, y: int) -> None: + """Draws a 9*9 finder pattern including the border separator, + with the center module at (x, y). Modules can be out of bounds.""" + for dy in range(-4, 5): + for dx in range(-4, 5): + xx, yy = x + dx, y + dy + if (0 <= xx < self._size) and (0 <= yy < self._size): + # Chebyshev/infinity norm + self._set_function_module(xx, yy, max(abs(dx), abs(dy)) not in (2, 4)) + + + def _draw_alignment_pattern(self, x: int, y: int) -> None: + """Draws a 5*5 alignment pattern, with the center module + at (x, y). All modules must be in bounds.""" + for dy in range(-2, 3): + for dx in range(-2, 3): + self._set_function_module(x + dx, y + dy, max(abs(dx), abs(dy)) != 1) + + + def _set_function_module(self, x: int, y: int, isdark: bool) -> None: + """Sets the color of a module and marks it as a function module. + Only used by the constructor. Coordinates must be in bounds.""" + assert type(isdark) is bool + self._modules[y][x] = isdark + self._isfunction[y][x] = True + + + # ---- Private helper methods for constructor: Codewords and masking ---- + + def _add_ecc_and_interleave(self, data: bytearray) -> bytes: + """Returns a new byte string representing the given data with the appropriate error correction + codewords appended to it, based on this object's version and error correction level.""" + version: int = self._version + assert len(data) == QrCode._get_num_data_codewords(version, self._errcorlvl) + + # Calculate parameter numbers + numblocks: int = QrCode._NUM_ERROR_CORRECTION_BLOCKS[self._errcorlvl.ordinal][version] + blockecclen: int = QrCode._ECC_CODEWORDS_PER_BLOCK [self._errcorlvl.ordinal][version] + rawcodewords: int = QrCode._get_num_raw_data_modules(version) // 8 + numshortblocks: int = numblocks - rawcodewords % numblocks + shortblocklen: int = rawcodewords // numblocks + + # Split data into blocks and append ECC to each block + blocks: list[bytes] = [] + rsdiv: bytes = QrCode._reed_solomon_compute_divisor(blockecclen) + k: int = 0 + for i in range(numblocks): + dat: bytearray = data[k : k + shortblocklen - blockecclen + (0 if i < numshortblocks else 1)] + k += len(dat) + ecc: bytes = QrCode._reed_solomon_compute_remainder(dat, rsdiv) + if i < numshortblocks: + dat.append(0) + blocks.append(dat + ecc) + assert k == len(data) + + # Interleave (not concatenate) the bytes from every block into a single sequence + result = bytearray() + for i in range(len(blocks[0])): + for (j, blk) in enumerate(blocks): + # Skip the padding byte in short blocks + if (i != shortblocklen - blockecclen) or (j >= numshortblocks): + result.append(blk[i]) + assert len(result) == rawcodewords + return result + + + def _draw_codewords(self, data: bytes) -> None: + """Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + data area of this QR Code. Function modules need to be marked off before this is called.""" + assert len(data) == QrCode._get_num_raw_data_modules(self._version) // 8 + + i: int = 0 # Bit index into the data + # Do the funny zigzag scan + for right in range(self._size - 1, 0, -2): # Index of right column in each column pair + if right <= 6: + right -= 1 + for vert in range(self._size): # Vertical counter + for j in range(2): + x: int = right - j # Actual x coordinate + upward: bool = (right + 1) & 2 == 0 + y: int = (self._size - 1 - vert) if upward else vert # Actual y coordinate + if (not self._isfunction[y][x]) and (i < len(data) * 8): + self._modules[y][x] = _get_bit(data[i >> 3], 7 - (i & 7)) + i += 1 + # If this QR Code has any remainder bits (0 to 7), they were assigned as + # 0/false/light by the constructor and are left unchanged by this method + assert i == len(data) * 8 + + + def _apply_mask(self, mask: int) -> None: + """XORs the codeword modules in this QR Code with the given mask pattern. + The function modules must be marked and the codeword bits must be drawn + before masking. Due to the arithmetic of XOR, calling _apply_mask() with + the same mask value a second time will undo the mask. A final well-formed + QR Code needs exactly one (not zero, two, etc.) mask applied.""" + if not (0 <= mask <= 7): + raise ValueError("Mask value out of range") + masker: collections.abc.Callable[[int,int],int] = QrCode._MASK_PATTERNS[mask] + for y in range(self._size): + for x in range(self._size): + self._modules[y][x] ^= (masker(x, y) == 0) and (not self._isfunction[y][x]) + + + def _get_penalty_score(self) -> int: + """Calculates and returns the penalty score based on state of this QR Code's current modules. + This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.""" + result: int = 0 + size: int = self._size + modules: list[list[bool]] = self._modules + + # Adjacent modules in row having same color, and finder-like patterns + for y in range(size): + runcolor: bool = False + runx: int = 0 + runhistory = collections.deque([0] * 7, 7) + for x in range(size): + if modules[y][x] == runcolor: + runx += 1 + if runx == 5: + result += QrCode._PENALTY_N1 + elif runx > 5: + result += 1 + else: + self._finder_penalty_add_history(runx, runhistory) + if not runcolor: + result += self._finder_penalty_count_patterns(runhistory) * QrCode._PENALTY_N3 + runcolor = modules[y][x] + runx = 1 + result += self._finder_penalty_terminate_and_count(runcolor, runx, runhistory) * QrCode._PENALTY_N3 + # Adjacent modules in column having same color, and finder-like patterns + for x in range(size): + runcolor = False + runy: int = 0 + runhistory = collections.deque([0] * 7, 7) + for y in range(size): + if modules[y][x] == runcolor: + runy += 1 + if runy == 5: + result += QrCode._PENALTY_N1 + elif runy > 5: + result += 1 + else: + self._finder_penalty_add_history(runy, runhistory) + if not runcolor: + result += self._finder_penalty_count_patterns(runhistory) * QrCode._PENALTY_N3 + runcolor = modules[y][x] + runy = 1 + result += self._finder_penalty_terminate_and_count(runcolor, runy, runhistory) * QrCode._PENALTY_N3 + + # 2*2 blocks of modules having same color + for y in range(size - 1): + for x in range(size - 1): + if modules[y][x] == modules[y][x + 1] == modules[y + 1][x] == modules[y + 1][x + 1]: + result += QrCode._PENALTY_N2 + + # Balance of dark and light modules + dark: int = sum((1 if cell else 0) for row in modules for cell in row) + total: int = size**2 # Note that size is odd, so dark/total != 1/2 + # Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + k: int = (abs(dark * 20 - total * 10) + total - 1) // total - 1 + assert 0 <= k <= 9 + result += k * QrCode._PENALTY_N4 + assert 0 <= result <= 2568888 # Non-tight upper bound based on default values of PENALTY_N1, ..., N4 + return result + + + # ---- Private helper functions ---- + + def _get_alignment_pattern_positions(self) -> list[int]: + """Returns an ascending list of positions of alignment patterns for this version number. + Each position is in the range [0,177), and are used on both the x and y axes. + This could be implemented as lookup table of 40 variable-length lists of integers.""" + if self._version == 1: + return [] + else: + numalign: int = self._version // 7 + 2 + step: int = (self._version * 8 + numalign * 3 + 5) // (numalign * 4 - 4) * 2 + result: list[int] = [(self._size - 7 - i * step) for i in range(numalign - 1)] + [6] + return list(reversed(result)) + + + @staticmethod + def _get_num_raw_data_modules(ver: int) -> int: + """Returns the number of data bits that can be stored in a QR Code of the given version number, after + all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.""" + if not (QrCode.MIN_VERSION <= ver <= QrCode.MAX_VERSION): + raise ValueError("Version number out of range") + result: int = (16 * ver + 128) * ver + 64 + if ver >= 2: + numalign: int = ver // 7 + 2 + result -= (25 * numalign - 10) * numalign - 55 + if ver >= 7: + result -= 36 + assert 208 <= result <= 29648 + return result + + + @staticmethod + def _get_num_data_codewords(ver: int, ecl: QrCode.Ecc) -> int: + """Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + QR Code of the given version number and error correction level, with remainder bits discarded. + This stateless pure function could be implemented as a (40*4)-cell lookup table.""" + return QrCode._get_num_raw_data_modules(ver) // 8 \ + - QrCode._ECC_CODEWORDS_PER_BLOCK [ecl.ordinal][ver] \ + * QrCode._NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] + + + @staticmethod + def _reed_solomon_compute_divisor(degree: int) -> bytes: + """Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + implemented as a lookup table over all possible parameter values, instead of as an algorithm.""" + if not (1 <= degree <= 255): + raise ValueError("Degree out of range") + # Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + # For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array [255, 8, 93]. + result = bytearray([0] * (degree - 1) + [1]) # Start off with the monomial x^0 + + # Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + # and drop the highest monomial term which is always 1x^degree. + # Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + root: int = 1 + for _ in range(degree): # Unused variable i + # Multiply the current product by (x - r^i) + for j in range(degree): + result[j] = QrCode._reed_solomon_multiply(result[j], root) + if j + 1 < degree: + result[j] ^= result[j + 1] + root = QrCode._reed_solomon_multiply(root, 0x02) + return result + + + @staticmethod + def _reed_solomon_compute_remainder(data: bytes, divisor: bytes) -> bytes: + """Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.""" + result = bytearray([0] * len(divisor)) + for b in data: # Polynomial division + factor: int = b ^ result.pop(0) + result.append(0) + for (i, coef) in enumerate(divisor): + result[i] ^= QrCode._reed_solomon_multiply(coef, factor) + return result + + + @staticmethod + def _reed_solomon_multiply(x: int, y: int) -> int: + """Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.""" + if (x >> 8 != 0) or (y >> 8 != 0): + raise ValueError("Byte out of range") + # Russian peasant multiplication + z: int = 0 + for i in reversed(range(8)): + z = (z << 1) ^ ((z >> 7) * 0x11D) + z ^= ((y >> i) & 1) * x + assert z >> 8 == 0 + return z + + + def _finder_penalty_count_patterns(self, runhistory: collections.deque[int]) -> int: + """Can only be called immediately after a light run is added, and + returns either 0, 1, or 2. A helper function for _get_penalty_score().""" + n: int = runhistory[1] + assert n <= self._size * 3 + core: bool = n > 0 and (runhistory[2] == runhistory[4] == runhistory[5] == n) and runhistory[3] == n * 3 + return (1 if (core and runhistory[0] >= n * 4 and runhistory[6] >= n) else 0) \ + + (1 if (core and runhistory[6] >= n * 4 and runhistory[0] >= n) else 0) + + + def _finder_penalty_terminate_and_count(self, currentruncolor: bool, currentrunlength: int, runhistory: collections.deque[int]) -> int: + """Must be called at the end of a line (row or column) of modules. A helper function for _get_penalty_score().""" + if currentruncolor: # Terminate dark run + self._finder_penalty_add_history(currentrunlength, runhistory) + currentrunlength = 0 + currentrunlength += self._size # Add light border to final run + self._finder_penalty_add_history(currentrunlength, runhistory) + return self._finder_penalty_count_patterns(runhistory) + + + def _finder_penalty_add_history(self, currentrunlength: int, runhistory: collections.deque[int]) -> None: + if runhistory[0] == 0: + currentrunlength += self._size # Add light border to initial run + runhistory.appendleft(currentrunlength) + + + # ---- Constants and tables ---- + + MIN_VERSION: int = 1 # The minimum version number supported in the QR Code Model 2 standard + MAX_VERSION: int = 40 # The maximum version number supported in the QR Code Model 2 standard + + # For use in _get_penalty_score(), when evaluating which mask is best. + _PENALTY_N1: int = 3 + _PENALTY_N2: int = 3 + _PENALTY_N3: int = 40 + _PENALTY_N4: int = 10 + + _ECC_CODEWORDS_PER_BLOCK: Sequence[Sequence[int]] = ( + # Version: (note that index 0 is for padding, and is set to an illegal value) + # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + (-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30), # Low + (-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28), # Medium + (-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30), # Quartile + (-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30)) # High + + _NUM_ERROR_CORRECTION_BLOCKS: Sequence[Sequence[int]] = ( + # Version: (note that index 0 is for padding, and is set to an illegal value) + # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + (-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25), # Low + (-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49), # Medium + (-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68), # Quartile + (-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81)) # High + + _MASK_PATTERNS: Sequence[collections.abc.Callable[[int,int],int]] = ( + (lambda x, y: (x + y) % 2 ), + (lambda x, y: y % 2 ), + (lambda x, y: x % 3 ), + (lambda x, y: (x + y) % 3 ), + (lambda x, y: (x // 3 + y // 2) % 2 ), + (lambda x, y: x * y % 2 + x * y % 3 ), + (lambda x, y: (x * y % 2 + x * y % 3) % 2 ), + (lambda x, y: ((x + y) % 2 + x * y % 3) % 2), + ) + + + # ---- Public helper enumeration ---- + + class Ecc: + ordinal: int # (Public) In the range 0 to 3 (unsigned 2-bit integer) + formatbits: int # (Package-private) In the range 0 to 3 (unsigned 2-bit integer) + + """The error correction level in a QR Code symbol. Immutable.""" + # Private constructor + def __init__(self, i: int, fb: int) -> None: + self.ordinal = i + self.formatbits = fb + + # Placeholders + LOW : QrCode.Ecc + MEDIUM : QrCode.Ecc + QUARTILE: QrCode.Ecc + HIGH : QrCode.Ecc + + # Public constants. Create them outside the class. + Ecc.LOW = Ecc(0, 1) # The QR Code can tolerate about 7% erroneous codewords + Ecc.MEDIUM = Ecc(1, 0) # The QR Code can tolerate about 15% erroneous codewords + Ecc.QUARTILE = Ecc(2, 3) # The QR Code can tolerate about 25% erroneous codewords + Ecc.HIGH = Ecc(3, 2) # The QR Code can tolerate about 30% erroneous codewords + + + +# ---- Data segment class ---- + +class QrSegment: + """A segment of character/binary/control data in a QR Code symbol. + Instances of this class are immutable. + The mid-level way to create a segment is to take the payload data + and call a static factory function such as QrSegment.make_numeric(). + The low-level way to create a segment is to custom-make the bit buffer + and call the QrSegment() constructor with appropriate values. + This segment class imposes no length restrictions, but QR Codes have restrictions. + Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + Any segment longer than this is meaningless for the purpose of generating QR Codes.""" + + # ---- Static factory functions (mid level) ---- + + @staticmethod + def make_bytes(data: Union[bytes,Sequence[int]]) -> QrSegment: + """Returns a segment representing the given binary data encoded in byte mode. + All input byte lists are acceptable. Any text string can be converted to + UTF-8 bytes (s.encode("UTF-8")) and encoded as a byte mode segment.""" + bb = _BitBuffer() + for b in data: + bb.append_bits(b, 8) + return QrSegment(QrSegment.Mode.BYTE, len(data), bb) + + + @staticmethod + def make_numeric(digits: str) -> QrSegment: + """Returns a segment representing the given string of decimal digits encoded in numeric mode.""" + if not QrSegment.is_numeric(digits): + raise ValueError("String contains non-numeric characters") + bb = _BitBuffer() + i: int = 0 + while i < len(digits): # Consume up to 3 digits per iteration + n: int = min(len(digits) - i, 3) + bb.append_bits(int(digits[i : i + n]), n * 3 + 1) + i += n + return QrSegment(QrSegment.Mode.NUMERIC, len(digits), bb) + + + @staticmethod + def make_alphanumeric(text: str) -> QrSegment: + """Returns a segment representing the given text string encoded in alphanumeric mode. + The characters allowed are: 0 to 9, A to Z (uppercase only), space, + dollar, percent, asterisk, plus, hyphen, period, slash, colon.""" + if not QrSegment.is_alphanumeric(text): + raise ValueError("String contains unencodable characters in alphanumeric mode") + bb = _BitBuffer() + for i in range(0, len(text) - 1, 2): # Process groups of 2 + temp: int = QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[i]] * 45 + temp += QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[i + 1]] + bb.append_bits(temp, 11) + if len(text) % 2 > 0: # 1 character remaining + bb.append_bits(QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[-1]], 6) + return QrSegment(QrSegment.Mode.ALPHANUMERIC, len(text), bb) + + + @staticmethod + def make_segments(text: str) -> list[QrSegment]: + """Returns a new mutable list of zero or more segments to represent the given Unicode text string. + The result may use various segment modes and switch modes to optimize the length of the bit stream.""" + + # Select the most efficient segment encoding automatically + if text == "": + return [] + elif QrSegment.is_numeric(text): + return [QrSegment.make_numeric(text)] + elif QrSegment.is_alphanumeric(text): + return [QrSegment.make_alphanumeric(text)] + else: + return [QrSegment.make_bytes(text.encode("UTF-8"))] + + + @staticmethod + def make_eci(assignval: int) -> QrSegment: + """Returns a segment representing an Extended Channel Interpretation + (ECI) designator with the given assignment value.""" + bb = _BitBuffer() + if assignval < 0: + raise ValueError("ECI assignment value out of range") + elif assignval < (1 << 7): + bb.append_bits(assignval, 8) + elif assignval < (1 << 14): + bb.append_bits(0b10, 2) + bb.append_bits(assignval, 14) + elif assignval < 1000000: + bb.append_bits(0b110, 3) + bb.append_bits(assignval, 21) + else: + raise ValueError("ECI assignment value out of range") + return QrSegment(QrSegment.Mode.ECI, 0, bb) + + + # Tests whether the given string can be encoded as a segment in numeric mode. + # A string is encodable iff each character is in the range 0 to 9. + @staticmethod + def is_numeric(text: str) -> bool: + return QrSegment._NUMERIC_REGEX.fullmatch(text) is not None + + + # Tests whether the given string can be encoded as a segment in alphanumeric mode. + # A string is encodable iff each character is in the following set: 0 to 9, A to Z + # (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + @staticmethod + def is_alphanumeric(text: str) -> bool: + return QrSegment._ALPHANUMERIC_REGEX.fullmatch(text) is not None + + + # ---- Private fields ---- + + # The mode indicator of this segment. Accessed through get_mode(). + _mode: QrSegment.Mode + + # The length of this segment's unencoded data. Measured in characters for + # numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + # Always zero or positive. Not the same as the data's bit length. + # Accessed through get_num_chars(). + _numchars: int + + # The data bits of this segment. Accessed through get_data(). + _bitdata: list[int] + + + # ---- Constructor (low level) ---- + + def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: Sequence[int]) -> None: + """Creates a new QR Code segment with the given attributes and data. + The character count (numch) must agree with the mode and the bit buffer length, + but the constraint isn't checked. The given bit buffer is cloned and stored.""" + if numch < 0: + raise ValueError() + self._mode = mode + self._numchars = numch + self._bitdata = list(bitdata) # Make defensive copy + + + # ---- Accessor methods ---- + + def get_mode(self) -> QrSegment.Mode: + """Returns the mode field of this segment.""" + return self._mode + + def get_num_chars(self) -> int: + """Returns the character count field of this segment.""" + return self._numchars + + def get_data(self) -> list[int]: + """Returns a new copy of the data bits of this segment.""" + return list(self._bitdata) # Make defensive copy + + + # Package-private function + @staticmethod + def get_total_bits(segs: Sequence[QrSegment], version: int) -> Optional[int]: + """Calculates the number of bits needed to encode the given segments at + the given version. Returns a non-negative number if successful. Otherwise + returns None if a segment has too many characters to fit its length field.""" + result = 0 + for seg in segs: + ccbits: int = seg.get_mode().num_char_count_bits(version) + if seg.get_num_chars() >= (1 << ccbits): + return None # The segment's length doesn't fit the field's bit width + result += 4 + ccbits + len(seg._bitdata) + return result + + + # ---- Constants ---- + + # Describes precisely all strings that are encodable in numeric mode. + _NUMERIC_REGEX: re.Pattern[str] = re.compile(r"[0-9]*") + + # Describes precisely all strings that are encodable in alphanumeric mode. + _ALPHANUMERIC_REGEX: re.Pattern[str] = re.compile(r"[A-Z0-9 $%*+./:-]*") + + # Dictionary of "0"->0, "A"->10, "$"->37, etc. + _ALPHANUMERIC_ENCODING_TABLE: dict[str,int] = {ch: i for (i, ch) in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:")} + + + # ---- Public helper enumeration ---- + + class Mode: + """Describes how a segment's data bits are interpreted. Immutable.""" + + _modebits: int # The mode indicator bits, which is a uint4 value (range 0 to 15) + _charcounts: tuple[int,int,int] # Number of character count bits for three different version ranges + + # Private constructor + def __init__(self, modebits: int, charcounts: tuple[int,int,int]): + self._modebits = modebits + self._charcounts = charcounts + + # Package-private method + def get_mode_bits(self) -> int: + """Returns an unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object.""" + return self._modebits + + # Package-private method + def num_char_count_bits(self, ver: int) -> int: + """Returns the bit width of the character count field for a segment in this mode + in a QR Code at the given version number. The result is in the range [0, 16].""" + return self._charcounts[(ver + 7) // 17] + + # Placeholders + NUMERIC : QrSegment.Mode + ALPHANUMERIC: QrSegment.Mode + BYTE : QrSegment.Mode + KANJI : QrSegment.Mode + ECI : QrSegment.Mode + + # Public constants. Create them outside the class. + Mode.NUMERIC = Mode(0x1, (10, 12, 14)) + Mode.ALPHANUMERIC = Mode(0x2, ( 9, 11, 13)) + Mode.BYTE = Mode(0x4, ( 8, 16, 16)) + Mode.KANJI = Mode(0x8, ( 8, 10, 12)) + Mode.ECI = Mode(0x7, ( 0, 0, 0)) + + + +# ---- Private helper class ---- + +class _BitBuffer(list[int]): + """An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.""" + + def append_bits(self, val: int, n: int) -> None: + """Appends the given number of low-order bits of the given + value to this buffer. Requires n >= 0 and 0 <= val < 2^n.""" + if (n < 0) or (val >> n != 0): + raise ValueError("Value out of range") + self.extend(((val >> i) & 1) for i in reversed(range(n))) + + +def _get_bit(x: int, i: int) -> bool: + """Returns true iff the i'th bit of x is set to 1.""" + return (x >> i) & 1 != 0 + + + +class DataTooLongError(ValueError): + """Raised when the supplied data does not fit any QR Code version. Ways to handle this exception include: + - Decrease the error correction level if it was greater than Ecc.LOW. + - If the encode_segments() function was called with a maxversion argument, then increase + it if it was less than QrCode.MAX_VERSION. (This advice does not apply to the other + factory functions because they search all versions up to QrCode.MAX_VERSION.) + - Split the text data into better or optimal segments in order to reduce the number of bits required. + - Change the text or binary data to be shorter. + - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + - Propagate the error upward to the caller/user.""" + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py new file mode 100644 index 0000000..beee47b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py @@ -0,0 +1,42 @@ +"""Separate loopback-only MCP entry point for the six Fleet READ tools.""" +from __future__ import annotations + +import os + +from mcp.server.fastmcp import FastMCP + +from framebridge_node_gateway import discover_nodes, node_read +from framebridge_linux_chatgpt.fleet_hub import FleetHub +from framebridge_linux_chatgpt.fleet_mcp_surface import register_fleet_read_tools + + +def _server_address() -> tuple[str, int]: + host = os.environ.get("FRAMEBRIDGE_FLEET_HUB_HOST", "127.0.0.1") + if not isinstance(host, str) or host not in ("127.0.0.1", "::1"): + raise ValueError("FLEET_HUB_HOST_INVALID") + try: + port = int(os.environ.get("FRAMEBRIDGE_FLEET_HUB_PORT", "8877")) + except (TypeError, ValueError): + raise ValueError("FLEET_HUB_PORT_INVALID") from None + if not 1024 <= port <= 65535: + raise ValueError("FLEET_HUB_PORT_INVALID") + return host, port + + +host, port = _server_address() +mcp = FastMCP( + "FrameBridge Fleet Hub", + host=host, + port=port, + streamable_http_path="/mcp", +) +hub = FleetHub(discover_nodes, node_read) +register_fleet_read_tools(mcp, hub) + + +def main() -> None: + mcp.run(transport="streamable-http") + + +if __name__ == "__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py new file mode 100644 index 0000000..24e4ef0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py @@ -0,0 +1 @@ +"""FrameBridge Fleet Hub runtime.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py new file mode 100644 index 0000000..7074e15 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py @@ -0,0 +1,42 @@ +"""Injected Node Gateway READ boundary; no transport or live gateway imports.""" +from __future__ import annotations + +from typing import Callable + +from .fleet_registry import FleetNode +from .fleet_routing import Operation, ReadRequest, normalize_read_args + + +class GatewayReadError(ValueError): + """Fixed local error codes; remote exceptions are never copied.""" + + +class GatewayReadAdapter: + def __init__(self, gateway_node_read: Callable[[str, str, dict], dict]) -> None: + if not callable(gateway_node_read): + raise GatewayReadError("FLEET_GATEWAY_CONFIG_INVALID") + self._gateway_node_read = gateway_node_read + + def __call__(self, node: FleetNode, request: ReadRequest) -> dict: + if type(node) is not FleetNode or type(request) is not ReadRequest: + raise GatewayReadError("FLEET_GATEWAY_REQUEST_INVALID") + if request.node_id != node.node_id: + raise GatewayReadError("FLEET_GATEWAY_NODE_MISMATCH") + if request.operation is not Operation.READ or not node.online: + raise GatewayReadError("FLEET_GATEWAY_READ_BLOCKED") + try: + args = normalize_read_args(request.capability, request.args) + except ValueError: + raise GatewayReadError("FLEET_GATEWAY_REQUEST_INVALID") from None + try: + response = self._gateway_node_read(node.alias, request.capability.value, args) + except Exception: + raise GatewayReadError("FLEET_GATEWAY_READ_FAILED") from None + if type(response) is not dict or response.get("ok") is not True: + raise GatewayReadError("FLEET_GATEWAY_READ_FAILED") + for key in ("node", "alias"): + if key in response and (type(response[key]) is not str + or response[key] != node.alias): + raise GatewayReadError("FLEET_GATEWAY_ALIAS_MISMATCH") + # Metadata is payload only. It cannot update identity, routing or policy. + return response diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py new file mode 100644 index 0000000..3a9b6a9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py @@ -0,0 +1,65 @@ +"""Synchronous, in-memory Fleet READ hub using injected discovery and gateway.""" +from __future__ import annotations + +from typing import Callable + +from .fleet_gateway_adapter import GatewayReadAdapter +from .fleet_registry import FleetNode, FleetRegistry, RegistryError +from .fleet_routing import FleetRequest, FleetRouter, RouteResult + + +class FleetHubError(ValueError): + """Sanitized discovery/configuration failure.""" + + +class FleetHub: + def __init__(self, discover_nodes: Callable[[], dict], + gateway_node_read: Callable[[str, str, dict], dict]) -> None: + if not callable(discover_nodes): + raise FleetHubError("FLEET_HUB_CONFIG_INVALID") + self._discover_nodes = discover_nodes + self._adapter = GatewayReadAdapter(gateway_node_read) + registry = FleetRegistry() + self._snapshot = (registry, FleetRouter(registry, self._adapter)) + + def refresh(self) -> tuple[FleetNode, ...]: + """Publish only a complete valid registry; failures preserve the old one. + + Entries with explicit pairing state must be paired. Legacy entries need + an explicit stable ID too. Missing descriptive metadata stays unknown; + it never supplies identity or readiness. Invalid entries are omitted, + but duplicate IDs/aliases and cross-namespace collisions abort refresh. + """ + try: + discovery = self._discover_nodes() + if (type(discovery) is not dict or discovery.get("ok") is not True + or type(discovery.get("nodes")) is not list): + raise FleetHubError("FLEET_REFRESH_FAILED") + registry = FleetRegistry() + for entry in discovery["nodes"]: + if type(entry) is not dict: + continue + if "pairing_state" in entry and entry["pairing_state"] != "paired": + continue + try: + node = FleetNode( + node_id=entry.get("node_id"), alias=entry.get("alias"), + hostname=entry.get("hostname", "unknown"), + transport=entry.get("transport", "unknown"), + capability_profile=entry.get("capability_profile", "unknown"), + online=entry.get("operational_transport_ready") is True, + ) + except RegistryError: + continue + registry.register(node) + router = FleetRouter(registry, self._adapter) + except Exception: + raise FleetHubError("FLEET_REFRESH_FAILED") from None + self._snapshot = (registry, router) + return registry.list_nodes() + + def read(self, target: str, capability: str, args: dict | None = None) -> RouteResult: + return self._snapshot[1].route(FleetRequest(target, capability, args=args)) + + def nodes(self) -> tuple[FleetNode, ...]: + return self._snapshot[0].list_nodes() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py new file mode 100644 index 0000000..78d4890 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py @@ -0,0 +1,83 @@ +"""Register exactly six READ tools on an injected MCP-compatible registrar.""" +from __future__ import annotations + +from dataclasses import asdict +import json + +from .fleet_hub import FleetHub +from .fleet_routing import RouteResult + + +_ANNOTATIONS = { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, +} + + +def _public_data(value: object) -> object: + """Copy JSON-only data and omit inert context even in nested gateway payloads.""" + if value is None or type(value) in (str, bool, int, float): + return value + if type(value) is list: + return [_public_data(item) for item in value] + if type(value) is dict and all(type(key) is str for key in value): + return {key: _public_data(item) for key, item in value.items() + if key != "context_text"} + raise ValueError("FLEET_RESPONSE_INVALID") + + +def _serializable(value: dict) -> dict: + # Reject nonfinite numbers as well as custom objects; no repr/str fallback. + return json.loads(json.dumps(_public_data(value), allow_nan=False)) + + +def register_fleet_read_tools(mcp, hub: FleetHub) -> None: + def read(node: str, capability: str, args: dict | None = None) -> dict: + try: + hub.refresh() + except Exception: + return {"ok": False, "error": "FLEET_REFRESH_FAILED"} + try: + result = hub.read(node, capability, args=args) + if type(result) is not RouteResult or result.ok is not True: + return {"ok": False, "error": "FLEET_READ_FAILED"} + return _serializable({"ok": True, "node_id": result.node_id, + "capability": result.capability, "data": result.data}) + except Exception: + return {"ok": False, "error": "FLEET_READ_FAILED"} + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_nodes() -> dict: + """Refresh and list Fleet nodes with stable identities and readiness.""" + try: + hub.refresh() + return _serializable({"ok": True, "nodes": [asdict(node) for node in hub.nodes()]}) + except Exception: + return {"ok": False, "error": "FLEET_NODES_FAILED"} + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_node_system_status(node: str) -> dict: + """Read system status for a Fleet node ID or alias.""" + return read(node, "system.status") + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_node_hardware_summary(node: str) -> dict: + """Read hardware summary for a Fleet node ID or alias.""" + return read(node, "hardware.summary") + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_node_storage_inspect(node: str) -> dict: + """Inspect storage for a Fleet node ID or alias.""" + return read(node, "storage.inspect") + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_node_network_inspect(node: str) -> dict: + """Inspect networking for a Fleet node ID or alias.""" + return read(node, "network.inspect") + + @mcp.tool(annotations=dict(_ANNOTATIONS)) + def framebridge_fleet_node_service_status(node: str, service: str) -> dict: + """Read status of one validated service for a Fleet node ID or alias.""" + return read(node, "service.status", {"service": service}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py new file mode 100644 index 0000000..808c298 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py @@ -0,0 +1,77 @@ +"""In-memory Fleet registry. Identity is explicit; no discovery or transport I/O.""" +from __future__ import annotations + +from dataclasses import dataclass, replace +import re + + +_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}") + + +class RegistryError(ValueError): + """Stable error code only; never includes supplied metadata.""" + + +def _identifier(value: object) -> bool: + return type(value) is str and _IDENTIFIER.fullmatch(value) is not None + + +@dataclass(frozen=True) +class FleetNode: + node_id: str + alias: str + hostname: str + transport: str + capability_profile: str + online: bool = False + + def __post_init__(self) -> None: + if not _identifier(self.node_id) or not _identifier(self.alias): + raise RegistryError("FLEET_IDENTIFIER_INVALID") + for value in (self.hostname, self.transport, self.capability_profile): + if (type(value) is not str or not value or len(value) > 253 + or any(ord(char) < 32 or ord(char) == 127 for char in value)): + raise RegistryError("FLEET_METADATA_INVALID") + if type(self.online) is not bool: + raise RegistryError("FLEET_STATE_INVALID") + + +class FleetRegistry: + """Case-sensitive identifiers, sorted by node_id; offline unless specified. + + Aliases and IDs share one namespace to prevent ambiguous destination lookup. + Metadata cannot grant capabilities. Instances are for synchronous local use. + """ + + def __init__(self) -> None: + self._nodes: dict[str, FleetNode] = {} + self._aliases: dict[str, str] = {} + + def register(self, node: FleetNode) -> FleetNode: + if type(node) is not FleetNode: + raise RegistryError("FLEET_NODE_INVALID") + if node.node_id in self._nodes: + raise RegistryError("FLEET_DUPLICATE_NODE_ID") + if node.alias in self._aliases: + raise RegistryError("FLEET_DUPLICATE_ALIAS") + if node.node_id in self._aliases or node.alias in self._nodes: + raise RegistryError("FLEET_IDENTIFIER_COLLISION") + self._nodes[node.node_id] = node + self._aliases[node.alias] = node.node_id + return node + + def list_nodes(self) -> tuple[FleetNode, ...]: + return tuple(self._nodes[key] for key in sorted(self._nodes)) + + def resolve(self, target: str) -> FleetNode: + if not _identifier(target): + raise RegistryError("FLEET_IDENTIFIER_INVALID") + node_id = target if target in self._nodes else self._aliases.get(target) + if node_id is None: + raise RegistryError("FLEET_NODE_NOT_FOUND") + return self._nodes[node_id] + + def set_online(self, target: str, online: bool) -> FleetNode: + node = replace(self.resolve(target), online=online) + self._nodes[node.node_id] = node + return node diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py new file mode 100644 index 0000000..35e4528 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py @@ -0,0 +1,131 @@ +"""Typed READ dispatch only. The injected adapter is the sole execution boundary. + +No default adapter, shell, SSH, sockets, plan engine, or authority from text. +The embedding application must supply a trusted read-only adapter; this module +does not sandbox arbitrary Python callbacks or authenticate remote callers. +""" +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +import re +from types import MappingProxyType +from typing import Callable, Mapping + +from .fleet_registry import FleetNode, FleetRegistry, RegistryError + + +class Operation(Enum): + READ = "READ" + WRITE = "WRITE" + PLAN = "PLAN" + APPLY = "APPLY" + ROLLBACK = "ROLLBACK" + EXEC = "EXEC" + + +class ReadCapability(Enum): + SYSTEM_STATUS = "system.status" + HARDWARE_SUMMARY = "hardware.summary" + STORAGE_INSPECT = "storage.inspect" + NETWORK_INSPECT = "network.inspect" + SERVICE_STATUS = "service.status" + + +READ_ALLOWLIST = frozenset(item.value for item in ReadCapability) +_FORBIDDEN_EXEC = frozenset({"framebridge_exec", "shell", "bash", "exec"}) +_SERVICE_NAME = re.compile(r"[A-Za-z0-9_.@:-]{1,128}") + + +def normalize_read_args(capability: ReadCapability, args: object) -> dict[str, str]: + """Return a fresh, closed-schema dictionary; never interpret supplied text.""" + if type(capability) is not ReadCapability: + raise ValueError("FLEET_ARGS_INVALID") + if args is None: + args = {} + if type(args) not in (dict, MappingProxyType): + raise ValueError("FLEET_ARGS_INVALID") + if capability is ReadCapability.SERVICE_STATUS: + if (set(args) != {"service"} or type(args["service"]) is not str + or _SERVICE_NAME.fullmatch(args["service"]) is None): + raise ValueError("FLEET_ARGS_INVALID") + return {"service": args["service"]} + if args: + raise ValueError("FLEET_ARGS_INVALID") + return {} + + +@dataclass(frozen=True) +class FleetRequest: + target: str + capability: str + operation: Operation = Operation.READ + context_text: str = "" # Inert text; never forwarded to an adapter. + args: Mapping[str, str] | None = None + + def __post_init__(self) -> None: + # Snapshot caller dictionaries before dispatch, including read-only views. + # Invalid inputs remain available for the router's fixed error result. + if type(self.args) in (dict, MappingProxyType): + object.__setattr__(self, "args", MappingProxyType(dict(self.args))) + + +@dataclass(frozen=True) +class ReadRequest: + node_id: str + capability: ReadCapability + operation: Operation = Operation.READ + args: Mapping[str, str] | None = None + + def __post_init__(self) -> None: + object.__setattr__(self, "args", MappingProxyType( + normalize_read_args(self.capability, self.args))) + + +@dataclass(frozen=True) +class RouteResult: + ok: bool + error: str | None = None + node_id: str | None = None + capability: str | None = None + data: object = None + + +class FleetRouter: + def __init__(self, registry: FleetRegistry, + adapter: Callable[[FleetNode, ReadRequest], object]) -> None: + if not isinstance(registry, FleetRegistry) or not callable(adapter): + raise ValueError("FLEET_ROUTER_CONFIG_INVALID") + self._registry = registry + self._adapter = adapter + + def route(self, request: FleetRequest) -> RouteResult: + if type(request) is not FleetRequest: + return RouteResult(False, "FLEET_REQUEST_INVALID") + # Enum identity deliberately rejects strings and foreign enum values. + if request.operation is not Operation.READ: + return RouteResult(False, "FLEET_OPERATION_BLOCKED") + if type(request.capability) is not str or type(request.context_text) is not str: + return RouteResult(False, "FLEET_REQUEST_INVALID") + if request.capability.strip().casefold() in _FORBIDDEN_EXEC: + return RouteResult(False, "FLEET_EXEC_FORBIDDEN") + if request.capability not in READ_ALLOWLIST: + return RouteResult(False, "FLEET_CAPABILITY_NOT_ALLOWED") + try: + args = normalize_read_args(ReadCapability(request.capability), request.args) + except ValueError: + return RouteResult(False, "FLEET_ARGS_INVALID") + try: + node = self._registry.resolve(request.target) + except RegistryError as exc: + return RouteResult(False, str(exc)) + if not node.online: + return RouteResult(False, "FLEET_NODE_OFFLINE", node.node_id) + typed = ReadRequest(node.node_id, ReadCapability(request.capability), args=args) + try: + data = self._adapter(node, typed) + except Exception: + return RouteResult(False, "FLEET_ADAPTER_FAILED", node.node_id, + request.capability) + return RouteResult(True, node_id=node.node_id, + capability=request.capability, data=data) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py new file mode 100644 index 0000000..bcaf919 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py @@ -0,0 +1,1308 @@ +from __future__ import annotations + +import json +import os +import re +import stat +import subprocess +import time +import uuid +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + +from mcp.types import ToolAnnotations + +NODE_RE = re.compile(r"^[A-Za-z0-9._-]{1,64}$") +SERVICE_RE = re.compile(r"^[A-Za-z0-9@_.-]{1,128}$") +PLAN_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +SHA256_RE = re.compile(r"^[a-f0-9]{64}$") +REMOTE_APPROVAL_SOURCE = "REMOTE_HUMAN" +TOTP_APPROVAL_SOURCE = "REMOTE_HUMAN_TOTP" +REMOTE_CHALLENGE_RE = re.compile(r"^[a-f0-9]{32}$") +REMOTE_KEY_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +PRIVILEGED_CHANGE_CAPS = { + "package.install", + "package.remove", + "service.manage", + "system.hostname", + "system.timezone", + "package.refresh", + "package.upgrade", + "system.file.write_text", + "system.directory.ensure", + "system.file.mode", + "system.daemon_reload", +} + +READ_CAPS = { + "system.status", + "system.resources", + "system.processes", + "system.time", + "hardware.summary", + "kernel.modules", + "storage.inspect", + "storage.usage", + "storage.mounts", + "network.inspect", + "network.sockets", + "network.dns", + "package.status", + "package.search", + "package.upgradable", + "service.status", + "service.list", + "journal.service", + "user.list", + "group.list", + "filesystem.stat", + "filesystem.list", + "filesystem.hash", + "filesystem.read_text", +} + +READ_ANNOTATIONS = ToolAnnotations( + title="FrameBridge node read", + readOnlyHint=True, + destructiveHint=False, + idempotentHint=True, + openWorldHint=False, +) + +CHANGE_ANNOTATIONS = ToolAnnotations( + title="FrameBridge node approved plan apply", + readOnlyHint=False, + destructiveHint=False, + idempotentHint=False, + openWorldHint=False, +) + + +class NodeGatewayError(RuntimeError): + pass + + +def _approval_source_ok(capability: str | None, source: str) -> bool: + if capability == "package.install": + return source in ( + "LOCAL_TTY_PRIVILEGED", + REMOTE_APPROVAL_SOURCE, + TOTP_APPROVAL_SOURCE, + ) + if capability in PRIVILEGED_CHANGE_CAPS: + return source == "LOCAL_TTY_PRIVILEGED" + return source == "LOCAL_TTY" + + +def _registry_path() -> Path: + return Path( + os.environ.get( + "FRAMEBRIDGE_NODE_REGISTRY", + "~/.config/framebridge/nodes.json", + ) + ).expanduser() + + +def _account_binding_path() -> Path: + return Path( + os.environ.get( + "FRAMEBRIDGE_ACCOUNT_BINDING", + "~/.config/framebridge/account-binding.json", + ) + ).expanduser() + + +def _paired_discovery_path() -> Path: + return Path( + os.environ.get( + "FRAMEBRIDGE_PAIRED_DISCOVERY", + "~/.local/share/framebridge/pairing-broker/paired-nodes.json", + ) + ).expanduser() + + +def _load_secure_json_optional(path: Path, prefix: str) -> dict[str, Any] | None: + if not path.is_file(): + return None + mode=stat.S_IMODE(path.stat().st_mode) + if mode & 0o077: + raise NodeGatewayError(prefix+"_PERMISSIONS_TOO_OPEN") + try: + value=json.loads(path.read_text(encoding="utf-8")) + except Exception: + raise NodeGatewayError(prefix+"_INVALID_JSON") + if not isinstance(value,dict): + raise NodeGatewayError(prefix+"_NOT_OBJECT") + return value + + +def _load_account_binding() -> dict[str, Any] | None: + value=_load_secure_json_optional(_account_binding_path(),"ACCOUNT_BINDING") + if value is None: + return None + if value.get("schema")!=1: + raise NodeGatewayError("ACCOUNT_BINDING_SCHEMA_INVALID") + if value.get("provider")!="chatgpt": + raise NodeGatewayError("ACCOUNT_BINDING_PROVIDER_INVALID") + if value.get("issuer")!="https://auth.openai.com": + raise NodeGatewayError("ACCOUNT_BINDING_ISSUER_INVALID") + binding=str(value.get("account_binding_id") or "") + client=str(value.get("client_id") or "") + if not binding.startswith("fbacct_"): + raise NodeGatewayError("ACCOUNT_BINDING_ID_INVALID") + if not client.startswith("oaiapp_"): + raise NodeGatewayError("ACCOUNT_BINDING_CLIENT_INVALID") + return value + + +def _load_paired_discovery() -> list[dict[str, Any]]: + account=_load_account_binding() + if account is None: + return [] + value=_load_secure_json_optional(_paired_discovery_path(),"PAIRED_DISCOVERY") + if value is None: + return [] + if value.get("schema")!=1 or not isinstance(value.get("nodes"),list): + raise NodeGatewayError("PAIRED_DISCOVERY_SCHEMA_INVALID") + result=[] + for raw in value["nodes"]: + if not isinstance(raw,dict): + continue + if raw.get("schema")!=1: + continue + if raw.get("source")!="fbpair" or raw.get("state")!="paired": + continue + if raw.get("provider")!=account.get("provider"): + continue + if raw.get("account_binding_id")!=account.get("account_binding_id"): + continue + node_id=str(raw.get("node_id") or "") + label=str(raw.get("node_label") or "") + try: + parsed=uuid.UUID(node_id) + except Exception: + continue + if NODE_RE.fullmatch(label) is None: + continue + scopes=sorted({str(x) for x in (raw.get("scopes") or []) if str(x)}) + if "node.read" not in scopes: + continue + result.append({ + "alias":label, + "hostname":label, + "transport":"broker_outbound", + "target":parsed.hex[:8], + "capability_profile":str(raw.get("capability_profile") or "paired"), + "source":"fbpair", + "pairing_state":"paired", + "provider":"chatgpt", + "node_id":node_id, + "scopes":scopes, + "operational_transport_ready":False, + }) + return result + + +def _load_registry() -> dict[str, Any]: + path = _registry_path() + if not path.is_file(): + raise NodeGatewayError("NODE_REGISTRY_NOT_FOUND") + + mode = stat.S_IMODE(path.stat().st_mode) + if mode & 0o077: + raise NodeGatewayError("NODE_REGISTRY_PERMISSIONS_TOO_OPEN") + + try: + value = json.loads(path.read_text(encoding="utf-8")) + except Exception: + raise NodeGatewayError("NODE_REGISTRY_INVALID_JSON") + + if not isinstance(value, dict): + raise NodeGatewayError("NODE_REGISTRY_NOT_OBJECT") + nodes = value.get("nodes") + if not isinstance(nodes, dict): + raise NodeGatewayError("NODE_REGISTRY_NODES_INVALID") + return value + + +def _node(alias: str) -> dict[str, Any]: + alias = str(alias).strip() + if not NODE_RE.fullmatch(alias): + raise NodeGatewayError("NODE_ALIAS_INVALID") + + registry = _load_registry() + raw = registry["nodes"].get(alias) + if isinstance(raw, dict): + node = dict(raw) + node["alias"] = alias + node["source"] = "registry" + return node + + account=_load_account_binding() + discovery=_load_secure_json_optional( + _paired_discovery_path(), + "PAIRED_DISCOVERY", + ) + if account and discovery and isinstance(discovery.get("nodes"),list): + for item in discovery["nodes"]: + if not isinstance(item,dict): + continue + if item.get("schema")!=1: + continue + if item.get("source")!="fbpair" or item.get("state")!="paired": + continue + if item.get("provider")!=account.get("provider"): + continue + if item.get("account_binding_id")!=account.get("account_binding_id"): + continue + if str(item.get("node_label") or "")!=alias: + continue + node_id=str(item.get("node_id") or "") + try: + parsed=uuid.UUID(node_id) + except Exception: + continue + scopes={str(x) for x in (item.get("scopes") or [])} + if "node.read" not in scopes: + continue + base=str(item.get("broker_base_url") or "").rstrip("/") + if not base: + continue + return { + "alias":alias, + "hostname":alias, + "target":parsed.hex[:8], + "node_id":node_id, + "transport":"broker_outbound", + "broker_base_url":base, + "capability_profile":str(item.get("capability_profile") or "paired"), + "source":"fbpair", + "pairing_state":"paired", + "provider":"chatgpt", + "scopes":sorted(scopes), + } + + raise NodeGatewayError("NODE_NOT_FOUND") + + +def _validate_node(node: dict[str, Any]) -> None: + if node.get("transport") != "ssh": + raise NodeGatewayError("NODE_TRANSPORT_UNSUPPORTED") + + host = str(node.get("host") or "") + user = str(node.get("user") or "") + target = str(node.get("target") or "") + try: + port = int(node.get("port")) + except Exception: + raise NodeGatewayError("NODE_PORT_INVALID") + + if not host or any(ch.isspace() for ch in host): + raise NodeGatewayError("NODE_HOST_INVALID") + if not user or not re.fullmatch(r"[A-Za-z0-9._-]{1,64}", user): + raise NodeGatewayError("NODE_USER_INVALID") + if not target or not re.fullmatch(r"[A-Za-z0-9._-]{1,80}", target): + raise NodeGatewayError("NODE_TARGET_INVALID") + if not (1 <= port <= 65535): + raise NodeGatewayError("NODE_PORT_INVALID") + + key = Path(str(node.get("identity_file") or "")).expanduser() + known = Path(str(node.get("known_hosts_file") or "")).expanduser() + + if not key.is_file(): + raise NodeGatewayError("NODE_IDENTITY_FILE_MISSING") + if stat.S_IMODE(key.stat().st_mode) & 0o077: + raise NodeGatewayError("NODE_IDENTITY_FILE_PERMISSIONS_TOO_OPEN") + if not known.is_file(): + raise NodeGatewayError("NODE_KNOWN_HOSTS_MISSING") + + +def _ssh_argv(node: dict[str, Any], remote_command: str) -> list[str]: + _validate_node(node) + return [ + "ssh", + "-p", + str(int(node["port"])), + "-oBatchMode=yes", + "-oIdentitiesOnly=yes", + "-oStrictHostKeyChecking=yes", + "-oUserKnownHostsFile=" + str(Path(str(node["known_hosts_file"])).expanduser()), + "-oConnectTimeout=5", + "-i", + str(Path(str(node["identity_file"])).expanduser()), + str(node["user"]) + "@" + str(node["host"]), + remote_command, + ] + + +def _run_ssh( + node: dict[str, Any], + remote_command: str, + input_text: str = "", + timeout: int = 20, +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + _ssh_argv(node, remote_command), + input=input_text, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=False, + shell=False, + ) + + +def _sanitize_text(value: str, limit: int = 4096) -> str: + text = "".join( + ch for ch in str(value) + if ch in "\n\r\t" or ord(ch) >= 32 + ) + return text[:limit] + + +def node_status(alias: str) -> dict[str, Any]: + node = _node(alias) + if node.get("transport")=="broker_outbound": + live=_broker_node_read(node,"system.status",{}) + return { + "ok":bool(live.get("ok")), + "node":alias, + "hostname":node.get("hostname"), + "transport":"broker_outbound", + "pairing_state":"paired", + "provider":node.get("provider"), + "status_source":"system.status", + "status":live.get("result") if isinstance(live.get("result"),dict) else live, + } + + proc = _run_ssh( + node, + "export PATH=$HOME/.local/bin:$PATH; framebridge status", + timeout=15, + ) + if proc.returncode != 0: + return { + "ok": False, + "node": alias, + "error": "NODE_SSH_OR_STATUS_FAILED", + "ssh_rc": proc.returncode, + "stderr": _sanitize_text(proc.stderr, 1024), + } + try: + status = json.loads(proc.stdout) + except Exception: + return { + "ok": False, + "node": alias, + "error": "NODE_STATUS_INVALID_JSON", + } + return { + "ok": bool(status.get("ok")), + "node": alias, + "hostname": node.get("hostname"), + "transport": "ssh-gateway", + "status": status, + } + + +def _parse_classic_result(raw: str) -> dict[str, Any]: + lines = raw.replace("\r\n", "\n").replace("\r", "\n").splitlines() + try: + start = lines.index("FRAMERESULT/1") + end = lines.index("FRAMEEND/1", start + 1) + except ValueError: + raise NodeGatewayError("NODE_CLASSIC_RESULT_FRAME_INVALID") + + headers: dict[str, str] = {} + body_start = None + for i, line in enumerate(lines[start + 1:end], start + 1): + if not line.strip(): + body_start = i + 1 + break + if "=" in line: + key, value = line.split("=", 1) + headers[key.strip()] = value.strip() + + if body_start is None: + raise NodeGatewayError("NODE_CLASSIC_RESULT_BODY_MISSING") + + body_text = "\n".join(lines[body_start:end]).strip() + try: + body = json.loads(body_text) + except Exception: + raise NodeGatewayError("NODE_CLASSIC_RESULT_JSON_INVALID") + + exit_code = int(headers.get("EXIT", "1")) + return { + "ok": exit_code == 0, + "id": headers.get("ID"), + "exit": exit_code, + "channel": headers.get("CHANNEL"), + "capability": headers.get("CAPABILITY"), + "body_sha256": headers.get("BODY_SHA256"), + "duration_ms": headers.get("DURATION_MS"), + "result": body, + } + + +def _broker_post(base: str, path: str, value: dict[str, Any], timeout: float=5.0) -> dict[str, Any]: + url=str(base).rstrip("/")+"/"+str(path).lstrip("/") + data=json.dumps(value,sort_keys=True,separators=(",",":")).encode("utf-8") + req=urllib.request.Request( + url, + data=data, + headers={ + "Content-Type":"application/json", + "Accept":"application/json", + "User-Agent":"FrameBridge-Gateway/1", + }, + method="POST", + ) + try: + with urllib.request.urlopen(req,timeout=timeout) as resp: + raw=resp.read(1024*1024) + result=json.loads(raw.decode("utf-8")) + except Exception as exc: + raise NodeGatewayError("BROKER_OUTBOUND_UNREACHABLE") from exc + if not isinstance(result,dict): + raise NodeGatewayError("BROKER_OUTBOUND_INVALID_RESPONSE") + return result + + +def _broker_node_ready(node: dict[str, Any]) -> bool: + try: + base=str(node.get("broker_base_url") or "") + node_id=str(node.get("node_id") or "") + if not base or not node_id: + return False + value=_broker_post( + base, + "/fbpair/v1/control/node-status", + {"node_id":node_id}, + timeout=2.0, + ) + return value.get("ok") is True and value.get("ready") is True + except Exception: + return False + + +def _broker_node_read(node: dict[str, Any], capability: str, args: dict[str, Any]) -> dict[str, Any]: + base=str(node.get("broker_base_url") or "") + node_id=str(node.get("node_id") or "") + if not base or not node_id: + raise NodeGatewayError("BROKER_OUTBOUND_NODE_INVALID") + + created=_broker_post( + base, + "/fbpair/v1/control/request", + { + "node_id":node_id, + "capability":capability, + "args":args, + }, + ) + if created.get("ok") is not True: + raise NodeGatewayError("BROKER_OUTBOUND_REQUEST_REJECTED") + request_id=str(created.get("request_id") or "") + if not request_id: + raise NodeGatewayError("BROKER_OUTBOUND_REQUEST_ID_MISSING") + + final=None + for _ in range(32): + time.sleep(0.25) + value=_broker_post( + base, + "/fbpair/v1/control/result", + {"request_id":request_id}, + ) + state=value.get("state") + if state=="complete": + final=value + break + if state=="expired": + raise NodeGatewayError("BROKER_OUTBOUND_REQUEST_EXPIRED") + if final is None: + raise NodeGatewayError("BROKER_OUTBOUND_RESULT_TIMEOUT") + + payload=final.get("result") + if not isinstance(payload,dict): + raise NodeGatewayError("BROKER_OUTBOUND_RESULT_INVALID") + result=dict(payload) + result["node"]=node.get("alias") + result["hostname"]=node.get("hostname") + result["transport"]="broker_outbound" + result["request_id"]=request_id + return result + + +def node_read( + alias: str, + capability: str, + args: dict[str, Any] | None = None, +) -> dict[str, Any]: + capability = str(capability).strip() + if capability not in READ_CAPS: + raise NodeGatewayError("NODE_CAPABILITY_NOT_ALLOWED") + if args is None: + args = {} + if not isinstance(args, dict): + raise NodeGatewayError("NODE_ARGS_NOT_OBJECT") + + node = _node(alias) + if node.get("transport")=="broker_outbound": + return _broker_node_read(node,capability,args) + + frame_id = "mcp-node-" + uuid.uuid4().hex[:12] + body = { + "capability": capability, + "args": args, + } + frame = "\n".join([ + "FRAMEEXEC/1", + "ID=" + frame_id, + "TARGET=" + str(node["target"]), + "MODE=EXEC", + "LANG=framebridge-cap-v1", + "AUTHORIZED=NO", + "TIMEOUT=30", + "", + json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=True), + "FRAMEEND/1", + "", + ]) + + proc = _run_ssh( + node, + "export PATH=$HOME/.local/bin:$PATH; framebridge classic --file -", + input_text=frame, + timeout=20, + ) + if proc.returncode != 0: + return { + "ok": False, + "node": alias, + "capability": capability, + "error": "NODE_SSH_OR_CLASSIC_FAILED", + "ssh_rc": proc.returncode, + "stderr": _sanitize_text(proc.stderr, 1024), + } + + result = _parse_classic_result(proc.stdout) + result["node"] = alias + result["hostname"] = node.get("hostname") + result["transport"] = "ssh-gateway" + return result + + + +def node_capabilities(alias: str) -> dict[str, Any]: + node = _node(alias) + proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" capabilities', + timeout=15, + ) + value = _json_result(proc, "NODE_CAPABILITIES_FAILED") + raw = value.get("capabilities") + if not isinstance(raw, list): + raise NodeGatewayError("NODE_CAPABILITIES_INVALID") + items = [] + for item in raw: + if not isinstance(item, dict): + continue + name = str(item.get("name") or "") + risk = str(item.get("risk") or "") + if name in READ_CAPS and risk == "READONLY": + items.append(item) + return { + "ok": True, + "node": alias, + "hostname": node.get("hostname"), + "transport": "ssh-gateway", + "capabilities": items, + "count": len(items), + } + + +def _validate_plan_id(plan_id: str) -> str: + value = str(plan_id).strip() + if not PLAN_ID_RE.fullmatch(value): + raise NodeGatewayError("PLAN_ID_INVALID") + return value + + +def _validate_sha256(value: str) -> str: + value = str(value).strip().lower() + if not SHA256_RE.fullmatch(value): + raise NodeGatewayError("PLAN_SHA256_INVALID") + return value + + +def _json_result(proc: subprocess.CompletedProcess[str], error: str) -> dict[str, Any]: + if proc.returncode != 0: + raise NodeGatewayError(error + "_RC_" + str(proc.returncode)) + try: + value = json.loads(proc.stdout) + except Exception: + raise NodeGatewayError(error + "_INVALID_JSON") + if not isinstance(value, dict): + raise NodeGatewayError(error + "_NOT_OBJECT") + return value + + +def _plan_show_raw(node: dict[str, Any], plan_id: str) -> dict[str, Any]: + plan_id = _validate_plan_id(plan_id) + proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" plan show ' + plan_id, + timeout=15, + ) + return _json_result(proc, "NODE_PLAN_SHOW_FAILED") + + +def _broker_plan_operation( + node: dict[str, Any], + operation: str, + payload: dict[str, Any], +) -> dict[str, Any]: + base=str(node.get("broker_base_url") or "") + node_id=str(node.get("node_id") or "") + if not base or not node_id: + raise NodeGatewayError("BROKER_OUTBOUND_NODE_INVALID") + created=_broker_post( + base, + "/fbpair/v1/control/operation", + { + "node_id":node_id, + "operation":operation, + "payload":payload, + }, + ) + if created.get("ok") is not True: + raise NodeGatewayError(str(created.get("error") or "BROKER_PLAN_REQUEST_REJECTED")) + request_id=str(created.get("request_id") or "") + if not request_id: + raise NodeGatewayError("BROKER_PLAN_REQUEST_ID_MISSING") + + final=None + for _ in range(64): + time.sleep(0.25) + value=_broker_post( + base, + "/fbpair/v1/control/result", + {"request_id":request_id}, + ) + state=value.get("state") + if state=="complete": + final=value + break + if state=="expired": + raise NodeGatewayError("BROKER_PLAN_REQUEST_EXPIRED") + if final is None: + raise NodeGatewayError("BROKER_PLAN_RESULT_TIMEOUT") + + envelope=final.get("result") + if not isinstance(envelope,dict): + raise NodeGatewayError("BROKER_PLAN_RESULT_INVALID") + if envelope.get("ok") is not True: + raise NodeGatewayError(str(envelope.get("error") or "BROKER_PLAN_OPERATION_FAILED")) + result=envelope.get("result") + if not isinstance(result,dict): + raise NodeGatewayError("BROKER_PLAN_PAYLOAD_INVALID") + return result + + +def node_plan_status(alias: str, plan_id: str) -> dict[str, Any]: + node = _node(alias) + plan_id = _validate_plan_id(plan_id) + if node.get("transport")=="broker_outbound": + raw=_broker_plan_operation( + node, + "plan.status", + {"plan_id":plan_id}, + ) + else: + raw = _plan_show_raw(node, plan_id) + approval = raw.get("approval") or {} + plan = raw.get("plan") or {} + steps = [] + for step in plan.get("steps") or []: + args = step.get("args") or {} + item = { + "id": step.get("id"), + "capability": step.get("capability"), + "risk": step.get("risk"), + } + if "path" in args: + item["path"] = args.get("path") + if "package" in args: + item["package"] = args.get("package") + steps.append(item) + rollback = raw.get("rollback") or {} + rollback_approval = raw.get("rollback_approval") or {} + return { + "ok": raw.get("ok") is True, + "node": alias, + "hostname": node.get("hostname"), + "plan_id": raw.get("plan_id"), + "plan_sha256": raw.get("plan_sha256"), + "state": raw.get("state"), + "title": plan.get("title"), + "steps": steps, + "approval": { + "source": approval.get("source"), + "one_shot": approval.get("one_shot"), + "consumed": approval.get("consumed"), + "plan_sha256": approval.get("plan_sha256"), + "auth_method": approval.get("auth_method"), + "key_id": approval.get("key_id"), + "challenge_id": approval.get("challenge_id"), + "totp_step": approval.get("totp_step"), + } if approval else None, + "apply_present": raw.get("apply") is not None, + "rollback_available": rollback.get("available") is True, + "rollback_verified": rollback.get("verified") is True, + "rollback_approval": { + "source": rollback_approval.get("source"), + "one_shot": rollback_approval.get("one_shot"), + "consumed": rollback_approval.get("consumed"), + "plan_sha256": rollback_approval.get("plan_sha256"), + "auth_method": rollback_approval.get("auth_method"), + "key_id": rollback_approval.get("key_id"), + "challenge_id": rollback_approval.get("challenge_id"), + "totp_step": rollback_approval.get("totp_step"), + } if rollback_approval else None, + "transport": "broker_outbound" if node.get("transport")=="broker_outbound" else "ssh-gateway", + } + + +def node_plan_import(alias: str, plan: dict[str, Any]) -> dict[str, Any]: + if not isinstance(plan, dict): + raise NodeGatewayError("PLAN_NOT_OBJECT") + plan_id = _validate_plan_id(str(plan.get("plan_id") or "")) + body = json.dumps(plan, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + if len(body.encode("utf-8")) > 32768: + raise NodeGatewayError("PLAN_BODY_TOO_LARGE") + node = _node(alias) + if node.get("transport")=="broker_outbound": + raw=_broker_plan_operation( + node, + "plan.import", + {"plan":plan}, + ) + if raw.get("ok") is not True: + return { + "ok":False, + "node":alias, + "plan_id":plan_id, + "error":str(raw.get("error") or "NODE_PLAN_IMPORT_FAILED"), + } + return node_plan_status(alias,plan_id) + + proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" plan import-stdin', + input_text=body + "\n", + timeout=20, + ) + if proc.returncode != 0: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_IMPORT_FAILED", + "ssh_rc": proc.returncode, + "stderr": _sanitize_text(proc.stderr, 1024), + } + try: + raw = json.loads(proc.stdout) + except Exception: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_IMPORT_INVALID_JSON", + } + if not isinstance(raw, dict) or raw.get("ok") is not True: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": str(raw.get("error") if isinstance(raw, dict) else "NODE_PLAN_IMPORT_FAILED"), + } + return node_plan_status(alias, plan_id) + + +def node_plan_apply(alias: str, plan_id: str, expected_sha256: str) -> dict[str, Any]: + plan_id = _validate_plan_id(plan_id) + expected_sha256 = _validate_sha256(expected_sha256) + node = _node(alias) + if node.get("transport")=="broker_outbound": + _broker_plan_operation( + node, + "plan.apply", + { + "plan_id":plan_id, + "plan_sha256":expected_sha256, + }, + ) + return node_plan_status(alias,plan_id) + + pre = node_plan_status(alias, plan_id) + approval = pre.get("approval") or {} + pre_steps = pre.get("steps") or [] + capability = pre_steps[0].get("capability") if len(pre_steps) == 1 else None + source = approval.get("source") + source_ok = _approval_source_ok(capability, source) + remote_meta_ok = True + if source == REMOTE_APPROVAL_SOURCE: + remote_meta_ok = (approval.get("auth_method") == "ecdsa-p256" and REMOTE_KEY_ID_RE.fullmatch(str(approval.get("key_id") or "")) is not None and REMOTE_CHALLENGE_RE.fullmatch(str(approval.get("challenge_id") or "")) is not None) + elif source == TOTP_APPROVAL_SOURCE: + remote_meta_ok = (approval.get("auth_method") == "totp-sha1-6-30" and REMOTE_KEY_ID_RE.fullmatch(str(approval.get("key_id") or "")) is not None and REMOTE_CHALLENGE_RE.fullmatch(str(approval.get("challenge_id") or "")) is not None and type(approval.get("totp_step")) is int) + checks = { + "state_approved": pre.get("state") == "APPROVED", + "approval_source_expected": source_ok, + "approval_remote_metadata_valid": remote_meta_ok, + "approval_one_shot": approval.get("one_shot") is True, + "approval_not_consumed": approval.get("consumed") is False, + "plan_sha256_match": pre.get("plan_sha256") == expected_sha256, + "approval_sha256_match": approval.get("plan_sha256") == expected_sha256, + "not_already_applied": pre.get("apply_present") is False, + } + if not all(checks.values()): + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "PLAN_PRECONDITION_FAILED", + "preconditions": checks, + "state": pre.get("state"), + } + + proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" plan apply ' + plan_id, + timeout=30, + ) + if proc.returncode != 0: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_APPLY_FAILED", + "ssh_rc": proc.returncode, + "stderr": _sanitize_text(proc.stderr, 1024), + } + try: + apply_raw = json.loads(proc.stdout) + except Exception: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_APPLY_INVALID_JSON", + } + + post_raw = _plan_show_raw(node, plan_id) + post_approval = post_raw.get("approval") or {} + apply_meta = post_raw.get("apply") or {} + rollback = post_raw.get("rollback") or {} + applied_steps = apply_meta.get("steps") or [] + step_summaries = [] + all_verified = bool(applied_steps) + for step in applied_steps: + verified = step.get("verified") is True + all_verified = all_verified and verified + item = { + "step_id": step.get("step_id"), + "capability": step.get("capability"), + "verified": verified, + } + for key in ("target", "bytes", "sha256_after", "package", "version"): + if step.get(key) is not None: + item[key] = step.get(key) + step_summaries.append(item) + + audit_proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" audit', + timeout=15, + ) + try: + audit = _json_result(audit_proc, "NODE_AUDIT_FAILED") + except Exception as exc: + audit = {"ok": False, "error": str(exc)} + + postchecks = { + "state_applied": post_raw.get("state") == "APPLIED", + "approval_consumed": post_approval.get("consumed") is True, + "plan_sha256_match": post_raw.get("plan_sha256") == expected_sha256, + "steps_verified": all_verified, + "rollback_available": rollback.get("available") is True, + "audit_chain_ok": audit.get("ok") is True, + } + ok = all(postchecks.values()) + return { + "ok": ok, + "node": alias, + "hostname": node.get("hostname"), + "plan_id": plan_id, + "plan_sha256": expected_sha256, + "state": post_raw.get("state"), + "approval_consumed": post_approval.get("consumed"), + "preconditions": checks, + "postconditions": postchecks, + "steps": step_summaries, + "rollback_available": rollback.get("available") is True, + "audit": audit, + "transport": "ssh-gateway", + "error": None if ok else "PLAN_POSTCONDITION_FAILED", + } + + +def node_plan_rollback(alias: str, plan_id: str, expected_sha256: str) -> dict[str, Any]: + plan_id = _validate_plan_id(plan_id) + expected_sha256 = _validate_sha256(expected_sha256) + node = _node(alias) + if node.get("transport")=="broker_outbound": + _broker_plan_operation( + node, + "plan.rollback", + { + "plan_id":plan_id, + "plan_sha256":expected_sha256, + }, + ) + return node_plan_status(alias,plan_id) + + pre = node_plan_status(alias, plan_id) + rb_approval = pre.get("rollback_approval") or {} + pre_steps = pre.get("steps") or [] + capability = pre_steps[0].get("capability") if len(pre_steps) == 1 else None + source = rb_approval.get("source") + source_ok = _approval_source_ok(capability, source) + remote_meta_ok = True + if source == REMOTE_APPROVAL_SOURCE: + remote_meta_ok = (rb_approval.get("auth_method") == "ecdsa-p256" and REMOTE_KEY_ID_RE.fullmatch(str(rb_approval.get("key_id") or "")) is not None and REMOTE_CHALLENGE_RE.fullmatch(str(rb_approval.get("challenge_id") or "")) is not None) + elif source == TOTP_APPROVAL_SOURCE: + remote_meta_ok = (rb_approval.get("auth_method") == "totp-sha1-6-30" and REMOTE_KEY_ID_RE.fullmatch(str(rb_approval.get("key_id") or "")) is not None and REMOTE_CHALLENGE_RE.fullmatch(str(rb_approval.get("challenge_id") or "")) is not None and type(rb_approval.get("totp_step")) is int) + checks = { + "state_applied": pre.get("state") == "APPLIED", + "rollback_available": pre.get("rollback_available") is True, + "rollback_approval_source_expected": source_ok, + "rollback_approval_remote_metadata_valid": remote_meta_ok, + "rollback_approval_one_shot": rb_approval.get("one_shot") is True, + "rollback_approval_not_consumed": rb_approval.get("consumed") is False, + "plan_sha256_match": pre.get("plan_sha256") == expected_sha256, + "rollback_approval_sha256_match": rb_approval.get("plan_sha256") == expected_sha256, + "apply_present": pre.get("apply_present") is True, + } + if not all(checks.values()): + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "ROLLBACK_PRECONDITION_FAILED", + "preconditions": checks, + "state": pre.get("state"), + } + + proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" plan rollback ' + plan_id, + timeout=30, + ) + if proc.returncode != 0: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_ROLLBACK_FAILED", + "ssh_rc": proc.returncode, + "stderr": _sanitize_text(proc.stderr, 1024), + } + + try: + json.loads(proc.stdout) + except Exception: + return { + "ok": False, + "node": alias, + "plan_id": plan_id, + "error": "NODE_PLAN_ROLLBACK_INVALID_JSON", + } + + post_raw = _plan_show_raw(node, plan_id) + post_rb = post_raw.get("rollback") or {} + post_approval = post_raw.get("rollback_approval") or {} + verification = post_rb.get("verification") or [] + all_verified = bool(verification) and all(x.get("verified") is True for x in verification) + + audit_proc = _run_ssh( + node, + 'exec "$HOME/.local/bin/framebridge" audit', + timeout=15, + ) + try: + audit = _json_result(audit_proc, "NODE_AUDIT_FAILED") + except Exception as exc: + audit = {"ok": False, "error": str(exc)} + + postchecks = { + "state_rolled_back": post_raw.get("state") == "ROLLED_BACK", + "rollback_approval_consumed": post_approval.get("consumed") is True, + "plan_sha256_match": post_raw.get("plan_sha256") == expected_sha256, + "rollback_unavailable": post_rb.get("available") is False, + "rollback_verified": post_rb.get("verified") is True and all_verified, + "audit_chain_ok": audit.get("ok") is True, + } + ok = all(postchecks.values()) + return { + "ok": ok, + "node": alias, + "hostname": node.get("hostname"), + "plan_id": plan_id, + "plan_sha256": expected_sha256, + "state": post_raw.get("state"), + "rollback_approval_consumed": post_approval.get("consumed"), + "preconditions": checks, + "postconditions": postchecks, + "rollback_available": post_rb.get("available") is True, + "rollback_verified": post_rb.get("verified") is True, + "verification": verification, + "audit": audit, + "transport": "ssh-gateway", + "error": None if ok else "ROLLBACK_POSTCONDITION_FAILED", + } + + +def discover_nodes() -> dict: + try: + registry = _load_registry() + items = [] + seen_targets=set() + seen_aliases=set() + for alias, raw in sorted(registry["nodes"].items()): + if not isinstance(raw, dict): + continue + target=str(raw.get("target") or "") + entry={ + "alias": alias, + "hostname": raw.get("hostname"), + "transport": raw.get("transport"), + "target": target, + "capability_profile": raw.get("capability_profile", "seed-readonly"), + "source":"registry", + } + raw_node_id=str(raw.get("node_id") or "") + if raw_node_id: + try: + entry["node_id"]=str(uuid.UUID(raw_node_id)) + except Exception: + pass + if raw.get("transport")=="broker_outbound": + candidate=dict(raw) + candidate["alias"]=alias + entry["operational_transport_ready"]=_broker_node_ready(candidate) + else: + entry["operational_transport_ready"]=True + items.append(entry) + seen_targets.add(target) + seen_aliases.add(alias) + + for item in _load_paired_discovery(): + if item["target"] in seen_targets: + continue + item=dict(item) + try: + resolved=_node(item["alias"]) + item["operational_transport_ready"]=_broker_node_ready(resolved) + except Exception: + item["operational_transport_ready"]=False + if item["alias"] in seen_aliases: + item["alias"]=item["alias"]+"-"+item["target"] + items.append(item) + seen_targets.add(item["target"]) + seen_aliases.add(item["alias"]) + + items.sort(key=lambda x:str(x.get("alias") or "")) + return { + "ok": True, + "nodes": items, + "sources":{ + "registry":sum(1 for x in items if x.get("source")=="registry"), + "fbpair":sum(1 for x in items if x.get("source")=="fbpair"), + }, + } + except Exception: + return {"ok": False, "error": "NODE_DISCOVERY_FAILED"} + + +def register_node_tools(mcp) -> None: + @mcp.tool( + description=( + "WRITE. Import one structured FrameBridge Seed plan into a configured node. " + "Importing a plan does not approve or apply it. Seed validates schema, " + "capability allowlist, arguments, and risk constraints." + ), + annotations=CHANGE_ANNOTATIONS, + ) + def framebridge_node_plan_import(node: str, plan: dict) -> dict: + try: + return node_plan_import(node, plan) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description=( + "CHANGE. Roll back exactly one APPLIED FrameBridge Seed plan after a new " + "LOCAL_TTY one-shot rollback approval. Requires exact plan SHA-256. The tool " + "cannot create rollback approval, run raw shell, or bypass Seed policy." + ), + annotations=CHANGE_ANNOTATIONS, + ) + def framebridge_node_plan_rollback(node: str, plan_id: str, plan_sha256: str) -> dict: + try: + return node_plan_rollback(node, plan_id, plan_sha256) + except Exception as exc: + return {"ok": False, "node": node, "plan_id": plan_id, "error": str(exc)} + + @mcp.tool( + description=( + "READ-ONLY. Read one existing FrameBridge Seed plan status from a node. " + "Does not import, approve, apply, or modify the plan." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_plan_status(node: str, plan_id: str) -> dict: + try: + return node_plan_status(node, plan_id) + except Exception as exc: + return {"ok": False, "node": node, "plan_id": plan_id, "error": str(exc)} + + @mcp.tool( + description=( + "CHANGE. Apply exactly one existing FrameBridge Seed plan that was already " + "approved locally in the node TTY. Requires exact plan SHA-256. The tool " + "cannot create plans, approve plans, run raw shell, or bypass Seed policy." + ), + annotations=CHANGE_ANNOTATIONS, + ) + def framebridge_node_plan_apply(node: str, plan_id: str, plan_sha256: str) -> dict: + try: + return node_plan_apply(node, plan_id, plan_sha256) + except Exception as exc: + return {"ok": False, "node": node, "plan_id": plan_id, "error": str(exc)} + + @mcp.tool( + description=( + "READ-ONLY. List FrameBridge nodes available to this account, " + "including configured registry nodes and account-authorized FBPAIR nodes. " + "Does not execute commands on the nodes." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_nodes() -> dict: + return discover_nodes() + + @mcp.tool( + description=( + "READ-ONLY. List typed READONLY capabilities advertised by one " + "FrameBridge Seed node after Gateway allowlist filtering." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_capabilities(node: str) -> dict: + try: + return node_capabilities(node) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description=( + "READ-ONLY. Execute one allowlisted typed READONLY capability on a " + "FrameBridge Seed node. No raw shell and no CHANGE capability is accepted." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_read(node: str, capability: str, args: dict | None = None) -> dict: + try: + return node_read(node, capability, args or {}) + except Exception as exc: + return { + "ok": False, + "node": node, + "capability": capability, + "error": str(exc), + } + + @mcp.tool( + description=( + "READ-ONLY. Read FrameBridge Seed status from a configured node. " + "No system changes are performed." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_status(node: str) -> dict: + try: + return node_status(node) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description="READ-ONLY. Read OS/kernel/uptime status from a FrameBridge Seed node.", + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_system_status(node: str) -> dict: + try: + return node_read(node, "system.status", {}) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description="READ-ONLY. Read CPU and memory summary from a FrameBridge Seed node.", + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_hardware_summary(node: str) -> dict: + try: + return node_read(node, "hardware.summary", {}) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description="READ-ONLY. Inspect block devices and filesystems on a FrameBridge Seed node.", + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_storage_inspect(node: str) -> dict: + try: + return node_read(node, "storage.inspect", {}) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description="READ-ONLY. Inspect addresses and routes on a FrameBridge Seed node.", + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_network_inspect(node: str) -> dict: + try: + return node_read(node, "network.inspect", {}) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} + + @mcp.tool( + description=( + "READ-ONLY. Read active/enabled state of one named systemd service " + "on a FrameBridge Seed node." + ), + annotations=READ_ANNOTATIONS, + ) + def framebridge_node_service_status(node: str, service: str) -> dict: + try: + if not SERVICE_RE.fullmatch(str(service)): + raise NodeGatewayError("SERVICE_NAME_INVALID") + return node_read(node, "service.status", {"service": str(service)}) + except Exception as exc: + return {"ok": False, "node": node, "error": str(exc)} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so new file mode 100644 index 0000000..3931a99 Binary files /dev/null and b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so differ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA new file mode 100644 index 0000000..eb5a088 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA @@ -0,0 +1,295 @@ +Metadata-Version: 2.4 +Name: annotated-types +Version: 0.8.0 +Summary: Reusable constraint types to use with typing.Annotated +Project-URL: Homepage, https://github.com/annotated-types/annotated-types +Project-URL: Source, https://github.com/annotated-types/annotated-types +Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases +Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin , Zac Hatfield-Dodds +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Requires-Python: >=3.10 +Description-Content-Type: text/markdown + +# annotated-types + +[![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types) +[![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types) +[![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE) + +[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of +adding context-specific metadata to existing types, and specifies that +`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special +logic for `x`. + +This package provides metadata objects which can be used to represent common +constraints such as upper and lower bounds on scalar values and collection sizes, +a `Predicate` marker for runtime checks, and +descriptions of how we intend these metadata to be interpreted. In some cases, +we also note alternative representations which do not require this package. + +## Install + +```bash +pip install annotated-types +``` + +## Examples + +```python +from typing import Annotated +from annotated_types import Gt, Len, Predicate + +class MyClass: + age: Annotated[int, Gt(18)] # Valid: 19, 20, ... + # Invalid: 17, 18, "19", 19.0, ... + factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ... + # Invalid: 4, 8, -2, 5.0, "prime", ... + + my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50] + # Invalid: (1, 2), ["abc"], [0] * 20 +``` + +## Documentation + +_While `annotated-types` avoids runtime checks for performance, users should not +construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`. +Downstream implementors may choose to raise an error, emit a warning, silently ignore +a metadata item, etc., if the metadata objects described below are used with an +incompatible type - or for any other reason!_ + +### Gt, Ge, Lt, Le + +Express inclusive and/or exclusive bounds on orderable values - which may be numbers, +dates, times, strings, sets, etc. Note that the boundary value need not be of the +same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]` +is fine, for example, and implies that the value is an integer x such that `x > 1.5`. + +We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)` +as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on +the `annotated-types` package. + +To be explicit, these types have the following meanings: + +* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum +* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum +* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum +* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum + +### Interval + +`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single +metadata object. `None` attributes should be ignored, and non-`None` attributes +treated as per the single bounds above. + +### MultipleOf + +`MultipleOf(multiple_of=x)` might be interpreted in two ways: + +1. Python semantics, implying `value % multiple_of == 0`, or +2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1), + where `int(value / multiple_of) == value / multiple_of`. + +We encourage users to be aware of these two common interpretations and their +distinct behaviours, especially since very large or non-integer numbers make +it easy to cause silent data corruption due to floating-point imprecision. + +We encourage libraries to carefully document which interpretation they implement. + +### MinLen, MaxLen, Len + +`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive. + +As well as `Len()` which can optionally include upper and lower bounds, we also +provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)` +and `Len(max_length=y)` respectively. + +`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`. + +Examples of usage: + +* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10)]`) - list must have a length of 10 or less +* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less +* `Annotated[list, MinLen(3)]` (or `Annotated[list, Len(min_length=3)]`) - list must have a length of 3 or more +* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6 +* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8 + +#### Changed in v0.4.0 + +* `min_inclusive` has been renamed to `min_length`, no change in meaning +* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive** +* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic + meaning of the upper bound in slices vs. `Len` + +See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion. + +### Timezone + +`Timezone` can be used with a `datetime` or a `time` to express which timezones +are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime. +`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis)) +expresses that any timezone-aware datetime is allowed. You may also pass a specific +timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) +object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only +allow a specific timezone, though we note that this is often a symptom of fragile design. + +#### Changed in v0.x.x + +* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of + `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries. + +### Unit + +`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of +a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]` +would be a float representing a velocity in meters per second. + +Please note that `annotated_types` itself makes no attempt to parse or validate +the unit string in any way. That is left entirely to downstream libraries, +such as [`pint`](https://pint.readthedocs.io) or +[`astropy.units`](https://docs.astropy.org/en/stable/units/). + +An example of how a library might use this metadata: + +```python +from annotated_types import Unit +from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args + +# given a type annotated with a unit: +Meters = Annotated[float, Unit("m")] + + +# you can cast the annotation to a specific unit type with any +# callable that accepts a string and returns the desired type +T = TypeVar("T") +def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None: + if get_origin(tp) is Annotated: + for arg in get_args(tp): + if isinstance(arg, Unit): + return unit_cls(arg.unit) + return None + + +# using `pint` +import pint +pint_unit = cast_unit(Meters, pint.Unit) + + +# using `astropy.units` +import astropy.units as u +astropy_unit = cast_unit(Meters, u.Unit) +``` + +### Predicate + +`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values. +Users should prefer the statically inspectable metadata above, but if you need +the full power and flexibility of arbitrary runtime predicates... here it is. + +For some common constraints, we provide generic types: + +* `LowerCase = Annotated[T, Predicate(str.islower)]` +* `UpperCase = Annotated[T, Predicate(str.isupper)]` +* `IsDigit = Annotated[T, Predicate(str.isdigit)]` +* `IsFinite = Annotated[T, Predicate(math.isfinite)]` +* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]` +* `IsNan = Annotated[T, Predicate(math.isnan)]` +* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]` +* `IsInfinite = Annotated[T, Predicate(math.isinf)]` +* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]` + +so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer +(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`. + +Some libraries might have special logic to handle known or understandable predicates, +for example by checking for `str.isdigit` and using its presence to both call custom +logic to enforce digit-only strings, and customise some generated external schema. +Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in +favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`. + +To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner. + +We do not specify what behaviour should be expected for predicates that raise +an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently +skip invalid constraints, or statically raise an error; or it might try calling it +and then propagate or discard the resulting +`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object` +exception. We encourage libraries to document the behaviour they choose. + +### Doc + +`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used. + +It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools. + +It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`. + +This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md). + +### Integrating downstream types with `GroupedMetadata` + +Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata. +This can help reduce verbosity and cognitive overhead for users. +For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata: + +```python +from dataclasses import dataclass +from typing import Iterator +from annotated_types import GroupedMetadata, Ge + +@dataclass +class Field(GroupedMetadata): + ge: int | None = None + description: str | None = None + + def __iter__(self) -> Iterator[object]: + # Iterating over a GroupedMetadata object should yield annotated-types + # constraint metadata objects which describe it as fully as possible, + # and may include other unknown objects too. + if self.ge is not None: + yield Ge(self.ge) + if self.description is not None: + yield Description(self.description) +``` + +Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently. + +Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself. + +Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`. + +### Consuming metadata + +We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103). + +It is up to the implementer to determine how this metadata is used. +You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases. + +## Design & History + +This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic +and Hypothesis, with the goal of making it as easy as possible for end-users to +provide more informative annotations for use by runtime libraries. + +It is deliberately minimal, and following PEP-593 allows considerable downstream +discretion in what (if anything!) they choose to support. Nonetheless, we expect +that staying simple and covering _only_ the most common use-cases will give users +and maintainers the best experience we can. If you'd like more constraints for your +types - follow our lead, by defining them and documenting them downstream! diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD new file mode 100644 index 0000000..646f789 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD @@ -0,0 +1,7 @@ +annotated_types/__init__.py,sha256=pxBKTUObJ6n3T8C-I2ubobeDHmBEAmgCogWrwSmKm8g,13273 +annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +annotated_types/test_cases.py,sha256=2GdHKstXuBzpf3iXKCjmvFZpyk2zP9Hm9kXGGDhGBo4,6310 +annotated_types-0.8.0.dist-info/METADATA,sha256=YUmFsnj2Abjvhj-CeDuZHMOtpLifWS63HHxIxfLvIpg,15009 +annotated_types-0.8.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87 +annotated_types-0.8.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083 +annotated_types-0.8.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL new file mode 100644 index 0000000..7401812 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.31.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..d99323a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 the contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py new file mode 100644 index 0000000..dcb35c5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py @@ -0,0 +1,416 @@ +import math +import types +from collections.abc import Callable, Iterator +from dataclasses import dataclass +from datetime import tzinfo +from types import EllipsisType +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Literal, + Protocol, + SupportsFloat, + SupportsIndex, + TypeVar, + Union, + runtime_checkable, +) + +__all__ = ( + 'BaseMetadata', + 'GroupedMetadata', + 'Gt', + 'Ge', + 'Lt', + 'Le', + 'Interval', + 'MultipleOf', + 'MinLen', + 'MaxLen', + 'Len', + 'Timezone', + 'Predicate', + 'LowerCase', + 'UpperCase', + 'IsDigits', + 'IsFinite', + 'IsNotFinite', + 'IsNan', + 'IsNotNan', + 'IsInfinite', + 'IsNotInfinite', + 'doc', + 'DocInfo', + '__version__', +) + +__version__ = '0.8.0' + + +T = TypeVar('T') + + +# arguments that start with __ are considered +# positional only +# see https://peps.python.org/pep-0484/#positional-only-arguments + + +class SupportsGt(Protocol): + def __gt__(self: T, __other: T) -> bool: + ... + + +class SupportsGe(Protocol): + def __ge__(self: T, __other: T) -> bool: + ... + + +class SupportsLt(Protocol): + def __lt__(self: T, __other: T) -> bool: + ... + + +class SupportsLe(Protocol): + def __le__(self: T, __other: T) -> bool: + ... + + +class SupportsMod(Protocol): + def __mod__(self: T, __other: T) -> T: + ... + + +class SupportsDiv(Protocol): + def __div__(self: T, __other: T) -> T: + ... + + +class BaseMetadata: + """Base class for all metadata. + + This exists mainly so that implementers + can do `isinstance(..., BaseMetadata)` while traversing field annotations. + """ + + __slots__ = () + + +@dataclass(frozen=True, slots=True) +class Gt(BaseMetadata): + """Gt(gt=x) implies that the value must be greater than x. + + It can be used with any type that supports the ``>`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + gt: SupportsGt + + +@dataclass(frozen=True, slots=True) +class Ge(BaseMetadata): + """Ge(ge=x) implies that the value must be greater than or equal to x. + + It can be used with any type that supports the ``>=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + ge: SupportsGe + + +@dataclass(frozen=True, slots=True) +class Lt(BaseMetadata): + """Lt(lt=x) implies that the value must be less than x. + + It can be used with any type that supports the ``<`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + lt: SupportsLt + + +@dataclass(frozen=True, slots=True) +class Le(BaseMetadata): + """Le(le=x) implies that the value must be less than or equal to x. + + It can be used with any type that supports the ``<=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + le: SupportsLe + + +@runtime_checkable +class GroupedMetadata(Protocol): + """A grouping of multiple objects, like typing.Unpack. + + `GroupedMetadata` on its own is not metadata and has no meaning. + All of the constraints and metadata should be fully expressable + in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`. + + Concrete implementations should override `GroupedMetadata.__iter__()` + to add their own metadata. + For example: + + >>> @dataclass + >>> class Field(GroupedMetadata): + >>> gt: float | None = None + >>> description: str | None = None + ... + >>> def __iter__(self) -> Iterable[object]: + >>> if self.gt is not None: + >>> yield Gt(self.gt) + >>> if self.description is not None: + >>> yield Description(self.gt) + + Also see the implementation of `Interval` below for an example. + + Parsers should recognize this and unpack it so that it can be used + both with and without unpacking: + + - `Annotated[int, Field(...)]` (parser must unpack Field) + - `Annotated[int, *Field(...)]` (PEP-646) + """ # noqa: trailing-whitespace + + @property + def __is_annotated_types_grouped_metadata__(self) -> Literal[True]: + return True + + def __iter__(self) -> Iterator[object]: + ... + + if not TYPE_CHECKING: + __slots__ = () # allow subclasses to use slots + + def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: + # Basic ABC like functionality without the complexity of an ABC + super().__init_subclass__(*args, **kwargs) + if cls.__iter__ is GroupedMetadata.__iter__: + raise TypeError("Can't subclass GroupedMetadata without implementing __iter__") + + def __iter__(self) -> Iterator[object]: # noqa: F811 + raise NotImplementedError # more helpful than "None has no attribute..." type errors + + +@dataclass(frozen=True, kw_only=True, slots=True) +class Interval(GroupedMetadata): + """Interval can express inclusive or exclusive bounds with a single object. + + It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which + are interpreted the same way as the single-bound constraints. + """ + + gt: SupportsGt | None = None + ge: SupportsGe | None = None + lt: SupportsLt | None = None + le: SupportsLe | None = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack an Interval into zero or more single-bounds.""" + if self.gt is not None: + yield Gt(self.gt) + if self.ge is not None: + yield Ge(self.ge) + if self.lt is not None: + yield Lt(self.lt) + if self.le is not None: + yield Le(self.le) + + +@dataclass(frozen=True, slots=True) +class MultipleOf(BaseMetadata): + """MultipleOf(multiple_of=x) might be interpreted in two ways: + + 1. Python semantics, implying ``value % multiple_of == 0``, or + 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of`` + + We encourage users to be aware of these two common interpretations, + and libraries to carefully document which they implement. + """ + + multiple_of: SupportsDiv | SupportsMod + + +@dataclass(frozen=True, slots=True) +class MinLen(BaseMetadata): + """ + MinLen() implies minimum inclusive length, + e.g. ``len(value) >= min_length``. + """ + + min_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, slots=True) +class MaxLen(BaseMetadata): + """ + MaxLen() implies maximum inclusive length, + e.g. ``len(value) <= max_length``. + """ + + max_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, slots=True) +class Len(GroupedMetadata): + """ + Len() implies that ``min_length <= len(value) <= max_length``. + + Upper bound may be omitted or ``None`` to indicate no upper length bound. + """ + + min_length: Annotated[int, Ge(0)] = 0 + max_length: Annotated[int, Ge(0)] | None = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack a Len into zero or more single-bounds.""" + if self.min_length > 0: + yield MinLen(self.min_length) + if self.max_length is not None: + yield MaxLen(self.max_length) + + +@dataclass(frozen=True, slots=True) +class Timezone(BaseMetadata): + """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive). + + ``Annotated[datetime, Timezone(None)]`` must be a naive datetime. + ``Timezone(...)`` (the ellipsis literal) expresses that the datetime must be + tz-aware but any timezone is allowed. + + You may also pass a specific timezone string or tzinfo object such as + ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that + you only allow a specific timezone, though we note that this is often + a symptom of poor design. + """ + + tz: str | tzinfo | EllipsisType | None + + +@dataclass(frozen=True, slots=True) +class Unit(BaseMetadata): + """Indicates that the value is a physical quantity with the specified unit. + + It is intended for usage with numeric types, where the value represents the + magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]`` + or ``speed: Annotated[float, Unit('m/s')]``. + + Interpretation of the unit string is left to the discretion of the consumer. + It is suggested to follow conventions established by python libraries that work + with physical quantities, such as + + - ``pint`` : + - ``astropy.units``: + + For indicating a quantity with a certain dimensionality but without a specific unit + it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`. + Note, however, ``annotated_types`` itself makes no use of the unit string. + """ + + unit: str + + +@dataclass(frozen=True, slots=True) +class Predicate(BaseMetadata): + """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values. + + Users should prefer statically inspectable metadata, but if you need the full + power and flexibility of arbitrary runtime predicates... here it is. + + We provide a few predefined predicates for common string constraints: + ``LowerCase = Predicate(str.islower)``, ``UpperCase = Predicate(str.isupper)``, and + ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which + can be given special handling, and avoid indirection like ``lambda s: s.lower()``. + + Some libraries might have special logic to handle certain predicates, e.g. by + checking for `str.isdigit` and using its presence to both call custom logic to + enforce digit-only strings, and customise some generated external schema. + + We do not specify what behaviour should be expected for predicates that raise + an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently + skip invalid constraints, or statically raise an error; or it might try calling it + and then propagate or discard the resulting exception. + """ + + func: Callable[[Any], bool] + + def __repr__(self) -> str: + if getattr(self.func, "__name__", "") == "": + return f"{self.__class__.__name__}({self.func!r})" + if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and ( + namespace := getattr(self.func.__self__, "__name__", None) + ): + return f"{self.__class__.__name__}({namespace}.{self.func.__name__})" + if isinstance(self.func, type(str.isascii)): # method descriptor + return f"{self.__class__.__name__}({self.func.__qualname__})" + return f"{self.__class__.__name__}({self.func.__name__})" + + +@dataclass +class Not: + func: Callable[[Any], bool] + + def __call__(self, __v: Any) -> bool: + return not self.func(__v) + + +_StrType = TypeVar("_StrType", bound=str) + +LowerCase = Annotated[_StrType, Predicate(str.islower)] +""" +Return True if the string is a lowercase string, False otherwise. + +A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string. +""" # noqa: E501 +UpperCase = Annotated[_StrType, Predicate(str.isupper)] +""" +Return True if the string is an uppercase string, False otherwise. + +A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string. +""" # noqa: E501 +IsDigit = Annotated[_StrType, Predicate(str.isdigit)] +IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63 +""" +Return True if the string is a digit string, False otherwise. + +A string is a digit string if all characters in the string are digits and there is at least one character in the string. +""" # noqa: E501 +IsAscii = Annotated[_StrType, Predicate(str.isascii)] +""" +Return True if all characters in the string are ASCII, False otherwise. + +ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too. +""" + +_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex]) +IsFinite = Annotated[_NumericType, Predicate(math.isfinite)] +"""Return True if x is neither an infinity nor a NaN, and False otherwise.""" +IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))] +"""Return True if x is one of infinity or NaN, and False otherwise""" +IsNan = Annotated[_NumericType, Predicate(math.isnan)] +"""Return True if x is a NaN (not a number), and False otherwise.""" +IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))] +"""Return True if x is anything but NaN (not a number), and False otherwise.""" +IsInfinite = Annotated[_NumericType, Predicate(math.isinf)] +"""Return True if x is a positive or negative infinity, and False otherwise.""" +IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))] +"""Return True if x is neither a positive or negative infinity, and False otherwise.""" + +try: + # PEP 727 – Documentation in Annotated Metadata + from typing_extensions import Doc # type: ignore[attr-defined] +except ImportError: + + @dataclass(frozen=True, slots=True) + class Doc: # type: ignore [no-redef] + """ " + The return value of doc(), mainly to be used by tools that want to extract the + Annotated documentation at runtime. + """ + + documentation: str + """The documentation string passed to doc().""" + + +DocInfo = Doc # backwards compatibility +doc = Doc diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py new file mode 100644 index 0000000..fba3c25 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py @@ -0,0 +1,146 @@ +import math +from collections.abc import Iterable, Iterator +from datetime import date, datetime, timedelta, timezone +from decimal import Decimal +from typing import Annotated, Any, NamedTuple + +import annotated_types as at + + +class Case(NamedTuple): + """ + A test case for `annotated_types`. + """ + + annotation: Any + valid_cases: Iterable[Any] + invalid_cases: Iterable[Any] + + +def cases() -> Iterable[Case]: + # Gt, Ge, Lt, Le + yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Gt(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(date(2000, 1, 1))], + [date(2000, 1, 2), date(2000, 1, 3)], + [date(2000, 1, 1), date(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(Decimal('1.123'))], + [Decimal('1.1231'), Decimal('123')], + [Decimal('1.123'), Decimal('0')], + ) + + yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1)) + yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Ge(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(1998, 1, 1), datetime(1999, 12, 31)], + ) + + yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4)) + yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Lt(datetime(2000, 1, 1))], + [datetime(1999, 12, 31), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000)) + yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Le(datetime(2000, 1, 1))], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + # Interval + yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1)) + yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1)) + yield Case( + Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(2000, 1, 4)], + ) + + yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4)) + yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1)) + + # lengths + + yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[list[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + yield Case(Annotated[list[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + + yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[list[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + yield Case(Annotated[list[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + + yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10)) + yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234')) + + yield Case(Annotated[dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}]) + yield Case(Annotated[set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4})) + yield Case(Annotated[tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4))) + + # Timezone + + yield Case( + Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)] + ) + yield Case( + Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)] + ) + yield Case( + Annotated[datetime, at.Timezone(timezone.utc)], + [datetime(2000, 1, 1, tzinfo=timezone.utc)], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + yield Case( + Annotated[datetime, at.Timezone('Europe/London')], + [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + + # Quantity + + yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m')) + + # predicate types + + yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom']) + yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC']) + yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2']) + yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀']) + + yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5]) + + yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf]) + yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23]) + yield Case(at.IsNan[float], [math.nan], [1.23, math.inf]) + yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan]) + yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23]) + yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf]) + + # check stacked predicates + yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan]) + + # doc + yield Case(Annotated[int, at.doc("A number")], [1, 2], []) + + # custom GroupedMetadata + class MyCustomGroupedMetadata(at.GroupedMetadata): + def __iter__(self) -> Iterator[at.Predicate]: + yield at.Predicate(lambda x: float(x).is_integer()) + + yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5]) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA new file mode 100644 index 0000000..13734ec --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA @@ -0,0 +1,108 @@ +Metadata-Version: 2.4 +Name: anyio +Version: 4.15.1 +Summary: High-level concurrency and networking framework on top of asyncio or Trio +Author-email: Alex Grönholm +License-Expression: MIT +Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ +Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html +Project-URL: Source code, https://github.com/agronholm/anyio +Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Framework :: AnyIO +Classifier: Typing :: Typed +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" +Requires-Dist: idna>=2.8 +Requires-Dist: typing_extensions>=4.16.0; python_version < "3.15" +Provides-Extra: trio +Requires-Dist: trio>=0.32.0; extra == "trio" +Dynamic: license-file + +.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/anyio/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/anyio?branch=master + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest + :target: https://anyio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation +.. image:: https://badges.gitter.im/gitterHQ/gitter.svg + :target: https://gitter.im/python-trio/AnyIO + :alt: Gitter chat +.. image:: https://tidelift.com/badges/package/pypi/anyio + :target: https://tidelift.com/subscription/pkg/pypi-anyio + :alt: Tidelift + +AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or +Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony +with the native SC of Trio itself. + +Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or +Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full +refactoring necessary. It will blend in with the native libraries of your chosen backend. + +To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it +`here `_. + +Documentation +------------- + +View full documentation at: https://anyio.readthedocs.io/ + +Features +-------- + +AnyIO offers the following functionality: + +* Task groups (nurseries_ in trio terminology) +* High-level networking (TCP, UDP and UNIX sockets) + + * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python + 3.8) + * async/await style UDP sockets (unlike asyncio where you still have to use Transports and + Protocols) + +* A versatile API for byte streams and object streams +* Inter-task synchronization and communication (locks, conditions, events, semaphores, object + streams and futures) +* Worker threads +* Subprocesses +* Subinterpreter support for code parallelization (on Python 3.13 and later) +* Asynchronous file I/O (using worker threads) +* Signal handling +* Asynchronous versions of the functools_ and itertools_ modules + +AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. +It even works with the popular Hypothesis_ library. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _Trio: https://github.com/python-trio/trio +.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency +.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning +.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs +.. _pytest: https://docs.pytest.org/en/latest/ +.. _functools: https://docs.python.org/3/library/functools.html +.. _itertools: https://docs.python.org/3/library/itertools.html +.. _Hypothesis: https://hypothesis.works/ + +Security contact information +---------------------------- + +To report a security vulnerability, please use the `Tidelift security contact`_. +Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD new file mode 100644 index 0000000..97a70cf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD @@ -0,0 +1,53 @@ +anyio/__init__.py,sha256=OfroYRjIUw_JFj0Zwh1JAaajMQg4VqtoAt0XoOHDRwE,7602 +anyio/_lazyimport.py,sha256=LCjnYanIfYE9G-Dv_EsqNLDuVRppNicWZjCTfS9-sJU,6082 +anyio/from_thread.py,sha256=iZZbfcNySi4wAcx1_cxAQE4J0YZC88qRMIYqQ6SF9sc,19415 +anyio/functools.py,sha256=T4JS8IXq-x1S0Lbo2owF8l9fza2KypO147QLeyz4cjs,11797 +anyio/itertools.py,sha256=TS7_UCa2Y3cggtaLlWHbjOyf6QhPfQUdPduNW-ddXTE,16255 +anyio/lowlevel.py,sha256=QyWkvfetbCH0mHkqv1yxGwOaiZ7fyxhpZkHWOXPV5JU,6280 +anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/pytest_plugin.py,sha256=oLXyOstvRZW3VVbqIODhZsPt4jSz2UGh44YeG2nJXx0,14516 +anyio/to_interpreter.py,sha256=ATnoT6kei9wDmQZtXTA3SsY4CK7XKr8Ta4_x5i6pbTw,7100 +anyio/to_process.py,sha256=p6n8j0nreJUl1DEkR_9PL6KRoKtNqFxORTn8qZBvn94,9850 +anyio/to_thread.py,sha256=8MPVrrQq8ttGvV21cSd68rMto5bhxJ0-i9CupXag5Gs,2750 +anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_backends/_asyncio.py,sha256=Vqy7O1ymmEpxTiPzehSuBCVTziGALNV-BbsIRaKXsg4,107767 +anyio/_backends/_trio.py,sha256=Gs8XzX0IeBvje8IEZtJmpNylKCUAChKv7I_YZi3wlmg,45715 +anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 +anyio/_core/_concurrency_utils.py,sha256=kvRXMrnDqgQTBkZc-f279-lBnZM4YbSToXqhnAUCfSc,4522 +anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215 +anyio/_core/_eventloop.py,sha256=ByZUeJD9alMfcyTseRo5IzTO0IltEul_Gyq9iqSjqDk,6658 +anyio/_core/_exceptions.py,sha256=B4ZhLkBQT4tI6vShv4-GJui492IJsoIDTw1FQJEkmAI,5618 +anyio/_core/_fileio.py,sha256=q--ft9h5Y_FRLzhAnoYrXf3tOuZ8ZyIN2A9mCfMbZRc,34163 +anyio/_core/_futures.py,sha256=YQq5uhnCnKQMQD62tOLfzEm7dUXY3GQorJdH6ghfgIQ,6177 +anyio/_core/_resources.py,sha256=Ld5duCe9NwgPfI7EIOh3CMIBYQ7rq6ZQEa7CFS0u9Ec,450 +anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016 +anyio/_core/_sockets.py,sha256=C8b-3w5H9n0Fo4g7X8WJvk0m6CGh6NVAAFtieBGHK3g,35289 +anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806 +anyio/_core/_subprocesses.py,sha256=TMBuGNILz-47w5t9vayJ1GpCpDT6LxsMohJDFCewIRc,7938 +anyio/_core/_synchronization.py,sha256=ggsd74QPd846faA2HQeT07ONQ5HQ1S78gy8mvB3qCrw,21658 +anyio/_core/_tasks.py,sha256=J2dhM_Jq3Grsfg_enNWpRz2SxfM15uLt-pEfvoQq3uA,15391 +anyio/_core/_tempfile.py,sha256=IgoBCFaPhrFvsF9KHcCcrnCRn-UqMfw0WRdSDWgxAiI,19726 +anyio/_core/_testing.py,sha256=ZRucLh5kqhiFWAJMMbd8yZ6uIvz0rq3WwL72ldosxAU,2340 +anyio/_core/_typedattr.py,sha256=gt9Dzn-IL0QW14aHPDD8PAtq95FYKNNupHyf37xZGPQ,2607 +anyio/abc/__init__.py,sha256=XIVvCI2c6oyaOcqXe94k2rzU5_s7-ZNnlxGTk34H4T0,3050 +anyio/abc/_eventloop.py,sha256=0-UwgB351rkocCt8m3fjTmcNx2Fxn52nUoujG5DzMvU,11127 +anyio/abc/_resources.py,sha256=hrb0LXUgHSp3i8G6W8JJchdzKXgg1m0T-L7udXmv2y0,900 +anyio/abc/_sockets.py,sha256=jQWnPX7iDIDyvyvVkbuFhjezw4oI2WggzbW6Vl2-eqs,13162 +anyio/abc/_streams.py,sha256=IMeOPPutFXRbyeAutxV1HJe7FUQZYp2qKrJJpP1DCv0,7620 +anyio/abc/_subprocesses.py,sha256=CDqU1p0HuBS0pXUzBW_hbfhihTJRM67n__7qZmoPU84,2123 +anyio/abc/_tasks.py,sha256=HBPpT72aOy0bwMAxBCKmnXsR328wCYRjYm3iaVw2fQo,7070 +anyio/abc/_testing.py,sha256=A9Y8RAClIPn1A0aNHBN8YoaRy_MKOpbFGEbTHAXgB6U,2145 +anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/streams/buffered.py,sha256=u7hCD8SNrYHcutG6K5wEiy1F88-pizgjvEFM22Kq2Cw,6746 +anyio/streams/file.py,sha256=6jujI2m-QJITqqKFamrupX_DNsU7y2Fz3omLZxOLuY0,4524 +anyio/streams/memory.py,sha256=rpxNzDMag9PCOynBqN64KyuAfAPB41vFyRP-Npo-lE0,10793 +anyio/streams/stapled.py,sha256=UJ2Rgt0NUmM1VovRNOoZoic960lcbDjb3vhfZg3b_Z0,4845 +anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765 +anyio/streams/tls.py,sha256=fqLmxvULFO9rvxGyJ33VPrOIN7E3eiU1q90BkmKigQ8,15818 +anyio-4.15.1.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 +anyio-4.15.1.dist-info/METADATA,sha256=BDrstXGvcYnopsUo9djjJUXxss36r3YdUebVtn6jyoE,4733 +anyio-4.15.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91 +anyio-4.15.1.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 +anyio-4.15.1.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 +anyio-4.15.1.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL new file mode 100644 index 0000000..13013d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt new file mode 100644 index 0000000..44dd9bd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +anyio = anyio.pytest_plugin diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000..104eebf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt new file mode 100644 index 0000000..c77c069 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt @@ -0,0 +1 @@ +anyio diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py new file mode 100644 index 0000000..30a3a16 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/__init__.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._lazyimport import ( + fix_package_names, + install_lazy_importer, + set_deprecated_aliases, +) + +if TYPE_CHECKING or not install_lazy_importer(): + from ._core._concurrency_utils import amap as amap + from ._core._concurrency_utils import as_completed as as_completed + from ._core._concurrency_utils import gather as gather + from ._core._contextmanagers import ( + AsyncContextManagerMixin as AsyncContextManagerMixin, + ) + from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin + from ._core._eventloop import current_time as current_time + from ._core._eventloop import get_all_backends as get_all_backends + from ._core._eventloop import get_available_backends as get_available_backends + from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class + from ._core._eventloop import run as run + from ._core._eventloop import sleep as sleep + from ._core._eventloop import sleep_forever as sleep_forever + from ._core._eventloop import sleep_until as sleep_until + from ._core._exceptions import BrokenResourceError as BrokenResourceError + from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter + from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess + from ._core._exceptions import BusyResourceError as BusyResourceError + from ._core._exceptions import ClosedResourceError as ClosedResourceError + from ._core._exceptions import ConnectionFailed as ConnectionFailed + from ._core._exceptions import DelimiterNotFound as DelimiterNotFound + from ._core._exceptions import EndOfStream as EndOfStream + from ._core._exceptions import FutureAlreadyFinished as FutureAlreadyFinished + from ._core._exceptions import FutureCancelled as FutureCancelled + from ._core._exceptions import FutureFailed as FutureFailed + from ._core._exceptions import FutureNotFinished as FutureNotFinished + from ._core._exceptions import IncompleteRead as IncompleteRead + from ._core._exceptions import NoEventLoopError as NoEventLoopError + from ._core._exceptions import RunFinishedError as RunFinishedError + from ._core._exceptions import TaskCancelled as TaskCancelled + from ._core._exceptions import TaskFailed as TaskFailed + from ._core._exceptions import TaskNotFinished as TaskNotFinished + from ._core._exceptions import ( + TypedAttributeLookupError as TypedAttributeLookupError, + ) + from ._core._exceptions import WouldBlock as WouldBlock + from ._core._fileio import AsyncFile as AsyncFile + from ._core._fileio import Path as Path + from ._core._fileio import open_file as open_file + from ._core._fileio import wrap_file as wrap_file + from ._core._futures import Future as Future + from ._core._resources import aclose_forcefully as aclose_forcefully + from ._core._signals import open_signal_receiver as open_signal_receiver + from ._core._sockets import TCPConnectable as TCPConnectable + from ._core._sockets import UNIXConnectable as UNIXConnectable + from ._core._sockets import as_connectable as as_connectable + from ._core._sockets import connect_tcp as connect_tcp + from ._core._sockets import connect_unix as connect_unix + from ._core._sockets import ( + create_connected_udp_socket as create_connected_udp_socket, + ) + from ._core._sockets import ( + create_connected_unix_datagram_socket as create_connected_unix_datagram_socket, + ) + from ._core._sockets import create_tcp_listener as create_tcp_listener + from ._core._sockets import create_udp_socket as create_udp_socket + from ._core._sockets import ( + create_unix_datagram_socket as create_unix_datagram_socket, + ) + from ._core._sockets import create_unix_listener as create_unix_listener + from ._core._sockets import getaddrinfo as getaddrinfo + from ._core._sockets import getnameinfo as getnameinfo + from ._core._sockets import notify_closing as notify_closing + from ._core._sockets import wait_readable as wait_readable + from ._core._sockets import wait_socket_readable as wait_socket_readable + from ._core._sockets import wait_socket_writable as wait_socket_writable + from ._core._sockets import wait_writable as wait_writable + from ._core._streams import ( + create_memory_object_stream as create_memory_object_stream, + ) + from ._core._subprocesses import open_process as open_process + from ._core._subprocesses import run_process as run_process + from ._core._synchronization import CapacityLimiter as CapacityLimiter + from ._core._synchronization import ( + CapacityLimiterStatistics as CapacityLimiterStatistics, + ) + from ._core._synchronization import Condition as Condition + from ._core._synchronization import ConditionStatistics as ConditionStatistics + from ._core._synchronization import Event as Event + from ._core._synchronization import EventStatistics as EventStatistics + from ._core._synchronization import Lock as Lock + from ._core._synchronization import LockStatistics as LockStatistics + from ._core._synchronization import ResourceGuard as ResourceGuard + from ._core._synchronization import Semaphore as Semaphore + from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics + from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED + from ._core._tasks import CancelScope as CancelScope + from ._core._tasks import TaskHandle as TaskHandle + from ._core._tasks import create_task_group as create_task_group + from ._core._tasks import current_effective_deadline as current_effective_deadline + from ._core._tasks import fail_after as fail_after + from ._core._tasks import fail_at as fail_at + from ._core._tasks import move_on_after as move_on_after + from ._core._tasks import move_on_at as move_on_at + from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile + from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile + from ._core._tempfile import TemporaryDirectory as TemporaryDirectory + from ._core._tempfile import TemporaryFile as TemporaryFile + from ._core._tempfile import gettempdir as gettempdir + from ._core._tempfile import gettempdirb as gettempdirb + from ._core._tempfile import mkdtemp as mkdtemp + from ._core._tempfile import mkstemp as mkstemp + from ._core._testing import TaskInfo as TaskInfo + from ._core._testing import get_current_task as get_current_task + from ._core._testing import get_running_tasks as get_running_tasks + from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked + from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider + from ._core._typedattr import TypedAttributeSet as TypedAttributeSet + from ._core._typedattr import typed_attribute as typed_attribute + + # ruff: isort: off + from . import ( + abc as abc, + ) + from . import ( + from_thread as from_thread, + ) + from . import ( + functools as functools, + ) + from . import ( + itertools as itertools, + ) + from . import ( + lowlevel as lowlevel, + ) + from . import ( + to_interpreter as to_interpreter, + ) + from . import ( + to_process as to_process, + ) + from . import ( + to_thread as to_thread, + ) + + fix_package_names() + set_deprecated_aliases( + { + "BrokenWorkerIntepreter": "anyio.BrokenWorkerInterpreter", + } + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py new file mode 100644 index 0000000..7eba257 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py @@ -0,0 +1,3201 @@ +from __future__ import annotations + +import array +import asyncio +import concurrent.futures +import math +import os +import socket +import sys +import threading +import weakref +from asyncio import ( + AbstractEventLoop, + CancelledError, + all_tasks, + create_task, + current_task, + get_running_loop, + sleep, +) +from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined] +from collections import OrderedDict, deque +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from concurrent.futures import Future +from contextlib import AbstractContextManager, suppress +from contextvars import Context, copy_context +from dataclasses import dataclass, field +from functools import partial, wraps +from inspect import ( + CORO_RUNNING, + CORO_SUSPENDED, + getcoroutinestate, +) +from io import IOBase +from os import PathLike +from queue import Queue +from signal import Signals +from socket import AddressFamily, SocketKind +from threading import Thread +from types import CodeType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Literal, + ParamSpec, + TypeVar, + cast, +) +from weakref import WeakKeyDictionary + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + TaskInfo, + abc, +) +from .._core._eventloop import ( + claim_worker_thread, + set_current_async_library, + threadlocals, +) +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, + RunFinishedError, + WouldBlock, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from .._core._tasks import TaskHandle +from ..abc import ( + AsyncBackend, + IPSockAddrType, + SocketListener, + UDPPacketType, + UNIXDatagramPacketType, +) +from ..abc._tasks import call_for_coroutine, get_callable_name, get_coro_name +from ..lowlevel import RunVar, _run_vars + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from ..abc._eventloop import StrOrBytesPath + from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +else: + FileDescriptorLike = object + +if sys.version_info >= (3, 11): + from asyncio import Runner + from typing import Self, TypeVarTuple, Unpack +else: + import contextvars + import enum + import signal + from asyncio import coroutines, events, exceptions, tasks + + from exceptiongroup import BaseExceptionGroup + from typing_extensions import Self, TypeVarTuple, Unpack + + class _State(enum.Enum): + CREATED = "created" + INITIALIZED = "initialized" + CLOSED = "closed" + + class Runner: + # Copied from CPython 3.11 + def __init__( + self, + *, + debug: bool | None = None, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ): + self._state = _State.CREATED + self._debug = debug + self._loop_factory = loop_factory + self._loop: AbstractEventLoop | None = None + self._context = None + self._interrupt_count = 0 + self._set_event_loop = False + + def __enter__(self) -> Self: + self._lazy_init() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """Shutdown and close event loop.""" + loop = self._loop + if self._state is not _State.INITIALIZED or loop is None: + return + try: + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + if hasattr(loop, "shutdown_default_executor"): + loop.run_until_complete(loop.shutdown_default_executor()) + else: + loop.run_until_complete(_shutdown_default_executor(loop)) + finally: + if self._set_event_loop: + events.set_event_loop(None) + loop.close() + self._loop = None + self._state = _State.CLOSED + + def get_loop(self) -> AbstractEventLoop: + """Return embedded event loop.""" + self._lazy_init() + return self._loop + + def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval: + """Run a coroutine inside the embedded event loop.""" + if not coroutines.iscoroutine(coro): + raise ValueError(f"a coroutine was expected, got {coro!r}") + + if events._get_running_loop() is not None: + # fail fast with short traceback + raise RuntimeError( + "Runner.run() cannot be called from a running event loop" + ) + + self._lazy_init() + + if context is None: + context = self._context + task = context.run(self._loop.create_task, coro) + + if ( + threading.current_thread() is threading.main_thread() + and signal.getsignal(signal.SIGINT) is signal.default_int_handler + ): + sigint_handler = partial(self._on_sigint, main_task=task) + try: + signal.signal(signal.SIGINT, sigint_handler) + except ValueError: + # `signal.signal` may throw if `threading.main_thread` does + # not support signals (e.g. embedded interpreter with signals + # not registered - see gh-91880) + sigint_handler = None + else: + sigint_handler = None + + self._interrupt_count = 0 + try: + return self._loop.run_until_complete(task) + except exceptions.CancelledError: + if self._interrupt_count > 0: + uncancel = getattr(task, "uncancel", None) + if uncancel is not None and uncancel() == 0: + raise KeyboardInterrupt # noqa: B904 + raise # CancelledError + finally: + if ( + sigint_handler is not None + and signal.getsignal(signal.SIGINT) is sigint_handler + ): + signal.signal(signal.SIGINT, signal.default_int_handler) + + def _lazy_init(self) -> None: + if self._state is _State.CLOSED: + raise RuntimeError("Runner is closed") + if self._state is _State.INITIALIZED: + return + if self._loop_factory is None: + self._loop = events.new_event_loop() + if not self._set_event_loop: + # Call set_event_loop only once to avoid calling + # attach_loop multiple times on child watchers + events.set_event_loop(self._loop) + self._set_event_loop = True + else: + self._loop = self._loop_factory() + if self._debug is not None: + self._loop.set_debug(self._debug) + self._context = contextvars.copy_context() + self._state = _State.INITIALIZED + + def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None: + self._interrupt_count += 1 + if self._interrupt_count == 1 and not main_task.done(): + main_task.cancel() + # wakeup loop if it is blocked by select() with long timeout + self._loop.call_soon_threadsafe(lambda: None) + return + raise KeyboardInterrupt() + + def _cancel_all_tasks(loop: AbstractEventLoop) -> None: + to_cancel = tasks.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + async def _shutdown_default_executor(loop: AbstractEventLoop) -> None: + """Schedule the shutdown of the default executor.""" + + def _do_shutdown(future: asyncio.futures.Future) -> None: + try: + loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined] + loop.call_soon_threadsafe(future.set_result, None) + except Exception as ex: + loop.call_soon_threadsafe(future.set_exception, ex) + + loop._executor_shutdown_called = True + if loop._default_executor is None: + return + future = loop.create_future() + thread = threading.Thread(target=_do_shutdown, args=(future,)) + thread.start() + try: + await future + finally: + thread.join() + + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + +_root_task: RunVar[asyncio.Task[Any] | None] = RunVar("_root_task") + + +def find_root_task() -> asyncio.Task: + root_task = _root_task.get(None) + if root_task is not None and not root_task.done(): + return root_task + + # Look for a task that has been started via run_until_complete() + for task in all_tasks(): + if task._callbacks and not task.done(): + for cb, context in task._callbacks: + if ( + cb is _run_until_complete_cb + or getattr(cb, "__module__", None) == "uvloop.loop" + ): + _root_task.set(task) + + def _unset(t: asyncio.Task[Any]) -> None: + if vars := _run_vars.get(t.get_loop()): + vars.pop(_root_task, None) + + # Register a callback to break the task -> loop -> _run_var[loop][_root_task] -> task cycle + # Also run it in its own context to not create another reference. + # We can't use RunVar.reset() here since these are called synchronously + # and thus lowlevel.current_token() (which RunVar.reset() depends on) fails. + task.add_done_callback(_unset, context=context) + return task + + # Look up the topmost task in the AnyIO task tree, if possible + task = cast(asyncio.Task, current_task()) + state = _task_states.get(task) + if state: + cancel_scope = state.cancel_scope + while cancel_scope and cancel_scope._parent_scope is not None: + cancel_scope = cancel_scope._parent_scope + + if cancel_scope is not None: + return cast(asyncio.Task, cancel_scope._host_task) + + return task + + +def _task_started(task: asyncio.Task) -> bool: + """Return ``True`` if the task has been started and has not finished.""" + # The task coro should never be None here, as we never add finished tasks to the + # task list + coro = task.get_coro() + assert coro is not None + return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED) + + +# +# Timeouts and cancellation +# + + +def is_anyio_cancellation(exc: CancelledError) -> bool: + # Sometimes third party frameworks catch a CancelledError and raise a new one, so as + # a workaround we have to look at the previous ones in __context__ too for a + # matching cancel message + while True: + if ( + exc.args + and isinstance(exc.args[0], str) + and exc.args[0].startswith("Cancelled via cancel scope ") + ): + return True + + if isinstance(exc.__context__, CancelledError): + exc = exc.__context__ + continue + + return False + + +class CancelScope(BaseCancelScope): + __slots__ = ( + "_active", + "_cancel_called", + "_cancel_handle", + "_cancel_reason", + "_cancelled_caught", + "_child_scopes", + "_deadline", + "_host_task", + "_parent_scope", + "_pending_uncancellations", + "_shield", + "_tasks", + "_timeout_handle", + ) + + def __new__(cls, *, deadline: float = math.inf, shield: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, deadline: float = math.inf, shield: bool = False): + self._deadline = deadline + self._shield = shield + self._parent_scope: CancelScope | None = None + self._child_scopes: set[CancelScope] = set() + self._cancel_called = False + self._cancel_reason: str | None = None + self._cancelled_caught = False + self._active = False + self._timeout_handle: asyncio.TimerHandle | None = None + self._cancel_handle: asyncio.Handle | None = None + self._tasks: set[asyncio.Task] = set() + self._host_task: asyncio.Task | None = None + if sys.version_info >= (3, 11): + self._pending_uncancellations: int | None = 0 + else: + self._pending_uncancellations = None + + def __enter__(self) -> Self: + if self._active: + raise RuntimeError( + "Each CancelScope may only be used for a single 'with' block" + ) + + self._host_task = host_task = cast(asyncio.Task, current_task()) + self._tasks.add(host_task) + try: + task_state = _task_states[host_task] + except KeyError: + task_state = TaskState(None, self) + _task_states[host_task] = task_state + else: + self._parent_scope = task_state.cancel_scope + task_state.cancel_scope = self + if self._parent_scope is not None: + # If using an eager task factory, the parent scope may not even contain + # the host task + self._parent_scope._child_scopes.add(self) + self._parent_scope._tasks.discard(host_task) + + self._timeout() + self._active = True + + # Start cancelling the host task if the scope was cancelled before entering + if self._cancel_called: + self._deliver_cancellation(self) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + del exc_tb + + if not self._active: + raise RuntimeError("This cancel scope is not active") + if current_task() is not self._host_task: + raise RuntimeError( + "Attempted to exit cancel scope in a different task than it was " + "entered in" + ) + + assert self._host_task is not None + host_task_state = _task_states.get(self._host_task) + if host_task_state is None or host_task_state.cancel_scope is not self: + raise RuntimeError( + "Attempted to exit a cancel scope that isn't the current tasks's " + "current cancel scope" + ) + + try: + self._active = False + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._tasks.remove(self._host_task) + if self._parent_scope is not None: + self._parent_scope._child_scopes.remove(self) + self._parent_scope._tasks.add(self._host_task) + + host_task_state.cancel_scope = self._parent_scope + + # Restart the cancellation effort in the closest visible, cancelled parent + # scope if necessary + self._restart_cancellation_in_parent() + + # We only swallow the exception iff it was an AnyIO CancelledError, either + # directly as exc_val or inside an exception group and there are no cancelled + # parent cancel scopes visible to us here + if self._cancel_called and not self._parent_cancellation_is_visible_to_us: + # For each level-cancel() call made on the host task, call uncancel() + while self._pending_uncancellations: + self._host_task.uncancel() + self._pending_uncancellations -= 1 + + # Update cancelled_caught and check for exceptions we must not swallow + if isinstance(exc_val, BaseExceptionGroup): + cancelleds_caught, remaining = exc_val.split( + lambda exc: ( + isinstance(exc, CancelledError) + and is_anyio_cancellation(exc) + ) + ) + + if cancelleds_caught is None: + return False + + self._cancelled_caught = True + + if remaining is None: + return True + + context = remaining.__context__ + try: + # Preserve __cause__ and __suppress_context__ by avoiding `raise + # ... from ...` + raise remaining + finally: + # Preserve __context__ + remaining.__context__ = context + del context + else: + if isinstance(exc_val, CancelledError) and is_anyio_cancellation( + exc_val + ): + self._cancelled_caught = True + return True + else: + return False + else: + if self._pending_uncancellations: + assert self._parent_scope is not None + assert self._parent_scope._pending_uncancellations is not None + self._parent_scope._pending_uncancellations += ( + self._pending_uncancellations + ) + self._pending_uncancellations = 0 + + return False + finally: + self._host_task = None + del exc_val + + @property + def _effectively_cancelled(self) -> bool: + cancel_scope: CancelScope | None = self + while cancel_scope is not None: + if cancel_scope._cancel_called: + return True + + if cancel_scope.shield: + return False + + cancel_scope = cancel_scope._parent_scope + + return False + + @property + def _parent_cancellation_is_visible_to_us(self) -> bool: + return ( + self._parent_scope is not None + and not self.shield + and self._parent_scope._effectively_cancelled + ) + + def _timeout(self) -> None: + if self._deadline != math.inf: + loop = get_running_loop() + if loop.time() >= self._deadline: + self.cancel("deadline exceeded") + else: + self._timeout_handle = loop.call_at(self._deadline, self._timeout) + + def _deliver_cancellation(self, origin: CancelScope) -> bool: + """ + Deliver cancellation to directly contained tasks and nested cancel scopes. + + Schedule another run at the end if we still have tasks eligible for + cancellation. + + :param origin: the cancel scope that originated the cancellation + :return: ``True`` if the delivery needs to be retried on the next cycle + + """ + should_retry = False + current = current_task() + for task in self._tasks: + # Always skip tasks that are already done (see issue #1111) + if task.done(): + continue + + should_retry = True + if task._must_cancel: # type: ignore[attr-defined] + continue + + # The task is eligible for cancellation if it has started + if task is not current and (task is self._host_task or _task_started(task)): + waiter = task._fut_waiter # type: ignore[attr-defined] + if not isinstance(waiter, asyncio.Future) or not waiter.done(): + task.cancel(origin._cancel_reason) + if ( + task is origin._host_task + and origin._pending_uncancellations is not None + ): + origin._pending_uncancellations += 1 + + # Deliver cancellation to child scopes that aren't shielded or running their own + # cancellation callbacks + for scope in self._child_scopes: + if not scope._shield and not scope.cancel_called: + should_retry = scope._deliver_cancellation(origin) or should_retry + + # Schedule another callback if there are still tasks left + if origin is self: + if should_retry: + self._cancel_handle = get_running_loop().call_soon( + self._deliver_cancellation, origin + ) + else: + self._cancel_handle = None + + return should_retry + + def _restart_cancellation_in_parent(self) -> None: + """ + Restart the cancellation effort in the closest directly cancelled parent scope. + + """ + scope = self._parent_scope + while scope is not None: + if scope._cancel_called: + if scope._cancel_handle is None: + scope._deliver_cancellation(scope) + + break + + # No point in looking beyond any shielded scope + if scope._shield: + break + + scope = scope._parent_scope + + def _reparent(self, new_parent: CancelScope) -> None: + """ + Move this active scope from its current parent to ``new_parent``. + + Used by :meth:`TaskGroup.start` to move a task that has just called + ``task_status.started()`` into the target task group's cancel scope. + """ + if self._parent_scope is new_parent: + return + + if self._parent_scope is not None: + self._parent_scope._child_scopes.discard(self) + + self._parent_scope = new_parent + new_parent._child_scopes.add(self) + + # If the new parent (or an ancestor) is already cancelled, (re)start the + # delivery loop to ensure we're cancelled at next checkpoint like Trio. + self._restart_cancellation_in_parent() + + def cancel(self, reason: str | None = None) -> None: + if not self._cancel_called: + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._cancel_called = True + self._cancel_reason = f"Cancelled via cancel scope {id(self):x}" + if task := current_task(): + self._cancel_reason += f" by {task}" + + if reason: + self._cancel_reason += f"; reason: {reason}" + + if self._host_task is not None: + self._deliver_cancellation(self) + + @property + def deadline(self) -> float: + return self._deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self._deadline = float(value) + if self._timeout_handle is not None: + self._timeout_handle.cancel() + self._timeout_handle = None + + if self._active and not self._cancel_called: + self._timeout() + + @property + def cancel_called(self) -> bool: + return self._cancel_called + + @property + def cancelled_caught(self) -> bool: + return self._cancelled_caught + + @property + def shield(self) -> bool: + return self._shield + + @shield.setter + def shield(self, value: bool) -> None: + if self._shield != value: + self._shield = value + if not value: + self._restart_cancellation_in_parent() + + +# +# Task states +# + + +class TaskState: + """ + Encapsulates auxiliary task information that cannot be added to the Task instance + itself because there are no guarantees about its implementation. + """ + + __slots__ = "__weakref__", "cancel_scope", "parent_id" + + def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None): + self.parent_id = parent_id + self.cancel_scope = cancel_scope + + +_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary() + + +# +# Task groups +# + + +class _AsyncioTaskStatus(abc.TaskStatus[T_contra]): + def __init__( + self, + future: asyncio.Future, + parent_id: int, + target_scope: CancelScope, + spawn_scope: CancelScope, + ): + self._future = future + self._parent_id = parent_id + # The eventual parent scope for this spawn_scope + # (after task_status.started() has been called) + self._target_scope = target_scope + # The task's own cancel scope, also held by its TaskHandle + self._spawn_scope = spawn_scope + + def started(self, value: T_contra | None = None) -> None: + task = cast(asyncio.Task, current_task()) + _task_states[task].parent_id = self._parent_id + if self._future.done(): + if not self._future.cancelled(): + raise RuntimeError("called 'started' twice on the same task status") + else: + # Caller of start() was cancelled, nothing to reparent + return + + self._future.set_result(value) + self._spawn_scope._reparent(self._target_scope) + + +if sys.version_info >= (3, 12): + _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__ +else: + _eager_task_factory_code = None + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self.cancel_scope: CancelScope = CancelScope() + self._entered = False + self._exceptions: list[BaseException] = [] + self._tasks: set[asyncio.Task] = set() + self._on_completed_fut: asyncio.Future[None] | None = None + + async def __aenter__(self) -> Self: + if self._entered: + raise RuntimeError("TaskGroup cannot be entered more than once") + + self._entered = True + + self.cancel_scope.__enter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + if exc_val is not None: + self.cancel_scope.cancel() + if not isinstance(exc_val, CancelledError): + self._exceptions.append(exc_val) + + loop = get_running_loop() + try: + if self._tasks: + with CancelScope() as wait_scope: + while self._tasks: + self._on_completed_fut = loop.create_future() + + try: + await self._on_completed_fut + except CancelledError as exc: + # Shield the scope against further cancellation attempts, + # as they're not productive (#695) + wait_scope.shield = True + self.cancel_scope.cancel() + + # Set exc_val from the cancellation exception if it was + # previously unset. However, we should not replace a native + # cancellation exception with one raise by a cancel scope. + if exc_val is None or ( + isinstance(exc_val, CancelledError) + and not is_anyio_cancellation(exc) + ): + exc_val = exc + + self._on_completed_fut = None + else: + # If there are no child tasks to wait on, run at least one checkpoint + # anyway + await AsyncIOBackend.cancel_shielded_checkpoint() + + if self._exceptions: + # The exception that got us here should already have been + # added to self._exceptions so it's ok to break exception + # chaining and avoid adding a "During handling of above..." + # for each nesting level. + raise BaseExceptionGroup( + "unhandled errors in a TaskGroup", self._exceptions + ) from None + elif exc_val: + raise exc_val + except BaseException as exc: + if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__): + return True + + raise + + return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb) + finally: + del exc_val, exc_tb, self._exceptions + + def _spawn( + self, + coro: Coroutine[Any, Any, T_co], + name: object, + task_status: _AsyncioTaskStatus | None = None, + ) -> TaskHandle[T_co]: + task_status_future = task_status._future if task_status is not None else None + + def task_done(_task: asyncio.Task) -> None: + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_discard_from_awaited_by( + _task, self.cancel_scope._host_task + ) + + task_state = _task_states[_task] + assert task_state.cancel_scope is not None + assert _task in task_state.cancel_scope._tasks + task_state.cancel_scope._tasks.remove(_task) + self._tasks.remove(task) + del _task_states[_task] + + if self._on_completed_fut is not None and not self._tasks: + try: + self._on_completed_fut.set_result(None) + except asyncio.InvalidStateError: + pass + + try: + exc = _task.exception() + except CancelledError as e: + while isinstance(e.__context__, CancelledError): + e = e.__context__ + + exc = e + + if exc is not None: + # The future can only be in the cancelled state if the host task was + # cancelled, so return immediately instead of adding one more + # CancelledError to the exceptions list + if task_status_future is not None and task_status_future.cancelled(): + return + + if task_status_future is None or task_status_future.done(): + if not isinstance(exc, CancelledError): + self._exceptions.append(exc) + + if not self.cancel_scope._effectively_cancelled: + self.cancel_scope.cancel() + else: + task_status_future.set_exception(exc) + elif task_status_future is not None and not task_status_future.done(): + task_status_future.set_exception( + RuntimeError("Child exited without calling task_status.started()") + ) + + if task_status_future is not None: + parent_id = id(current_task()) + caller_state = _task_states.get(cast(asyncio.Task, current_task())) + if caller_state is not None and caller_state.cancel_scope is not None: + initial_scope = caller_state.cancel_scope + else: + # The caller is an unmanaged task (no task state) + initial_scope = self.cancel_scope + else: + parent_id = id(self.cancel_scope._host_task) + initial_scope = self.cancel_scope + + spawn_scope = task_status._spawn_scope if task_status is not None else None + handle = TaskHandle(coro, name, cancel_scope=spawn_scope) + loop = asyncio.get_running_loop() + wrapper_coro = handle._run_coro() + try: + if ( + (factory := loop.get_task_factory()) + and getattr(factory, "__code__", None) is _eager_task_factory_code + and (closure := getattr(factory, "__closure__", None)) + ): + custom_task_constructor = closure[0].cell_contents + task = custom_task_constructor( + wrapper_coro, loop=loop, name=handle.name + ) + else: + task = loop.create_task(wrapper_coro, name=handle.name) + except BaseException: + with suppress(BaseException): + wrapper_coro.close() + + with suppress(BaseException): + coro.close() + + raise + + # Make the spawned task inherit the initial cancel scope + _task_states[task] = TaskState(parent_id=parent_id, cancel_scope=initial_scope) + initial_scope._tasks.add(task) + self._tasks.add(task) + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task) + + task.add_done_callback(task_done) + return handle + + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + if not isinstance(coro, Coroutine): + raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") + + if not self._entered or not self.cancel_scope._active: + coro.close() + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + final_name = get_coro_name(coro, name) + + if context is not None: + return context.run(self._spawn, coro, name=final_name) + else: + return self._spawn(coro, name=final_name) + + async def start( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + if not self._entered or not self.cancel_scope._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + # Until task_status.started() is called, a task spawned via start() belongs to + # the *caller's* cancel scope, not the target group's, so cancelling the group + # does not cancel a task that hasn't reported startup yet. The + # task_status.started() call moves the task's cancel scope to this task group's + # cancel scope. + # + # The caller may be an unmanaged task (no _task_states entry), in which case + # fall back to the group's own scope. + future: asyncio.Future = asyncio.Future() + final_name = get_callable_name(func, name) + task_status: _AsyncioTaskStatus[Any] = _AsyncioTaskStatus( + future, id(self.cancel_scope._host_task), self.cancel_scope, CancelScope() + ) + coro = call_for_coroutine(func, args, task_status=task_status) + handle = self._spawn(coro, final_name, task_status) + + # If the task raises an exception after sending a start value without a switch + # point between, the task group is cancelled and this method never proceeds to + # process the completed future. That's why we have to have a shielded cancel + # scope here. + try: + await future + except BaseException: + if handle.status is TaskHandle.Status.PENDING: + # Cancel the task and wait for it to exit before returning + handle.cancel() + with CancelScope(shield=True): + await handle.wait() + + raise + + if return_handle: + handle._start_value = future.result() + return handle + else: + return future.result() + + +# +# Threads +# + +_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None] + + +class WorkerThread(Thread): + MAX_IDLE_TIME = 10 # seconds + + def __init__( + self, + root_task: asyncio.Task, + workers: set[WorkerThread], + idle_workers: deque[WorkerThread], + ): + kwargs: dict[str, Any] = {} + if sys.version_info >= (3, 14): + kwargs["context"] = Context() + + super().__init__(name="AnyIO worker thread", **kwargs) + self.root_task = root_task + self.workers = workers + self.idle_workers = idle_workers + self.loop = root_task._loop + self.queue: Queue[ + tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None + ] = Queue(2) + self.idle_since = AsyncIOBackend.current_time() + self.stopping = False + + def _report_result( + self, future: asyncio.Future, result: Any, exc: BaseException | None + ) -> None: + self.idle_since = AsyncIOBackend.current_time() + if not self.stopping: + self.idle_workers.append(self) + + if not future.cancelled(): + if exc is not None: + if isinstance(exc, StopIteration): + new_exc = RuntimeError("coroutine raised StopIteration") + new_exc.__cause__ = exc + exc = new_exc + + future.set_exception(exc) + else: + future.set_result(result) + + def run(self) -> None: + with claim_worker_thread(AsyncIOBackend, self.loop): + while True: + item = self.queue.get() + if item is None: + # Shutdown command received + return + + context, func, args, future, cancel_scope = item + if not future.cancelled(): + result = None + exception: BaseException | None = None + threadlocals.current_cancel_scope = cancel_scope + try: + result = context.run(func, *args) + except BaseException as exc: + exception = exc + finally: + del threadlocals.current_cancel_scope + + try: + self.loop.call_soon_threadsafe( + self._report_result, future, result, exception + ) + except RuntimeError: + if not self.loop.is_closed(): + raise + + del result, exception + + self.queue.task_done() + del item, context, func, args, future, cancel_scope + + def stop(self, f: asyncio.Task | None = None) -> None: + self.stopping = True + self.queue.put_nowait(None) + self.workers.discard(self) + try: + self.idle_workers.remove(self) + except ValueError: + pass + + +_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar( + "_threadpool_idle_workers" +) +_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers") + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class StreamReaderWrapper(abc.ByteReceiveStream): + _stream: asyncio.StreamReader + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + data = await self._stream.read(max_bytes) + if data: + return data + else: + raise EndOfStream + + async def aclose(self) -> None: + self._stream.set_exception(ClosedResourceError()) + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class StreamWriterWrapper(abc.ByteSendStream): + _stream: asyncio.StreamWriter + _closed: bool = field(init=False, default=False) + + async def send(self, item: bytes) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + stream_paused = self._stream._protocol._paused # type: ignore[attr-defined] + try: + self._stream.write(item) + await self._stream.drain() + except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc: + # If closed by us and/or the peer: + # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError + # * on uvloop and Winloop, write() eventually starts raising RuntimeError + if self._closed: + raise ClosedResourceError from exc + elif self._stream.is_closing(): + raise BrokenResourceError from exc + + raise + + if not stream_paused: + await AsyncIOBackend.cancel_shielded_checkpoint() + + async def aclose(self) -> None: + self._closed = True + self._stream.close() + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: asyncio.subprocess.Process + _stdin: StreamWriterWrapper | None + _stdout: StreamReaderWrapper | None + _stderr: StreamReaderWrapper | None + _exited: asyncio.Event + _transport: asyncio.SubprocessTransport + + async def aclose(self) -> None: + with CancelScope(shield=True) as scope: + # We need to close the underlying pipe_transports as well to allow a + # process blocking on full buffers to receive SIGPIPE and exit. + if self._stdin: + await self._stdin.aclose() + if pipe := self._transport.get_pipe_transport(0): + pipe.close() + if self._stdout: + await self._stdout.aclose() + if pipe := self._transport.get_pipe_transport(1): + pipe.close() + if self._stderr: + await self._stderr.aclose() + if pipe := self._transport.get_pipe_transport(2): + pipe.close() + + scope.shield = False + try: + await self.wait() + except BaseException: + scope.shield = True + # Closing the transport on asyncio also handles sending kill + self._transport.close() + await self.wait() + raise + + async def wait(self) -> int: + await self._exited.wait() + assert self._process.returncode is not None + return self._process.returncode + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: int) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +def _forcibly_shutdown_process_pool_on_exit( + workers: set[Process], _task: object +) -> None: + """ + Forcibly shuts down worker processes belonging to this event loop.""" + child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined] + if sys.version_info < (3, 12): + try: + child_watcher = asyncio.get_event_loop_policy().get_child_watcher() + except NotImplementedError: + pass + + # Close as much as possible (w/o async/await) to avoid warnings + for process in workers.copy(): + if process.returncode is not None: + continue + + process._stdin._stream._transport.close() # type: ignore[union-attr] + process._stdout._stream._transport.close() # type: ignore[union-attr] + process._stderr._stream._transport.close() # type: ignore[union-attr] + process.kill() + if child_watcher: + child_watcher.remove_child_handler(process.pid) + + +async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None: + """ + Shuts down worker processes belonging to this event loop. + + NOTE: this only works when the event loop was started using asyncio.run() or + anyio.run(). + + """ + process: abc.Process + try: + await sleep(math.inf) + except asyncio.CancelledError: + workers = workers.copy() + for process in workers: + if process.returncode is None: + process.kill() + + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class StreamProtocol(asyncio.Protocol): + read_queue: deque[bytes] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + is_at_eof: bool = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque() + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + cast(asyncio.Transport, transport).set_write_buffer_limits(0) + + def connection_lost(self, exc: Exception | None) -> None: + if exc: + self.exception = exc + + self.read_event.set() + self.write_event.set() + + def data_received(self, data: bytes) -> None: + # ProactorEventloop sometimes sends bytearray instead of bytes + self.read_queue.append(bytes(data)) + self.read_event.set() + + def eof_received(self) -> bool | None: + self.is_at_eof = True + self.read_event.set() + return True + + def pause_writing(self) -> None: + self.write_event = asyncio.Event() + + def resume_writing(self) -> None: + self.write_event.set() + + +class DatagramProtocol(asyncio.DatagramProtocol): + read_queue: deque[tuple[bytes, IPSockAddrType]] + read_event: asyncio.Event + write_event: asyncio.Event + closed_event: asyncio.Event + exception: Exception | None = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque(maxlen=100) # arbitrary value + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.closed_event = asyncio.Event() + self.write_event.set() + + def connection_lost(self, exc: Exception | None) -> None: + self.read_event.set() + self.write_event.set() + self.closed_event.set() + + def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None: + addr = convert_ipv6_sockaddr(addr) + self.read_queue.append((data, addr)) + self.read_event.set() + + def error_received(self, exc: Exception) -> None: + self.exception = exc + + def pause_writing(self) -> None: + self.write_event.clear() + + def resume_writing(self) -> None: + self.write_event.set() + + +class SocketStream(abc.SocketStream): + def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + with self._receive_guard: + if ( + not self._protocol.read_event.is_set() + and not self._transport.is_closing() + and not self._protocol.is_at_eof + ): + self._transport.resume_reading() + await self._protocol.read_event.wait() + self._transport.pause_reading() + else: + await AsyncIOBackend.checkpoint() + + try: + chunk = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + elif self._protocol.exception: + raise BrokenResourceError from self._protocol.exception + else: + raise EndOfStream from None + + if len(chunk) > max_bytes: + # Split the oversized chunk + chunk, leftover = chunk[:max_bytes], chunk[max_bytes:] + self._protocol.read_queue.appendleft(leftover) + + # If the read queue is empty, clear the flag so that the next call will + # block until data is available + if not self._protocol.read_queue: + self._protocol.read_event.clear() + + return chunk + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + + if self._closed: + raise ClosedResourceError + elif self._protocol.exception is not None: + raise BrokenResourceError from self._protocol.exception + + try: + self._transport.write(item) + except RuntimeError as exc: + if self._transport.is_closing(): + raise BrokenResourceError from exc + else: + raise + + await self._protocol.write_event.wait() + + async def send_eof(self) -> None: + try: + self._transport.write_eof() + except OSError: + pass + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + try: + self._transport.write_eof() + except OSError: + pass + + self._transport.close() + await sleep(0) + self._transport.abort() + + +class _RawSocketMixin: + _receive_future: asyncio.Future | None = None + _send_future: asyncio.Future | None = None + _closing = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._receive_future + loop.remove_reader(self.__raw_socket) + + f = self._receive_future = asyncio.Future() + loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._send_future + loop.remove_writer(self.__raw_socket) + + f = self._send_future = asyncio.Future() + loop.add_writer(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + async def aclose(self) -> None: + if not self._closing: + self._closing = True + if self.__raw_socket.fileno() != -1: + self.__raw_socket.close() + + if self._receive_future and not self._receive_future.done(): + self._receive_future.set_result(None) + if self._send_future and not self._send_future.done(): + self._send_future.set_result(None) + + +class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream): + async def send_eof(self) -> None: + with self._send_guard: + self._raw_socket.shutdown(socket.SHUT_WR) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(max_bytes) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = self._raw_socket.send(view) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + view = view[bytes_sent:] + + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + loop = get_running_loop() + fds = array.array("i") + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, _flags, _addr = self._raw_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + loop = get_running_loop() + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + # The ignore can be removed after mypy picks up + # https://github.com/python/typeshed/pull/5545 + self._raw_socket.sendmsg( + [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)] + ) + break + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + +class TCPSocketListener(abc.SocketListener): + _accept_scope: CancelScope | None = None + _closed = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = cast(asyncio.BaseEventLoop, get_running_loop()) + self._accept_guard = ResourceGuard("accepting connections from") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + async def accept(self) -> abc.SocketStream: + if self._closed: + raise ClosedResourceError + + with self._accept_guard: + await AsyncIOBackend.checkpoint() + with CancelScope() as self._accept_scope: + try: + client_sock, _addr = await self._loop.sock_accept(self._raw_socket) + except asyncio.CancelledError: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + if self._closed: + raise ClosedResourceError from None + + raise + finally: + self._accept_scope = None + + client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + transport, protocol = await self._loop.connect_accepted_socket( + StreamProtocol, client_sock + ) + return SocketStream(transport, protocol) + + async def aclose(self) -> None: + if self._closed: + return + + self._closed = True + if self._accept_scope: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + self._accept_scope.cancel() + await sleep(0) + + self._raw_socket.close() + + +class UNIXSocketListener(abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = get_running_loop() + self._accept_guard = ResourceGuard("accepting connections from") + self._closed = False + + async def accept(self) -> abc.SocketStream: + await AsyncIOBackend.checkpoint() + with self._accept_guard: + while True: + try: + client_sock, _ = self.__raw_socket.accept() + client_sock.setblocking(False) + return UNIXSocketStream(client_sock) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + self._loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback( + lambda _: self._loop.remove_reader(self.__raw_socket) + ) + await f + except OSError as exc: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + async def aclose(self) -> None: + self._closed = True + self.__raw_socket.close() + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + +class UDPSocket(abc.UDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + await self._protocol.closed_event.wait() + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + return self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(*item) + + +class ConnectedUDPSocket(abc.ConnectedUDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + await self._protocol.closed_event.wait() + + async def receive(self) -> bytes: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + packet = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + return packet[0] + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(item) + + +class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket): + async def receive(self) -> UNIXDatagramPacketType: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recvfrom(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: UNIXDatagramPacketType) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.sendto(*item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket): + async def receive(self) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.send(item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events") +_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events") + + +# +# Synchronization +# + + +class Event(BaseEvent): + __slots__ = ("_event",) + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self._event = asyncio.Event() + + def set(self) -> None: + self._event.set() + + def is_set(self) -> bool: + return self._event.is_set() + + async def wait(self) -> None: + if self.is_set(): + await AsyncIOBackend.checkpoint() + else: + await self._event.wait() + + def statistics(self) -> EventStatistics: + return EventStatistics(len(self._event._waiters)) + + +class Lock(BaseLock): + __slots__ = "_fast_acquire", "_owner_task", "_waiters" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self._owner_task: asyncio.Task | None = None + self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque() + + async def acquire(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._owner_task = task + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + if self._owner_task == task: + raise RuntimeError("Attempted to acquire an already held Lock") + + fut: asyncio.Future[None] = asyncio.Future() + item = task, fut + self._waiters.append(item) + try: + await fut + except CancelledError: + if fut.cancelled(): + try: + self._waiters.remove(item) + except ValueError: + pass + else: + self.release() + + raise + + def acquire_nowait(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + self._owner_task = task + return + + if self._owner_task is task: + raise RuntimeError("Attempted to acquire an already held Lock") + + raise WouldBlock + + def locked(self) -> bool: + return self._owner_task is not None + + def release(self) -> None: + if self._owner_task != current_task(): + raise RuntimeError("The current task is not holding this lock") + + # A cancelled waiter that already received ownership removes itself from + # _waiters before calling release(); any cancelled waiter still queued here + # was cancelled before being woken, so drop it. + while self._waiters: + task, fut = self._waiters.popleft() + if fut.cancelled(): + continue + + self._owner_task = task + fut.set_result(None) + return + + self._owner_task = None + + def statistics(self) -> LockStatistics: + task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None + return LockStatistics(self.locked(), task_info, len(self._waiters)) + + +class Semaphore(BaseSemaphore): + __slots__ = "_fast_acquire", "_max_value", "_value", "_waiters" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + super().__init__(initial_value, max_value=max_value) + self._value = initial_value + self._max_value = max_value + self._fast_acquire = fast_acquire + self._waiters: deque[asyncio.Future[None]] = deque() + + async def acquire(self) -> None: + if self._value > 0 and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._value -= 1 + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + fut: asyncio.Future[None] = asyncio.Future() + self._waiters.append(fut) + try: + await fut + except CancelledError: + if fut.cancelled(): + try: + self._waiters.remove(fut) + except ValueError: + pass + else: + self.release() + + raise + + def acquire_nowait(self) -> None: + if self._value == 0: + raise WouldBlock + + self._value -= 1 + + def release(self) -> None: + if self._max_value is not None and self._value == self._max_value: + raise ValueError("semaphore released too many times") + + while self._waiters: + fut = self._waiters.popleft() + if fut.cancelled(): + continue + + fut.set_result(None) + return + + self._value += 1 + + @property + def value(self) -> int: + return self._value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + return SemaphoreStatistics(len(self._waiters)) + + +class CapacityLimiter(BaseCapacityLimiter): + __slots__ = "_borrowers", "_total_tokens", "_wait_queue" + + def __new__(cls, total_tokens: float) -> Self: + return object.__new__(cls) + + def __init__(self, total_tokens: float): + self._total_tokens: float = 0 + self._borrowers: set[Any] = set() + self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict() + self.total_tokens = total_tokens + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + @property + def total_tokens(self) -> float: + return self._total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + + if value < 0: + raise ValueError("total_tokens must be >= 0") + + self._total_tokens = value + + # Notify waiting tasks that they have acquired the limiter while + # there is spare capacity. + while self._wait_queue and len(self._borrowers) < self._total_tokens: + borrower, event = self._wait_queue.popitem(last=False) + self._borrowers.add(borrower) + event.set() + + @property + def borrowed_tokens(self) -> int: + return len(self._borrowers) + + @property + def available_tokens(self) -> float: + return self._total_tokens - len(self._borrowers) + + def _notify_next_waiter(self) -> None: + """Hand a free token to the next task in line, if any.""" + if self._wait_queue and len(self._borrowers) < self._total_tokens: + borrower, event = self._wait_queue.popitem(last=False) + self._borrowers.add(borrower) + event.set() + + def acquire_nowait(self) -> None: + self.acquire_on_behalf_of_nowait(current_task()) + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + if borrower in self._borrowers: + raise RuntimeError( + "this borrower is already holding one of this CapacityLimiter's tokens" + ) + + if self._wait_queue or len(self._borrowers) >= self._total_tokens: + raise WouldBlock + + self._borrowers.add(borrower) + + async def acquire(self) -> None: + return await self.acquire_on_behalf_of(current_task()) + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + try: + self.acquire_on_behalf_of_nowait(borrower) + except WouldBlock: + event = asyncio.Event() + self._wait_queue[borrower] = event + try: + await event.wait() + except BaseException: + self._wait_queue.pop(borrower, None) + if event.is_set(): + self._borrowers.discard(borrower) + self._notify_next_waiter() + + raise + else: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except BaseException: + self.release() + raise + + def release(self) -> None: + self.release_on_behalf_of(current_task()) + + def release_on_behalf_of(self, borrower: object) -> None: + try: + self._borrowers.remove(borrower) + except KeyError: + raise RuntimeError( + "this borrower isn't holding any of this CapacityLimiter's tokens" + ) from None + + self._notify_next_waiter() + + def statistics(self) -> CapacityLimiterStatistics: + return CapacityLimiterStatistics( + self.borrowed_tokens, + self.total_tokens, + tuple(self._borrowers), + len(self._wait_queue), + ) + + +_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter") + + +# +# Operating system signals +# + + +class _SignalReceiver: + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + self._loop = get_running_loop() + self._signal_queue: deque[Signals] = deque() + self._future: asyncio.Future = asyncio.Future() + self._handled_signals: set[Signals] = set() + + def _deliver(self, signum: Signals) -> None: + self._signal_queue.append(signum) + if not self._future.done(): + self._future.set_result(None) + + def __enter__(self) -> Self: + for sig in set(self._signals): + self._loop.add_signal_handler(sig, self._deliver, sig) + self._handled_signals.add(sig) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + for sig in self._handled_signals: + self._loop.remove_signal_handler(sig) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + await AsyncIOBackend.checkpoint() + if not self._signal_queue: + self._future = asyncio.Future() + await self._future + + return self._signal_queue.popleft() + + +# +# Testing and debugging +# + + +class AsyncIOTaskInfo(TaskInfo): + def __init__(self, task: asyncio.Task): + task_state = _task_states.get(task) + if task_state is None: + parent_id = None + else: + parent_id = task_state.parent_id + + coro = task.get_coro() + assert coro is not None, "created TaskInfo from a completed Task" + super().__init__(id(task), parent_id, task.get_name(), coro) + self._task = weakref.ref(task) + + def has_pending_cancellation(self) -> bool: + if not (task := self._task()): + # If the task isn't around anymore, it won't have a pending cancellation + return False + + if task._must_cancel or ( # type: ignore[attr-defined] + isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined] + and task._fut_waiter.cancelled() # type: ignore[attr-defined] + ): + return True + + if task_state := _task_states.get(task): + if cancel_scope := task_state.cancel_scope: + return cancel_scope._effectively_cancelled + + return False + + +class TestRunner(abc.TestRunner): + _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]] + + def __init__( + self, + *, + debug: bool | None = None, + use_uvloop: bool = False, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ) -> None: + if use_uvloop and loop_factory is None: + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + self._runner = Runner(debug=debug, loop_factory=loop_factory) + self._exceptions: list[BaseException] = [] + self._runner_task: asyncio.Task | None = None + + def __enter__(self) -> Self: + self._runner.__enter__() + self.get_loop().set_exception_handler(self._exception_handler) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._runner.__exit__(exc_type, exc_val, exc_tb) + + def get_loop(self) -> AbstractEventLoop: + return self._runner.get_loop() + + def is_running(self) -> bool: + try: + asyncio.get_running_loop() + return True + except RuntimeError: + return False + + def _exception_handler( + self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] + ) -> None: + if isinstance(context.get("exception"), Exception): + self._exceptions.append(context["exception"]) + else: + loop.default_exception_handler(context) + + def _raise_async_exceptions(self) -> None: + # Re-raise any exceptions raised in asynchronous callbacks + if self._exceptions: + exceptions, self._exceptions = self._exceptions, [] + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup( + "Multiple exceptions occurred in asynchronous callbacks", exceptions + ) + + async def _run_tests_and_fixtures( + self, + receive_stream: MemoryObjectReceiveStream[ + tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]] + ], + ) -> None: + from _pytest.outcomes import OutcomeException + + with receive_stream, self._send_stream: + async for coro, future in receive_stream: + try: + retval = await coro + except CancelledError as exc: + if not future.cancelled(): + future.cancel(*exc.args) + + raise + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + if not isinstance(exc, (Exception, OutcomeException)): + raise + else: + if not future.cancelled(): + future.set_result(retval) + + async def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + /, + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if not self._runner_task: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], asyncio.Future] + ](1) + self._runner_task = self.get_loop().create_task( + self._run_tests_and_fixtures(receive_stream) + ) + + coro = func(*args, **kwargs) + future: asyncio.Future[T_Retval] = self.get_loop().create_future() + self._send_stream.send_nowait((coro, future)) + return await future + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + self._raise_async_exceptions() + + yield fixturevalue + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + except StopAsyncIteration: + self._raise_async_exceptions() + else: + self.get_loop().run_until_complete(asyncgen.aclose()) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + retval = self.get_loop().run_until_complete( + self._call_in_runner_task(fixture_func, **kwargs) + ) + self._raise_async_exceptions() + return retval + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + from _pytest.outcomes import OutcomeException + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(test_func, **kwargs) + ) + except Exception as exc: + self._exceptions.append(exc) + except OutcomeException: + raise + except BaseException: + # A BaseException (e.g. KeyboardInterrupt, SystemExit) interrupted the event loop before + # the test completed. Cancel _runner_task so it does not resume when the event + # loop is re-entered during async generator fixture teardown. + if self._runner_task is not None and not self._runner_task.done(): + self._runner_task.cancel() + self._send_stream.close() + try: + self.get_loop().run_until_complete(self._runner_task) + except CancelledError: + pass + finally: + self._runner_task = None + raise + self._raise_async_exceptions() + + +class _ProcessStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol): + """ + A subprocess protocol that allows us to be notified of ``process_exited`` + + asyncio's own ``Process.wait()`` only resolves once every pipe transport has + disconnected so to get same semantics as on trio and uvloop we need this. + """ + + def __init__(self) -> None: + # Match the standard factory for asyncio.create_process + super().__init__(limit=2**16, loop=asyncio.get_running_loop()) + self.exited = asyncio.Event() + + def process_exited(self) -> None: + super().process_exited() + self.exited.set() + + +class AsyncIOBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + @wraps(func) + async def wrapper() -> T_Retval: + task = cast(asyncio.Task, current_task()) + task.set_name(get_callable_name(func)) + _task_states[task] = TaskState(None, None) + + try: + return await func(*args) + finally: + del _task_states[task] + + debug = options.get("debug", None) + loop_factory = options.get("loop_factory", None) + if loop_factory is None and options.get("use_uvloop", False): + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + with Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(wrapper()) + + @classmethod + def current_token(cls) -> object: + return get_running_loop() + + @classmethod + def current_time(cls) -> float: + return get_running_loop().time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return CancelledError + + @classmethod + async def checkpoint(cls) -> None: + await sleep(0) + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + task = current_task() + if task is None: + return + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return + + while cancel_scope: + if cancel_scope.cancel_called: + await sleep(0) + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + with CancelScope(shield=True): + await sleep(0) + + @classmethod + async def sleep(cls, delay: float) -> None: + await sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + if (task := current_task()) is None: + return math.inf + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return math.inf + + deadline = math.inf + while cancel_scope: + deadline = min(deadline, cancel_scope.deadline) + if cancel_scope._cancel_called: + deadline = -math.inf + break + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + return deadline + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> BaseEvent: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> BaseLock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> BaseSemaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> BaseCapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( # type: ignore[return] + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: BaseCapacityLimiter | None = None, + ) -> T_Retval: + await cls.checkpoint() + + # If this is the first run in this event loop thread, set up the necessary + # variables + try: + idle_workers = _threadpool_idle_workers.get() + workers = _threadpool_workers.get() + except LookupError: + idle_workers = deque() + workers = set() + _threadpool_idle_workers.set(idle_workers) + _threadpool_workers.set(workers) + + async with limiter or cls.current_default_thread_limiter(): + with CancelScope(shield=not abandon_on_cancel) as scope: + future = asyncio.Future[T_Retval]() + root_task = find_root_task() + if not idle_workers: + worker = WorkerThread(root_task, workers, idle_workers) + worker.start() + workers.add(worker) + root_task.add_done_callback(worker.stop, context=Context()) + else: + worker = idle_workers.pop() + + # Prune any other workers that have been idle for MAX_IDLE_TIME + # seconds or longer + now = cls.current_time() + while idle_workers: + if ( + now - idle_workers[0].idle_since + < WorkerThread.MAX_IDLE_TIME + ): + break + + expired_worker = idle_workers.popleft() + expired_worker.root_task.remove_done_callback( + expired_worker.stop + ) + expired_worker.stop() + + context = copy_context() + context.run(set_current_async_library, None) + if abandon_on_cancel or scope._parent_scope is None: + worker_scope = scope + else: + worker_scope = scope._parent_scope + + worker.queue.put_nowait((context, func, args, future, worker_scope)) + return await future + + @classmethod + def check_cancelled(cls) -> None: + scope: CancelScope | None = threadlocals.current_cancel_scope + while scope is not None: + if scope.cancel_called: + raise CancelledError(f"Cancelled via cancel scope {id(scope):x}") + + if scope.shield: + return + + scope = scope._parent_scope + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + async def task_wrapper() -> T_co: + __tracebackhide__ = True + if scope is not None: + task = cast(asyncio.Task, current_task()) + _task_states[task] = TaskState(None, scope) + scope._tasks.add(task) + try: + return await func(*args) + except CancelledError as exc: + raise concurrent.futures.CancelledError(str(exc)) from None + finally: + if scope is not None: + scope._tasks.discard(task) + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + context = copy_context() + context.run(set_current_async_library, "asyncio") + scope = getattr(threadlocals, "current_cancel_scope", None) + f: concurrent.futures.Future[T_co] = context.run( + asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop + ) + return f.result() + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + @wraps(func) + def wrapper() -> None: + try: + set_current_async_library("asyncio") + f.set_result(func(*args)) + except BaseException as exc: + f.set_exception(exc) + if not isinstance(exc, Exception): + raise + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + f: concurrent.futures.Future[T_Retval] = Future() + loop.call_soon_threadsafe(wrapper) + return f.result() + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + await cls.checkpoint() + if isinstance(command, PathLike): + command = os.fspath(command) + + # Use loop.subprocess_shell()/subprocess_exec() rather than their + # asyncio.create_subprocess_*() counterparts to get access to + # transport/protocol. + loop = asyncio.get_running_loop() + if isinstance(command, (str, bytes)): + transport, protocol = await loop.subprocess_shell( + _ProcessStreamProtocol, + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + else: + transport, protocol = await loop.subprocess_exec( + _ProcessStreamProtocol, + *command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + + process = asyncio.subprocess.Process(transport, protocol, loop) + stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None + stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None + stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None + return Process( + process, + stdin_stream, + stdout_stream, + stderr_stream, + protocol.exited, + transport, + ) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + create_task( + _shutdown_process_pool_on_exit(workers), + name="AnyIO process pool shutdown task", + ) + find_root_task().add_done_callback( + partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type] + ) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> abc.SocketStream: + transport, protocol = cast( + tuple[asyncio.Transport, StreamProtocol], + await get_running_loop().create_connection( + StreamProtocol, host, port, local_addr=local_address + ), + ) + transport.pause_reading() + return SocketStream(transport, protocol) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + await cls.checkpoint() + loop = get_running_loop() + raw_socket = socket.socket(socket.AF_UNIX) + raw_socket.setblocking(False) + while True: + try: + raw_socket.connect(path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return UNIXSocketStream(raw_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, + local_addr=local_address, + remote_addr=remote_address, + family=family, + reuse_port=reuse_port, + ) + if protocol.exception: + transport.close() + raise protocol.exception + + if not remote_address: + return UDPSocket(transport, protocol) + else: + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def create_unix_datagram_socket( # type: ignore[override] + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + await cls.checkpoint() + loop = get_running_loop() + + if remote_path: + while True: + try: + raw_socket.connect(remote_path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return ConnectedUNIXDatagramSocket(raw_socket) + else: + return UNIXDatagramSocket(raw_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await get_running_loop().getaddrinfo( + host, port, family=family, type=type, proto=proto, flags=flags + ) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await get_running_loop().getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + read_events = _read_events.get() + except LookupError: + read_events = {} + _read_events.set(read_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if read_events.get(fd): + raise BusyResourceError("reading from") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_reader(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_reader(fd, cb) + remove_reader = selector.remove_reader + else: + remove_reader = loop.remove_reader + + read_events[fd] = fut + try: + success = await fut + finally: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + if not success: + raise ClosedResourceError + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + write_events = _write_events.get() + except LookupError: + write_events = {} + _write_events.set(write_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if write_events.get(fd): + raise BusyResourceError("writing to") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_writer(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_writer(fd, cb) + remove_writer = selector.remove_writer + else: + remove_writer = loop.remove_writer + + write_events[fd] = fut + try: + success = await fut + finally: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + if not success: + raise ClosedResourceError + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + fd = obj if isinstance(obj, int) else obj.fileno() + loop = get_running_loop() + + try: + write_events = _write_events.get() + except LookupError: + pass + else: + try: + fut = write_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_writer(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_writer(fd) + + try: + read_events = _read_events.get() + except LookupError: + pass + else: + try: + fut = read_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_reader(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_reader(fd) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener: + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + return UNIXSocketListener(sock) + + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + transport, protocol = await get_running_loop().create_connection( + StreamProtocol, sock=sock + ) + return SocketStream(transport, protocol) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + return UNIXSocketStream(sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return UDPSocket(transport, protocol) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + return UNIXDatagramSocket(sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + return ConnectedUNIXDatagramSocket(sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _default_thread_limiter.get() + except LookupError: + limiter = CapacityLimiter(40) + _default_thread_limiter.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type] + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()] + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + await cls.checkpoint() + this_task = current_task() + while True: + for task in all_tasks(): + if task is this_task: + continue + + waiter = task._fut_waiter # type: ignore[attr-defined] + if waiter is None or waiter.done(): + await sleep(0.1) + break + else: + return + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = AsyncIOBackend diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py new file mode 100644 index 0000000..2e67786 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py @@ -0,0 +1,1473 @@ +from __future__ import annotations + +import array +import math +import os +import socket +import sys +import types +import weakref +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from contextlib import AbstractContextManager +from contextvars import Context +from dataclasses import dataclass +from functools import partial, wraps +from io import IOBase +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind +from types import TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Generic, + Literal, + NoReturn, + ParamSpec, + TypeVar, + cast, + overload, +) + +import trio.from_thread +import trio.lowlevel +from outcome import Error, Outcome, Value +from trio.lowlevel import ( + current_root_task, + current_task, + notify_closing, + wait_readable, + wait_writable, +) +from trio.socket import SocketType as TrioSocketType +from trio.to_thread import run_sync + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + RunFinishedError, + TaskInfo, + WouldBlock, + abc, +) +from .._core._eventloop import claim_worker_thread +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from .._core._tasks import TaskHandle +from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType +from ..abc._eventloop import AsyncBackend, StrOrBytesPath +from ..abc._tasks import T_contra, call_for_coroutine, get_callable_name, get_coro_name + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from ..streams.memory import MemoryObjectSendStream + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +if sys.version_info >= (3, 11): + from typing import Self, TypeVarTuple, Unpack +else: + from exceptiongroup import BaseExceptionGroup + from typing_extensions import Self, TypeVarTuple, Unpack + +T = TypeVar("T") +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + + +def ensure_returns_coro( + func: Callable[P, Awaitable[T_Retval]], +) -> Callable[P, Coroutine[Any, Any, T_Retval]]: + @wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T_Retval]: + awaitable = func(*args, **kwargs) + # Check the common case first. + if isinstance(awaitable, Coroutine): + return awaitable + elif not isinstance(awaitable, Awaitable): + # The user violated the type annotations. Still, we should pass this on to + # Trio so it can raise with an appropriate message. + return awaitable + else: + + @wraps(func) + async def inner_wrapper() -> T_Retval: + return await awaitable + + return inner_wrapper() + + return wrapper + + +# +# Event loop +# + +RunVar = trio.lowlevel.RunVar + + +# +# Timeouts and cancellation +# + + +class CancelScope(BaseCancelScope): + __slots__ = ("__original",) + + def __new__( + cls, original: trio.CancelScope | None = None, **kwargs: object + ) -> Self: + return object.__new__(cls) + + def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None: + self.__original = original or trio.CancelScope(**kwargs) + + def __enter__(self) -> Self: + self.__original.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + return self.__original.__exit__(exc_type, exc_val, exc_tb) + + def cancel(self, reason: str | None = None) -> None: + self.__original.cancel(reason) + + @property + def deadline(self) -> float: + return self.__original.deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self.__original.deadline = value + + @property + def cancel_called(self) -> bool: + return self.__original.cancel_called + + @property + def cancelled_caught(self) -> bool: + return self.__original.cancelled_caught + + @property + def shield(self) -> bool: + return self.__original.shield + + @shield.setter + def shield(self, value: bool) -> None: + self.__original.shield = value + + +# +# Task groups +# + +empty_start_value = sentinel("empty_start_value") + + +class _TrioTaskStatus(abc.TaskStatus[T_contra], Generic[T_contra]): + early_start_value: T_contra | object = empty_start_value + real_task_status: trio.TaskStatus[T_contra | None] | None = None + + def started(self, value: T_contra | None = None) -> None: + if self.real_task_status is None: + if self.early_start_value is not empty_start_value: + raise RuntimeError("called 'started' twice on the same task status") + + self.early_start_value = value + else: + self.real_task_status.started(value) + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self._entered = False + self._active = False + self._nursery_manager = trio.open_nursery(strict_exception_groups=True) + self.cancel_scope = None # type: ignore[assignment] + + async def __aenter__(self) -> Self: + if self._entered: + raise RuntimeError("TaskGroup cannot be entered more than once") + + self._entered = True + self._active = True + self._nursery = await self._nursery_manager.__aenter__() + self.cancel_scope = CancelScope(self._nursery.cancel_scope) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type + return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value] + except BaseExceptionGroup as exc: + if not exc.split(trio.Cancelled)[1]: + raise trio.Cancelled._create() from exc + + raise + finally: + del exc_val, exc_tb + self._active = False + + def _check_active(self, coro: Coroutine | None = None) -> None: + if not self._active: + if coro is not None: + coro.close() + + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + if not isinstance(coro, Coroutine): + raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") + + self._check_active(coro) + final_name = get_coro_name(coro, name) + handle = TaskHandle(coro, final_name) + if context is not None: + context.run( + partial(self._nursery.start_soon, handle._run_coro, name=handle.name) + ) + else: + self._nursery.start_soon(handle._run_coro, name=handle.name) + + return handle + + async def start( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + handle: TaskHandle[T_co] + + async def run_coro_with_task_status( + *, task_status: trio.TaskStatus[Any] + ) -> None: + nonlocal handle + wrapper_task_status = _TrioTaskStatus() + coro = call_for_coroutine(func, args, task_status=wrapper_task_status) + if wrapper_task_status.early_start_value is not empty_start_value: + task_status.started(wrapper_task_status.early_start_value) + else: + wrapper_task_status.real_task_status = task_status + + handle = TaskHandle(coro, final_name) + await handle._run_coro() + + self._check_active() + final_name = get_callable_name(func, name) + start_value = await self._nursery.start( + run_coro_with_task_status, name=final_name + ) + if return_handle: + handle._start_value = start_value + return handle + else: + return start_value + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class ReceiveStreamWrapper(abc.ByteReceiveStream): + _stream: trio.abc.ReceiveStream + + async def receive(self, max_bytes: int | None = None) -> bytes: + if max_bytes is not None and max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + try: + data = await self._stream.receive_some(max_bytes) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + if data: + return bytes(data) + else: + raise EndOfStream + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class SendStreamWrapper(abc.ByteSendStream): + _stream: trio.abc.SendStream + + async def send(self, item: bytes) -> None: + try: + await self._stream.send_all(item) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: trio.Process + _stdin: abc.ByteSendStream | None + _stdout: abc.ByteReceiveStream | None + _stderr: abc.ByteReceiveStream | None + + async def aclose(self) -> None: + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: Signals) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +class _ProcessPoolShutdownInstrument(trio.abc.Instrument): + def after_run(self) -> None: + super().after_run() + + +current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar( + "current_default_worker_process_limiter" +) + + +async def _shutdown_process_pool(workers: set[abc.Process]) -> None: + try: + await trio.sleep(math.inf) + except trio.Cancelled: + for process in workers: + if process.returncode is None: + process.kill() + + with CancelScope(shield=True): + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class _TrioSocketMixin(Generic[T_SockAddr]): + def __init__(self, trio_socket: TrioSocketType) -> None: + self._trio_socket = trio_socket + self._closed = False + + def _check_closed(self) -> None: + if self._closed: + raise ClosedResourceError + if self._trio_socket.fileno() < 0: + raise BrokenResourceError + + @property + def _raw_socket(self) -> socket.socket: + return self._trio_socket._sock # type: ignore[attr-defined] + + async def aclose(self) -> None: + if self._trio_socket.fileno() >= 0: + self._closed = True + self._trio_socket.close() + + def _convert_socket_error(self, exc: BaseException) -> NoReturn: + if isinstance(exc, trio.ClosedResourceError): + raise ClosedResourceError from exc + elif self._trio_socket.fileno() < 0 and self._closed: + raise ClosedResourceError from None + elif isinstance(exc, OSError): + raise BrokenResourceError from exc + else: + raise exc + + +class SocketStream(_TrioSocketMixin, abc.SocketStream): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + with self._receive_guard: + try: + data = await self._trio_socket.recv(max_bytes) + except BaseException as exc: + self._convert_socket_error(exc) + + if data: + return data + else: + raise EndOfStream + + async def send(self, item: bytes) -> None: + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = await self._trio_socket.send(view) + except BaseException as exc: + self._convert_socket_error(exc) + + view = view[bytes_sent:] + + async def send_eof(self) -> None: + self._trio_socket.shutdown(socket.SHUT_WR) + + +class UNIXSocketStream(SocketStream, abc.UNIXSocketStream): + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + fds = array.array("i") + await trio.lowlevel.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, _flags, _addr = await self._trio_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BaseException as exc: + self._convert_socket_error(exc) + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await trio.lowlevel.checkpoint() + with self._send_guard: + while True: + try: + await self._trio_socket.sendmsg( + [message], + [ + ( + socket.SOL_SOCKET, + socket.SCM_RIGHTS, + fdarray, + ) + ], + ) + break + except BaseException as exc: + self._convert_socket_error(exc) + + +class TCPSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> SocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SocketStream(trio_socket) + + +class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> UNIXSocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + return UNIXSocketStream(trio_socket) + + +class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, convert_ipv6_sockaddr(addr) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> UNIXDatagramPacketType: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, addr + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UNIXDatagramPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUNIXDatagramSocket( + _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket +): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +# +# Synchronization +# + + +class Event(BaseEvent): + __slots__ = ("__original",) + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self.__original = trio.Event() + + def is_set(self) -> bool: + return self.__original.is_set() + + async def wait(self) -> None: + return await self.__original.wait() + + def statistics(self) -> EventStatistics: + orig_statistics = self.__original.statistics() + return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting) + + def set(self) -> None: + self.__original.set() + + +class Lock(BaseLock): + __slots__ = "__original", "_fast_acquire" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self.__original = trio.Lock() + + @staticmethod + def _convert_runtime_error_msg(exc: RuntimeError) -> None: + if exc.args == ("attempt to re-acquire an already held Lock",): + exc.args = ("Attempted to acquire an already held Lock",) + + async def acquire(self) -> None: + if not self._fast_acquire: + try: + await self.__original.acquire() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def locked(self) -> bool: + return self.__original.locked() + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> LockStatistics: + orig_statistics = self.__original.statistics() + owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None + return LockStatistics( + orig_statistics.locked, owner, orig_statistics.tasks_waiting + ) + + +class Semaphore(BaseSemaphore): + __slots__ = ("__original",) + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self.__original = trio.Semaphore(initial_value, max_value=max_value) + + async def acquire(self) -> None: + if not self._fast_acquire: + await self.__original.acquire() + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + @property + def max_value(self) -> int | None: + return self.__original.max_value + + @property + def value(self) -> int: + return self.__original.value + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> SemaphoreStatistics: + orig_statistics = self.__original.statistics() + return SemaphoreStatistics(orig_statistics.tasks_waiting) + + +class CapacityLimiter(BaseCapacityLimiter): + __slots__ = ("__original",) + + def __new__( + cls, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> None: + if original is not None: + self.__original = original + else: + assert total_tokens is not None + self.__original = trio.CapacityLimiter(total_tokens) + + async def __aenter__(self) -> None: + return await self.__original.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.__original.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + return self.__original.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + self.__original.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + return self.__original.borrowed_tokens + + @property + def available_tokens(self) -> float: + return self.__original.available_tokens + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + try: + self.__original.acquire_on_behalf_of_nowait(borrower) + except trio.WouldBlock: + raise WouldBlock from None + + async def acquire(self) -> None: + await self.__original.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self.__original.acquire_on_behalf_of(borrower) + + def release(self) -> None: + return self.__original.release() + + def release_on_behalf_of(self, borrower: object) -> None: + return self.__original.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + orig = self.__original.statistics() + return CapacityLimiterStatistics( + borrowed_tokens=orig.borrowed_tokens, + total_tokens=orig.total_tokens, + borrowers=tuple(orig.borrowers), + tasks_waiting=orig.tasks_waiting, + ) + + +_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper") + + +# +# Signal handling +# + + +class _SignalReceiver: + _iterator: AsyncIterator[int] + + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + + def __enter__(self) -> Self: + self._cm = trio.open_signal_receiver(*self._signals) + self._iterator = self._cm.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return self._cm.__exit__(exc_type, exc_val, exc_tb) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + signum = await self._iterator.__anext__() + return Signals(signum) + + +# +# Testing and debugging +# + + +class TestRunner(abc.TestRunner): + def __init__(self, **options: Any) -> None: + from queue import Queue + + self._call_queue: Queue[Callable[[], object]] = Queue() + self._send_stream: ( + MemoryObjectSendStream[tuple[Awaitable[Any], list[Outcome]]] | None + ) = None + self._options = options + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + if self._send_stream: + self._send_stream.close() + while self._send_stream is not None: + self._call_queue.get()() + + def is_running(self) -> bool: + return trio.lowlevel.in_trio_task() + + async def _run_tests_and_fixtures(self) -> None: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], list[Outcome]] + ](1) + with receive_stream: + async for awaitable, outcome_holder in receive_stream: + try: + retval = await awaitable + except BaseException as exc: + outcome_holder.append(Error(exc)) + else: + outcome_holder.append(Value(retval)) + + def _main_task_finished(self, outcome: object) -> None: + self._send_stream = None + + def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + /, + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if self._send_stream is None: + trio.lowlevel.start_guest_run( + self._run_tests_and_fixtures, + run_sync_soon_threadsafe=self._call_queue.put, + done_callback=self._main_task_finished, + **self._options, + ) + while self._send_stream is None: + self._call_queue.get()() + + outcome_holder: list[Outcome] = [] + self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder)) + while not outcome_holder: + self._call_queue.get()() + + return outcome_holder[0].unwrap() + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None) + + yield fixturevalue + + try: + self._call_in_runner_task(asyncgen.asend, None) + except StopAsyncIteration: + pass + else: + self._call_in_runner_task(asyncgen.aclose) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + return self._call_in_runner_task(fixture_func, **kwargs) + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + self._call_in_runner_task(test_func, **kwargs) + + +class TrioTaskInfo(TaskInfo): + def __init__(self, task: trio.lowlevel.Task): + parent_id = None + if task.parent_nursery and task.parent_nursery.parent_task: + parent_id = id(task.parent_nursery.parent_task) + + super().__init__(id(task), parent_id, task.name, task.coro) + self._task = weakref.proxy(task) + + def has_pending_cancellation(self) -> bool: + try: + return self._task._cancel_status.effectively_cancelled + except ReferenceError: + # If the task is no longer around, it surely doesn't have a cancellation + # pending + return False + + +class TrioBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + assert not kwargs, "unreachable, and not supported by Trio" + return trio.run(ensure_returns_coro(func), *args, **options) + + @classmethod + def current_token(cls) -> object: + return trio.lowlevel.current_trio_token() + + @classmethod + def current_time(cls) -> float: + return trio.current_time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return trio.Cancelled + + @classmethod + async def checkpoint(cls) -> None: + await trio.lowlevel.checkpoint() + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + await trio.lowlevel.checkpoint_if_cancelled() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + await trio.lowlevel.cancel_shielded_checkpoint() + + @classmethod + async def sleep(cls, delay: float) -> None: + await trio.sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> BaseCancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + return trio.current_effective_deadline() + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> BaseEvent: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> BaseSemaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: BaseCapacityLimiter | None = None, + ) -> T_Retval: + def wrapper() -> T_Retval: + with claim_worker_thread(TrioBackend, token): + return func(*args) + + token = TrioBackend.current_token() + return await run_sync( + wrapper, + abandon_on_cancel=abandon_on_cancel, + limiter=cast(trio.CapacityLimiter, limiter), + ) + + @classmethod + def check_cancelled(cls) -> None: + trio.from_thread.check_cancelled() + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run_sync(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + def convert_item(item: StrOrBytesPath) -> str: + str_or_bytes = os.fspath(item) + if isinstance(str_or_bytes, str): + return str_or_bytes + else: + return os.fsdecode(str_or_bytes) + + if isinstance(command, (str, bytes, PathLike)): + process = await trio.lowlevel.open_process( + convert_item(command), + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=True, + **kwargs, + ) + else: + process = await trio.lowlevel.open_process( + [convert_item(item) for item in command], + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=False, + **kwargs, + ) + + stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None + stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None + stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + family = socket.AF_INET6 if ":" in host else socket.AF_INET + trio_socket = trio.socket.socket(family) + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if local_address: + await trio_socket.bind(local_address) + + try: + await trio_socket.connect((host, port)) + except BaseException: + trio_socket.close() + raise + + return SocketStream(trio_socket) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + trio_socket = trio.socket.socket(socket.AF_UNIX) + try: + await trio_socket.connect(path) + except BaseException: + trio_socket.close() + raise + + return UNIXSocketStream(trio_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: socket.AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM) + + if reuse_port: + trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + if local_address: + await trio_socket.bind(local_address) + + if remote_address: + await trio_socket.connect(remote_address) + return ConnectedUDPSocket(trio_socket) + else: + return UDPSocket(trio_socket) + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: None + ) -> abc.UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes + ) -> abc.ConnectedUNIXDatagramSocket: ... + + @classmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + trio_socket = trio.socket.from_stdlib_socket(raw_socket) + + if remote_path: + await trio_socket.connect(remote_path) + return ConnectedUNIXDatagramSocket(trio_socket) + else: + return UNIXDatagramSocket(trio_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await trio.socket.getaddrinfo(host, port, family, type, proto, flags) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await trio.socket.getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_readable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("reading from") from None + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_writable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("writing to") from None + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + notify_closing(obj) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener: + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + return UNIXSocketListener(sock) + + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return SocketStream(trio_sock) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXSocketStream(trio_sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UDPSocket(trio_sock) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUDPSocket(trio_sock) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXDatagramSocket(trio_sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUNIXDatagramSocket(trio_sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _capacity_limiter_wrapper.get() + except LookupError: + limiter = CapacityLimiter( + original=trio.to_thread.current_default_thread_limiter() + ) + _capacity_limiter_wrapper.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + task = current_task() + return TrioTaskInfo(task) + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + root_task = current_root_task() + assert root_task + task_infos = [TrioTaskInfo(root_task)] + nurseries = root_task.child_nurseries + while nurseries: + new_nurseries: list[trio.Nursery] = [] + for nursery in nurseries: + for task in nursery.child_tasks: + task_infos.append(TrioTaskInfo(task)) + new_nurseries.extend(task.child_nurseries) + + nurseries = new_nurseries + + return task_infos + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + from trio.testing import wait_all_tasks_blocked + + await wait_all_tasks_blocked() + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = TrioBackend diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py new file mode 100644 index 0000000..9f35bae --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import asyncio +import socket +import threading +from collections.abc import Callable +from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + +_selector_lock = threading.Lock() +_selector: Selector | None = None + + +class Selector: + def __init__(self) -> None: + self._thread = threading.Thread(target=self.run, name="AnyIO socket selector") + self._selector = DefaultSelector() + self._send, self._receive = socket.socketpair() + self._send.setblocking(False) + self._receive.setblocking(False) + # This somewhat reduces the amount of memory wasted queueing up data + # for wakeups. With these settings, maximum number of 1-byte sends + # before getting BlockingIOError: + # Linux 4.8: 6 + # macOS (darwin 15.5): 1 + # Windows 10: 525347 + # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send + # blocking, even on non-blocking sockets, so don't do that.) + self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) + self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) + # On Windows this is a TCP socket so this might matter. On other + # platforms this fails b/c AF_UNIX sockets aren't actually TCP. + try: + self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError: + pass + + self._selector.register(self._receive, EVENT_READ) + self._closed = False + + def start(self) -> None: + self._thread.start() + threading._register_atexit(self._stop) # type: ignore[attr-defined] + + def _stop(self) -> None: + global _selector + self._closed = True + self._notify_self() + self._send.close() + self._thread.join() + self._selector.unregister(self._receive) + self._receive.close() + self._selector.close() + _selector = None + assert not self._selector.get_map(), ( + "selector still has registered file descriptors after shutdown" + ) + + def _notify_self(self) -> None: + try: + self._send.send(b"\x00") + except BlockingIOError: + pass + + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)}) + else: + if EVENT_READ in key.data: + raise ValueError( + "this file descriptor is already registered for reading" + ) + + key.data[EVENT_READ] = loop, callback + self._selector.modify(fd, key.events | EVENT_READ, key.data) + + self._notify_self() + + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)}) + else: + if EVENT_WRITE in key.data: + raise ValueError( + "this file descriptor is already registered for writing" + ) + + key.data[EVENT_WRITE] = loop, callback + self._selector.modify(fd, key.events | EVENT_WRITE, key.data) + + self._notify_self() + + def remove_reader(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_READ: + del key.data[EVENT_READ] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def remove_writer(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_WRITE: + del key.data[EVENT_WRITE] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def run(self) -> None: + while not self._closed: + for key, events in self._selector.select(): + if key.fileobj is self._receive: + try: + while self._receive.recv(4096): + pass + except BlockingIOError: + pass + + continue + + if events & EVENT_READ: + loop, callback = key.data[EVENT_READ] + self.remove_reader(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + if events & EVENT_WRITE: + loop, callback = key.data[EVENT_WRITE] + self.remove_writer(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + +def get_selector() -> Selector: + global _selector + + with _selector_lock: + if _selector is None: + _selector = Selector() + _selector.start() + + return _selector diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py new file mode 100644 index 0000000..f66229a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from inspect import iscoroutine + +__all__ = ("amap", "as_completed", "gather") + +from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Iterable +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING, Any, TypeVar, overload + +from ..abc._tasks import get_coro_name +from ._exceptions import BrokenResourceError +from ._streams import create_memory_object_stream +from ._tasks import TaskHandle, create_task_group + +if TYPE_CHECKING: + from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +R = TypeVar("R") +S = TypeVar("S") +T = TypeVar("T") +U = TypeVar("U") +V = TypeVar("V") +W = TypeVar("W") + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], coro2: Coroutine[Any, Any, S], / +) -> tuple[R, S]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + /, +) -> tuple[R, S, T]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + /, +) -> tuple[R, S, T, U]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + coro5: Coroutine[Any, Any, V], + /, +) -> tuple[R, S, T, U, V]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + coro5: Coroutine[Any, Any, V], + coro6: Coroutine[Any, Any, W], + /, +) -> tuple[R, S, T, U, V, W]: ... + + +# handle arbitrary length if awaitables are all of the same type +@overload +async def gather(*coros: Coroutine[Any, Any, R]) -> tuple[R, ...]: ... + + +async def gather(*coros: Coroutine[Any, Any, Any]) -> tuple[Any, ...]: + """ + Run coroutines concurrently in a task group. The order of result values corresponds + to the order of coroutines passed. + + :param coros: coroutine objects to run as tasks + :return: task results for each argument in the same order as they were passed + + """ + handles: list[TaskHandle[Any]] = [] + async with create_task_group() as tg: + handles.extend(tg.create_task(coro) for coro in coros) + + return tuple(h.return_value for h in handles) + + +@asynccontextmanager +async def as_completed( + *awaitables: Awaitable[R], +) -> AsyncGenerator[MemoryObjectReceiveStream[TaskHandle[R]]]: + """ + Run awaitable objects concurrently in a task group, returning an iterator which can + be used to get finished task handles in the order they complete. + + :param awaitables: awaitable objects to run as tasks + :return: MemoryObjectReceiveStream for iterating over task handles as tasks complete. + + """ + if not awaitables: + raise ValueError("as_completed() takes at least one awaitable") + + send, recv = create_memory_object_stream[TaskHandle[R]](len(awaitables)) + task_handles: list[TaskHandle[R]] = [] + + async def runner( + awaitable: Awaitable[R], + index: int, + _send: MemoryObjectSendStream[TaskHandle[R]], + ) -> R: + async with _send: + try: + return await awaitable + finally: + try: + _send.send_nowait(task_handles[index]) + except BrokenResourceError: + pass + + async with recv, create_task_group() as tg: + async with send: + for i, awaitable in enumerate(awaitables): + name = get_coro_name(awaitable) if iscoroutine(awaitable) else None + task_handles.append( + tg.start_soon(runner, awaitable, i, send.clone(), name=name) + ) + try: + yield recv + finally: + tg.cancel_scope.cancel() + + +async def amap( + func: Callable[[T], Coroutine[Any, Any, R]], args: Iterable[T] +) -> list[R]: + """ + Run the given coroutine function concurrently for multiple argument values. + + :param func: a coroutine function that takes a single argument + :param args: a sequence of argument values to pass to ``func`` + :return: task results for each argument in the same order as they were passed + + """ + handles: list[TaskHandle[R]] = [] + async with create_task_group() as tg: + handles.extend(tg.start_soon(func, arg) for arg in args) + + return [h.return_value for h in handles] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py new file mode 100644 index 0000000..302f32b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from abc import abstractmethod +from contextlib import AbstractAsyncContextManager, AbstractContextManager +from inspect import isasyncgen, iscoroutine, isgenerator +from types import TracebackType +from typing import Protocol, TypeVar, cast, final + +_T_co = TypeVar("_T_co", covariant=True) +_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None") + + +class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]): + def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ... + + +class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]): + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ... + + +class ContextManagerMixin: + """ + Mixin class providing context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via :meth:`__contextmanager__` + which should return a generator. The mechanics are meant to mirror those of + :func:`@contextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractContextManager[object, bool | None] | None = None + + @final + def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__contextmanager__() + if not isinstance(cm, AbstractContextManager): + if isgenerator(cm): + raise TypeError( + "__contextmanager__() returned a generator object instead of " + "a context manager. Did you forget to add the @contextmanager " + "decorator?" + ) + + raise TypeError( + f"__contextmanager__() did not return a context manager object, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__contextmanager__() returned " + f"self. Did you forget to add the @contextmanager decorator and a " + f"'yield' statement?" + ) + + value = cm.__enter__() + self.__cm = cm + return value + + @final + def __exit__( + self: _SupportsCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __contextmanager__(self) -> AbstractContextManager[object, bool | None]: + """ + Implement your context manager logic here. + + This method **must** be decorated with + :func:`@contextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: a context manager object + """ + + +class AsyncContextManagerMixin: + """ + Mixin class providing async context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via + :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of + :func:`@asynccontextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractAsyncContextManager[object, bool | None] | None = None + + @final + async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__asynccontextmanager__() + if not isinstance(cm, AbstractAsyncContextManager): + if isasyncgen(cm): + raise TypeError( + "__asynccontextmanager__() returned an async generator instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator?" + ) + elif iscoroutine(cm): + cm.close() + raise TypeError( + "__asynccontextmanager__() returned a coroutine object instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator and a 'yield' statement?" + ) + + raise TypeError( + f"__asynccontextmanager__() did not return an async context manager, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__asynccontextmanager__() returned " + f"self. Did you forget to add the @asynccontextmanager decorator and a " + f"'yield' statement?" + ) + + value = await cm.__aenter__() + self.__cm = cm + return value + + @final + async def __aexit__( + self: _SupportsAsyncCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[object, bool | None]: + """ + Implement your async context manager logic here. + + This method **must** be decorated with + :func:`@asynccontextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: an async context manager object + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py new file mode 100644 index 0000000..a3e2ab1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import math +import sys +import threading +from collections.abc import Awaitable, Callable, Generator +from contextlib import contextmanager +from contextvars import Token +from importlib import import_module +from typing import TYPE_CHECKING, Any, TypeVar + +from ._exceptions import NoEventLoopError + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +sniffio: Any +try: + import sniffio +except ModuleNotFoundError: + sniffio = None + +if TYPE_CHECKING: + from ..abc import AsyncBackend + +# This must be updated when new backends are introduced +BACKENDS = "asyncio", "trio" + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +threadlocals = threading.local() +loaded_backends: dict[str, type[AsyncBackend]] = {} + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, +) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param backend: name of the asynchronous event loop implementation – currently + either ``asyncio`` or ``trio`` + :param backend_options: keyword arguments to call the backend ``run()`` + implementation with (documented :ref:`here `) + :return: the return value of the coroutine function + :raises RuntimeError: if an asynchronous event loop is already running in this + thread + :raises LookupError: if the named backend is not found + + """ + if asynclib_name := current_async_library(): + raise RuntimeError(f"Already running {asynclib_name} in this thread") + + try: + async_backend = get_async_backend(backend) + except ImportError as exc: + if backend in BACKENDS: + raise LookupError( + f"Backend {backend!r} is not available. " + f"Install it with: pip install anyio[{backend}]" + ) from exc + + raise LookupError(f"No such backend: {backend}") from exc + + token = None + if asynclib_name is None: + # Since we're in control of the event loop, we can cache the name of the async + # library + token = set_current_async_library(backend) + + try: + backend_options = backend_options or {} + return async_backend.run(func, args, {}, backend_options) + finally: + reset_current_async_library(token) + + +async def sleep(delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + + """ + return await get_async_backend().sleep(delay) + + +async def sleep_forever() -> None: + """ + Pause the current task until it's cancelled. + + This is a shortcut for ``sleep(math.inf)``. + + .. versionadded:: 3.1 + + """ + await sleep(math.inf) + + +async def sleep_until(deadline: float) -> None: + """ + Pause the current task until the given time. + + :param deadline: the absolute time to wake up at (according to the internal + monotonic clock of the event loop) + + .. versionadded:: 3.1 + + """ + now = current_time() + await sleep(max(deadline - now, 0)) + + +def current_time() -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_time() + + +def get_all_backends() -> tuple[str, ...]: + """Return a tuple of the names of all built-in backends.""" + return BACKENDS + + +def get_available_backends() -> tuple[str, ...]: + """ + Test for the availability of built-in backends. + + :return a tuple of the built-in backend names that were successfully imported + + .. versionadded:: 4.12 + + """ + available_backends: list[str] = [] + for backend_name in get_all_backends(): + try: + get_async_backend(backend_name) + except ImportError: + continue + + available_backends.append(backend_name) + + return tuple(available_backends) + + +def get_cancelled_exc_class() -> type[BaseException]: + """ + Return the current async library's cancellation exception class. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().cancelled_exception_class() + + +# +# Private API +# + + +@contextmanager +def claim_worker_thread( + backend_class: type[AsyncBackend], token: object +) -> Generator[Any, None, None]: + from ..lowlevel import EventLoopToken + + threadlocals.current_token = EventLoopToken(backend_class, token) + try: + yield + finally: + del threadlocals.current_token + + +def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]: + if asynclib_name is None: + asynclib_name = current_async_library() + if not asynclib_name: + raise NoEventLoopError( + f"Not currently running on any asynchronous event loop. " + f"Available async backends: {', '.join(get_all_backends())}" + ) + + # We use our own dict instead of sys.modules to get the already imported back-end + # class because the appropriate modules in sys.modules could potentially be only + # partially initialized + try: + return loaded_backends[asynclib_name] + except KeyError: + module = import_module(f"anyio._backends._{asynclib_name}") + loaded_backends[asynclib_name] = module.backend_class + return module.backend_class + + +def current_async_library() -> str | None: + if sniffio is None: + # If sniffio is not installed, we assume we're either running asyncio or nothing + import asyncio + + try: + asyncio.get_running_loop() + return "asyncio" + except RuntimeError: + pass + else: + try: + return sniffio.current_async_library() + except sniffio.AsyncLibraryNotFoundError: + pass + + return None + + +def set_current_async_library(asynclib_name: str | None) -> Token | None: + # no-op if sniffio is not installed + if sniffio is None: + return None + + return sniffio.current_async_library_cvar.set(asynclib_name) + + +def reset_current_async_library(token: Token | None) -> None: + if token is not None: + sniffio.current_async_library_cvar.reset(token) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py new file mode 100644 index 0000000..7d9165f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py @@ -0,0 +1,205 @@ +from __future__ import annotations + +import sys +from collections.abc import Generator +from textwrap import dedent +from typing import Any + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + +class BrokenResourceError(Exception): + """ + Raised when trying to use a resource that has been rendered unusable due to external + causes (e.g. a send stream whose peer has disconnected). + """ + + +class BrokenWorkerProcess(Exception): + """ + Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or + otherwise misbehaves. + """ + + +class BrokenWorkerInterpreter(Exception): + """ + Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is + raised in the subinterpreter. + """ + + def __init__(self, excinfo: Any): + # This was adapted from concurrent.futures.interpreter.ExecutionFailed + msg = excinfo.formatted + if not msg: + if excinfo.type and excinfo.msg: + msg = f"{excinfo.type.__name__}: {excinfo.msg}" + else: + msg = excinfo.type.__name__ or excinfo.msg + + super().__init__(msg) + self.excinfo = excinfo + + def __str__(self) -> str: + try: + formatted = self.excinfo.errdisplay + except Exception: + return super().__str__() + else: + return dedent( + f""" + {super().__str__()} + + Uncaught in the interpreter: + + {formatted} + """.strip() + ) + + +class BusyResourceError(Exception): + """ + Raised when two tasks are trying to read from or write to the same resource + concurrently. + """ + + def __init__(self, action: str): + super().__init__(f"Another task is already {action} this resource") + + +class ClosedResourceError(Exception): + """Raised when trying to use a resource that has been closed.""" + + +class ConnectionFailed(OSError): + """ + Raised when a connection attempt fails. + + .. note:: This class inherits from :exc:`OSError` for backwards compatibility. + """ + + +def iterate_exceptions( + exception: BaseException, +) -> Generator[BaseException, None, None]: + if isinstance(exception, BaseExceptionGroup): + for exc in exception.exceptions: + yield from iterate_exceptions(exc) + else: + yield exception + + +class DelimiterNotFound(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + maximum number of bytes has been read without the delimiter being found. + """ + + def __init__(self, max_bytes: int) -> None: + super().__init__( + f"The delimiter was not found among the first {max_bytes} bytes" + ) + + +class EndOfStream(Exception): + """ + Raised when trying to read from a stream that has been closed from the other end. + """ + + +class IncompleteRead(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + connection is closed before the requested amount of bytes has been read. + """ + + def __init__(self) -> None: + super().__init__( + "The stream was closed before the read operation could be completed" + ) + + +class TypedAttributeLookupError(LookupError): + """ + Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute + is not found and no default value has been given. + """ + + +class WouldBlock(Exception): + """Raised by ``X_nowait`` functions if ``X()`` would block.""" + + +class NoEventLoopError(RuntimeError): + """ + Raised by several functions that require an event loop to be running in the current + thread when there is no running event loop. + + This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` + if not calling from an AnyIO worker thread, and no ``token`` was passed. + """ + + +class RunFinishedError(RuntimeError): + """ + Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event + loop associated with the explicitly passed token has already finished. + """ + + def __init__(self) -> None: + super().__init__( + "The event loop associated with the given token has already finished" + ) + + +class TaskFailed(Exception): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.TaskHandle` that raised an exception. + """ + + +class TaskCancelled(TaskFailed): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.TaskHandle` that was cancelled. + """ + + +class TaskNotFinished(Exception): + """ + Raised when attempting to access the return value or exception of a + :class:`.TaskHandle` that is still pending completion. + """ + + +class FutureFailed(Exception): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.Future` that raised an exception. + """ + + +class FutureCancelled(FutureFailed): + """ + Raised when attempting to access the return value or exception of a + :class:`.Future` that was cancelled. + """ + + +class FutureNotFinished(Exception): + """ + Raised when attempting to access the return value or exception of a + :class:`.Future` that is still pending completion. + """ + + +class FutureAlreadyFinished(Exception): + """ + Raised when attempting set a result of or await a + :class:`.Future` that has already completed. + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py new file mode 100644 index 0000000..498eef4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py @@ -0,0 +1,1045 @@ +from __future__ import annotations + +import os +import pathlib +import sys +from collections.abc import ( + AsyncIterator, + Callable, + Iterable, + Iterator, + Sequence, +) +from dataclasses import dataclass +from functools import partial +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + AnyStr, + ClassVar, + Final, + Generic, + TypeVar, + overload, +) + +from .. import to_thread +from ..abc import AsyncResource +from ._synchronization import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +if sys.version_info >= (3, 14): + from pathlib.types import PathInfo + +if TYPE_CHECKING: + from types import ModuleType + + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer +else: + ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object + + +T = TypeVar("T", bound="Path") + + +class AsyncFile(AsyncResource, Generic[AnyStr]): + """ + An asynchronous file object. + + This class wraps a standard file object and provides async friendly versions of the + following blocking methods (where available on the original file object): + + * read + * read1 + * readline + * readlines + * readinto + * readinto1 + * write + * writelines + * truncate + * seek + * tell + * flush + + All other methods are directly passed through. + + This class supports the asynchronous context manager protocol which closes the + underlying file at the end of the context block. + + This class also supports asynchronous iteration:: + + async with await open_file(...) as f: + async for line in f: + print(line) + """ + + def __init__( + self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None + ) -> None: + if limiter is not None and not isinstance(limiter, CapacityLimiter): + raise TypeError( + f"limiter must be a CapacityLimiter or None, not " + f"{limiter.__class__.__name__}" + ) + + self._fp: Any = fp + self._limiter = limiter + + def __getattr__(self, name: str) -> object: + return getattr(self._fp, name) + + @property + def limiter(self) -> CapacityLimiter | None: + """The capacity limiter used by this file object, if not the global limiter.""" + return self._limiter + + @property + def wrapped(self) -> IO[AnyStr]: + """The wrapped file object.""" + return self._fp + + async def __aiter__(self) -> AsyncIterator[AnyStr]: + while True: + line = await self.readline() + if line: + yield line + else: + break + + async def aclose(self) -> None: + return await to_thread.run_sync(self._fp.close, limiter=self._limiter) + + async def read(self, size: int = -1) -> AnyStr: + return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter) + + async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes: + return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter) + + async def readline(self) -> AnyStr: + return await to_thread.run_sync(self._fp.readline, limiter=self._limiter) + + async def readlines(self) -> list[AnyStr]: + return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter) + + async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter) + + async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter) + + @overload + async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... + + @overload + async def write(self: AsyncFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter) + + @overload + async def writelines( + self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + + @overload + async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... + + async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None: + return await to_thread.run_sync( + self._fp.writelines, lines, limiter=self._limiter + ) + + async def truncate(self, size: int | None = None) -> int: + return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + return await to_thread.run_sync( + self._fp.seek, offset, whence, limiter=self._limiter + ) + + async def tell(self) -> int: + return await to_thread.run_sync(self._fp.tell, limiter=self._limiter) + + async def flush(self) -> None: + return await to_thread.run_sync(self._fp.flush, limiter=self._limiter) + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., + *, + limiter: CapacityLimiter | None = ..., +) -> AsyncFile[bytes]: ... + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., + *, + limiter: CapacityLimiter | None = ..., +) -> AsyncFile[str]: ... + + +async def open_file( + file: str | PathLike[str] | int, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + closefd: bool = True, + opener: Callable[[str, int], int] | None = None, + *, + limiter: CapacityLimiter | None = None, +) -> AsyncFile[Any]: + """ + Open a file asynchronously. + + Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`. + + :param limiter: an optional capacity limiter to use with the file + instead of the default one + :return: an asynchronous file object + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + + """ + fp = await to_thread.run_sync( + open, + file, + mode, + buffering, + encoding, + errors, + newline, + closefd, + opener, + limiter=limiter, + ) + return AsyncFile(fp, limiter=limiter) + + +def wrap_file( + file: IO[AnyStr], *, limiter: CapacityLimiter | None = None +) -> AsyncFile[AnyStr]: + """ + Wrap an existing file as an asynchronous file. + + :param file: an existing file-like object + :param limiter: an optional capacity limiter to use with the file + instead of the default one + :return: an asynchronous file object + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + + """ + return AsyncFile(file, limiter=limiter) + + +@dataclass(eq=False) +class _PathIterator(AsyncIterator[T]): + iterator: Iterator[PathLike[str]] + limiter: CapacityLimiter | None + # This was added to ensure that iterating over a subclass of Path yields instances + # of that subclass rather than the base Path class. + path_cls: type[T] + + async def __anext__(self) -> T: + nextval = await to_thread.run_sync( + next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter + ) + if nextval is None: + raise StopAsyncIteration from None + + return self.path_cls(nextval, limiter=self.limiter) + + +class Path: + """ + An asynchronous version of :class:`pathlib.Path`. + + This class cannot be substituted for :class:`pathlib.Path` or + :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike` + interface. + + It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for + the deprecated :meth:`~pathlib.Path.link_to` method. + + Some methods may be unavailable or have limited functionality, based on the Python + version: + + * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later) + * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later) + * :attr:`~pathlib.Path.info` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later) + * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only + available on Python 3.13 or later) + * :meth:`~pathlib.Path.move` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later) + * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available + on Python 3.12 or later) + * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later) + + Any methods that do disk I/O need to be awaited on. These methods are: + + * :meth:`~pathlib.Path.absolute` + * :meth:`~pathlib.Path.chmod` + * :meth:`~pathlib.Path.cwd` + * :meth:`~pathlib.Path.exists` + * :meth:`~pathlib.Path.expanduser` + * :meth:`~pathlib.Path.group` + * :meth:`~pathlib.Path.hardlink_to` + * :meth:`~pathlib.Path.home` + * :meth:`~pathlib.Path.is_block_device` + * :meth:`~pathlib.Path.is_char_device` + * :meth:`~pathlib.Path.is_dir` + * :meth:`~pathlib.Path.is_fifo` + * :meth:`~pathlib.Path.is_file` + * :meth:`~pathlib.Path.is_junction` + * :meth:`~pathlib.Path.is_mount` + * :meth:`~pathlib.Path.is_socket` + * :meth:`~pathlib.Path.is_symlink` + * :meth:`~pathlib.Path.lchmod` + * :meth:`~pathlib.Path.lstat` + * :meth:`~pathlib.Path.mkdir` + * :meth:`~pathlib.Path.open` + * :meth:`~pathlib.Path.owner` + * :meth:`~pathlib.Path.read_bytes` + * :meth:`~pathlib.Path.read_text` + * :meth:`~pathlib.Path.readlink` + * :meth:`~pathlib.Path.rename` + * :meth:`~pathlib.Path.replace` + * :meth:`~pathlib.Path.resolve` + * :meth:`~pathlib.Path.rmdir` + * :meth:`~pathlib.Path.samefile` + * :meth:`~pathlib.Path.stat` + * :meth:`~pathlib.Path.symlink_to` + * :meth:`~pathlib.Path.touch` + * :meth:`~pathlib.Path.unlink` + * :meth:`~pathlib.Path.walk` + * :meth:`~pathlib.Path.write_bytes` + * :meth:`~pathlib.Path.write_text` + + Additionally, the following methods return an async iterator yielding + :class:`~.Path` objects: + + * :meth:`~pathlib.Path.glob` + * :meth:`~pathlib.Path.iterdir` + * :meth:`~pathlib.Path.rglob` + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + """ + + __slots__ = "__weakref__", "_limiter", "_path" + + __weakref__: Any + + def __init__( + self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None + ) -> None: + if limiter is not None and not isinstance(limiter, CapacityLimiter): + raise TypeError( + f"limiter must be a CapacityLimiter or None, not " + f"{limiter.__class__.__name__}" + ) + + self._path: Final[pathlib.Path] = pathlib.Path(*args) + self._limiter = limiter + + def __fspath__(self) -> str: + return self._path.__fspath__() + + if sys.version_info >= (3, 15): + + def __vfspath__(self) -> str: + return self._path.__vfspath__() + + def __str__(self) -> str: + return self._path.__str__() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.as_posix()!r})" + + def __bytes__(self) -> bytes: + return self._path.__bytes__() + + def __hash__(self) -> int: + return self._path.__hash__() + + def __eq__(self, other: object) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__eq__(target) + + def __lt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__lt__(target) + + def __le__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__le__(target) + + def __gt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__gt__(target) + + def __ge__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__ge__(target) + + def __truediv__(self, other: str | PathLike[str]) -> Self: + return type(self)(self._path / other, limiter=self._limiter) + + def __rtruediv__(self, other: str | PathLike[str]) -> Self: + return type(self)(other, limiter=self._limiter) / self + + @property + def limiter(self) -> CapacityLimiter | None: + """The capacity limiter used by this path, if not the global limiter.""" + return self._limiter + + @property + def parts(self) -> tuple[str, ...]: + return self._path.parts + + @property + def drive(self) -> str: + return self._path.drive + + @property + def root(self) -> str: + return self._path.root + + @property + def anchor(self) -> str: + return self._path.anchor + + @property + def parents(self) -> Sequence[Self]: + return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents) + + @property + def parent(self) -> Self: + return type(self)(self._path.parent, limiter=self._limiter) + + @property + def name(self) -> str: + return self._path.name + + @property + def suffix(self) -> str: + return self._path.suffix + + @property + def suffixes(self) -> list[str]: + return self._path.suffixes + + @property + def stem(self) -> str: + return self._path.stem + + async def absolute(self) -> Self: + path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter) + return type(self)(path, limiter=self._limiter) + + def as_posix(self) -> str: + return self._path.as_posix() + + def as_uri(self) -> str: + return self._path.as_uri() + + if sys.version_info >= (3, 13): + parser: ClassVar[ModuleType] = pathlib.Path.parser + + @classmethod + def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self: + return cls(pathlib.Path.from_uri(uri), limiter=limiter) + + def full_match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.full_match(path_pattern, case_sensitive=case_sensitive) + + def match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.match(path_pattern, case_sensitive=case_sensitive) + else: + + def match(self, path_pattern: str) -> bool: + return self._path.match(path_pattern) + + if sys.version_info >= (3, 14): + + @property + def info(self) -> PathInfo: + return self._path.info + + async def copy( + self, + target: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Self: + func = partial( + self._path.copy, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return type(self)( + await to_thread.run_sync( + func, pathlib.Path(target), limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def copy_into( + self, + target_dir: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Self: + func = partial( + self._path.copy_into, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return type(self)( + await to_thread.run_sync( + func, pathlib.Path(target_dir), limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def move(self, target: str | os.PathLike[str]) -> Self: + # Upstream does not handle anyio.Path properly as a PathLike + target = pathlib.Path(target) + return type(self)( + await to_thread.run_sync( + self._path.move, target, limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def move_into( + self, + target_dir: str | os.PathLike[str], + ) -> Self: + return type(self)( + await to_thread.run_sync( + self._path.move_into, target_dir, limiter=self._limiter + ), + limiter=self._limiter, + ) + + def is_relative_to(self, other: str | PathLike[str]) -> bool: + try: + self.relative_to(other) + return True + except ValueError: + return False + + async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: + func = partial(os.chmod, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter) + + @classmethod + async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self: + path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter) + return cls(path, limiter=limiter) + + if sys.version_info >= (3, 12): + + async def exists(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.exists, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def exists(self) -> bool: + return await to_thread.run_sync( + self._path.exists, abandon_on_cancel=True, limiter=self._limiter + ) + + async def expanduser(self) -> Self: + return type(self)( + await to_thread.run_sync( + self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter + ), + limiter=self._limiter, + ) + + if sys.version_info < (3, 12): + # Python 3.11 and earlier + def glob(self, pattern: str) -> AsyncIterator[Self]: + gen = self._path.glob(pattern) + return _PathIterator(gen, self._limiter, type(self)) + elif (3, 12) <= sys.version_info < (3, 13): + # changed in Python 3.12: + # - The case_sensitive parameter was added. + def glob( + self, + pattern: str, + *, + case_sensitive: bool | None = None, + ) -> AsyncIterator[Self]: + gen = self._path.glob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen, self._limiter, type(self)) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Self]: + gen = self._path.glob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen, self._limiter, type(self)) + + if sys.version_info >= (3, 13): + + async def group(self, *, follow_symlinks: bool = True) -> str: + return await to_thread.run_sync( + partial(self._path.group, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def group(self) -> str: + return await to_thread.run_sync( + self._path.group, abandon_on_cancel=True, limiter=self._limiter + ) + + async def hardlink_to( + self, target: str | bytes | PathLike[str] | PathLike[bytes] + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(os.link, target, self, limiter=self._limiter) + + @classmethod + async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self: + home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter) + return cls(home_path, limiter=limiter) + + def is_absolute(self) -> bool: + return self._path.is_absolute() + + async def is_block_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_char_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 13): + + async def is_dir(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.is_dir, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def is_dir(self) -> bool: + return await to_thread.run_sync( + self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_fifo(self) -> bool: + return await to_thread.run_sync( + self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 13): + + async def is_file(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.is_file, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def is_file(self) -> bool: + return await to_thread.run_sync( + self._path.is_file, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 12): + + async def is_junction(self) -> bool: + return await to_thread.run_sync( + self._path.is_junction, limiter=self._limiter + ) + + async def is_mount(self) -> bool: + return await to_thread.run_sync( + os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info < (3, 15): + + def is_reserved(self) -> bool: + return self._path.is_reserved() + + async def is_socket(self) -> bool: + return await to_thread.run_sync( + self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_symlink(self) -> bool: + return await to_thread.run_sync( + self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter + ) + + async def iterdir(self) -> AsyncIterator[Self]: + gen = ( + self._path.iterdir() + if sys.version_info < (3, 13) + else await to_thread.run_sync( + self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter + ) + ) + async for path in _PathIterator(gen, self._limiter, type(self)): + yield path + + def joinpath(self, *args: str | PathLike[str]) -> Self: + return type(self)(self._path.joinpath(*args), limiter=self._limiter) + + async def lchmod(self, mode: int) -> None: + await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter) + + async def lstat(self) -> os.stat_result: + return await to_thread.run_sync( + self._path.lstat, abandon_on_cancel=True, limiter=self._limiter + ) + + async def mkdir( + self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False + ) -> None: + await to_thread.run_sync( + self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter + ) + + @overload + async def open( + self, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[bytes]: ... + + @overload + async def open( + self, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[str]: ... + + async def open( + self, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> AsyncFile[Any]: + fp = await to_thread.run_sync( + self._path.open, + mode, + buffering, + encoding, + errors, + newline, + limiter=self._limiter, + ) + return AsyncFile(fp, limiter=self._limiter) + + if sys.version_info >= (3, 13): + + async def owner(self, *, follow_symlinks: bool = True) -> str: + return await to_thread.run_sync( + partial(self._path.owner, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def owner(self) -> str: + return await to_thread.run_sync( + self._path.owner, abandon_on_cancel=True, limiter=self._limiter + ) + + async def read_bytes(self) -> bytes: + return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter) + + if sys.version_info >= (3, 13): + + async def read_text( + self, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> str: + return await to_thread.run_sync( + self._path.read_text, + encoding, + errors, + newline, + limiter=self._limiter, + ) + + else: + + async def read_text( + self, encoding: str | None = None, errors: str | None = None + ) -> str: + return await to_thread.run_sync( + self._path.read_text, encoding, errors, limiter=self._limiter + ) + + if sys.version_info >= (3, 12): + + def relative_to( + self, *other: str | PathLike[str], walk_up: bool = False + ) -> Self: + # relative_to() should work with any PathLike but it doesn't + others = [pathlib.Path(other) for other in other] + return type(self)( + self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter + ) + + else: + + def relative_to(self, *other: str | PathLike[str]) -> Self: + return type(self)(self._path.relative_to(*other), limiter=self._limiter) + + async def readlink(self) -> Self: + target = await to_thread.run_sync( + os.readlink, self._path, limiter=self._limiter + ) + return type(self)(target, limiter=self._limiter) + + async def rename(self, target: str | pathlib.PurePath | Path) -> Self: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.rename, target, limiter=self._limiter) + return type(self)(target, limiter=self._limiter) + + async def replace(self, target: str | pathlib.PurePath | Path) -> Self: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.replace, target, limiter=self._limiter) + return type(self)(target, limiter=self._limiter) + + async def resolve(self, strict: bool = False) -> Self: + func = partial(self._path.resolve, strict=strict) + return type(self)( + await to_thread.run_sync( + func, abandon_on_cancel=True, limiter=self._limiter + ), + limiter=self._limiter, + ) + + if sys.version_info < (3, 12): + # Pre Python 3.12 + def rglob(self, pattern: str) -> AsyncIterator[Self]: + gen = self._path.rglob(pattern) + return _PathIterator(gen, self._limiter, type(self)) + elif (3, 12) <= sys.version_info < (3, 13): + # Changed in Python 3.12: + # - The case_sensitive parameter was added. + def rglob( + self, pattern: str, *, case_sensitive: bool | None = None + ) -> AsyncIterator[Self]: + gen = self._path.rglob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen, self._limiter, type(self)) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Self]: + gen = self._path.rglob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen, self._limiter, type(self)) + + async def rmdir(self) -> None: + await to_thread.run_sync(self._path.rmdir, limiter=self._limiter) + + async def samefile(self, other_path: str | PathLike[str]) -> bool: + if isinstance(other_path, Path): + other_path = other_path._path + + return await to_thread.run_sync( + self._path.samefile, + other_path, + abandon_on_cancel=True, + limiter=self._limiter, + ) + + async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result: + func = partial(os.stat, follow_symlinks=follow_symlinks) + return await to_thread.run_sync( + func, self._path, abandon_on_cancel=True, limiter=self._limiter + ) + + async def symlink_to( + self, + target: str | bytes | PathLike[str] | PathLike[bytes], + target_is_directory: bool = False, + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync( + self._path.symlink_to, target, target_is_directory, limiter=self._limiter + ) + + async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: + await to_thread.run_sync( + self._path.touch, mode, exist_ok, limiter=self._limiter + ) + + async def unlink(self, missing_ok: bool = False) -> None: + try: + await to_thread.run_sync(self._path.unlink, limiter=self._limiter) + except FileNotFoundError: + if not missing_ok: + raise + + if sys.version_info >= (3, 12): + + async def walk( + self, + top_down: bool = True, + on_error: Callable[[OSError], object] | None = None, + follow_symlinks: bool = False, + ) -> AsyncIterator[tuple[Self, list[str], list[str]]]: + def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None: + try: + return next(gen) + except StopIteration: + return None + + gen = self._path.walk(top_down, on_error, follow_symlinks) + while True: + value = await to_thread.run_sync(get_next_value, limiter=self._limiter) + if value is None: + return + + root, dirs, paths = value + yield type(self)(root, limiter=self._limiter), dirs, paths + + def with_name(self, name: str) -> Self: + return type(self)(self._path.with_name(name), limiter=self._limiter) + + if sys.version_info < (3, 13): + # Backport pathlib's Python>=3.13 behavior for empty stems on paths with non-empty suffixes. + # See: https://github.com/python/cpython/pull/114612 + def with_stem(self, stem: str) -> Self: + suffix = self._path.suffix + if not suffix: + return self.with_name(stem) + elif not stem: + # If the suffix is non-empty, we can't make the stem empty. + raise ValueError(f"{self!r} has a non-empty suffix") + else: + return self.with_name(stem + suffix) + else: + + def with_stem(self, stem: str) -> Self: + return type(self)(self._path.with_stem(stem), limiter=self._limiter) + + def with_suffix(self, suffix: str) -> Self: + return type(self)(self._path.with_suffix(suffix), limiter=self._limiter) + + def with_segments(self, *pathsegments: str | PathLike[str]) -> Self: + return type(self)(*pathsegments, limiter=self._limiter) + + async def write_bytes(self, data: ReadableBuffer) -> int: + return await to_thread.run_sync( + self._path.write_bytes, data, limiter=self._limiter + ) + + async def write_text( + self, + data: str, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> int: + return await to_thread.run_sync( + self._path.write_text, + data, + encoding, + errors, + newline, + limiter=self._limiter, + ) + + +PathLike.register(Path) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py new file mode 100644 index 0000000..7730c53 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +from collections.abc import Generator +from enum import Enum, auto +from typing import Any, Generic, TypeVar + +from ._exceptions import ( + FutureAlreadyFinished, + FutureCancelled, + FutureFailed, + FutureNotFinished, +) +from ._synchronization import Event + +T = TypeVar("T") + + +class Future(Generic[T]): + """ + An awaitable object that works similar to a :class:`asyncio.Future` but + with similar characteristics to a :class:`.TaskHandle`. + """ + + class Status(Enum): + """ + The status of a future handle. + + .. attribute:: PENDING + + The future has not finished yet. + .. attribute:: FINISHED + + The future has finished with a return value. + .. attribute:: CANCELLED + + The future was cancelled and has finished since. + .. attribute:: FAILED + + The future raised an exception. + """ + + PENDING = auto() + FINISHED = auto() + CANCELLED = auto() + FAILED = auto() + + __slots__ = ( + "_cancelled", + "_exception", + "_finished_event", + "_name", + "_return_value", + ) + _return_value: T + + def __init__(self, *, name: str | None = None) -> None: + self._finished_event = Event() + self._exception: BaseException | None = None + self._cancelled: bool = False + self._name = name + + def _check_pending(self) -> None: + """Shortcut for checking if a Future is pending + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + match self.status: + case Future.Status.PENDING: + return + case Future.Status.FINISHED: + raise FutureAlreadyFinished("future has already finished") + case Future.Status.FAILED: + raise FutureAlreadyFinished("future already failed") + case Future.Status.CANCELLED: + raise FutureCancelled("future was cancelled") + + async def wait(self) -> None: + """ + Waits for the future to finish. + + This method will attempt to wait for a result or exception + """ + await self._finished_event.wait() + + def cancel(self) -> None: + """Cancels a pending `.Future` object + + Does nothing if the Future was already finished. + """ + if self.status is Future.Status.PENDING: + self._cancelled = True + self._finished_event.set() + + @property + def exception(self) -> BaseException | None: + """ + The exception value of a `.Future` + + :raises FutureNotFinished: if future is still pending + :raises FutureCancelled: if future was cancelled + :returns: None if future succeeds with a result sent instead + otherwise this will be an exception + """ + + match self.status: + case Future.Status.PENDING: + raise FutureNotFinished("the future has not finished yet") + case Future.Status.FINISHED: + return None + case Future.Status.CANCELLED: + raise FutureCancelled("the future was cancelled") + case Future.Status.FAILED: + return self._exception + + @exception.setter + def exception(self, exception: BaseException) -> None: + """Send exception for a `.Future` object + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + self._check_pending() + self._exception = exception + self._finished_event.set() + + @property + def return_value(self) -> T: + """ + The return value of the future. + + :raises FutureNotFinished: if the future has not finished yet + :raises FutureCancelled: if the future was cancelled + :raises FutureFailed: if the future raised an exception + + """ + match self.status: + case Future.Status.PENDING: + raise FutureNotFinished("the future has not finished yet") + case Future.Status.FINISHED: + return self._return_value + case Future.Status.CANCELLED: + raise FutureCancelled("the future was cancelled") + case Future.Status.FAILED: + raise FutureFailed( + "the future raised an exception" + ) from self._exception + + @return_value.setter + def return_value(self, value: T) -> None: + """ + Send pending result for a `.Future` object + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + self._check_pending() + self._return_value = value + self._finished_event.set() + + @property + def status(self) -> Future.Status: + """ + The current status of a future. + + Every future starts in the :attr:`~Future.Status.PENDING` state. + If a future is cancelled while in this state, it will transition to the + :attr:`Future.Status.CANCELLED` state. When the task finishes, it will + transition to one of the three final states ( + :attr:`Future.Status.FINISHED`, :attr:`Future.Status.FAILED`, or + :attr:`Future.Status.CANCELLED`) depending on the exception the task + raised, if any. No other status transitions will happen. + """ + if not self._finished_event.is_set(): + return Future.Status.PENDING + elif self._cancelled: + return Future.Status.CANCELLED + elif self._exception is not None: + return Future.Status.FAILED + else: + return Future.Status.FINISHED + + def __await__(self) -> Generator[Any, Any, T]: + yield from self.wait().__await__() + return self.return_value + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} {self.status.name.lower()} " + f"name={self._name!r}>" + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py new file mode 100644 index 0000000..6ca35d9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from ..abc import AsyncResource # noqa: TC001 +from ._tasks import CancelScope + + +async def aclose_forcefully(resource: AsyncResource) -> None: + """ + Close an asynchronous resource in a cancelled scope. + + Doing this closes the resource without waiting on anything. + + :param resource: the resource to close + + """ + with CancelScope() as scope: + scope.cancel() + await resource.aclose() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py new file mode 100644 index 0000000..e24c79e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import AbstractContextManager +from signal import Signals + +from ._eventloop import get_async_backend + + +def open_signal_receiver( + *signals: Signals, +) -> AbstractContextManager[AsyncIterator[Signals]]: + """ + Start receiving operating system signals. + + :param signals: signals to receive (e.g. ``signal.SIGINT``) + :return: an asynchronous context manager for an asynchronous iterator which yields + signal numbers + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. warning:: Windows does not support signals natively so it is best to avoid + relying on this in cross-platform applications. + + .. warning:: On asyncio, this permanently replaces any previous signal handler for + the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`. + + """ + return get_async_backend().open_signal_receiver(*signals) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py new file mode 100644 index 0000000..14ee6e4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py @@ -0,0 +1,1011 @@ +from __future__ import annotations + +import errno +import os +import socket +import ssl +import stat +import sys +from collections.abc import Awaitable +from dataclasses import dataclass +from ipaddress import IPv4Address, IPv6Address, ip_address +from os import PathLike, chmod +from socket import AddressFamily, SocketKind +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +from .. import ConnectionFailed, to_thread +from ..abc import ( + ByteStreamConnectable, + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPAddressType, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, +) +from ..streams.stapled import MultiListener +from ..streams.tls import TLSConnectable, TLSStream +from ._eventloop import get_async_backend +from ._resources import aclose_forcefully +from ._synchronization import Event +from ._tasks import create_task_group, move_on_after + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike +else: + FileDescriptorLike = object + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +if sys.version_info < (3, 13): + from typing_extensions import deprecated +else: + from warnings import deprecated + +IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515 + +AnyIPAddressFamily = Literal[ + AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6 +] +IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6] + + +def idna2008_resolve(host: str) -> bytes: + try: + return host.encode("ascii") + except UnicodeEncodeError: + import idna + + return idna.encode(host, uts46=True) + + +# tls_hostname given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str, + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# ssl_context given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + ssl_context: ssl.SSLContext, + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=True +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + tls: Literal[True], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=False +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + tls: Literal[False], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +# No TLS arguments +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = None, + local_port: int | None = None, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_standard_compatible: bool = True, + tls_hostname: str | None = None, + happy_eyeballs_delay: float = 0.25, +) -> SocketStream | TLSStream: + """ + Connect to a host using the TCP protocol. + + This function implements the stateless version of the Happy Eyeballs algorithm (RFC + 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses, + each one is tried until one connection attempt succeeds. If the first attempt does + not connected within 250 milliseconds, a second attempt is started using the next + address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if + available) is tried first. + + When the connection has been established, a TLS handshake will be done if either + ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``. + + :param remote_host: the IP address or host name to connect to + :param remote_port: port on the target host to connect to + :param local_host: the interface address or name to bind the socket to before + connecting + :param local_port: the local port to bind to (requires ``local_host`` to also be + set) + :param tls: ``True`` to do a TLS handshake with the connected stream and return a + :class:`~anyio.streams.tls.TLSStream` instead + :param ssl_context: the SSL context object to use (if omitted, a default context is + created) + :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake + before closing the stream and requires that the server does this as well. + Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream. + Some protocols, such as HTTP, require this option to be ``False``. + See :meth:`~ssl.SSLContext.wrap_socket` for details. + :param tls_hostname: host name to check the server certificate against (defaults to + the value of ``remote_host``) + :param happy_eyeballs_delay: delay (in seconds) before starting the next connection + attempt + :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream + :raises ConnectionFailed: if the connection fails + + """ + # Placed here due to https://github.com/python/mypy/issues/7057 + connected_stream: SocketStream | None = None + + async def try_connect(remote_host: str, event: Event) -> None: + nonlocal connected_stream + try: + stream = await asynclib.connect_tcp(remote_host, remote_port, local_address) + except OSError as exc: + oserrors.append(exc) + return + else: + if connected_stream is None: + connected_stream = stream + tg.cancel_scope.cancel() + else: + await stream.aclose() + finally: + event.set() + + asynclib = get_async_backend() + local_address: IPSockAddrType | None = None + family = socket.AF_UNSPEC + if local_host: + gai_res = await getaddrinfo(str(local_host), local_port) + family, *_, local_address = gai_res[0] + + target_host = str(remote_host) + try: + addr_obj = ip_address(remote_host) + except ValueError: + addr_obj = None + + if addr_obj is not None: + if isinstance(addr_obj, IPv6Address): + target_addrs = [(socket.AF_INET6, addr_obj.compressed)] + else: + target_addrs = [(socket.AF_INET, addr_obj.compressed)] + else: + # getaddrinfo() will raise an exception if name resolution fails + gai_res = await getaddrinfo( + target_host, remote_port, family=family, type=socket.SOCK_STREAM + ) + + # Organize the list so that the first address is an IPv6 address (if available) + # and the second one is an IPv4 addresses. The rest can be in whatever order. + v6_found = v4_found = False + target_addrs = [] + for af, *_, sa in gai_res: + if af == socket.AF_INET6 and not v6_found: + v6_found = True + target_addrs.insert(0, (af, sa[0])) + elif af == socket.AF_INET and not v4_found and v6_found: + v4_found = True + target_addrs.insert(1, (af, sa[0])) + else: + target_addrs.append((af, sa[0])) + + oserrors: list[OSError] = [] + try: + async with create_task_group() as tg: + for _af, addr in target_addrs: + event = Event() + tg.start_soon(try_connect, addr, event) + with move_on_after(happy_eyeballs_delay): + await event.wait() + + if connected_stream is None: + cause = ( + oserrors[0] + if len(oserrors) == 1 + else ExceptionGroup("multiple connection attempts failed", oserrors) + ) + raise OSError("All connection attempts failed") from cause + finally: + oserrors.clear() + + if tls or tls_hostname or ssl_context: + try: + return await TLSStream.wrap( + connected_stream, + server_side=False, + hostname=tls_hostname or str(remote_host), + ssl_context=ssl_context, + standard_compatible=tls_standard_compatible, + ) + except BaseException: + await aclose_forcefully(connected_stream) + raise + + return connected_stream + + +async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream: + """ + Connect to the given UNIX socket. + + Not available on Windows. + + :param path: path to the socket + :return: a socket stream object + :raises ConnectionFailed: if the connection fails + + """ + path = os.fspath(path) + return await get_async_backend().connect_unix(path) + + +async def create_tcp_listener( + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC, + backlog: int = 65536, + reuse_port: bool = False, +) -> MultiListener[SocketStream]: + """ + Create a TCP socket listener. + + :param local_port: port number to listen on + :param local_host: IP address of the interface to listen on. If omitted, listen on + all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address + family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6. + :param family: address family (used if ``local_host`` was omitted) + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a multi-listener object containing one or more socket listeners + :raises OSError: if there's an error creating a socket, or binding to one or more + interfaces failed + + """ + asynclib = get_async_backend() + backlog = min(backlog, 65536) + local_host = str(local_host) if local_host is not None else None + + def setup_raw_socket( + fam: AddressFamily, + bind_addr: tuple[str, int] | tuple[str, int, int, int], + *, + v6only: bool = True, + ) -> socket.socket: + sock = socket.socket(fam) + try: + sock.setblocking(False) + + if fam == AddressFamily.AF_INET6: + sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only) + + # For Windows, enable exclusive address use. For others, enable address + # reuse. + if sys.platform == "win32": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + else: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if reuse_port: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + # Workaround for #554 + if fam == socket.AF_INET6 and "%" in bind_addr[0]: + addr, scope_id = bind_addr[0].split("%", 1) + bind_addr = (addr, bind_addr[1], 0, int(scope_id)) + + sock.bind(bind_addr) + sock.listen(backlog) + except BaseException: + sock.close() + raise + + return sock + + # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug + # where we don't get the correct scope ID for IPv6 link-local addresses when passing + # type=socket.SOCK_STREAM to getaddrinfo(): + # https://github.com/MagicStack/uvloop/issues/539 + gai_res = await getaddrinfo( + local_host, + local_port, + family=family, + type=socket.SOCK_STREAM if sys.platform == "win32" else 0, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + + # The set comprehension is here to work around a glibc bug: + # https://sourceware.org/bugzilla/show_bug.cgi?id=14969 + sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM}) + + # Special case for dual-stack binding on the "any" interface + if ( + local_host is None + and family == AddressFamily.AF_UNSPEC + and socket.has_dualstack_ipv6() + and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res) + ): + raw_socket = setup_raw_socket( + AddressFamily.AF_INET6, ("::", local_port), v6only=False + ) + listener = asynclib.create_tcp_listener(raw_socket) + return MultiListener([listener]) + + errors: list[OSError] = [] + try: + for _ in range(len(sockaddrs)): + listeners: list[SocketListener] = [] + bound_ephemeral_port = local_port + try: + for fam, *_, sockaddr in sockaddrs: + sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:] + raw_socket = setup_raw_socket(fam, sockaddr) + + # Store the assigned port if an ephemeral port was requested, so + # we'll bind to the same port on all interfaces + if local_port == 0 and len(gai_res) > 1: + bound_ephemeral_port = raw_socket.getsockname()[1] + + listeners.append(asynclib.create_tcp_listener(raw_socket)) + except BaseException as exc: + for listener in listeners: + await listener.aclose() + + # If an ephemeral port was requested but binding the assigned port + # failed for another interface, rotate the address list and try again + if ( + isinstance(exc, OSError) + and exc.errno == errno.EADDRINUSE + and local_port == 0 + and bound_ephemeral_port + ): + errors.append(exc) + sockaddrs.append(sockaddrs.pop(0)) + continue + + raise + + return MultiListener(listeners) + + raise OSError( + f"Could not create {len(sockaddrs)} listeners with a consistent port" + ) from ExceptionGroup("Several bind attempts failed", errors) + finally: + del errors # Prevent reference cycles + + +async def create_unix_listener( + path: str | bytes | PathLike[Any], + *, + mode: int | None = None, + backlog: int = 65536, +) -> SocketListener: + """ + Create a UNIX socket listener. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :return: a listener object + + .. versionchanged:: 3.0 + If a socket already exists on the file system in the given path, it will be + removed first. + + """ + backlog = min(backlog, 65536) + raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM) + try: + raw_socket.listen(backlog) + return get_async_backend().create_unix_listener(raw_socket) + except BaseException: + raw_socket.close() + raise + + +async def create_udp_socket( + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> UDPSocket: + """ + Create a UDP socket. + + If ``port`` has been given, the socket will be bound to this port on the local + machine, making this socket suitable for providing UDP based services. + + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a UDP socket + + """ + if family is AddressFamily.AF_UNSPEC and not local_host: + raise ValueError('Either "family" or "local_host" must be given') + + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + elif family is AddressFamily.AF_INET6: + local_address = ("::", 0) + else: + local_address = ("0.0.0.0", 0) + + sock = await get_async_backend().create_udp_socket( + family, local_address, None, reuse_port + ) + return cast(UDPSocket, sock) + + +async def create_connected_udp_socket( + remote_host: IPAddressType, + remote_port: int, + *, + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> ConnectedUDPSocket: + """ + Create a connected UDP socket. + + Connected UDP sockets can only communicate with the specified remote host/port, an + any packets sent from other sources are dropped. + + :param remote_host: remote host to set as the default target + :param remote_port: port on the remote host to set as the default target + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` or ``remote_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a connected UDP socket + + """ + local_address = None + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + + gai_res = await getaddrinfo( + str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + remote_address = gai_res[0][-1] + + sock = await get_async_backend().create_udp_socket( + family, local_address, remote_address, reuse_port + ) + return cast(ConnectedUDPSocket, sock) + + +async def create_unix_datagram_socket( + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> UNIXDatagramSocket: + """ + Create a UNIX datagram socket. + + Not available on Windows. + + If ``local_path`` has been given, the socket will be bound to this path, making this + socket suitable for receiving datagrams from other processes. Other processes can + send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a UNIX datagram socket + + """ + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket(raw_socket, None) + + +async def create_connected_unix_datagram_socket( + remote_path: str | bytes | PathLike[Any], + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> ConnectedUNIXDatagramSocket: + """ + Create a connected UNIX datagram socket. + + Connected datagram sockets can only communicate with the specified remote path. + + If ``local_path`` has been given, the socket will be bound to this path, making + this socket suitable for receiving datagrams from other processes. Other processes + can send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param remote_path: the path to set as the default target + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a connected UNIX datagram socket + + """ + remote_path = os.fspath(remote_path) + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket( + raw_socket, remote_path + ) + + +async def getaddrinfo( + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, +) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]: + """ + Look up a numeric IP address given a host name. + + Internationalized domain names are translated according to the (non-transitional) + IDNA 2008 standard. + + .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of + (host, port), unlike what :func:`socket.getaddrinfo` does. + + :param host: host name + :param port: port number + :param family: socket family (`'AF_INET``, ...) + :param type: socket type (``SOCK_STREAM``, ...) + :param proto: protocol number + :param flags: flags to pass to upstream ``getaddrinfo()`` + :return: list of tuples containing (family, type, proto, canonname, sockaddr) + + .. seealso:: :func:`socket.getaddrinfo` + + """ + # Handle unicode hostnames + encoded_host = idna2008_resolve(host) if isinstance(host, str) else host + gai_res = await get_async_backend().getaddrinfo( + encoded_host, port, family=family, type=type, proto=proto, flags=flags + ) + return [ + (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr)) + for family, type, proto, canonname, sockaddr in gai_res + # filter out IPv6 results when IPv6 is disabled + if not isinstance(sockaddr[0], int) + ] + + +def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]: + """ + Look up the host name of an IP address. + + :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4) + :param flags: flags to pass to upstream ``getnameinfo()`` + :return: a tuple of (host name, service name) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: :func:`socket.getnameinfo` + + """ + return get_async_backend().getnameinfo(sockaddr, flags) + + +@deprecated("This function is deprecated; use `wait_readable` instead") +def wait_socket_readable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_readable` instead. + + Wait until the given socket has data to be read. + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(sock.fileno()) + + +@deprecated("This function is deprecated; use `wait_writable` instead") +def wait_socket_writable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_writable` instead. + + Wait until the given socket can be written to. + + This does **NOT** work on Windows when using the asyncio backend with a proactor + event loop (default on py3.8+). + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_writable(sock.fileno()) + + +def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object has data to be read. + + On Unix systems, ``obj`` must either be an integer file descriptor, or else an + object with a ``.fileno()`` method which returns an integer file descriptor. Any + kind of file descriptor can be passed, though the exact semantics will depend on + your kernel. For example, this probably won't do anything useful for on-disk files. + + On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an + object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File + descriptors aren't supported, and neither are handles that refer to anything besides + a ``SOCKET``. + + On backends where this functionality is not natively provided (asyncio + ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread + which is set to shut down when the interpreter shuts down. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(obj) + + +def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object can be written to. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: See the documentation of :func:`wait_readable` for the definition of + ``obj`` and notes on backend compatibility. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + """ + return get_async_backend().wait_writable(obj) + + +def notify_closing(obj: FileDescriptorLike) -> None: + """ + Call this before closing a file descriptor (on Unix) or socket (on + Windows). This will cause any `wait_readable` or `wait_writable` + calls on the given object to immediately wake up and raise + `~anyio.ClosedResourceError`. + + This doesn't actually close the object – you still have to do that + yourself afterwards. Also, you want to be careful to make sure no + new tasks start waiting on the object in between when you call this + and when it's actually closed. So to close something properly, you + usually want to do these steps in order: + + 1. Explicitly mark the object as closed, so that any new attempts + to use it will abort before they start. + 2. Call `notify_closing` to wake up any already-existing users. + 3. Actually close the object. + + It's also possible to do them in a different order if that's more + convenient, *but only if* you make sure not to have any checkpoints in + between the steps. This way they all happen in a single atomic + step, so other tasks won't be able to tell what order they happened + in anyway. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + get_async_backend().notify_closing(obj) + + +# +# Private API +# + + +def convert_ipv6_sockaddr( + sockaddr: tuple[str, int, int, int] | tuple[str, int], +) -> tuple[str, int]: + """ + Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format. + + If the scope ID is nonzero, it is added to the address, separated with ``%``. + Otherwise the flow id and scope id are simply cut off from the tuple. + Any other kinds of socket addresses are returned as-is. + + :param sockaddr: the result of :meth:`~socket.socket.getsockname` + :return: the converted socket address + + """ + # This is more complicated than it should be because of MyPy + if isinstance(sockaddr, tuple) and len(sockaddr) == 4: + host, port, _flowinfo, scope_id = sockaddr + if scope_id: + # PyPy (as of v7.3.11) leaves the interface name in the result, so + # we discard it and only get the scope ID from the end + # (https://foss.heptapod.net/pypy/pypy/-/issues/3938) + host = host.split("%")[0] + + # Add scope_id to the address + return f"{host}%{scope_id}", port + else: + return host, port + else: + return sockaddr + + +async def setup_unix_local_socket( + path: None | str | bytes | PathLike[Any], + mode: int | None, + socktype: int, +) -> socket.socket: + """ + Create a UNIX local socket object, deleting the socket at the given path if it + exists. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM + + """ + path_str: str | None + if path is not None: + path_str = os.fsdecode(path) + + # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call + if not path_str.startswith("\0"): + # Copied from pathlib... + try: + stat_result = os.stat(path) + except OSError as e: + if e.errno not in ( + errno.ENOENT, + errno.ENOTDIR, + errno.EBADF, + errno.ELOOP, + ): + raise + else: + if stat.S_ISSOCK(stat_result.st_mode): + os.unlink(path) + else: + path_str = None + + raw_socket = socket.socket(socket.AF_UNIX, socktype) + raw_socket.setblocking(False) + + if path_str is not None: + try: + await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True) + if mode is not None: + await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True) + except BaseException: + raw_socket.close() + raise + + return raw_socket + + +@dataclass +class TCPConnectable(ByteStreamConnectable): + """ + Connects to a TCP server at the given host and port. + + :param host: host name or IP address of the server + :param port: TCP port number of the server + """ + + host: str | IPv4Address | IPv6Address + port: int + + def __post_init__(self) -> None: + if self.port < 1 or self.port > 65535: + raise ValueError("TCP port number out of range") + + @override + async def connect(self) -> SocketStream: + try: + return await connect_tcp(self.host, self.port) + except OSError as exc: + raise ConnectionFailed( + f"error connecting to {self.host}:{self.port}: {exc}" + ) from exc + + +@dataclass +class UNIXConnectable(ByteStreamConnectable): + """ + Connects to a UNIX domain socket at the given path. + + :param path: the file system path of the socket + """ + + path: str | bytes | PathLike[str] | PathLike[bytes] + + @override + async def connect(self) -> UNIXSocketStream: + try: + return await connect_unix(self.path) + except OSError as exc: + raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc + + +def as_connectable( + remote: ByteStreamConnectable + | tuple[str | IPv4Address | IPv6Address, int] + | str + | bytes + | PathLike[str], + /, + *, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_hostname: str | None = None, + tls_standard_compatible: bool = True, +) -> ByteStreamConnectable: + """ + Return a byte stream connectable from the given object. + + If a bytestream connectable is given, it is returned unchanged. + If a tuple of (host, port) is given, a TCP connectable is returned. + If a string or bytes path is given, a UNIX connectable is returned. + + If ``tls=True``, the connectable will be wrapped in a + :class:`~.streams.tls.TLSConnectable`. + + :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket + :param tls: if ``True``, wrap the plaintext connectable in a + :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings) + :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided, + a secure default will be created) + :param tls_hostname: if ``tls=True``, host name of the server to use for checking + the server certificate (defaults to the host portion of the address for TCP + connectables) + :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream + skip the closing handshake when closing the connection, so it won't raise an + exception if the server does the same + + """ + connectable: TCPConnectable | UNIXConnectable | TLSConnectable + if isinstance(remote, ByteStreamConnectable): + return remote + elif isinstance(remote, tuple) and len(remote) == 2: + connectable = TCPConnectable(*remote) + elif isinstance(remote, (str, bytes, PathLike)): + connectable = UNIXConnectable(remote) + else: + raise TypeError(f"cannot convert {remote!r} to a connectable") + + if tls: + if not tls_hostname and isinstance(connectable, TCPConnectable): + tls_hostname = str(connectable.host) + + connectable = TLSConnectable( + connectable, + ssl_context=ssl_context, + hostname=tls_hostname, + standard_compatible=tls_standard_compatible, + ) + + return connectable diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py new file mode 100644 index 0000000..2b9c7df --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import math +from typing import TypeVar +from warnings import warn + +from ..streams.memory import ( + MemoryObjectReceiveStream, + MemoryObjectSendStream, + _MemoryObjectStreamState, +) + +T_Item = TypeVar("T_Item") + + +class create_memory_object_stream( + tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]], +): + """ + Create a memory object stream. + + The stream's item type can be annotated like + :func:`create_memory_object_stream[T_Item]`. + + :param max_buffer_size: number of items held in the buffer until ``send()`` starts + blocking + :param item_type: old way of marking the streams with the right generic type for + static typing (does nothing on AnyIO 4) + + .. deprecated:: 4.0 + Use ``create_memory_object_stream[YourItemType](...)`` instead. + :return: a tuple of (send stream, receive stream) + + """ + + def __new__( # type: ignore[misc] + cls, max_buffer_size: float = 0, item_type: object = None + ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]: + if max_buffer_size != math.inf and not isinstance(max_buffer_size, int): + raise ValueError("max_buffer_size must be either an integer or math.inf") + if max_buffer_size < 0: + raise ValueError("max_buffer_size cannot be negative") + if item_type is not None: + warn( + "The item_type argument has been deprecated in AnyIO 4.0. " + "Use create_memory_object_stream[YourItemType](...) instead.", + DeprecationWarning, + stacklevel=2, + ) + + state = _MemoryObjectStreamState[T_Item](max_buffer_size) + return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py new file mode 100644 index 0000000..53184a2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from io import BytesIO +from os import PathLike +from subprocess import PIPE, CalledProcessError, CompletedProcess +from typing import IO, Any, TypeAlias, cast + +from ..abc import Process # noqa: TC001 +from ._eventloop import get_async_backend +from ._tasks import create_task_group + +StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] + + +async def run_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + input: bytes | None = None, + stdin: int | IO[Any] | None = None, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + check: bool = True, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> CompletedProcess[bytes]: + """ + Run an external command in a subprocess and wait until it completes. + + .. seealso:: :func:`subprocess.run` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param input: bytes passed to the standard input of the subprocess + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None`; ``input`` overrides this + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or `None` + :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the + process terminates with a return code other than 0 + :param cwd: If not ``None``, change the working directory to this before running the + command + :param env: if not ``None``, this mapping replaces the inherited environment + variables from the parent process + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (Python >= 3.9, POSIX only) + :param group: effective group to run the process as (Python >= 3.9, POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9, + POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (Python >= 3.9, POSIX only) + :return: an object representing the completed process + :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process + exits with a nonzero return code + + """ + + async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: + buffer = BytesIO() + async for chunk in stream: + buffer.write(chunk) + + stream_contents[index] = buffer.getvalue() + + if stdin is not None and input is not None: + raise ValueError("only one of stdin and input is allowed") + + async with await open_process( + command, + stdin=PIPE if input else stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + user=user, + group=group, + extra_groups=extra_groups, + umask=umask, + ) as process: + stream_contents: list[bytes | None] = [None, None] + async with create_task_group() as tg: + if process.stdout: + tg.start_soon(drain_stream, process.stdout, 0) + + if process.stderr: + tg.start_soon(drain_stream, process.stderr, 1) + + if process.stdin and input: + await process.stdin.send(input) + await process.stdin.aclose() + + await process.wait() + + output, errors = stream_contents + if check and process.returncode != 0: + raise CalledProcessError(cast(int, process.returncode), command, output, errors) + + return CompletedProcess(command, cast(int, process.returncode), output, errors) + + +async def open_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None = PIPE, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> Process: + """ + Start an external command in a subprocess. + + .. seealso:: :class:`subprocess.Popen` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a + file-like object, or ``None`` + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or ``None`` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or ``None`` + :param cwd: If not ``None``, the working directory is changed before executing + :param env: If env is not ``None``, it must be a mapping that defines the + environment variables for the new process + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (POSIX only) + :param group: effective group to run the process as (POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (POSIX only) + :return: an asynchronous process object + + """ + kwargs: dict[str, Any] = {} + if user is not None: + kwargs["user"] = user + + if group is not None: + kwargs["group"] = group + + if extra_groups is not None: + kwargs["extra_groups"] = extra_groups + + if umask >= 0: + kwargs["umask"] = umask + + return await get_async_backend().open_process( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + **kwargs, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py new file mode 100644 index 0000000..cb988c6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py @@ -0,0 +1,778 @@ +from __future__ import annotations + +import math +import sys +from collections import deque +from collections.abc import Callable +from dataclasses import dataclass +from types import TracebackType +from typing import TypeVar + +from ..lowlevel import checkpoint_if_cancelled +from ._eventloop import get_async_backend +from ._exceptions import BusyResourceError, NoEventLoopError +from ._tasks import CancelScope +from ._testing import TaskInfo, get_current_task + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +T = TypeVar("T") + + +@dataclass(frozen=True) +class EventStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait` + """ + + tasks_waiting: int + + +@dataclass(frozen=True) +class CapacityLimiterStatistics: + """ + :ivar int borrowed_tokens: number of tokens currently borrowed by tasks + :ivar float total_tokens: total number of available tokens + :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from + this limiter + :ivar int tasks_waiting: number of tasks waiting on + :meth:`~.CapacityLimiter.acquire` or + :meth:`~.CapacityLimiter.acquire_on_behalf_of` + """ + + borrowed_tokens: int + total_tokens: float + borrowers: tuple[object, ...] + tasks_waiting: int + + +@dataclass(frozen=True) +class LockStatistics: + """ + :ivar bool locked: flag indicating if this lock is locked or not + :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the + lock is not held by any task) + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire` + """ + + locked: bool + owner: TaskInfo | None + tasks_waiting: int + + +@dataclass(frozen=True) +class ConditionStatistics: + """ + :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait` + :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying + :class:`~.Lock` + """ + + tasks_waiting: int + lock_statistics: LockStatistics + + +@dataclass(frozen=True) +class SemaphoreStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire` + + """ + + tasks_waiting: int + + +class Event: + __slots__ = ("__weakref__",) + + def __new__(cls) -> Event: + try: + return get_async_backend().create_event() + except NoEventLoopError: + return EventAdapter() + + def set(self) -> None: + """Set the flag, notifying all listeners.""" + raise NotImplementedError + + def is_set(self) -> bool: + """Return ``True`` if the flag is set, ``False`` if not.""" + raise NotImplementedError + + async def wait(self) -> None: + """ + Wait until the flag has been set. + + If the flag has already been set when this method is called, it returns + immediately. + + """ + raise NotImplementedError + + def statistics(self) -> EventStatistics: + """Return statistics about the current state of this event.""" + raise NotImplementedError + + +class EventAdapter(Event): + __slots__ = "_internal_event", "_is_set" + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self._internal_event: Event | None = None + self._is_set = False + + @property + def _event(self) -> Event: + if self._internal_event is None: + self._internal_event = get_async_backend().create_event() + if self._is_set: + self._internal_event.set() + + return self._internal_event + + def set(self) -> None: + if self._internal_event is None: + self._is_set = True + else: + self._event.set() + + def is_set(self) -> bool: + if self._internal_event is None: + return self._is_set + + return self._internal_event.is_set() + + async def wait(self) -> None: + await self._event.wait() + + def statistics(self) -> EventStatistics: + if self._internal_event is None: + return EventStatistics(tasks_waiting=0) + + return self._internal_event.statistics() + + +class Lock: + __slots__ = ("__weakref__",) + + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + try: + return get_async_backend().create_lock(fast_acquire=fast_acquire) + except NoEventLoopError: + return LockAdapter(fast_acquire=fast_acquire) + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Release the lock.""" + raise NotImplementedError + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + raise NotImplementedError + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class LockAdapter(Lock): + __slots__ = "_fast_acquire", "_internal_lock" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False): + self._internal_lock: Lock | None = None + self._fast_acquire = fast_acquire + + @property + def _lock(self) -> Lock: + if self._internal_lock is None: + self._internal_lock = get_async_backend().create_lock( + fast_acquire=self._fast_acquire + ) + + return self._internal_lock + + async def __aenter__(self) -> None: + await self._lock.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self._internal_lock is not None: + self._internal_lock.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + await self._lock.acquire() + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + + def release(self) -> None: + """Release the lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + return self._lock.locked() + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + + """ + if self._internal_lock is None: + return LockStatistics(False, None, 0) + + return self._internal_lock.statistics() + + +class Condition: + __slots__ = "__weakref__", "_lock", "_owner_task", "_waiters" + + def __init__(self, lock: Lock | None = None): + self._owner_task: TaskInfo | None = None + self._lock = lock or Lock() + self._waiters: deque[Event] = deque() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + def _check_acquired(self) -> None: + if self._owner_task != get_current_task(): + raise RuntimeError("The current task is not holding the underlying lock") + + async def acquire(self) -> None: + """Acquire the underlying lock.""" + await self._lock.acquire() + self._owner_task = get_current_task() + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + self._owner_task = get_current_task() + + def release(self) -> None: + """Release the underlying lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is set.""" + return self._lock.locked() + + def notify(self, n: int = 1) -> None: + """Notify exactly n listeners.""" + self._check_acquired() + for _ in range(n): + try: + event = self._waiters.popleft() + except IndexError: + break + + event.set() + + def notify_all(self) -> None: + """Notify all the listeners.""" + self._check_acquired() + for event in self._waiters: + event.set() + + self._waiters.clear() + + async def wait(self) -> None: + """Wait for a notification.""" + await checkpoint_if_cancelled() + self._check_acquired() + event = Event() + self._waiters.append(event) + self.release() + try: + await event.wait() + except BaseException: + if not event.is_set(): + self._waiters.remove(event) + elif self._waiters: + # This task was notified by could not act on it, so pass + # it on to the next task + self._waiters.popleft().set() + + raise + finally: + with CancelScope(shield=True): + await self.acquire() + + async def wait_for(self, predicate: Callable[[], T]) -> T: + """ + Wait until a predicate becomes true. + + :param predicate: a callable that returns a truthy value when the condition is + met + :return: the result of the predicate + + .. versionadded:: 4.11.0 + + """ + while not (result := predicate()): + await self.wait() + + return result + + def statistics(self) -> ConditionStatistics: + """ + Return statistics about the current state of this condition. + + .. versionadded:: 3.0 + """ + return ConditionStatistics(len(self._waiters), self._lock.statistics()) + + +class Semaphore: + __slots__ = "__weakref__", "_fast_acquire" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + try: + return get_async_backend().create_semaphore( + initial_value, max_value=max_value, fast_acquire=fast_acquire + ) + except NoEventLoopError: + return SemaphoreAdapter(initial_value, max_value=max_value) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + if not isinstance(initial_value, int): + raise TypeError("initial_value must be an integer") + if initial_value < 0: + raise ValueError("initial_value must be >= 0") + if max_value is not None: + if not isinstance(max_value, int): + raise TypeError("max_value must be an integer or None") + if max_value < initial_value: + raise ValueError( + "max_value must be equal to or higher than initial_value" + ) + + self._fast_acquire = fast_acquire + + async def __aenter__(self) -> Self: + await self.acquire() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Decrement the semaphore value, blocking if necessary.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Increment the semaphore value.""" + raise NotImplementedError + + @property + def value(self) -> int: + """The current value of the semaphore.""" + raise NotImplementedError + + @property + def max_value(self) -> int | None: + """The maximum value of the semaphore.""" + raise NotImplementedError + + def statistics(self) -> SemaphoreStatistics: + """ + Return statistics about the current state of this semaphore. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class SemaphoreAdapter(Semaphore): + __slots__ = "_initial_value", "_internal_semaphore", "_max_value" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self._internal_semaphore: Semaphore | None = None + self._initial_value = initial_value + self._max_value = max_value + + @property + def _semaphore(self) -> Semaphore: + if self._internal_semaphore is None: + self._internal_semaphore = get_async_backend().create_semaphore( + self._initial_value, max_value=self._max_value + ) + + return self._internal_semaphore + + async def acquire(self) -> None: + await self._semaphore.acquire() + + def acquire_nowait(self) -> None: + self._semaphore.acquire_nowait() + + def release(self) -> None: + self._semaphore.release() + + @property + def value(self) -> int: + if self._internal_semaphore is None: + return self._initial_value + + return self._semaphore.value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + if self._internal_semaphore is None: + return SemaphoreStatistics(tasks_waiting=0) + + return self._semaphore.statistics() + + +class CapacityLimiter: + __slots__ = ("__weakref__",) + + def __new__(cls, total_tokens: float) -> CapacityLimiter: + try: + return get_async_backend().create_capacity_limiter(total_tokens) + except NoEventLoopError: + return CapacityLimiterAdapter(total_tokens) + + async def __aenter__(self) -> None: + raise NotImplementedError + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + raise NotImplementedError + + @property + def total_tokens(self) -> float: + """ + The total number of tokens available for borrowing. + + This is a read-write property. If the total number of tokens is increased, the + proportionate number of tasks waiting on this limiter will be granted their + tokens. + + .. versionchanged:: 3.0 + The property is now writable. + .. versionchanged:: 4.12 + The value can now be set to 0. + + """ + raise NotImplementedError + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + raise NotImplementedError + + @property + def borrowed_tokens(self) -> int: + """The number of tokens that have currently been borrowed.""" + raise NotImplementedError + + @property + def available_tokens(self) -> float: + """The number of tokens currently available to be borrowed""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire a token for the current task without waiting for one to become + available. + + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + """ + Acquire a token without waiting for one to become available. + + :param borrower: the entity borrowing a token + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + async def acquire(self) -> None: + """ + Acquire a token for the current task, waiting if necessary for one to become + available. + + """ + raise NotImplementedError + + async def acquire_on_behalf_of(self, borrower: object) -> None: + """ + Acquire a token, waiting if necessary for one to become available. + + :param borrower: the entity borrowing a token + + """ + raise NotImplementedError + + def release(self) -> None: + """ + Release the token held by the current task. + + :raises RuntimeError: if the current task has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def release_on_behalf_of(self, borrower: object) -> None: + """ + Release the token held by the given borrower. + + :raises RuntimeError: if the borrower has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def statistics(self) -> CapacityLimiterStatistics: + """ + Return statistics about the current state of this limiter. + + .. versionadded:: 3.0 + + """ + raise NotImplementedError + + +class CapacityLimiterAdapter(CapacityLimiter): + __slots__ = "_internal_limiter", "_total_tokens" + + def __new__(cls, total_tokens: float) -> Self: + return object.__new__(cls) + + def __init__(self, total_tokens: float) -> None: + self._internal_limiter: CapacityLimiter | None = None + self.total_tokens = total_tokens + + @property + def _limiter(self) -> CapacityLimiter: + if self._internal_limiter is None: + self._internal_limiter = get_async_backend().create_capacity_limiter( + self._total_tokens + ) + + return self._internal_limiter + + async def __aenter__(self) -> None: + await self._limiter.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + return await self._limiter.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + elif value < 0: + raise ValueError("total_tokens must be >= 0") + + if self._internal_limiter is None: + self._total_tokens = value + return + + self._limiter.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + if self._internal_limiter is None: + return 0 + + return self._internal_limiter.borrowed_tokens + + @property + def available_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.available_tokens + + def acquire_nowait(self) -> None: + self._limiter.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self._limiter.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self._limiter.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self._limiter.acquire_on_behalf_of(borrower) + + def release(self) -> None: + self._limiter.release() + + def release_on_behalf_of(self, borrower: object) -> None: + self._limiter.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + if self._internal_limiter is None: + return CapacityLimiterStatistics( + borrowed_tokens=0, + total_tokens=self.total_tokens, + borrowers=(), + tasks_waiting=0, + ) + + return self._internal_limiter.statistics() + + +class ResourceGuard: + """ + A context manager for ensuring that a resource is only used by a single task at a + time. + + Entering this context manager while the previous has not exited it yet will trigger + :exc:`BusyResourceError`. + + :param action: the action to guard against (visible in the :exc:`BusyResourceError` + when triggered, e.g. "Another task is already {action} this resource") + + .. versionadded:: 4.1 + """ + + __slots__ = "__weakref__", "_guarded", "action" + + def __init__(self, action: str = "using"): + self.action: str = action + self._guarded = False + + def __enter__(self) -> None: + if self._guarded: + raise BusyResourceError(self.action) + + self._guarded = True + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._guarded = False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py new file mode 100644 index 0000000..044eda6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py @@ -0,0 +1,464 @@ +from __future__ import annotations + +import math +import sys +from collections.abc import ( + Coroutine, + Generator, +) +from contextlib import contextmanager +from enum import Enum, auto +from inspect import iscoroutine +from types import TracebackType +from typing import Any, Generic, final + +from ..abc import TaskGroup, TaskStatus +from ._eventloop import get_async_backend, get_cancelled_exc_class +from ._exceptions import TaskCancelled, TaskFailed, TaskNotFinished + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + +if sys.version_info >= (3, 11): + from typing import Never, Self, TypeVarTuple +else: + from typing_extensions import Never, Self, TypeVarTuple + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) +T_startval_co = TypeVar("T_startval_co", covariant=True, default=Never) +PosArgsT = TypeVarTuple("PosArgsT") + + +class _IgnoredTaskStatus(TaskStatus[object]): + def started(self, value: object = None) -> None: + pass + + +TASK_STATUS_IGNORED = _IgnoredTaskStatus() + + +class CancelScope: + """ + Wraps a unit of work that can be made separately cancellable. + + :param deadline: The time (clock value) when this scope is cancelled automatically + :param shield: ``True`` to shield the cancel scope from external cancellation + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + __slots__ = ("__weakref__",) + + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline) + + def cancel(self, reason: str | None = None) -> None: + """ + Cancel this scope immediately. + + :param reason: a message describing the reason for the cancellation + + """ + raise NotImplementedError + + @property + def deadline(self) -> float: + """ + The time (clock value) when this scope is cancelled automatically. + + Will be ``float('inf')`` if no timeout has been set. + + """ + raise NotImplementedError + + @deadline.setter + def deadline(self, value: float) -> None: + raise NotImplementedError + + @property + def cancel_called(self) -> bool: + """``True`` if :meth:`cancel` has been called.""" + raise NotImplementedError + + @property + def cancelled_caught(self) -> bool: + """ + ``True`` if this scope suppressed a cancellation exception it itself raised. + + This is typically used to check if any work was interrupted, or to see if the + scope was cancelled due to its deadline being reached. The value will, however, + only be ``True`` if the cancellation was triggered by the scope itself (and not + an outer scope). + + """ + raise NotImplementedError + + @property + def shield(self) -> bool: + """ + ``True`` if this scope is shielded from external cancellation. + + While a scope is shielded, it will not receive cancellations from outside. + + """ + raise NotImplementedError + + @shield.setter + def shield(self, value: bool) -> None: + raise NotImplementedError + + def __enter__(self) -> Self: + raise NotImplementedError + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + raise NotImplementedError + + +@contextmanager +def fail_at( + deadline: float | None, shield: bool = False, reason: str | None = None +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if the code in the + enclosing context does not finish by the given deadline. + + :param deadline: the deadline before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :param reason: explanation for timeout to add to the message of a raised `TimeoutError` + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.15.0 + + """ + current_time = get_async_backend().current_time + effective_deadline = math.inf if deadline is None else deadline + with get_async_backend().create_cancel_scope( + deadline=effective_deadline, shield=shield + ) as cancel_scope: + yield cancel_scope + + if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline: + raise TimeoutError(reason) if reason else TimeoutError + + +@contextmanager +def fail_after( + delay: float | None, shield: bool = False, reason: str | None = None +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if the code in the + enclosing context block does not finish in time. + + :param delay: maximum allowed time (in seconds) before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :param reason: explanation for timeout to add to the message of a raised `TimeoutError` + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + current_time = get_async_backend().current_time + deadline = (current_time() + delay) if delay is not None else math.inf + with fail_at(deadline, shield=shield, reason=reason) as scope: + yield scope + + +def move_on_at(deadline: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param deadline: the deadline before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.15.0 + + """ + return get_async_backend().create_cancel_scope( + deadline=deadline if deadline is not None else math.inf, shield=shield + ) + + +def move_on_after(delay: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param delay: maximum allowed time (in seconds) before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. note:: Unlike with :func:`fail_after`, the timer starts when this function is + called, not when the context manager is entered. This will be changed in v5.0. + + """ + deadline = ( + (get_async_backend().current_time() + delay) if delay is not None else math.inf + ) + return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield) + + +def current_effective_deadline() -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the current + task. + + :return: a clock value from the event loop's internal clock (or ``float('inf')`` if + there is no deadline in effect, or ``float('-inf')`` if the current scope has + been cancelled) + :rtype: float + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_effective_deadline() + + +def create_task_group() -> TaskGroup: + """ + Create a task group. + + :return: a task group + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().create_task_group() + + +@final +class TaskHandle(Generic[T_co, T_startval_co]): + """ + Returned from the task-spawning methods of :class:`TaskGroup`. Can be awaited on to + get the return value of the task (or the raised exception). If the task was + terminated by a :exc:`BaseException`, :exc:`TaskFailed` will be raised (or its + subclass :exc:`TaskCancelled` if the task was cancelled). + + .. versionadded:: 4.14.0 + """ + + class Status(Enum): + """ + The status of a task handle. + + .. attribute:: PENDING + + The task has not finished yet. + .. attribute:: FINISHED + + The task has finished with a return value. + .. attribute:: CANCELLING + + The task has been cancelled but has not finished yet. + .. attribute:: CANCELLED + + The task was cancelled and has finished since. + .. attribute:: FAILED + + The task raised an exception. + """ + + PENDING = auto() + FINISHED = auto() + CANCELLING = auto() + CANCELLED = auto() + FAILED = auto() + + __slots__ = ( + "__weakref__", + "_cancel_scope", + "_coro", + "_exception", + "_finished_event", + "_name", + "_return_value", + "_start_value", + ) + + _return_value: T_co + _start_value: T_startval_co + + def __init__( + self, + coro: Coroutine[Any, Any, T_co], + name: object, + cancel_scope: CancelScope | None = None, + ) -> None: + from ._synchronization import Event + + self._coro = coro + self._cancel_scope = cancel_scope if cancel_scope is not None else CancelScope() + self._finished_event = Event() + self._exception: BaseException | None = None + + if name is not None: + self._name = str(name) + elif iscoroutine(coro): + self._name = coro.__qualname__ + else: + self._name = str(coro) # coroutine-like object (e.g. asend() objects) + + async def _run_coro(self) -> None: + __tracebackhide__ = True + + with self._cancel_scope: + try: + retval = await self._coro + except BaseException as exc: + self._exception = exc + raise + else: + self._return_value = retval + finally: + self._finished_event.set() + del self # Break the reference cycle + + def cancel(self) -> None: + """ + Set the task to a cancelled state. + + This will interrupt any interruptible asynchronous operation, and will cause + any further awaits on this task to get immediately cancelled, unless done in + a shielded cancel scope. + + If the task has already finished, this method has no effect. + """ + if not self._finished_event.is_set(): + self._cancel_scope.cancel() + + @property + def coro(self) -> Coroutine[Any, Any, T_co]: + """ + The coroutine object that was passed to one of the task-spawning methods in + :class:`TaskGroup`. + """ + return self._coro + + @property + def status(self) -> TaskHandle.Status: + """ + The current status of the task. + + Every task starts in the :attr:`~TaskHandle.Status.PENDING` state. + If a task is cancelled while in this state, it will transition to the + :attr:`~TaskHandle.Status.CANCELLING` state. When the task finishes, it will + transition to one of the three final states ( + :attr:`~TaskHandle.Status.FINISHED`, :attr:`~TaskHandle.Status.FAILED`, or + :attr:`~TaskHandle.Status.CANCELLING`) depending on the exception the task + raised, if any. No other status transitions will happen. + """ + if not self._finished_event.is_set(): + if self._cancel_scope.cancel_called: + return TaskHandle.Status.CANCELLING + else: + return TaskHandle.Status.PENDING + elif self._exception is not None: + if isinstance(self._exception, get_cancelled_exc_class()): + return TaskHandle.Status.CANCELLED + else: + return TaskHandle.Status.FAILED + else: + return TaskHandle.Status.FINISHED + + @property + def name(self) -> str: + """The name of the task.""" + return self._name + + @property + def exception(self) -> BaseException | None: + """ + The exception raised by the task, or ``None`` if it finished without raising. + + :raises TaskNotFinished: if the task has not finished yet + :raises TaskCancelled: if the task was cancelled + + """ + match self.status: + case TaskHandle.Status.PENDING: + raise TaskNotFinished("the task has not finished yet") + case TaskHandle.Status.FINISHED: + return None + case TaskHandle.Status.CANCELLING: + raise TaskCancelled("the task was cancelled") + case TaskHandle.Status.CANCELLED: + raise TaskCancelled("the task was cancelled") from self._exception + case TaskHandle.Status.FAILED: + return self._exception + + @property + def return_value(self) -> T_co: + """ + The return value of the task. + + :raises TaskNotFinished: if the task has not finished yet + :raises TaskCancelled: if the task was cancelled + :raises TaskFailed: if the task raised an exception + + """ + match self.status: + case TaskHandle.Status.PENDING: + raise TaskNotFinished("the task has not finished yet") + case TaskHandle.Status.FINISHED: + return self._return_value + case TaskHandle.Status.CANCELLING: + raise TaskCancelled("the task was cancelled") + case TaskHandle.Status.CANCELLED: + raise TaskCancelled("the task was cancelled") from self._exception + case TaskHandle.Status.FAILED: + raise TaskFailed("the task raised an exception") from self._exception + + @property + def start_value(self) -> T_startval_co: + """ + The value passed to :meth:`task_status.started() <.abc.TaskStatus.started>`, + + :raises RuntimeError: if the task was not started with :meth:`TaskGroup.start() + <.abc.TaskGroup.start>` + """ + try: + return self._start_value + except AttributeError: + raise RuntimeError( + "the task was not started with TaskGroup.start()" + ) from None + + async def wait(self) -> None: + """ + Wait for the task to finish. + + This method will return as soon as the task has finished, no matter how it + happened. + """ + await self._finished_event.wait() + + def __await__(self) -> Generator[Any, Any, T_co]: + yield from self._finished_event.wait().__await__() + return self.return_value + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} {self.status.name.lower()} " + f"name={self._name!r} coro={self._coro!r}>" + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py new file mode 100644 index 0000000..9603086 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py @@ -0,0 +1,615 @@ +from __future__ import annotations + +import os +import sys +import tempfile +from collections.abc import Iterable +from io import BytesIO, TextIOWrapper +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AnyStr, + Generic, + overload, +) + +from .. import to_thread +from .._core._fileio import AsyncFile +from ..lowlevel import checkpoint_if_cancelled +from ._tasks import CancelScope + +if TYPE_CHECKING: + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer + + +class TemporaryFile(Generic[AnyStr]): + """ + An asynchronous temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager interface to a temporary file. + The file is created using Python's standard `tempfile.TemporaryFile` function in a + background thread, and is wrapped as an asynchronous file using `AsyncFile`. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: TemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: TemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + mode: OpenTextMode | OpenBinaryMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self.mode = mode + self.buffering = buffering + self.encoding = encoding + self.newline = newline + self.suffix: str | None = suffix + self.prefix: str | None = prefix + self.dir: str | None = dir + self.errors = errors + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile( + self.mode, + self.buffering, + self.encoding, + self.newline, + self.suffix, + self.prefix, + self.dir, + errors=self.errors, + ) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class NamedTemporaryFile(Generic[AnyStr]): + """ + An asynchronous named temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager for a temporary file with a + visible name in the file system. It uses Python's standard + :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with + :class:`AsyncFile` for asynchronous operations. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param delete: Whether to delete the file when it is closed. + :param errors: The error handling scheme used for encoding/decoding errors. + :param delete_on_close: (Python 3.12+) Whether to delete the file on close. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: NamedTemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + @overload + def __init__( + self: NamedTemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + + def __init__( + self, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> None: + self._params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "delete": delete, + "errors": errors, + } + if sys.version_info >= (3, 12): + self._params["delete_on_close"] = delete_on_close + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.NamedTemporaryFile(**self._params) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class SpooledTemporaryFile(AsyncFile[AnyStr]): + """ + An asynchronous spooled temporary file that starts in memory and is spooled to disk. + + This class provides an asynchronous interface to a spooled temporary file, much like + Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous + write operations and provides a method to force a rollover to disk. + + :param max_size: Maximum size in bytes before the file is rolled over to disk. + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file (text mode only). + :param newline: Controls how universal newlines mode works (text mode only). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _rolled: bool = False + + @overload + def __init__( + self: SpooledTemporaryFile[bytes], + max_size: int = ..., + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int = ..., + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + max_size: int = 0, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self._tempfile_params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "errors": errors, + } + self._max_size = max_size + if "b" in mode: + super().__init__(BytesIO()) # type: ignore[arg-type] + else: + super().__init__( + TextIOWrapper( # type: ignore[arg-type] + BytesIO(), + encoding=encoding, + errors=errors, + newline=newline, + write_through=True, + ) + ) + + async def aclose(self) -> None: + if not self._rolled: + self._fp.close() + return + + await super().aclose() + + async def _check(self) -> None: + if self._rolled or self._fp.tell() <= self._max_size: + return + + await self.rollover() + + async def rollover(self) -> None: + if self._rolled: + return + + self._rolled = True + buffer = self._fp + buffer.seek(0) + self._fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile(**self._tempfile_params) + ) + await self.write(buffer.read()) + buffer.close() + + @property + def closed(self) -> bool: + return self._fp.closed + + async def read(self, size: int = -1) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read(size) + + return await super().read(size) # type: ignore[return-value] + + async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read1(size) + + return await super().read1(size) + + async def readline(self) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readline() + + return await super().readline() # type: ignore[return-value] + + async def readlines(self) -> list[AnyStr]: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readlines() + + return await super().readlines() # type: ignore[return-value] + + async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readinto(b) + + return await super().readinto(b) + + async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readinto1(b) + + return await super().readinto1(b) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.seek(offset, whence) + + return await super().seek(offset, whence) + + async def tell(self) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.tell() + + return await super().tell() + + async def truncate(self, size: int | None = None) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.truncate(size) + + return await super().truncate(size) + + @overload + async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ... + @overload + async def write(self: SpooledTemporaryFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + """ + Asynchronously write data to the spooled temporary file. + + If the file has not yet been rolled over, the data is written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param s: The data to write. + :return: The number of bytes written. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.write(b) + await self._check() + return result + + return await super().write(b) # type: ignore[misc] + + @overload + async def writelines( + self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + @overload + async def writelines( + self: SpooledTemporaryFile[str], lines: Iterable[str] + ) -> None: ... + + async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None: + """ + Asynchronously write a list of lines to the spooled temporary file. + + If the file has not yet been rolled over, the lines are written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param lines: An iterable of lines to write. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.writelines(lines) + await self._check() + return result + + return await super().writelines(lines) # type: ignore[misc] + + +class TemporaryDirectory(Generic[AnyStr]): + """ + An asynchronous temporary directory that is created and cleaned up automatically. + + This class provides an asynchronous context manager for creating a temporary + directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to + perform directory creation and cleanup operations in a background thread. + + :param suffix: Suffix to be added to the temporary directory name. + :param prefix: Prefix to be added to the temporary directory name. + :param dir: The parent directory where the temporary directory is created. + :param ignore_cleanup_errors: Whether to ignore errors during cleanup + :param delete: Whether to delete the directory upon closing (Python 3.12+). + """ + + def __init__( + self, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + *, + ignore_cleanup_errors: bool = False, + delete: bool = True, + ) -> None: + self.suffix: AnyStr | None = suffix + self.prefix: AnyStr | None = prefix + self.dir: AnyStr | None = dir + self.ignore_cleanup_errors = ignore_cleanup_errors + self.delete = delete + + self._tempdir: tempfile.TemporaryDirectory | None = None + + async def __aenter__(self) -> str: + params: dict[str, Any] = { + "suffix": self.suffix, + "prefix": self.prefix, + "dir": self.dir, + "ignore_cleanup_errors": self.ignore_cleanup_errors, + } + if sys.version_info >= (3, 12): + params["delete"] = self.delete + + self._tempdir = await to_thread.run_sync( + lambda: tempfile.TemporaryDirectory(**params) + ) + return await to_thread.run_sync(self._tempdir.__enter__) + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if self._tempdir is not None: + with CancelScope(shield=True): + await to_thread.run_sync( + self._tempdir.__exit__, exc_type, exc_value, traceback + ) + + async def cleanup(self) -> None: + if self._tempdir is not None: + await to_thread.run_sync(self._tempdir.cleanup) + + +@overload +async def mkstemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + text: bool = False, +) -> tuple[int, str]: ... + + +@overload +async def mkstemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, + text: bool = False, +) -> tuple[int, bytes]: ... + + +async def mkstemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + text: bool = False, +) -> tuple[int, str | bytes]: + """ + Asynchronously create a temporary file and return an OS-level handle and the file + name. + + This function wraps `tempfile.mkstemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the file name. + :param prefix: Prefix to be added to the file name. + :param dir: Directory in which the temporary file is created. + :param text: Whether the file is opened in text mode. + :return: A tuple containing the file descriptor and the file name. + + """ + return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text) + + +@overload +async def mkdtemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, +) -> str: ... + + +@overload +async def mkdtemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, +) -> bytes: ... + + +async def mkdtemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, +) -> str | bytes: + """ + Asynchronously create a temporary directory and return its path. + + This function wraps `tempfile.mkdtemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the directory name. + :param prefix: Prefix to be added to the directory name. + :param dir: Parent directory where the temporary directory is created. + :return: The path of the created temporary directory. + + """ + return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir) + + +async def gettempdir() -> str: + """ + Asynchronously return the name of the directory used for temporary files. + + This function wraps `tempfile.gettempdir` and executes it in a background thread. + + :return: The path of the temporary directory as a string. + + """ + return await to_thread.run_sync(tempfile.gettempdir) + + +async def gettempdirb() -> bytes: + """ + Asynchronously return the name of the directory used for temporary files in bytes. + + This function wraps `tempfile.gettempdirb` and executes it in a background thread. + + :return: The path of the temporary directory as bytes. + + """ + return await to_thread.run_sync(tempfile.gettempdirb) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py new file mode 100644 index 0000000..fa2c391 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Generator +from typing import Any, cast + +from ._eventloop import get_async_backend + + +class TaskInfo: + """ + Represents an asynchronous task. + + :ivar int id: the unique identifier of the task + :ivar parent_id: the identifier of the parent task, if any + :vartype parent_id: Optional[int] + :ivar str name: the description of the task (if any) + :ivar ~collections.abc.Coroutine coro: the coroutine object of the task + """ + + __slots__ = "_name", "coro", "id", "name", "parent_id" + + def __init__( + self, + id: int, + parent_id: int | None, + name: str | None, + coro: Generator[Any, Any, Any] | Awaitable[Any], + ): + func = get_current_task + self._name = f"{func.__module__}.{func.__qualname__}" + self.id: int = id + self.parent_id: int | None = parent_id + self.name: str | None = name + self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro + + def __eq__(self, other: object) -> bool: + if isinstance(other, TaskInfo): + return self.id == other.id + + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})" + + def has_pending_cancellation(self) -> bool: + """ + Return ``True`` if the task has a cancellation pending, ``False`` otherwise. + + """ + return False + + +def get_current_task() -> TaskInfo: + """ + Return the current task. + + :return: a representation of the current task + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().get_current_task() + + +def get_running_tasks() -> list[TaskInfo]: + """ + Return a list of running tasks in the current event loop. + + :return: a list of task info objects + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return cast("list[TaskInfo]", get_async_backend().get_running_tasks()) + + +async def wait_all_tasks_blocked() -> None: + """Wait until all other tasks are waiting for something.""" + await get_async_backend().wait_all_tasks_blocked() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py new file mode 100644 index 0000000..59d3da9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import sys +from collections.abc import Callable, Mapping +from typing import Any, TypeVar, final, overload + +from ._exceptions import TypedAttributeLookupError + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +T_Attr = TypeVar("T_Attr") +T_Default = TypeVar("T_Default") +undefined = sentinel("undefined") + + +def typed_attribute() -> Any: + """Return a unique object, used to mark typed attributes.""" + return object() + + +class TypedAttributeSet: + """ + Superclass for typed attribute collections. + + Checks that every public attribute of every subclass has a type annotation. + """ + + def __init_subclass__(cls) -> None: + annotations: dict[str, Any] = getattr(cls, "__annotations__", {}) + for attrname in dir(cls): + if not attrname.startswith("_") and attrname not in annotations: + raise TypeError( + f"Attribute {attrname!r} is missing its type annotation" + ) + + super().__init_subclass__() + + +class TypedAttributeProvider: + """Base class for classes that wish to provide typed extra attributes.""" + + @property + def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]: + """ + A mapping of the extra attributes to callables that return the corresponding + values. + + If the provider wraps another provider, the attributes from that wrapper should + also be included in the returned mapping (but the wrapper may override the + callables from the wrapped instance). + + """ + return {} + + @overload + def extra(self, attribute: T_Attr) -> T_Attr: ... + + @overload + def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ... + + @final + def extra(self, attribute: Any, default: object = undefined) -> object: + """ + extra(attribute, default=undefined) + + Return the value of the given typed extra attribute. + + :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to + look for + :param default: the value that should be returned if no value is found for the + attribute + :raises ~anyio.TypedAttributeLookupError: if the search failed and no default + value was given + + """ + try: + getter = self.extra_attributes[attribute] + except KeyError: + if default is undefined: + raise TypedAttributeLookupError("Attribute not found") from None + else: + return default + + return getter() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py new file mode 100644 index 0000000..dc0a4fa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +__all__ = ( + "fix_package_names", + "install_lazy_importer", + "set_deprecated_aliases", +) + +import ast +import inspect +import sys +import warnings +from importlib import import_module +from types import ModuleType +from typing import Any + + +def install_lazy_importer() -> bool: + module_globals = sys._getframe(1).f_globals + module_name = module_globals["__name__"] + module_prefix = module_name + "." + module = sys.modules[module_name] + lazy_map, deprecated_aliases, submodule_names = _build_lazy_map(module) + names = sorted(lazy_map) + + # Delete symbols that are not part of the API + del module_globals["TYPE_CHECKING"] + del module_globals["install_lazy_importer"] + + if not lazy_map and not deprecated_aliases: + return False + + def __getattr__(name: str) -> Any: + if new_name := deprecated_aliases.get(name): + emit_deprecation_warning(module_name, name, new_name) + target_mod, target_attr = new_name.rsplit(".", 1) + elif name in submodule_names: + target_mod, target_attr = "." + name, "" + else: + try: + target_mod, target_attr = lazy_map[name] + except KeyError: + raise AttributeError( + f"module {module_name!r} has no attribute {name!r}" + ) from None + + imported = import_module(target_mod, module_name) + value = getattr(imported, target_attr) if target_attr else imported + + # patch the module name to match + if ( + getattr(value, "__module__", "").startswith(module_prefix) + and name not in deprecated_aliases + ): + value.__module__ = module_name + + module_globals[name] = value + return value + + def __dir__() -> list[str]: + return names + + module_globals["__dir__"] = __dir__ + module_globals["__getattr__"] = __getattr__ + module_globals.pop("fix_package_names", None) + module_globals.pop("set_deprecated_aliases", None) + return True + + +def fix_package_names() -> None: + module_globals = sys._getframe(1).f_globals + module_prefix = module_globals["__name__"] + "." + del module_globals[fix_package_names.__name__] + for value in module_globals.values(): + if modname := getattr(value, "__module__", ""): + if modname.startswith(module_prefix): + parts = modname.split(".") + value.__module__ = ".".join( + part for part in parts if not part.startswith("_") + ) + + +def emit_deprecation_warning(module_name: str, name: str, target: str) -> None: + warnings.warn( + f"The {module_name}.{name} alias is deprecated, use {target} instead.", + DeprecationWarning, + stacklevel=3, + ) + + +def set_deprecated_aliases(aliases: dict[str, str]) -> None: + module_globals = sys._getframe(1).f_globals + module_name = module_globals["__name__"] + del module_globals[set_deprecated_aliases.__name__] + + def __getattr__(name: str) -> Any: + try: + target = aliases[name] + except KeyError: + raise AttributeError( + f"module {module_name!r} has no attribute {name!r}" + ) from None + + emit_deprecation_warning(module_name, name, target) + target_modname, attrname = target.rsplit(".", 1) + module = import_module(target_modname) + return getattr(module, attrname) + + sys.modules[module_name].__dict__["__getattr__"] = __getattr__ + + +def _build_lazy_map( + module: ModuleType, +) -> tuple[dict[str, tuple[str, str]], dict[str, str], list[str]]: + try: + source = inspect.getsource(module) + except OSError: + return {}, {}, [] + + tree = compile(source, module.__file__ or "", "exec", ast.PyCF_ONLY_AST) + assert isinstance(tree, ast.Module) + out: dict[str, tuple[str, str]] = {} + deprecated_aliases: dict[str, str] = {} + submodule_names: list[str] = [] + + for node in tree.body: + if not isinstance(node, ast.If) or not _is_type_checking_block(node.test): + continue + + for stmt in node.body: + match stmt: + case ast.ImportFrom(): + if stmt.module is None: + submodule_names.extend(alias.name for alias in stmt.names) + else: + base = "." * stmt.level + (stmt.module or "") + for alias in stmt.names: + if alias.name == "*": + raise RuntimeError("star imports not supported") + + exported = alias.asname or alias.name + out[exported] = (base, alias.name) + case ast.Expr() if isinstance(stmt.value, ast.Call): + call = stmt.value + if ( + isinstance(call.func, ast.Name) + and call.func.id == "set_deprecated_aliases" + ): + arg0 = call.args[0] + assert isinstance(arg0, ast.Dict) + for key, value in zip(arg0.keys, arg0.values, strict=True): + assert isinstance(key, ast.Constant) + assert isinstance(key.value, str) + assert isinstance(value, ast.Constant) + assert isinstance(value.value, str) + deprecated_aliases[key.value] = value.value + + return out, deprecated_aliases, submodule_names + + +def _is_type_checking_block(test: ast.AST) -> bool: + if not isinstance(test, ast.BoolOp): + return False + + subtest = test.values[0] + match subtest: + case ast.Name(): + return subtest.id == "TYPE_CHECKING" + case ast.Attribute(): + return ( + isinstance(subtest.value, ast.Name) + and subtest.value.id == "typing" + and subtest.attr == "TYPE_CHECKING" + ) + case _: + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py new file mode 100644 index 0000000..ba738f8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .._lazyimport import ( + fix_package_names, + install_lazy_importer, + set_deprecated_aliases, +) + +if TYPE_CHECKING or not install_lazy_importer(): + from ._eventloop import AsyncBackend as AsyncBackend + from ._resources import AsyncResource as AsyncResource + from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket + from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket + from ._sockets import IPAddressType as IPAddressType + from ._sockets import IPSockAddrType as IPSockAddrType + from ._sockets import SocketAttribute as SocketAttribute + from ._sockets import SocketListener as SocketListener + from ._sockets import SocketStream as SocketStream + from ._sockets import UDPPacketType as UDPPacketType + from ._sockets import UDPSocket as UDPSocket + from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType + from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket + from ._sockets import UNIXSocketStream as UNIXSocketStream + from ._streams import AnyByteReceiveStream as AnyByteReceiveStream + from ._streams import AnyByteSendStream as AnyByteSendStream + from ._streams import AnyByteStream as AnyByteStream + from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable + from ._streams import ( + AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream, + ) + from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream + from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream + from ._streams import ByteReceiveStream as ByteReceiveStream + from ._streams import ByteSendStream as ByteSendStream + from ._streams import ByteStream as ByteStream + from ._streams import ByteStreamConnectable as ByteStreamConnectable + from ._streams import Listener as Listener + from ._streams import ObjectReceiveStream as ObjectReceiveStream + from ._streams import ObjectSendStream as ObjectSendStream + from ._streams import ObjectStream as ObjectStream + from ._streams import ObjectStreamConnectable as ObjectStreamConnectable + from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream + from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream + from ._streams import UnreliableObjectStream as UnreliableObjectStream + from ._subprocesses import Process as Process + from ._tasks import TaskGroup as TaskGroup + from ._tasks import TaskStatus as TaskStatus + from ._testing import TestRunner as TestRunner + + fix_package_names() + set_deprecated_aliases( + { + "CapacityLimiter": "anyio.CapacityLimiter", + "Condition": "anyio.Condition", + "Event": "anyio.Event", + "Lock": "anyio.Lock", + "Semaphore": "anyio.Semaphore", + "CancelScope": "anyio.CancelScope", + "BlockingPortal": "anyio.from_thread.BlockingPortal", + } + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py new file mode 100644 index 0000000..a3cbed2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py @@ -0,0 +1,428 @@ +from __future__ import annotations + +import math +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import ( + AsyncIterator, + Awaitable, + Callable, + Coroutine, + Iterable, + Mapping, + Sequence, +) +from contextlib import AbstractContextManager +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind, socket +from typing import ( + IO, + TYPE_CHECKING, + Any, + TypeAlias, + TypeVar, + overload, +) + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore + from .._core._tasks import CancelScope + from .._core._testing import TaskInfo + from ._sockets import ( + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, + ) + from ._subprocesses import Process + from ._tasks import TaskGroup + from ._testing import TestRunner + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] + + +class AsyncBackend(metaclass=ABCMeta): + @classmethod + @abstractmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param kwargs: positional arguments to ``func`` + :param options: keyword arguments to call the backend ``run()`` implementation + with + :return: the return value of the coroutine function + """ + + @classmethod + @abstractmethod + def current_token(cls) -> object: + """ + Return an object that allows other threads to run code inside the event loop. + + :return: a token object, specific to the event loop running in the current + thread + """ + + @classmethod + @abstractmethod + def current_time(cls) -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + """ + + @classmethod + @abstractmethod + def cancelled_exception_class(cls) -> type[BaseException]: + """Return the exception class that is raised in a task if it's cancelled.""" + + @classmethod + @abstractmethod + async def checkpoint(cls) -> None: + """ + Check if the task has been cancelled, and allow rescheduling of other tasks. + + This is effectively the same as running :meth:`checkpoint_if_cancelled` and then + :meth:`cancel_shielded_checkpoint`. + """ + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + """ + Check if the current task group has been cancelled. + + This will check if the task has been cancelled, but will not allow other tasks + to be scheduled if not. + + """ + if cls.current_effective_deadline() == -math.inf: + await cls.checkpoint() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + """ + Allow the rescheduling of other tasks. + + This will give other tasks the opportunity to run, but without checking if the + current task group has been cancelled, unlike with :meth:`checkpoint`. + + """ + with cls.create_cancel_scope(shield=True): + await cls.sleep(0) + + @classmethod + @abstractmethod + async def sleep(cls, delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + """ + + @classmethod + @abstractmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + pass + + @classmethod + @abstractmethod + def current_effective_deadline(cls) -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the + current task. + + :return: + - a clock value from the event loop's internal clock + - ``inf`` if there is no deadline in effect + - ``-inf`` if the current scope has been cancelled + :rtype: float + """ + + @classmethod + @abstractmethod + def create_task_group(cls) -> TaskGroup: + pass + + @classmethod + @abstractmethod + def create_event(cls) -> Event: + pass + + @classmethod + @abstractmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + pass + + @classmethod + @abstractmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + pass + + @classmethod + @abstractmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: CapacityLimiter | None = None, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def check_cancelled(cls) -> None: + pass + + @classmethod + @abstractmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + pass + + @classmethod + @abstractmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + **kwargs: Any, + ) -> Process: + pass + + @classmethod + @abstractmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None: + pass + + @classmethod + @abstractmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + def create_tcp_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + def create_unix_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + pass + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: None + ) -> UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes + ) -> ConnectedUNIXDatagramSocket: ... + + @classmethod + @abstractmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes | None + ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + pass + + @classmethod + @abstractmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + pass + + @classmethod + @abstractmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wrap_listener_socket(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def wrap_stream_socket(cls, sock: socket) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_udp_socket(cls, sock: socket) -> UDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket + ) -> ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + pass + + @classmethod + @abstractmethod + def get_current_task(cls) -> TaskInfo: + pass + + @classmethod + @abstractmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + pass + + @classmethod + @abstractmethod + async def wait_all_tasks_blocked(cls) -> None: + pass + + @classmethod + @abstractmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py new file mode 100644 index 0000000..9373f71 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from types import TracebackType +from typing import TypeVar + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +T = TypeVar("T") + + +class AsyncResource(metaclass=ABCMeta): + """ + Abstract base class for all closeable asynchronous resources. + + Works as an asynchronous context manager which returns the instance itself on enter, + and calls :meth:`aclose` on exit. + """ + + __slots__ = () + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.aclose() + + @abstractmethod + async def aclose(self) -> None: + """Close the resource.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py new file mode 100644 index 0000000..d6a1139 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py @@ -0,0 +1,401 @@ +from __future__ import annotations + +import errno +import socket +from abc import abstractmethod +from collections.abc import Callable, Collection, Mapping +from contextlib import AsyncExitStack +from io import IOBase +from ipaddress import IPv4Address, IPv6Address +from socket import AddressFamily +from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar + +from .._core._eventloop import get_async_backend +from .._core._typedattr import ( + TypedAttributeProvider, + TypedAttributeSet, + typed_attribute, +) +from ._streams import ByteStream, Listener, UnreliableObjectStream + +if TYPE_CHECKING: + from ._tasks import TaskGroup + +IPAddressType: TypeAlias = str | IPv4Address | IPv6Address +IPSockAddrType: TypeAlias = tuple[str, int] +SockAddrType: TypeAlias = IPSockAddrType | str +UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType] +UNIXDatagramPacketType: TypeAlias = tuple[bytes, str] +T_Retval = TypeVar("T_Retval") + + +def _validate_socket( + sock_or_fd: socket.socket | int, + sock_type: socket.SocketKind, + addr_family: socket.AddressFamily = socket.AF_UNSPEC, + *, + require_connected: bool = False, + require_bound: bool = False, +) -> socket.socket: + if isinstance(sock_or_fd, int): + try: + sock = socket.socket(fileno=sock_or_fd) + except OSError as exc: + if exc.errno == errno.ENOTSOCK: + raise ValueError( + "the file descriptor does not refer to a socket" + ) from exc + elif require_connected: + raise ValueError("the socket must be connected") from exc + elif require_bound: + raise ValueError("the socket must be bound to a local address") from exc + else: + raise + elif isinstance(sock_or_fd, socket.socket): + sock = sock_or_fd + else: + raise TypeError( + f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead" + ) + + try: + if require_connected: + try: + sock.getpeername() + except OSError as exc: + raise ValueError("the socket must be connected") from exc + + if require_bound: + try: + if sock.family in (socket.AF_INET, socket.AF_INET6): + bound_addr = sock.getsockname()[1] + else: + bound_addr = sock.getsockname() + except OSError: + bound_addr = None + + if not bound_addr: + raise ValueError("the socket must be bound to a local address") + + if addr_family != socket.AF_UNSPEC and sock.family != addr_family: + raise ValueError( + f"address family mismatch: expected {addr_family.name}, got " + f"{sock.family.name}" + ) + + if sock.type != sock_type: + raise ValueError( + f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}" + ) + except BaseException: + # Avoid ResourceWarning from the locally constructed socket object + if isinstance(sock_or_fd, int): + sock.detach() + + raise + + sock.setblocking(False) + return sock + + +class SocketAttribute(TypedAttributeSet): + """ + .. attribute:: family + :type: socket.AddressFamily + + the address family of the underlying socket + + .. attribute:: local_address + :type: tuple[str, int] | str + + the local address the underlying socket is connected to + + .. attribute:: local_port + :type: int + + for IP based sockets, the local port the underlying socket is bound to + + .. attribute:: raw_socket + :type: socket.socket + + the underlying stdlib socket object + + .. attribute:: remote_address + :type: tuple[str, int] | str + + the remote address the underlying socket is connected to + + .. attribute:: remote_port + :type: int + + for IP based sockets, the remote port the underlying socket is connected to + """ + + family: AddressFamily = typed_attribute() + local_address: SockAddrType = typed_attribute() + local_port: int = typed_attribute() + raw_socket: socket.socket = typed_attribute() + remote_address: SockAddrType = typed_attribute() + remote_port: int = typed_attribute() + + +class _SocketProvider(TypedAttributeProvider): + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + from .._core._sockets import convert_ipv6_sockaddr as convert + + attributes: dict[Any, Callable[[], Any]] = { + SocketAttribute.family: lambda: self._raw_socket.family, + SocketAttribute.local_address: lambda: convert( + self._raw_socket.getsockname() + ), + SocketAttribute.raw_socket: lambda: self._raw_socket, + } + try: + peername: tuple[str, int] | None = convert(self._raw_socket.getpeername()) + except OSError: + peername = None + + # Provide the remote address for connected sockets + if peername is not None: + attributes[SocketAttribute.remote_address] = lambda: peername + + # Provide local and remote ports for IP based sockets + if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6): + attributes[SocketAttribute.local_port] = lambda: ( + self._raw_socket.getsockname()[1] + ) + if peername is not None: + remote_port = peername[1] + attributes[SocketAttribute.remote_port] = lambda: remote_port + + return attributes + + @property + @abstractmethod + def _raw_socket(self) -> socket.socket: + pass + + +class SocketStream(ByteStream, _SocketProvider): + """ + Transports bytes over a socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream: + """ + Wrap an existing socket object or file descriptor as a socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket stream + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True) + return await get_async_backend().wrap_stream_socket(sock) + + +class UNIXSocketStream(SocketStream): + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream: + """ + Wrap an existing socket object or file descriptor as a UNIX socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX socket stream + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_unix_stream_socket(sock) + + @abstractmethod + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + """ + Send file descriptors along with a message to the peer. + + :param message: a non-empty bytestring + :param fds: a collection of files (either numeric file descriptors or open file + or socket objects) + """ + + @abstractmethod + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + """ + Receive file descriptors along with a message from the peer. + + :param msglen: length of the message to expect from the peer + :param maxfds: maximum number of file descriptors to expect from the peer + :return: a tuple of (message, file descriptors) + """ + + +class SocketListener(Listener[SocketStream], _SocketProvider): + """ + Listens to incoming socket connections. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> SocketListener: + """ + Wrap an existing socket object or file descriptor as a socket listener. + + The newly created listener takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket listener + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True) + return await get_async_backend().wrap_listener_socket(sock) + + @abstractmethod + async def accept(self) -> SocketStream: + """Accept an incoming connection.""" + + async def serve( + self, + handler: Callable[[SocketStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + from .. import create_task_group + + async with AsyncExitStack() as stack: + if task_group is None: + task_group = await stack.enter_async_context(create_task_group()) + + while True: + stream = await self.accept() + task_group.start_soon(handler, stream) + + +class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider): + """ + Represents an unconnected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket: + """ + Wrap an existing socket object or file descriptor as a UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must be bound to a local address. + + :param sock_or_fd: a socket object or file descriptor + :return: a UDP socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True) + return await get_async_backend().wrap_udp_socket(sock) + + async def sendto(self, data: bytes, host: str, port: int) -> None: + """ + Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))). + + """ + return await self.send((data, (host, port))) + + +class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents an connected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket: + """ + Wrap an existing socket object or file descriptor as a connected UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UDP socket + + """ + sock = _validate_socket( + sock_or_fd, + socket.SOCK_DGRAM, + require_connected=True, + ) + return await get_async_backend().wrap_connected_udp_socket(sock) + + +class UNIXDatagramSocket( + UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider +): + """ + Represents an unconnected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> UNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX datagram socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX) + return await get_async_backend().wrap_unix_datagram_socket(sock) + + async def sendto(self, data: bytes, path: str) -> None: + """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path)).""" + return await self.send((data, path)) + + +class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents a connected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> ConnectedUNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a connected UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UNIX datagram socket + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_connected_unix_datagram_socket(sock) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py new file mode 100644 index 0000000..daf335d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py @@ -0,0 +1,237 @@ +from __future__ import annotations + +from abc import ABCMeta, abstractmethod +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar + +from .._core._exceptions import EndOfStream +from .._core._typedattr import TypedAttributeProvider +from ._resources import AsyncResource + +if TYPE_CHECKING: + from ._tasks import TaskGroup + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class UnreliableObjectReceiveStream( + AsyncResource, TypedAttributeProvider, Generic[T_co] +): + """ + An interface for receiving objects. + + This interface makes no guarantees that the received messages arrive in the order in + which they were sent, or that no messages are missed. + + Asynchronously iterating over objects of this type will yield objects matching the + given type parameter. + """ + + def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]: + return self + + async def __anext__(self) -> T_co: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self) -> T_co: + """ + Receive the next item. + + :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly + closed + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectSendStream( + AsyncResource, TypedAttributeProvider, Generic[T_contra] +): + """ + An interface for sending objects. + + This interface makes no guarantees that the messages sent will reach the + recipient(s) in the same order in which they were sent, or at all. + """ + + @abstractmethod + async def send(self, item: T_contra) -> None: + """ + Send an item to the peer(s). + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if the send stream has been explicitly + closed + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectStream( + UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item] +): + """ + A bidirectional message stream which does not guarantee the order or reliability of + message delivery. + """ + + +class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]): + """ + A receive message stream which guarantees that messages are received in the same + order in which they were sent, and that no messages are missed. + """ + + +class ObjectSendStream(UnreliableObjectSendStream[T_contra]): + """ + A send message stream which guarantees that messages are delivered in the same order + in which they were sent, without missing any messages in the middle. + """ + + +class ObjectStream( + ObjectReceiveStream[T_Item], + ObjectSendStream[T_Item], + UnreliableObjectStream[T_Item], +): + """ + A bidirectional message stream which guarantees the order and reliability of message + delivery. + """ + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +class ByteReceiveStream(AsyncResource, TypedAttributeProvider): + """ + An interface for receiving bytes from a single peer. + + Iterating this byte stream will yield a byte string of arbitrary length, but no more + than 65536 bytes. + """ + + def __aiter__(self) -> ByteReceiveStream: + return self + + async def __anext__(self) -> bytes: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self, max_bytes: int = 65536) -> bytes: + """ + Receive at most ``max_bytes`` bytes from the peer. + + .. note:: Implementers of this interface should not return an empty + :class:`bytes` object, and users should ignore them. + + :param max_bytes: maximum number of bytes to receive (must be a positive + integer) + :return: the received bytes + :raises ValueError: if ``max_bytes`` is less than 1 + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + """ + + +class ByteSendStream(AsyncResource, TypedAttributeProvider): + """An interface for sending bytes to a single peer.""" + + @abstractmethod + async def send(self, item: bytes) -> None: + """ + Send the given bytes to the peer. + + :param item: the bytes to send + """ + + +class ByteStream(ByteReceiveStream, ByteSendStream): + """A bidirectional byte stream.""" + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +#: Type alias for all unreliable bytes-oriented receive streams. +AnyUnreliableByteReceiveStream: TypeAlias = ( + UnreliableObjectReceiveStream[bytes] | ByteReceiveStream +) +#: Type alias for all unreliable bytes-oriented send streams. +AnyUnreliableByteSendStream: TypeAlias = ( + UnreliableObjectSendStream[bytes] | ByteSendStream +) +#: Type alias for all unreliable bytes-oriented streams. +AnyUnreliableByteStream: TypeAlias = UnreliableObjectStream[bytes] | ByteStream +#: Type alias for all bytes-oriented receive streams. +AnyByteReceiveStream: TypeAlias = ObjectReceiveStream[bytes] | ByteReceiveStream +#: Type alias for all bytes-oriented send streams. +AnyByteSendStream: TypeAlias = ObjectSendStream[bytes] | ByteSendStream +#: Type alias for all bytes-oriented streams. +AnyByteStream: TypeAlias = ObjectStream[bytes] | ByteStream + + +class Listener(AsyncResource, TypedAttributeProvider, Generic[T_co]): + """An interface for objects that let you accept incoming connections.""" + + @abstractmethod + async def serve( + self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None + ) -> None: + """ + Accept incoming connections as they come in and start tasks to handle them. + + :param handler: a callable that will be used to handle each accepted connection + :param task_group: the task group that will be used to start tasks for handling + each accepted connection (if omitted, an ad-hoc task group will be created) + """ + + +class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ObjectStream[T_co]: + """ + Connect to the remote endpoint. + + :return: an object stream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +class ByteStreamConnectable(metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ByteStream: + """ + Connect to the remote endpoint. + + :return: a bytestream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +#: Type alias for all connectables returning bytestreams or bytes-oriented object streams +AnyByteStreamConnectable: TypeAlias = ( + ObjectStreamConnectable[bytes] | ByteStreamConnectable +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py new file mode 100644 index 0000000..555f15f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from abc import abstractmethod +from signal import Signals +from typing import TYPE_CHECKING + +from ._resources import AsyncResource + +if TYPE_CHECKING: + from ._streams import ByteReceiveStream, ByteSendStream + + +class Process(AsyncResource): + """An asynchronous version of :class:`subprocess.Popen`.""" + + @abstractmethod + async def wait(self) -> int: + """ + Wait until the process exits. + + :return: the exit code of the process + """ + + @abstractmethod + def terminate(self) -> None: + """ + Terminates the process, gracefully if possible. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGTERM`` to the process. + + .. seealso:: :meth:`subprocess.Popen.terminate` + """ + + @abstractmethod + def kill(self) -> None: + """ + Kills the process. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGKILL`` to the process. + + .. seealso:: :meth:`subprocess.Popen.kill` + """ + + @abstractmethod + def send_signal(self, signal: Signals) -> None: + """ + Send a signal to the subprocess. + + .. seealso:: :meth:`subprocess.Popen.send_signal` + + :param signal: the signal number (e.g. :data:`signal.SIGHUP`) + """ + + @property + @abstractmethod + def pid(self) -> int: + """The process ID of the process.""" + + @property + @abstractmethod + def returncode(self) -> int | None: + """ + The return code of the process. If the process has not yet terminated, this will + be ``None``. + """ + + @property + @abstractmethod + def stdin(self) -> ByteSendStream | None: + """The stream for the standard input of the process.""" + + @property + @abstractmethod + def stdout(self) -> ByteReceiveStream | None: + """The stream for the standard output of the process.""" + + @property + @abstractmethod + def stderr(self) -> ByteReceiveStream | None: + """The stream for the standard error output of the process.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py new file mode 100644 index 0000000..0a019e2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py @@ -0,0 +1,223 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Callable, Coroutine +from contextvars import Context +from types import TracebackType +from typing import TYPE_CHECKING, Any, Literal, Protocol, final, overload + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from .._core._tasks import CancelScope, TaskHandle + +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True, default=None) +PosArgsT = TypeVarTuple("PosArgsT") + + +def get_coro_name(coro: Coroutine[Any, Any, object], override: object = None) -> str: + if override is not None: + return str(override) + + try: + cr_frame = coro.cr_frame # type: ignore[attr-defined] + module = cr_frame.f_globals["__name__"] + except (AttributeError, KeyError): + module = None + + qualname = getattr(coro, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +def get_callable_name(func: Callable, override: object = None) -> str: + if override is not None: + return str(override) + + module = getattr(func, "__module__", None) + qualname = getattr(func, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +def call_for_coroutine( + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + **kwargs: Any, +) -> Coroutine[Any, Any, T_co]: + """ + Call the given function with the given positional and keyword arguments. + + :return: the resulting coroutine + :raises TypeError: if the return value was not a coroutine object + + """ + coro = func(*args, **kwargs) + if not isinstance(coro, Coroutine): + prefix = f"{func.__module__}." if hasattr(func, "__module__") else "" + raise TypeError( + f"Expected {prefix}{func.__qualname__}() to return a coroutine, but " + f"the return value ({coro!r}) is not a coroutine object" + ) + + return coro + + +class TaskStatus(Protocol[T_contra]): + @overload + def started(self: TaskStatus[None]) -> None: ... + + @overload + def started(self, value: T_contra) -> None: ... + + def started(self, value: T_contra | None = None) -> None: + """ + Signal that the task has started. + + :param value: object passed back to the starter of the task + """ + + +class TaskGroup(metaclass=ABCMeta): + """ + Groups several asynchronous tasks together. + + :ivar cancel_scope: the cancel scope inherited by all child tasks + :vartype cancel_scope: CancelScope + + .. note:: On asyncio, support for eager task factories is considered to be + **experimental**. In particular, they don't follow the usual semantics of new + tasks being scheduled on the next iteration of the event loop, and may thus + cause unexpected behavior in code that wasn't written with such semantics in + mind. + """ + + cancel_scope: CancelScope + + def cancel(self, reason: str | None = None) -> None: + """ + Cancel this task group's cancel scope immediately. + + This is a shortcut for calling ``.cancel_scope.cancel()`` on the task group. + + :param reason: a message describing the reason for the cancellation + + .. versionadded:: 4.14.0 + + """ + self.cancel_scope.cancel(reason) + + @abstractmethod + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + """ + Create a new task from a coroutine object and schedule it to run. + + :param coro: a coroutine object + :param name: optional name to give the task + :param context: optional context to run the task in + :return: a task handle + + .. versionadded:: 4.14.0 + """ + + @final + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> TaskHandle[T_co]: + """ + Start a new task in this task group. + + :param func: a coroutine function + :param args: positional arguments to call the function with + :param name: name of the task, for the purposes of introspection and debugging + :return: a task handle + + .. versionadded:: 3.0 + .. versionchanged:: 4.14.0 + This method now returns a task handle. + + """ + final_name = get_callable_name(func, name) + return self.create_task(call_for_coroutine(func, args), name=final_name) + + @overload + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[False] = ..., + ) -> Any: ... + + @overload + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[True], + ) -> TaskHandle[T_co, Any]: ... + + @abstractmethod + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + """ + Start a new task and wait until it signals for readiness. + + The target callable must accept a keyword argument ``task_status`` (of type + :class:`TaskStatus`). Awaiting on this method will return whatever was passed to + ``task_status.started()`` (``None`` by default). + + .. note:: The :class:`TaskStatus` class is generic, and the type argument should + indicate the type of the value that will be passed to + ``task_status.started()``. + + :param func: a coroutine function that accepts the ``task_status`` keyword + argument + :param args: positional arguments to call the function with + :param name: an optional name for the task, for introspection and debugging + :param return_handle: if ``True``, return a :class:`TaskHandle` which also + contains the start value in ``start_value`` + :return: the value passed to ``task_status.started()`` + :raises RuntimeError: if the task finishes without calling + ``task_status.started()`` + + .. seealso:: :ref:`start_initialize` + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def __aenter__(self) -> TaskGroup: + """Enter the task group context and allow starting new tasks.""" + + @abstractmethod + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + """Exit the task group context waiting for all tasks to finish.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py new file mode 100644 index 0000000..b18d835 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import sys +import types +from abc import ABCMeta, abstractmethod +from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable +from typing import Any, TypeVar + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +_T = TypeVar("_T") + + +class TestRunner(metaclass=ABCMeta): + """ + Encapsulates a running event loop. Every call made through this object will use the + same event loop. + """ + + def __enter__(self) -> Self: + return self + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool | None: ... + + @abstractmethod + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[_T, Any]], + kwargs: dict[str, Any], + ) -> Iterable[_T]: + """ + Run an async generator fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: an iterator yielding the value yielded from the async generator + """ + + @abstractmethod + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, _T]], + kwargs: dict[str, Any], + ) -> _T: + """ + Run an async fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: the return value of the fixture function + """ + + @abstractmethod + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + """ + Run an async test function. + + :param test_func: the test function + :param kwargs: keyword arguments to call the test function with + """ + + @abstractmethod + def is_running(self) -> bool: + """ + Check if the test runner is running. + + :return: ``True`` if the coroutine is currently being run, ``False`` otherwise. + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py new file mode 100644 index 0000000..f163d81 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py @@ -0,0 +1,588 @@ +from __future__ import annotations + +__all__ = ( + "BlockingPortal", + "BlockingPortalProvider", + "check_cancelled", + "run", + "run_sync", + "start_blocking_portal", +) + +import sys +from collections.abc import Awaitable, Callable, Coroutine, Generator +from concurrent.futures import Future +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + contextmanager, +) +from contextvars import Context +from dataclasses import dataclass, field +from functools import partial +from inspect import isawaitable +from threading import Lock, Thread, current_thread, get_ident +from types import TracebackType +from typing import ( + Any, + Generic, + TypeVar, + cast, + overload, +) + +from ._core._eventloop import ( + get_cancelled_exc_class, + threadlocals, +) +from ._core._eventloop import run as run_eventloop +from ._core._exceptions import NoEventLoopError +from ._core._synchronization import Event +from ._core._tasks import CancelScope, create_task_group +from .abc._tasks import TaskStatus +from .lowlevel import EventLoopToken, current_token + +if sys.version_info >= (3, 11): + from typing import Self, TypeVarTuple, Unpack +else: + from typing_extensions import Self, TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") + + +def _token_or_error(token: EventLoopToken | None) -> EventLoopToken: + if token is not None: + return token + + try: + return threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "Not running inside an AnyIO worker thread, and no event loop token was " + "provided" + ) from None + + +def run( + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_co: + """ + Call a coroutine function from a worker thread. + + :param func: a coroutine function + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the coroutine function + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_async_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_Retval: + """ + Call a function in the event loop thread from a worker thread. + + :param func: a callable + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the callable + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_sync_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +class _BlockingAsyncContextManager(AbstractContextManager, Generic[T_co]): + _enter_future: Future[T_co] + _exit_future: Future[bool | None] + _exit_event: Event + _exit_exc_info: tuple[ + type[BaseException] | None, BaseException | None, TracebackType | None + ] = (None, None, None) + + def __init__( + self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal + ): + self._async_cm = async_cm + self._portal = portal + + async def run_async_cm(self) -> bool | None: + try: + self._exit_event = Event() + value = await self._async_cm.__aenter__() + except BaseException as exc: + self._enter_future.set_exception(exc) + raise + else: + self._enter_future.set_result(value) + + try: + # Wait for the sync context manager to exit. + # This next statement can raise `get_cancelled_exc_class()` if + # something went wrong in a task group in this async context + # manager. + await self._exit_event.wait() + finally: + # In case of cancellation, it could be that we end up here before + # `_BlockingAsyncContextManager.__exit__` is called, and an + # `_exit_exc_info` has been set. + result = await self._async_cm.__aexit__(*self._exit_exc_info) + + return result + + def __enter__(self) -> T_co: + self._enter_future = Future() + self._exit_future = self._portal.start_task_soon(self.run_async_cm) + return self._enter_future.result() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + /, + ) -> bool | None: + self._exit_exc_info = exc_type, exc_value, traceback + self._portal.call(self._exit_event.set) + return self._exit_future.result() + + +class _BlockingPortalTaskStatus(TaskStatus): + def __init__(self, future: Future): + self._future = future + + def started(self, value: object = None) -> None: + self._future.set_result(value) + + +class BlockingPortal: + """ + An object that lets external threads run code in an asynchronous event loop. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + def __init__(self) -> None: + self._token = current_token() + self._event_loop_thread_id: int | None = get_ident() + self._stop_event = Event() + self._task_group = create_task_group() + + async def __aenter__(self) -> Self: + await self._task_group.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + await self.stop() + return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) + + def _check_running(self) -> None: + if self._event_loop_thread_id is None: + raise RuntimeError("This portal is not running") + if self._event_loop_thread_id == get_ident(): + raise RuntimeError( + "This method cannot be called from the event loop thread" + ) + + async def sleep_until_stopped(self) -> None: + """Sleep until :meth:`stop` is called.""" + await self._stop_event.wait() + + async def stop(self, cancel_remaining: bool = False) -> None: + """ + Signal the portal to shut down. + + This marks the portal as no longer accepting new calls and exits from + :meth:`sleep_until_stopped`. + + :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False`` + to let them finish before returning + + """ + self._event_loop_thread_id = None + self._stop_event.set() + if cancel_remaining: + self._task_group.cancel_scope.cancel("the blocking portal is shutting down") + + async def _call_func( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + future: Future[T_Retval], + ) -> None: + event_loop_thread_id = self._event_loop_thread_id + + def callback(f: Future[T_Retval]) -> None: + if f.cancelled(): + if event_loop_thread_id == get_ident(): + scope.cancel("the future was cancelled") + elif event_loop_thread_id is not None: + run_sync( + scope.cancel, "the future was cancelled", token=self._token + ) + + try: + retval_or_awaitable = func(*args, **kwargs) + if isawaitable(retval_or_awaitable): + with CancelScope() as scope: + future.add_done_callback(callback) + retval = await retval_or_awaitable + else: + retval = retval_or_awaitable + except get_cancelled_exc_class(): + future.cancel() + future.set_running_or_notify_cancel() + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + # Let base exceptions fall through + if not isinstance(exc, Exception): + raise + else: + if not future.cancelled(): + future.set_result(retval) + finally: + scope = None # type: ignore[assignment] + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + """ + Spawn a new task using the given callable. + + :param func: a callable + :param args: positional arguments to be passed to the callable + :param kwargs: keyword arguments to be passed to the callable + :param name: name of the task (will be coerced to a string if not ``None``) + :param future: a future that will resolve to the return value of the callable, + or the exception raised during its execution + + """ + run_sync( + partial(self._task_group.start_soon, name=name), + self._call_func, + func, + args, + kwargs, + future, + token=self._token, + ) + + @overload + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + ) -> T_Retval: ... + + @overload + def call( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: ... + + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + ) -> T_Retval: + """ + Call the given function in the event loop thread. + + If the callable returns a coroutine object, it is awaited on. + + :param func: any callable + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + + """ + return cast(T_Retval, self.start_task_soon(func, *args).result()) + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: + """ + Start a task in the portal's task group. + + The task will be run inside a cancel scope which can be cancelled by cancelling + the returned future. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a future that resolves with the return value of the callable if the + task completes successfully, or with the exception raised in the task + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + :rtype: concurrent.futures.Future[T_Retval] + + .. versionadded:: 3.0 + + """ + self._check_running() + f: Future[T_Retval] = Future() + self._spawn_task_from_thread(func, args, {}, name, f) + return f + + def start_task( + self, + func: Callable[..., Awaitable[T_Retval]], + *args: object, + name: object = None, + ) -> tuple[Future[T_Retval], Any]: + """ + Start a task in the portal's task group and wait until it signals for readiness. + + This method works the same way as :meth:`.abc.TaskGroup.start`. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a tuple of (future, task_status_value) where the ``task_status_value`` + is the value passed to ``task_status.started()`` from within the target + function + :rtype: tuple[concurrent.futures.Future[T_Retval], Any] + + .. versionadded:: 3.0 + + """ + + def task_done(future: Future[T_Retval]) -> None: + if not task_status_future.done(): + if future.cancelled(): + task_status_future.cancel() + elif future.exception(): + task_status_future.set_exception(future.exception()) + else: + exc = RuntimeError( + "Task exited without calling task_status.started()" + ) + task_status_future.set_exception(exc) + + self._check_running() + task_status_future: Future = Future() + task_status = _BlockingPortalTaskStatus(task_status_future) + f: Future = Future() + f.add_done_callback(task_done) + self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f) + return f, task_status_future.result() + + def wrap_async_context_manager( + self, cm: AbstractAsyncContextManager[T_co] + ) -> AbstractContextManager[T_co]: + """ + Wrap an async context manager as a synchronous context manager via this portal. + + Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping + in the middle until the synchronous context manager exits. + + :param cm: an asynchronous context manager + :return: a synchronous context manager + + .. versionadded:: 2.1 + + """ + return _BlockingAsyncContextManager(cm, self) + + +@dataclass +class BlockingPortalProvider: + """ + A manager for a blocking portal. Used as a context manager. The first thread to + enter this context manager causes a blocking portal to be started with the specific + parameters, and the last thread to exit causes the portal to be shut down. Thus, + there will be exactly one blocking portal running in this context as long as at + least one thread has entered this context manager. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + + .. versionadded:: 4.4 + """ + + backend: str = "asyncio" + backend_options: dict[str, Any] | None = None + _lock: Lock = field(init=False, default_factory=Lock) + _leases: int = field(init=False, default=0) + _portal: BlockingPortal = field(init=False) + _portal_cm: AbstractContextManager[BlockingPortal] | None = field( + init=False, default=None + ) + + def __enter__(self) -> BlockingPortal: + with self._lock: + if self._portal_cm is None: + self._portal_cm = start_blocking_portal( + self.backend, self.backend_options + ) + self._portal = self._portal_cm.__enter__() + + self._leases += 1 + return self._portal + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + portal_cm: AbstractContextManager[BlockingPortal] | None = None + with self._lock: + assert self._portal_cm + assert self._leases > 0 + self._leases -= 1 + if not self._leases: + portal_cm = self._portal_cm + self._portal_cm = None + del self._portal + + if portal_cm: + portal_cm.__exit__(None, None, None) + + +@contextmanager +def start_blocking_portal( + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, + *, + name: str | None = None, +) -> Generator[BlockingPortal, Any, None]: + """ + Start a new event loop in a new thread and run a blocking portal in its main task. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + :param name: name of the thread + :return: a context manager that yields a blocking portal + + .. versionchanged:: 3.0 + Usage as a context manager is now required. + + """ + + async def run_portal() -> None: + async with BlockingPortal() as portal_: + if name is None: + current_thread().name = f"{backend}-portal-{id(portal_):x}" + + future.set_result(portal_) + await portal_.sleep_until_stopped() + + def run_blocking_portal() -> None: + if future.set_running_or_notify_cancel(): + try: + run_eventloop( + run_portal, backend=backend, backend_options=backend_options + ) + except BaseException as exc: + if not future.done(): + future.set_exception(exc) + + future: Future[BlockingPortal] = Future() + kwargs: dict[str, Any] = {} + if sys.version_info >= (3, 14): + kwargs["context"] = Context() + + thread = Thread(target=run_blocking_portal, daemon=True, name=name, **kwargs) + thread.start() + try: + cancel_remaining_tasks = False + portal = future.result() + try: + yield portal + except BaseException: + cancel_remaining_tasks = True + raise + finally: + try: + portal.call(portal.stop, cancel_remaining_tasks) + except RuntimeError: + pass + finally: + thread.join() + + +def check_cancelled() -> None: + """ + Check if the cancel scope of the host task's running the current worker thread has + been cancelled. + + If the host task's current cancel scope has indeed been cancelled, the + backend-specific cancellation exception will be raised. + + :raises RuntimeError: if the current thread was not spawned by + :func:`.to_thread.run_sync` + + """ + try: + token: EventLoopToken = threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "This function can only be called inside an AnyIO worker thread" + ) from None + + token.backend_class.check_cancelled() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py new file mode 100644 index 0000000..b0bdfb4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/functools.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +__all__ = ( + "AsyncCacheInfo", + "AsyncCacheParameters", + "AsyncLRUCacheWrapper", + "cache", + "lru_cache", + "reduce", +) + +import functools +from collections import OrderedDict +from collections.abc import ( + AsyncIterable, + Awaitable, + Callable, + Coroutine, + Hashable, + Iterable, +) +from functools import update_wrapper +from inspect import iscoroutinefunction +from typing import ( + Any, + Generic, + NamedTuple, + ParamSpec, + TypedDict, + TypeVar, + cast, + final, + overload, +) +from weakref import WeakKeyDictionary + +from ._core._eventloop import current_time +from ._core._synchronization import Lock +from .lowlevel import RunVar, checkpoint + +T = TypeVar("T") +S = TypeVar("S") +P = ParamSpec("P") +lru_cache_items: RunVar[ + WeakKeyDictionary[ + AsyncLRUCacheWrapper[Any, Any], + OrderedDict[ + Hashable, + tuple[_InitialMissingType, Lock, float | None] + | tuple[Any, None, float | None], + ], + ] +] = RunVar("lru_cache_items") + + +class _InitialMissingType: + pass + + +initial_missing: _InitialMissingType = _InitialMissingType() + + +class AsyncCacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int | None + currsize: int + ttl: int | None + + +class AsyncCacheParameters(TypedDict): + maxsize: int | None + typed: bool + always_checkpoint: bool + ttl: int | None + + +class _LRUMethodWrapper(Generic[T]): + def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object): + self.__wrapper = wrapper + self.__instance = instance + + def cache_info(self) -> AsyncCacheInfo: + return self.__wrapper.cache_info() + + def cache_parameters(self) -> AsyncCacheParameters: + return self.__wrapper.cache_parameters() + + def cache_clear(self) -> None: + self.__wrapper.cache_clear() + + async def __call__(self, *args: Any, **kwargs: Any) -> T: + if self.__instance is None: + return await self.__wrapper(*args, **kwargs) + + return await self.__wrapper(self.__instance, *args, **kwargs) + + +@final +class AsyncLRUCacheWrapper(Generic[P, T]): + def __init__( + self, + func: Callable[P, Awaitable[T]], + maxsize: int | None, + typed: bool, + always_checkpoint: bool, + ttl: int | None, + ): + self.__wrapped__ = func + self._hits: int = 0 + self._misses: int = 0 + self._maxsize = max(maxsize, 0) if maxsize is not None else None + self._currsize: int = 0 + self._typed = typed + self._always_checkpoint = always_checkpoint + self._ttl = ttl + update_wrapper(self, func) + + def cache_info(self) -> AsyncCacheInfo: + return AsyncCacheInfo( + self._hits, self._misses, self._maxsize, self._currsize, self._ttl + ) + + def cache_parameters(self) -> AsyncCacheParameters: + return { + "maxsize": self._maxsize, + "typed": self._typed, + "always_checkpoint": self._always_checkpoint, + "ttl": self._ttl, + } + + def cache_clear(self) -> None: + if cache := lru_cache_items.get(None): + cache.pop(self, None) + self._hits = self._misses = self._currsize = 0 + + async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: + # Easy case first: if maxsize == 0, no caching is done + if self._maxsize == 0: + value = await self.__wrapped__(*args, **kwargs) + self._misses += 1 + return value + + # The key is constructed as a flat tuple to avoid memory overhead + key: tuple[Any, ...] = args + if kwargs: + # initial_missing is used as a separator + key += (initial_missing,) + sum(kwargs.items(), ()) + + if self._typed: + key += tuple(type(arg) for arg in args) + if kwargs: + key += (initial_missing,) + tuple(type(val) for val in kwargs.values()) + + try: + cache = lru_cache_items.get() + except LookupError: + cache = WeakKeyDictionary() + lru_cache_items.set(cache) + + try: + cache_entry = cache[self] + except KeyError: + cache_entry = cache[self] = OrderedDict() + + cached_value: T | _InitialMissingType + try: + cached_value, lock, expires_at = cache_entry[key] + except KeyError: + # We're the first task to call this function + cached_value, lock, expires_at = ( + initial_missing, + Lock(fast_acquire=not self._always_checkpoint), + None, + ) + cache_entry[key] = cached_value, lock, expires_at + + if lock is None: + if expires_at is not None and current_time() >= expires_at: + self._currsize -= 1 + cached_value, lock, expires_at = ( + initial_missing, + Lock(fast_acquire=not self._always_checkpoint), + None, + ) + cache_entry[key] = cached_value, lock, expires_at + else: + # The value was already cached + self._hits += 1 + cache_entry.move_to_end(key) + if self._always_checkpoint: + await checkpoint() + + return cast(T, cached_value) + + async with lock: + # Check if another task filled the cache while we acquired the lock + if (cached_value := cache_entry[key][0]) is initial_missing: + self._misses += 1 + if self._maxsize is not None and self._currsize >= self._maxsize: + cache_entry.popitem(last=False) + else: + self._currsize += 1 + + value = await self.__wrapped__(*args, **kwargs) + expires_at = ( + current_time() + self._ttl if self._ttl is not None else None + ) + cache_entry[key] = value, None, expires_at + else: + # Another task filled the cache while we were waiting for the lock + self._hits += 1 + cache_entry.move_to_end(key) + value = cast(T, cached_value) + + return value + + def __get__( + self, instance: object, owner: type | None = None + ) -> _LRUMethodWrapper[T]: + wrapper = _LRUMethodWrapper(self, instance) + update_wrapper(wrapper, self.__wrapped__) + return wrapper + + +class _LRUCacheWrapper: + def __init__( + self, maxsize: int | None, typed: bool, always_checkpoint: bool, ttl: int | None + ): + self._maxsize = maxsize + self._typed = typed + self._always_checkpoint = always_checkpoint + self._ttl = ttl + + @overload + def __call__( # type: ignore[overload-overlap] + self, func: Callable[P, Coroutine[Any, Any, T]], / + ) -> AsyncLRUCacheWrapper[P, T]: ... + + @overload + def __call__( + self, func: Callable[..., T], / + ) -> functools._lru_cache_wrapper[T]: ... + + def __call__( + self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], / + ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]: + if iscoroutinefunction(f): + return AsyncLRUCacheWrapper( + f, self._maxsize, self._typed, self._always_checkpoint, self._ttl + ) + + return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f) # type: ignore[arg-type] + + +@overload +def cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def cache(func: Callable[..., Any] | Callable[P, Coroutine[Any, Any, Any]], /) -> Any: + """ + A convenient shortcut for :func:`lru_cache` with ``maxsize=None``. + + This is the asynchronous equivalent to :func:`functools.cache`. + + """ + return lru_cache(maxsize=None)(func) + + +@overload +def lru_cache( + *, + maxsize: int | None = ..., + typed: bool = ..., + always_checkpoint: bool = ..., + ttl: int | None = ..., +) -> _LRUCacheWrapper: ... + + +@overload +def lru_cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def lru_cache( + func: Callable[..., Coroutine[Any, Any, Any]] | Callable[..., Any] | None = None, + /, + *, + maxsize: int | None = 128, + typed: bool = False, + always_checkpoint: bool = False, + ttl: int | None = None, +) -> Any: + """ + An asynchronous version of :func:`functools.lru_cache`. + + If a synchronous function is passed, the standard library + :func:`functools.lru_cache` is applied instead. + + :param always_checkpoint: if ``True``, every call to the cached function will be + guaranteed to yield control to the event loop at least once + :param ttl: time in seconds after which to invalidate cache entries + + .. note:: Caches and locks are managed on a per-event loop basis. + + """ + if func is None: + return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl) + + if not callable(func): + raise TypeError("the first argument must be callable") + + return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl)(func) + + +@overload +async def reduce( + function: Callable[[T, S], Awaitable[T]], + iterable: Iterable[S] | AsyncIterable[S], + /, + initial: T, +) -> T: ... + + +@overload +async def reduce( + function: Callable[[T, T], Awaitable[T]], + iterable: Iterable[T] | AsyncIterable[T], + /, +) -> T: ... + + +async def reduce( # type: ignore[misc] + function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]], + iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S], + /, + initial: T | _InitialMissingType = initial_missing, +) -> T: + """ + Asynchronous version of :func:`functools.reduce`. + + :param function: a coroutine function that takes two arguments: the accumulated + value and the next element from the iterable + :param iterable: an iterable or async iterable + :param initial: the initial value (if missing, the first element of the iterable is + used as the initial value) + + """ + element: Any + function_called = False + if isinstance(iterable, AsyncIterable): + async_it = iterable.__aiter__() + if initial is initial_missing: + try: + value = cast(T, await async_it.__anext__()) + except StopAsyncIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + async for element in async_it: + value = await function(value, element) + function_called = True + elif isinstance(iterable, Iterable): + it = iter(iterable) + if initial is initial_missing: + try: + value = cast(T, next(it)) + except StopIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + for element in it: + value = await function(value, element) + function_called = True + else: + raise TypeError("reduce() argument 2 must be an iterable or async iterable") + + # Make sure there is at least one checkpoint, even if an empty iterable and an + # initial value were given + if not function_called: + await checkpoint() + + return value diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py new file mode 100644 index 0000000..2a10cfd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/itertools.py @@ -0,0 +1,629 @@ +from __future__ import annotations + +__all__ = ( + "Chain", + "accumulate", + "batched", + "combinations", + "combinations_with_replacement", + "compress", + "count", + "cycle", + "dropwhile", + "filterfalse", + "groupby", + "islice", + "pairwise", + "permutations", + "product", + "repeat", + "starmap", + "takewhile", + "tee", + "zip_longest", +) + +import itertools +import operator +import sys +from collections.abc import ( + AsyncGenerator, + AsyncIterable, + AsyncIterator, + Awaitable, + Callable, + Iterable, + Iterator, +) +from dataclasses import dataclass, field +from typing import Any, Generic, TypeVar, cast, overload + +from ._core._synchronization import Lock +from ._core._tasks import CancelScope +from .lowlevel import cancel_shielded_checkpoint, checkpoint, checkpoint_if_cancelled + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +T = TypeVar("T") +R = TypeVar("R") +_tee_end = sentinel("_tee_end") + + +@dataclass(eq=False) +class _IterableAsyncIterator(AsyncIterator[T]): + iterator: Iterator[T] + + async def __anext__(self) -> T: + await checkpoint_if_cancelled() + try: + result = next(self.iterator) + except StopIteration: + await cancel_shielded_checkpoint() + raise StopAsyncIteration from None + + await cancel_shielded_checkpoint() + return result + + +def _iterate(iterable: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]: + if isinstance(iterable, AsyncIterator): + return iterable + + if isinstance(iterable, AsyncIterable): + return iterable.__aiter__() + + return _IterableAsyncIterator(iter(iterable)) + + +@dataclass(eq=False) +class _TeeLink(Generic[T]): + value: object | None = None + next: _TeeLink[T] | None = None + filled: bool = False + + +@dataclass(eq=False) +class _TeeState(Generic[T]): + iterator: AsyncIterator[T] + lock: Lock = field(default_factory=Lock) + + async def fill(self, link: _TeeLink[T]) -> bool: + if link.filled: + return False + + async with self.lock: + if link.filled: + return True + + link.value = await anext(self.iterator, _tee_end) + if link.value is not _tee_end: + link.next = _TeeLink() + + link.filled = True + return True + + +class _TeeAsyncIterator(AsyncIterator[T]): + _state: _TeeState[T] + _link: _TeeLink[T] + _element_yielded: bool + + def __init__( + self, iterable: Iterable[T] | AsyncIterable[T] | _TeeAsyncIterator[T] + ) -> None: + if isinstance(iterable, _TeeAsyncIterator): + self._state = iterable._state + self._link = iterable._link + else: + self._state = _TeeState(_iterate(iterable)) + self._link = _TeeLink() + + self._element_yielded = False + + async def __anext__(self) -> T: + had_yieldpoint = await self._state.fill(self._link) + if self._link.value is _tee_end: + if not self._element_yielded: + await checkpoint() + + raise StopAsyncIteration + + if not had_yieldpoint: + await checkpoint_if_cancelled() + + self._element_yielded = True + value = cast(T, self._link.value) + next_link = self._link.next + assert next_link is not None + self._link = next_link + if not had_yieldpoint: + await cancel_shielded_checkpoint() + + return value + + +async def _operator_add(x: T, y: T) -> T: + return operator.add(x, y) + + +async def accumulate( + iterable: Iterable[T] | AsyncIterable[T], + function: Callable[[T, T], Awaitable[T]] = _operator_add, + *, + initial: T | None = None, +) -> AsyncGenerator[T, None]: + iterator = _iterate(iterable) + if initial is None: + try: + total = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + else: + await checkpoint_if_cancelled() + total = initial + await cancel_shielded_checkpoint() + + yield total + + async for element in iterator: + total = await function(total, element) + yield total + + +async def batched( + iterable: Iterable[T] | AsyncIterable[T], n: int, *, strict: bool = False +) -> AsyncGenerator[tuple[T, ...], None]: + if n < 1: + raise ValueError("n must be at least one") + + iterator = _iterate(iterable) + + while True: + batch: list[T] = [] + for _ in range(n): + try: + batch.append(await anext(iterator)) + except StopAsyncIteration: + if not batch: + await checkpoint() + return + if strict: + raise ValueError("batched(): incomplete batch") from None + + yield tuple(batch) + return + + yield tuple(batch) + + +class Chain: + def __call__( + self, *iterables: Iterable[T] | AsyncIterable[T] + ) -> AsyncGenerator[T, None]: + return self.from_iterable(iterables) + + async def from_iterable( + self, + iterables: ( + Iterable[Iterable[T] | AsyncIterable[T]] + | AsyncIterable[Iterable[T] | AsyncIterable[T]] + ), + ) -> AsyncGenerator[T, None]: + element_yielded = False + outer_iter = _iterate(iterables) + + try: + async for iterable in outer_iter: + async for element in _iterate(iterable): + element_yielded = True + yield element + finally: + aclose = getattr(outer_iter, "aclose", None) + if aclose is not None: + with CancelScope(shield=True): + await aclose() + + if not element_yielded: + await checkpoint() + + +chain: Chain = Chain() + + +async def combinations( + iterable: Iterable[T] | AsyncIterable[T], r: int +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + async for combination in _iterate(itertools.combinations(pool, r)): + yield combination + + +async def combinations_with_replacement( + iterable: Iterable[T] | AsyncIterable[T], r: int +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + async for combination in _iterate(itertools.combinations_with_replacement(pool, r)): + yield combination + + +async def compress( + data: Iterable[T] | AsyncIterable[T], + selectors: Iterable[object] | AsyncIterable[object], +) -> AsyncGenerator[T, None]: + data_iterator = _iterate(data) + selector_iterator = _iterate(selectors) + element_yielded = False + + while True: + try: + datum = await anext(data_iterator) + selector = await anext(selector_iterator) + except StopAsyncIteration: + if not element_yielded: + await checkpoint() + + return + + if selector: + element_yielded = True + yield datum + + +async def count(start: int = 0, step: int = 1) -> AsyncGenerator[int, None]: + n = start + while True: + await checkpoint_if_cancelled() + value = n + n += step + await cancel_shielded_checkpoint() + yield value + + +async def cycle( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + saved: list[T] = [] + async for element in _iterate(iterable): + saved.append(element) + yield element + + if not saved: + await checkpoint() + return + + while True: + for element in saved: + await checkpoint() + yield element + + +async def dropwhile( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + dropping = True + + async for element in _iterate(iterable): + if dropping and await predicate(element): + continue + + dropping = False + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +async def filterfalse( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + + async for element in _iterate(iterable): + if not await predicate(element): + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +@overload +def groupby( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[tuple[T, list[T]], None]: ... + + +@overload +def groupby( + iterable: Iterable[T] | AsyncIterable[T], + key: Callable[[T], Awaitable[R]], +) -> AsyncGenerator[tuple[R, list[T]], None]: ... + + +async def groupby( + iterable: Iterable[T] | AsyncIterable[T], + key: Callable[[T], Awaitable[object]] | None = None, +) -> AsyncGenerator[tuple[object, list[T]], None]: + iterator = _iterate(iterable) + try: + element = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + + group_key = element if key is None else await key(element) + values = [element] + + async for element in iterator: + next_key = element if key is None else await key(element) + if next_key != group_key: + completed_group = group_key, values + group_key = next_key + values = [element] + yield completed_group + else: + values.append(element) + + yield group_key, values + + +@overload +def islice( + iterable: Iterable[T] | AsyncIterable[T], + stop: int | None, + /, +) -> AsyncGenerator[T, None]: ... + + +@overload +def islice( + iterable: Iterable[T] | AsyncIterable[T], + start: int | None, + stop: int | None, + step: int | None = 1, + /, +) -> AsyncGenerator[T, None]: ... + + +async def islice( + iterable: Iterable[T] | AsyncIterable[T], + *args: int | None, +) -> AsyncGenerator[T, None]: + if not args: + raise TypeError("islice expected at least 2 arguments, got 1") + if len(args) > 3: + raise TypeError(f"islice expected at most 4 arguments, got {len(args) + 1}") + + slice_args = slice(*args) + + start_message = ( + "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize." + ) + stop_message = ( + "Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize." + ) + step_message = "Step for islice() must be a positive integer or None." + + def normalize_index(value: object, message: str) -> int: + try: + index = operator.index(cast(Any, value)) + except TypeError: + raise ValueError(message) from None + + if index < 0 or index > sys.maxsize: + raise ValueError(message) + + return index + + start = ( + 0 + if slice_args.start is None + else normalize_index(slice_args.start, start_message) + ) + stop = ( + None + if slice_args.stop is None + else normalize_index(slice_args.stop, stop_message) + ) + step = ( + 1 if slice_args.step is None else normalize_index(slice_args.step, step_message) + ) + + if step <= 0: + raise ValueError(step_message) + + if stop == 0 or start == stop: + await checkpoint() + return + + iterator = _iterate(iterable) + index = 0 + element_yielded = False + + while stop is None or index < stop: + try: + element = await anext(iterator) + except StopAsyncIteration: + if not element_yielded: + await checkpoint() + + return + + if index >= start and (index - start) % step == 0: + index += 1 + element_yielded = True + yield element + else: + index += 1 + + if not element_yielded: + await checkpoint() + + +async def pairwise( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[tuple[T, T], None]: + iterator = _iterate(iterable) + try: + previous = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + + element_yielded = False + async for element in iterator: + element_yielded = True + pair = (previous, element) + previous = element + yield pair + + if not element_yielded: + await checkpoint() + + +async def permutations( + iterable: Iterable[T] | AsyncIterable[T], r: int | None = None +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + n = len(pool) + if r is None: + r = n + elif not isinstance(r, int): + raise TypeError("Expected int as r") + elif r < 0: + raise ValueError("r must be non-negative") + + async for permutation in _iterate(itertools.permutations(pool, r)): + yield permutation + + +async def product( + *iterables: Iterable[T] | AsyncIterable[T], repeat: int = 1 +) -> AsyncGenerator[tuple[T, ...], None]: + repeat = operator.index(repeat) + if repeat < 0: + raise ValueError("repeat argument cannot be negative") + + pools: list[tuple[T, ...]] = [] + for iterable in iterables: + pool: list[T] = [element async for element in _iterate(iterable)] + pools.append(tuple(pool)) + + async for value in _iterate(itertools.product(*pools, repeat=repeat)): + yield value + + +async def repeat(element: T, times: int | None = None) -> AsyncGenerator[T, None]: + if times is None: + while True: + await checkpoint() + yield element + + remaining = operator.index(cast(Any, times)) + if remaining <= 0: + await checkpoint() + return + + while remaining > 0: + await checkpoint_if_cancelled() + remaining -= 1 + await cancel_shielded_checkpoint() + yield element + + +async def starmap( + function: Callable[..., Awaitable[R]], + iterable: ( + Iterable[Iterable[object] | AsyncIterable[object]] + | AsyncIterable[Iterable[object] | AsyncIterable[object]] + ), +) -> AsyncGenerator[R, None]: + result_yielded = False + + async for args_iterable in _iterate(iterable): + args = [element async for element in _iterate(args_iterable)] + result_yielded = True + yield await function(*args) + + if not result_yielded: + await checkpoint() + + +def tee( + iterable: Iterable[T] | AsyncIterable[T], n: int = 2 +) -> tuple[AsyncIterator[T], ...]: + n = operator.index(cast(Any, n)) + if n < 0: + raise ValueError("n must be >= 0") + if n == 0: + return () + + iterator = _TeeAsyncIterator(iterable) + iterators: list[AsyncIterator[T]] = [iterator] + iterators.extend(_TeeAsyncIterator(iterator) for _ in range(n - 1)) + return tuple(iterators) + + +async def takewhile( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + + async for element in _iterate(iterable): + if not await predicate(element): + if not element_yielded: + await checkpoint() + + return + + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +async def zip_longest( + *iterables: Iterable[object] | AsyncIterable[object], + fillvalue: object = None, +) -> AsyncGenerator[tuple[object, ...], None]: + iterators = [_iterate(iterable) for iterable in iterables] + num_active = len(iterators) + if not num_active: + await checkpoint() + return + + active = [True] * num_active + tuple_yielded = False + + while True: + values: list[object] = [] + for index, iterator in enumerate(iterators): + if not active[index]: + values.append(fillvalue) + continue + + try: + value = await anext(iterator) + except StopAsyncIteration: + active[index] = False + num_active -= 1 + if not num_active: + if not tuple_yielded: + await checkpoint() + + return + + value = fillvalue + + values.append(value) + + tuple_yielded = True + yield tuple(values) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py new file mode 100644 index 0000000..361b2eb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +__all__ = ( + "EventLoopToken", + "RunVar", + "RunvarToken", + "cancel_shielded_checkpoint", + "checkpoint", + "checkpoint_if_cancelled", + "current_token", +) + +import enum +from dataclasses import dataclass +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, final, overload +from weakref import WeakKeyDictionary + +from ._core._eventloop import get_async_backend + +if TYPE_CHECKING: + from .abc import AsyncBackend + +T = TypeVar("T") +D = TypeVar("D") + + +async def checkpoint() -> None: + """ + Check for cancellation and allow the scheduler to switch to another task. + + Equivalent to (but more efficient than):: + + await checkpoint_if_cancelled() + await cancel_shielded_checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint() + + +async def checkpoint_if_cancelled() -> None: + """ + Enter a checkpoint if the enclosing cancel scope has been cancelled. + + This does not allow the scheduler to switch to a different task. + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint_if_cancelled() + + +async def cancel_shielded_checkpoint() -> None: + """ + Allow the scheduler to switch to another task but without checking for cancellation. + + Equivalent to (but potentially more efficient than):: + + with CancelScope(shield=True): + await checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().cancel_shielded_checkpoint() + + +@final +@dataclass(frozen=True, repr=False) +class EventLoopToken: + """ + An opaque object that holds a reference to an event loop. + + .. versionadded:: 4.11.0 + """ + + backend_class: type[AsyncBackend] + native_token: object + + +def current_token() -> EventLoopToken: + """ + Return a token object that can be used to call code in the current event loop from + another thread. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.11.0 + + """ + backend_class = get_async_backend() + raw_token = backend_class.current_token() + return EventLoopToken(backend_class, raw_token) + + +_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary() + + +class _NoValueSet(enum.Enum): + NO_VALUE_SET = enum.auto() + + +class RunvarToken(Generic[T]): + """ + A token that can be used to restore a :class:`RunVar` to its previous value. + + Returned by :meth:`RunVar.set`. Can be used as a context manager to automatically + reset the variable on exit, or passed directly to :meth:`RunVar.reset`. + """ + + __slots__ = "_redeemed", "_value", "_var" + + def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): + self._var = var + self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value + self._redeemed = False + + def __enter__(self) -> RunvarToken[T]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._var.reset(self) + + +class RunVar(Generic[T]): + """ + Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. + + Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that + will reset the variable to its previous value when the context block is exited. + """ + + __slots__ = "_default", "_name" + + NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET + + def __init__( + self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ): + self._name = name + self._default = default + + @property + def _current_vars(self) -> dict[RunVar[T], T]: + native_token = current_token().native_token + try: + return _run_vars[native_token] + except KeyError: + run_vars = _run_vars[native_token] = {} + return run_vars + + @overload + def get(self, default: D) -> T | D: ... + + @overload + def get(self) -> T: ... + + def get( + self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ) -> T | D: + """ + Return the current value of this run variable. + + :param default: a fallback value to return if no value has been set + :return: the current value, the provided default, or the variable's own default + :raises LookupError: if no value is set and no default is available + + """ + try: + return self._current_vars[self] + except KeyError: + if default is not RunVar.NO_VALUE_SET: + return default + elif self._default is not RunVar.NO_VALUE_SET: + return self._default + + raise LookupError( + f'Run variable "{self._name}" has no value and no default set' + ) + + def set(self, value: T) -> RunvarToken[T]: + """ + Set the value of this run variable for the current event loop. + + :param value: the new value + :return: a token that can be used to restore the previous value + + """ + current_vars = self._current_vars + token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET)) + current_vars[self] = value + return token + + def reset(self, token: RunvarToken[T]) -> None: + """ + Restore this run variable to the value it held before the matching :meth:`set`. + + :param token: the token returned by :meth:`set` + :raises ValueError: if the token belongs to a different :class:`RunVar` or the token + has already been used + + """ + if token._var is not self: + raise ValueError("This token does not belong to this RunVar") + + if token._redeemed: + raise ValueError("This token has already been used") + + if token._value is _NoValueSet.NO_VALUE_SET: + try: + del self._current_vars[self] + except KeyError: + pass + else: + self._current_vars[self] = token._value + + token._redeemed = True + + def __repr__(self) -> str: + return f"" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py new file mode 100644 index 0000000..d6238cd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +import dataclasses +import socket +import sys +from collections.abc import Callable, Generator, Iterator +from contextlib import ExitStack, contextmanager +from inspect import isasyncgenfunction, iscoroutinefunction, ismethod +from typing import TYPE_CHECKING, Any, cast + +import pytest +from _pytest.fixtures import FuncFixtureInfo, SubRequest +from _pytest.outcomes import Exit +from _pytest.scope import Scope + +from . import get_available_backends +from ._core._eventloop import ( + current_async_library, + get_async_backend, + reset_current_async_library, + set_current_async_library, +) +from ._core._exceptions import iterate_exceptions + +if TYPE_CHECKING: + from .abc import TestRunner + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if TYPE_CHECKING: + # pytest >= 9.2 keeps CallSpec2 as a TYPE_CHECKING-only alias of CallSpec + from _pytest.python import CallSpec2 as CallSpec +else: + try: + # c.f. https://github.com/pytest-dev/pytest/pull/14742 + # pytest >= 9.2 + from _pytest.python import CallSpec + except ImportError: + from _pytest.python import CallSpec2 as CallSpec + +_current_runner: TestRunner | None = None +_runner_stack: ExitStack | None = None +_runner_leases = 0 + + +def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]: + if isinstance(backend, str): + return backend, {} + elif isinstance(backend, tuple) and len(backend) == 2: + if isinstance(backend[0], str) and isinstance(backend[1], dict): + return cast(tuple[str, dict[str, Any]], backend) + + raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") + + +@contextmanager +def get_runner( + backend_name: str, backend_options: dict[str, Any] +) -> Iterator[TestRunner]: + global _current_runner, _runner_leases, _runner_stack + if _current_runner is None: + asynclib = get_async_backend(backend_name) + _runner_stack = ExitStack() + if current_async_library() is None: + # Since we're in control of the event loop, we can cache the name of the + # async library + token = set_current_async_library(backend_name) + _runner_stack.callback(reset_current_async_library, token) + + backend_options = backend_options or {} + _current_runner = _runner_stack.enter_context( + asynclib.create_test_runner(backend_options) + ) + + _runner_leases += 1 + try: + yield _current_runner + finally: + _runner_leases -= 1 + if not _runner_leases: + assert _runner_stack is not None + _runner_stack.close() + _runner_stack = _current_runner = None + + +def pytest_addoption(parser: pytest.Parser) -> None: + group = parser.getgroup("anyio") + parser.addini( + "anyio_mode", + default="strict", + help='AnyIO plugin mode (either "strict" or "auto")', + ) + group.addoption( + "--anyio-mode", + default=None, + dest="anyio_mode", + help=""" + 'auto' - All async test functions will be handled by AnyIO pytest plugin + 'strict' - Disabling autoprocessing(useful when anyio tests + need to coexist with other async test plugins) + """, + ) + + +def _is_auto_mode(config: pytest.Config, mode: str) -> bool: + return ( + config.getoption(mode, default=None) == "auto" or config.getini(mode) == "auto" + ) + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "anyio: mark the (coroutine function) test to be run asynchronously via anyio.", + ) + if ( + _is_auto_mode(config, "anyio_mode") + and config.pluginmanager.has_plugin("asyncio") + and _is_auto_mode(config, "asyncio_mode") + ): + config.issue_config_time_warning( + pytest.PytestConfigWarning( + "AnyIO auto mode has been enabled together with pytest-asyncio auto " + "mode. This may cause unexpected behavior." + ), + 1, + ) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]: + def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any: + # Rebind any fixture methods to the request instance + if ( + request.instance + and ismethod(func) + and type(func.__self__) is type(request.instance) + ): + local_func = func.__func__.__get__(request.instance) + else: + local_func = func + + backend_name, backend_options = extract_backend_and_options(anyio_backend) + if has_backend_arg: + kwargs["anyio_backend"] = anyio_backend + + if has_request_arg: + kwargs["request"] = request + + with get_runner(backend_name, backend_options) as runner: + # re-entrant call into the test runner detected. this happens when an async fixture + # is dynamically requested via request.getfixturevalue() from inside a running async + # test or fixture. on asyncio this raises RuntimeError: This event loop is already + # running, on trio the runner deadlocks - the host loop blocks waiting for the + # coroutine to return, but the coroutine is waiting for the host loop. raising here + # prevents the hang and gives a consistent error across backends. + if runner.is_running(): + raise RuntimeError( + "Cannot schedule a coroutine in the test runner while another is already running; " + "likely caused by request.getfixturevalue() on an async fixture." + ) + + if isasyncgenfunction(local_func): + yield from runner.run_asyncgen_fixture(local_func, kwargs) + else: + yield runner.run_fixture(local_func, kwargs) + + # Only apply this to coroutine functions and async generator functions in requests + # that involve the anyio_backend fixture + func = fixturedef.func + if isasyncgenfunction(func) or iscoroutinefunction(func): + if "anyio_backend" in request.fixturenames: + fixturedef.func = wrapper + original_argname = fixturedef.argnames + + if not (has_backend_arg := "anyio_backend" in fixturedef.argnames): + fixturedef.argnames += ("anyio_backend",) + + if not (has_request_arg := "request" in fixturedef.argnames): + fixturedef.argnames += ("request",) + + try: + return (yield) + finally: + fixturedef.func = func + fixturedef.argnames = original_argname + + return (yield) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pycollect_makeitem( + collector: pytest.Module | pytest.Class, name: str, obj: object +) -> None: + if collector.istestfunction(obj, name): + inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj + if iscoroutinefunction(inner_func): + anyio_auto_mode = _is_auto_mode(collector.config, "anyio_mode") + marker = collector.get_closest_marker("anyio") + own_markers = getattr(obj, "pytestmark", ()) + if ( + anyio_auto_mode + or marker + or any(marker.name == "anyio" for marker in own_markers) + ): + pytest.mark.usefixtures("anyio_backend")(obj) + + +def pytest_collection_finish(session: pytest.Session) -> None: + for i, item in reversed(list(enumerate(session.items))): + if ( + isinstance(item, pytest.Function) + and iscoroutinefunction(item.function) + and item.get_closest_marker("anyio") is not None + and "anyio_backend" not in item.fixturenames + ): + new_items = [] + try: + cs_fields = {f.name for f in dataclasses.fields(CallSpec)} + except TypeError: + cs_fields = set() + + for param_index, backend in enumerate(get_available_backends()): + if "_arg2scope" in cs_fields: # pytest >= 8 + callspec = CallSpec( + params={"anyio_backend": backend}, + indices={"anyio_backend": param_index}, + _arg2scope={"anyio_backend": Scope.Module}, + _idlist=[backend], + marks=[], + ) + else: # pytest 7.x + callspec = CallSpec( # type: ignore[call-arg] + funcargs={}, + params={"anyio_backend": backend}, + indices={"anyio_backend": param_index}, + arg2scope={"anyio_backend": Scope.Module}, + idlist=[backend], + marks=[], + ) + + fi = item._fixtureinfo + new_names_closure = list(fi.names_closure) + if "anyio_backend" not in new_names_closure: + new_names_closure.append("anyio_backend") + + new_fixtureinfo = FuncFixtureInfo( + argnames=fi.argnames, + initialnames=fi.initialnames, + names_closure=new_names_closure, + name2fixturedefs=fi.name2fixturedefs, + ) + new_item = pytest.Function.from_parent( + item.parent, + name=f"{item.originalname}[{backend}]", + callspec=callspec, + callobj=item.obj, + fixtureinfo=new_fixtureinfo, + keywords=item.keywords, + originalname=item.originalname, + ) + new_items.append(new_item) + + session.items[i : i + 1] = new_items + + +@pytest.hookimpl(tryfirst=True) +def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None: + def run_with_hypothesis(**kwargs: Any) -> None: + with get_runner(backend_name, backend_options) as runner: + runner.run_test(original_func, kwargs) + + backend = pyfuncitem.funcargs.get("anyio_backend") + if backend: + backend_name, backend_options = extract_backend_and_options(backend) + + if hasattr(pyfuncitem.obj, "hypothesis"): + # Wrap the inner test function unless it's already wrapped + original_func = pyfuncitem.obj.hypothesis.inner_test + if original_func.__qualname__ != run_with_hypothesis.__qualname__: + if iscoroutinefunction(original_func): + pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis + + return None + + if iscoroutinefunction(pyfuncitem.obj): + funcargs = pyfuncitem.funcargs + testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} + with get_runner(backend_name, backend_options) as runner: + try: + runner.run_test(pyfuncitem.obj, testargs) + except ExceptionGroup as excgrp: + for exc in iterate_exceptions(excgrp): + if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)): + raise exc from excgrp + + raise + + return True + + return None + + +@pytest.fixture(scope="module", params=get_available_backends()) +def anyio_backend(request: Any) -> Any: + return request.param + + +@pytest.fixture +def anyio_backend_name(anyio_backend: Any) -> str: + if isinstance(anyio_backend, str): + return anyio_backend + else: + return anyio_backend[0] + + +@pytest.fixture +def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]: + if isinstance(anyio_backend, str): + return {} + else: + return anyio_backend[1] + + +class FreePortFactory: + """ + Manages port generation based on specified socket kind, ensuring no duplicate + ports are generated. + + This class provides functionality for generating available free ports on the + system. It is initialized with a specific socket kind and can generate ports + for given address families while avoiding reuse of previously generated ports. + + Users should not instantiate this class directly, but use the + ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple + uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead. + """ + + def __init__(self, kind: socket.SocketKind) -> None: + self._kind = kind + self._generated = set[int]() + + @property + def kind(self) -> socket.SocketKind: + """ + The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or + :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability + + """ + return self._kind + + def __call__(self, family: socket.AddressFamily | None = None) -> int: + """ + Return an unbound port for the given address family. + + :param family: if omitted, both IPv4 and IPv6 addresses will be tried + :return: a port number + + """ + if family is not None: + families = [family] + else: + families = [socket.AF_INET] + if socket.has_ipv6: + families.append(socket.AF_INET6) + + while True: + port = 0 + with ExitStack() as stack: + for fam in families: + sock = stack.enter_context(socket.socket(fam, self._kind)) + addr = "::1" if fam == socket.AF_INET6 else "127.0.0.1" + try: + sock.bind((addr, port)) + except OSError: + break + + if not port: + port = sock.getsockname()[1] + else: + if port not in self._generated: + self._generated.add(port) + return port + + +@pytest.fixture(scope="session") +def free_tcp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_STREAM) + + +@pytest.fixture(scope="session") +def free_udp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_DGRAM) + + +@pytest.fixture +def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int: + return free_tcp_port_factory() + + +@pytest.fixture +def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int: + return free_udp_port_factory() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py new file mode 100644 index 0000000..a3b07f7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +__all__ = ( + "BufferedByteReceiveStream", + "BufferedByteStream", + "BufferedConnectable", +) + +import sys +from collections.abc import Callable, Iterable, Mapping +from dataclasses import dataclass, field +from typing import Any, SupportsIndex + +from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead +from ..abc import ( + AnyByteReceiveStream, + AnyByteStream, + AnyByteStreamConnectable, + ByteReceiveStream, + ByteStream, + ByteStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class BufferedByteReceiveStream(ByteReceiveStream): + """ + Wraps any bytes-based receive stream and uses a buffer to provide sophisticated + receiving capabilities in the form of a byte stream. + """ + + receive_stream: AnyByteReceiveStream + _buffer: bytearray = field(init=False, default_factory=bytearray) + _closed: bool = field(init=False, default=False) + + async def aclose(self) -> None: + await self.receive_stream.aclose() + self._closed = True + + @property + def buffer(self) -> bytes: + """The bytes currently in the buffer.""" + return bytes(self._buffer) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.receive_stream.extra_attributes + + def feed_data(self, data: Iterable[SupportsIndex], /) -> None: + """ + Append data directly into the buffer. + + Any data in the buffer will be consumed by receive operations before receiving + anything from the wrapped stream. + + :param data: the data to append to the buffer (can be bytes or anything else + that supports ``__index__()``) + + """ + self._buffer.extend(data) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + if self._closed: + raise ClosedResourceError + + if self._buffer: + chunk = bytes(self._buffer[:max_bytes]) + del self._buffer[:max_bytes] + return chunk + elif isinstance(self.receive_stream, ByteReceiveStream): + return await self.receive_stream.receive(max_bytes) + else: + # With a bytes-oriented object stream, we need to handle any surplus bytes + # we get from the receive() call + chunk = await self.receive_stream.receive() + if len(chunk) > max_bytes: + # Save the surplus bytes in the buffer + self._buffer.extend(chunk[max_bytes:]) + return chunk[:max_bytes] + else: + return chunk + + async def receive_exactly(self, nbytes: int) -> bytes: + """ + Read exactly the given amount of bytes from the stream. + + :param nbytes: the number of bytes to read + :return: the bytes read + :raises ~anyio.IncompleteRead: if the stream was closed before the requested + amount of bytes could be read from the stream + + """ + while True: + remaining = nbytes - len(self._buffer) + if remaining <= 0: + retval = self._buffer[:nbytes] + del self._buffer[:nbytes] + return bytes(retval) + + try: + if isinstance(self.receive_stream, ByteReceiveStream): + chunk = await self.receive_stream.receive(remaining) + else: + chunk = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + self._buffer.extend(chunk) + + async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes: + """ + Read from the stream until the delimiter is found or max_bytes have been read. + + :param delimiter: the marker to look for in the stream + :param max_bytes: maximum number of bytes that will be read before raising + :exc:`~anyio.DelimiterNotFound` + :return: the bytes read (not including the delimiter) + :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter + was found + :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the + bytes read up to the maximum allowed + + """ + delimiter_size = len(delimiter) + offset = 0 + while True: + # Check if the delimiter can be found in the current buffer + index = self._buffer.find(delimiter, offset) + if index >= 0: + found = self._buffer[:index] + del self._buffer[: index + len(delimiter) :] + return bytes(found) + + # Check if the buffer is already at or over the limit + if len(self._buffer) >= max_bytes: + raise DelimiterNotFound(max_bytes) + + # Read more data into the buffer from the socket + try: + data = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + # Move the offset forward and add the new data to the buffer + offset = max(len(self._buffer) - delimiter_size + 1, 0) + self._buffer.extend(data) + + +class BufferedByteStream(BufferedByteReceiveStream, ByteStream): + """ + A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed + through to the wrapped stream as-is. + """ + + def __init__(self, stream: AnyByteStream): + """ + :param stream: the stream to be wrapped + + """ + super().__init__(stream) + self._stream = stream + + @override + async def send_eof(self) -> None: + await self._stream.send_eof() + + @override + async def send(self, item: bytes) -> None: + await self._stream.send(item) + + +class BufferedConnectable(ByteStreamConnectable): + """ + Wraps a byte stream connectable to produce :class:`BufferedByteStream` connections. + + Use this when you want the streams returned by :meth:`connect` to have the buffered + receive API (e.g. :meth:`~BufferedByteReceiveStream.receive_exactly` and + :meth:`~BufferedByteReceiveStream.receive_until`). + + :param connectable: the byte stream connectable to wrap + """ + + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the connectable to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> BufferedByteStream: + stream = await self.connectable.connect() + return BufferedByteStream(stream) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py new file mode 100644 index 0000000..b9d7ca4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +__all__ = ( + "FileReadStream", + "FileStreamAttribute", + "FileWriteStream", +) + +from collections.abc import Callable, Mapping +from io import SEEK_SET, UnsupportedOperation +from os import PathLike +from pathlib import Path +from typing import IO, Any + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + TypedAttributeSet, + to_thread, + typed_attribute, +) +from ..abc import ByteReceiveStream, ByteSendStream + + +class FileStreamAttribute(TypedAttributeSet): + #: the open file descriptor + file: IO[bytes] = typed_attribute() + #: the path of the file on the file system, if available (file must be a real file) + path: Path = typed_attribute() + #: the file number, if available (file must be a real file or a TTY) + fileno: int = typed_attribute() + + +class _BaseFileStream: + def __init__(self, file: IO[bytes]): + self._file = file + + async def aclose(self) -> None: + await to_thread.run_sync(self._file.close) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict[Any, Callable[[], Any]] = { + FileStreamAttribute.file: lambda: self._file, + } + + if hasattr(self._file, "name"): + attributes[FileStreamAttribute.path] = lambda: Path(self._file.name) + + try: + self._file.fileno() + except UnsupportedOperation: + pass + else: + attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno() + + return attributes + + +class FileReadStream(_BaseFileStream, ByteReceiveStream): + """ + A byte stream that reads from a file in the file system. + + :param file: a file that has been opened for reading in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path(cls, path: str | PathLike[str]) -> FileReadStream: + """ + Create a file read stream by opening the given file. + + :param path: path of the file to read from + + """ + file = await to_thread.run_sync(Path(path).open, "rb") + return cls(file) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + try: + data = await to_thread.run_sync(self._file.read, max_bytes) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc + + if data: + return data + else: + raise EndOfStream + + async def seek(self, position: int, whence: int = SEEK_SET) -> int: + """ + Seek the file to the given position. + + .. seealso:: :meth:`io.IOBase.seek` + + .. note:: Not all file descriptors are seekable. + + :param position: position to seek the file to + :param whence: controls how ``position`` is interpreted + :return: the new absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.seek, position, whence) + + async def tell(self) -> int: + """ + Return the current stream position. + + .. note:: Not all file descriptors are seekable. + + :return: the current absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.tell) + + +class FileWriteStream(_BaseFileStream, ByteSendStream): + """ + A byte stream that writes to a file in the file system. + + :param file: a file that has been opened for writing in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path( + cls, path: str | PathLike[str], append: bool = False + ) -> FileWriteStream: + """ + Create a file write stream by opening the given file for writing. + + :param path: path of the file to write to + :param append: if ``True``, open the file for appending; if ``False``, any + existing file at the given path will be truncated + + """ + mode = "ab" if append else "wb" + file = await to_thread.run_sync(Path(path).open, mode) + return cls(file) + + async def send(self, item: bytes) -> None: + try: + await to_thread.run_sync(self._file.write, item) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py new file mode 100644 index 0000000..329a75e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py @@ -0,0 +1,326 @@ +from __future__ import annotations + +__all__ = ( + "MemoryObjectReceiveStream", + "MemoryObjectSendStream", + "MemoryObjectStreamStatistics", +) + +import warnings +from collections import OrderedDict, deque +from dataclasses import dataclass, field +from types import TracebackType +from typing import Generic, NamedTuple, TypeVar + +from .._core._exceptions import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + WouldBlock, +) +from .._core._synchronization import Event +from .._core._testing import TaskInfo, get_current_task +from ..abc import ObjectReceiveStream, ObjectSendStream +from ..lowlevel import checkpoint + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class MemoryObjectStreamStatistics(NamedTuple): + current_buffer_used: int #: number of items stored in the buffer + #: maximum number of items that can be stored on this stream (or :data:`math.inf`) + max_buffer_size: float + open_send_streams: int #: number of unclosed clones of the send stream + open_receive_streams: int #: number of unclosed clones of the receive stream + #: number of tasks blocked on :meth:`MemoryObjectSendStream.send` + tasks_waiting_send: int + #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive` + tasks_waiting_receive: int + + +@dataclass(eq=False) +class _MemoryObjectItemReceiver(Generic[T_Item]): + task_info: TaskInfo = field(init=False, default_factory=get_current_task) + item: T_Item = field(init=False) + + def __repr__(self) -> str: + # When item is not defined, we get following error with default __repr__: + # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item' + item = getattr(self, "item", None) + return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})" + + +@dataclass(eq=False) +class _MemoryObjectStreamState(Generic[T_Item]): + max_buffer_size: float = field() + buffer: deque[T_Item] = field(init=False, default_factory=deque) + open_send_channels: int = field(init=False, default=0) + open_receive_channels: int = field(init=False, default=0) + waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field( + init=False, default_factory=OrderedDict + ) + waiting_senders: OrderedDict[Event, T_Item] = field( + init=False, default_factory=OrderedDict + ) + + def statistics(self) -> MemoryObjectStreamStatistics: + return MemoryObjectStreamStatistics( + len(self.buffer), + self.max_buffer_size, + self.open_send_channels, + self.open_receive_channels, + len(self.waiting_senders), + len(self.waiting_receivers), + ) + + +@dataclass(eq=False) +class MemoryObjectReceiveStream(ObjectReceiveStream[T_co], Generic[T_co]): + _state: _MemoryObjectStreamState[T_co] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_receive_channels += 1 + + def receive_nowait(self) -> T_co: + """ + Receive the next item if it can be done without waiting. + + :return: the received item + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been + closed from the sending end + :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks + waiting to send + + """ + if self._closed: + raise ClosedResourceError + + if self._state.waiting_senders: + # Get the item from the next sender + send_event, item = self._state.waiting_senders.popitem(last=False) + self._state.buffer.append(item) + send_event.set() + + if self._state.buffer: + return self._state.buffer.popleft() + elif not self._state.open_send_channels: + raise EndOfStream + + raise WouldBlock + + async def receive(self) -> T_co: + await checkpoint() + try: + return self.receive_nowait() + except WouldBlock: + # Add ourselves in the queue + receive_event = Event() + receiver = _MemoryObjectItemReceiver[T_co]() + self._state.waiting_receivers[receive_event] = receiver + + try: + await receive_event.wait() + finally: + self._state.waiting_receivers.pop(receive_event, None) + + try: + return receiver.item + except AttributeError: + raise EndOfStream from None + + def clone(self) -> MemoryObjectReceiveStream[T_co]: + """ + Create a clone of this receive stream. + + Each clone can be closed separately. Only when all clones have been closed will + the receiving end of the memory stream be considered closed by the sending ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectReceiveStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_receive_channels -= 1 + if self._state.open_receive_channels == 0: + send_events = list(self._state.waiting_senders.keys()) + for event in send_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectReceiveStream[T_co]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) + + +@dataclass(eq=False) +class MemoryObjectSendStream(ObjectSendStream[T_contra], Generic[T_contra]): + _state: _MemoryObjectStreamState[T_contra] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_send_channels += 1 + + def send_nowait(self, item: T_contra) -> None: + """ + Send an item immediately if it can be done without waiting. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting + to receive + + """ + if self._closed: + raise ClosedResourceError + if not self._state.open_receive_channels: + raise BrokenResourceError + + while self._state.waiting_receivers: + receive_event, receiver = self._state.waiting_receivers.popitem(last=False) + if not receiver.task_info.has_pending_cancellation(): + receiver.item = item + receive_event.set() + return + + if len(self._state.buffer) < self._state.max_buffer_size: + self._state.buffer.append(item) + else: + raise WouldBlock + + async def send(self, item: T_contra) -> None: + """ + Send an item to the stream. + + If the buffer is full, this method blocks until there is again room in the + buffer or the item can be sent directly to a receiver. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + + """ + await checkpoint() + try: + self.send_nowait(item) + except WouldBlock: + # Wait until there's someone on the receiving end + send_event = Event() + self._state.waiting_senders[send_event] = item + try: + await send_event.wait() + except BaseException: + self._state.waiting_senders.pop(send_event, None) + raise + + if send_event in self._state.waiting_senders: + del self._state.waiting_senders[send_event] + raise BrokenResourceError from None + + def clone(self) -> MemoryObjectSendStream[T_contra]: + """ + Create a clone of this send stream. + + Each clone can be closed separately. Only when all clones have been closed will + the sending end of the memory stream be considered closed by the receiving ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectSendStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_send_channels -= 1 + if self._state.open_send_channels == 0: + receive_events = list(self._state.waiting_receivers.keys()) + self._state.waiting_receivers.clear() + for event in receive_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectSendStream[T_contra]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py new file mode 100644 index 0000000..9a75345 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +__all__ = ( + "MultiListener", + "StapledByteStream", + "StapledObjectStream", +) + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Any, Generic, TypeVar + +from ..abc import ( + ByteReceiveStream, + ByteSendStream, + ByteStream, + Listener, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + TaskGroup, +) + +T_Item = TypeVar("T_Item") +T_Stream = TypeVar("T_Stream") + + +@dataclass(eq=False) +class StapledByteStream(ByteStream): + """ + Combines two byte streams into a single, bidirectional byte stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ByteSendStream send_stream: the sending byte stream + :param ByteReceiveStream receive_stream: the receiving byte stream + """ + + send_stream: ByteSendStream + receive_stream: ByteReceiveStream + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + return await self.receive_stream.receive(max_bytes) + + async def send(self, item: bytes) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class StapledObjectStream(ObjectStream[T_Item], Generic[T_Item]): + """ + Combines two object streams into a single, bidirectional object stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ObjectSendStream send_stream: the sending object stream + :param ObjectReceiveStream receive_stream: the receiving object stream + """ + + send_stream: ObjectSendStream[T_Item] + receive_stream: ObjectReceiveStream[T_Item] + + async def receive(self) -> T_Item: + return await self.receive_stream.receive() + + async def send(self, item: T_Item) -> None: + await self.send_stream.send(item) + + def send_nowait(self, item: T_Item) -> None: + try: + send_nowait = self.send_stream.send_nowait # type: ignore[attr-defined] + except AttributeError as exc: + raise NotImplementedError( + f"'send_nowait' method not implemented in {type(self.send_stream)}" + ) from exc + + send_nowait(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class MultiListener(Listener[T_Stream], Generic[T_Stream]): + """ + Combines multiple listeners into one, serving connections from all of them at once. + + Any MultiListeners in the given collection of listeners will have their listeners + moved into this one. + + Extra attributes are provided from each listener, with each successive listener + overriding any conflicting attributes from the previous one. + + :param listeners: listeners to serve + :type listeners: Sequence[Listener[T_Stream]] + """ + + listeners: Sequence[Listener[T_Stream]] + + def __post_init__(self) -> None: + listeners: list[Listener[T_Stream]] = [] + for listener in self.listeners: + if isinstance(listener, MultiListener): + listeners.extend(listener.listeners) + del listener.listeners[:] # type: ignore[attr-defined] + else: + listeners.append(listener) + + self.listeners = listeners + + async def serve( + self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None + ) -> None: + from .. import create_task_group + + async with create_task_group() as tg: + for listener in self.listeners: + tg.start_soon(listener.serve, handler, task_group) + + async def aclose(self) -> None: + for listener in self.listeners: + await listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict = {} + for listener in self.listeners: + attributes.update(listener.extra_attributes) + + return attributes diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py new file mode 100644 index 0000000..296cd25 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +__all__ = ( + "TextConnectable", + "TextReceiveStream", + "TextSendStream", + "TextStream", +) + +import codecs +import sys +from collections.abc import Callable, Mapping +from dataclasses import InitVar, dataclass, field +from typing import Any + +from ..abc import ( + AnyByteReceiveStream, + AnyByteSendStream, + AnyByteStream, + AnyByteStreamConnectable, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + ObjectStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class TextReceiveStream(ObjectReceiveStream[str]): + """ + Stream wrapper that decodes bytes to strings using the given encoding. + + Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any + completely received unicode characters as soon as they come in. + + :param transport_stream: any bytes-based receive stream + :param encoding: character encoding to use for decoding bytes to strings (defaults + to ``utf-8``) + :param errors: handling scheme for decoding errors (defaults to ``strict``; see the + `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteReceiveStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _decoder: codecs.IncrementalDecoder = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + decoder_class = codecs.getincrementaldecoder(encoding) + self._decoder = decoder_class(errors=errors) + + async def receive(self) -> str: + while True: + chunk = await self.transport_stream.receive() + decoded = self._decoder.decode(chunk) + if decoded: + return decoded + + async def aclose(self) -> None: + await self.transport_stream.aclose() + self._decoder.reset() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextSendStream(ObjectSendStream[str]): + """ + Sends strings to the wrapped stream as bytes using the given encoding. + + :param AnyByteSendStream transport_stream: any bytes-based send stream + :param str encoding: character encoding to use for encoding strings to bytes + (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteSendStream + encoding: InitVar[str] = "utf-8" + errors: str = "strict" + _encoder: Callable[..., tuple[bytes, int]] = field(init=False) + + def __post_init__(self, encoding: str) -> None: + self._encoder = codecs.getencoder(encoding) + + async def send(self, item: str) -> None: + encoded = self._encoder(item, self.errors)[0] + await self.transport_stream.send(encoded) + + async def aclose(self) -> None: + await self.transport_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextStream(ObjectStream[str]): + """ + A bidirectional stream that decodes bytes to strings on receive and encodes strings + to bytes on send. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param AnyByteStream transport_stream: any bytes-based stream + :param str encoding: character encoding to use for encoding/decoding strings to/from + bytes (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _receive_stream: TextReceiveStream = field(init=False) + _send_stream: TextSendStream = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + self._receive_stream = TextReceiveStream( + self.transport_stream, encoding=encoding, errors=errors + ) + self._send_stream = TextSendStream( + self.transport_stream, encoding=encoding, errors=errors + ) + + async def receive(self) -> str: + return await self._receive_stream.receive() + + async def send(self, item: str) -> None: + await self._send_stream.send(item) + + async def send_eof(self) -> None: + await self.transport_stream.send_eof() + + async def aclose(self) -> None: + await self._send_stream.aclose() + await self._receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self._send_stream.extra_attributes, + **self._receive_stream.extra_attributes, + } + + +class TextConnectable(ObjectStreamConnectable[str]): + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the bytestream endpoint to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> TextStream: + stream = await self.connectable.connect() + return TextStream(stream) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py new file mode 100644 index 0000000..b9ddf1d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py @@ -0,0 +1,436 @@ +from __future__ import annotations + +__all__ = ( + "TLSAttribute", + "TLSConnectable", + "TLSListener", + "TLSStream", +) + +import logging +import re +import ssl +import sys +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from functools import wraps +from ssl import SSLContext +from typing import Any, TypeAlias, TypeVar + +from .. import ( + BrokenResourceError, + EndOfStream, + aclose_forcefully, + get_cancelled_exc_class, + to_thread, +) +from .._core._typedattr import TypedAttributeSet, typed_attribute +from ..abc import ( + AnyByteStream, + AnyByteStreamConnectable, + ByteStream, + ByteStreamConnectable, + Listener, + TaskGroup, +) + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +_PCTRTT: TypeAlias = tuple[tuple[str, str], ...] +_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...] + + +class TLSAttribute(TypedAttributeSet): + """Contains Transport Layer Security related attributes.""" + + #: the selected ALPN protocol + alpn_protocol: str | None = typed_attribute() + #: the channel binding for type ``tls-unique`` + channel_binding_tls_unique: bytes = typed_attribute() + #: the selected cipher + cipher: tuple[str, str, int] = typed_attribute() + #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert` + # for more information) + peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute() + #: the peer certificate in binary form + peer_certificate_binary: bytes | None = typed_attribute() + #: ``True`` if this is the server side of the connection + server_side: bool = typed_attribute() + #: ciphers shared by the client during the TLS handshake (``None`` if this is the + #: client side) + shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute() + #: the :class:`~ssl.SSLObject` used for encryption + ssl_object: ssl.SSLObject = typed_attribute() + #: ``True`` if this stream does (and expects) a closing TLS handshake when the + #: stream is being closed + standard_compatible: bool = typed_attribute() + #: the TLS protocol version (e.g. ``TLSv1.2``) + tls_version: str = typed_attribute() + + +@dataclass(eq=False) +class TLSStream(ByteStream): + """ + A stream wrapper that encrypts all sent data and decrypts received data. + + This class has no public initializer; use :meth:`wrap` instead. + All extra attributes from :class:`~TLSAttribute` are supported. + + :var AnyByteStream transport_stream: the wrapped stream + + """ + + transport_stream: AnyByteStream + standard_compatible: bool + _ssl_object: ssl.SSLObject + _read_bio: ssl.MemoryBIO + _write_bio: ssl.MemoryBIO + + @classmethod + async def wrap( + cls, + transport_stream: AnyByteStream, + *, + server_side: bool | None = None, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> TLSStream: + """ + Wrap an existing stream with Transport Layer Security. + + This performs a TLS handshake with the peer. + + :param transport_stream: a bytes-transporting stream to wrap + :param server_side: ``True`` if this is the server side of the connection, + ``False`` if this is the client side (if omitted, will be set to ``False`` + if ``hostname`` has been provided, ``False`` otherwise). Used only to create + a default context when an explicit context has not been provided. + :param hostname: host name of the peer (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure + default will be created) + :param standard_compatible: if ``False``, skip the closing handshake when + closing the connection, and don't raise an exception if the peer does the + same + :raises ~ssl.SSLError: if the TLS handshake fails + + """ + if server_side is None: + server_side = not hostname + + if not ssl_context: + purpose = ( + ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH + ) + ssl_context = ssl.create_default_context(purpose) + + # Re-enable detection of unexpected EOFs if it was disabled by Python + if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): + ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF + + bio_in = ssl.MemoryBIO() + bio_out = ssl.MemoryBIO() + + # Resolve international host names using IDNA 2008. + # Otherwise wrap_bio() would resolve them with IDNA 2003. + if hostname is not None: + from .._core._sockets import idna2008_resolve + + server_hostname: bytes | None = idna2008_resolve(hostname) + else: + server_hostname = None + + # External SSLContext implementations may do blocking I/O in wrap_bio(), + # but the standard library implementation won't + if type(ssl_context) is ssl.SSLContext: + ssl_object = ssl_context.wrap_bio( + bio_in, + bio_out, + server_side=server_side, + server_hostname=server_hostname, + ) + else: + ssl_object = await to_thread.run_sync( + ssl_context.wrap_bio, + bio_in, + bio_out, + server_side, + server_hostname, + None, + ) + + wrapper = cls( + transport_stream=transport_stream, + standard_compatible=standard_compatible, + _ssl_object=ssl_object, + _read_bio=bio_in, + _write_bio=bio_out, + ) + await wrapper._call_sslobject_method(ssl_object.do_handshake) + return wrapper + + async def _call_sslobject_method( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: + while True: + try: + result = func(*args) + except ssl.SSLWantReadError: + try: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + data = await self.transport_stream.receive() + except EndOfStream: + self._read_bio.write_eof() + except OSError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + else: + self._read_bio.write(data) + except ssl.SSLWantWriteError: + await self.transport_stream.send(self._write_bio.read()) + except ssl.SSLSyscallError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + except ssl.SSLError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + if isinstance(exc, ssl.SSLEOFError) or ( + exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror + ): + if self.standard_compatible: + raise BrokenResourceError from exc + else: + raise EndOfStream from None + + raise + else: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + return result + + async def unwrap(self) -> tuple[AnyByteStream, bytes]: + """ + Does the TLS closing handshake. + + :return: a tuple of (wrapped byte stream, bytes left in the read buffer) + + """ + await self._call_sslobject_method(self._ssl_object.unwrap) + self._read_bio.write_eof() + self._write_bio.write_eof() + return self.transport_stream, self._read_bio.read() + + async def aclose(self) -> None: + if self.standard_compatible: + try: + await self.unwrap() + except BaseException: + await aclose_forcefully(self.transport_stream) + raise + + await self.transport_stream.aclose() + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + data = await self._call_sslobject_method(self._ssl_object.read, max_bytes) + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + await self._call_sslobject_method(self._ssl_object.write, item) + + async def send_eof(self) -> None: + tls_version = self.extra(TLSAttribute.tls_version) + match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version) + if match: + major, minor = int(match.group(1)), int(match.group(2) or 0) + if (major, minor) < (1, 3): + raise NotImplementedError( + f"send_eof() requires at least TLSv1.3; current " + f"session uses {tls_version}" + ) + + raise NotImplementedError( + "send_eof() has not yet been implemented for TLS streams" + ) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.transport_stream.extra_attributes, + TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol, + TLSAttribute.channel_binding_tls_unique: ( + self._ssl_object.get_channel_binding + ), + TLSAttribute.cipher: self._ssl_object.cipher, + TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False), + TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert( + True + ), + TLSAttribute.server_side: lambda: self._ssl_object.server_side, + TLSAttribute.shared_ciphers: lambda: ( + self._ssl_object.shared_ciphers() + if self._ssl_object.server_side + else None + ), + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + TLSAttribute.ssl_object: lambda: self._ssl_object, + TLSAttribute.tls_version: self._ssl_object.version, + } + + +@dataclass(eq=False) +class TLSListener(Listener[TLSStream]): + """ + A convenience listener that wraps another listener and auto-negotiates a TLS session + on every accepted connection. + + If the TLS handshake times out or raises an exception, + :meth:`handle_handshake_error` is called to do whatever post-mortem processing is + deemed necessary. + + Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute. + + :param Listener listener: the listener to wrap + :param ssl_context: the SSL context object + :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap` + :param handshake_timeout: time limit for the TLS handshake + (passed to :func:`~anyio.fail_after`) + """ + + listener: Listener[Any] + ssl_context: ssl.SSLContext + standard_compatible: bool = True + handshake_timeout: float = 30 + + @staticmethod + async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None: + """ + Handle an exception raised during the TLS handshake. + + This method does 3 things: + + #. Forcefully closes the original stream + #. Logs the exception (unless it was a cancellation exception) using the + ``anyio.streams.tls`` logger + #. Reraises the exception if it was a base exception or a cancellation exception + + :param exc: the exception + :param stream: the original stream + + """ + await aclose_forcefully(stream) + + # Log all except cancellation exceptions + if not isinstance(exc, get_cancelled_exc_class()): + # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using + # any asyncio implementation, so we explicitly pass the exception to log + # (https://github.com/python/cpython/issues/108668). Trio does not have this + # issue because it works around the CPython bug. + logging.getLogger(__name__).exception( + "Error during TLS handshake", exc_info=exc + ) + + # Only reraise base exceptions and cancellation exceptions + if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()): + raise # noqa: PLE0704 + + async def serve( + self, + handler: Callable[[TLSStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + @wraps(handler) + async def handler_wrapper(stream: AnyByteStream) -> None: + from .. import fail_after + + try: + with fail_after(self.handshake_timeout): + wrapped_stream = await TLSStream.wrap( + stream, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException as exc: + await self.handle_handshake_error(exc, stream) + else: + await handler(wrapped_stream) + + await self.listener.serve(handler_wrapper, task_group) + + async def aclose(self) -> None: + await self.listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + } + + +class TLSConnectable(ByteStreamConnectable): + """ + Wraps another connectable and does TLS negotiation after a successful connection. + + :param connectable: the connectable to wrap + :param hostname: host name of the server (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure default + will be created) + :param standard_compatible: if ``False``, skip the closing handshake when closing + the connection, and don't raise an exception if the server does the same + """ + + def __init__( + self, + connectable: AnyByteStreamConnectable, + *, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> None: + self.connectable = connectable + self.ssl_context: SSLContext = ssl_context or ssl.create_default_context( + ssl.Purpose.SERVER_AUTH + ) + if not isinstance(self.ssl_context, ssl.SSLContext): + raise TypeError( + "ssl_context must be an instance of ssl.SSLContext, not " + f"{type(self.ssl_context).__name__}" + ) + self.hostname = hostname + self.standard_compatible = standard_compatible + + @override + async def connect(self) -> TLSStream: + stream = await self.connectable.connect() + try: + return await TLSStream.wrap( + stream, + hostname=self.hostname, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException: + await aclose_forcefully(stream) + raise diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py new file mode 100644 index 0000000..54ef4a8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py @@ -0,0 +1,246 @@ +from __future__ import annotations + +__all__ = ( + "current_default_interpreter_limiter", + "run_sync", +) + +import atexit +import os +import sys +from collections import deque +from collections.abc import Callable +from typing import Any, Final, TypeVar + +from . import current_time, to_thread +from ._core._exceptions import BrokenWorkerInterpreter +from ._core._synchronization import CapacityLimiter +from .lowlevel import RunVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 14): + from concurrent.interpreters import ExecutionFailed, create + + def _interp_call( + func: Callable[..., Any], args: tuple[Any, ...] + ) -> tuple[Any, bool]: + try: + retval = func(*args) + except BaseException as exc: + return exc, True + else: + return retval, False + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter = create() + + def destroy(self) -> None: + self._interpreter.close() + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + try: + res, is_exception = self._interpreter.call(_interp_call, func, args) + except ExecutionFailed as exc: + raise BrokenWorkerInterpreter(exc.excinfo) from exc + + if is_exception: + raise res + + return res +elif sys.version_info >= (3, 13): + import _interpqueues + import _interpreters + + UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib + FMT_UNPICKLED: Final = 0 + FMT_PICKLED: Final = 1 + QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND) + QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND) + + _run_func = compile( + """ +import _interpqueues +from _interpreters import NotShareableError +from pickle import loads, dumps, HIGHEST_PROTOCOL + +QUEUE_PICKLE_ARGS = (1, 2) +QUEUE_UNPICKLE_ARGS = (0, 2) + +item = _interpqueues.get(queue_id)[0] +try: + func, args = loads(item) + retval = func(*args) +except BaseException as exc: + is_exception = True + retval = exc +else: + is_exception = False + +try: + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS) +except NotShareableError: + retval = dumps(retval, HIGHEST_PROTOCOL) + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS) + """, + "", + "exec", + ) + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter_id = _interpreters.create() + self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS) + _interpreters.set___main___attrs( + self._interpreter_id, {"queue_id": self._queue_id} + ) + + def destroy(self) -> None: + _interpqueues.destroy(self._queue_id) + _interpreters.destroy(self._interpreter_id) + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + import pickle + + item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL) + _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS) + exc_info = _interpreters.exec(self._interpreter_id, _run_func) + if exc_info: + raise BrokenWorkerInterpreter(exc_info) + + res = _interpqueues.get(self._queue_id) + (res, is_exception), fmt = res[:2] + if fmt == FMT_PICKLED: + res = pickle.loads(res) + + if is_exception: + raise res + + return res +else: + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + raise RuntimeError("subinterpreters require at least Python 3.13") + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + raise NotImplementedError + + def destroy(self) -> None: + pass + + +DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value +MAX_WORKER_IDLE_TIME = ( + 30 # seconds a subinterpreter can be idle before becoming eligible for pruning +) + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_idle_workers = RunVar[deque[_Worker]]("_available_workers") +_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter") + + +def _stop_workers(workers: deque[_Worker]) -> None: + for worker in workers: + worker.destroy() + + workers.clear() + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a subinterpreter. + + .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet + available, so the code path for that Python version relies on an undocumented, + private API. As such, it is recommended to not rely on this function for anything + mission-critical on Python 3.13. + + :param func: a callable + :param args: the positional arguments for the callable + :param limiter: capacity limiter to use to limit the total number of subinterpreters + running (if omitted, the default limiter is used) + :return: the result of the call + :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter + + """ + if limiter is None: + limiter = current_default_interpreter_limiter() + + try: + idle_workers = _idle_workers.get() + except LookupError: + idle_workers = deque() + _idle_workers.set(idle_workers) + atexit.register(_stop_workers, idle_workers) + + async with limiter: + try: + worker = idle_workers.pop() + except IndexError: + worker = _Worker() + + try: + return await to_thread.run_sync( + worker.call, + func, + args, + limiter=limiter, + ) + finally: + # Prune workers that have been idle for too long + now = current_time() + while idle_workers: + if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME: + break + + await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter) + + worker.last_used = current_time() + idle_workers.append(worker) + + +def current_default_interpreter_limiter() -> CapacityLimiter: + """ + Return the capacity limiter used by default to limit the number of concurrently + running subinterpreters. + + Defaults to the number of CPU cores. + + :return: a capacity limiter object + + """ + try: + return _default_interpreter_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT) + _default_interpreter_limiter.set(limiter) + return limiter diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py new file mode 100644 index 0000000..584128e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_process.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +__all__ = ( + "current_default_process_limiter", + "process_worker", + "run_sync", +) + +import os +import pickle +import runpy +import subprocess +import sys +from collections import deque +from collections.abc import Callable +from types import ModuleType +from typing import TypeVar, cast + +from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class +from ._core._exceptions import BrokenWorkerProcess +from ._core._subprocesses import open_process +from ._core._synchronization import CapacityLimiter +from ._core._tasks import CancelScope, fail_after +from .abc import ByteReceiveStream, ByteSendStream, Process +from .lowlevel import RunVar, checkpoint_if_cancelled +from .streams.buffered import BufferedByteReceiveStream + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +WORKER_MAX_IDLE_TIME = 300 # 5 minutes + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers") +_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar( + "_process_pool_idle_workers" +) +_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") + + +async def run_sync( # type: ignore[return] + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + cancellable: bool = False, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker process. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the worker process running it will be abruptly terminated using SIGKILL + (or ``terminateProcess()`` on Windows). + + :param func: a callable + :param args: positional arguments for the callable + :param cancellable: ``True`` to allow cancellation of the operation while it's + running + :param limiter: capacity limiter to use to limit the total amount of processes + running (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + + async def send_raw_command(pickled_cmd: bytes) -> object: + try: + await stdin.send(pickled_cmd) + response = await buffered.receive_until(b"\n", 50) + status, length = response.split(b" ") + if status not in (b"RETURN", b"EXCEPTION"): + raise RuntimeError( + f"Worker process returned unexpected response: {response!r}" + ) + + pickled_response = await buffered.receive_exactly(int(length)) + except BaseException as exc: + workers.discard(process) + try: + process.kill() + with CancelScope(shield=True): + await process.aclose() + except ProcessLookupError: + pass + + if isinstance(exc, get_cancelled_exc_class()): + raise + else: + raise BrokenWorkerProcess from exc + + retval = pickle.loads(pickled_response) + if status == b"EXCEPTION": + assert isinstance(retval, BaseException) + raise retval + else: + return retval + + # First pickle the request before trying to reserve a worker process + await checkpoint_if_cancelled() + request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL) + + # If this is the first run in this event loop thread, set up the necessary variables + try: + workers = _process_pool_workers.get() + idle_workers = _process_pool_idle_workers.get() + except LookupError: + workers = set() + idle_workers = deque() + _process_pool_workers.set(workers) + _process_pool_idle_workers.set(idle_workers) + get_async_backend().setup_process_pool_exit_at_shutdown(workers) + + async with limiter or current_default_process_limiter(): + # Pop processes from the pool (starting from the most recently used) until we + # find one that hasn't exited yet + process: Process + while idle_workers: + process, _idle_since = idle_workers.pop() + if process.returncode is None: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + + # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME + # seconds or longer + now = current_time() + killed_processes: list[Process] = [] + while idle_workers: + if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME: + break + + process_to_kill, _idle_since = idle_workers.popleft() + process_to_kill.kill() + workers.remove(process_to_kill) + killed_processes.append(process_to_kill) + + with CancelScope(shield=True): + for killed_process in killed_processes: + await killed_process.aclose() + + break + + workers.remove(process) + else: + command = [sys.executable, "-u", "-m", __name__] + process = await open_process( + command, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + try: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + with fail_after(20): + message = await buffered.receive(6) + + if message != b"READY\n": + raise BrokenWorkerProcess( + f"Worker process returned unexpected response: {message!r}" + ) + + main_module_path = getattr(sys.modules["__main__"], "__file__", None) + pickled = pickle.dumps( + ("init", sys.path, main_module_path), + protocol=pickle.HIGHEST_PROTOCOL, + ) + await send_raw_command(pickled) + except (BrokenWorkerProcess, get_cancelled_exc_class()): + raise + except BaseException as exc: + process.kill() + raise BrokenWorkerProcess( + "Error during worker process initialization" + ) from exc + + workers.add(process) + + with CancelScope(shield=not cancellable): + try: + return cast(T_Retval, await send_raw_command(request)) + finally: + if process in workers: + idle_workers.append((process, current_time())) + + +def current_default_process_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of worker + processes. + + :return: a capacity limiter object + + """ + try: + return _default_process_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or 2) + _default_process_limiter.set(limiter) + return limiter + + +def process_worker() -> None: + # Redirect standard streams to os.devnull so that user code won't interfere with the + # parent-worker communication + stdin = sys.stdin + stdout = sys.stdout + sys.stdin = open(os.devnull) + sys.stdout = open(os.devnull, "w") + sys.stderr = open(os.devnull, "w") + + stdout.buffer.write(b"READY\n") + while True: + retval = exception = None + try: + command, *args = pickle.load(stdin.buffer) + except EOFError: + return + except BaseException as exc: + exception = exc + else: + if command == "run": + func, args = args + try: + retval = func(*args) + except BaseException as exc: + exception = exc + elif command == "init": + main_module_path: str | None + sys.path, main_module_path = args + del sys.modules["__main__"] + if main_module_path and os.path.isfile(main_module_path): + # Load the parent's main module but as __mp_main__ instead of + # __main__ (like multiprocessing does) to avoid infinite recursion + try: + main = ModuleType("__mp_main__") + main_content = runpy.run_path( + main_module_path, run_name="__mp_main__" + ) + main.__dict__.update(main_content) + sys.modules["__main__"] = sys.modules["__mp_main__"] = main + except BaseException as exc: + exception = exc + try: + if exception is not None: + status = b"EXCEPTION" + pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL) + else: + status = b"RETURN" + pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL) + except BaseException as exc: + exception = exc + status = b"EXCEPTION" + pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL) + + stdout.buffer.write(b"%s %d\n" % (status, len(pickled))) + stdout.buffer.write(pickled) + + # Respect SIGTERM + if isinstance(exception, SystemExit): + raise exception + + +if __name__ == "__main__": + process_worker() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py new file mode 100644 index 0000000..5cdb725 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +__all__ = ( + "current_default_thread_limiter", + "run_sync", +) + +import sys +from collections.abc import Callable +from typing import TYPE_CHECKING, TypeVar +from warnings import warn + +from ._core._eventloop import get_async_backend + +if TYPE_CHECKING: + from ._core._synchronization import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + abandon_on_cancel: bool = False, + cancellable: bool | None = None, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker thread. + + If the ``abandon_on_cancel`` option is enabled and the task waiting for its + completion is cancelled, the thread will still run its course but its + return value (or any raised exception) will be ignored. + + :param func: a callable + :param args: positional arguments for the callable + :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run + unchecked on own) if the host task is cancelled, ``False`` to ignore + cancellations in the host task until the operation has completed in the worker + thread + :param cancellable: deprecated alias of ``abandon_on_cancel``; will override + ``abandon_on_cancel`` if both parameters are passed + :param limiter: capacity limiter to use to limit the total amount of threads running + (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + if cancellable is not None: + abandon_on_cancel = cancellable + warn( + "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is " + "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead", + DeprecationWarning, + stacklevel=2, + ) + + return await get_async_backend().run_sync_in_worker_thread( + func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter + ) + + +def current_default_thread_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of + concurrent threads. + + :return: a capacity limiter object + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_default_thread_limiter() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py new file mode 100644 index 0000000..5c6e065 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.py @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: MIT + +""" +Classes Without Boilerplate +""" + +from functools import partial +from typing import Callable, Literal, Protocol + +from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, has, resolve_types +from ._make import ( + NOTHING, + Attribute, + Converter, + Factory, + _Nothing, + attrib, + attrs, + evolve, + fields, + fields_dict, + make_class, + validate, +) +from ._next_gen import define, field, frozen, mutable +from ._version_info import VersionInfo + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + + +class AttrsInstance(Protocol): + pass + + +NothingType = Literal[_Nothing.NOTHING] + +__all__ = [ + "NOTHING", + "Attribute", + "AttrsInstance", + "Converter", + "Factory", + "NothingType", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "get_run_validators", + "has", + "ib", + "make_class", + "mutable", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] + + +def _make_getattr(mod_name: str) -> Callable: + """ + Create a metadata proxy for packaging information that uses *mod_name* in + its warnings and errors. + """ + + def __getattr__(name: str) -> str: + if name not in ("__version__", "__version_info__"): + msg = f"module {mod_name} has no attribute {name}" + raise AttributeError(msg) + + from importlib.metadata import metadata + + meta = metadata("attrs") + + if name == "__version_info__": + return VersionInfo._from_version_string(meta["version"]) + + return meta["version"] + + return __getattr__ + + +__getattr__ = _make_getattr(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi new file mode 100644 index 0000000..758decf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi @@ -0,0 +1,389 @@ +import enum +import sys + +from typing import ( + Any, + Callable, + Generic, + Literal, + Mapping, + Protocol, + Sequence, + TypeVar, + overload, +) + +# `import X as X` is required to make these public +from . import converters as converters +from . import exceptions as exceptions +from . import filters as filters +from . import setters as setters +from . import validators as validators +from ._cmp import cmp_using as cmp_using +from ._typing_compat import AttrsInstance_ +from ._version_info import VersionInfo +from attrs import ( + define as define, + field as field, + mutable as mutable, + frozen as frozen, + _EqOrderType, + _ValidatorType, + _ConverterType, + _ReprArgType, + _OnSetAttrType, + _OnSetAttrArgType, + _FieldTransformer, + _ValidatorArgType, +) + +if sys.version_info >= (3, 10): + from typing import TypeGuard, TypeAlias +else: + from typing_extensions import TypeGuard, TypeAlias + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_FilterType = Callable[["Attribute[_T]", _T], bool] + +# We subclass this here to keep the protocol's qualified name clean. +class AttrsInstance(AttrsInstance_, Protocol): + pass + +_A = TypeVar("_A", bound=type[AttrsInstance]) + +class _Nothing(enum.Enum): + NOTHING = enum.auto() + +NOTHING = _Nothing.NOTHING +NothingType: TypeAlias = Literal[_Nothing.NOTHING] + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. + +@overload +def Factory(factory: Callable[[], _T]) -> _T: ... +@overload +def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], +) -> _T: ... +@overload +def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], +) -> _T: ... + +In = TypeVar("In") +Out = TypeVar("Out") + +class Converter(Generic[In, Out]): + @overload + def __init__(self, converter: Callable[[In], Out]) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance, Attribute], Out], + *, + takes_self: Literal[True], + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, Attribute], Out], + *, + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance], Out], + *, + takes_self: Literal[True], + ) -> None: ... + +class Attribute(Generic[_T]): + name: str + default: _T | None + validator: _ValidatorType[_T] | None + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: bool | None + init: bool + converter: Converter | None + metadata: dict[Any, Any] + type: type[_T] | None + kw_only: bool + on_setattr: _OnSetAttrType + alias: str | None + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: object = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: _C, + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> _C: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: None = ..., + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> Callable[[_C], _C]: ... +def fields(cls: type[AttrsInstance] | AttrsInstance) -> Any: ... +def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ... +def validate(inst: AttrsInstance) -> None: ... +def resolve_types( + cls: _A, + globalns: dict[str, Any] | None = ..., + localns: dict[str, Any] | None = ..., + attribs: list[Attribute[Any]] | None = ..., + include_extras: bool = ..., +) -> _A: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: list[str] | tuple[str, ...] | dict[str, Any], + bases: tuple[type, ...] = ..., + class_body: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + collect_by_mro: bool = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +# XXX: remember to fix attrs.asdict/astuple too! +def asdict( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + dict_factory: type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ..., + tuple_keys: bool | None = ..., +) -> dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + tuple_factory: type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> tuple[Any, ...]: ... +def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py new file mode 100644 index 0000000..09bab49 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.py @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: MIT + + +import functools +import types + +from ._make import __ne__ + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, + and ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if at least + one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + Args: + eq (typing.Callable | None): + Callable used to evaluate equality of two objects. + + lt (typing.Callable | None): + Callable used to evaluate whether one object is less than another + object. + + le (typing.Callable | None): + Callable used to evaluate whether one object is less than or equal + to another object. + + gt (typing.Callable | None): + Callable used to evaluate whether one object is greater than + another object. + + ge (typing.Callable | None): + Callable used to evaluate whether one object is greater than or + equal to another object. + + require_same_type (bool): + When `True`, equality and ordering methods will return + `NotImplemented` if objects are not of the same type. + + class_name (str | None): Name of class. Defaults to "Comparable". + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = __ne__ + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + msg = "eq must be define is order to complete ordering from lt, le, gt, ge." + raise ValueError(msg) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = f"__{name}__" + method.__doc__ = ( + f"Return a {_operation_names[name]} b. Computed by attrs." + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + return all(func(self, other) for func in self._requirements) + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi new file mode 100644 index 0000000..cc7893b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi @@ -0,0 +1,13 @@ +from typing import Any, Callable + +_CompareWithType = Callable[[Any, Any], bool] + +def cmp_using( + eq: _CompareWithType | None = ..., + lt: _CompareWithType | None = ..., + le: _CompareWithType | None = ..., + gt: _CompareWithType | None = ..., + ge: _CompareWithType | None = ..., + require_same_type: bool = ..., + class_name: str = ..., +) -> type: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py new file mode 100644 index 0000000..bc68ed9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_compat.py @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: MIT + +import inspect +import platform +import sys +import threading + +from collections.abc import Mapping, Sequence # noqa: F401 +from typing import _GenericAlias + + +PYPY = platform.python_implementation() == "PyPy" +PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) +PY_3_11_PLUS = sys.version_info[:2] >= (3, 11) +PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) +PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) +PY_3_14_PLUS = sys.version_info[:2] >= (3, 14) + + +if PY_3_14_PLUS: + import annotationlib + + # We request forward-ref annotations to not break in the presence of + # forward references. + + def _get_annotations(cls): + return annotationlib.get_annotations( + cls, format=annotationlib.Format.FORWARDREF + ) + +else: + + def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + return cls.__dict__.get("__annotations__", {}) + + +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ + + __slots__ = ["sig"] + + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None + + def get_first_param_type(self): + """ + Return the type annotation of the first argument if it's not empty. + """ + if not self.sig: + return None + + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation + + return None + + def get_return_type(self): + """ + Return the return type if it's not empty. + """ + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation + + return None + + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() + + +def get_generic_base(cl): + """If this is a generic class (A[str]), return the generic base for it.""" + if cl.__class__ is _GenericAlias: + return cl.__origin__ + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py new file mode 100644 index 0000000..4b25772 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_config.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + +__all__ = ["get_run_validators", "set_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. + """ + if not isinstance(run, bool): + msg = "'run' must be bool." + raise TypeError(msg) + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. + """ + return _run_validators diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py new file mode 100644 index 0000000..1adb500 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_funcs.py @@ -0,0 +1,497 @@ +# SPDX-License-Identifier: MIT + + +import copy + +from ._compat import get_generic_base +from ._make import _OBJ_SETATTR, NOTHING, fields +from .exceptions import AttrsAttributeNotFoundError + + +_ATOMIC_TYPES = frozenset( + { + type(None), + bool, + int, + float, + str, + complex, + bytes, + type(...), + type, + range, + property, + } +) + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the *attrs* attribute values of *inst* as a dict. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + dict_factory (~typing.Callable): + A callable to produce dictionaries from. For example, to produce + ordered dictionaries instead of normal Python dictionaries, pass in + ``collections.OrderedDict``. + + retain_collection_types (bool): + Do not convert to `list` when encountering an attribute whose type + is `tuple` or `set`. Only meaningful if *recurse* is `True`. + + value_serializer (typing.Callable | None): + A hook that is called for every attribute or dict key/value. It + receives the current instance, field and value and must return the + (updated) value. The hook is run *after* the optional *filter* has + been applied. + + Returns: + Return type of *dict_factory*. + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 + If a dict has a collection for a key, it is serialized as a tuple. + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + value_type = type(v) + if value_type in _ATOMIC_TYPES: + rv[a.name] = v + elif has(value_type): + rv[a.name] = asdict( + v, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif issubclass(value_type, (tuple, list, set, frozenset)): + cf = value_type if retain_collection_types is True else list + items = [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + try: + rv[a.name] = cf(items) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv[a.name] = cf(*items) + elif issubclass(value_type, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in v.items() + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + is_key, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + val_type = type(val) + if val_type in _ATOMIC_TYPES: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + elif getattr(val_type, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif issubclass(val_type, (tuple, list, set, frozenset)): + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + + rv = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in val + ] + ) + elif issubclass(val_type, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in val.items() + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the *attrs* attribute values of *inst* as a tuple. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): + Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + tuple_factory (~typing.Callable): + A callable to produce tuples from. For example, to produce lists + instead of tuples. + + retain_collection_types (bool): + Do not convert to `list` or `dict` when encountering an attribute + which type is `tuple`, `dict` or `set`. Only meaningful if + *recurse* is `True`. + + Returns: + Return type of *tuple_factory* + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + value_type = type(v) + if recurse is True: + if value_type in _ATOMIC_TYPES: + rv.append(v) + elif has(value_type): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif issubclass(value_type, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + items = [ + ( + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + ) + for j in v + ] + try: + rv.append(cf(items)) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv.append(cf(*items)) + elif issubclass(value_type, dict): + df = value_type if retain is True else dict + rv.append( + df( + ( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk + ), + ( + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv + ), + ) + for kk, vv in v.items() + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with *attrs* attributes. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + Returns: + bool: + """ + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is not None: + return True + + # No attrs, maybe it's a specialized generic (A[str])? + generic_base = get_generic_base(cls) + if generic_base is not None: + generic_attrs = getattr(generic_base, "__attrs_attrs__", None) + if generic_attrs is not None: + # Stick it on here for speed next time. + cls.__attrs_attrs__ = generic_attrs + return generic_attrs is not None + return False + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + This is different from `evolve` that applies the changes to the arguments + that create the new instance. + + `evolve`'s behavior is preferable, but there are `edge cases`_ where it + doesn't work. Therefore `assoc` is deprecated, but will not be removed. + + .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 + + Args: + inst: Instance of a class with *attrs* attributes. + + changes: Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + attrs.exceptions.AttrsAttributeNotFoundError: + If *attr_name* couldn't be found on *cls*. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. deprecated:: 17.1.0 + Use `attrs.evolve` instead if you can. This function will not be + removed du to the slightly different approach compared to + `attrs.evolve`, though. + """ + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in changes.items(): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + msg = f"{k} is not an attrs attribute on {new.__class__}." + raise AttrsAttributeNotFoundError(msg) + _OBJ_SETATTR(new, k, v) + return new + + +def resolve_types( + cls, globalns=None, localns=None, attribs=None, include_extras=True +): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in :class:`Attribute`'s + *type* field. In other words, you don't need to resolve your types if you + only use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, for example, if the name only + exists inside a method, you may pass *globalns* or *localns* to specify + other dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + Args: + cls (type): Class to resolve. + + globalns (dict | None): Dictionary containing global variables. + + localns (dict | None): Dictionary containing local variables. + + attribs (list | None): + List of attribs for the given class. This is necessary when calling + from inside a ``field_transformer`` since *cls* is not an *attrs* + class yet. + + include_extras (bool): + Resolve more accurately, if possible. Pass ``include_extras`` to + ``typing.get_hints``, if supported by the typing module. On + supported Python versions (3.9+), this resolves the types more + accurately. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class and you didn't pass any attribs. + + NameError: If types cannot be resolved because of missing variables. + + Returns: + *cls* so you can use this function also as a class decorator. Please + note that you have to apply it **after** `attrs.define`. That means the + decorator has to come in the line **before** `attrs.define`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + .. versionadded:: 23.1.0 *include_extras* + """ + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: + import typing + + kwargs = { + "globalns": globalns, + "localns": localns, + "include_extras": include_extras, + } + + hints = typing.get_type_hints(cls, **kwargs) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _OBJ_SETATTR(field, "type", hints[field.name]) + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls + + # Return the class so you can use it as a decorator too. + return cls diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py new file mode 100644 index 0000000..4b32d6a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_make.py @@ -0,0 +1,3406 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import abc +import contextlib +import copy +import enum +import inspect +import itertools +import linecache +import sys +import types +import unicodedata +import weakref + +from collections.abc import Callable, Mapping +from functools import cached_property +from typing import Any, NamedTuple, TypeVar + +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters +from ._compat import ( + PY_3_10_PLUS, + PY_3_11_PLUS, + PY_3_13_PLUS, + _AnnotationExtractor, + _get_annotations, + get_generic_base, +) +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + UnannotatedAttributeError, +) + + +# This is used at least twice, so cache it here. +_OBJ_SETATTR = object.__setattr__ +_INIT_FACTORY_PAT = "__attr_factory_%s" +_CLASSVAR_PREFIXES = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_HASH_CACHE_FIELD = "_attrs_cached_hash" + +_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) + +# Unique object for unequivocal getattr() defaults. +_SENTINEL = object() + +_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) + + +class _Nothing(enum.Enum): + """ + Sentinel to indicate the lack of a value when `None` is ambiguous. + + If extending attrs, you can use ``typing.Literal[NOTHING]`` to show + that a value may be ``NOTHING``. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. + """ + + NOTHING = enum.auto() + + def __repr__(self): + return "NOTHING" + + def __bool__(self): + return False + + +NOTHING = _Nothing.NOTHING +""" +Sentinel to indicate the lack of a value when `None` is ambiguous. + +When using in 3rd party code, use `attrs.NothingType` for type annotations. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since `None` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=None, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new field / attribute on a class. + + Identical to `attrs.field`, except it's not keyword-only. + + Consider using `attrs.field` in new code (``attr.ib`` will *never* go away, + though). + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attr.s` (or similar)! + + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is `None` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 + *converter* as a replacement for the deprecated *convert* to achieve + consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 22.2.0 *alias* + .. versionchanged:: 25.4.0 + *kw_only* can now be None, and its default is also changed from False to + None. + """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + + if hash is not None and hash is not True and hash is not False: + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if factory is not None: + if default is not NOTHING: + msg = ( + "The `default` and `factory` arguments are mutually exclusive." + ) + raise ValueError(msg) + if not callable(factory): + msg = "The `factory` argument must be a callable." + raise ValueError(msg) + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, + alias=alias, + ) + + +def _compile_and_eval( + script: str, + globs: dict[str, Any] | None, + locs: Mapping[str, object] | None = None, + filename: str = "", +) -> None: + """ + Evaluate the script with the given global (globs) and local (locs) + variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _linecache_and_compile( + script: str, + filename: str, + globs: dict[str, Any] | None, + locals: Mapping[str, object] | None = None, +) -> dict[str, Any]: + """ + Cache the script with _linecache_, compile it and return the _locals_. + """ + + locs = {} if locals is None else locals + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + + filename = f"{base_filename[:-1]}-{count}>" + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs + + +def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type: + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = f"{cls_name}Attributes" + body = {} + for i, attr_name in enumerate(attr_names): + + def getter(self, i=i): + return self[i] + + body[attr_name] = property(getter) + return type(attr_class_name, (tuple,), body) + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +class _Attributes(NamedTuple): + attrs: type + base_attrs: list[Attribute] + base_attrs_map: dict[str, type] + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_CLASSVAR_PREFIXES) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + """ + return attrib_name in cls.__dict__ + + +def _collect_base_attrs( + cls, taken_attr_names +) -> tuple[list[Attribute], dict[str, type]]: + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, +) -> _Attributes: + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + If *collect_by_mro* is True, collect them in the correct MRO order, + otherwise use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = list(these.items()) + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if attr.__class__ is _CountingAttr + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if a.__class__ is not _CountingAttr: + a = attrib(a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if unannotated: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if attr.__class__ is _CountingAttr + ), + key=lambda e: e[1].counter, + ) + + fca = Attribute.from_counting_attr + no = ClassProps.KeywordOnly.NO + own_attrs = [ + fca( + attr_name, + ca, + kw_only is not no, + anns.get(attr_name), + ) + for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + if kw_only is ClassProps.KeywordOnly.FORCE: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = base_attrs + own_attrs + + # Resolve default field alias before executing field_transformer, so that + # the transformer receives fully populated Attribute objects with usable + # alias values. + for a in attrs: + if not a.alias: + # Evolve is very slow, so we hold our nose and do it dirty. + _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name)) + _OBJ_SETATTR.__get__(a)("alias_is_default", True) + + if field_transformer is not None: + attrs = tuple(field_transformer(cls, attrs)) + + # Check attr order after executing the field_transformer. + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" + raise ValueError(msg) + + if had_default is False and a.default is not NOTHING: + had_default = True + + # Resolve default field alias for any new attributes that the + # field_transformer may have added without setting an alias. + for a in attrs: + if not a.alias: + _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name)) + _OBJ_SETATTR.__get__(a)("alias_is_default", True) + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map) + + +def _make_cached_property_getattr(cached_properties, original_getattr, cls): + lines = [ + # Wrapped to get `__class__` into closure cell for super() + # (It will be replaced with the newly constructed class after construction). + "def wrapper(_cls):", + " __class__ = _cls", + " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", + " func = cached_properties.get(item)", + " if func is not None:", + " result = func(self)", + " _setter = _cached_setattr_get(self)", + " _setter(item, result)", + " return result", + ] + if original_getattr is not None: + lines.append( + " return original_getattr(self, item)", + ) + else: + lines.extend( + [ + " try:", + " return super().__getattribute__(item)", + " except AttributeError:", + " if not hasattr(super(), '__getattr__'):", + " raise", + " return super().__getattr__(item)", + " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", + " raise AttributeError(original_error)", + ] + ) + + lines.extend( + [ + " return __getattr__", + "__getattr__ = wrapper(_cls)", + ] + ) + + unique_filename = _generate_unique_filename(cls, "getattr") + + glob = { + "cached_properties": cached_properties, + "_cached_setattr_get": _OBJ_SETATTR.__get__, + "original_getattr": original_getattr, + } + + return _linecache_and_compile( + "\n".join(lines), unique_filename, glob, locals={"_cls": cls} + )["__getattr__"] + + +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + "__traceback__", + "__suppress_context__", + "__notes__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + if isinstance(self, BaseException) and name == "__notes__": + BaseException.__delattr__(self, name) + return + + raise FrozenInstanceError + + +def evolve(*args, **changes): + """ + Create a new instance, based on the first positional argument with + *changes* applied. + + .. tip:: + + On Python 3.13 and later, you can also use `copy.replace` instead. + + Args: + + inst: + Instance of a class with *attrs* attributes. *inst* must be passed + as a positional argument. + + changes: + Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + TypeError: + If *attr_name* couldn't be found in the class ``__init__``. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 17.1.0 + .. deprecated:: 23.1.0 + It is now deprecated to pass the instance using the keyword argument + *inst*. It will raise a warning until at least April 2024, after which + it will become an error. Always pass the instance as a positional + argument. + .. versionchanged:: 24.1.0 + *inst* can't be passed as a keyword argument anymore. + """ + try: + (inst,) = args + except ValueError: + msg = ( + f"evolve() takes 1 positional argument, but {len(args)} were given" + ) + raise TypeError(msg) from None + + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = a.alias + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +class _ClassBuilder: + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_add_method_dunders", + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_custom_setattr", + "_has_post_init", + "_has_pre_init", + "_is_exc", + "_on_setattr", + "_pre_init_has_args", + "_repr_added", + "_script_snippets", + "_slots", + "_weakref_slot", + "_wrote_own_setattr", + ) + + def __init__( + self, + cls: type, + these, + auto_attribs: bool, + props: ClassProps, + has_custom_setattr: bool, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + props.kw_only, + props.collected_fields_by_mro, + props.field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if props.is_slotted else {} + self._attrs = attrs + self._base_names = {a.name for a in base_attrs} + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = props.is_slotted + self._frozen = props.is_frozen + self._weakref_slot = props.has_weakref_slot + self._cache_hash = ( + props.hashability is ClassProps.Hashability.HASHABLE_CACHED + ) + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._pre_init_has_args = False + if self._has_pre_init: + # Check if the pre init method has more arguments than just `self` + # We want to pass arguments if pre init expects arguments + pre_init_func = cls.__attrs_pre_init__ + pre_init_signature = inspect.signature(pre_init_func) + self._pre_init_has_args = len(pre_init_signature.parameters) > 1 + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = props.is_exception + self._on_setattr = props.on_setattr_hook + + self._has_custom_setattr = has_custom_setattr + self._wrote_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + self._cls_dict["__attrs_props__"] = props + + if props.is_frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._wrote_own_setattr = True + elif self._on_setattr in ( + _DEFAULT_ON_SETATTR, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + self._on_setattr == _DEFAULT_ON_SETATTR + and not (has_validator or has_converter) + ) + or (self._on_setattr == setters.validate and not has_validator) + or (self._on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None + + if props.added_pickling: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + # tuples of script, globs, hook + self._script_snippets: list[ + tuple[str, dict, Callable[[dict, dict], Any]] + ] = [] + self._repr_added = False + + # We want to only do this check once; in 99.9% of cases these + # exist. + if not hasattr(self._cls, "__module__") or not hasattr( + self._cls, "__qualname__" + ): + self._add_method_dunders = self._add_method_dunders_safe + else: + self._add_method_dunders = self._add_method_dunders_unsafe + + def __repr__(self): + return f"<_ClassBuilder(cls={self._cls.__name__})>" + + def _eval_snippets(self) -> None: + """ + Evaluate any registered snippets in one go. + """ + script = "\n".join([snippet[0] for snippet in self._script_snippets]) + globs = {} + for _, snippet_globs, _ in self._script_snippets: + globs.update(snippet_globs) + + locs = _linecache_and_compile( + script, + _generate_unique_filename(self._cls, "methods"), + globs, + ) + + for _, _, hook in self._script_snippets: + hook(self._cls_dict, locs) + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + self._eval_snippets() + if self._slots is True: + cls = self._create_slots_class() + self._cls.__attrs_base_of_slotted__ = weakref.ref(cls) + else: + cls = self._patch_original_class() + if PY_3_10_PLUS: + cls = abc.update_abstractmethods(cls) + + # The method gets only called if it's not inherited from a base class. + # _has_own_attribute does NOT work properly for classmethods. + if ( + getattr(cls, "__attrs_init_subclass__", None) + and "__attrs_init_subclass__" not in cls.__dict__ + ): + cls.__attrs_init_subclass__() + + return cls + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _SENTINEL) is not _SENTINEL + ): + # An AttributeError can happen if a base class defines a + # class variable and we want to set an attribute with the + # same name by using only a type annotation. + with contextlib.suppress(AttributeError): + delattr(cls, name) + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._wrote_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = _OBJ_SETATTR + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + cd = { + k: v + for k, v in self._cls_dict.items() + if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") + } + + # 3.14.0rc2+ + if hasattr(sys, "_clear_type_descriptors"): + sys._clear_type_descriptors(self._cls) + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._wrote_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = _OBJ_SETATTR + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = {} + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + cached_properties = { + name: cached_prop.func + for name, cached_prop in cd.items() + if isinstance(cached_prop, cached_property) + } + + # Collect methods with a `__class__` reference that are shadowed in the new class. + # To know to update them. + additional_closure_functions_to_update = [] + if cached_properties: + class_annotations = _get_annotations(self._cls) + for name, func in cached_properties.items(): + # Add cached properties to names for slotting. + names += (name,) + # Clear out function from class to avoid clashing. + del cd[name] + additional_closure_functions_to_update.append(func) + annotation = inspect.signature(func).return_annotation + if annotation is not inspect.Parameter.empty: + class_annotations[name] = annotation + + original_getattr = cd.get("__getattr__") + if original_getattr is not None: + additional_closure_functions_to_update.append(original_getattr) + + cd["__getattr__"] = _make_cached_property_getattr( + cached_properties, original_getattr, self._cls + ) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overridden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in existing_slots.items() + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) + if self._cache_hash: + slot_names.append(_HASH_CACHE_FIELD) + + cd["__slots__"] = tuple(slot_names) + + cd["__qualname__"] = self._cls.__qualname__ + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # . + # If a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in itertools.chain( + cls.__dict__.values(), additional_closure_functions_to_update + ): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # noqa: PERF203 + # ValueError: Cell is empty + pass + else: + if match: + cell.cell_contents = cls + return cls + + def add_repr(self, ns): + script, globs = _make_repr_script(self._attrs, ns) + + def _attach_repr(cls_dict, globs): + cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"]) + + self._script_snippets.append((script, globs, _attach_repr)) + self._repr_added = True + return self + + def add_str(self): + if not self._repr_added: + msg = "__str__ can only be generated if a __repr__ exists." + raise ValueError(msg) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return {name: getattr(self, name) for name in state_attr_names} + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _OBJ_SETATTR.__get__(self) + if isinstance(state, tuple): + # Backward compatibility with attrs instances pickled with + # attrs versions before v22.2.0 which stored tuples. + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + else: + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_HASH_CACHE_FIELD, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + script, globs = _make_hash_script( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + + def attach_hash(cls_dict: dict, locs: dict) -> None: + cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"]) + + self._script_snippets.append((script, globs, attach_hash)) + + return self + + def add_init(self): + script, globs, annotations = _make_init_script( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=False, + ) + + def _attach_init(cls_dict, globs): + init = globs["__init__"] + init.__annotations__ = annotations + cls_dict["__init__"] = self._add_method_dunders(init) + + self._script_snippets.append((script, globs, _attach_init)) + + return self + + def add_replace(self): + self._cls_dict["__replace__"] = self._add_method_dunders(evolve) + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + script, globs, annotations = _make_init_script( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, + ) + + def _attach_attrs_init(cls_dict, globs): + init = globs["__attrs_init__"] + init.__annotations__ = annotations + cls_dict["__attrs_init__"] = self._add_method_dunders(init) + + self._script_snippets.append((script, globs, _attach_attrs_init)) + + return self + + def add_eq(self): + cd = self._cls_dict + + script, globs = _make_eq_script(self._attrs) + + def _attach_eq(cls_dict, globs): + cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"]) + + self._script_snippets.append((script, globs, _attach_eq)) + + cd["__ne__"] = __ne__ + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + msg = "Can't combine custom __setattr__ with on_setattr hooks." + raise ValueError(msg) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _OBJ_SETATTR(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._wrote_own_setattr = True + + return self + + def _add_method_dunders_unsafe(self, method: Callable) -> Callable: + """ + Add __module__ and __qualname__ to a *method*. + """ + method.__module__ = self._cls.__module__ + + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" + + method.__doc__ = ( + f"Method generated by attrs for class {self._cls.__qualname__}." + ) + + return method + + def _add_method_dunders_safe(self, method: Callable) -> Callable: + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + with contextlib.suppress(AttributeError): + method.__module__ = self._cls.__module__ + + with contextlib.suppress(AttributeError): + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" + + with contextlib.suppress(AttributeError): + method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}." + + return method + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, + unsafe_hash=None, + force_kw_only=True, +): + r""" + A class decorator that adds :term:`dunder methods` according to the + specified attributes using `attr.ib` or the *these* argument. + + Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will + *never* go away, though). + + Args: + repr_ns (str): + When using nested classes, there was no way in Python 2 to + automatically detect that. This argument allows to set a custom + name for a more meaningful ``repr`` output. This argument is + pointless in Python 3 and is therefore deprecated. + + .. caution:: + Refer to `attrs.define` for the rest of the parameters, but note that they + can have different defaults. + + Notably, leaving *on_setattr* as `None` will **not** add any hooks. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports `None` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. deprecated:: 24.1.0 *repr_ns* + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + .. versionchanged:: 25.4.0 + *kw_only* now only applies to attributes defined in the current class, + and respects attribute-level ``kw_only=False`` settings. + .. versionadded:: 25.4.0 *force_kw_only* + """ + if repr_ns is not None: + import warnings + + warnings.warn( + DeprecationWarning( + "The `repr_ns` argument is deprecated and will be removed in or after August 2025." + ), + stacklevel=2, + ) + + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + + # unsafe_hash takes precedence due to PEP 681. + if unsafe_hash is not None: + hash = unsafe_hash + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + nonlocal hash + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + msg = "Can't freeze a class with a custom __setattr__." + raise ValueError(msg) + + eq = not is_exc and _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + + Hashability = ClassProps.Hashability + + if is_exc: + hashability = Hashability.LEAVE_ALONE + elif hash is True: + hashability = ( + Hashability.HASHABLE_CACHED + if cache_hash + else Hashability.HASHABLE + ) + elif hash is False: + hashability = Hashability.LEAVE_ALONE + elif hash is None: + if auto_detect is True and _has_own_attribute(cls, "__hash__"): + hashability = Hashability.LEAVE_ALONE + elif eq is True and is_frozen is True: + hashability = ( + Hashability.HASHABLE_CACHED + if cache_hash + else Hashability.HASHABLE + ) + elif eq is False: + hashability = Hashability.LEAVE_ALONE + else: + hashability = Hashability.UNHASHABLE + else: + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + KeywordOnly = ClassProps.KeywordOnly + if kw_only: + kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES + else: + kwo = KeywordOnly.NO + + props = ClassProps( + is_exception=is_exc, + is_frozen=is_frozen, + is_slotted=slots, + collected_fields_by_mro=collect_by_mro, + added_init=_determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ), + added_repr=_determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ), + added_eq=eq, + added_ordering=not is_exc + and _determine_whether_to_implement( + cls, + order_, + auto_detect, + ("__lt__", "__le__", "__gt__", "__ge__"), + ), + hashability=hashability, + added_match_args=match_args, + kw_only=kwo, + has_weakref_slot=weakref_slot, + added_str=str, + added_pickling=_determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + on_setattr_hook=on_setattr, + field_transformer=field_transformer, + ) + + if not props.is_hashable and cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) + + builder = _ClassBuilder( + cls, + these, + auto_attribs=auto_attribs, + props=props, + has_custom_setattr=has_own_setattr, + ) + + if props.added_repr: + builder.add_repr(repr_ns) + + if props.added_str: + builder.add_str() + + if props.added_eq: + builder.add_eq() + if props.added_ordering: + builder.add_order() + + if not frozen: + builder.add_setattr() + + if props.is_hashable: + builder.add_hash() + elif props.hashability is Hashability.UNHASHABLE: + builder.make_unhashable() + + if props.added_init: + builder.add_init() + else: + builder.add_attrs_init() + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, init must be True." + raise TypeError(msg) + + if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"): + builder.add_replace() + + if ( + PY_3_10_PLUS + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs + + +def _generate_unique_filename(cls: type, func_name: str) -> str: + """ + Create a "filename" suitable for a function being generated. + """ + return ( + f"" + ) + + +def _make_hash_script( + cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool +) -> tuple[str, dict]: + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + type_hash = hash(_generate_unique_filename(cls, "hash")) + # If eq is custom generated, we need to include the functions in globs + globs = {} + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + hash_def += ", *" + + hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + f" {type_hash},", + ] + ) + + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + globs[cmp_name] = a.eq_key + method_lines.append( + indent + f" {cmp_name}(self.{a.name})," + ) + else: + method_lines.append(indent + f" self.{a.name},") + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") + if frozen: + append_hash_computation_lines( + f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + f"self.{_HASH_CACHE_FIELD} = ", tab * 2 + ) + method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + return script, globs + + +def _add_hash(cls: type, attrs: list[Attribute]): + """ + Add a hash method to *cls*. + """ + script, globs = _make_hash_script( + cls, attrs, frozen=False, cache_hash=False + ) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__hash__") + ) + cls.__hash__ = globs["__hash__"] + return cls + + +def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + +def _make_eq_script(attrs: list) -> tuple[str, dict]: + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + + globs = {} + if attrs: + lines.append(" return (") + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})" + ) + else: + lines.append(f" self.{a.name} == other.{a.name}") + if a is not attrs[-1]: + lines[-1] = f"{lines[-1]} and" + lines.append(" )") + else: + lines.append(" return True") + + script = "\n".join(lines) + + return script, globs + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + script, globs = _make_eq_script(attrs) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__eq__") + ) + cls.__eq__ = globs["__eq__"] + cls.__ne__ = __ne__ + + return cls + + +def _make_repr_script(attrs, ns) -> tuple[str, dict]: + """ + Create the source and globs for a __repr__ and return it. + """ + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) + + if ns is None: + cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + f" return f'{cls_name_fragment}({repr_fragment})'", + " finally:", + " already_repring.remove(id(self))", + ] + + return "\n".join(lines), globs + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + script, globs = _make_repr_script(attrs, ns) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__repr__") + ) + cls.__repr__ = globs["__repr__"] + return cls + + +def fields(cls): + """ + Return the tuple of *attrs* attributes for a class or instance. + + The tuple also allows accessing the fields by their names (see below for + examples). + + Args: + cls (type): Class or instance to introspect. + + Raises: + TypeError: If *cls* is neither a class nor an *attrs* instance. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + tuple (with name accessors) of `attrs.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + .. versionchanged:: 23.1.0 Add support for generic classes. + .. versionchanged:: 26.1.0 Add support for instances. + """ + generic_base = get_generic_base(cls) + + if generic_base is None and not isinstance(cls, type): + type_ = type(cls) + if getattr(type_, "__attrs_attrs__", None) is None: + msg = "Passed object must be a class or attrs instance." + raise TypeError(msg) + + return fields(type_) + + attrs = getattr(cls, "__attrs_attrs__", None) + + if attrs is None: + if generic_base is not None: + attrs = getattr(generic_base, "__attrs_attrs__", None) + if attrs is not None: + # Even though this is global state, stick it on here to speed + # it up. We rely on `cls` being cached for this to be + # efficient. + cls.__attrs_attrs__ = attrs + return attrs + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of *attrs* attributes for a class, whose keys + are the attribute names. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + dict[str, attrs.Attribute]: Dict of attribute name to definition + + .. versionadded:: 18.1.0 + """ + if not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + return {a.name: a for a in attrs} + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + Args: + inst: Instance of a class with *attrs* attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + cls = base_attr_map.get(a_name) + return cls and "__slots__" in cls.__dict__ + + +def _make_init_script( + cls, + attrs, + pre_init, + pre_init_has_args, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + cls_on_setattr, + attrs_init, +) -> tuple[str, dict, dict]: + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True and a.on_setattr is not setters.NO_OP: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = True + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: + needs_cached_setattr = True + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + pre_init_has_args, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + "__attrs_init__" if attrs_init else "__init__", + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ + + return script, globs, annotations + + +def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return f"_setattr('{attr_name}', {value_var})" + + +def _setattr_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})" + + +def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return f"self.{attr_name} = {value}" + + +def _assign_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True, converter) + + return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}" + + +def _determine_setters( + frozen: bool, slots: bool, base_attr_map: dict[str, type] +): + """ + Determine the correct setter functions based on whether a class is frozen + and/or slotted. + """ + if frozen is True: + if slots is True: + return (), _setattr, _setattr_with_converter + + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + + def fmt_setter( + attr_name: str, value_var: str, has_on_setattr: bool + ) -> str: + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return f"_inst_dict['{attr_name}'] = {value_var}" + + def fmt_setter_with_converter( + attr_name: str, + value_var: str, + has_on_setattr: bool, + converter: Converter, + ) -> str: + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr, converter + ) + + return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}" + + return ( + ("_inst_dict = self.__dict__",), + fmt_setter, + fmt_setter_with_converter, + ) + + # Not frozen -- we can just assign directly. + return (), _assign, _assign_with_converter + + +def _attrs_to_init_script( + attrs: list[Attribute], + is_frozen: bool, + is_slotted: bool, + call_pre_init: bool, + pre_init_has_args: bool, + call_post_init: bool, + does_cache_hash: bool, + base_attr_map: dict[str, type], + is_exc: bool, + needs_cached_setattr: bool, + has_cls_on_setattr: bool, + method_name: str, +) -> tuple[str, dict, dict]: + """ + Return a script of an initializer for *attrs*, a dict of globals, and + annotations for the initializer. + + The globals are required by the generated script. + """ + lines = ["self.__attrs_pre_init__()"] if call_pre_init else [] + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. Note _setattr will be used again below if + # does_cache_hash is True. + "_setattr = _cached_setattr_get(self)" + ) + + extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( + is_frozen, is_slotted, base_attr_map + ) + lines.extend(extra_lines) + + args = [] # Parameters in the definition of __init__ + pre_init_args = [] # Parameters in the call to __attrs_pre_init__ + kw_only_args = [] # Used for both 'args' and 'pre_init_args' above + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_cls_on_setattr + ) + # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not + # explicitly provided + arg_name = a.alias + + has_factory = isinstance(a.default, Factory) + maybe_self = "self" if has_factory and a.default.takes_self else "" + + if a.converter is not None and not isinstance(a.converter, Converter): + converter = Converter(a.converter) + else: + converter = a.converter + + if a.init is False: + if has_factory: + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + elif converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = f"{arg_name}=attr_dict['{attr_name}'].default" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + pre_init_args.append(arg_name) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = f"{arg_name}=NOTHING" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + pre_init_args.append(arg_name) + lines.append(f"if {arg_name} is not NOTHING:") + + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + pre_init_args.append(arg_name) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True: + if a.type is not None and converter is None: + annotations[arg_name] = a.type + elif converter is not None and converter._first_param_type: + # Use the type from the converter if present. + annotations[arg_name] = converter._first_param_type + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if call_post_init: + lines.append("self.__attrs_post_init__()") + + # Because this is set only after __attrs_post_init__ is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to field + # values during post-init combined with post-init accessing the hash code + # would result in silent bugs. + if does_cache_hash: + if is_frozen: + if is_slotted: + init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)" + else: + init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None" + else: + init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None" + lines.append(init_hash_cache) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join(f"self.{a.name}" for a in attrs if a.init) + + lines.append(f"BaseException.__init__(self, {vals})") + + args = ", ".join(args) + pre_init_args = ", ".join(pre_init_args) + if kw_only_args: + # leading comma & kw_only args + args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}" + pre_init_kw_only_args = ", ".join( + [ + f"{kw_arg_name}={kw_arg_name}" + # We need to remove the defaults from the kw_only_args. + for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args) + ] + ) + pre_init_args += ", " if pre_init_args else "" + pre_init_args += pre_init_kw_only_args + + if call_pre_init and pre_init_has_args: + # If pre init method has arguments, pass the values given to __init__. + lines[0] = f"self.__attrs_pre_init__({pre_init_args})" + + # Python <3.12 doesn't allow backslashes in f-strings. + NL = "\n " + return ( + f"""def {method_name}(self, {args}): + {NL.join(lines) if lines else "pass"} +""", + names_for_globals, + annotations, + ) + + +def _default_init_alias_for(name: str) -> str: + """ + The default __init__ parameter name for a field. + + This performs private-name adjustment via leading-unscore stripping, + and is the default value of Attribute.alias if not provided. + """ + + return name.lstrip("_") + + +class Attribute: + """ + *Read-only* representation of an attribute. + + .. warning:: + + You should never instantiate this class yourself. + + The class has *all* arguments of `attr.ib` (except for ``factory`` which is + only syntactic sugar for ``default=Factory(...)`` plus the following: + + - ``name`` (`str`): The name of the attribute. + - ``alias`` (`str`): The __init__ parameter name of the attribute, after + any explicit overrides and default private-attribute-name handling. + - ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically + generated (``True``) or explicitly provided by the user (``False``). + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The + callables that are used for comparing and ordering objects by this + attribute, respectively. These are set by passing a callable to + `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also + :ref:`comparison customization `. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The :ref:`field transformer ` hook receives a list of + them. + - The ``alias`` property exposes the __init__ parameter name of the field, + with any overrides and default private-attribute handling applied. + + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + .. versionadded:: 22.2.0 *alias* + .. versionadded:: 26.1.0 *alias_is_default* + + For the full version history of the fields, see `attr.ib`. + """ + + # These slots must NOT be reordered because we use them later for + # instantiation. + __slots__ = ( # noqa: RUF023 + "name", + "default", + "validator", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + "alias", + "alias_is_default", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, + alias=None, + alias_is_default=None, + ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + + # Cache this descriptor here to speed things up later. + bound_setattr = _OBJ_SETATTR.__get__(self) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + types.MappingProxyType(dict(metadata)) # Shallow copy + if metadata + else _EMPTY_METADATA_SINGLETON + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + bound_setattr("alias", alias) + bound_setattr( + "alias_is_default", + alias is None if alias_is_default is None else alias_is_default, + ) + + def __setattr__(self, name, value): + raise FrozenInstanceError + + @classmethod + def from_counting_attr( + cls, name: str, ca: _CountingAttr, kw_only: bool, type=None + ): + # The 'kw_only' argument is the class-level setting, and is used if the + # attribute itself does not explicitly set 'kw_only'. + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + msg = f"Type annotation and type argument cannot both be present for '{name}'." + raise ValueError(msg) + return cls( + name, + ca._default, + ca._validator, + ca.repr, + None, + ca.hash, + ca.init, + False, + ca.metadata, + type, + ca.converter, + kw_only if ca.kw_only is None else ca.kw_only, + ca.eq, + ca.eq_key, + ca.order, + ca.order_key, + ca.on_setattr, + ca.alias, + ca.alias is None, + ) + + # Don't use attrs.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attrs.evolve` but that function does not work + with :class:`attrs.Attribute`. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + if "alias" in changes and "alias_is_default" not in changes: + # Explicit alias provided -- no longer the default. + _OBJ_SETATTR.__get__(new)("alias_is_default", False) + elif ( + "name" in changes + and "alias" not in changes + # Don't auto-generate alias if the user picked picked the old one. + and self.alias_is_default + ): + # Name changed, alias was auto-generated -- update it. + _OBJ_SETATTR.__get__(new)( + "alias", _default_init_alias_for(new.name) + ) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + if len(state) < len(self.__slots__): + # Pre-26.1.0 pickle without alias_is_default -- infer it + # heuristically. + state_dict = dict(zip(self.__slots__, state)) + alias_is_default = state_dict.get( + "alias" + ) is None or state_dict.get("alias") == _default_init_alias_for( + state_dict["name"] + ) + state = (*state, alias_is_default) + + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _OBJ_SETATTR.__get__(self) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + ( + types.MappingProxyType(dict(value)) + if value + else _EMPTY_METADATA_SINGLETON + ), + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=(name != "alias_is_default"), + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + alias=_default_init_alias_for(name), + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr: + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "_default", + "_validator", + "alias", + "converter", + "counter", + "eq", + "eq_key", + "hash", + "init", + "kw_only", + "metadata", + "on_setattr", + "order", + "order_key", + "repr", + "type", + ) + __attrs_attrs__ = ( + *tuple( + Attribute( + name=name, + alias=_default_init_alias_for(name), + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + "alias", + ) + ), + Attribute( + name="metadata", + alias="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, + alias, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + self.alias = alias + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + Raises: + DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +class ClassProps: + """ + Effective class properties as derived from parameters to `attr.s()` or + `define()` decorators. + + This is the same data structure that *attrs* uses internally to decide how + to construct the final class. + + Warning: + + This feature is currently **experimental** and is not covered by our + strict backwards-compatibility guarantees. + + + Attributes: + is_exception (bool): + Whether the class is treated as an exception class. + + is_slotted (bool): + Whether the class is `slotted `. + + has_weakref_slot (bool): + Whether the class has a slot for weak references. + + is_frozen (bool): + Whether the class is frozen. + + kw_only (KeywordOnly): + Whether / how the class enforces keyword-only arguments on the + ``__init__`` method. + + collected_fields_by_mro (bool): + Whether the class fields were collected by method resolution order. + That is, correctly but unlike `dataclasses`. + + added_init (bool): + Whether the class has an *attrs*-generated ``__init__`` method. + + added_repr (bool): + Whether the class has an *attrs*-generated ``__repr__`` method. + + added_eq (bool): + Whether the class has *attrs*-generated equality methods. + + added_ordering (bool): + Whether the class has *attrs*-generated ordering methods. + + hashability (Hashability): How `hashable ` the class is. + + added_match_args (bool): + Whether the class supports positional `match ` over its + fields. + + added_str (bool): + Whether the class has an *attrs*-generated ``__str__`` method. + + added_pickling (bool): + Whether the class has *attrs*-generated ``__getstate__`` and + ``__setstate__`` methods for `pickle`. + + on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None): + The class's ``__setattr__`` hook. + + field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None): + The class's `field transformers `. + + .. versionadded:: 25.4.0 + """ + + class Hashability(enum.Enum): + """ + The hashability of a class. + + .. versionadded:: 25.4.0 + """ + + HASHABLE = "hashable" + """Write a ``__hash__``.""" + HASHABLE_CACHED = "hashable_cache" + """Write a ``__hash__`` and cache the hash.""" + UNHASHABLE = "unhashable" + """Set ``__hash__`` to ``None``.""" + LEAVE_ALONE = "leave_alone" + """Don't touch ``__hash__``.""" + + class KeywordOnly(enum.Enum): + """ + How attributes should be treated regarding keyword-only parameters. + + .. versionadded:: 25.4.0 + """ + + NO = "no" + """Attributes are not keyword-only.""" + YES = "yes" + """Attributes in current class without kw_only=False are keyword-only.""" + FORCE = "force" + """All attributes are keyword-only.""" + + __slots__ = ( # noqa: RUF023 -- order matters for __init__ + "is_exception", + "is_slotted", + "has_weakref_slot", + "is_frozen", + "kw_only", + "collected_fields_by_mro", + "added_init", + "added_repr", + "added_eq", + "added_ordering", + "hashability", + "added_match_args", + "added_str", + "added_pickling", + "on_setattr_hook", + "field_transformer", + ) + + def __init__( + self, + is_exception, + is_slotted, + has_weakref_slot, + is_frozen, + kw_only, + collected_fields_by_mro, + added_init, + added_repr, + added_eq, + added_ordering, + hashability, + added_match_args, + added_str, + added_pickling, + on_setattr_hook, + field_transformer, + ): + self.is_exception = is_exception + self.is_slotted = is_slotted + self.has_weakref_slot = has_weakref_slot + self.is_frozen = is_frozen + self.kw_only = kw_only + self.collected_fields_by_mro = collected_fields_by_mro + self.added_init = added_init + self.added_repr = added_repr + self.added_eq = added_eq + self.added_ordering = added_ordering + self.hashability = hashability + self.added_match_args = added_match_args + self.added_str = added_str + self.added_pickling = added_pickling + self.on_setattr_hook = on_setattr_hook + self.field_transformer = field_transformer + + @property + def is_hashable(self): + return ( + self.hashability is ClassProps.Hashability.HASHABLE + or self.hashability is ClassProps.Hashability.HASHABLE_CACHED + ) + + +_cas = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + alias=_default_init_alias_for(name), + ) + for name in ClassProps.__slots__ +] + +ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas) + + +class Factory: + """ + Stores a factory callable. + + If passed as the default value to `attrs.field`, the factory is used to + generate a new value. + + Args: + factory (typing.Callable): + A callable that takes either none or exactly one mandatory + positional argument depending on *takes_self*. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + __slots__ = ("factory", "takes_self") + + def __init__(self, factory, takes_self=False): + self.factory = factory + self.takes_self = takes_self + + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + + +class Converter: + """ + Stores a converter callable. + + Allows for the wrapped converter to take additional arguments. The + arguments are passed in the order they are documented. + + Args: + converter (Callable): A callable that converts the passed value. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. (default: `False`) + + takes_field (bool): + Pass the field definition (an :class:`Attribute`) into the + converter as a positional argument. (default: `False`) + + .. versionadded:: 24.1.0 + """ + + __slots__ = ( + "__call__", + "_first_param_type", + "_global_name", + "converter", + "takes_field", + "takes_self", + ) + + def __init__(self, converter, *, takes_self=False, takes_field=False): + self.converter = converter + self.takes_self = takes_self + self.takes_field = takes_field + + ex = _AnnotationExtractor(converter) + self._first_param_type = ex.get_first_param_type() + + if not (self.takes_self or self.takes_field): + self.__call__ = lambda value, _, __: self.converter(value) + elif self.takes_self and not self.takes_field: + self.__call__ = lambda value, instance, __: self.converter( + value, instance + ) + elif not self.takes_self and self.takes_field: + self.__call__ = lambda value, __, field: self.converter( + value, field + ) + else: + self.__call__ = self.converter + + rt = ex.get_return_type() + if rt is not None: + self.__call__.__annotations__["return"] = rt + + @staticmethod + def _get_global_name(attr_name: str) -> str: + """ + Return the name that a converter for an attribute name *attr_name* + would have. + """ + return f"__attr_converter_{attr_name}" + + def _fmt_converter_call(self, attr_name: str, value_var: str) -> str: + """ + Return a string that calls the converter for an attribute name + *attr_name* and the value in variable named *value_var* according to + `self.takes_self` and `self.takes_field`. + """ + if not (self.takes_self or self.takes_field): + return f"{self._get_global_name(attr_name)}({value_var})" + + if self.takes_self and self.takes_field: + return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])" + + if self.takes_self: + return f"{self._get_global_name(attr_name)}({value_var}, self)" + + return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])" + + def __getstate__(self): + """ + Return a dict containing only converter and takes_self -- the rest gets + computed when loading. + """ + return { + "converter": self.converter, + "takes_self": self.takes_self, + "takes_field": self.takes_field, + } + + def __setstate__(self, state): + """ + Load instance from state. + """ + self.__init__(**state) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in ("converter", "takes_self", "takes_field") +] + +Converter = _add_hash( + _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f +) + + +def make_class( + name, attrs, bases=(object,), class_body=None, **attributes_arguments +): + r""" + A quick way to create a new class called *name* with *attrs*. + + .. note:: + + ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define` + which means that it doesn't come with some of the improved defaults. + + For example, if you want the same ``on_setattr`` behavior as in + `attrs.define`, you have to pass the hooks yourself: ``make_class(..., + on_setattr=setters.pipe(setters.convert, setters.validate)`` + + .. warning:: + + It is *your* duty to ensure that the class name and the attribute names + are valid identifiers. ``make_class()`` will *not* validate them for + you. + + Args: + name (str): The name for the new class. + + attrs (list | dict): + A list of names or a dictionary of mappings of names to `attr.ib`\ + s / `attrs.field`\ s. + + The order is deduced from the order of the names or attributes + inside *attrs*. Otherwise the order of the definition of the + attributes is used. + + bases (tuple[type, ...]): Classes that the new class will subclass. + + class_body (dict): + An optional dictionary of class attributes for the new class. + + attributes_arguments: Passed unmodified to `attr.s`. + + Returns: + type: A new class with *attrs*. + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* + .. versionchanged:: 25.2.0 Class names can now be unicode. + """ + # Class identifiers are converted into the normal form NFKC while parsing + name = unicodedata.normalize("NFKC", name) + + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = {a: attrib() for a in attrs} + else: + msg = "attrs argument must be a dict or a list." + raise TypeError(msg) + + pre_init = cls_dict.pop("__attrs_pre_init__", None) + post_init = cls_dict.pop("__attrs_post_init__", None) + user_init = cls_dict.pop("__init__", None) + + body = {} + if class_body is not None: + body.update(class_body) + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + with contextlib.suppress(AttributeError, ValueError): + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + cls = _attrs(these=cls_dict, **attributes_arguments)(type_) + # Only add type annotations now or "_attrs()" will complain: + cls.__annotations__ = { + k: v.type for k, v in cls_dict.items() if v.type is not None + } + return cls + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, unsafe_hash=True) +class _AndValidator: + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if they + have any. + + converters (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + return_instance = any(isinstance(c, Converter) for c in converters) + + if return_instance: + + def pipe_converter(val, inst, field): + for c in converters: + val = ( + c(val, inst, field) if isinstance(c, Converter) else c(val) + ) + + return val + + else: + + def pipe_converter(val): + for c in converters: + val = c(val) + + return val + + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = TypeVar("A") + pipe_converter.__annotations__.update({"val": A, "return": A}) + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + last = converters[-1] + if not PY_3_11_PLUS and isinstance(last, Converter): + last = last.__call__ + + # Get return type from last converter. + rt = _AnnotationExtractor(last).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt + + if return_instance: + return Converter(pipe_converter, takes_self=True, takes_field=True) + return pipe_converter diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py new file mode 100644 index 0000000..4ccd0da --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py @@ -0,0 +1,674 @@ +# SPDX-License-Identifier: MIT + +""" +These are keyword-only APIs that call `attr.s` and `attr.ib` with different +default values. +""" + +from functools import partial + +from . import setters +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + _DEFAULT_ON_SETATTR, + NOTHING, + _frozen_setattrs, + attrib, + attrs, +) +from .exceptions import NotAnAttrsClassError, UnannotatedAttributeError + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + unsafe_hash=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, + force_kw_only=False, +): + r""" + A class decorator that adds :term:`dunder methods` according to + :term:`fields ` specified using :doc:`type annotations `, + `field()` calls, or the *these* argument. + + Since *attrs* patches or replaces an existing class, you cannot use + `object.__init_subclass__` with *attrs* classes, because it runs too early. + As a replacement, you can define ``__attrs_init_subclass__`` on your class. + It will be called by *attrs* classes that subclass it after they're + created. See also :ref:`init-subclass`. + + Args: + slots (bool): + Create a :term:`slotted class ` that's more + memory-efficient. Slotted classes are generally superior to the + default dict classes, but have some gotchas you should know about, + so we encourage you to read the :term:`glossary entry `. + + auto_detect (bool): + Instead of setting the *init*, *repr*, *eq*, and *hash* arguments + explicitly, assume they are set to True **unless any** of the + involved methods for one of the arguments is implemented in the + *current* class (meaning, it is *not* inherited from some base + class). + + So, for example by implementing ``__eq__`` on a class yourself, + *attrs* will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a + sensible ``__ne__`` by default, so it *should* be enough to only + implement ``__eq__`` in most cases). + + Passing :data:`True` or :data:`False` to *init*, *repr*, *eq*, or *hash* + overrides whatever *auto_detect* would determine. + + auto_exc (bool): + If the class subclasses `BaseException` (which implicitly includes + any subclass of any exception), the following happens to behave + like a well-behaved Python exception class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids [#]_ , + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the + ``args`` attribute, + - the value of *str* is ignored leaving ``__str__`` to base + classes. + + .. [#] + Note that *attrs* will *not* remove existing implementations of + ``__hash__`` or the equality methods. It just won't add own + ones. + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + A callable that is run whenever the user attempts to set an + attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same + arguments as validators: the instance, the attribute that is being + modified, and the new value. + + If no exception is raised, the attribute is set to the return value + of the callable. + + If a list of callables is passed, they're automatically wrapped in + an `attrs.setters.pipe`. + + If left None, the default behavior is to run converters and + validators whenever an attribute is set. + + init (bool): + Create a ``__init__`` method that initializes the *attrs* + attributes. Leading underscores are stripped for the argument name, + unless an alias is set on the attribute. + + .. seealso:: + `init` shows advanced ways to customize the generated + ``__init__`` method, including executing code before and after. + + repr(bool): + Create a ``__repr__`` method with a human readable representation + of *attrs* attributes. + + str (bool): + Create a ``__str__`` method that is identical to ``__repr__``. This + is usually not necessary except for `Exception`\ s. + + eq (bool | None): + If True or None (default), add ``__eq__`` and ``__ne__`` methods + that check two instances for equality. + + .. seealso:: + `comparison` describes how to customize the comparison behavior + going as far comparing NumPy arrays. + + order (bool | None): + If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` + methods that behave like *eq* above and allow instances to be + ordered. + + They compare the instances as if they were tuples of their *attrs* + attributes if and only if the types of both classes are + *identical*. + + If `None` mirror value of *eq*. + + .. seealso:: `comparison` + + unsafe_hash (bool | None): + If None (default), the ``__hash__`` method is generated according + how *eq* and *frozen* are set. + + 1. If *both* are True, *attrs* will generate a ``__hash__`` for + you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set + to None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning + the ``__hash__`` method of the base class will be used. If the + base class is `object`, this means it will fall back to id-based + hashing. + + Although not recommended, you can decide for yourself and force + *attrs* to create one (for example, if the class is immutable even + though you didn't freeze it programmatically) by passing True or + not. Both of these cases are rather special and should be used + carefully. + + .. seealso:: + + - Our documentation on `hashing`, + - Python's documentation on `object.__hash__`, + - and the `GitHub issue that led to the default \ behavior + `_ for more + details. + + hash (bool | None): + Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence. + + cache_hash (bool): + Ensure that the object's hash code is computed only once and stored + on the object. If this is set to True, hashing must be either + explicitly or implicitly enabled for this class. If the hash code + is cached, avoid any reassignments of fields involved in hash code + computation or mutations of the objects those fields point to after + object creation. If such changes occur, the behavior of the + object's hash code is undefined. + + frozen (bool): + Make instances immutable after initialization. If someone attempts + to modify a frozen instance, `attrs.exceptions.FrozenInstanceError` + is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` + method on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other + words: ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You + can circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + kw_only (bool): + Make attributes keyword-only in the generated ``__init__`` (if + *init* is False, this parameter is ignored). Attributes that + explicitly set ``kw_only=False`` are not affected; base class + attributes are also not affected. + + Also see *force_kw_only*. + + weakref_slot (bool): + Make instances weak-referenceable. This has no effect unless + *slots* is True. + + field_transformer (~typing.Callable | None): + A function that is called with the original class object and all + fields right before *attrs* finalizes the class. You can use this, + for example, to automatically add converters or validators to + fields based on their types. + + .. seealso:: `transform-fields` + + match_args (bool): + If True (default), set ``__match_args__`` on the class to support + :pep:`634` (*Structural Pattern Matching*). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and + later. Ignored on older Python versions. + + collect_by_mro (bool): + If True, *attrs* collects attributes from base classes correctly + according to the `method resolution order + `_. If False, *attrs* + will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. + + See also `issue #428 + `_. + + force_kw_only (bool): + A back-compat flag for restoring pre-25.4.0 behavior. If True and + ``kw_only=True``, all attributes are made keyword-only, including + base class attributes, and those set to ``kw_only=False`` at the + attribute level. Defaults to False. + + See also `issue #980 + `_. + + getstate_setstate (bool | None): + .. note:: + + This is usually only interesting for slotted classes and you + should probably just set *auto_detect* to True. + + If True, ``__getstate__`` and ``__setstate__`` are generated and + attached to the class. This is necessary for slotted classes to be + pickleable. If left None, it's True by default for slotted classes + and False for dict classes. + + If *auto_detect* is True, and *getstate_setstate* is left None, and + **either** ``__getstate__`` or ``__setstate__`` is detected + directly on the class (meaning: not inherited), it is set to False + (this is usually what you want). + + auto_attribs (bool | None): + If True, look at type annotations to determine which attributes to + use, like `dataclasses`. If False, it will only look for explicit + :func:`field` class attributes, like classic *attrs*. + + If left None, it will guess: + + 1. If any attributes are annotated and no unannotated + `attrs.field`\ s are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attrs.field`\ s. + + If *attrs* decides to look at type annotations, **all** fields + **must** be annotated. If *attrs* encounters a field that is set to + a :func:`field` / `attr.ib` but lacks a type annotation, an + `attrs.exceptions.UnannotatedAttributeError` is raised. Use + ``field_name: typing.Any = field(...)`` if you don't want to set a + type. + + .. warning:: + + For features that use the attribute name to create decorators + (for example, :ref:`validators `), you still *must* + assign :func:`field` / `attr.ib` to them. Otherwise Python will + either not find the name or try to use the default value to + call, for example, ``validator`` on it. + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `field()` are **ignored**. + + these (dict[str, object]): + A dictionary of name to the (private) return value of `field()` + mappings. This is useful to avoid the definition of your attributes + within the class body because you can't (for example, if you want + to add ``__repr__`` methods to Django models) or don't want to. + + If *these* is not `None`, *attrs* will *not* search the class body + for attributes and will *not* remove any attributes from it. + + The order is deduced from the order of the attributes inside + *these*. + + Arguably, this is a rather obscure feature. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + .. versionadded:: 24.3.0 + Unless already present, a ``__replace__`` method is automatically + created for `copy.replace` (Python 3.13+ only). + .. versionchanged:: 25.4.0 + *kw_only* now only applies to attributes defined in the current class, + and respects attribute-level ``kw_only=False`` settings. + .. versionadded:: 25.4.0 + Added *force_kw_only* to go back to the previous *kw_only* behavior. + + .. note:: + + The main differences to the classic `attr.s` are: + + - Automatically detect whether or not *auto_attribs* should be `True` + (c.f. *auto_attribs* parameter). + - Converters and validators run when attributes are set by default -- + if *frozen* is `False`. + - *slots=True* + + Usually, this has only upsides and few visible effects in everyday + programming. But it *can* lead to some surprising behaviors, so + please make sure to read :term:`slotted classes`. + + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - *force_kw_only=False* + - Some options that were only relevant on Python 2 or were kept around + for backwards-compatibility have been removed. + + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + unsafe_hash=unsafe_hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + match_args=match_args, + force_kw_only=force_kw_only, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes convert & validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = _DEFAULT_ON_SETATTR + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." + raise ValueError(msg) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=None, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new :term:`field` / :term:`attribute` on a class. + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attrs.define` (or similar)! + + Args: + default: + A value that is used if an *attrs*-generated ``__init__`` is used + and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `attrs.Factory`, its callable will + be used to construct a new value (useful for mutable data types + like lists or dicts). + + If a default is not set (or set manually to `attrs.NOTHING`), a + value *must* be supplied when instantiating; otherwise a + `TypeError` will be raised. + + .. seealso:: `defaults` + + factory (~typing.Callable): + Syntactic sugar for ``default=attr.Factory(factory)``. + + validator (~typing.Callable | list[~typing.Callable]): + Callable that is called by *attrs*-generated ``__init__`` methods + after the instance has been initialized. They receive the + initialized instance, the :func:`~attrs.Attribute`, and the passed + value. + + The return value is *not* inspected so the validator has to throw + an exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. + + The validator can also be set using decorator notation as shown + below. + + .. seealso:: :ref:`validators` + + repr (bool | ~typing.Callable): + Include this attribute in the generated ``__repr__`` method. If + True, include the attribute; if False, omit it. By default, the + built-in ``repr()`` function is used. To override how the attribute + value is formatted, pass a ``callable`` that takes a single value + and returns a string. Note that the resulting string is used as-is, + which means it will be used directly *instead* of calling + ``repr()`` (the default). + + eq (bool | ~typing.Callable): + If True (default), include this attribute in the generated + ``__eq__`` and ``__ne__`` methods that check two instances for + equality. To override how the attribute value is compared, pass a + callable that takes a single value and returns the value to be + compared. + + .. seealso:: `comparison` + + order (bool | ~typing.Callable): + If True (default), include this attributes in the generated + ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To + override how the attribute value is ordered, pass a callable that + takes a single value and returns the value to be ordered. + + .. seealso:: `comparison` + + hash (bool | None): + Include this attribute in the generated ``__hash__`` method. If + None (default), mirror *eq*'s value. This is the correct behavior + according the Python spec. Setting this value to anything else + than None is *discouraged*. + + .. seealso:: `hashing` + + init (bool): + Include this attribute in the generated ``__init__`` method. + + It is possible to set this to False and set a default value. In + that case this attributed is unconditionally initialized with the + specified default value or factory. + + .. seealso:: `init` + + converter (typing.Callable | Converter): + A callable that is called by *attrs*-generated ``__init__`` methods + to convert attribute's value to the desired format. + + If a vanilla callable is passed, it is given the passed-in value as + the only positional argument. It is possible to receive additional + arguments by wrapping the callable in a `Converter`. + + Either way, the returned value will be used as the new value of the + attribute. The value is converted before being passed to the + validator, if any. + + .. seealso:: :ref:`converters` + + metadata (dict | None): + An arbitrary mapping, to be used by third-party code. + + .. seealso:: `extending-metadata`. + + type (type): + The type of the attribute. Nowadays, the preferred method to + specify the type is using a variable annotation (see :pep:`526`). + This argument is provided for backwards-compatibility and for usage + with `make_class`. Regardless of the approach used, the type will + be stored on ``Attribute.type``. + + Please note that *attrs* doesn't do anything with this metadata by + itself. You can use it as part of your own code or for `static type + checking `. + + kw_only (bool | None): + Make this attribute keyword-only in the generated ``__init__`` (if + *init* is False, this parameter is ignored). If None (default), + mirror the setting from `attrs.define`. + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + Allows to overwrite the *on_setattr* setting from `attr.s`. If left + None, the *on_setattr* value from `attr.s` is used. Set to + `attrs.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `define()`. + + alias (str | None): + Override this attribute's parameter name in the generated + ``__init__`` method. If left None, default to ``name`` stripped + of leading underscores. See `private-attributes`. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionadded:: 22.2.0 *alias* + .. versionadded:: 23.1.0 + The *type* parameter has been re-added; mostly for `attrs.make_class`. + Please note that type checkers ignore this metadata. + .. versionchanged:: 25.4.0 + *kw_only* can now be None, and its default is also changed from False to + None. + + .. seealso:: + + `attr.ib` + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + type=type, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + alias=alias, + ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) + + +def inspect(cls): + """ + Inspect the class and return its effective build parameters. + + Warning: + This feature is currently **experimental** and is not covered by our + strict backwards-compatibility guarantees. + + Args: + cls: The *attrs*-decorated class to inspect. + + Returns: + The effective build parameters of the class. + + Raises: + NotAnAttrsClassError: If the class is not an *attrs*-decorated class. + + .. versionadded:: 25.4.0 + """ + try: + return cls.__dict__["__attrs_props__"] + except KeyError: + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) from None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi new file mode 100644 index 0000000..ca7b71e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi @@ -0,0 +1,15 @@ +from typing import Any, ClassVar, Protocol + +# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. +MYPY = False + +if MYPY: + # A protocol to be able to statically accept an attrs class. + class AttrsInstance_(Protocol): + __attrs_attrs__: ClassVar[Any] + +else: + # For type checkers without plug-in support use an empty protocol that + # will (hopefully) be combined into a union. + class AttrsInstance_(Protocol): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py new file mode 100644 index 0000000..27f1888 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.py @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: MIT + + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo: + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them + + def __hash__(self): + return hash((self.year, self.minor, self.micro, self.releaselevel)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi new file mode 100644 index 0000000..45ced08 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py new file mode 100644 index 0000000..0a79dee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.py @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful converters. +""" + +import typing + +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Converter, Factory, pipe + + +__all__ = [ + "default_if_none", + "optional", + "pipe", + "to_bool", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to `None`. + + Type annotations will be inferred from the wrapped converter's, if it has + any. + + Args: + converter (typing.Callable): + the converter that is used for non-`None` values. + + .. versionadded:: 17.1.0 + """ + + if isinstance(converter, Converter): + + def optional_converter(val, inst, field): + if val is None: + return None + return converter(val, inst, field) + + else: + + def optional_converter(val): + if val is None: + return None + return converter(val) + + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] + + if isinstance(converter, Converter): + return Converter(optional_converter, takes_self=True, takes_field=True) + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace `None` values by *default* or the result + of *factory*. + + Args: + default: + Value to be used if `None` is passed. Passing an instance of + `attrs.Factory` is supported, however the ``takes_self`` option is + *not*. + + factory (typing.Callable): + A callable that takes no parameters whose result is used if `None` + is passed. + + Raises: + TypeError: If **neither** *default* or *factory* is passed. + + TypeError: If **both** *default* and *factory* are passed. + + ValueError: + If an instance of `attrs.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + msg = "Must pass either `default` or `factory`." + raise TypeError(msg) + + if default is not NOTHING and factory is not None: + msg = "Must pass either `default` or `factory` but not both." + raise TypeError(msg) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + msg = "`takes_self` is not supported by default_if_none." + raise ValueError(msg) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (for example, from environment variables) to real + booleans. + + Values mapping to `True`: + + - ``True`` + - ``"true"`` / ``"t"`` + - ``"yes"`` / ``"y"`` + - ``"on"`` + - ``"1"`` + - ``1`` + + Values mapping to `False`: + + - ``False`` + - ``"false"`` / ``"f"`` + - ``"no"`` / ``"n"`` + - ``"off"`` + - ``"0"`` + - ``0`` + + Raises: + ValueError: For any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + + if val in (True, "true", "t", "yes", "y", "on", "1", 1): + return True + if val in (False, "false", "f", "no", "n", "off", "0", 0): + return False + + msg = f"Cannot convert value to bool: {val!r}" + raise ValueError(msg) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi new file mode 100644 index 0000000..12bd0c4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/converters.pyi @@ -0,0 +1,19 @@ +from typing import Callable, Any, overload + +from attrs import _ConverterType, _CallableConverterType + +@overload +def pipe(*validators: _CallableConverterType) -> _CallableConverterType: ... +@overload +def pipe(*validators: _ConverterType) -> _ConverterType: ... +@overload +def optional(converter: _CallableConverterType) -> _CallableConverterType: ... +@overload +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: Any) -> _CallableConverterType: ... +@overload +def default_if_none( + *, factory: Callable[[], Any] +) -> _CallableConverterType: ... +def to_bool(val: str | int | bool) -> bool: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py new file mode 100644 index 0000000..a207df4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.py @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute have been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + def __init__(self): + msg = "can't set attribute" + super().__init__(msg) + self.msg = msg + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An *attrs* function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-*attrs* class has been passed into an *attrs* function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set when defining the field and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has a field without a type annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an *attrs* feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A field requiring a callable has been set with a value that is not + callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi new file mode 100644 index 0000000..f268011 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py new file mode 100644 index 0000000..689b170 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful filters for `attrs.asdict` and `attrs.astuple`. +""" + +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isinstance(cls, type)), + frozenset(cls for cls in what if isinstance(cls, str)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Create a filter that only allows *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to include. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.1.0 Accept strings with field names. + """ + cls, names, attrs = _split_what(what) + + def include_(attribute, value): + return ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return include_ + + +def exclude(*what): + """ + Create a filter that does **not** allow *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to exclude. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.3.0 Accept field name string as input argument + """ + cls, names, attrs = _split_what(what) + + def exclude_(attribute, value): + return not ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return exclude_ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi new file mode 100644 index 0000000..974abdc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/filters.pyi @@ -0,0 +1,6 @@ +from typing import Any + +from . import Attribute, _FilterType + +def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... +def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py new file mode 100644 index 0000000..78b0839 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.py @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly used hooks for on_setattr. +""" + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + # This can be removed once we drop 3.8 and use attrs.Converter instead. + from ._make import Converter + + if not isinstance(c, Converter): + return c(new_value) + + return c(new_value, instance, attrib) + + return new_value + + +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# Sphinx's autodata stopped working, so the docstring is inlined in the API +# docs. +NO_OP = object() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi new file mode 100644 index 0000000..73abf36 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/setters.pyi @@ -0,0 +1,20 @@ +from typing import Any, NewType, NoReturn, TypeVar + +from . import Attribute +from attrs import _OnSetAttrType + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py new file mode 100644 index 0000000..0b1a294 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.py @@ -0,0 +1,750 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful validators. +""" + +import operator +import re + +from contextlib import contextmanager +from re import Pattern + +from ._config import get_run_validators, set_run_validators +from ._make import _AndValidator, and_, attrib, attrs +from .converters import default_if_none +from .exceptions import NotCallableError + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", + "in_", + "instance_of", + "is_callable", + "le", + "lt", + "matches_re", + "max_len", + "min_len", + "not_", + "optional", + "or_", + "set_disabled", +] + + +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + Args: + disabled (bool): If `True`, disable running all validators. + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + Returns: + bool:`True` if validators are currently disabled. + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + .. versionchanged:: 26.1.0 The contextmanager is nestable. + """ + prev = get_run_validators() + set_run_validators(False) + try: + yield + finally: + set_run_validators(prev) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InstanceOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type]): The type to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator: + pattern = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" + raise ValueError( + msg, + attr, + self.pattern, + value, + ) + + def __repr__(self): + return f"" + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called with a + string that doesn't match *regex*. + + Args: + regex (str, re.Pattern): + A regex string or precompiled pattern to match against + + flags (int): + Flags that will be passed to the underlying re function (default 0) + + func (typing.Callable): + Which underlying `re` function to call. Valid options are + `re.fullmatch`, `re.search`, and `re.match`; the default `None` + means `re.fullmatch`. For performance reasons, the pattern is + always precompiled using `re.compile`. + + .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. + """ + valid_funcs = (re.fullmatch, None, re.search, re.match) + if func not in valid_funcs: + msg = "'func' must be one of {}.".format( + ", ".join( + sorted((e and e.__name__) or "None" for e in set(valid_funcs)) + ) + ) + raise ValueError(msg) + + if isinstance(regex, Pattern): + if flags: + msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" + raise TypeError(msg) + pattern = regex + else: + pattern = re.compile(regex, flags) + + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + match_func = pattern.fullmatch + + return _MatchesReValidator(pattern, match_func) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OptionalValidator: + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return f"" + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to `None` in addition to satisfying the requirements of + the sub-validator. + + Args: + validator + (typing.Callable | tuple[typing.Callable] | list[typing.Callable]): + A validator (or validators) that is used for non-`None` values. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. + """ + if isinstance(validator, (list, tuple)): + return _OptionalValidator(_AndValidator(validator)) + + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InValidator: + options = attrib() + _original_options = attrib(hash=False) + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})" + raise ValueError( + msg, + attr, + self._original_options, + value, + ) + + def __repr__(self): + return f"" + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called with a + value that does not belong in the *options* provided. + + The check is performed using ``value in options``, so *options* has to + support that operation. + + To keep the validator hashable, dicts, lists, and sets are transparently + transformed into a `tuple`. + + Args: + options: Allowed options. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected options, and the value it got. + + .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. + .. versionchanged:: 24.1.0 + *options* that are a list, dict, or a set are now transformed into a + tuple to keep the validator hashable. + """ + repr_options = options + if isinstance(options, (list, dict, set)): + options = tuple(options) + + return _InValidator(options, repr_options) + + +@attrs(repr=False, slots=False, unsafe_hash=True) +class _IsCallableValidator: + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attrs.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute that is + not callable. + + .. versionadded:: 19.1.0 + + Raises: + attrs.exceptions.NotCallableError: + With a human readable error message containing the attribute + (`attrs.Attribute`) name, and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepIterable: + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else f" {self.iterable_validator!r}" + ) + return ( + f"" + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + Args: + member_validator: Validator(s) to apply to iterable members. + + iterable_validator: + Validator(s) to apply to iterable itself (optional). + + Raises + TypeError: if any sub-validators fail + + .. versionadded:: 19.1.0 + + .. versionchanged:: 25.4.0 + *member_validator* and *iterable_validator* can now be a list or tuple + of validators. + """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) + if isinstance(iterable_validator, (list, tuple)): + iterable_validator = and_(*iterable_validator) + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepMapping: + key_validator = attrib(validator=optional(is_callable())) + value_validator = attrib(validator=optional(is_callable())) + mapping_validator = attrib(validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + if self.key_validator is not None: + self.key_validator(inst, attr, key) + if self.value_validator is not None: + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return f"" + + +def deep_mapping( + key_validator=None, value_validator=None, mapping_validator=None +): + """ + A validator that performs deep validation of a dictionary. + + All validators are optional, but at least one of *key_validator* or + *value_validator* must be provided. + + Args: + key_validator: Validator(s) to apply to dictionary keys. + + value_validator: Validator(s) to apply to dictionary values. + + mapping_validator: + Validator(s) to apply to top-level mapping attribute. + + .. versionadded:: 19.1.0 + + .. versionchanged:: 25.4.0 + *key_validator* and *value_validator* are now optional, but at least one + of them must be provided. + + .. versionchanged:: 25.4.0 + *key_validator*, *value_validator*, and *mapping_validator* can now be a + list or tuple of validators. + + Raises: + TypeError: If any sub-validator fails on validation. + + ValueError: + If neither *key_validator* nor *value_validator* is provided on + instantiation. + """ + if key_validator is None and value_validator is None: + msg = ( + "At least one of key_validator or value_validator must be provided" + ) + raise ValueError(msg) + + if isinstance(key_validator, (list, tuple)): + key_validator = and_(*key_validator) + if isinstance(value_validator, (list, tuple)): + value_validator = and_(*value_validator) + if isinstance(mapping_validator, (list, tuple)): + mapping_validator = and_(*mapping_validator) + + return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator: + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number larger or equal to *val*. + + The validator uses `operator.lt` to compare the values. + + Args: + val: Exclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number greater than *val*. + + The validator uses `operator.le` to compare the values. + + Args: + val: Inclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller than *val*. + + The validator uses `operator.ge` to compare the values. + + Args: + val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller or equal to *val*. + + The validator uses `operator.gt` to compare the values. + + Args: + val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator: + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + Args: + length (int): Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + Args: + length (int): Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _SubclassOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not issubclass(value, self.type): + msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def _subclass_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `issubclass` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type, ...]): The type(s) to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _SubclassOfValidator(type) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _NotValidator: + validator = attrib() + msg = attrib( + converter=default_if_none( + "not_ validator child '{validator!r}' " + "did not raise a captured error" + ) + ) + exc_types = attrib( + validator=deep_iterable( + member_validator=_subclass_of(Exception), + iterable_validator=instance_of(tuple), + ), + ) + + def __call__(self, inst, attr, value): + try: + self.validator(inst, attr, value) + except self.exc_types: + pass # suppress error to invert validity + else: + raise ValueError( + self.msg.format( + validator=self.validator, + exc_types=self.exc_types, + ), + attr, + self.validator, + value, + self.exc_types, + ) + + def __repr__(self): + return f"" + + +def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): + """ + A validator that wraps and logically 'inverts' the validator passed to it. + It will raise a `ValueError` if the provided validator *doesn't* raise a + `ValueError` or `TypeError` (by default), and will suppress the exception + if the provided validator *does*. + + Intended to be used with existing validators to compose logic without + needing to create inverted variants, for example, ``not_(in_(...))``. + + Args: + validator: A validator to be logically inverted. + + msg (str): + Message to raise if validator fails. Formatted with keys + ``exc_types`` and ``validator``. + + exc_types (tuple[type, ...]): + Exception type(s) to capture. Other types raised by child + validators will not be intercepted and pass through. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the validator that failed to raise an + exception, the value it got, and the expected exception types. + + .. versionadded:: 22.2.0 + """ + try: + exc_types = tuple(exc_types) + except TypeError: + exc_types = (exc_types,) + return _NotValidator(validator, msg, exc_types) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OrValidator: + validators = attrib() + + def __call__(self, inst, attr, value): + for v in self.validators: + try: + v(inst, attr, value) + except Exception: # noqa: BLE001, PERF203, S112 + continue + else: + return + + msg = f"None of {self.validators!r} satisfied for value {value!r}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def or_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators until one of them is + satisfied. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + Raises: + ValueError: + If no validator is satisfied. Raised with a human-readable error + message listing all the wrapped validators and the value that + failed all of them. + + .. versionadded:: 24.1.0 + """ + vals = [] + for v in validators: + vals.extend(v.validators if isinstance(v, _OrValidator) else [v]) + + return _OrValidator(tuple(vals)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi new file mode 100644 index 0000000..18fb112 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attr/validators.pyi @@ -0,0 +1,140 @@ +from types import UnionType +from typing import ( + Any, + AnyStr, + Callable, + Container, + ContextManager, + Iterable, + Mapping, + Match, + Pattern, + TypeVar, + overload, +) + +from attrs import _ValidatorType +from attrs import _ValidatorArgType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_T4 = TypeVar("_T4") +_T5 = TypeVar("_T5") +_T6 = TypeVar("_T6") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +def set_disabled(run: bool) -> None: ... +def get_disabled() -> bool: ... +def disabled() -> ContextManager[None]: ... + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2]], +) -> _ValidatorType[_T1 | _T2]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2], type[_T3]], +) -> _ValidatorType[_T1 | _T2 | _T3]: ... +@overload +def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ... +@overload +def instance_of(type: UnionType) -> _ValidatorType[Any]: ... +def optional( + validator: ( + _ValidatorType[_T] + | list[_ValidatorType[_T]] + | tuple[_ValidatorType[_T], ...] + ), +) -> _ValidatorType[_T | None]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: Pattern[AnyStr] | AnyStr, + flags: int = ..., + func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorArgType[_T], + iterable_validator: _ValidatorArgType[_I] | None = ..., +) -> _ValidatorType[_I]: ... +@overload +def deep_mapping( + key_validator: _ValidatorArgType[_K], + value_validator: _ValidatorArgType[_V] | None = ..., + mapping_validator: _ValidatorArgType[_M] | None = ..., +) -> _ValidatorType[_M]: ... +@overload +def deep_mapping( + key_validator: _ValidatorArgType[_K] | None = ..., + value_validator: _ValidatorArgType[_V] = ..., + mapping_validator: _ValidatorArgType[_M] | None = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... +def lt(val: _T) -> _ValidatorType[_T]: ... +def le(val: _T) -> _ValidatorType[_T]: ... +def ge(val: _T) -> _ValidatorType[_T]: ... +def gt(val: _T) -> _ValidatorType[_T]: ... +def max_len(length: int) -> _ValidatorType[_T]: ... +def min_len(length: int) -> _ValidatorType[_T]: ... +def not_( + validator: _ValidatorType[_T], + *, + msg: str | None = None, + exc_types: type[Exception] | Iterable[type[Exception]] = ..., +) -> _ValidatorType[_T]: ... +@overload +def or_( + __v1: _ValidatorType[_T1], + __v2: _ValidatorType[_T2], +) -> _ValidatorType[_T1 | _T2]: ... +@overload +def or_( + __v1: _ValidatorType[_T1], + __v2: _ValidatorType[_T2], + __v3: _ValidatorType[_T3], +) -> _ValidatorType[_T1 | _T2 | _T3]: ... +@overload +def or_( + __v1: _ValidatorType[_T1], + __v2: _ValidatorType[_T2], + __v3: _ValidatorType[_T3], + __v4: _ValidatorType[_T4], +) -> _ValidatorType[_T1 | _T2 | _T3 | _T4]: ... +@overload +def or_( + __v1: _ValidatorType[_T1], + __v2: _ValidatorType[_T2], + __v3: _ValidatorType[_T3], + __v4: _ValidatorType[_T4], + __v5: _ValidatorType[_T5], +) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5]: ... +@overload +def or_( + __v1: _ValidatorType[_T1], + __v2: _ValidatorType[_T2], + __v3: _ValidatorType[_T3], + __v4: _ValidatorType[_T4], + __v5: _ValidatorType[_T5], + __v6: _ValidatorType[_T6], +) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5 | _T6]: ... +@overload +def or_( + __v1: _ValidatorType[Any], + __v2: _ValidatorType[Any], + __v3: _ValidatorType[Any], + __v4: _ValidatorType[Any], + __v5: _ValidatorType[Any], + __v6: _ValidatorType[Any], + *validators: _ValidatorType[Any], +) -> _ValidatorType[Any]: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA new file mode 100644 index 0000000..5cf16a0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA @@ -0,0 +1,199 @@ +Metadata-Version: 2.4 +Name: attrs +Version: 26.1.0 +Summary: Classes Without Boilerplate +Project-URL: Documentation, https://www.attrs.org/ +Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html +Project-URL: GitHub, https://github.com/python-attrs/attrs +Project-URL: Funding, https://github.com/sponsors/hynek +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi +Author-email: Hynek Schlawack +License-Expression: MIT +License-File: LICENSE +Keywords: attribute,boilerplate,class +Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.9 +Description-Content-Type: text/markdown + +

+ + attrs + +

+ + +*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). +Trusted by NASA for [Mars missions since 2020](https://github.com/readme/featured/nasa-ingenuity-helicopter)! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + + +## Sponsors + +*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). +Especially those generously supporting us at the *The Organization* tier and higher: + + + +

+ + + + + + + + + + + +

+ + + +

+ Please consider joining them to help make attrs’s maintenance more sustainable! +

+ + + +## Example + +*attrs* gives you a class decorator and a way to declaratively define the attributes on that class: + + + +```pycon +>>> from attrs import asdict, define, make_class, Factory + +>>> @define +... class SomeClass: +... a_number: int = 42 +... list_of_numbers: list[int] = Factory(list) +... +... def hard_math(self, another_number): +... return self.a_number + sum(self.list_of_numbers) * another_number + + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +>>> sc.hard_math(3) +19 +>>> sc == SomeClass(1, [1, 2, 3]) +True +>>> sc != SomeClass(2, [3, 2, 1]) +True + +>>> asdict(sc) +{'a_number': 1, 'list_of_numbers': [1, 2, 3]} + +>>> SomeClass() +SomeClass(a_number=42, list_of_numbers=[]) + +>>> C = make_class("C", ["a", "b"]) +>>> C("foo", "bar") +C(a='foo', b='bar') +``` + +After *declaring* your attributes, *attrs* gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable `__repr__`, +- equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +--- + +This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. +The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. + +Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation! + + +### Hate Type Annotations!? + +No problem! +Types are entirely **optional** with *attrs*. +Simply assign `attrs.field()` to the attributes instead of annotating them with types: + +```python +from attrs import define, field + +@define +class SomeClass: + a_number = field(default=42) + list_of_numbers = field(factory=list) +``` + + +## Data Classes + +On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). +In practice it does a lot more and is more flexible. +For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger. + +For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice. + + +## Project Information + +- [**Changelog**](https://www.attrs.org/en/stable/changelog.html) +- [**Documentation**](https://www.attrs.org/) +- [**PyPI**](https://pypi.org/project/attrs/) +- [**Source Code**](https://github.com/python-attrs/attrs) +- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) +- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) +- **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs) + + +### *attrs* for Enterprise + +Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). + +The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. + +## Release Information + +### Backwards-incompatible Changes + +- Field aliases are now resolved *before* calling `field_transformer`, so transformers receive fully populated `Attribute` objects with usable `alias` values instead of `None`. + The new `Attribute.alias_is_default` flag indicates whether the alias was auto-generated (`True`) or explicitly set by the user (`False`). + [#1509](https://github.com/python-attrs/attrs/issues/1509) + + +### Changes + +- Fix type annotations for `attrs.validators.optional()`, so it no longer rejects tuples with more than one validator. + [#1496](https://github.com/python-attrs/attrs/issues/1496) +- The `attrs.validators.disabled()` contextmanager can now be nested. + [#1513](https://github.com/python-attrs/attrs/issues/1513) +- Frozen classes can set `on_setattr=attrs.setters.NO_OP` in addition to `None`. + [#1515](https://github.com/python-attrs/attrs/issues/1515) +- It's now possible to pass *attrs* **instances** in addition to *attrs* **classes** to `attrs.fields()`. + [#1529](https://github.com/python-attrs/attrs/issues/1529) + + + +--- + +[Full changelog →](https://www.attrs.org/en/stable/changelog.html) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD new file mode 100644 index 0000000..d048dcf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD @@ -0,0 +1,35 @@ +attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057 +attr/__init__.pyi,sha256=pVGImAUVovq2_TYl_r_HIYnGlyOaoCuEhxo-EvsnnSc,11325 +attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117 +attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368 +attr/_compat.py,sha256=x0g7iEUOnBVJC72zyFCgb1eKqyxS-7f2LGnNyZ_r95s,2829 +attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843 +attr/_funcs.py,sha256=Ix5IETTfz5F01F-12MF_CSFomIn2h8b67EVVz2gCtBE,16479 +attr/_make.py,sha256=H7OH2eWS5CnBzLUjNFE1WymfPrmF1r8fv2RPdt9MuYA,106129 +attr/_next_gen.py,sha256=BQtCUlzwg2gWHTYXBQvrEYBnzBUrDvO57u0Py6UCPhc,26274 +attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469 +attr/_version_info.py,sha256=w4R-FYC3NK_kMkGUWJlYP4cVAlH9HRaC-um3fcjYkHM,2222 +attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861 +attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643 +attr/exceptions.py,sha256=b4vMbnoQ3VpwWZhqrYi_ssXVCK8o2c4HQSS09cSUM9o,1990 +attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539 +attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795 +attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617 +attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584 +attr/validators.py,sha256=m3QRzZTANr4f2C4eVdUoFg11NgXWak8Wat4qQTGhvcs,21553 +attr/validators.pyi,sha256=gM1ZmHaBckyYWI2EirpRNzqm3B19cw5Iq6B4Kno9YCM,4087 +attrs/__init__.py,sha256=RxaAZNwYiEh-fcvHLZNpQ_DWKni73M_jxEPEftiq1Zc,1183 +attrs/__init__.pyi,sha256=2gV79g9UxJppGSM48hAZJ6h_MHb70dZoJL31ZNJeZYI,9416 +attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76 +attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76 +attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73 +attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73 +attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76 +attrs-26.1.0.dist-info/METADATA,sha256=TNQOaQ8jvzfLytNO_WdY4GLfHfB8hoM_fjzpW_H6OMw,8754 +attrs-26.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87 +attrs-26.1.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109 +attrs-26.1.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL new file mode 100644 index 0000000..b1b94fd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.29.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..2bd6453 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py new file mode 100644 index 0000000..dc1ce4b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + AttrsInstance, + Converter, + Factory, + NothingType, + _make_getattr, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._make import ClassProps +from attr._next_gen import asdict, astuple, inspect + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "NOTHING", + "Attribute", + "AttrsInstance", + "ClassProps", + "Converter", + "Factory", + "NothingType", + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "has", + "inspect", + "make_class", + "mutable", + "resolve_types", + "setters", + "validate", + "validators", +] + +__getattr__ = _make_getattr(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi new file mode 100644 index 0000000..6364bac --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi @@ -0,0 +1,314 @@ +import sys + +from typing import ( + Any, + Callable, + Mapping, + Sequence, + overload, + TypeVar, +) + +# Because we need to type our own stuff, we have to make everything from +# attr explicitly public too. +from attr import __author__ as __author__ +from attr import __copyright__ as __copyright__ +from attr import __description__ as __description__ +from attr import __email__ as __email__ +from attr import __license__ as __license__ +from attr import __title__ as __title__ +from attr import __url__ as __url__ +from attr import __version__ as __version__ +from attr import __version_info__ as __version_info__ +from attr import assoc as assoc +from attr import Attribute as Attribute +from attr import AttrsInstance as AttrsInstance +from attr import cmp_using as cmp_using +from attr import converters as converters +from attr import Converter as Converter +from attr import evolve as evolve +from attr import exceptions as exceptions +from attr import Factory as Factory +from attr import fields as fields +from attr import fields_dict as fields_dict +from attr import filters as filters +from attr import has as has +from attr import make_class as make_class +from attr import NOTHING as NOTHING +from attr import resolve_types as resolve_types +from attr import setters as setters +from attr import validate as validate +from attr import validators as validators +from attr import attrib, asdict as asdict, astuple as astuple +from attr import NothingType as NothingType + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_EqOrderType = bool | Callable[[Any], Any] +_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] +_CallableConverterType = Callable[[Any], Any] +_ConverterType = _CallableConverterType | Converter[Any, Any] +_ReprType = Callable[[Any], str] +_ReprArgType = bool | _ReprType +_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] +_OnSetAttrArgType = _OnSetAttrType | list[_OnSetAttrType] | setters._NoOpType +_FieldTransformer = Callable[ + [type, list["Attribute[Any]"]], list["Attribute[Any]"] +] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = _ValidatorType[_T] | Sequence[_ValidatorType[_T]] + +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool | None = ..., + eq: bool | None = ..., + order: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType, ...] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType, ...] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType, ...] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool | None = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +mutable = define + +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +class ClassProps: + # XXX: somehow when defining/using enums Mypy starts looking at our own + # (untyped) code and causes tons of errors. + Hashability: Any + KeywordOnly: Any + + is_exception: bool + is_slotted: bool + has_weakref_slot: bool + is_frozen: bool + # kw_only: ClassProps.KeywordOnly + kw_only: Any + collected_fields_by_mro: bool + added_init: bool + added_repr: bool + added_eq: bool + added_ordering: bool + # hashability: ClassProps.Hashability + hashability: Any + added_match_args: bool + added_str: bool + added_pickling: bool + on_setattr_hook: _OnSetAttrType | None + field_transformer: Callable[[Attribute[Any]], Attribute[Any]] | None + + def __init__( + self, + is_exception: bool, + is_slotted: bool, + has_weakref_slot: bool, + is_frozen: bool, + # kw_only: ClassProps.KeywordOnly + kw_only: Any, + collected_fields_by_mro: bool, + added_init: bool, + added_repr: bool, + added_eq: bool, + added_ordering: bool, + # hashability: ClassProps.Hashability + hashability: Any, + added_match_args: bool, + added_str: bool, + added_pickling: bool, + on_setattr_hook: _OnSetAttrType, + field_transformer: Callable[[Attribute[Any]], Attribute[Any]], + ) -> None: ... + @property + def is_hashable(self) -> bool: ... + +def inspect(cls: type) -> ClassProps: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py new file mode 100644 index 0000000..7821f6c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/converters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.converters import * # noqa: F403 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py new file mode 100644 index 0000000..3323f9d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.exceptions import * # noqa: F403 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py new file mode 100644 index 0000000..3080f48 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/filters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.filters import * # noqa: F403 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py new file mode 100644 index 0000000..f3d73bb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/setters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.setters import * # noqa: F403 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py new file mode 100644 index 0000000..037e124 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/attrs/validators.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.validators import * # noqa: F403 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA new file mode 100644 index 0000000..eb460c2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA @@ -0,0 +1,78 @@ +Metadata-Version: 2.4 +Name: certifi +Version: 2026.7.22 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.7 +License-File: LICENSE +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: project-url +Dynamic: requires-python +Dynamic: summary + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/latest/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD new file mode 100644 index 0000000..f378e12 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD @@ -0,0 +1,12 @@ +certifi/__init__.py,sha256=elLl9CBfmz0SoxiYzqp-P2g30ZzCMHALyuGp2R0UhsE,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/cacert.pem,sha256=nMKndLUZjc_xTZvh5mCR9TiXXYZ84Cmpa84VpV39cw8,240216 +certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +certifi/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +certifi/tests/test_certify.py,sha256=fBedHt839-kSsTNmNzX7RsYzjV0OX4kV0Rw7wofSJ7g,467 +certifi-2026.7.22.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2026.7.22.dist-info/METADATA,sha256=71rxY4-7I2dqw8V3ffz8LNnDSP5Bcu1bo9J3ZVskgJA,2474 +certifi-2026.7.22.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91 +certifi-2026.7.22.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi-2026.7.22.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL new file mode 100644 index 0000000..1d472b6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (83.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py new file mode 100644 index 0000000..66dae9e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2026.07.22" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem new file mode 100644 index 0000000..9a40f43 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem @@ -0,0 +1,3959 @@ + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Label: "TunTrust Root CA" +# Serial: 108534058042236574382096126452369648152337120275 +# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 +# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb +# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS RSA Root CA 2021" +# Serial: 76817823531813593706434026085292783742 +# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 +# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d +# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS ECC Root CA 2021" +# Serial: 137515985548005187474074462014555733966 +# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 +# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 +# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 1977337328857672817 +# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 +# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe +# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus ECC Root CA" +# Serial: 630369271402956006249506845124680065938238527194 +# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 +# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 +# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw +RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY +BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz +MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u +LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 +v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd +e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw +V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA +AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG +GJTO +-----END CERTIFICATE----- + +# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus Root CA" +# Serial: 387574501246983434957692974888460947164905180485 +# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc +# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 +# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x +FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx +MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s +THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc +IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU +AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ +GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 +8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH +flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt +J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim +0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN +pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ +UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW +OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB +AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet +8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j +bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM +Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv +TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS +S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr +I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 +b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB +UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P +Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven +sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X2 O=Internet Security Research Group +# Subject: CN=ISRG Root X2 O=Internet Security Research Group +# Label: "ISRG Root X2" +# Serial: 87493402998870891108772069816698636114 +# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 +# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af +# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Label: "HiPKI Root CA - G1" +# Serial: 60966262342023497858655262305426234976 +# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 +# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 +# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 159662223612894884239637590694 +# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc +# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 +# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 159662320309726417404178440727 +# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 +# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a +# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 159662495401136852707857743206 +# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 +# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 +# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 159662532700760215368942768210 +# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 +# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 +# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj +# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj +# Label: "Telia Root CA v2" +# Serial: 7288924052977061235122729490515358 +# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 +# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd +# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 1 2020" +# Serial: 165870826978392376648679885835942448534 +# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed +# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 +# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 1 2020" +# Serial: 126288379621884218666039612629459926992 +# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e +# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 +# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS ECC P384 Root G5" +# Serial: 13129116028163249804115411775095713523 +# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed +# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee +# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS RSA4096 Root G5" +# Serial: 11930366277458970227240571539258396554 +# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 +# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 +# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root R1 O=Certainly +# Subject: CN=Certainly Root R1 O=Certainly +# Label: "Certainly Root R1" +# Serial: 188833316161142517227353805653483829216 +# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 +# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af +# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root E1 O=Certainly +# Subject: CN=Certainly Root E1 O=Certainly +# Label: "Certainly Root E1" +# Serial: 8168531406727139161245376702891150584 +# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 +# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b +# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication ECC RootCA1" +# Serial: 15446673492073852651 +# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 +# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 +# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA1" +# Serial: 113562791157148395269083148143378328608 +# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90 +# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a +# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU +MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI +T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz +MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF +SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh +bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z +xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ +spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 +58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR +at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll +5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq +nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK +V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ +pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO +z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn +jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ +WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF +7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli +awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u ++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 +X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN +SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo +P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI ++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz +znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 +eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 +YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy +r/6zcCwupvI= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA2" +# Serial: 58605626836079930195615843123109055211 +# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c +# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6 +# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82 +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw +CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ +VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy +MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ +TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS +b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B +IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ ++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK +sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA +94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B +43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root E46" +# Serial: 88989738453351742415770396670917916916 +# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01 +# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a +# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root R46" +# Serial: 156256931880233212765902055439220583700 +# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5 +# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38 +# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS RSA Root CA 2022" +# Serial: 148535279242832292258835760425842727825 +# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da +# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca +# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS ECC Root CA 2022" +# Serial: 26605119622390491762507526719404364228 +# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5 +# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39 +# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA ECC TLS 2021" +# Serial: 81873346711060652204712539181482831616 +# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8 +# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd +# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8 +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA RSA TLS 2021" +# Serial: 111436099570196163832749341232207667876 +# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2 +# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48 +# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G3" +# Serial: 576386314500428537169965010905813481816650257167 +# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 +# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 +# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G4" +# Serial: 451799571007117016466790293371524403291602933463 +# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb +# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a +# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA CYBER Root CA" +# Serial: 85076849864375384482682434040119489222 +# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51 +# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66 +# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58 +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA14" +# Serial: 575790784512929437950770173562378038616896959179 +# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5 +# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f +# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA15" +# Serial: 126083514594751269499665114766174399806381178503 +# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47 +# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d +# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 2 2023" +# Serial: 153168538924886464690566649552453098598 +# MD5 Fingerprint: e1:09:ed:d3:60:d4:56:1b:47:1f:b7:0c:5f:1b:5f:85 +# SHA1 Fingerprint: 2d:b0:70:ee:71:94:af:69:68:17:db:79:ce:58:9f:a0:6b:96:f7:87 +# SHA256 Fingerprint: 05:52:e6:f8:3f:df:65:e8:fa:96:70:e6:66:df:28:a4:e2:13:40:b5:10:cb:e5:25:66:f9:7c:4f:b9:4b:2b:d1 +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. +# Label: "TrustAsia TLS ECC Root CA" +# Serial: 310892014698942880364840003424242768478804666567 +# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c +# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36 +# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11 +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw +WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw +NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE +ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB +c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ +AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp +guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw +DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 +L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR +OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. +# Label: "TrustAsia TLS RSA Root CA" +# Serial: 160405846464868906657516898462547310235378010780 +# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12 +# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa +# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3 +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 +c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ +NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ +Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 +HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 +ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb +xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX +i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ +UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j +TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT +bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 +S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 +iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt +7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp +2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ +g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj +pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M +pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP +XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe +SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 +ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy +323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 2 2023" +# Serial: 139766439402180512324132425437959641711 +# MD5 Fingerprint: 96:b4:78:09:f0:09:cb:77:eb:bb:1b:4d:6f:36:bc:b6 +# SHA1 Fingerprint: a5:5b:d8:47:6c:8f:19:f7:4c:f4:6d:6b:b6:c2:79:82:22:df:54:8b +# SHA256 Fingerprint: 8e:82:21:b2:e7:d4:00:78:36:a1:67:2f:0d:cc:29:9c:33:bc:07:d3:16:f1:32:fa:1a:20:6d:58:71:50:f1:ce +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG +# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG +# Label: "SwissSign RSA TLS Root CA 2022 - 1" +# Serial: 388078645722908516278762308316089881486363258315 +# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39 +# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce +# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41 +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- + +# Issuer: CN=OISTE Server Root ECC G1 O=OISTE Foundation +# Subject: CN=OISTE Server Root ECC G1 O=OISTE Foundation +# Label: "OISTE Server Root ECC G1" +# Serial: 47819833811561661340092227008453318557 +# MD5 Fingerprint: 42:a7:d2:35:ae:02:92:db:19:76:08:de:2f:05:b4:d4 +# SHA1 Fingerprint: 3b:f6:8b:09:ae:2a:92:7b:ba:e3:8d:3f:11:95:d9:e6:44:0c:45:e2 +# SHA256 Fingerprint: ee:c9:97:c0:c3:0f:21:6f:7e:3b:8b:30:7d:2b:ae:42:41:2d:75:3f:c8:21:9d:af:d1:52:0b:25:72:85:0f:49 +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy +NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy +cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N +2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3 +TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C +tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR +QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD +YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE Server Root RSA G1 O=OISTE Foundation +# Subject: CN=OISTE Server Root RSA G1 O=OISTE Foundation +# Label: "OISTE Server Root RSA G1" +# Serial: 113845518112613905024960613408179309848 +# MD5 Fingerprint: 23:a7:9e:d4:70:b8:b9:14:57:41:8a:7e:44:59:e2:68 +# SHA1 Fingerprint: f7:00:34:25:94:88:68:31:e4:34:87:3f:70:fe:86:b3:86:9f:f0:6e +# SHA256 Fingerprint: 9a:e3:62:32:a5:18:9f:fd:db:35:3d:fd:26:52:0c:01:53:95:d2:27:77:da:c5:9d:b5:7b:98:c0:89:a6:51:e6 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4 +MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM +vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b +rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk +ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z +O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R +tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS +jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh +sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho +mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu ++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR +i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT +kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2 +zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG +5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8 +qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP +AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk +gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs +YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1 +9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome +/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3 +J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 +wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy +BiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Subject: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Label: "e-Szigno TLS Root CA 2023" +# Serial: 71934828665710877219916191754 +# MD5 Fingerprint: 6a:e9:99:74:a5:da:5e:f1:d9:2e:f2:c8:d1:86:8b:71 +# SHA1 Fingerprint: 6f:9a:d5:d5:df:e8:2c:eb:be:37:07:ee:4f:4f:52:58:29:41:d1:fe +# SHA256 Fingerprint: b4:91:41:50:2d:00:66:3d:74:0f:2e:7e:c3:40:c5:28:00:96:26:66:12:1a:36:d0:9c:f7:dd:2b:90:38:4f:b4 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS RSA Root CA 2024" +# Serial: 17188327524208271538 +# MD5 Fingerprint: d0:a4:db:32:eb:44:98:d2:62:0b:3e:bc:4d:7c:5c:e9 +# SHA1 Fingerprint: fb:97:96:7c:ef:8d:98:63:06:c0:3b:b6:11:f8:e0:13:97:a2:98:d3 +# SHA256 Fingerprint: 14:35:f2:25:c5:d2:52:d7:a2:19:48:cc:3c:e6:2a:ec:fa:88:00:1e:3d:d7:2d:1c:c3:55:51:00:eb:37:2f:93 +-----BEGIN CERTIFICATE----- +MIIFmjCCA4KgAwIBAgIJAO6JNNDLgOCyMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNV +BAYTAkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEj +MCEGA1UEAxMaU0VDT00gVExTIFJTQSBSb290IENBIDIwMjQwHhcNMjQwMTMxMDUx +MTU1WhcNNDkwMTE0MDUxMTU1WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VD +T00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBS +U0EgUm9vdCBDQSAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4TjizUwzxbInq8Tx11gaFYNk5fO+34y7TyM4neh0UgL5JIZbJNLTz2x//L/B71+5 +m6X6nGIr7d4lFJBGtjO677hXOz93zkcWaUTm3VbOAjBlt4YWxlcccBHXuZ7o3Q+4 +R+ormrBdHeJ1CTUEG8ttQbKIl3G7OZYbnH8/pP8cjPub/0kDVNuMzp7xsVRROOis +Qt53fMoJLlYgoebbuMphOqMCtjkJ7R6efEMfLp8UAVi9ZaLRn76ET/CJkk925ndu +uufC4BatS4mnXFmxN0vUXb0ij9B8O/D8gixQEsVSD4GK8FWRPh3bVd/6bzdkHGJj +y21XI0yejVomZUbRrOfNuz0boPGV1pt18fFC39IHQEth3OFqb5NDO3L+A9bNqTgA +yUgRmIn4ucgDc/Ri/Km3V51ueZjy1/yk0qwJVadAVVrCt56iNeXOyEvzJADGgDQ8 +E1Pdaqct8Cynz/47ReQM62vFYO08wcQkrjmX/tesiko1V1yyaf6EfPzUFzmaGy9x +vkCwdbm15EdTolOjE0H2Vb5/APDOyCFEokiYGmXTLdAUl0wKZ4IyjkHGzy0jhpaX +EXE/GJcEvI6VzEchjaBL03EJ0h9pG4OqeIOycKvAo3A+TbetyfsrgYyHzU0a7/qU +jGat1AAq1nVljMpKqpinPTsf/d9H39FTUeJL7TpzzjUCAwEAAaNjMGEwHQYDVR0O +BBYEFCzrchKOWHdkNRVWNQFXB6l9DTbmMB8GA1UdIwQYMBaAFCzrchKOWHdkNRVW +NQFXB6l9DTbmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAVwsvluSafaez5tFPR/hRTBzRxEyMMQF3XJXCVi3yegZyK +oec7hmE6jx2ZM8KgM1kn2yJRwFXHX8zUW9nBLEDWc4wuE8LrlZqhGZM9pJQXGmGz +ResDJV6JgRBna+j4sA1M7yIdlvL0sAfFXFCTRaWTD4E1V99RLrFzWfTcC+e180hD +uNMpqOEo46+lMeW/Wvh7ifOQs+kiK0O2gHxQDNxslSavnCs4V7l8HRDJ2La10o70 +Bo7VLzf1W8MBvv0VTnxB+NjT5qTAbhGFh9Gvp4BaJpmdUf0C5CEP6dbQlfgxWfzY +r69yVT6dPQB+GFEaY03IMY+AcBCs+om1fNxrQXt9zoofMBNFbLhvpNH/JsXWdGUz +fNbO12uswTa5wah8LB18FTQN2/zPHYmvBEoLuyUgZ09VNLJo5YA0kXItVYkLjMe2 +SixzK4scUHv81IK99I91DWx7FwMVKw2xgFp+ZLYB2dnpQQrqwlW64glHUcK2N9BD +snjLSxeZ+UPECh9RxH4WAcKiZW+cqaKMmhP2WBfR4IcR7NOL32ml11ds87hhV1CZ +WWFCJAcCidYZz6CZa8exzHojP9SB5RH0/v1KdHAisqhSjtJl/UIAHIQ48elOn8wr +TdFap4Yb5aHglmMeNx+fAIhDluWVfxTO7H4dTPU+SFVRMLAh+wwKZfqb94nMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS ECC Root CA 2024" +# Serial: 9329818985461676612 +# MD5 Fingerprint: 99:d3:9d:e4:d2:b1:2d:f0:2a:04:67:85:f3:df:46:d6 +# SHA1 Fingerprint: 7a:1f:22:2d:72:b2:c3:19:87:44:db:61:69:e8:a6:4b:d7:0d:44:0e +# SHA256 Fingerprint: 6a:b2:ab:75:f5:1c:b4:f4:f0:15:62:03:fb:f6:f6:46:23:2f:51:4b:e0:59:f6:28:33:30:8b:82:b4:d7:2d:b1 +-----BEGIN CERTIFICATE----- +MIICTDCCAdGgAwIBAgIJAIF6LO+PI3pEMAoGCCqGSM49BAMDMFoxCzAJBgNVBAYT +AkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEG +A1UEAxMaU0VDT00gVExTIEVDQyBSb290IENBIDIwMjQwHhcNMjQwMTMxMDU1MjM0 +WhcNNDkwMTE0MDU1MjM0WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VDT00g +VHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBFQ0Mg +Um9vdCBDQSAyMDI0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7NzFMtu9dzQXSNC1 +2fabk0+GlC5finB3R7XaZonRUd20aFiWObtuNBCLUZSfk6QXAE55BjEXsXQ/NG8y +UqicXjsu9ksDK3JZBgCwLOVh6+nwJXTvso/dEj/GUYH5mBdoo2MwYTAdBgNVHQ4E +FgQUO3YReyl04k4GTFaCQNAhL3qzydUwHwYDVR0jBBgwFoAUO3YReyl04k4GTFaC +QNAhL3qzydUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI +zj0EAwMDaQAwZgIxAN3ib8fi1pMYtAPjMilB5e5/H+t5CL0xPL+cZ5oTTZuSCjpA +n1v7F/VAr8bFxQXAowIxAKsBVO1ACFp7skwzPvdv1EUY5a897WGLT4lb+bjxFAWy +l8wDcZJdwGZ/pAHxt1AJ1g== +-----END CERTIFICATE----- + +# Issuer: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Label: "Telia EC TLS Root CA v3" +# Serial: 8028200332079443110287896228123317 +# MD5 Fingerprint: b6:fa:6a:5c:42:fb:c5:67:72:c0:e0:2f:72:fb:5a:44 +# SHA1 Fingerprint: b4:d6:07:c2:a5:95:bc:5b:f4:67:4d:c9:dc:6f:6f:0a:00:7a:a5:35 +# SHA256 Fingerprint: 09:8e:08:a9:1d:bb:f7:74:78:b9:6c:ce:b8:9b:14:13:a5:da:37:b7:c8:62:60:6a:95:5d:eb:07:17:9f:43:26 +-----BEGIN CERTIFICATE----- +MIICMjCCAbegAwIBAgIPAYvSIlRjTQSLbOVHH9K1MAoGCCqGSM49BAMDMEoxCzAJ +BgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdU +ZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzAeFw0yMzExMTUwODU1MjZaFw00ODA1MjMx +MTAwMDBaMEoxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFC +MSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABMHIlhVDLbmFKUpW0iK4dpryT6emYOeS31JPwWnWPmkWRrAkTbPX +40sQfHI9mpR7Rbktu3ngg6W+BBSXSechtMCnBmWXj/EaVlmV5cY1jD2HoTfhBQ3A +acpCNMLJK4NpZaNjMGEwHwYDVR0jBBgwFoAU1GToQ4g6cy/QGnGCNgtehd7H3kMw +HQYDVR0OBBYEFNRk6EOIOnMv0BpxgjYLXoXex95DMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2kAMGYCMQCXAUdS/9bbJ8A1JYaG +f/bWt/s7Ta0ot5Ulno8OjSNYRWQIlS4tVWldvTAVA7heOFgCMQCvKr8+Z2Rn+OBr +5UHzlgBObpad1LuwNTRcdNgUJxIWadcki+UBLEi1/AURKV5md2M= +-----END CERTIFICATE----- + +# Issuer: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Label: "Telia RSA TLS Root CA v3" +# Serial: 8028214673410753541795188766939845 +# MD5 Fingerprint: f4:8b:9c:f3:f8:63:cf:dc:25:8f:b4:bb:a2:e9:9d:e2 +# SHA1 Fingerprint: b5:2e:88:4e:40:c1:11:fb:50:c7:e2:4f:ac:18:2b:bd:68:15:d2:34 +# SHA256 Fingerprint: d1:3d:b1:29:4c:45:eb:c6:fc:86:c6:bb:f6:9f:a2:9b:df:e6:92:df:f7:c7:13:c2:43:c7:a9:56:c6:a2:28:4c +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgIPAYvSUKtCVSxHWr2h3BrFMA0GCSqGSIb3DQEBDAUAMEsx +CzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQD +DBhUZWxpYSBSU0EgVExTIFJvb3QgQ0EgdjMwHhcNMjMxMTE1MDk0NzQyWhcNNDgw +NTIzMTEwMDAwWjBLMQswCQYDVQQGEwJTRTEZMBcGA1UECgwQVGVsaWEgQ29tcGFu +eSBBQjEhMB8GA1UEAwwYVGVsaWEgUlNBIFRMUyBSb290IENBIHYzMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsV89KG19hCf4S1Fvk8D3TyDERhmcvx8F +7Kmb4WATx3ije1id3KHxRE0TKmcNCbAQ57bvHFEYa4hR2l20VjVadExqOW+2ld99 +MbEiO+jRVOz+BbxLxJnmGwCqI+BfuTjjVReDxsxjQvjgBsClaO/sm5i70nlZcWGR +tIkvWDK3NNkT5RtwXc/O8NTFVpbUqT6cRjIj3olAblR+lRf4Ffy5o+Q9fabjYn9Z +9S4itruElcEFf9Ljk7fwdTycT/rvJW9w/B3G2a3r0f/zXNOVruIBcqE6pkSospAC +U2bG42fYKrbM/GWnp7u+p9Frz4jaNwpb4YHuEeS8BratNcP8X62jXIvvKHxlsMDJ +Cnb4U8JzFOLsU6mohVY58BdZrvi0Gk9UOuqmgoG6dskHoksjZTlK61D/InzmEoA1 +yAYJFDVysjRxDUOu9cAwANbqmq77WIFL6BpnZgVqPtMfG6wN8BrTKdapvilVsYR5 +9BFgIsAVBMxrGh+W+QcvmJafUpASvlArKvVG2FI4i6PiLjSBT0+6F6EQLrYqefOQ +F/fBNEXb+njUQ0SUVrAqtH4Y+OjCI/a4/JJQppxeemZcQ0SUShgiI5AM5xHO5iya +UrTjYH4zxUz9j+1FEbDH/xpstr1gXBykspup+hRTaJcbA+UbpJqtWZndAPddJmt6 +YJQ+dU3pDu8CAwEAAaNjMGEwHwYDVR0jBBgwFoAUsMep0t2yKFZzBJSMFFxIbzdS +kqgwHQYDVR0OBBYEFLDHqdLdsihWcwSUjBRcSG83UpKoMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBdYzFsNGDRk7bR +/AgRKq+5637YuOW+w6uhpoS0VnKMUpyHCwku86hEvqivakPtfmlm4bFwt++sb/8O +XsWBqtfbXMaBNDTZl8XRMJuLWOW2JrbKkRzgG0eBUcvsadG1rrhbmZqYvFXaAZO7 +o4TdOZzxhBB5GOAWWXB3IeraNP4J63zyo9n8Gqw3sJBG44em5hoYjBffP+npibys +lnslRi4L6xHsCYj/Pab+OlqbMCB6v+sTCLeEIukRVzoR9aQ45pEK7Z1QBnSsbAKQ +tss0JKD9d/mX143H1xePjPhTXlv5JCkhrcj+SShz0P9+EHoWe6m9lyUEOIVn0rp+ +yVJWNbmyDv3VkwFxHC1ApSQsgSimjGQ4wtr6cSmordYxkV+Ro8lOIIhRksXPyDk2 +7gW6IjUXCkZKpxFjkL3jiBSc8SkxnwCWtXg8xwNwdFVNBGLCCuJnsneYXjJNqzRq +UcoGwzsvF3Qi/ZnHUNvISdevlgIAXL4Wvrxaqvoa01wB+GCfs57RTGE4TvAGhKNK +us8K3hRT1BSpigzMIRzSxtAOrqPN6j//QSmW9f8Jcncri4j2ihSpVrFU0NdNkMhZ +eAKidTFPsxCVFuW4Aniz7jqiw5sWtjbQrlW035izIEU4sYwQoC1Nx0Svy+mMTRai +50LqFQ+A1/Hq6xHHDNx7CI83d23Erw== +-----END CERTIFICATE----- diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py new file mode 100644 index 0000000..1c9661c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/core.py @@ -0,0 +1,83 @@ +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +else: + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py new file mode 100644 index 0000000..54670ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py @@ -0,0 +1,18 @@ +import os +import unittest + +import certifi + + +class TestCertifi(unittest.TestCase): + def test_cabundle_exists(self) -> None: + assert os.path.exists(certifi.where()) + + def test_read_contents(self) -> None: + content = certifi.contents() + assert "-----BEGIN CERTIFICATE-----" in content + + def test_py_typed_exists(self) -> None: + assert os.path.exists( + os.path.join(os.path.dirname(certifi.__file__), 'py.typed') + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA new file mode 100644 index 0000000..7c07654 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA @@ -0,0 +1,67 @@ +Metadata-Version: 2.4 +Name: cffi +Version: 2.1.1 +Summary: Foreign Function Interface for Python calling C code. +Author: Armin Rigo, Maciej Fijalkowski +Maintainer: Matt Davis, Matt Clay +License-Expression: MIT-0 +Project-URL: Documentation, https://cffi.readthedocs.io/ +Project-URL: Changelog, https://cffi.readthedocs.io/en/latest/whatsnew.html +Project-URL: Download, https://github.com/python-cffi/cffi/releases +Project-URL: Contact, https://groups.google.com/forum/#!forum/python-cffi +Project-URL: Source Code, https://github.com/python-cffi/cffi +Project-URL: Issue Tracker, https://github.com/python-cffi/cffi/issues +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Classifier: Programming Language :: Python :: Implementation :: CPython +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: pycparser; implementation_name != "PyPy" +Dynamic: license-file + +[![GitHub Actions Status](https://github.com/python-cffi/cffi/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/python-cffi/cffi/actions/workflows/ci.yaml?query=branch%3Amain++) +[![PyPI version](https://img.shields.io/pypi/v/cffi.svg)](https://pypi.org/project/cffi) +[![Read the Docs](https://img.shields.io/badge/docs-latest-blue.svg)][Documentation] + + +CFFI +==== + +Foreign Function Interface for Python calling C code. + +Please see the [Documentation] or uncompiled in the `doc/` subdirectory. + +Download +-------- + +[Download page](https://github.com/python-cffi/cffi/releases) + +Source Code +----------- + +Source code is publicly available on +[GitHub](https://github.com/python-cffi/cffi). + +Contact +------- + +[Mailing list](https://groups.google.com/forum/#!forum/python-cffi) + +Testing/development tips +------------------------ + +After `git clone` or `wget && tar`, we will get a directory called `cffi` or `cffi-x.x.x`. We call it `repo-directory`. To run tests under CPython, run the following in the `repo-directory`: + + pip install pytest + pip install -e . # editable install of CFFI for local development + pytest src/c/ testing/ + +[Documentation]: http://cffi.readthedocs.org/ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD new file mode 100644 index 0000000..4f53638 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD @@ -0,0 +1,31 @@ +_cffi_backend.cpython-313-x86_64-linux-gnu.so,sha256=2sgVFUUCLMNWH7t6j_-s9o_bcnOpQYVS1qGN2l6YajQ,348776 +cffi/__init__.py,sha256=xHstsoxfGX4kLU3Ur5W367DaZXY_x2lyyv0G-T6ULNo,511 +cffi/_cffi_errors.h,sha256=Wzg-0Bu1_K2GEK0pSU5b6Q2GAAudcaF87HNG8LoFmWw,3583 +cffi/_cffi_gen_src.py,sha256=yUR53pFSbsa3ssF7_VOqXTaKeL5AJTHM_dyZ9IGY9sw,7044 +cffi/_cffi_include.h,sha256=zY6KFoo_bpa02PagQs1hzeW9O56snioloyxqvRKAot8,15234 +cffi/_embedding.h,sha256=PhAdAMcKClATVZ-lBcZeDEc12F6FqVdaHtaJ2Pkg6E8,17554 +cffi/_imp_emulation.py,sha256=RxREG8zAbI2RPGBww90u_5fi8sWdahpdipOoPzkp7C0,2960 +cffi/_shimmed_dist_utils.py,sha256=Bjj2wm8yZbvFvWEx5AEfmqaqZyZFhYfoyLLQHkXZuao,2230 +cffi/api.py,sha256=cWX1ZlscmOyTkvthiBpUWLcSRBtqlpn-moBu4esBmzI,42013 +cffi/backend_ctypes.py,sha256=T7CSc14P5vyloi5hyox68qhBWX079owHXaGDbY_U7bo,42283 +cffi/cffi_opcode.py,sha256=5-1cU6ppHGzsstF_eoglD_8tCDk76_Zhkzj3fW4Sic0,5723 +cffi/commontypes.py,sha256=7N6zPtCFlvxXMWhHV08psUjdYIK2XgsN3yo5dgua_v4,2805 +cffi/cparser.py,sha256=WY-ox4UGSuVSYTtgm02FmGgbmcuaaBK3moFlkdFSmsU,44754 +cffi/error.py,sha256=v6xTiS4U0kvDcy4h_BDRo5v39ZQuj-IMRYLv5ETddZs,877 +cffi/ffiplatform.py,sha256=avxFjdikYGJoEtmJO7ewVmwG_VEVl6EZ_WaNhZYCqv4,3584 +cffi/gen_src.py,sha256=TU_ZVDwoLnHPlal8_mPmpxqyWJ7U752jTc3UsTmE1QI,132 +cffi/lock.py,sha256=l9TTdwMIMpi6jDkJGnQgE9cvTIR7CAntIJr8EGHt3pY,747 +cffi/model.py,sha256=cA-bEAibcHUbtbqfgMq0CLQjFPAjV0JSRzMyKViNgF4,21752 +cffi/parse_c_type.h,sha256=OdwQfwM9ktq6vlCB43exFQmxDBtj2MBNdK8LYl15tjw,5976 +cffi/pkgconfig.py,sha256=uyt117TQeea3djagM1W5go8RpnySGTgoCzN8QHyZdlc,4365 +cffi/recompiler.py,sha256=TJVF07mFzYGTV6szp2zPrJd2dJKHN-eB_SuiJiGl9Kw,65140 +cffi/setuptools_ext.py,sha256=pZrnTqVCkGMqu8NxIxkdbItl65gehMP1LoraUdT7-4U,9566 +cffi/vengine_cpy.py,sha256=rRouYQ6BllAIXNjZGTPUW54rB952qi3gcm16SWXWKLc,43030 +cffi/vengine_gen.py,sha256=Z_KqaF6r54n5i4hjQThZdb-1DBTEimnU4y4E_KxSCDg,26931 +cffi/verifier.py,sha256=xIzV8iDMhZ1-qhX3RRX4DWo2k07WpcJxdhhAFXAjrmY,11154 +cffi-2.1.1.dist-info/METADATA,sha256=VYuvuQk6jnkBddns_5aIOCmB10SHfa2G_Wdw2w9NyRg,2527 +cffi-2.1.1.dist-info/WHEEL,sha256=6ms3XnzBnat_sZzZrDidy-tujCypEPg5zKUj6j3hTbo,151 +cffi-2.1.1.dist-info/entry_points.txt,sha256=prYQgBH44nBQ_lJpu6iMgFG0FGl4gNOwtvvjoDoLABw,132 +cffi-2.1.1.dist-info/top_level.txt,sha256=rE7WR3rZfNKxWI9-jn6hsHCAl7MDkB-FmuQbxWjFehQ,19 +cffi-2.1.1.dist-info/RECORD,, +cffi-2.1.1.dist-info/licenses/LICENSE,sha256=W6JN3FcGf5JJrdZEw6_EGl1tw34jQz73Wdld83Cwr2M,1123 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL new file mode 100644 index 0000000..f13e730 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (83.0.0) +Root-Is-Purelib: false +Tag: cp313-cp313-manylinux_2_17_x86_64 +Tag: cp313-cp313-manylinux2014_x86_64 + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt new file mode 100644 index 0000000..46bb21b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +cffi-gen-src = cffi._cffi_gen_src:run + +[distutils.setup_keywords] +cffi_modules = cffi.setuptools_ext:cffi_modules diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000..0a1dbfb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE @@ -0,0 +1,23 @@ + +Except when otherwise stated (look for LICENSE files in directories or +information at the beginning of each file) all software and +documentation is licensed as follows: + + MIT No Attribution + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the + Software is furnished to do so. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt new file mode 100644 index 0000000..f645779 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_cffi_backend +cffi diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py new file mode 100644 index 0000000..93dcdb3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/__init__.py @@ -0,0 +1,14 @@ +__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', + 'FFIError'] + +from .api import FFI +from .error import CDefError, FFIError, VerificationError, VerificationMissing +from .error import PkgConfigError + +__version__ = "2.1.1" +__version_info__ = (2, 1, 1) + +# The verifier module file names are based on the CRC32 of a string that +# contains the following version number. It may be older than __version__ +# if nothing is clearly incompatible. +__version_verifier_modules__ = "0.8.6" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h new file mode 100644 index 0000000..0931379 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h @@ -0,0 +1,135 @@ +#ifndef CFFI_MESSAGEBOX +# ifdef _MSC_VER +# define CFFI_MESSAGEBOX 1 +# else +# define CFFI_MESSAGEBOX 0 +# endif +#endif + + +#if CFFI_MESSAGEBOX +/* Windows only: logic to take the Python-CFFI embedding logic + initialization errors and display them in a background thread + with MessageBox. The idea is that if the whole program closes + as a result of this problem, then likely it is already a console + program and you can read the stderr output in the console too. + If it is not a console program, then it will likely show its own + dialog to complain, or generally not abruptly close, and for this + case the background thread should stay alive. +*/ +static void *volatile _cffi_bootstrap_text; + +static PyObject *_cffi_start_error_capture(void) +{ + PyObject *result = NULL; + PyObject *x, *m, *bi; + + if (InterlockedCompareExchangePointer(&_cffi_bootstrap_text, + (void *)1, NULL) != NULL) + return (PyObject *)1; + + m = PyImport_AddModule("_cffi_error_capture"); + if (m == NULL) + goto error; + + result = PyModule_GetDict(m); + if (result == NULL) + goto error; + + bi = PyImport_ImportModule("builtins"); + if (bi == NULL) + goto error; + PyDict_SetItemString(result, "__builtins__", bi); + Py_DECREF(bi); + + x = PyRun_String( + "import sys\n" + "class FileLike:\n" + " def write(self, x):\n" + " try:\n" + " of.write(x)\n" + " except: pass\n" + " self.buf += x\n" + " def flush(self):\n" + " pass\n" + "fl = FileLike()\n" + "fl.buf = ''\n" + "of = sys.stderr\n" + "sys.stderr = fl\n" + "def done():\n" + " sys.stderr = of\n" + " return fl.buf\n", /* make sure the returned value stays alive */ + Py_file_input, + result, result); + Py_XDECREF(x); + + error: + if (PyErr_Occurred()) + { + PyErr_WriteUnraisable(Py_None); + PyErr_Clear(); + } + return result; +} + +#pragma comment(lib, "user32.lib") + +static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored) +{ + Sleep(666); /* may be interrupted if the whole process is closing */ + MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text, + L"Python-CFFI error", + MB_OK | MB_ICONERROR); + _cffi_bootstrap_text = NULL; + return 0; +} + +static void _cffi_stop_error_capture(PyObject *ecap) +{ + PyObject *s; + void *text; + + if (ecap == (PyObject *)1) + return; + + if (ecap == NULL) + goto error; + + s = PyRun_String("done()", Py_eval_input, ecap, ecap); + if (s == NULL) + goto error; + + /* Show a dialog box, but in a background thread, and + never show multiple dialog boxes at once. */ + text = PyUnicode_AsWideCharString(s, NULL); + + _cffi_bootstrap_text = text; + + if (text != NULL) + { + HANDLE h; + h = CreateThread(NULL, 0, _cffi_bootstrap_dialog, + NULL, 0, NULL); + if (h != NULL) + CloseHandle(h); + } + /* decref the string, but it should stay alive as 'fl.buf' + in the small module above. It will really be freed only if + we later get another similar error. So it's a leak of at + most one copy of the small module. That's fine for this + situation which is usually a "fatal error" anyway. */ + Py_DECREF(s); + PyErr_Clear(); + return; + + error: + _cffi_bootstrap_text = NULL; + PyErr_Clear(); +} + +#else + +static PyObject *_cffi_start_error_capture(void) { return NULL; } +static void _cffi_stop_error_capture(PyObject *ecap) { } + +#endif diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py new file mode 100644 index 0000000..3d4fb7e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py @@ -0,0 +1,216 @@ +# Integrated from the cffi-buildtool project by Rose Davidson +# (https://github.com/inklesspen/cffi-buildtool), under the following +# license: +# +# MIT License +# +# Copyright (c) 2024, Rose Davidson +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice (including the +# next paragraph) shall be included in all copies or substantial portions +# of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +"""Implementation of the ``cffi-gen-src`` command-line tool. + +This module is private; the command line is the only supported +interface. Two subcommands: + +``exec-python`` + Execute a Python script that constructs a :class:`cffi.FFI` + (the same kind of script that the CFFI docs' "Main mode of usage" + describes) and emit the generated C source. + +``read-sources`` + Create the :class:`cffi.FFI` from a separate ``cdef`` file and C + source prelude, then emit the generated C source. +""" + +import argparse +import io +import os +import sys + +from .api import FFI + + +def _execfile(pysrc, filename, globs): + compiled = compile(source=pysrc, filename=filename, mode='exec') + exec(compiled, globs, globs) + + +def find_ffi_in_python_script(pysrc, filename, ffivar): + """Execute ``pysrc`` and return the :class:`FFI` object it defines. + + The script is executed with ``__name__`` set to ``"cffi.gen_src"``, + so a trailing ``if __name__ == "__main__": ffibuilder.compile()`` + block in the script is skipped. + + ``ffivar`` is the name bound by the script to the :class:`FFI` + object, or to a callable that returns one. + + Raises :class:`NameError` if the name is not bound by the script, + or :class:`TypeError` if the name does not resolve to an + :class:`FFI` instance. + """ + filename = os.path.abspath(filename) + globs = {'__name__': 'cffi.gen_src', '__file__': filename} + old_path = sys.path[:] + sys.path.insert(0, os.path.dirname(filename)) + try: + _execfile(pysrc, filename, globs) + if ffivar not in globs: + raise NameError( + "Expected to find the FFI object with the name %r, " + "but it was not found." % (ffivar,) + ) + ffi = globs[ffivar] + if not isinstance(ffi, FFI) and callable(ffi): + # Maybe it's a callable that returns a FFI + ffi = ffi() + if not isinstance(ffi, FFI): + raise TypeError( + "Found an object with the name %r but it was not an " + "instance of cffi.api.FFI" % (ffivar,) + ) + return ffi + finally: + sys.path[:] = old_path + + +def make_ffi_from_sources(modulename, cdef, csrc): + """Create an :class:`FFI` from ``cdef`` text and a C source prelude.""" + ffibuilder = FFI() + ffibuilder.cdef(cdef) + ffibuilder.set_source(modulename, csrc) + return ffibuilder + + +def generate_c_source(ffi): + """Return the C source that :meth:`FFI.emit_c_code` would write.""" + output = io.StringIO() + ffi.emit_c_code(output) + return output.getvalue() + + +def write_c_source(output, generated): + if output == '-': + sys.stdout.write(generated) + return + with open(output, 'w', encoding='utf-8') as f: + f.write(generated) + + +def same_input_file(file1, file2): + if file1 is file2: + return True + try: + return os.path.samefile(file1.name, file2.name) + except OSError: + return False + + +def exec_python(*, output, pyfile, ffi_var): + with pyfile: + ffi = find_ffi_in_python_script(pyfile.read(), pyfile.name, ffi_var) + generated = generate_c_source(ffi) + write_c_source(output, generated) + + +def read_sources(*, output, module_name, cdef_input, csrc_input): + with csrc_input, cdef_input: + csrc = csrc_input.read() + cdef = cdef_input.read() + ffi = make_ffi_from_sources(module_name, cdef, csrc) + generated = generate_c_source(ffi) + write_c_source(output, generated) + + +def _prog(): + # The same parser serves both documented invocations; make --help + # and usage messages show the one that was actually used. + argv0 = os.path.basename(sys.argv[0]) if sys.argv else '' + if argv0.startswith('cffi-gen-src'): + return 'cffi-gen-src' + return 'python -m cffi.gen_src' + + +parser = argparse.ArgumentParser( + prog=_prog(), + description='Generate CFFI C source for extension modules.', +) +subparsers = parser.add_subparsers(dest='mode') + +exec_python_parser = subparsers.add_parser( + 'exec-python', + help='Execute a Python script that defines an FFI object', +) +exec_python_parser.add_argument( + '--ffi-var', + default='ffibuilder', + help="Name of the FFI object in the Python script; defaults to 'ffibuilder'.", +) +exec_python_parser.add_argument( + 'pyfile', + type=argparse.FileType('r', encoding='utf-8'), + help='Path to the Python script', +) +exec_python_parser.add_argument( + 'output', + help='Output path for the C source', +) + +read_sources_parser = subparsers.add_parser( + 'read-sources', + help='Read cdef and C source prelude files that define an FFI object', +) +read_sources_parser.add_argument( + 'module_name', + help='Full name of the generated module, including packages', +) +read_sources_parser.add_argument( + 'cdef', + type=argparse.FileType('r', encoding='utf-8'), + help='File containing C definitions', +) +read_sources_parser.add_argument( + 'csrc', + type=argparse.FileType('r', encoding='utf-8'), + help='File containing C source prelude', +) +read_sources_parser.add_argument( + 'output', + help='Output path for the C source', +) + + +def run(args=None): + args = parser.parse_args(args=args) + if args.mode == 'exec-python': + exec_python(output=args.output, pyfile=args.pyfile, ffi_var=args.ffi_var) + elif args.mode == 'read-sources': + if same_input_file(args.cdef, args.csrc): + parser.error('cdef and csrc are the same file and should not be') + read_sources( + output=args.output, + module_name=args.module_name, + cdef_input=args.cdef, + csrc_input=args.csrc, + ) + else: + parser.error('a subcommand is required: exec-python or read-sources') + parser.exit(0) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h new file mode 100644 index 0000000..c8c6d34 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h @@ -0,0 +1,397 @@ +#define _CFFI_ + +/* We try to define Py_LIMITED_API before including Python.h. + + Mess: we can only define it if Py_DEBUG, Py_TRACE_REFS and + Py_REF_DEBUG are not defined. This is a best-effort approximation: + we can learn about Py_DEBUG from pyconfig.h, but it is unclear if + the same works for the other two macros. Py_DEBUG implies them, + but not the other way around. + + The implementation is messy (issue #350): on Windows, with _MSC_VER, + we have to define Py_LIMITED_API even before including pyconfig.h. + In that case, we guess what pyconfig.h will do to the macros above, + and check our guess after the #include. + + Note that on Windows, with CPython 3.x, you need >= 3.5 and virtualenv + version >= 16.0.0. With older versions of either, you don't get a + copy of PYTHON3.DLL in the virtualenv. We can't check the version of + CPython *before* we even include pyconfig.h. ffi.set_source() puts + a ``#define _CFFI_NO_LIMITED_API'' at the start of this file if it is + running on Windows < 3.5, as an attempt at fixing it, but that's + arguably wrong because it may not be the target version of Python. + Still better than nothing I guess. As another workaround, you can + remove the definition of Py_LIMITED_API here. + + See also 'py_limited_api' in cffi/setuptools_ext.py. +*/ +#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API) +# ifdef _MSC_VER +# if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# if !defined(Py_GIL_DISABLED) +# define Py_LIMITED_API +# else +# define Py_LIMITED_API 0x030f0000 +# endif +# endif + +# include + /* sanity-check: Py_LIMITED_API will cause crashes if any of these + are also defined. Normally, the Python file PC/pyconfig.h does not + cause any of these to be defined, with the exception that _DEBUG + causes Py_DEBUG. Double-check that. */ +# ifdef Py_LIMITED_API +# if defined(Py_DEBUG) +# error "pyconfig.h unexpectedly defines Py_DEBUG, but Py_LIMITED_API is set" +# endif +# if defined(Py_TRACE_REFS) +# error "pyconfig.h unexpectedly defines Py_TRACE_REFS, but Py_LIMITED_API is set" +# endif +# if defined(Py_REF_DEBUG) +# error "pyconfig.h unexpectedly defines Py_REF_DEBUG, but Py_LIMITED_API is set" +# endif +# endif +# else +# include +# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# if !defined(Py_GIL_DISABLED) +# define Py_LIMITED_API +# else +# define Py_LIMITED_API 0x030f0000 +# endif +# endif +# endif +#endif + +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#include + +#include "parse_c_type.h" + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#ifdef __GNUC__ +# define _CFFI_UNUSED_FN __attribute__((unused)) +#else +# define _CFFI_UNUSED_FN /* nothing */ +#endif + +#ifdef __cplusplus +# ifndef _Bool + typedef bool _Bool; /* semi-hackish: C++ has no _Bool; bool is builtin */ +# endif +#endif + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyLong_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyLong_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyLong_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, struct _cffi_ctypedescr *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + not used any more +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((_cffi_wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(_cffi_wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(struct _cffi_ctypedescr *, \ + PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[24]) +#define _CFFI_CPIDX 25 +#define _cffi_call_python \ + ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX]) +#define _cffi_to_c_wchar3216_t \ + ((int(*)(PyObject *))_cffi_exports[26]) +#define _cffi_from_c_wchar3216_t \ + ((PyObject *(*)(int))_cffi_exports[27]) +#define _CFFI_NUM_EXPORTS 28 + +struct _cffi_ctypedescr; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; + +#define _cffi_type(index) ( \ + assert((((uintptr_t)_cffi_types[index]) & 1) == 0), \ + (struct _cffi_ctypedescr *)_cffi_types[index]) + +static PyObject *_cffi_init(const char *module_name, Py_ssize_t version, + const struct _cffi_type_context_s *ctx) +{ + PyObject *module, *o_arg, *new_module; + void *raw[] = { + (void *)module_name, + (void *)version, + (void *)_cffi_exports, + (void *)ctx, + }; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + o_arg = PyLong_FromVoidPtr((void *)raw); + if (o_arg == NULL) + goto failure; + + new_module = PyObject_CallMethod( + module, (char *)"_init_cffi_1_0_external_module", (char *)"O", o_arg); + + Py_DECREF(o_arg); + Py_DECREF(module); + return new_module; + + failure: + Py_XDECREF(module); + return NULL; +} + + +#ifdef HAVE_WCHAR_H +typedef wchar_t _cffi_wchar_t; +#else +typedef uint16_t _cffi_wchar_t; /* same random pick as _cffi_backend.c */ +#endif + +_CFFI_UNUSED_FN static uint16_t _cffi_to_c_char16_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 2) + return (uint16_t)_cffi_to_c_wchar_t(o); + else + return (uint16_t)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char16_t(uint16_t x) +{ + if (sizeof(_cffi_wchar_t) == 2) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +_CFFI_UNUSED_FN static int _cffi_to_c_char32_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 4) + return (int)_cffi_to_c_wchar_t(o); + else + return (int)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char32_t(unsigned int x) +{ + if (sizeof(_cffi_wchar_t) == 4) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +_CFFI_UNUSED_FN static int +_cffi_convert_array_argument(struct _cffi_ctypedescr *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +_CFFI_UNUSED_FN static void +_cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +/********** end CPython-specific section **********/ +#else +_CFFI_UNUSED_FN +static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *); +# define _cffi_call_python _cffi_call_python_org +#endif + + +#define _cffi_array_len(array) (sizeof(array) / sizeof((array)[0])) + +#define _cffi_prim_int(size, sign) \ + ((size) == 1 ? ((sign) ? _CFFI_PRIM_INT8 : _CFFI_PRIM_UINT8) : \ + (size) == 2 ? ((sign) ? _CFFI_PRIM_INT16 : _CFFI_PRIM_UINT16) : \ + (size) == 4 ? ((sign) ? _CFFI_PRIM_INT32 : _CFFI_PRIM_UINT32) : \ + (size) == 8 ? ((sign) ? _CFFI_PRIM_INT64 : _CFFI_PRIM_UINT64) : \ + _CFFI__UNKNOWN_PRIM) + +#define _cffi_prim_float(size) \ + ((size) == sizeof(float) ? _CFFI_PRIM_FLOAT : \ + (size) == sizeof(double) ? _CFFI_PRIM_DOUBLE : \ + (size) == sizeof(long double) ? _CFFI__UNKNOWN_LONG_DOUBLE : \ + _CFFI__UNKNOWN_FLOAT_PRIM) + +#define _cffi_check_int(got, got_nonpos, expected) \ + ((got_nonpos) == (expected <= 0) && \ + (got) == (unsigned long long)expected) + +#ifdef MS_WIN32 +# define _cffi_stdcall __stdcall +#else +# define _cffi_stdcall /* nothing */ +#endif + +#ifdef __cplusplus +} +#endif diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h new file mode 100644 index 0000000..ba6e79b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h @@ -0,0 +1,512 @@ + +/***** Support code for embedding *****/ + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(_WIN32) +# define CFFI_DLLEXPORT __declspec(dllexport) +#elif defined(__GNUC__) +# define CFFI_DLLEXPORT __attribute__((visibility("default"))) +#else +# define CFFI_DLLEXPORT /* nothing */ +#endif + + +/* There are two global variables of type _cffi_call_python_fnptr: + + * _cffi_call_python, which we declare just below, is the one called + by ``extern "Python"`` implementations. + + * _cffi_call_python_org, which on CPython is actually part of the + _cffi_exports[] array, is the function pointer copied from + _cffi_backend. If _cffi_start_python() fails, then this is set + to NULL; otherwise, it should never be NULL. + + After initialization is complete, both are equal. However, the + first one remains equal to &_cffi_start_and_call_python until the + very end of initialization, when we are (or should be) sure that + concurrent threads also see a completely initialized world, and + only then is it changed. +*/ +#undef _cffi_call_python +typedef void (*_cffi_call_python_fnptr)(struct _cffi_externpy_s *, char *); +static void _cffi_start_and_call_python(struct _cffi_externpy_s *, char *); +static _cffi_call_python_fnptr _cffi_call_python = &_cffi_start_and_call_python; + + +#ifndef _MSC_VER + /* --- Assuming a GCC not infinitely old --- */ +# define cffi_compare_and_swap(l,o,n) __sync_bool_compare_and_swap(l,o,n) +# define cffi_write_barrier() __sync_synchronize() +# if !defined(__amd64__) && !defined(__x86_64__) && \ + !defined(__i386__) && !defined(__i386) +# define cffi_read_barrier() __sync_synchronize() +# else +# define cffi_read_barrier() (void)0 +# endif +#else + /* --- Windows threads version --- */ +# include +# define cffi_compare_and_swap(l,o,n) \ + (InterlockedCompareExchangePointer(l,n,o) == (o)) +# define cffi_write_barrier() InterlockedCompareExchange(&_cffi_dummy,0,0) +# define cffi_read_barrier() (void)0 +static volatile LONG _cffi_dummy; +#endif + +#ifdef WITH_THREAD +# ifndef _MSC_VER +# include + static pthread_mutex_t _cffi_embed_startup_lock; +# else + static CRITICAL_SECTION _cffi_embed_startup_lock; +# endif + static char _cffi_embed_startup_lock_ready = 0; +#endif + +static void _cffi_acquire_reentrant_mutex(void) +{ + static void *volatile lock = NULL; + + while (!cffi_compare_and_swap(&lock, NULL, (void *)1)) { + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: pthread_mutex_init() should be very fast, and + this is only run at start-up anyway. */ + } + +#ifdef WITH_THREAD + if (!_cffi_embed_startup_lock_ready) { +# ifndef _MSC_VER + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&_cffi_embed_startup_lock, &attr); +# else + InitializeCriticalSection(&_cffi_embed_startup_lock); +# endif + _cffi_embed_startup_lock_ready = 1; + } +#endif + + while (!cffi_compare_and_swap(&lock, (void *)1, NULL)) + ; + +#ifndef _MSC_VER + pthread_mutex_lock(&_cffi_embed_startup_lock); +#else + EnterCriticalSection(&_cffi_embed_startup_lock); +#endif +} + +static void _cffi_release_reentrant_mutex(void) +{ +#ifndef _MSC_VER + pthread_mutex_unlock(&_cffi_embed_startup_lock); +#else + LeaveCriticalSection(&_cffi_embed_startup_lock); +#endif +} + + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + +#include "_cffi_errors.h" + + +#define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX] + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(void); /* forward */ + +static void _cffi_py_initialize(void) +{ + /* XXX use initsigs=0, which "skips initialization registration of + signal handlers, which might be useful when Python is + embedded" according to the Python docs. But review and think + if it should be a user-controllable setting. + + XXX we should also give a way to write errors to a buffer + instead of to stderr. + + XXX if importing 'site' fails, CPython (any version) calls + exit(). Should we try to work around this behavior here? + */ + Py_InitializeEx(0); +} + +static int _cffi_initialize_python(void) +{ + /* This initializes Python, imports _cffi_backend, and then the + present .dll/.so is set up as a CPython C extension module. + */ + int result; + PyGILState_STATE state; + PyObject *pycode=NULL, *global_dict=NULL, *x; + PyObject *builtins; + + state = PyGILState_Ensure(); + + /* Call the initxxx() function from the present module. It will + create and initialize us as a CPython extension module, instead + of letting the startup Python code do it---it might reimport + the same .dll/.so and get maybe confused on some platforms. + It might also have troubles locating the .dll/.so again for all + I know. + */ + (void)_CFFI_PYTHON_STARTUP_FUNC(); + if (PyErr_Occurred()) + goto error; + + /* Now run the Python code provided to ffi.embedding_init_code(). + */ + pycode = Py_CompileString(_CFFI_PYTHON_STARTUP_CODE, + "", + Py_file_input); + if (pycode == NULL) + goto error; + global_dict = PyDict_New(); + if (global_dict == NULL) + goto error; + builtins = PyEval_GetBuiltins(); + if (builtins == NULL) + goto error; + if (PyDict_SetItemString(global_dict, "__builtins__", builtins) < 0) + goto error; + x = PyEval_EvalCode( + pycode, global_dict, global_dict); + if (x == NULL) + goto error; + Py_DECREF(x); + + /* Done! Now if we've been called from + _cffi_start_and_call_python() in an ``extern "Python"``, we can + only hope that the Python code did correctly set up the + corresponding @ffi.def_extern() function. Otherwise, the + general logic of ``extern "Python"`` functions (inside the + _cffi_backend module) will find that the reference is still + missing and print an error. + */ + result = 0; + done: + Py_XDECREF(pycode); + Py_XDECREF(global_dict); + PyGILState_Release(state); + return result; + + error:; + { + /* Print as much information as potentially useful. + Debugging load-time failures with embedding is not fun + */ + PyObject *ecap; + PyObject *exception, *v, *tb, *f, *modules, *mod; + PyErr_Fetch(&exception, &v, &tb); + ecap = _cffi_start_error_capture(); + f = PySys_GetObject((char *)"stderr"); + if (f != NULL && f != Py_None) { + PyFile_WriteString( + "Failed to initialize the Python-CFFI embedding logic:\n\n", f); + } + + if (exception != NULL) { + PyErr_NormalizeException(&exception, &v, &tb); + PyErr_Display(exception, v, tb); + } + Py_XDECREF(exception); + Py_XDECREF(v); + Py_XDECREF(tb); + + if (f != NULL && f != Py_None) { + PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME + "\ncompiled with cffi version: 2.1.1" + "\n_cffi_backend module: ", f); + modules = PyImport_GetModuleDict(); + mod = PyDict_GetItemString(modules, "_cffi_backend"); + if (mod == NULL) { + PyFile_WriteString("not loaded", f); + } + else { + v = PyObject_GetAttrString(mod, "__file__"); + PyFile_WriteObject(v, f, 0); + Py_XDECREF(v); + } + PyFile_WriteString("\nsys.path: ", f); + PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0); + PyFile_WriteString("\n\n", f); + } + _cffi_stop_error_capture(ecap); + } + result = -1; + goto done; +} + +static int _cffi_carefully_make_gil(void) +{ + /* This does the basic initialization of Python. It can be called + completely concurrently from unrelated threads. It assumes + that we don't hold the GIL before (if it exists), and we don't + hold it afterwards. + + (What it really does used to be completely different in Python 2 + and Python 3, with the Python 2 solution avoiding the spin-lock + around the Py_InitializeEx() call. However, after recent changes + to CPython 2.7 (issue #358) it no longer works. So we use the + Python 3 solution everywhere.) + + This initializes Python by calling Py_InitializeEx(). + Important: this must not be called concurrently at all. + So we use a global variable as a simple spin lock. This global + variable must be from 'libpythonX.Y.so', not from this + cffi-based extension module, because it must be shared from + different cffi-based extension modules. + + In Python < 3.8, we choose + _PyParser_TokenNames[0] as a completely arbitrary pointer value + that is never written to. The default is to point to the + string "ENDMARKER". We change it temporarily to point to the + next character in that string. (Yes, I know it's REALLY + obscure.) + + In Python >= 3.8, this string array is no longer writable, so + instead we pick PyCapsuleType.tp_version_tag. We can't change + Python < 3.8 because someone might use a mixture of cffi + embedded modules, some of which were compiled before this file + changed. + + In Python >= 3.12, this stopped working because that particular + tp_version_tag gets modified during interpreter startup. It's + arguably a bad idea before 3.12 too, but again we can't change + that because someone might use a mixture of cffi embedded + modules, and no-one reported a bug so far. In Python >= 3.12 + we go instead for PyCapsuleType.tp_as_buffer, which is supposed + to always be NULL. We write to it temporarily a pointer to + a struct full of NULLs, which is semantically the same. + */ + +#ifdef WITH_THREAD +# if PY_VERSION_HEX < 0x030C0000 + int volatile *lock = (int volatile *)&PyCapsule_Type.tp_version_tag; + int old_value, locked_value = -42; + assert(!(PyCapsule_Type.tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG)); +# else + static struct ebp_s { PyBufferProcs buf; int mark; } empty_buffer_procs; + empty_buffer_procs.mark = -42; + PyBufferProcs *volatile *lock = (PyBufferProcs *volatile *) + &PyCapsule_Type.tp_as_buffer; + PyBufferProcs *old_value, *locked_value = &empty_buffer_procs.buf; +# endif + + while (1) { /* spin loop */ + old_value = *lock; + if (old_value == 0) { + if (cffi_compare_and_swap(lock, old_value, locked_value)) + break; + } + else { +# if PY_VERSION_HEX < 0x030C0000 + assert(old_value == locked_value); +# else + /* The pointer should point to a possibly different + empty_buffer_procs from another C extension module */ + assert(((struct ebp_s *)old_value)->mark == -42); +# endif + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: PyEval_InitThreads() should be very fast, and + this is only run at start-up anyway. */ + } + } +#endif + + /* call Py_InitializeEx() */ + if (!Py_IsInitialized()) { + _cffi_py_initialize(); + PyEval_SaveThread(); /* release the GIL */ + /* the returned tstate must be the one that has been stored into the + autoTLSkey by _PyGILState_Init() called from Py_Initialize(). */ + } + else { + } + +#ifdef WITH_THREAD + /* release the lock */ + while (!cffi_compare_and_swap(lock, locked_value, old_value)) + ; +#endif + + return 0; +} + +/********** end CPython-specific section **********/ + + +#else + + +/********** PyPy-specific section **********/ + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(const void *[]); /* forward */ + +static struct _cffi_pypy_init_s { + const char *name; + void *func; /* function pointer */ + const char *code; +} _cffi_pypy_init = { + _CFFI_MODULE_NAME, + _CFFI_PYTHON_STARTUP_FUNC, + _CFFI_PYTHON_STARTUP_CODE, +}; + +extern int pypy_carefully_make_gil(const char *); +extern int pypy_init_embedded_cffi_module(int, struct _cffi_pypy_init_s *); + +static int _cffi_carefully_make_gil(void) +{ + return pypy_carefully_make_gil(_CFFI_MODULE_NAME); +} + +static int _cffi_initialize_python(void) +{ + return pypy_init_embedded_cffi_module(0xB011, &_cffi_pypy_init); +} + +/********** end PyPy-specific section **********/ + + +#endif + + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static _cffi_call_python_fnptr _cffi_start_python(void) +{ + /* Delicate logic to initialize Python. This function can be + called multiple times concurrently, e.g. when the process calls + its first ``extern "Python"`` functions in multiple threads at + once. It can also be called recursively, in which case we must + ignore it. We also have to consider what occurs if several + different cffi-based extensions reach this code in parallel + threads---it is a different copy of the code, then, and we + can't have any shared global variable unless it comes from + 'libpythonX.Y.so'. + + Idea: + + * _cffi_carefully_make_gil(): "carefully" call + PyEval_InitThreads() (possibly with Py_InitializeEx() first). + + * then we use a (local) custom lock to make sure that a call to this + cffi-based extension will wait if another call to the *same* + extension is running the initialization in another thread. + It is reentrant, so that a recursive call will not block, but + only one from a different thread. + + * then we grab the GIL and (Python 2) we call Py_InitializeEx(). + At this point, concurrent calls to Py_InitializeEx() are not + possible: we have the GIL. + + * do the rest of the specific initialization, which may + temporarily release the GIL but not the custom lock. + Only release the custom lock when we are done. + */ + static char called = 0; + + if (_cffi_carefully_make_gil() != 0) + return NULL; + + _cffi_acquire_reentrant_mutex(); + + /* Here the GIL exists, but we don't have it. We're only protected + from concurrency by the reentrant mutex. */ + + /* This file only initializes the embedded module once, the first + time this is called, even if there are subinterpreters. */ + if (!called) { + called = 1; /* invoke _cffi_initialize_python() only once, + but don't set '_cffi_call_python' right now, + otherwise concurrent threads won't call + this function at all (we need them to wait) */ + if (_cffi_initialize_python() == 0) { + /* now initialization is finished. Switch to the fast-path. */ + + /* We would like nobody to see the new value of + '_cffi_call_python' without also seeing the rest of the + data initialized. However, this is not possible. But + the new value of '_cffi_call_python' is the function + 'cffi_call_python()' from _cffi_backend. So: */ + cffi_write_barrier(); + /* ^^^ we put a write barrier here, and a corresponding + read barrier at the start of cffi_call_python(). This + ensures that after that read barrier, we see everything + done here before the write barrier. + */ + + assert(_cffi_call_python_org != NULL); + _cffi_call_python = (_cffi_call_python_fnptr)_cffi_call_python_org; + } + else { + /* initialization failed. Reset this to NULL, even if it was + already set to some other value. Future calls to + _cffi_start_python() are still forced to occur, and will + always return NULL from now on. */ + _cffi_call_python_org = NULL; + } + } + + _cffi_release_reentrant_mutex(); + + return (_cffi_call_python_fnptr)_cffi_call_python_org; +} + +static +void _cffi_start_and_call_python(struct _cffi_externpy_s *externpy, char *args) +{ + _cffi_call_python_fnptr fnptr; + int current_err = errno; +#ifdef _MSC_VER + int current_lasterr = GetLastError(); +#endif + fnptr = _cffi_start_python(); + if (fnptr == NULL) { + fprintf(stderr, "function %s() called, but initialization code " + "failed. Returning 0.\n", externpy->name); + memset(args, 0, externpy->size_of_result); + } +#ifdef _MSC_VER + SetLastError(current_lasterr); +#endif + errno = current_err; + + if (fnptr != NULL) + fnptr(externpy, args); +} + + +/* The cffi_start_python() function makes sure Python is initialized + and our cffi module is set up. It can be called manually from the + user C code. The same effect is obtained automatically from any + dll-exported ``extern "Python"`` function. This function returns + -1 if initialization failed, 0 if all is OK. */ +_CFFI_UNUSED_FN +static int cffi_start_python(void) +{ + if (_cffi_call_python == &_cffi_start_and_call_python) { + if (_cffi_start_python() == NULL) + return -1; + } + cffi_read_barrier(); + return 0; +} + +#undef cffi_compare_and_swap +#undef cffi_write_barrier +#undef cffi_read_barrier + +#ifdef __cplusplus +} +#endif diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py new file mode 100644 index 0000000..136abdd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py @@ -0,0 +1,83 @@ + +try: + # this works on Python < 3.12 + from imp import * + +except ImportError: + # this is a limited emulation for Python >= 3.12. + # Note that this is used only for tests or for the old ffi.verify(). + # This is copied from the source code of Python 3.11. + + from _imp import (acquire_lock, release_lock, + is_builtin, is_frozen) + + from importlib._bootstrap import _load + + from importlib import machinery + import os + import sys + import tokenize + + SEARCH_ERROR = 0 + PY_SOURCE = 1 + PY_COMPILED = 2 + C_EXTENSION = 3 + PY_RESOURCE = 4 + PKG_DIRECTORY = 5 + C_BUILTIN = 6 + PY_FROZEN = 7 + PY_CODERESOURCE = 8 + IMP_HOOK = 9 + + def get_suffixes(): + extensions = [(s, 'rb', C_EXTENSION) + for s in machinery.EXTENSION_SUFFIXES] + source = [(s, 'r', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] + bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES] + return extensions + source + bytecode + + def find_module(name, path=None): + if not isinstance(name, str): + raise TypeError("'name' must be a str, not {}".format(type(name))) + elif not isinstance(path, (type(None), list)): + # Backwards-compatibility + raise RuntimeError("'path' must be None or a list, " + "not {}".format(type(path))) + + if path is None: + if is_builtin(name): + return None, None, ('', '', C_BUILTIN) + elif is_frozen(name): + return None, None, ('', '', PY_FROZEN) + else: + path = sys.path + + for entry in path: + package_directory = os.path.join(entry, name) + for suffix in ['.py', machinery.BYTECODE_SUFFIXES[0]]: + package_file_name = '__init__' + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return None, package_directory, ('', '', PKG_DIRECTORY) + for suffix, mode, type_ in get_suffixes(): + file_name = name + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + break + else: + continue + break # Break out of outer loop when breaking out of inner loop. + else: + raise ImportError(name, name=name) + + encoding = None + if 'b' not in mode: + with open(file_path, 'rb') as file: + encoding = tokenize.detect_encoding(file.readline)[0] + file = open(file_path, mode, encoding=encoding) + return file, file_path, (suffix, mode, type_) + + def load_dynamic(name, path, file=None): + loader = machinery.ExtensionFileLoader(name, path) + spec = machinery.ModuleSpec(name=name, loader=loader, origin=path) + return _load(spec) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py new file mode 100644 index 0000000..c3d2312 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py @@ -0,0 +1,45 @@ +""" +Temporary shim module to indirect the bits of distutils we need from setuptools/distutils while providing useful +error messages beyond `No module named 'distutils' on Python >= 3.12, or when setuptools' vendored distutils is broken. + +This is a compromise to avoid a hard-dep on setuptools for Python >= 3.12, since many users don't need runtime compilation support from CFFI. +""" +import sys + +try: + # import setuptools first; this is the most robust way to ensure its embedded distutils is available + # (the .pth shim should usually work, but this is even more robust) + import setuptools +except Exception as ex: + if sys.version_info >= (3, 12): + # Python 3.12 has no built-in distutils to fall back on, so any import problem is fatal + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. The setuptools module is missing or non-functional.") from ex + + # silently ignore on older Pythons (support fallback to stdlib distutils where available) +else: + del setuptools + +try: + # bring in just the bits of distutils we need, whether they really came from setuptools or stdlib-embedded distutils + from distutils import log, sysconfig + from distutils.ccompiler import CCompiler + from distutils.command.build_ext import build_ext + from distutils.core import Distribution, Extension + from distutils.dir_util import mkpath + from distutils.errors import DistutilsSetupError, CompileError, LinkError + from distutils.log import set_threshold, set_verbosity + + if sys.platform == 'win32': + try: + # FUTURE: msvc9compiler module was removed in setuptools 74; consider removing, as it's only used by an ancient patch in `recompiler` + from distutils.msvc9compiler import MSVCCompiler + except ImportError: + MSVCCompiler = None +except Exception as ex: + if sys.version_info >= (3, 12): + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. Please install the setuptools package.") from ex + + # anything older, just let the underlying distutils import error fly + raise Exception("This CFFI feature requires distutils. Please install the distutils or setuptools package.") from ex + +del sys diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py new file mode 100644 index 0000000..ec8ee7d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/api.py @@ -0,0 +1,960 @@ +import sys, types +from .lock import allocate_lock +from .error import CDefError +from . import model + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +_unspecified = object() + + + +class FFI: + r''' + The main top-level class that you instantiate once, or once per module. + + Example usage: + + ffi = FFI() + ffi.cdef(""" + int printf(const char *, ...); + """) + + C = ffi.dlopen(None) # standard library + -or- + C = ffi.verify() # use a C compiler: verify the decl above is right + + C.printf("hello, %s!\n", ffi.new("char[]", "world")) + ''' + + def __init__(self, backend=None): + """Create an FFI instance. The 'backend' argument is used to + select a non-default backend, mostly for tests. + """ + if backend is None: + # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with + # _cffi_backend.so compiled. + import _cffi_backend as backend + from . import __version__ + if backend.__version__ != __version__: + # bad version! Try to be as explicit as possible. + if hasattr(backend, '__file__'): + # CPython + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. When we import the top-level '_cffi_backend' extension module, we get version %s, located in %r. The two versions should be equal; check your installation." % ( + __version__, __file__, + backend.__version__, backend.__file__)) + else: + # PyPy + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. This interpreter comes with a built-in '_cffi_backend' module, which is version %s. The two versions should be equal; check your installation." % ( + __version__, __file__, backend.__version__)) + # (If you insist you can also try to pass the option + # 'backend=backend_ctypes.CTypesBackend()', but don't + # rely on it! It's probably not going to work well.) + + from . import cparser + self._backend = backend + self._lock = allocate_lock() + self._parser = cparser.Parser() + self._cached_btypes = {} + self._parsed_types = types.ModuleType('parsed_types').__dict__ + self._new_types = types.ModuleType('new_types').__dict__ + self._function_caches = [] + self._libraries = [] + self._cdefsources = [] + self._included_ffis = [] + self._windows_unicode = None + self._init_once_cache = {} + self._cdef_version = None + self._embedding = None + self._typecache = model.get_typecache(backend) + if hasattr(backend, 'set_ffi'): + backend.set_ffi(self) + for name in list(backend.__dict__): + if name.startswith('RTLD_'): + setattr(self, name, getattr(backend, name)) + # + with self._lock: + self.BVoidP = self._get_cached_btype(model.voidp_type) + self.BCharA = self._get_cached_btype(model.char_array_type) + if isinstance(backend, types.ModuleType): + # _cffi_backend: attach these constants to the class + if not hasattr(FFI, 'NULL'): + FFI.NULL = self.cast(self.BVoidP, 0) + FFI.CData, FFI.CType = backend._get_types() + else: + # ctypes backend: attach these constants to the instance + self.NULL = self.cast(self.BVoidP, 0) + self.CData, self.CType = backend._get_types() + self.buffer = backend.buffer + + def cdef(self, csource, override=False, packed=False, pack=None): + """Parse the given C source. This registers all declared functions, + types, and global variables. The functions and global variables can + then be accessed via either 'ffi.dlopen()' or 'ffi.verify()'. + The types can be used in 'ffi.new()' and other functions. + If 'packed' is specified as True, all structs declared inside this + cdef are packed, i.e. laid out without any field alignment at all. + Alternatively, 'pack' can be a small integer, and requests for + alignment greater than that are ignored (pack=1 is equivalent to + packed=True). + """ + self._cdef(csource, override=override, packed=packed, pack=pack) + + def embedding_api(self, csource, packed=False, pack=None): + self._cdef(csource, packed=packed, pack=pack, dllexport=True) + if self._embedding is None: + self._embedding = '' + + def _cdef(self, csource, override=False, **options): + if not isinstance(csource, str): # unicode, on Python 2 + if not isinstance(csource, basestring): + raise TypeError("cdef() argument must be a string") + csource = csource.encode('ascii') + with self._lock: + self._cdef_version = object() + self._parser.parse(csource, override=override, **options) + self._cdefsources.append(csource) + if override: + for cache in self._function_caches: + cache.clear() + finishlist = self._parser._recomplete + if finishlist: + self._parser._recomplete = [] + for tp in finishlist: + tp.finish_backend_type(self, finishlist) + + def dlopen(self, name, flags=0): + """Load and return a dynamic library identified by 'name'. + The standard C library can be loaded by passing None. + Note that functions and types declared by 'ffi.cdef()' are not + linked to a particular library, just like C headers; in the + library we only look for the actual (untyped) symbols. + """ + if not (isinstance(name, basestring) or + name is None or + isinstance(name, self.CData)): + raise TypeError("dlopen(name): name must be a file name, None, " + "or an already-opened 'void *' handle") + with self._lock: + lib, function_cache = _make_ffi_library(self, name, flags) + self._function_caches.append(function_cache) + self._libraries.append(lib) + return lib + + def dlclose(self, lib): + """Close a library obtained with ffi.dlopen(). After this call, + access to functions or variables from the library will fail + (possibly with a segmentation fault). + """ + type(lib).__cffi_close__(lib) + + def _typeof_locked(self, cdecl): + # call me with the lock! + key = cdecl + if key in self._parsed_types: + return self._parsed_types[key] + # + if not isinstance(cdecl, str): # unicode, on Python 2 + cdecl = cdecl.encode('ascii') + # + type = self._parser.parse_type(cdecl) + really_a_function_type = type.is_raw_function + if really_a_function_type: + type = type.as_function_pointer() + btype = self._get_cached_btype(type) + result = btype, really_a_function_type + self._parsed_types[key] = result + return result + + def _typeof(self, cdecl, consider_function_as_funcptr=False): + # string -> ctype object + try: + result = self._parsed_types[cdecl] + except KeyError: + with self._lock: + result = self._typeof_locked(cdecl) + # + btype, really_a_function_type = result + if really_a_function_type and not consider_function_as_funcptr: + raise CDefError("the type %r is a function type, not a " + "pointer-to-function type" % (cdecl,)) + return btype + + def typeof(self, cdecl): + """Parse the C type given as a string and return the + corresponding object. + It can also be used on 'cdata' instance to get its C type. + """ + if isinstance(cdecl, basestring): + return self._typeof(cdecl) + if isinstance(cdecl, self.CData): + return self._backend.typeof(cdecl) + if isinstance(cdecl, types.BuiltinFunctionType): + res = _builtin_function_type(cdecl) + if res is not None: + return res + if (isinstance(cdecl, types.FunctionType) + and hasattr(cdecl, '_cffi_base_type')): + with self._lock: + return self._get_cached_btype(cdecl._cffi_base_type) + raise TypeError(type(cdecl)) + + def sizeof(self, cdecl): + """Return the size in bytes of the argument. It can be a + string naming a C type, or a 'cdata' instance. + """ + if isinstance(cdecl, basestring): + BType = self._typeof(cdecl) + return self._backend.sizeof(BType) + else: + return self._backend.sizeof(cdecl) + + def alignof(self, cdecl): + """Return the natural alignment size in bytes of the C type + given as a string. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.alignof(cdecl) + + def offsetof(self, cdecl, *fields_or_indexes): + """Return the offset of the named field inside the given + structure or array, which must be given as a C type name. + You can give several field names in case of nested structures. + You can also give numeric values which correspond to array + items, in case of an array type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._typeoffsetof(cdecl, *fields_or_indexes)[1] + + def new(self, cdecl, init=None): + """Allocate an instance according to the specified C type and + return a pointer to it. The specified C type must be either a + pointer or an array: ``new('X *')`` allocates an X and returns + a pointer to it, whereas ``new('X[n]')`` allocates an array of + n X'es and returns an array referencing it (which works + mostly like a pointer, like in C). You can also use + ``new('X[]', n)`` to allocate an array of a non-constant + length n. + + The memory is initialized following the rules of declaring a + global variable in C: by default it is zero-initialized, but + an explicit initializer can be given which can be used to + fill all or part of the memory. + + When the returned object goes out of scope, the memory + is freed. In other words the returned object has + ownership of the value of type 'cdecl' that it points to. This + means that the raw data can be used as long as this object is + kept alive, but must not be used for a longer time. Be careful + about that when copying the pointer to the memory somewhere + else, e.g. into another structure. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.newp(cdecl, init) + + def new_allocator(self, alloc=None, free=None, + should_clear_after_alloc=True): + """Return a new allocator, i.e. a function that behaves like ffi.new() + but uses the provided low-level 'alloc' and 'free' functions. + + 'alloc' is called with the size as argument. If it returns NULL, a + MemoryError is raised. 'free' is called with the result of 'alloc' + as argument. Both can be either Python function or directly C + functions. If 'free' is None, then no free function is called. + If both 'alloc' and 'free' are None, the default is used. + + If 'should_clear_after_alloc' is set to False, then the memory + returned by 'alloc' is assumed to be already cleared (or you are + fine with garbage); otherwise CFFI will clear it. + """ + compiled_ffi = self._backend.FFI() + allocator = compiled_ffi.new_allocator(alloc, free, + should_clear_after_alloc) + def allocate(cdecl, init=None): + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return allocator(cdecl, init) + return allocate + + def cast(self, cdecl, source): + """Similar to a C cast: returns an instance of the named C + type initialized with the given 'source'. The source is + casted between integers or pointers of any type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.cast(cdecl, source) + + def string(self, cdata, maxlen=-1): + """Return a Python string (or unicode string) from the 'cdata'. + If 'cdata' is a pointer or array of characters or bytes, returns + the null-terminated string. The returned string extends until + the first null character, or at most 'maxlen' characters. If + 'cdata' is an array then 'maxlen' defaults to its length. + + If 'cdata' is a pointer or array of wchar_t, returns a unicode + string following the same rules. + + If 'cdata' is a single character or byte or a wchar_t, returns + it as a string or unicode string. + + If 'cdata' is an enum, returns the value of the enumerator as a + string, or 'NUMBER' if the value is out of range. + """ + return self._backend.string(cdata, maxlen) + + def unpack(self, cdata, length): + """Unpack an array of C data of the given length, + returning a Python string/unicode/list. + + If 'cdata' is a pointer to 'char', returns a byte string. + It does not stop at the first null. This is equivalent to: + ffi.buffer(cdata, length)[:] + + If 'cdata' is a pointer to 'wchar_t', returns a unicode string. + 'length' is measured in wchar_t's; it is not the size in bytes. + + If 'cdata' is a pointer to anything else, returns a list of + 'length' items. This is a faster equivalent to: + [cdata[i] for i in range(length)] + """ + return self._backend.unpack(cdata, length) + + #def buffer(self, cdata, size=-1): + # """Return a read-write buffer object that references the raw C data + # pointed to by the given 'cdata'. The 'cdata' must be a pointer or + # an array. Can be passed to functions expecting a buffer, or directly + # manipulated with: + # + # buf[:] get a copy of it in a regular string, or + # buf[idx] as a single character + # buf[:] = ... + # buf[idx] = ... change the content + # """ + # note that 'buffer' is a type, set on this instance by __init__ + + def from_buffer(self, cdecl, python_buffer=_unspecified, + require_writable=False): + """Return a cdata of the given type pointing to the data of the + given Python object, which must support the buffer interface. + Note that this is not meant to be used on the built-in types + str or unicode (you can build 'char[]' arrays explicitly) + but only on objects containing large quantities of raw data + in some other format, like 'array.array' or numpy arrays. + + The first argument is optional and default to 'char[]'. + """ + if python_buffer is _unspecified: + cdecl, python_buffer = self.BCharA, cdecl + elif isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.from_buffer(cdecl, python_buffer, + require_writable) + + def memmove(self, dest, src, n): + """ffi.memmove(dest, src, n) copies n bytes of memory from src to dest. + + Like the C function memmove(), the memory areas may overlap; + apart from that it behaves like the C function memcpy(). + + 'src' can be any cdata ptr or array, or any Python buffer object. + 'dest' can be any cdata ptr or array, or a writable Python buffer + object. The size to copy, 'n', is always measured in bytes. + + Unlike other methods, this one supports all Python buffer including + byte strings and bytearrays---but it still does not support + non-contiguous buffers. + """ + return self._backend.memmove(dest, src, n) + + def callback(self, cdecl, python_callable=None, error=None, onerror=None): + """Return a callback object or a decorator making such a + callback object. 'cdecl' must name a C function pointer type. + The callback invokes the specified 'python_callable' (which may + be provided either directly or via a decorator). Important: the + callback object must be manually kept alive for as long as the + callback may be invoked from the C level. + """ + def callback_decorator_wrap(python_callable): + if not callable(python_callable): + raise TypeError("the 'python_callable' argument " + "is not callable") + return self._backend.callback(cdecl, python_callable, + error, onerror) + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl, consider_function_as_funcptr=True) + if python_callable is None: + return callback_decorator_wrap # decorator mode + else: + return callback_decorator_wrap(python_callable) # direct mode + + def getctype(self, cdecl, replace_with=''): + """Return a string giving the C type 'cdecl', which may be itself + a string or a object. If 'replace_with' is given, it gives + extra text to append (or insert for more complicated C types), like + a variable name, or '*' to get actually the C type 'pointer-to-cdecl'. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + replace_with = replace_with.strip() + if (replace_with.startswith('*') + and '&[' in self._backend.getcname(cdecl, '&')): + replace_with = '(%s)' % replace_with + elif replace_with and replace_with[0] not in '[(': + replace_with = ' ' + replace_with + return self._backend.getcname(cdecl, replace_with) + + def gc(self, cdata, destructor, size=0): + """Return a new cdata object that points to the same + data. Later, when this new cdata object is garbage-collected, + 'destructor(old_cdata_object)' will be called. + + The optional 'size' gives an estimate of the size, used to + trigger the garbage collection more eagerly. So far only used + on PyPy. It tells the GC that the returned object keeps alive + roughly 'size' bytes of external memory. + """ + return self._backend.gcp(cdata, destructor, size) + + def _get_cached_btype(self, type): + assert self._lock.acquire(False) is False + # call me with the lock! + try: + BType = self._cached_btypes[type] + except KeyError: + finishlist = [] + BType = type.get_cached_btype(self, finishlist) + for type in finishlist: + type.finish_backend_type(self, finishlist) + return BType + + def verify(self, source='', tmpdir=None, **kwargs): + """Verify that the current ffi signatures compile on this + machine, and return a dynamic library object. The dynamic + library can be used to call functions and access global + variables declared in this 'ffi'. The library is compiled + by the C compiler: it gives you C-level API compatibility + (including calling macros). This is unlike 'ffi.dlopen()', + which requires binary compatibility in the signatures. + """ + from .verifier import Verifier, _caller_dir_pycache + # + # If set_unicode(True) was called, insert the UNICODE and + # _UNICODE macro declarations + if self._windows_unicode: + self._apply_windows_unicode(kwargs) + # + # Set the tmpdir here, and not in Verifier.__init__: it picks + # up the caller's directory, which we want to be the caller of + # ffi.verify(), as opposed to the caller of Veritier(). + tmpdir = tmpdir or _caller_dir_pycache() + # + # Make a Verifier() and use it to load the library. + self.verifier = Verifier(self, source, tmpdir, **kwargs) + lib = self.verifier.load_library() + # + # Save the loaded library for keep-alive purposes, even + # if the caller doesn't keep it alive itself (it should). + self._libraries.append(lib) + return lib + + def _get_errno(self): + return self._backend.get_errno() + def _set_errno(self, errno): + self._backend.set_errno(errno) + errno = property(_get_errno, _set_errno, None, + "the value of 'errno' from/to the C calls") + + def getwinerror(self, code=-1): + return self._backend.getwinerror(code) + + def _pointer_to(self, ctype): + with self._lock: + return model.pointer_cache(self, ctype) + + def addressof(self, cdata, *fields_or_indexes): + """Return the address of a . + If 'fields_or_indexes' are given, returns the address of that + field or array item in the structure or array, recursively in + case of nested structures. + """ + try: + ctype = self._backend.typeof(cdata) + except TypeError: + if '__addressof__' in type(cdata).__dict__: + return type(cdata).__addressof__(cdata, *fields_or_indexes) + raise + if fields_or_indexes: + ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes) + else: + if ctype.kind == "pointer": + raise TypeError("addressof(pointer)") + offset = 0 + ctypeptr = self._pointer_to(ctype) + return self._backend.rawaddressof(ctypeptr, cdata, offset) + + def _typeoffsetof(self, ctype, field_or_index, *fields_or_indexes): + ctype, offset = self._backend.typeoffsetof(ctype, field_or_index) + for field1 in fields_or_indexes: + ctype, offset1 = self._backend.typeoffsetof(ctype, field1, 1) + offset += offset1 + return ctype, offset + + def include(self, ffi_to_include): + """Includes the typedefs, structs, unions and enums defined + in another FFI instance. Usage is similar to a #include in C, + where a part of the program might include types defined in + another part for its own usage. Note that the include() + method has no effect on functions, constants and global + variables, which must anyway be accessed directly from the + lib object returned by the original FFI instance. + """ + if not isinstance(ffi_to_include, FFI): + raise TypeError("ffi.include() expects an argument that is also of" + " type cffi.FFI, not %r" % ( + type(ffi_to_include).__name__,)) + if ffi_to_include is self: + raise ValueError("self.include(self)") + with ffi_to_include._lock: + with self._lock: + self._parser.include(ffi_to_include._parser) + self._cdefsources.append('[') + self._cdefsources.extend(ffi_to_include._cdefsources) + self._cdefsources.append(']') + self._included_ffis.append(ffi_to_include) + + def new_handle(self, x): + return self._backend.newp_handle(self.BVoidP, x) + + def from_handle(self, x): + return self._backend.from_handle(x) + + def release(self, x): + self._backend.release(x) + + def set_unicode(self, enabled_flag): + """Windows: if 'enabled_flag' is True, enable the UNICODE and + _UNICODE defines in C, and declare the types like TCHAR and LPTCSTR + to be (pointers to) wchar_t. If 'enabled_flag' is False, + declare these types to be (pointers to) plain 8-bit characters. + This is mostly for backward compatibility; you usually want True. + """ + if self._windows_unicode is not None: + raise ValueError("set_unicode() can only be called once") + enabled_flag = bool(enabled_flag) + if enabled_flag: + self.cdef("typedef wchar_t TBYTE;" + "typedef wchar_t TCHAR;" + "typedef const wchar_t *LPCTSTR;" + "typedef const wchar_t *PCTSTR;" + "typedef wchar_t *LPTSTR;" + "typedef wchar_t *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + else: + self.cdef("typedef char TBYTE;" + "typedef char TCHAR;" + "typedef const char *LPCTSTR;" + "typedef const char *PCTSTR;" + "typedef char *LPTSTR;" + "typedef char *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + self._windows_unicode = enabled_flag + + def _apply_windows_unicode(self, kwds): + defmacros = kwds.get('define_macros', ()) + if not isinstance(defmacros, (list, tuple)): + raise TypeError("'define_macros' must be a list or tuple") + defmacros = list(defmacros) + [('UNICODE', '1'), + ('_UNICODE', '1')] + kwds['define_macros'] = defmacros + + def _apply_embedding_fix(self, kwds): + # must include an argument like "-lpython2.7" for the compiler + def ensure(key, value): + lst = kwds.setdefault(key, []) + if value not in lst: + lst.append(value) + # + if '__pypy__' in sys.builtin_module_names: + import os + if sys.platform == "win32": + # we need 'libpypy-c.lib'. Current distributions of + # pypy (>= 4.1) contain it as 'libs/python27.lib'. + pythonlib = "python{0[0]}{0[1]}".format(sys.version_info) + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'libs')) + else: + # we need 'libpypy-c.{so,dylib}', which should be by + # default located in 'sys.prefix/bin' for installed + # systems. + if sys.version_info < (3,): + pythonlib = "pypy-c" + else: + pythonlib = "pypy3-c" + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'bin')) + # On uninstalled pypy's, the libpypy-c is typically found in + # .../pypy/goal/. + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'pypy', 'goal')) + else: + if sys.platform == "win32": + template = "python%d%d" + if hasattr(sys, 'gettotalrefcount'): + template += '_d' + else: + try: + import sysconfig + except ImportError: # 2.6 + from cffi._shimmed_dist_utils import sysconfig + template = "python%d.%d" + if sysconfig.get_config_var('DEBUG_EXT'): + template += sysconfig.get_config_var('DEBUG_EXT') + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + if hasattr(sys, 'abiflags'): + pythonlib += sys.abiflags + ensure('libraries', pythonlib) + if sys.platform == "win32": + ensure('extra_link_args', '/MANIFEST') + + def set_source(self, module_name, source, source_extension='.c', **kwds): + import os + if hasattr(self, '_assigned_source'): + raise ValueError("set_source() cannot be called several times " + "per ffi object") + if not isinstance(module_name, basestring): + raise TypeError("'module_name' must be a string") + if os.sep in module_name or (os.altsep and os.altsep in module_name): + raise ValueError("'module_name' must not contain '/': use a dotted " + "name to make a 'package.module' location") + self._assigned_source = (str(module_name), source, + source_extension, kwds) + + def set_source_pkgconfig(self, module_name, pkgconfig_libs, source, + source_extension='.c', **kwds): + from . import pkgconfig + if not isinstance(pkgconfig_libs, list): + raise TypeError("the pkgconfig_libs argument must be a list " + "of package names") + kwds2 = pkgconfig.flags_from_pkgconfig(pkgconfig_libs) + pkgconfig.merge_flags(kwds, kwds2) + self.set_source(module_name, source, source_extension, **kwds) + + def distutils_extension(self, tmpdir='build', verbose=True): + from cffi._shimmed_dist_utils import mkpath + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + if hasattr(self, 'verifier'): # fallback, 'tmpdir' ignored + return self.verifier.get_extension() + raise ValueError("set_source() must be called before" + " distutils_extension()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("distutils_extension() is only for C extension " + "modules, not for dlopen()-style pure Python " + "modules") + mkpath(tmpdir) + ext, updated = recompile(self, module_name, + source, tmpdir=tmpdir, extradir=tmpdir, + source_extension=source_extension, + call_c_compiler=False, **kwds) + if verbose: + if updated: + sys.stderr.write("regenerated: %r\n" % (ext.sources[0],)) + else: + sys.stderr.write("not modified: %r\n" % (ext.sources[0],)) + return ext + + def emit_c_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("emit_c_code() is only for C extension modules, " + "not for dlopen()-style pure Python modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def emit_python_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is not None: + raise TypeError("emit_python_code() is only for dlopen()-style " + "pure Python modules, not for C extension modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def compile(self, tmpdir='.', verbose=0, target=None, debug=None): + """The 'target' argument gives the final file name of the + compiled DLL. Use '*' to force distutils' choice, suitable for + regular CPython C API modules. Use a file name ending in '.*' + to ask for the system's default extension for dynamic libraries + (.so/.dll/.dylib). + + The default is '*' when building a non-embedded C API extension, + and (module_name + '.*') when building an embedded library. + """ + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before compile()") + module_name, source, source_extension, kwds = self._assigned_source + return recompile(self, module_name, source, tmpdir=tmpdir, + target=target, source_extension=source_extension, + compiler_verbose=verbose, debug=debug, **kwds) + + def init_once(self, func, tag): + # Read _init_once_cache[tag], which is either (False, lock) if + # we're calling the function now in some thread, or (True, result). + # Don't call setdefault() in most cases, to avoid allocating and + # immediately freeing a lock; but still use setdefaut() to avoid + # races. + try: + x = self._init_once_cache[tag] + except KeyError: + x = self._init_once_cache.setdefault(tag, (False, allocate_lock())) + # Common case: we got (True, result), so we return the result. + if x[0]: + return x[1] + # Else, it's a lock. Acquire it to serialize the following tests. + with x[1]: + # Read again from _init_once_cache the current status. + x = self._init_once_cache[tag] + if x[0]: + return x[1] + # Call the function and store the result back. + result = func() + self._init_once_cache[tag] = (True, result) + return result + + def embedding_init_code(self, pysource): + if self._embedding: + raise ValueError("embedding_init_code() can only be called once") + # fix 'pysource' before it gets dumped into the C file: + # - remove empty lines at the beginning, so it starts at "line 1" + # - dedent, if all non-empty lines are indented + # - check for SyntaxErrors + import re + match = re.match(r'\s*\n', pysource) + if match: + pysource = pysource[match.end():] + lines = pysource.splitlines() or [''] + prefix = re.match(r'\s*', lines[0]).group() + for i in range(1, len(lines)): + line = lines[i] + if line.rstrip(): + while not line.startswith(prefix): + prefix = prefix[:-1] + i = len(prefix) + lines = [line[i:]+'\n' for line in lines] + pysource = ''.join(lines) + # + compile(pysource, "cffi_init", "exec") + # + self._embedding = pysource + + def def_extern(self, *args, **kwds): + raise ValueError("ffi.def_extern() is only available on API-mode FFI " + "objects") + + def list_types(self): + """Returns the user type names known to this FFI instance. + This returns a tuple containing three lists of names: + (typedef_names, names_of_structs, names_of_unions) + """ + typedefs = [] + structs = [] + unions = [] + for key in self._parser._declarations: + if key.startswith('typedef '): + typedefs.append(key[8:]) + elif key.startswith('struct '): + structs.append(key[7:]) + elif key.startswith('union '): + unions.append(key[6:]) + typedefs.sort() + structs.sort() + unions.sort() + return (typedefs, structs, unions) + + +def _load_backend_lib(backend, name, flags): + import os + if not isinstance(name, basestring): + if sys.platform != "win32" or name is not None: + return backend.load_library(name, flags) + name = "c" # Windows: load_library(None) fails, but this works + # on Python 2 (backward compatibility hack only) + first_error = None + if '.' in name or '/' in name or os.sep in name: + try: + return backend.load_library(name, flags) + except OSError as e: + first_error = e + import ctypes.util + path = ctypes.util.find_library(name) + if path is None: + if name == "c" and sys.platform == "win32" and sys.version_info >= (3,): + raise OSError("dlopen(None) cannot work on Windows for Python 3 " + "(see http://bugs.python.org/issue23606)") + msg = ("ctypes.util.find_library() did not manage " + "to locate a library called %r" % (name,)) + if first_error is not None: + msg = "%s. Additionally, %s" % (first_error, msg) + raise OSError(msg) + return backend.load_library(path, flags) + +def _make_ffi_library(ffi, libname, flags): + backend = ffi._backend + backendlib = _load_backend_lib(backend, libname, flags) + # + def accessor_function(name): + key = 'function ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + value = backendlib.load_function(BType, name) + library.__dict__[name] = value + # + def accessor_variable(name): + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + read_variable = backendlib.read_variable + write_variable = backendlib.write_variable + setattr(FFILibrary, name, property( + lambda self: read_variable(BType, name), + lambda self, value: write_variable(BType, name, value))) + # + def addressof_var(name): + try: + return addr_variables[name] + except KeyError: + with ffi._lock: + if name not in addr_variables: + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + if BType.kind != 'array': + BType = model.pointer_cache(ffi, BType) + p = backendlib.load_function(BType, name) + addr_variables[name] = p + return addr_variables[name] + # + def accessor_constant(name): + raise NotImplementedError("non-integer constant '%s' cannot be " + "accessed from a dlopen() library" % (name,)) + # + def accessor_int_constant(name): + library.__dict__[name] = ffi._parser._int_constants[name] + # + accessors = {} + accessors_version = [False] + addr_variables = {} + # + def update_accessors(): + if accessors_version[0] is ffi._cdef_version: + return + # + for key, (tp, _) in ffi._parser._declarations.items(): + if not isinstance(tp, model.EnumType): + tag, name = key.split(' ', 1) + if tag == 'function': + accessors[name] = accessor_function + elif tag == 'variable': + accessors[name] = accessor_variable + elif tag == 'constant': + accessors[name] = accessor_constant + else: + for i, enumname in enumerate(tp.enumerators): + def accessor_enum(name, tp=tp, i=i): + tp.check_not_partial() + library.__dict__[name] = tp.enumvalues[i] + accessors[enumname] = accessor_enum + for name in ffi._parser._int_constants: + accessors.setdefault(name, accessor_int_constant) + accessors_version[0] = ffi._cdef_version + # + def make_accessor(name): + with ffi._lock: + if name in library.__dict__ or name in FFILibrary.__dict__: + return # added by another thread while waiting for the lock + if name not in accessors: + update_accessors() + if name not in accessors: + raise AttributeError(name) + accessors[name](name) + # + class FFILibrary: + def __getattr__(self, name): + make_accessor(name) + return getattr(self, name) + def __setattr__(self, name, value): + try: + property = getattr(self.__class__, name) + except AttributeError: + make_accessor(name) + setattr(self, name, value) + else: + property.__set__(self, value) + def __dir__(self): + with ffi._lock: + update_accessors() + return accessors.keys() + def __addressof__(self, name): + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + make_accessor(name) + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + raise AttributeError("cffi library has no function or " + "global variable named '%s'" % (name,)) + def __cffi_close__(self): + backendlib.close_lib() + self.__dict__.clear() + # + if isinstance(libname, basestring): + try: + if not isinstance(libname, str): # unicode, on Python 2 + libname = libname.encode('utf-8') + FFILibrary.__name__ = 'FFILibrary_%s' % libname + except UnicodeError: + pass + library = FFILibrary() + return library, library.__dict__ + +def _builtin_function_type(func): + # a hack to make at least ffi.typeof(builtin_function) work, + # if the builtin function was obtained by 'vengine_cpy'. + import sys + try: + module = sys.modules[func.__module__] + ffi = module._cffi_original_ffi + types_of_builtin_funcs = module._cffi_types_of_builtin_funcs + tp = types_of_builtin_funcs[func] + except (KeyError, AttributeError, TypeError): + return None + else: + with ffi._lock: + return ffi._get_cached_btype(tp) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py new file mode 100644 index 0000000..12cd162 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py @@ -0,0 +1,1120 @@ +import ctypes, ctypes.util, operator, sys +from . import model + +if sys.version_info < (3,): + bytechr = chr +else: + unicode = str + long = int + xrange = range + bytechr = lambda num: bytes([num]) + +class CTypesType(type): + pass + +class CTypesData: + __metaclass__ = CTypesType + __slots__ = ['__weakref__'] + __name__ = '' + + def __init__(self, *args): + raise TypeError("cannot instantiate %r" % (self.__class__,)) + + @classmethod + def _newp(cls, init): + raise TypeError("expected a pointer or array ctype, got '%s'" + % (cls._get_c_name(),)) + + @staticmethod + def _to_ctypes(value): + raise TypeError + + @classmethod + def _arg_to_ctypes(cls, *value): + try: + ctype = cls._ctype + except AttributeError: + raise TypeError("cannot create an instance of %r" % (cls,)) + if value: + res = cls._to_ctypes(*value) + if not isinstance(res, ctype): + res = cls._ctype(res) + else: + res = cls._ctype() + return res + + @classmethod + def _create_ctype_obj(cls, init): + if init is None: + return cls._arg_to_ctypes() + else: + return cls._arg_to_ctypes(init) + + @staticmethod + def _from_ctypes(ctypes_value): + raise TypeError + + @classmethod + def _get_c_name(cls, replace_with=''): + return cls._reftypename.replace(' &', replace_with) + + @classmethod + def _fix_class(cls): + cls.__name__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__qualname__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__module__ = 'ffi' + + def _get_own_repr(self): + raise NotImplementedError + + def _addr_repr(self, address): + if address == 0: + return 'NULL' + else: + if address < 0: + address += 1 << (8*ctypes.sizeof(ctypes.c_void_p)) + return '0x%x' % address + + def __repr__(self, c_name=None): + own = self._get_own_repr() + return '' % (c_name or self._get_c_name(), own) + + def _convert_to_address(self, BClass): + if BClass is None: + raise TypeError("cannot convert %r to an address" % ( + self._get_c_name(),)) + else: + raise TypeError("cannot convert %r to %r" % ( + self._get_c_name(), BClass._get_c_name())) + + @classmethod + def _get_size(cls): + return ctypes.sizeof(cls._ctype) + + def _get_size_of_instance(self): + return ctypes.sizeof(self._ctype) + + @classmethod + def _cast_from(cls, source): + raise TypeError("cannot cast to %r" % (cls._get_c_name(),)) + + def _cast_to_integer(self): + return self._convert_to_address(None) + + @classmethod + def _alignment(cls): + return ctypes.alignment(cls._ctype) + + def __iter__(self): + raise TypeError("cdata %r does not support iteration" % ( + self._get_c_name()),) + + def _make_cmp(name): + cmpfunc = getattr(operator, name) + def cmp(self, other): + v_is_ptr = not isinstance(self, CTypesGenericPrimitive) + w_is_ptr = (isinstance(other, CTypesData) and + not isinstance(other, CTypesGenericPrimitive)) + if v_is_ptr and w_is_ptr: + return cmpfunc(self._convert_to_address(None), + other._convert_to_address(None)) + elif v_is_ptr or w_is_ptr: + return NotImplemented + else: + if isinstance(self, CTypesGenericPrimitive): + self = self._value + if isinstance(other, CTypesGenericPrimitive): + other = other._value + return cmpfunc(self, other) + cmp.func_name = name + return cmp + + __eq__ = _make_cmp('__eq__') + __ne__ = _make_cmp('__ne__') + __lt__ = _make_cmp('__lt__') + __le__ = _make_cmp('__le__') + __gt__ = _make_cmp('__gt__') + __ge__ = _make_cmp('__ge__') + + def __hash__(self): + return hash(self._convert_to_address(None)) + + def _to_string(self, maxlen): + raise TypeError("string(): %r" % (self,)) + + +class CTypesGenericPrimitive(CTypesData): + __slots__ = [] + + def __hash__(self): + return hash(self._value) + + def _get_own_repr(self): + return repr(self._from_ctypes(self._value)) + + +class CTypesGenericArray(CTypesData): + __slots__ = [] + + @classmethod + def _newp(cls, init): + return cls(init) + + def __iter__(self): + for i in xrange(len(self)): + yield self[i] + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + +class CTypesGenericPtr(CTypesData): + __slots__ = ['_address', '_as_ctype_ptr'] + _automatic_casts = False + kind = "pointer" + + @classmethod + def _newp(cls, init): + return cls(init) + + @classmethod + def _cast_from(cls, source): + if source is None: + address = 0 + elif isinstance(source, CTypesData): + address = source._cast_to_integer() + elif isinstance(source, (int, long)): + address = source + else: + raise TypeError("bad type for cast to %r: %r" % + (cls, type(source).__name__)) + return cls._new_pointer_at(address) + + @classmethod + def _new_pointer_at(cls, address): + self = cls.__new__(cls) + self._address = address + self._as_ctype_ptr = ctypes.cast(address, cls._ctype) + return self + + def _get_own_repr(self): + try: + return self._addr_repr(self._address) + except AttributeError: + return '???' + + def _cast_to_integer(self): + return self._address + + def __nonzero__(self): + return bool(self._address) + __bool__ = __nonzero__ + + @classmethod + def _to_ctypes(cls, value): + if not isinstance(value, CTypesData): + raise TypeError("unexpected %s object" % type(value).__name__) + address = value._convert_to_address(cls) + return ctypes.cast(address, cls._ctype) + + @classmethod + def _from_ctypes(cls, ctypes_ptr): + address = ctypes.cast(ctypes_ptr, ctypes.c_void_p).value or 0 + return cls._new_pointer_at(address) + + @classmethod + def _initialize(cls, ctypes_ptr, value): + if value: + ctypes_ptr.contents = cls._to_ctypes(value).contents + + def _convert_to_address(self, BClass): + if (BClass in (self.__class__, None) or BClass._automatic_casts + or self._automatic_casts): + return self._address + else: + return CTypesData._convert_to_address(self, BClass) + + +class CTypesBaseStructOrUnion(CTypesData): + __slots__ = ['_blob'] + + @classmethod + def _create_ctype_obj(cls, init): + # may be overridden + raise TypeError("cannot instantiate opaque type %s" % (cls,)) + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + @classmethod + def _offsetof(cls, fieldname): + return getattr(cls._ctype, fieldname).offset + + def _convert_to_address(self, BClass): + if getattr(BClass, '_BItem', None) is self.__class__: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @classmethod + def _from_ctypes(cls, ctypes_struct_or_union): + self = cls.__new__(cls) + self._blob = ctypes_struct_or_union + return self + + @classmethod + def _to_ctypes(cls, value): + return value._blob + + def __repr__(self, c_name=None): + return CTypesData.__repr__(self, c_name or self._get_c_name(' &')) + + +class CTypesBackend: + + PRIMITIVE_TYPES = { + 'char': ctypes.c_char, + 'short': ctypes.c_short, + 'int': ctypes.c_int, + 'long': ctypes.c_long, + 'long long': ctypes.c_longlong, + 'signed char': ctypes.c_byte, + 'unsigned char': ctypes.c_ubyte, + 'unsigned short': ctypes.c_ushort, + 'unsigned int': ctypes.c_uint, + 'unsigned long': ctypes.c_ulong, + 'unsigned long long': ctypes.c_ulonglong, + 'float': ctypes.c_float, + 'double': ctypes.c_double, + '_Bool': ctypes.c_bool, + } + + for _name in ['unsigned long long', 'unsigned long', + 'unsigned int', 'unsigned short', 'unsigned char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['uint%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['uintptr_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['size_t'] = PRIMITIVE_TYPES[_name] + + for _name in ['long long', 'long', 'int', 'short', 'signed char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['int%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['intptr_t'] = PRIMITIVE_TYPES[_name] + PRIMITIVE_TYPES['ptrdiff_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['ssize_t'] = PRIMITIVE_TYPES[_name] + + + def __init__(self): + self.RTLD_LAZY = 0 # not supported anyway by ctypes + self.RTLD_NOW = 0 + self.RTLD_GLOBAL = ctypes.RTLD_GLOBAL + self.RTLD_LOCAL = ctypes.RTLD_LOCAL + + def set_ffi(self, ffi): + self.ffi = ffi + + def _get_types(self): + return CTypesData, CTypesType + + def load_library(self, path, flags=0): + cdll = ctypes.CDLL(path, flags) + return CTypesLibrary(self, cdll) + + def new_void_type(self): + class CTypesVoid(CTypesData): + __slots__ = [] + _reftypename = 'void &' + @staticmethod + def _from_ctypes(novalue): + return None + @staticmethod + def _to_ctypes(novalue): + if novalue is not None: + raise TypeError("None expected, got %s object" % + (type(novalue).__name__,)) + CTypesVoid._fix_class() + return CTypesVoid + + def new_primitive_type(self, name): + if name == 'wchar_t': + raise NotImplementedError(name) + ctype = self.PRIMITIVE_TYPES[name] + if name == 'char': + kind = 'char' + elif name in ('float', 'double'): + kind = 'float' + else: + if name in ('signed char', 'unsigned char'): + kind = 'byte' + elif name == '_Bool': + kind = 'bool' + else: + kind = 'int' + is_signed = (ctype(-1).value == -1) + # + def _cast_source_to_int(source): + if isinstance(source, (int, long, float)): + source = int(source) + elif isinstance(source, CTypesData): + source = source._cast_to_integer() + elif isinstance(source, bytes): + source = ord(source) + elif source is None: + source = 0 + else: + raise TypeError("bad type for cast to %r: %r" % + (CTypesPrimitive, type(source).__name__)) + return source + # + kind1 = kind + class CTypesPrimitive(CTypesGenericPrimitive): + __slots__ = ['_value'] + _ctype = ctype + _reftypename = '%s &' % name + kind = kind1 + + def __init__(self, value): + self._value = value + + @staticmethod + def _create_ctype_obj(init): + if init is None: + return ctype() + return ctype(CTypesPrimitive._to_ctypes(init)) + + if kind in {'int', 'byte'}: + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = ctype(source).value # cast within range + return cls(source) + def __int__(self): + return self._value + + if kind == 'bool': + @classmethod + def _cast_from(cls, source): + if not isinstance(source, (int, long, float)): + source = _cast_source_to_int(source) + return cls(bool(source)) + def __int__(self): + return int(self._value) + + if kind == 'char': + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = bytechr(source & 0xFF) + return cls(source) + def __int__(self): + return ord(self._value) + + if kind == 'float': + @classmethod + def _cast_from(cls, source): + if isinstance(source, float): + pass + elif isinstance(source, CTypesGenericPrimitive): + if hasattr(source, '__float__'): + source = float(source) + else: + source = int(source) + else: + source = _cast_source_to_int(source) + source = ctype(source).value # fix precision + return cls(source) + def __int__(self): + return int(self._value) + def __float__(self): + return self._value + + _cast_to_integer = __int__ + + if kind in {'int', 'byte', 'bool'}: + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long)): + if isinstance(x, CTypesData): + x = int(x) + else: + raise TypeError("integer expected, got %s" % + type(x).__name__) + if ctype(x).value != x: + if not is_signed and x < 0: + raise OverflowError("%s: negative integer" % name) + else: + raise OverflowError("%s: integer out of bounds" + % name) + return x + + if kind == 'char': + @staticmethod + def _to_ctypes(x): + if isinstance(x, bytes) and len(x) == 1: + return x + if isinstance(x, CTypesPrimitive): # > + return x._value + raise TypeError("character expected, got %s" % + type(x).__name__) + def __nonzero__(self): + return ord(self._value) != 0 + else: + def __nonzero__(self): + return self._value != 0 + __bool__ = __nonzero__ + + if kind == 'float': + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long, float, CTypesData)): + raise TypeError("float expected, got %s" % + type(x).__name__) + return ctype(x).value + + @staticmethod + def _from_ctypes(value): + return getattr(value, 'value', value) + + @staticmethod + def _initialize(blob, init): + blob.value = CTypesPrimitive._to_ctypes(init) + + if kind == 'char': + def _to_string(self, maxlen): + return self._value + if kind == 'byte': + def _to_string(self, maxlen): + return chr(self._value & 0xff) + # + CTypesPrimitive._fix_class() + return CTypesPrimitive + + def new_pointer_type(self, BItem): + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'charp' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'bytep' + elif BItem is getbtype(model.void_type): + kind = 'voidp' + else: + kind = 'generic' + # + class CTypesPtr(CTypesGenericPtr): + __slots__ = ['_own'] + if kind == 'charp': + __slots__ += ['__as_strbuf'] + _BItem = BItem + if hasattr(BItem, '_ctype'): + _ctype = ctypes.POINTER(BItem._ctype) + _bitem_size = ctypes.sizeof(BItem._ctype) + else: + _ctype = ctypes.c_void_p + if issubclass(BItem, CTypesGenericArray): + _reftypename = BItem._get_c_name('(* &)') + else: + _reftypename = BItem._get_c_name(' * &') + + def __init__(self, init): + ctypeobj = BItem._create_ctype_obj(init) + if kind == 'charp': + self.__as_strbuf = ctypes.create_string_buffer( + ctypeobj.value + b'\x00') + self._as_ctype_ptr = ctypes.cast( + self.__as_strbuf, self._ctype) + else: + self._as_ctype_ptr = ctypes.pointer(ctypeobj) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own = True + + def __add__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address + + other * self._bitem_size) + else: + return NotImplemented + + def __sub__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address - + other * self._bitem_size) + elif type(self) is type(other): + return (self._address - other._address) // self._bitem_size + else: + return NotImplemented + + def __getitem__(self, index): + if getattr(self, '_own', False) and index != 0: + raise IndexError + return BItem._from_ctypes(self._as_ctype_ptr[index]) + + def __setitem__(self, index, value): + self._as_ctype_ptr[index] = BItem._to_ctypes(value) + + if kind in {'charp', 'voidp'}: + @classmethod + def _arg_to_ctypes(cls, *value): + if value and isinstance(value[0], bytes): + return ctypes.c_char_p(value[0]) + else: + return super()._arg_to_ctypes(*value) + + if kind in {'charp', 'bytep'}: + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = sys.maxsize + p = ctypes.cast(self._as_ctype_ptr, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % ( + ctypes.sizeof(self._as_ctype_ptr.contents),) + return super()._get_own_repr() + # + if (BItem is self.ffi._get_cached_btype(model.void_type) or + BItem is self.ffi._get_cached_btype(model.PrimitiveType('char'))): + CTypesPtr._automatic_casts = True + # + CTypesPtr._fix_class() + return CTypesPtr + + def new_array_type(self, CTypesPtr, length): + if length is None: + brackets = ' &[]' + else: + brackets = ' &[%d]' % length + BItem = CTypesPtr._BItem + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'char' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'byte' + else: + kind = 'generic' + # + class CTypesArray(CTypesGenericArray): + __slots__ = ['_blob', '_own'] + if length is not None: + _ctype = BItem._ctype * length + else: + __slots__.append('_ctype') + _reftypename = BItem._get_c_name(brackets) + _declared_length = length + _CTPtr = CTypesPtr + + def __init__(self, init): + if length is None: + if isinstance(init, (int, long)): + len1 = init + init = None + elif kind == 'char' and isinstance(init, bytes): + len1 = len(init) + 1 # extra null + else: + init = tuple(init) + len1 = len(init) + self._ctype = BItem._ctype * len1 + self._blob = self._ctype() + self._own = True + if init is not None: + self._initialize(self._blob, init) + + @staticmethod + def _initialize(blob, init): + if isinstance(init, bytes): + init = [init[i:i+1] for i in range(len(init))] + else: + if isinstance(init, CTypesGenericArray): + if (len(init) != len(blob) or + not isinstance(init, CTypesArray)): + raise TypeError("length/type mismatch: %s" % (init,)) + init = tuple(init) + if len(init) > len(blob): + raise IndexError("too many initializers") + addr = ctypes.cast(blob, ctypes.c_void_p).value + PTR = ctypes.POINTER(BItem._ctype) + itemsize = ctypes.sizeof(BItem._ctype) + for i, value in enumerate(init): + p = ctypes.cast(addr + i * itemsize, PTR) + BItem._initialize(p.contents, value) + + def __len__(self): + return len(self._blob) + + def __getitem__(self, index): + if not (0 <= index < len(self._blob)): + raise IndexError + return BItem._from_ctypes(self._blob[index]) + + def __setitem__(self, index, value): + if not (0 <= index < len(self._blob)): + raise IndexError + self._blob[index] = BItem._to_ctypes(value) + + if kind in {'char', 'byte'}: + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = len(self._blob) + p = ctypes.cast(self._blob, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % (ctypes.sizeof(self._blob),) + return super()._get_own_repr() + + def _convert_to_address(self, BClass): + if BClass in (CTypesPtr, None) or BClass._automatic_casts: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @staticmethod + def _from_ctypes(ctypes_array): + self = CTypesArray.__new__(CTypesArray) + self._blob = ctypes_array + return self + + @staticmethod + def _arg_to_ctypes(value): + return CTypesPtr._arg_to_ctypes(value) + + def __add__(self, other): + if isinstance(other, (int, long)): + return CTypesPtr._new_pointer_at( + ctypes.addressof(self._blob) + + other * ctypes.sizeof(BItem._ctype)) + else: + return NotImplemented + + @classmethod + def _cast_from(cls, source): + raise NotImplementedError("casting to %r" % ( + cls._get_c_name(),)) + # + CTypesArray._fix_class() + return CTypesArray + + def _new_struct_or_union(self, kind, name, base_ctypes_class): + # + class struct_or_union(base_ctypes_class): + pass + struct_or_union.__name__ = '%s_%s' % (kind, name) + kind1 = kind + # + class CTypesStructOrUnion(CTypesBaseStructOrUnion): + __slots__ = ['_blob'] + _ctype = struct_or_union + _reftypename = '%s &' % (name,) + _kind = kind = kind1 + # + CTypesStructOrUnion._fix_class() + return CTypesStructOrUnion + + def new_struct_type(self, name): + return self._new_struct_or_union('struct', name, ctypes.Structure) + + def new_union_type(self, name): + return self._new_struct_or_union('union', name, ctypes.Union) + + def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp, + totalsize=-1, totalalignment=-1, sflags=0, + pack=0): + if totalsize >= 0 or totalalignment >= 0: + raise NotImplementedError("the ctypes backend of CFFI does not support " + "structures completed by verify(); please " + "compile and install the _cffi_backend module.") + struct_or_union = CTypesStructOrUnion._ctype + fnames = [fname for (fname, BField, bitsize) in fields] + btypes = [BField for (fname, BField, bitsize) in fields] + bitfields = [bitsize for (fname, BField, bitsize) in fields] + # + bfield_types = {} + cfields = [] + for (fname, BField, bitsize) in fields: + if bitsize < 0: + cfields.append((fname, BField._ctype)) + bfield_types[fname] = BField + else: + cfields.append((fname, BField._ctype, bitsize)) + bfield_types[fname] = Ellipsis + if sflags & 8: + struct_or_union._pack_ = 1 + elif pack: + struct_or_union._pack_ = pack + struct_or_union._fields_ = cfields + CTypesStructOrUnion._bfield_types = bfield_types + # + @staticmethod + def _create_ctype_obj(init): + result = struct_or_union() + if init is not None: + initialize(result, init) + return result + CTypesStructOrUnion._create_ctype_obj = _create_ctype_obj + # + def initialize(blob, init): + if is_union: + if len(init) > 1: + raise ValueError("union initializer: %d items given, but " + "only one supported (use a dict if needed)" + % (len(init),)) + if not isinstance(init, dict): + if isinstance(init, (bytes, unicode)): + raise TypeError("union initializer: got a str") + init = tuple(init) + if len(init) > len(fnames): + raise ValueError("too many values for %s initializer" % + CTypesStructOrUnion._get_c_name()) + init = dict(zip(fnames, init)) + addr = ctypes.addressof(blob) + for fname, value in init.items(): + BField, bitsize = name2fieldtype[fname] + assert bitsize < 0, \ + "not implemented: initializer with bit fields" + offset = CTypesStructOrUnion._offsetof(fname) + PTR = ctypes.POINTER(BField._ctype) + p = ctypes.cast(addr + offset, PTR) + BField._initialize(p.contents, value) + is_union = CTypesStructOrUnion._kind == 'union' + name2fieldtype = dict(zip(fnames, zip(btypes, bitfields))) + # + for fname, BField, bitsize in fields: + if fname == '': + raise NotImplementedError("nested anonymous structs/unions") + if hasattr(CTypesStructOrUnion, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + if bitsize < 0: + def getter(self, fname=fname, BField=BField, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BField._from_ctypes(p.contents) + def setter(self, value, fname=fname, BField=BField): + setattr(self._blob, fname, BField._to_ctypes(value)) + # + if issubclass(BField, CTypesGenericArray): + setter = None + if BField._declared_length == 0: + def getter(self, fname=fname, BFieldPtr=BField._CTPtr, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BFieldPtr._from_ctypes(p) + # + else: + def getter(self, fname=fname, BField=BField): + return BField._from_ctypes(getattr(self._blob, fname)) + def setter(self, value, fname=fname, BField=BField): + # xxx obscure workaround + value = BField._to_ctypes(value) + oldvalue = getattr(self._blob, fname) + setattr(self._blob, fname, value) + if value != getattr(self._blob, fname): + setattr(self._blob, fname, oldvalue) + raise OverflowError("value too large for bitfield") + setattr(CTypesStructOrUnion, fname, property(getter, setter)) + # + CTypesPtr = self.ffi._get_cached_btype(model.PointerType(tp)) + for fname in fnames: + if hasattr(CTypesPtr, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + def getter(self, fname=fname): + return getattr(self[0], fname) + def setter(self, value, fname=fname): + setattr(self[0], fname, value) + setattr(CTypesPtr, fname, property(getter, setter)) + + def new_function_type(self, BArgs, BResult, has_varargs): + nameargs = [BArg._get_c_name() for BArg in BArgs] + if has_varargs: + nameargs.append('...') + nameargs = ', '.join(nameargs) + # + class CTypesFunctionPtr(CTypesGenericPtr): + __slots__ = ['_own_callback', '_name'] + _ctype = ctypes.CFUNCTYPE(getattr(BResult, '_ctype', None), + *[BArg._ctype for BArg in BArgs], + use_errno=True) + _reftypename = BResult._get_c_name('(* &)(%s)' % (nameargs,)) + + def __init__(self, init, error=None): + # create a callback to the Python callable init() + import traceback + assert not has_varargs, "varargs not supported for callbacks" + if getattr(BResult, '_ctype', None) is not None: + error = BResult._from_ctypes( + BResult._create_ctype_obj(error)) + else: + error = None + def callback(*args): + args2 = [] + for arg, BArg in zip(args, BArgs): + args2.append(BArg._from_ctypes(arg)) + try: + res2 = init(*args2) + res2 = BResult._to_ctypes(res2) + except: + traceback.print_exc() + res2 = error + if issubclass(BResult, CTypesGenericPtr): + if res2: + res2 = ctypes.cast(res2, ctypes.c_void_p).value + # .value: http://bugs.python.org/issue1574593 + else: + res2 = None + #print repr(res2) + return res2 + if issubclass(BResult, CTypesGenericPtr): + # The only pointers callbacks can return are void*s: + # http://bugs.python.org/issue5710 + callback_ctype = ctypes.CFUNCTYPE( + ctypes.c_void_p, + *[BArg._ctype for BArg in BArgs], + use_errno=True) + else: + callback_ctype = CTypesFunctionPtr._ctype + self._as_ctype_ptr = callback_ctype(callback) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own_callback = init + + @staticmethod + def _initialize(ctypes_ptr, value): + if value: + raise NotImplementedError("ctypes backend: not supported: " + "initializers for function pointers") + + def __repr__(self): + c_name = getattr(self, '_name', None) + if c_name: + i = self._reftypename.index('(* &)') + if self._reftypename[i-1] not in ' )*': + c_name = ' ' + c_name + c_name = self._reftypename.replace('(* &)', c_name) + return CTypesData.__repr__(self, c_name) + + def _get_own_repr(self): + if getattr(self, '_own_callback', None) is not None: + return 'calling %r' % (self._own_callback,) + return super()._get_own_repr() + + def __call__(self, *args): + if has_varargs: + assert len(args) >= len(BArgs) + extraargs = args[len(BArgs):] + args = args[:len(BArgs)] + else: + assert len(args) == len(BArgs) + ctypes_args = [] + for arg, BArg in zip(args, BArgs): + ctypes_args.append(BArg._arg_to_ctypes(arg)) + if has_varargs: + for i, arg in enumerate(extraargs): + if arg is None: + ctypes_args.append(ctypes.c_void_p(0)) # NULL + continue + if not isinstance(arg, CTypesData): + raise TypeError( + "argument %d passed in the variadic part " + "needs to be a cdata object (got %s)" % + (1 + len(BArgs) + i, type(arg).__name__)) + ctypes_args.append(arg._arg_to_ctypes(arg)) + result = self._as_ctype_ptr(*ctypes_args) + return BResult._from_ctypes(result) + # + CTypesFunctionPtr._fix_class() + return CTypesFunctionPtr + + def new_enum_type(self, name, enumerators, enumvalues, CTypesInt): + assert isinstance(name, str) + reverse_mapping = dict(zip(reversed(enumvalues), + reversed(enumerators))) + # + class CTypesEnum(CTypesInt): + __slots__ = [] + _reftypename = '%s &' % name + + def _get_own_repr(self): + value = self._value + try: + return '%d: %s' % (value, reverse_mapping[value]) + except KeyError: + return str(value) + + def _to_string(self, maxlen): + value = self._value + try: + return reverse_mapping[value] + except KeyError: + return str(value) + # + CTypesEnum._fix_class() + return CTypesEnum + + def get_errno(self): + return ctypes.get_errno() + + def set_errno(self, value): + ctypes.set_errno(value) + + def string(self, b, maxlen=-1): + return b._to_string(maxlen) + + def buffer(self, bptr, size=-1): + raise NotImplementedError("buffer() with ctypes backend") + + def sizeof(self, cdata_or_BType): + if isinstance(cdata_or_BType, CTypesData): + return cdata_or_BType._get_size_of_instance() + else: + assert issubclass(cdata_or_BType, CTypesData) + return cdata_or_BType._get_size() + + def alignof(self, BType): + assert issubclass(BType, CTypesData) + return BType._alignment() + + def newp(self, BType, source): + if not issubclass(BType, CTypesData): + raise TypeError + return BType._newp(source) + + def cast(self, BType, source): + return BType._cast_from(source) + + def callback(self, BType, source, error, onerror): + assert onerror is None # XXX not implemented + return BType(source, error) + + _weakref_cache_ref = None + + def gcp(self, cdata, destructor, size=0): + if self._weakref_cache_ref is None: + import weakref + class MyRef(weakref.ref): + def __eq__(self, other): + myref = self() + return self is other or ( + myref is not None and myref is other()) + def __ne__(self, other): + return not (self == other) + def __hash__(self): + try: + return self._hash + except AttributeError: + self._hash = hash(self()) + return self._hash + self._weakref_cache_ref = {}, MyRef + weak_cache, MyRef = self._weakref_cache_ref + + if destructor is None: + try: + del weak_cache[MyRef(cdata)] + except KeyError: + raise TypeError("Can remove destructor only on a object " + "previously returned by ffi.gc()") + return None + + def remove(k): + cdata, destructor = weak_cache.pop(k, (None, None)) + if destructor is not None: + destructor(cdata) + + new_cdata = self.cast(self.typeof(cdata), cdata) + assert new_cdata is not cdata + weak_cache[MyRef(new_cdata, remove)] = (cdata, destructor) + return new_cdata + + typeof = type + + def getcname(self, BType, replace_with): + return BType._get_c_name(replace_with) + + def typeoffsetof(self, BType, fieldname, num=0): + if isinstance(fieldname, str): + if num == 0 and issubclass(BType, CTypesGenericPtr): + BType = BType._BItem + if not issubclass(BType, CTypesBaseStructOrUnion): + raise TypeError("expected a struct or union ctype") + BField = BType._bfield_types[fieldname] + if BField is Ellipsis: + raise TypeError("not supported for bitfields") + return (BField, BType._offsetof(fieldname)) + elif isinstance(fieldname, (int, long)): + if issubclass(BType, CTypesGenericArray): + BType = BType._CTPtr + if not issubclass(BType, CTypesGenericPtr): + raise TypeError("expected an array or ptr ctype") + BItem = BType._BItem + offset = BItem._get_size() * fieldname + if offset > sys.maxsize: + raise OverflowError + return (BItem, offset) + else: + raise TypeError(type(fieldname)) + + def rawaddressof(self, BTypePtr, cdata, offset=None): + if isinstance(cdata, CTypesBaseStructOrUnion): + ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata)) + elif isinstance(cdata, CTypesGenericPtr): + if offset is None or not issubclass(type(cdata)._BItem, + CTypesBaseStructOrUnion): + raise TypeError("unexpected cdata type") + ptr = type(cdata)._to_ctypes(cdata) + elif isinstance(cdata, CTypesGenericArray): + ptr = type(cdata)._to_ctypes(cdata) + else: + raise TypeError("expected a ") + if offset: + ptr = ctypes.cast( + ctypes.c_void_p( + ctypes.cast(ptr, ctypes.c_void_p).value + offset), + type(ptr)) + return BTypePtr._from_ctypes(ptr) + + +class CTypesLibrary: + + def __init__(self, backend, cdll): + self.backend = backend + self.cdll = cdll + + def load_function(self, BType, name): + c_func = getattr(self.cdll, name) + funcobj = BType._from_ctypes(c_func) + funcobj._name = name + return funcobj + + def read_variable(self, BType, name): + try: + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + except AttributeError as e: + raise NotImplementedError(e) + return BType._from_ctypes(ctypes_obj) + + def write_variable(self, BType, name, value): + new_ctypes_obj = BType._to_ctypes(value) + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + ctypes.memmove(ctypes.addressof(ctypes_obj), + ctypes.addressof(new_ctypes_obj), + ctypes.sizeof(BType._ctype)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py new file mode 100644 index 0000000..c964ba1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py @@ -0,0 +1,187 @@ +from .error import VerificationError + +class CffiOp: + def __init__(self, op, arg): + self.op = op + self.arg = arg + + def as_c_expr(self): + if self.op is None: + assert isinstance(self.arg, str) + return '(_cffi_opcode_t)(%s)' % (self.arg,) + classname = CLASS_NAME[self.op] + return '_CFFI_OP(_CFFI_OP_%s, %s)' % (classname, self.arg) + + def as_python_bytes(self): + if self.op is None and self.arg.isdigit(): + value = int(self.arg) # non-negative: '-' not in self.arg + if value >= 2**31: + raise OverflowError("cannot emit %r: limited to 2**31-1" + % (self.arg,)) + return format_four_bytes(value) + if isinstance(self.arg, str): + raise VerificationError("cannot emit to Python: %r" % (self.arg,)) + return format_four_bytes((self.arg << 8) | self.op) + + def __str__(self): + classname = CLASS_NAME.get(self.op, self.op) + return '(%s %s)' % (classname, self.arg) + +def format_four_bytes(num): + return '\\x%02X\\x%02X\\x%02X\\x%02X' % ( + (num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + (num ) & 0xFF) + +OP_PRIMITIVE = 1 +OP_POINTER = 3 +OP_ARRAY = 5 +OP_OPEN_ARRAY = 7 +OP_STRUCT_UNION = 9 +OP_ENUM = 11 +OP_FUNCTION = 13 +OP_FUNCTION_END = 15 +OP_NOOP = 17 +OP_BITFIELD = 19 +OP_TYPENAME = 21 +OP_CPYTHON_BLTN_V = 23 # varargs +OP_CPYTHON_BLTN_N = 25 # noargs +OP_CPYTHON_BLTN_O = 27 # O (i.e. a single arg) +OP_CONSTANT = 29 +OP_CONSTANT_INT = 31 +OP_GLOBAL_VAR = 33 +OP_DLOPEN_FUNC = 35 +OP_DLOPEN_CONST = 37 +OP_GLOBAL_VAR_F = 39 +OP_EXTERN_PYTHON = 41 + +PRIM_VOID = 0 +PRIM_BOOL = 1 +PRIM_CHAR = 2 +PRIM_SCHAR = 3 +PRIM_UCHAR = 4 +PRIM_SHORT = 5 +PRIM_USHORT = 6 +PRIM_INT = 7 +PRIM_UINT = 8 +PRIM_LONG = 9 +PRIM_ULONG = 10 +PRIM_LONGLONG = 11 +PRIM_ULONGLONG = 12 +PRIM_FLOAT = 13 +PRIM_DOUBLE = 14 +PRIM_LONGDOUBLE = 15 + +PRIM_WCHAR = 16 +PRIM_INT8 = 17 +PRIM_UINT8 = 18 +PRIM_INT16 = 19 +PRIM_UINT16 = 20 +PRIM_INT32 = 21 +PRIM_UINT32 = 22 +PRIM_INT64 = 23 +PRIM_UINT64 = 24 +PRIM_INTPTR = 25 +PRIM_UINTPTR = 26 +PRIM_PTRDIFF = 27 +PRIM_SIZE = 28 +PRIM_SSIZE = 29 +PRIM_INT_LEAST8 = 30 +PRIM_UINT_LEAST8 = 31 +PRIM_INT_LEAST16 = 32 +PRIM_UINT_LEAST16 = 33 +PRIM_INT_LEAST32 = 34 +PRIM_UINT_LEAST32 = 35 +PRIM_INT_LEAST64 = 36 +PRIM_UINT_LEAST64 = 37 +PRIM_INT_FAST8 = 38 +PRIM_UINT_FAST8 = 39 +PRIM_INT_FAST16 = 40 +PRIM_UINT_FAST16 = 41 +PRIM_INT_FAST32 = 42 +PRIM_UINT_FAST32 = 43 +PRIM_INT_FAST64 = 44 +PRIM_UINT_FAST64 = 45 +PRIM_INTMAX = 46 +PRIM_UINTMAX = 47 +PRIM_FLOATCOMPLEX = 48 +PRIM_DOUBLECOMPLEX = 49 +PRIM_CHAR16 = 50 +PRIM_CHAR32 = 51 + +_NUM_PRIM = 52 +_UNKNOWN_PRIM = -1 +_UNKNOWN_FLOAT_PRIM = -2 +_UNKNOWN_LONG_DOUBLE = -3 + +_IO_FILE_STRUCT = -1 + +PRIMITIVE_TO_INDEX = { + 'char': PRIM_CHAR, + 'short': PRIM_SHORT, + 'int': PRIM_INT, + 'long': PRIM_LONG, + 'long long': PRIM_LONGLONG, + 'signed char': PRIM_SCHAR, + 'unsigned char': PRIM_UCHAR, + 'unsigned short': PRIM_USHORT, + 'unsigned int': PRIM_UINT, + 'unsigned long': PRIM_ULONG, + 'unsigned long long': PRIM_ULONGLONG, + 'float': PRIM_FLOAT, + 'double': PRIM_DOUBLE, + 'long double': PRIM_LONGDOUBLE, + '_cffi_float_complex_t': PRIM_FLOATCOMPLEX, + '_cffi_double_complex_t': PRIM_DOUBLECOMPLEX, + '_Bool': PRIM_BOOL, + 'wchar_t': PRIM_WCHAR, + 'char16_t': PRIM_CHAR16, + 'char32_t': PRIM_CHAR32, + 'int8_t': PRIM_INT8, + 'uint8_t': PRIM_UINT8, + 'int16_t': PRIM_INT16, + 'uint16_t': PRIM_UINT16, + 'int32_t': PRIM_INT32, + 'uint32_t': PRIM_UINT32, + 'int64_t': PRIM_INT64, + 'uint64_t': PRIM_UINT64, + 'intptr_t': PRIM_INTPTR, + 'uintptr_t': PRIM_UINTPTR, + 'ptrdiff_t': PRIM_PTRDIFF, + 'size_t': PRIM_SIZE, + 'ssize_t': PRIM_SSIZE, + 'int_least8_t': PRIM_INT_LEAST8, + 'uint_least8_t': PRIM_UINT_LEAST8, + 'int_least16_t': PRIM_INT_LEAST16, + 'uint_least16_t': PRIM_UINT_LEAST16, + 'int_least32_t': PRIM_INT_LEAST32, + 'uint_least32_t': PRIM_UINT_LEAST32, + 'int_least64_t': PRIM_INT_LEAST64, + 'uint_least64_t': PRIM_UINT_LEAST64, + 'int_fast8_t': PRIM_INT_FAST8, + 'uint_fast8_t': PRIM_UINT_FAST8, + 'int_fast16_t': PRIM_INT_FAST16, + 'uint_fast16_t': PRIM_UINT_FAST16, + 'int_fast32_t': PRIM_INT_FAST32, + 'uint_fast32_t': PRIM_UINT_FAST32, + 'int_fast64_t': PRIM_INT_FAST64, + 'uint_fast64_t': PRIM_UINT_FAST64, + 'intmax_t': PRIM_INTMAX, + 'uintmax_t': PRIM_UINTMAX, + } + +F_UNION = 0x01 +F_CHECK_FIELDS = 0x02 +F_PACKED = 0x04 +F_EXTERNAL = 0x08 +F_OPAQUE = 0x10 + +G_FLAGS = dict([('_CFFI_' + _key, globals()[_key]) + for _key in ['F_UNION', 'F_CHECK_FIELDS', 'F_PACKED', + 'F_EXTERNAL', 'F_OPAQUE']]) + +CLASS_NAME = {} +for _name, _value in list(globals().items()): + if _name.startswith('OP_') and isinstance(_value, int): + CLASS_NAME[_value] = _name[3:] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py new file mode 100644 index 0000000..d4dae35 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py @@ -0,0 +1,82 @@ +import sys +from . import model +from .error import FFIError + + +COMMON_TYPES = {} + +try: + # fetch "bool" and all simple Windows types + from _cffi_backend import _get_common_types + _get_common_types(COMMON_TYPES) +except ImportError: + pass + +COMMON_TYPES['FILE'] = model.unknown_type('FILE', '_IO_FILE') +COMMON_TYPES['bool'] = '_Bool' # in case we got ImportError above +COMMON_TYPES['float _Complex'] = '_cffi_float_complex_t' +COMMON_TYPES['double _Complex'] = '_cffi_double_complex_t' + +for _type in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + if _type.endswith('_t'): + COMMON_TYPES[_type] = _type +del _type + +_CACHE = {} + +def resolve_common_type(parser, commontype): + try: + return _CACHE[commontype] + except KeyError: + cdecl = COMMON_TYPES.get(commontype, commontype) + if not isinstance(cdecl, str): + result, quals = cdecl, 0 # cdecl is already a BaseType + elif cdecl in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + result, quals = model.PrimitiveType(cdecl), 0 + elif cdecl == 'set-unicode-needed': + raise FFIError("The Windows type %r is only available after " + "you call ffi.set_unicode()" % (commontype,)) + else: + if commontype == cdecl: + raise FFIError( + "Unsupported type: %r. Please look at " + "http://cffi.readthedocs.io/en/latest/cdef.html#ffi-cdef-limitations " + "and file an issue if you think this type should really " + "be supported." % (commontype,)) + result, quals = parser.parse_type_and_quals(cdecl) # recursive + + assert isinstance(result, model.BaseTypeByIdentity) + _CACHE[commontype] = result, quals + return result, quals + + +# ____________________________________________________________ +# extra types for Windows (most of them are in commontypes.c) + + +def win_common_types(): + return { + "UNICODE_STRING": model.StructType( + "_UNICODE_STRING", + ["Length", + "MaximumLength", + "Buffer"], + [model.PrimitiveType("unsigned short"), + model.PrimitiveType("unsigned short"), + model.PointerType(model.PrimitiveType("wchar_t"))], + [-1, -1, -1]), + "PUNICODE_STRING": "UNICODE_STRING *", + "PCUNICODE_STRING": "const UNICODE_STRING *", + + "TBYTE": "set-unicode-needed", + "TCHAR": "set-unicode-needed", + "LPCTSTR": "set-unicode-needed", + "PCTSTR": "set-unicode-needed", + "LPTSTR": "set-unicode-needed", + "PTSTR": "set-unicode-needed", + "PTBYTE": "set-unicode-needed", + "PTCHAR": "set-unicode-needed", + } + +if sys.platform == 'win32': + COMMON_TYPES.update(win_common_types()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py new file mode 100644 index 0000000..1896b7e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/cparser.py @@ -0,0 +1,1014 @@ +from . import model +from .commontypes import COMMON_TYPES, resolve_common_type +from .error import FFIError, CDefError +try: + from . import _pycparser as pycparser +except ImportError: + import pycparser +import weakref, re, sys + +try: + if sys.version_info < (3,): + import thread as _thread + else: + import _thread + lock = _thread.allocate_lock() +except ImportError: + lock = None + +def _workaround_for_static_import_finders(): + # Issue #392: packaging tools like cx_Freeze can not find these + # because pycparser uses exec dynamic import. This is an obscure + # workaround. This function is never called. + import pycparser.yacctab + import pycparser.lextab + +CDEF_SOURCE_STRING = "" +_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", + re.DOTALL | re.MULTILINE) +_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" + r"\b((?:[^\n\\]|\\.)*?)$", + re.DOTALL | re.MULTILINE) +_r_line_directive = re.compile(r"^[ \t]*#[ \t]*(?:line|\d+)\b.*$", re.MULTILINE) +_r_partial_enum = re.compile(r"=\s*\.\.\.\s*[,}]|\.\.\.\s*\}") +_r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") +_r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") +_r_words = re.compile(r"\w+|\S") +_parser_cache = None +_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) +_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") +_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") +_r_cdecl = re.compile(r"\b__cdecl\b") +_r_extern_python = re.compile(r'\bextern\s*"' + r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') +_r_star_const_space = re.compile( # matches "* const " + r"[*]\s*((const|volatile|restrict)\b\s*)+") +_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" + r"\.\.\.") +_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") + +def _get_parser(): + global _parser_cache + if _parser_cache is None: + _parser_cache = pycparser.CParser() + return _parser_cache + +def _workaround_for_old_pycparser(csource): + # Workaround for a pycparser issue (fixed between pycparser 2.10 and + # 2.14): "char*const***" gives us a wrong syntax tree, the same as + # for "char***(*const)". This means we can't tell the difference + # afterwards. But "char(*const(***))" gives us the right syntax + # tree. The issue only occurs if there are several stars in + # sequence with no parenthesis in between, just possibly qualifiers. + # Attempt to fix it by adding some parentheses in the source: each + # time we see "* const" or "* const *", we add an opening + # parenthesis before each star---the hard part is figuring out where + # to close them. + parts = [] + while True: + match = _r_star_const_space.search(csource) + if not match: + break + #print repr(''.join(parts)+csource), '=>', + parts.append(csource[:match.start()]) + parts.append('('); closing = ')' + parts.append(match.group()) # e.g. "* const " + endpos = match.end() + if csource.startswith('*', endpos): + parts.append('('); closing += ')' + level = 0 + i = endpos + while i < len(csource): + c = csource[i] + if c == '(': + level += 1 + elif c == ')': + if level == 0: + break + level -= 1 + elif c in ',;=': + if level == 0: + break + i += 1 + csource = csource[endpos:i] + closing + csource[i:] + #print repr(''.join(parts)+csource) + parts.append(csource) + return ''.join(parts) + +def _preprocess_extern_python(csource): + # input: `extern "Python" int foo(int);` or + # `extern "Python" { int foo(int); }` + # output: + # void __cffi_extern_python_start; + # int foo(int); + # void __cffi_extern_python_stop; + # + # input: `extern "Python+C" int foo(int);` + # output: + # void __cffi_extern_python_plus_c_start; + # int foo(int); + # void __cffi_extern_python_stop; + parts = [] + while True: + match = _r_extern_python.search(csource) + if not match: + break + endpos = match.end() - 1 + #print + #print ''.join(parts)+csource + #print '=>' + parts.append(csource[:match.start()]) + if 'C' in match.group(1): + parts.append('void __cffi_extern_python_plus_c_start; ') + else: + parts.append('void __cffi_extern_python_start; ') + if csource[endpos] == '{': + # grouping variant + closing = csource.find('}', endpos) + if closing < 0: + raise CDefError("'extern \"Python\" {': no '}' found") + if csource.find('{', endpos + 1, closing) >= 0: + raise NotImplementedError("cannot use { } inside a block " + "'extern \"Python\" { ... }'") + parts.append(csource[endpos+1:closing]) + csource = csource[closing+1:] + else: + # non-grouping variant + semicolon = csource.find(';', endpos) + if semicolon < 0: + raise CDefError("'extern \"Python\": no ';' found") + parts.append(csource[endpos:semicolon+1]) + csource = csource[semicolon+1:] + parts.append(' void __cffi_extern_python_stop;') + #print ''.join(parts)+csource + #print + parts.append(csource) + return ''.join(parts) + +def _warn_for_string_literal(csource): + if '"' not in csource: + return + for line in csource.splitlines(): + if '"' in line and not line.lstrip().startswith('#'): + import warnings + warnings.warn("String literal found in cdef() or type source. " + "String literals are ignored here, but you should " + "remove them anyway because some character sequences " + "confuse pre-parsing.") + break + +def _warn_for_non_extern_non_static_global_variable(decl): + if not decl.storage: + import warnings + warnings.warn("Global variable '%s' in cdef(): for consistency " + "with C it should have a storage class specifier " + "(usually 'extern')" % (decl.name,)) + +def _remove_line_directives(csource): + # _r_line_directive matches whole lines, without the final \n, if they + # start with '#line' with some spacing allowed, or '#NUMBER'. This + # function stores them away and replaces them with exactly the string + # '#line@N', where N is the index in the list 'line_directives'. + line_directives = [] + def replace(m): + i = len(line_directives) + line_directives.append(m.group()) + return '#line@%d' % i + csource = _r_line_directive.sub(replace, csource) + return csource, line_directives + +def _put_back_line_directives(csource, line_directives): + def replace(m): + s = m.group() + if not s.startswith('#line@'): + raise AssertionError("unexpected #line directive " + "(should have been processed and removed") + return line_directives[int(s[6:])] + return _r_line_directive.sub(replace, csource) + +def _preprocess(csource): + # First, remove the lines of the form '#line N "filename"' because + # the "filename" part could confuse the rest + csource, line_directives = _remove_line_directives(csource) + # Remove comments. NOTE: this only work because the cdef() section + # should not contain any string literals (except in line directives)! + def replace_keeping_newlines(m): + return ' ' + m.group().count('\n') * '\n' + csource = _r_comment.sub(replace_keeping_newlines, csource) + # Remove the "#define FOO x" lines + macros = {} + for match in _r_define.finditer(csource): + macroname, macrovalue = match.groups() + macrovalue = macrovalue.replace('\\\n', '').strip() + macros[macroname] = macrovalue + csource = _r_define.sub('', csource) + # + if pycparser.__version__ < '2.14': + csource = _workaround_for_old_pycparser(csource) + # + # BIG HACK: replace WINAPI or __stdcall with "volatile const". + # It doesn't make sense for the return type of a function to be + # "volatile volatile const", so we abuse it to detect __stdcall... + # Hack number 2 is that "int(volatile *fptr)();" is not valid C + # syntax, so we place the "volatile" before the opening parenthesis. + csource = _r_stdcall2.sub(' volatile volatile const(', csource) + csource = _r_stdcall1.sub(' volatile volatile const ', csource) + csource = _r_cdecl.sub(' ', csource) + # + # Replace `extern "Python"` with start/end markers + csource = _preprocess_extern_python(csource) + # + # Now there should not be any string literal left; warn if we get one + _warn_for_string_literal(csource) + # + # Replace "[...]" with "[__dotdotdotarray__]" + csource = _r_partial_array.sub('[__dotdotdotarray__]', csource) + # + # Replace "...}" with "__dotdotdotNUM__}". This construction should + # occur only at the end of enums; at the end of structs we have "...;}" + # and at the end of vararg functions "...);". Also replace "=...[,}]" + # with ",__dotdotdotNUM__[,}]": this occurs in the enums too, when + # giving an unknown value. + matches = list(_r_partial_enum.finditer(csource)) + for number, match in enumerate(reversed(matches)): + p = match.start() + if csource[p] == '=': + p2 = csource.find('...', p, match.end()) + assert p2 > p + csource = '%s,__dotdotdot%d__ %s' % (csource[:p], number, + csource[p2+3:]) + else: + assert csource[p:p+3] == '...' + csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, + csource[p+3:]) + # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" + csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) + # Replace "float ..." or "double..." with "__dotdotdotfloat__" + csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) + # Replace all remaining "..." with the same name, "__dotdotdot__", + # which is declared with a typedef for the purpose of C parsing. + csource = csource.replace('...', ' __dotdotdot__ ') + # Finally, put back the line directives + csource = _put_back_line_directives(csource, line_directives) + return csource, macros + +def _common_type_names(csource): + # Look in the source for what looks like usages of types from the + # list of common types. A "usage" is approximated here as the + # appearance of the word, minus a "definition" of the type, which + # is the last word in a "typedef" statement. Approximative only + # but should be fine for all the common types. + look_for_words = set(COMMON_TYPES) + look_for_words.add(';') + look_for_words.add(',') + look_for_words.add('(') + look_for_words.add(')') + look_for_words.add('typedef') + words_used = set() + is_typedef = False + paren = 0 + previous_word = '' + for word in _r_words.findall(csource): + if word in look_for_words: + if word == ';': + if is_typedef: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + is_typedef = False + elif word == 'typedef': + is_typedef = True + paren = 0 + elif word == '(': + paren += 1 + elif word == ')': + paren -= 1 + elif word == ',': + if is_typedef and paren == 0: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + else: # word in COMMON_TYPES + words_used.add(word) + previous_word = word + return words_used + + +class Parser: + + def __init__(self): + self._declarations = {} + self._included_declarations = set() + self._anonymous_counter = 0 + self._structnode2type = weakref.WeakKeyDictionary() + self._options = {} + self._int_constants = {} + self._recomplete = [] + self._uses_new_feature = None + + def _parse(self, csource): + csource, macros = _preprocess(csource) + # XXX: for more efficiency we would need to poke into the + # internals of CParser... the following registers the + # typedefs, because their presence or absence influences the + # parsing itself (but what they are typedef'ed to plays no role) + ctn = _common_type_names(csource) + typenames = [] + for name in sorted(self._declarations): + if name.startswith('typedef '): + name = name[8:] + typenames.append(name) + ctn.discard(name) + typenames += sorted(ctn) + # + csourcelines = [] + csourcelines.append('# 1 ""') + for typename in typenames: + csourcelines.append('typedef int %s;' % typename) + csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' + ' __dotdotdot__;') + # this forces pycparser to consider the following in the file + # called from line 1 + csourcelines.append('# 1 "%s"' % (CDEF_SOURCE_STRING,)) + csourcelines.append(csource) + csourcelines.append('') # see test_missing_newline_bug + fullcsource = '\n'.join(csourcelines) + if lock is not None: + lock.acquire() # pycparser is not thread-safe... + try: + ast = _get_parser().parse(fullcsource) + except pycparser.c_parser.ParseError as e: + self.convert_pycparser_error(e, csource) + finally: + if lock is not None: + lock.release() + # csource will be used to find buggy source text + return ast, macros, csource + + def _convert_pycparser_error(self, e, csource): + # xxx look for ":NUM:" at the start of str(e) + # and interpret that as a line number. This will not work if + # the user gives explicit ``# NUM "FILE"`` directives. + line = None + msg = str(e) + match = re.match(r"%s:(\d+):" % (CDEF_SOURCE_STRING,), msg) + if match: + linenum = int(match.group(1), 10) + csourcelines = csource.splitlines() + if 1 <= linenum <= len(csourcelines): + line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) + if line: + msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) + else: + msg = 'parse error\n%s' % (msg,) + raise CDefError(msg) + + def parse(self, csource, override=False, packed=False, pack=None, + dllexport=False): + if packed: + if packed != True: + raise ValueError("'packed' should be False or True; use " + "'pack' to give another value") + if pack: + raise ValueError("cannot give both 'pack' and 'packed'") + pack = 1 + elif pack: + if pack & (pack - 1): + raise ValueError("'pack' must be a power of two, not %r" % + (pack,)) + else: + pack = 0 + prev_options = self._options + try: + self._options = {'override': override, + 'packed': pack, + 'dllexport': dllexport} + self._internal_parse(csource) + finally: + self._options = prev_options + + def _internal_parse(self, csource): + ast, macros, csource = self._parse(csource) + # add the macros + self._process_macros(macros) + # find the first "__dotdotdot__" and use that as a separator + # between the repeated typedefs and the real csource + iterator = iter(ast.ext) + for decl in iterator: + if decl.name == '__dotdotdot__': + break + else: + assert 0 + current_decl = None + # + try: + self._inside_extern_python = '__cffi_extern_python_stop' + for decl in iterator: + current_decl = decl + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise CDefError("typedef does not declare any name", + decl) + quals = 0 + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and + decl.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_type(decl) + elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and + isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and + isinstance(decl.type.type.type, + pycparser.c_ast.IdentifierType) and + decl.type.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_ptr_type(decl) + else: + realtype, quals = self._get_type_and_quals( + decl.type, name=decl.name, partial_length_ok=True, + typedef_example="*(%s *)0" % (decl.name,)) + self._declare('typedef ' + decl.name, realtype, quals=quals) + elif decl.__class__.__name__ == 'Pragma': + # skip pragma, only in pycparser 2.15 + import warnings + warnings.warn( + "#pragma in cdef() are entirely ignored. " + "They should be removed for now, otherwise your " + "code might behave differently in a future version " + "of CFFI if #pragma support gets added. Note that " + "'#pragma pack' needs to be replaced with the " + "'packed' keyword argument to cdef().") + else: + raise CDefError("unexpected <%s>: this construct is valid " + "C but not valid in cdef()" % + decl.__class__.__name__, decl) + except CDefError as e: + if len(e.args) == 1: + e.args = e.args + (current_decl,) + raise + except FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + if self._int_constants[key] == val: + return # ignore identical double declarations + raise FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _add_integer_constant(self, name, int_str): + int_str = int_str.lower().rstrip("ul") + neg = int_str.startswith('-') + if neg: + int_str = int_str[1:] + # "010" is not valid oct in py3 + if (int_str.startswith("0") and int_str != '0' + and not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + pyvalue = int(int_str, 0) + if neg: + pyvalue = -pyvalue + self._add_constants(name, pyvalue) + self._declare('macro ' + name, pyvalue) + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + if _r_int_literal.match(value): + self._add_integer_constant(key, value) + elif value == '...': + self._declare('macro ' + key, value) + else: + raise CDefError( + 'only supports one of the following syntax:\n' + ' #define %s ... (literally dot-dot-dot)\n' + ' #define %s NUMBER (with NUMBER an integer' + ' constant, decimal/hex/octal)\n' + 'got:\n' + ' #define %s %s' + % (key, key, key, value)) + + def _declare_function(self, tp, quals, decl): + tp = self._get_type_pointer(tp, quals) + if self._options.get('dllexport'): + tag = 'dllexport_python ' + elif self._inside_extern_python == '__cffi_extern_python_start': + tag = 'extern_python ' + elif self._inside_extern_python == '__cffi_extern_python_plus_c_start': + tag = 'extern_python_plus_c ' + else: + tag = 'function ' + self._declare(tag + decl.name, tp) + + def _parse_decl(self, decl): + node = decl.type + if isinstance(node, pycparser.c_ast.FuncDecl): + tp, quals = self._get_type_and_quals(node, name=decl.name) + assert isinstance(tp, model.RawFunctionType) + self._declare_function(tp, quals, decl) + else: + if isinstance(node, pycparser.c_ast.Struct): + self._get_struct_union_enum_type('struct', node) + elif isinstance(node, pycparser.c_ast.Union): + self._get_struct_union_enum_type('union', node) + elif isinstance(node, pycparser.c_ast.Enum): + self._get_struct_union_enum_type('enum', node) + elif not decl.name: + raise CDefError("construct does not declare any variable", + decl) + # + if decl.name: + tp, quals = self._get_type_and_quals(node, + partial_length_ok=True) + if tp.is_raw_function: + self._declare_function(tp, quals, decl) + elif (tp.is_integer_type() and + hasattr(decl, 'init') and + hasattr(decl.init, 'value') and + _r_int_literal.match(decl.init.value)): + self._add_integer_constant(decl.name, decl.init.value) + elif (tp.is_integer_type() and + isinstance(decl.init, pycparser.c_ast.UnaryOp) and + decl.init.op == '-' and + hasattr(decl.init.expr, 'value') and + _r_int_literal.match(decl.init.expr.value)): + self._add_integer_constant(decl.name, + '-' + decl.init.expr.value) + elif (tp is model.void_type and + decl.name.startswith('__cffi_extern_python_')): + # hack: `extern "Python"` in the C source is replaced + # with "void __cffi_extern_python_start;" and + # "void __cffi_extern_python_stop;" + self._inside_extern_python = decl.name + else: + if self._inside_extern_python !='__cffi_extern_python_stop': + raise CDefError( + "cannot declare constants or " + "variables with 'extern \"Python\"'") + if (quals & model.Q_CONST) and not tp.is_array_type: + self._declare('constant ' + decl.name, tp, quals=quals) + else: + _warn_for_non_extern_non_static_global_variable(decl) + self._declare('variable ' + decl.name, tp, quals=quals) + + def parse_type(self, cdecl): + return self.parse_type_and_quals(cdecl)[0] + + def parse_type_and_quals(self, cdecl): + ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] + assert not macros + exprnode = ast.ext[-1].type.args.params[0] + if isinstance(exprnode, pycparser.c_ast.ID): + raise CDefError("unknown identifier '%s'" % (exprnode.name,)) + return self._get_type_and_quals(exprnode.type) + + def _declare(self, name, obj, included=False, quals=0): + if name in self._declarations: + prevobj, prevquals = self._declarations[name] + if prevobj is obj and prevquals == quals: + return + if not self._options.get('override'): + raise FFIError( + "multiple declarations of %s (for interactive usage, " + "try cdef(xx, override=True))" % (name,)) + assert '__dotdotdot__' not in name.split() + self._declarations[name] = (obj, quals) + if included: + self._included_declarations.add(obj) + + def _extract_quals(self, type): + quals = 0 + if isinstance(type, (pycparser.c_ast.TypeDecl, + pycparser.c_ast.PtrDecl)): + if 'const' in type.quals: + quals |= model.Q_CONST + if 'volatile' in type.quals: + quals |= model.Q_VOLATILE + if 'restrict' in type.quals: + quals |= model.Q_RESTRICT + return quals + + def _get_type_pointer(self, type, quals, declname=None): + if isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname, quals) + return model.PointerType(type, quals) + + def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False, + typedef_example=None): + # first, dereference typedefs, if we have it already parsed, we're good + if (isinstance(typenode, pycparser.c_ast.TypeDecl) and + isinstance(typenode.type, pycparser.c_ast.IdentifierType) and + len(typenode.type.names) == 1 and + ('typedef ' + typenode.type.names[0]) in self._declarations): + tp, quals = self._declarations['typedef ' + typenode.type.names[0]] + quals |= self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.ArrayDecl): + # array type + if typenode.dim is None: + length = None + else: + length = self._parse_constant( + typenode.dim, partial_length_ok=partial_length_ok) + # a hack: in 'typedef int foo_t[...][...];', don't use '...' as + # the length but use directly the C expression that would be + # generated by recompiler.py. This lets the typedef be used in + # many more places within recompiler.py + if typedef_example is not None: + if length == '...': + length = '_cffi_array_len(%s)' % (typedef_example,) + typedef_example = "*" + typedef_example + # + tp, quals = self._get_type_and_quals(typenode.type, + partial_length_ok=partial_length_ok, + typedef_example=typedef_example) + return model.ArrayType(tp, length), quals + # + if isinstance(typenode, pycparser.c_ast.PtrDecl): + # pointer type + itemtype, itemquals = self._get_type_and_quals(typenode.type) + tp = self._get_type_pointer(itemtype, itemquals, declname=name) + quals = self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.TypeDecl): + quals = self._extract_quals(typenode) + type = typenode.type + if isinstance(type, pycparser.c_ast.IdentifierType): + # assume a primitive type. get it from .names, but reduce + # synonyms to a single chosen combination + names = list(type.names) + if names != ['signed', 'char']: # keep this unmodified + prefixes = {} + while names: + name = names[0] + if name in ('short', 'long', 'signed', 'unsigned'): + prefixes[name] = prefixes.get(name, 0) + 1 + del names[0] + else: + break + # ignore the 'signed' prefix below, and reorder the others + newnames = [] + for prefix in ('unsigned', 'short', 'long'): + for i in range(prefixes.get(prefix, 0)): + newnames.append(prefix) + if not names: + names = ['int'] # implicitly + if names == ['int']: # but kill it if 'short' or 'long' + if 'short' in prefixes or 'long' in prefixes: + names = [] + names = newnames + names + ident = ' '.join(names) + if ident == 'void': + return model.void_type, quals + if ident == '__dotdotdot__': + raise FFIError(':%d: bad usage of "..."' % + typenode.coord.line) + tp0, quals0 = resolve_common_type(self, ident) + return tp0, (quals | quals0) + # + if isinstance(type, pycparser.c_ast.Struct): + # 'struct foobar' + tp = self._get_struct_union_enum_type('struct', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Union): + # 'union foobar' + tp = self._get_struct_union_enum_type('union', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Enum): + # 'enum foobar' + tp = self._get_struct_union_enum_type('enum', type, name) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.FuncDecl): + # a function type + return self._parse_function_type(typenode, name), 0 + # + # nested anonymous structs or unions end up here + if isinstance(typenode, pycparser.c_ast.Struct): + return self._get_struct_union_enum_type('struct', typenode, name, + nested=True), 0 + if isinstance(typenode, pycparser.c_ast.Union): + return self._get_struct_union_enum_type('union', typenode, name, + nested=True), 0 + # + raise FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) + + def _parse_function_type(self, typenode, funcname=None): + params = list(getattr(typenode.args, 'params', [])) + for i, arg in enumerate(params): + if not hasattr(arg, 'type'): + raise CDefError("%s arg %d: unknown type '%s'" + " (if you meant to use the old C syntax of giving" + " untyped arguments, it is not supported)" + % (funcname or 'in expression', i + 1, + getattr(arg, 'name', '?'))) + ellipsis = ( + len(params) > 0 and + isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and + isinstance(params[-1].type.type, + pycparser.c_ast.IdentifierType) and + params[-1].type.type.names == ['__dotdotdot__']) + if ellipsis: + params.pop() + if not params: + raise CDefError( + "%s: a function with only '(...)' as argument" + " is not correct C" % (funcname or 'in expression')) + args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) + for argdeclnode in params] + if not ellipsis and args == [model.void_type]: + args = [] + result, quals = self._get_type_and_quals(typenode.type) + # the 'quals' on the result type are ignored. HACK: we absure them + # to detect __stdcall functions: we textually replace "__stdcall" + # with "volatile volatile const" above. + abi = None + if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway + if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: + abi = '__stdcall' + return model.RawFunctionType(tuple(args), result, ellipsis, abi) + + def _as_func_arg(self, type, quals): + if isinstance(type, model.ArrayType): + return model.PointerType(type.item, quals) + elif isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + else: + return type + + def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): + # First, a level of caching on the exact 'type' node of the AST. + # This is obscure, but needed because pycparser "unrolls" declarations + # such as "typedef struct { } foo_t, *foo_p" and we end up with + # an AST that is not a tree, but a DAG, with the "type" node of the + # two branches foo_t and foo_p of the trees being the same node. + # It's a bit silly but detecting "DAG-ness" in the AST tree seems + # to be the only way to distinguish this case from two independent + # structs. See test_struct_with_two_usages. + try: + return self._structnode2type[type] + except KeyError: + pass + # + # Note that this must handle parsing "struct foo" any number of + # times and always return the same StructType object. Additionally, + # one of these times (not necessarily the first), the fields of + # the struct can be specified with "struct foo { ...fields... }". + # If no name is given, then we have to create a new anonymous struct + # with no caching; in this case, the fields are either specified + # right now or never. + # + force_name = name + name = type.name + # + # get the type or create it if needed + if name is None: + # 'force_name' is used to guess a more readable name for + # anonymous structs, for the common case "typedef struct { } foo". + if force_name is not None: + explicit_name = '$%s' % force_name + else: + self._anonymous_counter += 1 + explicit_name = '$%d' % self._anonymous_counter + tp = None + else: + explicit_name = name + key = '%s %s' % (kind, name) + tp, _ = self._declarations.get(key, (None, None)) + # + if tp is None: + if kind == 'struct': + tp = model.StructType(explicit_name, None, None, None) + elif kind == 'union': + tp = model.UnionType(explicit_name, None, None, None) + elif kind == 'enum': + if explicit_name == '__dotdotdot__': + raise CDefError("Enums cannot be declared with ...") + tp = self._build_enum_type(explicit_name, type.values) + else: + raise AssertionError("kind = %r" % (kind,)) + if name is not None: + self._declare(key, tp) + elif kind == 'enum' and type.values is not None: + raise NotImplementedError( + "enum %s: the '{}' declaration should appear on the first " + "time the enum is mentioned, not later" % explicit_name) + if not tp.forcename: + tp.force_the_name(force_name) + if tp.forcename and '$' in tp.name: + self._declare('anonymous %s' % tp.forcename, tp) + # + self._structnode2type[type] = tp + # + # enums: done here + if kind == 'enum': + return tp + # + # is there a 'type.decls'? If yes, then this is the place in the + # C sources that declare the fields. If no, then just return the + # existing type, possibly still incomplete. + if type.decls is None: + return tp + # + if tp.fldnames is not None: + raise CDefError("duplicate declaration of struct %s" % name) + fldnames = [] + fldtypes = [] + fldbitsize = [] + fldquals = [] + for decl in type.decls: + if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and + ''.join(decl.type.names) == '__dotdotdot__'): + # XXX pycparser is inconsistent: 'names' should be a list + # of strings, but is sometimes just one string. Use + # str.join() as a way to cope with both. + self._make_partial(tp, nested) + continue + if decl.bitsize is None: + bitsize = -1 + else: + bitsize = self._parse_constant(decl.bitsize) + self._partial_length = False + type, fqual = self._get_type_and_quals(decl.type, + partial_length_ok=True) + if self._partial_length: + self._make_partial(tp, nested) + if isinstance(type, model.StructType) and type.partial: + self._make_partial(tp, nested) + fldnames.append(decl.name or '') + fldtypes.append(type) + fldbitsize.append(bitsize) + fldquals.append(fqual) + tp.fldnames = tuple(fldnames) + tp.fldtypes = tuple(fldtypes) + tp.fldbitsize = tuple(fldbitsize) + tp.fldquals = tuple(fldquals) + if fldbitsize != [-1] * len(fldbitsize): + if isinstance(tp, model.StructType) and tp.partial: + raise NotImplementedError("%s: using both bitfields and '...;'" + % (tp,)) + tp.packed = self._options.get('packed') + if tp.completed: # must be re-completed: it is not opaque any more + tp.completed = 0 + self._recomplete.append(tp) + return tp + + def _make_partial(self, tp, nested): + if not isinstance(tp, model.StructOrUnion): + raise CDefError("%s cannot be partial" % (tp,)) + if not tp.has_c_name() and not nested: + raise NotImplementedError("%s is partial but has no C name" %(tp,)) + tp.partial = True + + def _parse_constant(self, exprnode, partial_length_ok=False): + # for now, limited to expressions that are an immediate number + # or positive/negative number + if isinstance(exprnode, pycparser.c_ast.Constant): + s = exprnode.value + if '0' <= s[0] <= '9': + s = s.rstrip('uUlL') + try: + if s.startswith('0'): + return int(s, 8) + else: + return int(s, 10) + except ValueError: + if len(s) > 1: + if s.lower()[0:2] == '0x': + return int(s, 16) + elif s.lower()[0:2] == '0b': + return int(s, 2) + raise CDefError("invalid constant %r" % (s,)) + elif s[0] == "'" and s[-1] == "'" and ( + len(s) == 3 or (len(s) == 4 and s[1] == "\\")): + return ord(s[-2]) + else: + raise CDefError("invalid constant %r" % (s,)) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '+'): + return self._parse_constant(exprnode.expr) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '-'): + return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] + # + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name == '__dotdotdotarray__'): + if partial_length_ok: + self._partial_length = True + return '...' + raise FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) + # + if isinstance(exprnode, pycparser.c_ast.BinaryOp): + left = self._parse_constant(exprnode.left) + right = self._parse_constant(exprnode.right) + if exprnode.op == '+': + return left + right + elif exprnode.op == '-': + return left - right + elif exprnode.op == '*': + return left * right + elif exprnode.op == '/': + return self._c_div(left, right) + elif exprnode.op == '%': + return left - self._c_div(left, right) * right + elif exprnode.op == '<<': + return left << right + elif exprnode.op == '>>': + return left >> right + elif exprnode.op == '&': + return left & right + elif exprnode.op == '|': + return left | right + elif exprnode.op == '^': + return left ^ right + # + raise FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) + + def _c_div(self, a, b): + result = a // b + if ((a < 0) ^ (b < 0)) and (a % b) != 0: + result += 1 + return result + + def _build_enum_type(self, explicit_name, decls): + if decls is not None: + partial = False + enumerators = [] + enumvalues = [] + nextenumvalue = 0 + for enum in decls.enumerators: + if _r_enum_dotdotdot.match(enum.name): + partial = True + continue + if enum.value is not None: + nextenumvalue = self._parse_constant(enum.value) + enumerators.append(enum.name) + enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) + nextenumvalue += 1 + enumerators = tuple(enumerators) + enumvalues = tuple(enumvalues) + tp = model.EnumType(explicit_name, enumerators, enumvalues) + tp.partial = partial + else: # opaque enum + tp = model.EnumType(explicit_name, (), ()) + return tp + + def include(self, other): + for name, (tp, quals) in other._declarations.items(): + if name.startswith('anonymous $enum_$'): + continue # fix for test_anonymous_enum_include + kind = name.split(' ', 1)[0] + if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef'): + self._declare(name, tp, included=True, quals=quals) + for k, v in other._int_constants.items(): + self._add_constants(k, v) + + def _get_unknown_type(self, decl): + typenames = decl.type.type.names + if typenames == ['__dotdotdot__']: + return model.unknown_type(decl.name) + + if typenames == ['__dotdotdotint__']: + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef int... %s'" % decl.name + return model.UnknownIntegerType(decl.name) + + if typenames == ['__dotdotdotfloat__']: + # note: not for 'long double' so far + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef float... %s'" % decl.name + return model.UnknownFloatType(decl.name) + + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) + + def _get_unknown_ptr_type(self, decl): + if decl.type.type.type.names == ['__dotdotdot__']: + return model.unknown_ptr_type(decl.name) + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py new file mode 100644 index 0000000..0a27247 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/error.py @@ -0,0 +1,31 @@ + +class FFIError(Exception): + __module__ = 'cffi' + +class CDefError(Exception): + __module__ = 'cffi' + def __str__(self): + try: + current_decl = self.args[1] + filename = current_decl.coord.file + linenum = current_decl.coord.line + prefix = '%s:%d: ' % (filename, linenum) + except (AttributeError, TypeError, IndexError): + prefix = '' + return '%s%s' % (prefix, self.args[0]) + +class VerificationError(Exception): + """ An error raised when verification fails + """ + __module__ = 'cffi' + +class VerificationMissing(Exception): + """ An error raised when incomplete structures are passed into + cdef, but no verification has been done + """ + __module__ = 'cffi' + +class PkgConfigError(Exception): + """ An error raised for missing modules in pkg-config + """ + __module__ = 'cffi' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py new file mode 100644 index 0000000..adca28f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py @@ -0,0 +1,113 @@ +import sys, os +from .error import VerificationError + + +LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', + 'extra_objects', 'depends'] + +def get_extension(srcfilename, modname, sources=(), **kwds): + from cffi._shimmed_dist_utils import Extension + allsources = [srcfilename] + for src in sources: + allsources.append(os.path.normpath(src)) + return Extension(name=modname, sources=allsources, **kwds) + +def compile(tmpdir, ext, compiler_verbose=0, debug=None): + """Compile a C extension module using distutils.""" + + saved_environ = os.environ.copy() + try: + outputfilename = _build(tmpdir, ext, compiler_verbose, debug) + outputfilename = os.path.abspath(outputfilename) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename + +def _build(tmpdir, ext, compiler_verbose=0, debug=None): + # XXX compact but horrible :-( + from cffi._shimmed_dist_utils import Distribution, CompileError, LinkError, set_threshold, set_verbosity + + dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() + options = dist.get_option_dict('build_ext') + if debug is None: + debug = sys.flags.debug + options['debug'] = ('ffiplatform', debug) + options['force'] = ('ffiplatform', True) + options['build_lib'] = ('ffiplatform', tmpdir) + options['build_temp'] = ('ffiplatform', tmpdir) + # + try: + old_level = set_threshold(0) or 0 + try: + set_verbosity(compiler_verbose) + dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() + finally: + set_threshold(old_level) + except (CompileError, LinkError) as e: + raise VerificationError('%s: %s' % (e.__class__.__name__, e)) + # + return soname + +try: + from os.path import samefile +except ImportError: + def samefile(f1, f2): + return os.path.abspath(f1) == os.path.abspath(f2) + +def maybe_relative_path(path): + if not os.path.isabs(path): + return path # already relative + dir = path + names = [] + while True: + prevdir = dir + dir, name = os.path.split(prevdir) + if dir == prevdir or not dir: + return path # failed to make it relative + names.append(name) + try: + if samefile(dir, os.curdir): + names.reverse() + return os.path.join(*names) + except OSError: + pass + +# ____________________________________________________________ + +try: + int_or_long = (int, long) + import cStringIO +except NameError: + int_or_long = int # Python 3 + import io as cStringIO + +def _flatten(x, f): + if isinstance(x, str): + f.write('%ds%s' % (len(x), x)) + elif isinstance(x, dict): + keys = sorted(x.keys()) + f.write('%dd' % len(keys)) + for key in keys: + _flatten(key, f) + _flatten(x[key], f) + elif isinstance(x, (list, tuple)): + f.write('%dl' % len(x)) + for value in x: + _flatten(value, f) + elif isinstance(x, int_or_long): + f.write('%di' % (x,)) + else: + raise TypeError( + "the keywords to verify() contains unsupported object %r" % (x,)) + +def flatten(x): + f = cStringIO.StringIO() + _flatten(x, f) + return f.getvalue() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py new file mode 100644 index 0000000..8eb57b1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py @@ -0,0 +1,5 @@ +"""Entry point so ``python -m cffi.gen_src`` works.""" + +if __name__ == '__main__': + from cffi._cffi_gen_src import run + run() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py new file mode 100644 index 0000000..db91b71 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/lock.py @@ -0,0 +1,30 @@ +import sys + +if sys.version_info < (3,): + try: + from thread import allocate_lock + except ImportError: + from dummy_thread import allocate_lock +else: + try: + from _thread import allocate_lock + except ImportError: + from _dummy_thread import allocate_lock + + +##import sys +##l1 = allocate_lock + +##class allocate_lock(object): +## def __init__(self): +## self._real = l1() +## def __enter__(self): +## for i in range(4, 0, -1): +## print sys._getframe(i).f_code +## print +## return self._real.__enter__() +## def __exit__(self, *args): +## return self._real.__exit__(*args) +## def acquire(self, f): +## assert f is False +## return self._real.acquire(f) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py new file mode 100644 index 0000000..8bade1d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/model.py @@ -0,0 +1,617 @@ +import types +import weakref + +from .lock import allocate_lock +from .error import CDefError, VerificationError, VerificationMissing + +# type qualifiers +Q_CONST = 0x01 +Q_RESTRICT = 0x02 +Q_VOLATILE = 0x04 + +def qualify(quals, replace_with): + if quals & Q_CONST: + replace_with = ' const ' + replace_with.lstrip() + if quals & Q_VOLATILE: + replace_with = ' volatile ' + replace_with.lstrip() + if quals & Q_RESTRICT: + # It seems that __restrict is supported by gcc and msvc. + # If you hit some different compiler, add a #define in + # _cffi_include.h for it (and in its copies, documented there) + replace_with = ' __restrict ' + replace_with.lstrip() + return replace_with + + +class BaseTypeByIdentity: + is_array_type = False + is_raw_function = False + + def get_c_name(self, replace_with='', context='a C file', quals=0): + result = self.c_name_with_marker + assert result.count('&') == 1 + # some logic duplication with ffi.getctype()... :-( + replace_with = replace_with.strip() + if replace_with: + if replace_with.startswith('*') and '&[' in result: + replace_with = '(%s)' % replace_with + elif replace_with[0] not in '[(': + replace_with = ' ' + replace_with + replace_with = qualify(quals, replace_with) + result = result.replace('&', replace_with) + if '$' in result: + raise VerificationError( + "cannot generate '%s' in %s: unknown type name" + % (self._get_c_name(), context)) + return result + + def _get_c_name(self): + return self.c_name_with_marker.replace('&', '') + + def has_c_name(self): + return '$' not in self._get_c_name() + + def is_integer_type(self): + return False + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + try: + BType = ffi._cached_btypes[self] + except KeyError: + BType = self.build_backend_type(ffi, finishlist) + BType2 = ffi._cached_btypes.setdefault(self, BType) + assert BType2 is BType + return BType + + def __repr__(self): + return '<%s>' % (self._get_c_name(),) + + def _get_items(self): + return [(name, getattr(self, name)) for name in self._attrs_] + + +class BaseType(BaseTypeByIdentity): + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self._get_items() == other._get_items()) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.__class__, tuple(self._get_items()))) + + +class VoidType(BaseType): + _attrs_ = () + + def __init__(self): + self.c_name_with_marker = 'void&' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_void_type') + +void_type = VoidType() + + +class BasePrimitiveType(BaseType): + def is_complex_type(self): + return False + + +class PrimitiveType(BasePrimitiveType): + _attrs_ = ('name',) + + ALL_PRIMITIVE_TYPES = { + 'char': 'c', + 'short': 'i', + 'int': 'i', + 'long': 'i', + 'long long': 'i', + 'signed char': 'i', + 'unsigned char': 'i', + 'unsigned short': 'i', + 'unsigned int': 'i', + 'unsigned long': 'i', + 'unsigned long long': 'i', + 'float': 'f', + 'double': 'f', + 'long double': 'f', + '_cffi_float_complex_t': 'j', + '_cffi_double_complex_t': 'j', + '_Bool': 'i', + # the following types are not primitive in the C sense + 'wchar_t': 'c', + 'char16_t': 'c', + 'char32_t': 'c', + 'int8_t': 'i', + 'uint8_t': 'i', + 'int16_t': 'i', + 'uint16_t': 'i', + 'int32_t': 'i', + 'uint32_t': 'i', + 'int64_t': 'i', + 'uint64_t': 'i', + 'int_least8_t': 'i', + 'uint_least8_t': 'i', + 'int_least16_t': 'i', + 'uint_least16_t': 'i', + 'int_least32_t': 'i', + 'uint_least32_t': 'i', + 'int_least64_t': 'i', + 'uint_least64_t': 'i', + 'int_fast8_t': 'i', + 'uint_fast8_t': 'i', + 'int_fast16_t': 'i', + 'uint_fast16_t': 'i', + 'int_fast32_t': 'i', + 'uint_fast32_t': 'i', + 'int_fast64_t': 'i', + 'uint_fast64_t': 'i', + 'intptr_t': 'i', + 'uintptr_t': 'i', + 'intmax_t': 'i', + 'uintmax_t': 'i', + 'ptrdiff_t': 'i', + 'size_t': 'i', + 'ssize_t': 'i', + } + + def __init__(self, name): + assert name in self.ALL_PRIMITIVE_TYPES + self.name = name + self.c_name_with_marker = name + '&' + + def is_char_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'c' + def is_integer_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'i' + def is_float_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'f' + def is_complex_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'j' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_primitive_type', self.name) + + +class UnknownIntegerType(BasePrimitiveType): + _attrs_ = ('name',) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def is_integer_type(self): + return True + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("integer type '%s' can only be used after " + "compilation" % self.name) + +class UnknownFloatType(BasePrimitiveType): + _attrs_ = ('name', ) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("float type '%s' can only be used after " + "compilation" % self.name) + + +class BaseFunctionType(BaseType): + _attrs_ = ('args', 'result', 'ellipsis', 'abi') + + def __init__(self, args, result, ellipsis, abi=None): + self.args = args + self.result = result + self.ellipsis = ellipsis + self.abi = abi + # + reprargs = [arg._get_c_name() for arg in self.args] + if self.ellipsis: + reprargs.append('...') + reprargs = reprargs or ['void'] + replace_with = self._base_pattern % (', '.join(reprargs),) + if abi is not None: + replace_with = replace_with[:1] + abi + ' ' + replace_with[1:] + self.c_name_with_marker = ( + self.result.c_name_with_marker.replace('&', replace_with)) + + +class RawFunctionType(BaseFunctionType): + # Corresponds to a C type like 'int(int)', which is the C type of + # a function, but not a pointer-to-function. The backend has no + # notion of such a type; it's used temporarily by parsing. + _base_pattern = '(&)(%s)' + is_raw_function = True + + def build_backend_type(self, ffi, finishlist): + raise CDefError("cannot render the type %r: it is a function " + "type, not a pointer-to-function type" % (self,)) + + def as_function_pointer(self): + return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi) + + +class FunctionPtrType(BaseFunctionType): + _base_pattern = '(*&)(%s)' + + def build_backend_type(self, ffi, finishlist): + result = self.result.get_cached_btype(ffi, finishlist) + args = [] + for tp in self.args: + args.append(tp.get_cached_btype(ffi, finishlist)) + abi_args = () + if self.abi == "__stdcall": + if not self.ellipsis: # __stdcall ignored for variadic funcs + try: + abi_args = (ffi._backend.FFI_STDCALL,) + except AttributeError: + pass + return global_cache(self, ffi, 'new_function_type', + tuple(args), result, self.ellipsis, *abi_args) + + def as_raw_function(self): + return RawFunctionType(self.args, self.result, self.ellipsis, self.abi) + + +class PointerType(BaseType): + _attrs_ = ('totype', 'quals') + + def __init__(self, totype, quals=0): + self.totype = totype + self.quals = quals + extra = " *&" + if totype.is_array_type: + extra = "(%s)" % (extra.lstrip(),) + extra = qualify(quals, extra) + self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra) + + def build_backend_type(self, ffi, finishlist): + BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True) + return global_cache(self, ffi, 'new_pointer_type', BItem) + +voidp_type = PointerType(void_type) + +def ConstPointerType(totype): + return PointerType(totype, Q_CONST) + +const_voidp_type = ConstPointerType(void_type) + + +class NamedPointerType(PointerType): + _attrs_ = ('totype', 'name') + + def __init__(self, totype, name, quals=0): + PointerType.__init__(self, totype, quals) + self.name = name + self.c_name_with_marker = name + '&' + + +class ArrayType(BaseType): + _attrs_ = ('item', 'length') + is_array_type = True + + def __init__(self, item, length): + self.item = item + self.length = length + # + if length is None: + brackets = '&[]' + elif length == '...': + brackets = '&[/*...*/]' + else: + brackets = '&[%s]' % length + self.c_name_with_marker = ( + self.item.c_name_with_marker.replace('&', brackets)) + + def length_is_unknown(self): + return isinstance(self.length, str) + + def resolve_length(self, newlength): + return ArrayType(self.item, newlength) + + def build_backend_type(self, ffi, finishlist): + if self.length_is_unknown(): + raise CDefError("cannot render the type %r: unknown length" % + (self,)) + self.item.get_cached_btype(ffi, finishlist) # force the item BType + BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist) + return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length) + +char_array_type = ArrayType(PrimitiveType('char'), None) + + +class StructOrUnionOrEnum(BaseTypeByIdentity): + _attrs_ = ('name',) + forcename = None + + def build_c_name_with_marker(self): + name = self.forcename or '%s %s' % (self.kind, self.name) + self.c_name_with_marker = name + '&' + + def force_the_name(self, forcename): + self.forcename = forcename + self.build_c_name_with_marker() + + def get_official_name(self): + assert self.c_name_with_marker.endswith('&') + return self.c_name_with_marker[:-1] + + +class StructOrUnion(StructOrUnionOrEnum): + fixedlayout = None + completed = 0 + partial = False + packed = 0 + + def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None): + self.name = name + self.fldnames = fldnames + self.fldtypes = fldtypes + self.fldbitsize = fldbitsize + self.fldquals = fldquals + self.build_c_name_with_marker() + + def anonymous_struct_fields(self): + if self.fldtypes is not None: + for name, type in zip(self.fldnames, self.fldtypes): + if name == '' and isinstance(type, StructOrUnion): + yield type + + def enumfields(self, expand_anonymous_struct_union=True): + fldquals = self.fldquals + if fldquals is None: + fldquals = (0,) * len(self.fldnames) + for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes, + self.fldbitsize, fldquals): + if (name == '' and isinstance(type, StructOrUnion) + and expand_anonymous_struct_union): + # nested anonymous struct/union + yield from type.enumfields() + else: + yield (name, type, bitsize, quals) + + def force_flatten(self): + # force the struct or union to have a declaration that lists + # directly all fields returned by enumfields(), flattening + # nested anonymous structs/unions. + names = [] + types = [] + bitsizes = [] + fldquals = [] + for name, type, bitsize, quals in self.enumfields(): + names.append(name) + types.append(type) + bitsizes.append(bitsize) + fldquals.append(quals) + self.fldnames = tuple(names) + self.fldtypes = tuple(types) + self.fldbitsize = tuple(bitsizes) + self.fldquals = tuple(fldquals) + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist, + can_delay) + if not can_delay: + self.finish_backend_type(ffi, finishlist) + return BType + + def finish_backend_type(self, ffi, finishlist): + if self.completed: + if self.completed != 2: + raise NotImplementedError("recursive structure declaration " + "for '%s'" % (self.name,)) + return + BType = ffi._cached_btypes[self] + # + self.completed = 1 + # + if self.fldtypes is None: + pass # not completing it: it's an opaque struct + # + elif self.fixedlayout is None: + fldtypes = [tp.get_cached_btype(ffi, finishlist) + for tp in self.fldtypes] + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize)) + extra_flags = () + if self.packed: + if self.packed == 1: + extra_flags = (8,) # SF_PACKED + else: + extra_flags = (0, self.packed) + ffi._backend.complete_struct_or_union(BType, lst, self, + -1, -1, *extra_flags) + # + else: + fldtypes = [] + fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout + for i in range(len(self.fldnames)): + fsize = fieldsize[i] + ftype = self.fldtypes[i] + # + if isinstance(ftype, ArrayType) and ftype.length_is_unknown(): + # fix the length to match the total size + BItemType = ftype.item.get_cached_btype(ffi, finishlist) + nlen, nrest = divmod(fsize, ffi.sizeof(BItemType)) + if nrest != 0: + self._verification_error( + "field '%s.%s' has a bogus size?" % ( + self.name, self.fldnames[i] or '{}')) + ftype = ftype.resolve_length(nlen) + self.fldtypes = (self.fldtypes[:i] + (ftype,) + + self.fldtypes[i+1:]) + # + BFieldType = ftype.get_cached_btype(ffi, finishlist) + if isinstance(ftype, ArrayType) and ftype.length is None: + assert fsize == 0 + else: + bitemsize = ffi.sizeof(BFieldType) + if bitemsize != fsize: + self._verification_error( + "field '%s.%s' is declared as %d bytes, but is " + "really %d bytes" % (self.name, + self.fldnames[i] or '{}', + bitemsize, fsize)) + fldtypes.append(BFieldType) + # + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs)) + ffi._backend.complete_struct_or_union(BType, lst, self, + totalsize, totalalignment) + self.completed = 2 + + def _verification_error(self, msg): + raise VerificationError(msg) + + def check_not_partial(self): + if self.partial and self.fixedlayout is None: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + finishlist.append(self) + # + return global_cache(self, ffi, 'new_%s_type' % self.kind, + self.get_official_name(), key=self) + + +class StructType(StructOrUnion): + kind = 'struct' + + +class UnionType(StructOrUnion): + kind = 'union' + + +class EnumType(StructOrUnionOrEnum): + kind = 'enum' + partial = False + partial_resolved = False + + def __init__(self, name, enumerators, enumvalues, baseinttype=None): + self.name = name + self.enumerators = enumerators + self.enumvalues = enumvalues + self.baseinttype = baseinttype + self.build_c_name_with_marker() + + def force_the_name(self, forcename): + StructOrUnionOrEnum.force_the_name(self, forcename) + if self.forcename is None: + name = self.get_official_name() + self.forcename = '$' + name.replace(' ', '_') + + def check_not_partial(self): + if self.partial and not self.partial_resolved: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + base_btype = self.build_baseinttype(ffi, finishlist) + return global_cache(self, ffi, 'new_enum_type', + self.get_official_name(), + self.enumerators, self.enumvalues, + base_btype, key=self) + + def build_baseinttype(self, ffi, finishlist): + if self.baseinttype is not None: + return self.baseinttype.get_cached_btype(ffi, finishlist) + # + if self.enumvalues: + smallest_value = min(self.enumvalues) + largest_value = max(self.enumvalues) + else: + import warnings + try: + # XXX! The goal is to ensure that the warnings.warn() + # will not suppress the warning. We want to get it + # several times if we reach this point several times. + __warningregistry__.clear() + except NameError: + pass + warnings.warn("%r has no values explicitly defined; " + "guessing that it is equivalent to 'unsigned int'" + % self._get_c_name()) + smallest_value = largest_value = 0 + if smallest_value < 0: # needs a signed type + sign = 1 + candidate1 = PrimitiveType("int") + candidate2 = PrimitiveType("long") + else: + sign = 0 + candidate1 = PrimitiveType("unsigned int") + candidate2 = PrimitiveType("unsigned long") + btype1 = candidate1.get_cached_btype(ffi, finishlist) + btype2 = candidate2.get_cached_btype(ffi, finishlist) + size1 = ffi.sizeof(btype1) + size2 = ffi.sizeof(btype2) + if (smallest_value >= ((-1) << (8*size1-1)) and + largest_value < (1 << (8*size1-sign))): + return btype1 + if (smallest_value >= ((-1) << (8*size2-1)) and + largest_value < (1 << (8*size2-sign))): + return btype2 + raise CDefError("%s values don't all fit into either 'long' " + "or 'unsigned long'" % self._get_c_name()) + +def unknown_type(name, structname=None): + if structname is None: + structname = '$%s' % name + tp = StructType(structname, None, None, None) + tp.force_the_name(name) + tp.origin = "unknown_type" + return tp + +def unknown_ptr_type(name, structname=None): + if structname is None: + structname = '$$%s' % name + tp = StructType(structname, None, None, None) + return NamedPointerType(tp, name) + + +global_lock = allocate_lock() +_typecache_cffi_backend = weakref.WeakValueDictionary() + +def get_typecache(backend): + # returns _typecache_cffi_backend if backend is the _cffi_backend + # module, or type(backend).__typecache if backend is an instance of + # CTypesBackend (or some FakeBackend class during tests) + if isinstance(backend, types.ModuleType): + return _typecache_cffi_backend + with global_lock: + if not hasattr(type(backend), '__typecache'): + type(backend).__typecache = weakref.WeakValueDictionary() + return type(backend).__typecache + +def global_cache(srctype, ffi, funcname, *args, **kwds): + key = kwds.pop('key', (funcname, args)) + assert not kwds + try: + return ffi._typecache[key] + except KeyError: + pass + try: + res = getattr(ffi._backend, funcname)(*args) + except NotImplementedError as e: + raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e)) + # note that setdefault() on WeakValueDictionary is not atomic + # and contains a rare bug (http://bugs.python.org/issue19542); + # we have to use a lock and do it ourselves + cache = ffi._typecache + with global_lock: + res1 = cache.get(key) + if res1 is None: + cache[key] = res + return res + else: + return res1 + +def pointer_cache(ffi, BType): + return global_cache('?', ffi, 'new_pointer_type', BType) + +def attach_exception_info(e, name): + if e.args and type(e.args[0]) is str: + e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h new file mode 100644 index 0000000..84e4ef8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h @@ -0,0 +1,181 @@ + +/* This part is from file 'cffi/parse_c_type.h'. It is copied at the + beginning of C sources generated by CFFI's ffi.set_source(). */ + +typedef void *_cffi_opcode_t; + +#define _CFFI_OP(opcode, arg) (_cffi_opcode_t)(opcode | (((uintptr_t)(arg)) << 8)) +#define _CFFI_GETOP(cffi_opcode) ((unsigned char)(uintptr_t)cffi_opcode) +#define _CFFI_GETARG(cffi_opcode) (((intptr_t)cffi_opcode) >> 8) + +#define _CFFI_OP_PRIMITIVE 1 +#define _CFFI_OP_POINTER 3 +#define _CFFI_OP_ARRAY 5 +#define _CFFI_OP_OPEN_ARRAY 7 +#define _CFFI_OP_STRUCT_UNION 9 +#define _CFFI_OP_ENUM 11 +#define _CFFI_OP_FUNCTION 13 +#define _CFFI_OP_FUNCTION_END 15 +#define _CFFI_OP_NOOP 17 +#define _CFFI_OP_BITFIELD 19 +#define _CFFI_OP_TYPENAME 21 +#define _CFFI_OP_CPYTHON_BLTN_V 23 // varargs +#define _CFFI_OP_CPYTHON_BLTN_N 25 // noargs +#define _CFFI_OP_CPYTHON_BLTN_O 27 // O (i.e. a single arg) +#define _CFFI_OP_CONSTANT 29 +#define _CFFI_OP_CONSTANT_INT 31 +#define _CFFI_OP_GLOBAL_VAR 33 +#define _CFFI_OP_DLOPEN_FUNC 35 +#define _CFFI_OP_DLOPEN_CONST 37 +#define _CFFI_OP_GLOBAL_VAR_F 39 +#define _CFFI_OP_EXTERN_PYTHON 41 + +#define _CFFI_PRIM_VOID 0 +#define _CFFI_PRIM_BOOL 1 +#define _CFFI_PRIM_CHAR 2 +#define _CFFI_PRIM_SCHAR 3 +#define _CFFI_PRIM_UCHAR 4 +#define _CFFI_PRIM_SHORT 5 +#define _CFFI_PRIM_USHORT 6 +#define _CFFI_PRIM_INT 7 +#define _CFFI_PRIM_UINT 8 +#define _CFFI_PRIM_LONG 9 +#define _CFFI_PRIM_ULONG 10 +#define _CFFI_PRIM_LONGLONG 11 +#define _CFFI_PRIM_ULONGLONG 12 +#define _CFFI_PRIM_FLOAT 13 +#define _CFFI_PRIM_DOUBLE 14 +#define _CFFI_PRIM_LONGDOUBLE 15 + +#define _CFFI_PRIM_WCHAR 16 +#define _CFFI_PRIM_INT8 17 +#define _CFFI_PRIM_UINT8 18 +#define _CFFI_PRIM_INT16 19 +#define _CFFI_PRIM_UINT16 20 +#define _CFFI_PRIM_INT32 21 +#define _CFFI_PRIM_UINT32 22 +#define _CFFI_PRIM_INT64 23 +#define _CFFI_PRIM_UINT64 24 +#define _CFFI_PRIM_INTPTR 25 +#define _CFFI_PRIM_UINTPTR 26 +#define _CFFI_PRIM_PTRDIFF 27 +#define _CFFI_PRIM_SIZE 28 +#define _CFFI_PRIM_SSIZE 29 +#define _CFFI_PRIM_INT_LEAST8 30 +#define _CFFI_PRIM_UINT_LEAST8 31 +#define _CFFI_PRIM_INT_LEAST16 32 +#define _CFFI_PRIM_UINT_LEAST16 33 +#define _CFFI_PRIM_INT_LEAST32 34 +#define _CFFI_PRIM_UINT_LEAST32 35 +#define _CFFI_PRIM_INT_LEAST64 36 +#define _CFFI_PRIM_UINT_LEAST64 37 +#define _CFFI_PRIM_INT_FAST8 38 +#define _CFFI_PRIM_UINT_FAST8 39 +#define _CFFI_PRIM_INT_FAST16 40 +#define _CFFI_PRIM_UINT_FAST16 41 +#define _CFFI_PRIM_INT_FAST32 42 +#define _CFFI_PRIM_UINT_FAST32 43 +#define _CFFI_PRIM_INT_FAST64 44 +#define _CFFI_PRIM_UINT_FAST64 45 +#define _CFFI_PRIM_INTMAX 46 +#define _CFFI_PRIM_UINTMAX 47 +#define _CFFI_PRIM_FLOATCOMPLEX 48 +#define _CFFI_PRIM_DOUBLECOMPLEX 49 +#define _CFFI_PRIM_CHAR16 50 +#define _CFFI_PRIM_CHAR32 51 + +#define _CFFI__NUM_PRIM 52 +#define _CFFI__UNKNOWN_PRIM (-1) +#define _CFFI__UNKNOWN_FLOAT_PRIM (-2) +#define _CFFI__UNKNOWN_LONG_DOUBLE (-3) + +#define _CFFI__IO_FILE_STRUCT (-1) + + +struct _cffi_global_s { + const char *name; + void *address; + _cffi_opcode_t type_op; + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function +}; + +struct _cffi_getconst_s { + unsigned long long value; + const struct _cffi_type_context_s *ctx; + int gindex; +}; + +struct _cffi_struct_union_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_STRUCT_UNION + int flags; // _CFFI_F_* flags below + size_t size; + int alignment; + int first_field_index; // -> _cffi_fields array + int num_fields; +}; +#define _CFFI_F_UNION 0x01 // is a union, not a struct +#define _CFFI_F_CHECK_FIELDS 0x02 // complain if fields are not in the + // "standard layout" or if some are missing +#define _CFFI_F_PACKED 0x04 // for CHECK_FIELDS, assume a packed struct +#define _CFFI_F_EXTERNAL 0x08 // in some other ffi.include() +#define _CFFI_F_OPAQUE 0x10 // opaque + +struct _cffi_field_s { + const char *name; + size_t field_offset; + size_t field_size; + _cffi_opcode_t field_type_op; +}; + +struct _cffi_enum_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_ENUM + int type_prim; // _CFFI_PRIM_xxx + const char *enumerators; // comma-delimited string +}; + +struct _cffi_typename_s { + const char *name; + int type_index; /* if opaque, points to a possibly artificial + OP_STRUCT which is itself opaque */ +}; + +struct _cffi_type_context_s { + _cffi_opcode_t *types; + const struct _cffi_global_s *globals; + const struct _cffi_field_s *fields; + const struct _cffi_struct_union_s *struct_unions; + const struct _cffi_enum_s *enums; + const struct _cffi_typename_s *typenames; + int num_globals; + int num_struct_unions; + int num_enums; + int num_typenames; + const char *const *includes; + int num_types; + int flags; /* future extension */ +}; + +struct _cffi_parse_info_s { + const struct _cffi_type_context_s *ctx; + _cffi_opcode_t *output; + unsigned int output_size; + size_t error_location; + const char *error_message; +}; + +struct _cffi_externpy_s { + const char *name; + size_t size_of_result; + void *reserved1, *reserved2; +}; + +#ifdef _CFFI_INTERNAL +static int parse_c_type(struct _cffi_parse_info_s *info, const char *input); +static int search_in_globals(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +static int search_in_struct_unions(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +#endif diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py new file mode 100644 index 0000000..e1034ad --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py @@ -0,0 +1,121 @@ +# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi +import sys, os, subprocess + +from .error import PkgConfigError + + +def merge_flags(cfg1, cfg2): + """Merge values from cffi config flags cfg2 to cf1 + + Example: + merge_flags({"libraries": ["one"]}, {"libraries": ["two"]}) + {"libraries": ["one", "two"]} + """ + for key, value in cfg2.items(): + if key not in cfg1: + cfg1[key] = value + else: + if not isinstance(cfg1[key], list): + raise TypeError("cfg1[%r] should be a list of strings" % (key,)) + if not isinstance(value, list): + raise TypeError("cfg2[%r] should be a list of strings" % (key,)) + cfg1[key].extend(value) + return cfg1 + + +def call(libname, flag, encoding=sys.getfilesystemencoding()): + """Calls pkg-config and returns the output if found + """ + a = ["pkg-config", "--print-errors"] + a.append(flag) + a.append(libname) + try: + pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError as e: + raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),)) + + bout, berr = pc.communicate() + if pc.returncode != 0: + try: + berr = berr.decode(encoding) + except Exception: + pass + raise PkgConfigError(berr.strip()) + + if sys.version_info >= (3,) and not isinstance(bout, str): # Python 3.x + try: + bout = bout.decode(encoding) + except UnicodeDecodeError: + raise PkgConfigError("pkg-config %s %s returned bytes that cannot " + "be decoded with encoding %r:\n%r" % + (flag, libname, encoding, bout)) + + if os.altsep != '\\' and '\\' in bout: + raise PkgConfigError("pkg-config %s %s returned an unsupported " + "backslash-escaped output:\n%r" % + (flag, libname, bout)) + return bout + + +def flags_from_pkgconfig(libs): + r"""Return compiler line flags for FFI.set_source based on pkg-config output + + Usage + ... + ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"]) + + If pkg-config is installed on build machine, then arguments include_dirs, + library_dirs, libraries, define_macros, extra_compile_args and + extra_link_args are extended with an output of pkg-config for libfoo and + libbar. + + Raises PkgConfigError in case the pkg-config call fails. + """ + + def get_include_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-I")] + + def get_library_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-L")] + + def get_libraries(string): + return [x[2:] for x in string.split() if x.startswith("-l")] + + # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils + def get_macros(string): + def _macro(x): + x = x[2:] # drop "-D" + if '=' in x: + return tuple(x.split("=", 1)) # "-Dfoo=bar" => ("foo", "bar") + else: + return (x, None) # "-Dfoo" => ("foo", None) + return [_macro(x) for x in string.split() if x.startswith("-D")] + + def get_other_cflags(string): + return [x for x in string.split() if not x.startswith("-I") and + not x.startswith("-D")] + + def get_other_libs(string): + return [x for x in string.split() if not x.startswith("-L") and + not x.startswith("-l")] + + # return kwargs for given libname + def kwargs(libname): + fse = sys.getfilesystemencoding() + all_cflags = call(libname, "--cflags") + all_libs = call(libname, "--libs") + return { + "include_dirs": get_include_dirs(all_cflags), + "library_dirs": get_library_dirs(all_libs), + "libraries": get_libraries(all_libs), + "define_macros": get_macros(all_cflags), + "extra_compile_args": get_other_cflags(all_cflags), + "extra_link_args": get_other_libs(all_libs), + } + + # merge all arguments together + ret = {} + for libname in libs: + lib_flags = kwargs(libname) + merge_flags(ret, lib_flags) + return ret diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py new file mode 100644 index 0000000..5e71aed --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py @@ -0,0 +1,1586 @@ +import io, os, sys, sysconfig +from . import ffiplatform, model +from .error import VerificationError +from .cffi_opcode import * + +VERSION_BASE = 0x2601 +VERSION_EMBEDDED = 0x2701 +VERSION_CHAR16CHAR32 = 0x2801 + +FREE_THREADED_BUILD = sysconfig.get_config_var("Py_GIL_DISABLED") +USE_LIMITED_API = ((sys.platform != 'win32' or sys.version_info < (3, 0) or + sys.version_info >= (3, 5)) and + # free-threaded build doesn't support the stable ABI until Python 3.15 + (not FREE_THREADED_BUILD or sys.version_info >= (3, 15))) + +class GlobalExpr: + def __init__(self, name, address, type_op, size=0, check_value=0): + self.name = name + self.address = address + self.type_op = type_op + self.size = size + self.check_value = check_value + + def as_c_expr(self): + return ' { "%s", (void *)%s, %s, (void *)%s },' % ( + self.name, self.address, self.type_op.as_c_expr(), self.size) + + def as_python_expr(self): + return "b'%s%s',%d" % (self.type_op.as_python_bytes(), self.name, + self.check_value) + +class FieldExpr: + def __init__(self, name, field_offset, field_size, fbitsize, field_type_op): + self.name = name + self.field_offset = field_offset + self.field_size = field_size + self.fbitsize = fbitsize + self.field_type_op = field_type_op + + def as_c_expr(self): + spaces = " " * len(self.name) + return (' { "%s", %s,\n' % (self.name, self.field_offset) + + ' %s %s,\n' % (spaces, self.field_size) + + ' %s %s },' % (spaces, self.field_type_op.as_c_expr())) + + def as_python_expr(self): + raise NotImplementedError + + def as_field_python_expr(self): + if self.field_type_op.op == OP_NOOP: + size_expr = '' + elif self.field_type_op.op == OP_BITFIELD: + size_expr = format_four_bytes(self.fbitsize) + else: + raise NotImplementedError + return "b'%s%s%s'" % (self.field_type_op.as_python_bytes(), + size_expr, + self.name) + +class StructUnionExpr: + def __init__(self, name, type_index, flags, size, alignment, comment, + first_field_index, c_fields): + self.name = name + self.type_index = type_index + self.flags = flags + self.size = size + self.alignment = alignment + self.comment = comment + self.first_field_index = first_field_index + self.c_fields = c_fields + + def as_c_expr(self): + return (' { "%s", %d, %s,' % (self.name, self.type_index, self.flags) + + '\n %s, %s, ' % (self.size, self.alignment) + + '%d, %d ' % (self.first_field_index, len(self.c_fields)) + + ('/* %s */ ' % self.comment if self.comment else '') + + '},') + + def as_python_expr(self): + flags = eval(self.flags, G_FLAGS) + fields_expr = [c_field.as_field_python_expr() + for c_field in self.c_fields] + return "(b'%s%s%s',%s)" % ( + format_four_bytes(self.type_index), + format_four_bytes(flags), + self.name, + ','.join(fields_expr)) + +class EnumExpr: + def __init__(self, name, type_index, size, signed, allenums): + self.name = name + self.type_index = type_index + self.size = size + self.signed = signed + self.allenums = allenums + + def as_c_expr(self): + lines = [' { "%s", %d, _cffi_prim_int(%s, %s),' % (self.name, self.type_index, + self.size, self.signed,)] + pending = 0 + while len(self.allenums) > pending + 110: + j = self.allenums.find(',', pending + 100) + if j < 0: + break + j += 1 + lines.append(' "%s"' % (self.allenums[pending:j],)) + pending = j + lines.append(' "%s" },' % (self.allenums[pending:],)) + return '\n'.join(lines) + + def as_python_expr(self): + prim_index = { + (1, 0): PRIM_UINT8, (1, 1): PRIM_INT8, + (2, 0): PRIM_UINT16, (2, 1): PRIM_INT16, + (4, 0): PRIM_UINT32, (4, 1): PRIM_INT32, + (8, 0): PRIM_UINT64, (8, 1): PRIM_INT64, + }[self.size, self.signed] + return "b'%s%s%s\\x00%s'" % (format_four_bytes(self.type_index), + format_four_bytes(prim_index), + self.name, self.allenums) + +class TypenameExpr: + def __init__(self, name, type_index): + self.name = name + self.type_index = type_index + + def as_c_expr(self): + return ' { "%s", %d },' % (self.name, self.type_index) + + def as_python_expr(self): + return "b'%s%s'" % (format_four_bytes(self.type_index), self.name) + + +# ____________________________________________________________ + + +class Recompiler: + _num_externpy = 0 + + def __init__(self, ffi, module_name, target_is_python=False): + self.ffi = ffi + self.module_name = module_name + self.target_is_python = target_is_python + self._version = VERSION_BASE + + def needs_version(self, ver): + self._version = max(self._version, ver) + + def collect_type_table(self): + self._typesdict = {} + self._generate("collecttype") + # + all_decls = sorted(self._typesdict, key=str) + # + # prepare all FUNCTION bytecode sequences first + self.cffi_types = [] + for tp in all_decls: + if tp.is_raw_function: + assert self._typesdict[tp] is None + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + for tp1 in tp.args: + assert isinstance(tp1, (model.VoidType, + model.BasePrimitiveType, + model.PointerType, + model.StructOrUnionOrEnum, + model.FunctionPtrType)) + if self._typesdict[tp1] is None: + self._typesdict[tp1] = len(self.cffi_types) + self.cffi_types.append(tp1) # placeholder + self.cffi_types.append('END') # placeholder + # + # prepare all OTHER bytecode sequences + for tp in all_decls: + if not tp.is_raw_function and self._typesdict[tp] is None: + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + if tp.is_array_type and tp.length is not None: + self.cffi_types.append('LEN') # placeholder + assert None not in self._typesdict.values() + # + # collect all structs and unions and enums + self._struct_unions = {} + self._enums = {} + for tp in all_decls: + if isinstance(tp, model.StructOrUnion): + self._struct_unions[tp] = None + elif isinstance(tp, model.EnumType): + self._enums[tp] = None + for i, tp in enumerate(sorted(self._struct_unions, + key=lambda tp: tp.name)): + self._struct_unions[tp] = i + for i, tp in enumerate(sorted(self._enums, + key=lambda tp: tp.name)): + self._enums[tp] = i + # + # emit all bytecode sequences now + for tp in all_decls: + method = getattr(self, '_emit_bytecode_' + tp.__class__.__name__) + method(tp, self._typesdict[tp]) + # + # consistency check + for op in self.cffi_types: + assert isinstance(op, CffiOp) + self.cffi_types = tuple(self.cffi_types) # don't change any more + + def _enum_fields(self, tp): + # When producing C, expand all anonymous struct/union fields. + # That's necessary to have C code checking the offsets of the + # individual fields contained in them. When producing Python, + # don't do it and instead write it like it is, with the + # corresponding fields having an empty name. Empty names are + # recognized at runtime when we import the generated Python + # file. + expand_anonymous_struct_union = not self.target_is_python + return tp.enumfields(expand_anonymous_struct_union) + + def _do_collect_type(self, tp): + if not isinstance(tp, model.BaseTypeByIdentity): + if isinstance(tp, tuple): + for x in tp: + self._do_collect_type(x) + return + if tp not in self._typesdict: + self._typesdict[tp] = None + if isinstance(tp, model.FunctionPtrType): + self._do_collect_type(tp.as_raw_function()) + elif isinstance(tp, model.StructOrUnion): + if tp.fldtypes is not None and ( + tp not in self.ffi._parser._included_declarations): + for name1, tp1, _, _ in self._enum_fields(tp): + self._do_collect_type(self._field_type(tp, name1, tp1)) + else: + for _, x in tp._get_items(): + self._do_collect_type(x) + + def _generate(self, step_name): + lst = self.ffi._parser._declarations.items() + for name, (tp, quals) in sorted(lst): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in recompile(): %r" % name) + try: + self._current_quals = quals + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + # ---------- + + ALL_STEPS = ["global", "field", "struct_union", "enum", "typename"] + + def collect_step_tables(self): + # collect the declarations for '_cffi_globals', '_cffi_typenames', etc. + self._lsts = {} + for step_name in self.ALL_STEPS: + self._lsts[step_name] = [] + self._seen_struct_unions = set() + self._generate("ctx") + self._add_missing_struct_unions() + # + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if step_name != "field": + lst.sort(key=lambda entry: entry.name) + self._lsts[step_name] = tuple(lst) # don't change any more + # + # check for a possible internal inconsistency: _cffi_struct_unions + # should have been generated with exactly self._struct_unions + lst = self._lsts["struct_union"] + for tp, i in self._struct_unions.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._struct_unions) + # same with enums + lst = self._lsts["enum"] + for tp, i in self._enums.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._enums) + + # ---------- + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self, f, preamble): + if self.target_is_python: + assert preamble is None + self.write_py_source_to_f(f) + else: + assert preamble is not None + self.write_c_source_to_f(f, preamble) + + def _rel_readlines(self, filename): + with open(os.path.join(os.path.dirname(__file__), filename), 'r') as g: + return g.readlines() + + def write_c_source_to_f(self, f, preamble): + self._f = f + prnt = self._prnt + if self.ffi._embedding is not None: + prnt('#define _CFFI_USE_EMBEDDING') + if not USE_LIMITED_API: + prnt('#define _CFFI_NO_LIMITED_API') + # + # first the '#include' (actually done by inlining the file's content) + lines = self._rel_readlines('_cffi_include.h') + i = lines.index('#include "parse_c_type.h"\n') + lines[i:i+1] = self._rel_readlines('parse_c_type.h') + prnt(''.join(lines)) + # + # if we have ffi._embedding != None, we give it here as a macro + # and include an extra file + base_module_name = self.module_name.split('.')[-1] + if self.ffi._embedding is not None: + prnt('#define _CFFI_MODULE_NAME "%s"' % (self.module_name,)) + prnt('static const char _CFFI_PYTHON_STARTUP_CODE[] = {') + self._print_string_literal_in_array(self.ffi._embedding) + prnt('0 };') + prnt('#ifdef PYPY_VERSION') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC _cffi_pypyinit_%s' % ( + base_module_name,)) + prnt('#else') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC PyInit_%s' % ( + base_module_name,)) + prnt('#endif') + lines = self._rel_readlines('_embedding.h') + i = lines.index('#include "_cffi_errors.h"\n') + lines[i:i+1] = self._rel_readlines('_cffi_errors.h') + prnt(''.join(lines)) + self.needs_version(VERSION_EMBEDDED) + # + # then paste the C source given by the user, verbatim. + prnt('/************************************************************/') + prnt() + prnt(preamble) + prnt() + prnt('/************************************************************/') + prnt() + # + # the declaration of '_cffi_types' + prnt('static void *_cffi_types[] = {') + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + for i, op in enumerate(self.cffi_types): + comment = '' + if i in typeindex2type: + comment = ' // ' + typeindex2type[i]._get_c_name() + prnt('/* %2d */ %s,%s' % (i, op.as_c_expr(), comment)) + if not self.cffi_types: + prnt(' 0') + prnt('};') + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._seen_constants = set() + self._generate("decl") + # + # the declaration of '_cffi_globals' and '_cffi_typenames' + nums = {} + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + nums[step_name] = len(lst) + if nums[step_name] > 0: + prnt('static const struct _cffi_%s_s _cffi_%ss[] = {' % ( + step_name, step_name)) + for entry in lst: + prnt(entry.as_c_expr()) + prnt('};') + prnt() + # + # the declaration of '_cffi_includes' + if self.ffi._included_ffis: + prnt('static const char * const _cffi_includes[] = {') + for ffi_to_include in self.ffi._included_ffis: + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is None: + raise VerificationError( + "not implemented yet: ffi.include() of a Python-based " + "ffi inside a C-based ffi") + prnt(' "%s",' % (included_module_name,)) + prnt(' NULL') + prnt('};') + prnt() + # + # the declaration of '_cffi_type_context' + prnt('static const struct _cffi_type_context_s _cffi_type_context = {') + prnt(' _cffi_types,') + for step_name in self.ALL_STEPS: + if nums[step_name] > 0: + prnt(' _cffi_%ss,' % step_name) + else: + prnt(' NULL, /* no %ss */' % step_name) + for step_name in self.ALL_STEPS: + if step_name != "field": + prnt(' %d, /* num_%ss */' % (nums[step_name], step_name)) + if self.ffi._included_ffis: + prnt(' _cffi_includes,') + else: + prnt(' NULL, /* no includes */') + prnt(' %d, /* num_types */' % (len(self.cffi_types),)) + flags = 0 + if self._num_externpy > 0 or self.ffi._embedding is not None: + flags |= 1 # set to mean that we use extern "Python" + prnt(' %d, /* flags */' % flags) + prnt('};') + prnt() + # + # the init function + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility push(default) /* for -fvisibility= */') + prnt('#endif') + prnt() + prnt('#ifdef PYPY_VERSION') + prnt('PyMODINIT_FUNC') + prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,)) + prnt('{') + if flags & 1: + prnt(' if (((intptr_t)p[0]) >= 0x0A03) {') + prnt(' _cffi_call_python_org = ' + '(void(*)(struct _cffi_externpy_s *, char *))p[1];') + prnt(' }') + prnt(' p[0] = (const void *)0x%x;' % self._version) + prnt(' p[1] = &_cffi_type_context;') + prnt(' return NULL;') + prnt('}') + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + prnt('# ifdef _MSC_VER') + prnt(' PyMODINIT_FUNC') + prnt(' PyInit_%s(void) { return NULL; }' % (base_module_name,)) + prnt('# endif') + prnt('#else') + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % (base_module_name,)) + prnt('{') + prnt(' return _cffi_init("%s", 0x%x, &_cffi_type_context);' % ( + self.module_name, self._version)) + prnt('}') + prnt('#endif') + prnt() + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility pop') + prnt('#endif') + self._version = None + + def _to_py(self, x): + if isinstance(x, str): + return "b'%s'" % (x,) + if isinstance(x, (list, tuple)): + rep = [self._to_py(item) for item in x] + if len(rep) == 1: + rep.append('') + return "(%s)" % (','.join(rep),) + return x.as_python_expr() # Py2: unicode unexpected; Py3: bytes unexp. + + def write_py_source_to_f(self, f): + self._f = f + prnt = self._prnt + # + # header + prnt("# auto-generated file") + prnt("import _cffi_backend") + # + # the 'import' of the included ffis + num_includes = len(self.ffi._included_ffis or ()) + for i in range(num_includes): + ffi_to_include = self.ffi._included_ffis[i] + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is not None: + raise VerificationError( + "not implemented yet: ffi.include() of a C-based " + "ffi inside a Python-based ffi") + prnt('from %s import ffi as _ffi%d' % (included_module_name, i)) + prnt() + prnt("ffi = _cffi_backend.FFI('%s'," % (self.module_name,)) + prnt(" _version = 0x%x," % (self._version,)) + self._version = None + # + # the '_types' keyword argument + self.cffi_types = tuple(self.cffi_types) # don't change any more + types_lst = [op.as_python_bytes() for op in self.cffi_types] + prnt(' _types = %s,' % (self._to_py(''.join(types_lst)),)) + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + # + # the keyword arguments from ALL_STEPS + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if len(lst) > 0 and step_name != "field": + prnt(' _%ss = %s,' % (step_name, self._to_py(lst))) + # + # the '_includes' keyword argument + if num_includes > 0: + prnt(' _includes = (%s,),' % ( + ', '.join(['_ffi%d' % i for i in range(num_includes)]),)) + # + # the footer + prnt(')') + + # ---------- + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.BasePrimitiveType) and not tp.is_complex_type(): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif isinstance(tp, model.UnknownFloatType): + # don't check with is_float_type(): it may be a 'long + # double' here, and _cffi_to_c_double would loose precision + converter = '(%s)_cffi_to_c_double' % (tp.get_c_name(''),) + else: + cname = tp.get_c_name('') + converter = '(%s)_cffi_to_c_%s' % (cname, + tp.name.replace(' ', '_')) + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif (isinstance(tp, (model.StructOrUnionOrEnum, model.BasePrimitiveType))): + # a struct (not a struct pointer) as a function argument; + # or, a complex (the same code works) + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars, freelines): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + localvars.add('struct _cffi_freeme_s *large_args_free = NULL') + freelines.add('if (large_args_free != NULL)' + ' _cffi_free_array_arguments(large_args_free);') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' %s = ((size_t)datasize) <= 640 ? ' + '(%s)alloca((size_t)datasize) : NULL;' % ( + tovar, tp.get_c_name(''))) + self._prnt(' if (_cffi_convert_array_argument(_cffi_type(%d), %s, ' + '(char **)&%s,' % (self._gettypenum(tp), fromvar, tovar)) + self._prnt(' datasize, &large_args_free) < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.BasePrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif isinstance(tp, model.UnknownFloatType): + return '_cffi_from_c_double(%s)' % (var,) + elif tp.name != 'long double' and not tp.is_complex_type(): + cname = tp.name.replace(' ', '_') + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + return '_cffi_from_c_%s(%s)' % (cname, var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs + + def _typedef_type(self, tp, name): + return self._global_type(tp, "(*(%s *)0)" % (name,)) + + def _generate_cpy_typedef_collecttype(self, tp, name): + self._do_collect_type(self._typedef_type(tp, name)) + + def _generate_cpy_typedef_decl(self, tp, name): + pass + + def _typedef_ctx(self, tp, name): + type_index = self._typesdict[tp] + self._lsts["typename"].append(TypenameExpr(name, type_index)) + + def _generate_cpy_typedef_ctx(self, tp, name): + tp = self._typedef_type(tp, name) + self._typedef_ctx(tp, name) + if getattr(tp, "origin", None) == "unknown_type": + self._struct_ctx(tp, tp.name, approxname=None) + elif isinstance(tp, model.NamedPointerType): + self._struct_ctx(tp.totype, tp.totype.name, approxname=tp.name, + named_ptr=tp) + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + self._do_collect_type(tp.as_raw_function()) + if tp.ellipsis and not self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_function_decl(self, tp, name): + assert not self.target_is_python + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_constant_decl(tp, name) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + # + # ------------------------------ + # the 'd' version of the function, only for addressof(lib, 'func') + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arguments.append(type.get_c_name(' x%d' % i, context)) + call_arguments.append('x%d' % i) + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + name_and_arguments = '%s_cffi_d_%s(%s)' % (abi, name, repr_arguments) + prnt('static %s' % (tp.result.get_c_name(name_and_arguments),)) + prnt('{') + call_arguments = ', '.join(call_arguments) + result_code = 'return ' + if isinstance(tp.result, model.VoidType): + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, call_arguments)) + prnt('}') + # + prnt('#ifndef PYPY_VERSION') # ------------------------------ + # + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' x%d' % i, context) + prnt(' %s;' % arg) + # + localvars = set() + freelines = set() + for type in tp.args: + self._extra_local_variables(type, localvars, freelines) + for decl in sorted(localvars): + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + result_decl = ' %s;' % tp.result.get_c_name(' result', context) + prnt(result_decl) + prnt(' PyObject *pyresult;') + else: + result_decl = None + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_UnpackTuple(args, "%s", %d, %d, %s))' % ( + name, len(rng), len(rng), + ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + call_arguments = ['x%d' % i for i in range(len(tp.args))] + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' pyresult = %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + for freeline in freelines: + prnt(' ' + freeline) + prnt(' return pyresult;') + else: + for freeline in freelines: + prnt(' ' + freeline) + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + # + prnt('#else') # ------------------------------ + # + # the PyPy version: need to replace struct/union arguments with + # pointers, and if the result is a struct/union, insert a first + # arg that is a pointer to the result. We also do that for + # complex args and return type. + def need_indirection(type): + return (isinstance(type, model.StructOrUnion) or + (isinstance(type, model.PrimitiveType) and + type.is_complex_type())) + difference = False + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + indirection = '' + if need_indirection(type): + indirection = '*' + difference = True + arg = type.get_c_name(' %sx%d' % (indirection, i), context) + arguments.append(arg) + call_arguments.append('%sx%d' % (indirection, i)) + tp_result = tp.result + if need_indirection(tp_result): + context = 'result of %s' % name + arg = tp_result.get_c_name(' *result', context) + arguments.insert(0, arg) + tp_result = model.void_type + result_decl = None + result_code = '*result = ' + difference = True + if difference: + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s_cffi_f_%s(%s)' % (abi, name, + repr_arguments) + prnt('static %s' % (tp_result.get_c_name(name_and_arguments),)) + prnt('{') + if result_decl: + prnt(result_decl) + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + if result_decl: + prnt(' return result;') + prnt('}') + else: + prnt('# define _cffi_f_%s _cffi_d_%s' % (name, name)) + # + prnt('#endif') # ------------------------------ + prnt() + + def _generate_cpy_function_ctx(self, tp, name): + if tp.ellipsis and not self.target_is_python: + self._generate_cpy_constant_ctx(tp, name) + return + type_index = self._typesdict[tp.as_raw_function()] + numargs = len(tp.args) + if self.target_is_python: + meth_kind = OP_DLOPEN_FUNC + elif numargs == 0: + meth_kind = OP_CPYTHON_BLTN_N # 'METH_NOARGS' + elif numargs == 1: + meth_kind = OP_CPYTHON_BLTN_O # 'METH_O' + else: + meth_kind = OP_CPYTHON_BLTN_V # 'METH_VARARGS' + self._lsts["global"].append( + GlobalExpr(name, '_cffi_f_%s' % name, + CffiOp(meth_kind, type_index), + size='_cffi_d_%s' % name)) + + # ---------- + # named structs or unions + + def _field_type(self, tp_struct, field_name, tp_field): + if isinstance(tp_field, model.ArrayType): + actual_length = tp_field.length + if actual_length == '...': + ptr_struct_name = tp_struct.get_c_name('*') + actual_length = '_cffi_array_len(((%s)0)->%s)' % ( + ptr_struct_name, field_name) + tp_item = self._field_type(tp_struct, '%s[0]' % field_name, + tp_field.item) + tp_field = model.ArrayType(tp_item, actual_length) + return tp_field + + def _struct_collecttype(self, tp): + self._do_collect_type(tp) + if self.target_is_python: + # also requires nested anon struct/unions in ABI mode, recursively + for fldtype in tp.anonymous_struct_fields(): + self._struct_collecttype(fldtype) + + def _struct_decl(self, tp, cname, approxname): + if tp.fldtypes is None: + return + prnt = self._prnt + checkfuncname = '_cffi_checkfld_%s' % (approxname,) + prnt('_CFFI_UNUSED_FN') + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in self._enum_fields(tp): + try: + if ftype.is_integer_type() or fbitsize >= 0: + # accept all integers, but complain on float or double + if fname != '': + prnt(" (void)((p->%s) | 0); /* check that '%s.%s' is " + "an integer */" % (fname, cname, fname)) + continue + # only accept exactly the type declared, except that '[]' + # is interpreted as a '*' and so will match any array length. + # (It would also match '*', but that's harder to detect...) + while (isinstance(ftype, model.ArrayType) + and (ftype.length is None or ftype.length == '...')): + ftype = ftype.item + fname = fname + '[0]' + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('struct _cffi_align_%s { char x; %s y; };' % (approxname, cname)) + prnt() + + def _struct_ctx(self, tp, cname, approxname, named_ptr=None): + type_index = self._typesdict[tp] + reason_for_not_expanding = None + flags = [] + if isinstance(tp, model.UnionType): + flags.append("_CFFI_F_UNION") + if tp.fldtypes is None: + flags.append("_CFFI_F_OPAQUE") + reason_for_not_expanding = "opaque" + if (tp not in self.ffi._parser._included_declarations and + (named_ptr is None or + named_ptr not in self.ffi._parser._included_declarations)): + if tp.fldtypes is None: + pass # opaque + elif tp.partial or any(tp.anonymous_struct_fields()): + pass # field layout obtained silently from the C compiler + else: + flags.append("_CFFI_F_CHECK_FIELDS") + if tp.packed: + if tp.packed > 1: + raise NotImplementedError( + "%r is declared with 'pack=%r'; only 0 or 1 are " + "supported in API mode (try to use \"...;\", which " + "does not require a 'pack' declaration)" % + (tp, tp.packed)) + flags.append("_CFFI_F_PACKED") + else: + flags.append("_CFFI_F_EXTERNAL") + reason_for_not_expanding = "external" + flags = '|'.join(flags) or '0' + c_fields = [] + if reason_for_not_expanding is None: + enumfields = list(self._enum_fields(tp)) + for fldname, fldtype, fbitsize, fqual in enumfields: + fldtype = self._field_type(tp, fldname, fldtype) + self._check_not_opaque(fldtype, + "field '%s.%s'" % (tp.name, fldname)) + # cname is None for _add_missing_struct_unions() only + op = OP_NOOP + if fbitsize >= 0: + op = OP_BITFIELD + size = '%d /* bits */' % fbitsize + elif cname is None or ( + isinstance(fldtype, model.ArrayType) and + fldtype.length is None): + size = '(size_t)-1' + else: + size = 'sizeof(((%s)0)->%s)' % ( + tp.get_c_name('*') if named_ptr is None + else named_ptr.name, + fldname) + if cname is None or fbitsize >= 0: + offset = '(size_t)-1' + elif named_ptr is not None: + offset = '(size_t)(((char *)&((%s)4096)->%s) - (char *)4096)' % ( + named_ptr.name, fldname) + else: + offset = 'offsetof(%s, %s)' % (tp.get_c_name(''), fldname) + c_fields.append( + FieldExpr(fldname, offset, size, fbitsize, + CffiOp(op, self._typesdict[fldtype]))) + first_field_index = len(self._lsts["field"]) + self._lsts["field"].extend(c_fields) + # + if cname is None: # unknown name, for _add_missing_struct_unions + size = '(size_t)-2' + align = -2 + comment = "unnamed" + else: + if named_ptr is not None: + size = 'sizeof(*(%s)0)' % (named_ptr.name,) + align = '-1 /* unknown alignment */' + else: + size = 'sizeof(%s)' % (cname,) + align = 'offsetof(struct _cffi_align_%s, y)' % (approxname,) + comment = None + else: + size = '(size_t)-1' + align = -1 + first_field_index = -1 + comment = reason_for_not_expanding + self._lsts["struct_union"].append( + StructUnionExpr(tp.name, type_index, flags, size, align, comment, + first_field_index, c_fields)) + self._seen_struct_unions.add(tp) + + def _check_not_opaque(self, tp, location): + while isinstance(tp, model.ArrayType): + tp = tp.item + if isinstance(tp, model.StructOrUnion) and tp.fldtypes is None: + raise TypeError( + "%s is of an opaque type (not declared in cdef())" % location) + + def _add_missing_struct_unions(self): + # not very nice, but some struct declarations might be missing + # because they don't have any known C name. Check that they are + # not partial (we can't complete or verify them!) and emit them + # anonymously. + lst = list(self._struct_unions.items()) + lst.sort(key=lambda tp_order: tp_order[1]) + for tp, order in lst: + if tp not in self._seen_struct_unions: + if tp.partial: + raise NotImplementedError("internal inconsistency: %r is " + "partial but was not seen at " + "this point" % (tp,)) + if tp.name.startswith('$') and tp.name[1:].isdigit(): + approxname = tp.name[1:] + elif tp.name == '_IO_FILE' and tp.forcename == 'FILE': + approxname = 'FILE' + self._typedef_ctx(tp, 'FILE') + else: + raise NotImplementedError("internal inconsistency: %r" % + (tp,)) + self._struct_ctx(tp, None, approxname) + + def _generate_cpy_struct_collecttype(self, tp, name): + self._struct_collecttype(tp) + _generate_cpy_union_collecttype = _generate_cpy_struct_collecttype + + def _struct_names(self, tp): + cname = tp.get_c_name('') + if ' ' in cname: + return cname, cname.replace(' ', '_') + else: + return cname, '_' + cname + + def _generate_cpy_struct_decl(self, tp, name): + self._struct_decl(tp, *self._struct_names(tp)) + _generate_cpy_union_decl = _generate_cpy_struct_decl + + def _generate_cpy_struct_ctx(self, tp, name): + self._struct_ctx(tp, *self._struct_names(tp)) + _generate_cpy_union_ctx = _generate_cpy_struct_ctx + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_cpy_anonymous_collecttype(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_collecttype(tp, name) + else: + self._struct_collecttype(tp) + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp) + else: + self._struct_decl(tp, name, 'typedef_' + name) + + def _generate_cpy_anonymous_ctx(self, tp, name): + if isinstance(tp, model.EnumType): + self._enum_ctx(tp, name) + else: + self._struct_ctx(tp, name, 'typedef_' + name) + + # ---------- + # constants, declared with "static const ..." + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + check_value=None): + if (category, name) in self._seen_constants: + raise VerificationError( + "duplicate declaration of %s '%s'" % (category, name)) + self._seen_constants.add((category, name)) + # + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + if is_int: + prnt('static int %s(unsigned long long *o)' % funcname) + prnt('{') + prnt(' int n = (%s) <= 0;' % (name,)) + prnt(' *o = (unsigned long long)((%s) | 0);' + ' /* check that %s is an integer */' % (name, name)) + if check_value is not None: + if check_value > 0: + check_value = '%dU' % (check_value,) + prnt(' if (!_cffi_check_int(*o, n, %s))' % (check_value,)) + prnt(' n |= 2;') + prnt(' return n;') + prnt('}') + else: + assert check_value is None + prnt('static void %s(char *o)' % funcname) + prnt('{') + prnt(' *(%s)o = %s;' % (tp.get_c_name('*'), name)) + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = tp.is_integer_type() + if not is_int or self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + def _generate_cpy_constant_ctx(self, tp, name): + if not self.target_is_python and tp.is_integer_type(): + type_op = CffiOp(OP_CONSTANT_INT, -1) + else: + if self.target_is_python: + const_kind = OP_DLOPEN_CONST + else: + const_kind = OP_CONSTANT + type_index = self._typesdict[tp] + type_op = CffiOp(const_kind, type_index) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op)) + + # ---------- + # enums + + def _generate_cpy_enum_collecttype(self, tp, name): + self._do_collect_type(tp) + + def _generate_cpy_enum_decl(self, tp, name=None): + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator) + + def _enum_ctx(self, tp, cname): + type_index = self._typesdict[tp] + type_op = CffiOp(OP_ENUM, -1) + if self.target_is_python: + tp.check_not_partial() + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._lsts["global"].append( + GlobalExpr(enumerator, '_cffi_const_%s' % enumerator, type_op, + check_value=enumvalue)) + # + if cname is not None and '$' not in cname and not self.target_is_python: + size = "sizeof(%s)" % cname + signed = "((%s)-1) <= 0" % cname + else: + basetp = tp.build_baseinttype(self.ffi, []) + size = self.ffi.sizeof(basetp) + signed = int(int(self.ffi.cast(basetp, -1)) < 0) + allenums = ",".join(tp.enumerators) + self._lsts["enum"].append( + EnumExpr(tp.name, type_index, size, signed, allenums)) + + def _generate_cpy_enum_ctx(self, tp, name): + self._enum_ctx(tp, tp._get_c_name()) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_collecttype(self, tp, name): + pass + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + def _generate_cpy_macro_ctx(self, tp, name): + if tp == '...': + if self.target_is_python: + raise VerificationError( + "cannot use the syntax '...' in '#define %s ...' when " + "using the ABI mode" % (name,)) + check_value = None + else: + check_value = tp # an integer + type_op = CffiOp(OP_CONSTANT_INT, -1) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op, + check_value=check_value)) + + # ---------- + # global variables + + def _global_type(self, tp, global_name): + if isinstance(tp, model.ArrayType): + actual_length = tp.length + if actual_length == '...': + actual_length = '_cffi_array_len(%s)' % (global_name,) + tp_item = self._global_type(tp.item, '%s[0]' % global_name) + tp = model.ArrayType(tp_item, actual_length) + return tp + + def _generate_cpy_variable_collecttype(self, tp, name): + self._do_collect_type(self._global_type(tp, name)) + + def _generate_cpy_variable_decl(self, tp, name): + prnt = self._prnt + tp = self._global_type(tp, name) + if isinstance(tp, model.ArrayType) and tp.length is None: + tp = tp.item + ampersand = '' + else: + ampersand = '&' + # This code assumes that casts from "tp *" to "void *" is a + # no-op, i.e. a function that returns a "tp *" can be called + # as if it returned a "void *". This should be generally true + # on any modern machine. The only exception to that rule (on + # uncommon architectures, and as far as I can tell) might be + # if 'tp' were a function type, but that is not possible here. + # (If 'tp' is a function _pointer_ type, then casts from "fn_t + # **" to "void *" are again no-ops, as far as I can tell.) + decl = '*_cffi_var_%s(void)' % (name,) + prnt('static ' + tp.get_c_name(decl, quals=self._current_quals)) + prnt('{') + prnt(' return %s(%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_cpy_variable_ctx(self, tp, name): + tp = self._global_type(tp, name) + type_index = self._typesdict[tp] + if self.target_is_python: + op = OP_GLOBAL_VAR + else: + op = OP_GLOBAL_VAR_F + self._lsts["global"].append( + GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index))) + + # ---------- + # extern "Python" + + def _generate_cpy_extern_python_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + self._do_collect_type(tp) + _generate_cpy_dllexport_python_collecttype = \ + _generate_cpy_extern_python_plus_c_collecttype = \ + _generate_cpy_extern_python_collecttype + + def _extern_python_decl(self, tp, name, tag_and_space): + prnt = self._prnt + if isinstance(tp.result, model.VoidType): + size_of_result = '0' + else: + context = 'result of %s' % name + size_of_result = '(int)sizeof(%s)' % ( + tp.result.get_c_name('', context),) + prnt('static struct _cffi_externpy_s _cffi_externpy__%s =' % name) + prnt(' { "%s.%s", %s, 0, 0 };' % ( + self.module_name, name, size_of_result)) + prnt() + # + arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' a%d' % i, context) + arguments.append(arg) + # + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s(%s)' % (name, repr_arguments) + if tp.abi == "__stdcall": + name_and_arguments = '_cffi_stdcall ' + name_and_arguments + # + def may_need_128_bits(tp): + return (isinstance(tp, model.PrimitiveType) and + tp.name == 'long double') + # + size_of_a = max(len(tp.args)*8, 8) + if may_need_128_bits(tp.result): + size_of_a = max(size_of_a, 16) + if isinstance(tp.result, model.StructOrUnion): + size_of_a = 'sizeof(%s) > %d ? sizeof(%s) : %d' % ( + tp.result.get_c_name(''), size_of_a, + tp.result.get_c_name(''), size_of_a) + prnt('%s%s' % (tag_and_space, tp.result.get_c_name(name_and_arguments))) + prnt('{') + prnt(' char a[%s];' % size_of_a) + prnt(' char *p = a;') + for i, type in enumerate(tp.args): + arg = 'a%d' % i + if (isinstance(type, model.StructOrUnion) or + may_need_128_bits(type)): + arg = '&' + arg + type = model.PointerType(type) + prnt(' *(%s)(p + %d) = %s;' % (type.get_c_name('*'), i*8, arg)) + prnt(' _cffi_call_python(&_cffi_externpy__%s, p);' % name) + if not isinstance(tp.result, model.VoidType): + prnt(' return *(%s)p;' % (tp.result.get_c_name('*'),)) + prnt('}') + prnt() + self._num_externpy += 1 + + def _generate_cpy_extern_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'static ') + + def _generate_cpy_dllexport_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'CFFI_DLLEXPORT ') + + def _generate_cpy_extern_python_plus_c_decl(self, tp, name): + self._extern_python_decl(tp, name, '') + + def _generate_cpy_extern_python_ctx(self, tp, name): + if self.target_is_python: + raise VerificationError( + "cannot use 'extern \"Python\"' in the ABI mode") + if tp.ellipsis: + raise NotImplementedError("a vararg function is extern \"Python\"") + type_index = self._typesdict[tp] + type_op = CffiOp(OP_EXTERN_PYTHON, type_index) + self._lsts["global"].append( + GlobalExpr(name, '&_cffi_externpy__%s' % name, type_op, name)) + + _generate_cpy_dllexport_python_ctx = \ + _generate_cpy_extern_python_plus_c_ctx = \ + _generate_cpy_extern_python_ctx + + def _print_string_literal_in_array(self, s): + prnt = self._prnt + prnt('// # NB. this is not a string because of a size limit in MSVC') + if not isinstance(s, bytes): # unicode + s = s.encode('utf-8') # -> bytes + else: + s.decode('utf-8') # got bytes, check for valid utf-8 + for line in s.splitlines(True): + comment = line + if type('//') is bytes: # python2 + line = map(ord, line) # make a list of integers + else: # python3 + # type(line) is bytes, which enumerates like a list of integers + comment = ascii(comment)[1:-1] + prnt(('// ' + comment).rstrip()) + printed_line = '' + for c in line: + if len(printed_line) >= 76: + prnt(printed_line) + printed_line = '' + printed_line += '%d,' % (c,) + prnt(printed_line) + + # ---------- + # emitting the opcodes for individual types + + def _emit_bytecode_VoidType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, PRIM_VOID) + + def _emit_bytecode_PrimitiveType(self, tp, index): + prim_index = PRIMITIVE_TO_INDEX[tp.name] + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, prim_index) + + def _emit_bytecode_UnknownIntegerType(self, tp, index): + s = ('_cffi_prim_int(sizeof(%s), (\n' + ' ((%s)-1) | 0 /* check that %s is an integer type */\n' + ' ) <= 0)' % (tp.name, tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_UnknownFloatType(self, tp, index): + s = ('_cffi_prim_float(sizeof(%s) *\n' + ' (((%s)1) / 2) * 2 /* integer => 0, float => 1 */\n' + ' )' % (tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_RawFunctionType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_FUNCTION, self._typesdict[tp.result]) + index += 1 + for tp1 in tp.args: + realindex = self._typesdict[tp1] + if index != realindex: + if isinstance(tp1, model.PrimitiveType): + self._emit_bytecode_PrimitiveType(tp1, index) + else: + self.cffi_types[index] = CffiOp(OP_NOOP, realindex) + index += 1 + flags = int(tp.ellipsis) + if tp.abi is not None: + if tp.abi == '__stdcall': + flags |= 2 + else: + raise NotImplementedError("abi=%r" % (tp.abi,)) + self.cffi_types[index] = CffiOp(OP_FUNCTION_END, flags) + + def _emit_bytecode_PointerType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[tp.totype]) + + _emit_bytecode_ConstPointerType = _emit_bytecode_PointerType + _emit_bytecode_NamedPointerType = _emit_bytecode_PointerType + + def _emit_bytecode_FunctionPtrType(self, tp, index): + raw = tp.as_raw_function() + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[raw]) + + def _emit_bytecode_ArrayType(self, tp, index): + item_index = self._typesdict[tp.item] + if tp.length is None: + self.cffi_types[index] = CffiOp(OP_OPEN_ARRAY, item_index) + elif tp.length == '...': + raise VerificationError( + "type %s badly placed: the '...' array length can only be " + "used on global arrays or on fields of structures" % ( + str(tp).replace('/*...*/', '...'),)) + else: + assert self.cffi_types[index + 1] == 'LEN' + self.cffi_types[index] = CffiOp(OP_ARRAY, item_index) + self.cffi_types[index + 1] = CffiOp(None, str(tp.length)) + + def _emit_bytecode_StructType(self, tp, index): + struct_index = self._struct_unions[tp] + self.cffi_types[index] = CffiOp(OP_STRUCT_UNION, struct_index) + _emit_bytecode_UnionType = _emit_bytecode_StructType + + def _emit_bytecode_EnumType(self, tp, index): + enum_index = self._enums[tp] + self.cffi_types[index] = CffiOp(OP_ENUM, enum_index) + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super().write(s) + +def _is_file_like(maybefile): + # compare to xml.etree.ElementTree._get_writer + return hasattr(maybefile, 'write') + +def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose): + if verbose: + print("generating %s" % (target_file,)) + recompiler = Recompiler(ffi, module_name, + target_is_python=(preamble is None)) + recompiler.collect_type_table() + recompiler.collect_step_tables() + if _is_file_like(target_file): + recompiler.write_source_to_f(target_file, preamble) + return True + f = NativeIO() + recompiler.write_source_to_f(f, preamble) + output = f.getvalue() + try: + with open(target_file, 'r') as f1: + if f1.read(len(output) + 1) != output: + raise OSError + if verbose: + print("(already up-to-date)") + return False # already up-to-date + except OSError: + tmp_file = '%s.~%d' % (target_file, os.getpid()) + with open(tmp_file, 'w') as f1: + f1.write(output) + try: + os.rename(tmp_file, target_file) + except OSError: + os.unlink(target_file) + os.rename(tmp_file, target_file) + return True + +def make_c_source(ffi, module_name, preamble, target_c_file, verbose=False): + assert preamble is not None + return _make_c_or_py_source(ffi, module_name, preamble, target_c_file, + verbose) + +def make_py_source(ffi, module_name, target_py_file, verbose=False): + return _make_c_or_py_source(ffi, module_name, None, target_py_file, + verbose) + +def _modname_to_file(outputdir, modname, extension): + parts = modname.split('.') + try: + os.makedirs(os.path.join(outputdir, *parts[:-1])) + except OSError: + pass + parts[-1] += extension + return os.path.join(outputdir, *parts), parts + + +# Aaargh. Distutils is not tested at all for the purpose of compiling +# DLLs that are not extension modules. Here are some hacks to work +# around that, in the _patch_for_*() functions... + +def _patch_meth(patchlist, cls, name, new_meth): + old = getattr(cls, name) + patchlist.append((cls, name, old)) + setattr(cls, name, new_meth) + return old + +def _unpatch_meths(patchlist): + for cls, name, old_meth in reversed(patchlist): + setattr(cls, name, old_meth) + +def _patch_for_embedding(patchlist): + if sys.platform == 'win32': + # we must not remove the manifest when building for embedding! + # FUTURE: this module was removed in setuptools 74; this is likely dead code and should be removed, + # since the toolchain it supports (VS2005-2008) is also long dead. + from cffi._shimmed_dist_utils import MSVCCompiler + if MSVCCompiler is not None: + _patch_meth(patchlist, MSVCCompiler, '_remove_visual_c_ref', + lambda self, manifest_file: manifest_file) + + if sys.platform == 'darwin': + # we must not make a '-bundle', but a '-dynamiclib' instead + from cffi._shimmed_dist_utils import CCompiler + def my_link_shared_object(self, *args, **kwds): + if '-bundle' in self.linker_so: + self.linker_so = list(self.linker_so) + i = self.linker_so.index('-bundle') + self.linker_so[i] = '-dynamiclib' + return old_link_shared_object(self, *args, **kwds) + old_link_shared_object = _patch_meth(patchlist, CCompiler, + 'link_shared_object', + my_link_shared_object) + +def _patch_for_target(patchlist, target): + from cffi._shimmed_dist_utils import build_ext + # if 'target' is different from '*', we need to patch some internal + # method to just return this 'target' value, instead of having it + # built from module_name + if target.endswith('.*'): + target = target[:-2] + if sys.platform == 'win32': + target += '.dll' + elif sys.platform == 'darwin': + target += '.dylib' + else: + target += '.so' + _patch_meth(patchlist, build_ext, 'get_ext_filename', + lambda self, ext_name: target) + + +def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True, + c_file=None, source_extension='.c', extradir=None, + compiler_verbose=1, target=None, debug=None, + uses_ffiplatform=True, **kwds): + if not isinstance(module_name, str): + module_name = module_name.encode('ascii') + if ffi._windows_unicode: + ffi._apply_windows_unicode(kwds) + if preamble is not None: + if call_c_compiler and _is_file_like(c_file): + raise TypeError("Writing to file-like objects is not supported " + "with call_c_compiler=True") + embedding = (ffi._embedding is not None) + if embedding: + ffi._apply_embedding_fix(kwds) + if c_file is None: + c_file, parts = _modname_to_file(tmpdir, module_name, + source_extension) + if extradir: + parts = [extradir] + parts + ext_c_file = os.path.join(*parts) + else: + ext_c_file = c_file + # + if target is None: + if embedding: + target = '%s.*' % module_name + else: + target = '*' + # + if uses_ffiplatform: + ext = ffiplatform.get_extension(ext_c_file, module_name, **kwds) + else: + ext = None + updated = make_c_source(ffi, module_name, preamble, c_file, + verbose=compiler_verbose) + if call_c_compiler: + patchlist = [] + cwd = os.getcwd() + try: + if embedding: + _patch_for_embedding(patchlist) + if target != '*': + _patch_for_target(patchlist, target) + if compiler_verbose: + if tmpdir == '.': + msg = 'the current directory is' + else: + msg = 'setting the current directory to' + print('%s %r' % (msg, os.path.abspath(tmpdir))) + os.chdir(tmpdir) + outputfilename = ffiplatform.compile('.', ext, + compiler_verbose, debug) + finally: + os.chdir(cwd) + _unpatch_meths(patchlist) + return outputfilename + else: + return ext, updated + else: + if c_file is None: + c_file, _ = _modname_to_file(tmpdir, module_name, '.py') + updated = make_py_source(ffi, module_name, c_file, + verbose=compiler_verbose) + if call_c_compiler: + return c_file + else: + return None, updated + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py new file mode 100644 index 0000000..946afb4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py @@ -0,0 +1,231 @@ +import os +import sys +import sysconfig + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +def error(msg): + from cffi._shimmed_dist_utils import DistutilsSetupError + raise DistutilsSetupError(msg) + + +def execfile(filename, glob): + # We use execfile() (here rewritten for Python 3) instead of + # __import__() to load the build script. The problem with + # a normal import is that in some packages, the intermediate + # __init__.py files may already try to import the file that + # we are generating. + with open(filename) as f: + src = f.read() + src += '\n' # Python 2.6 compatibility + code = compile(src, filename, 'exec') + exec(code, glob, glob) + + +def add_cffi_module(dist, mod_spec): + from cffi.api import FFI + + if not isinstance(mod_spec, basestring): + error("argument to 'cffi_modules=...' must be a str or a list of str," + " not %r" % (type(mod_spec).__name__,)) + mod_spec = str(mod_spec) + try: + build_file_name, ffi_var_name = mod_spec.split(':') + except ValueError: + error("%r must be of the form 'path/build.py:ffi_variable'" % + (mod_spec,)) + if not os.path.exists(build_file_name): + ext = '' + rewritten = build_file_name.replace('.', '/') + '.py' + if os.path.exists(rewritten): + ext = ' (rewrite cffi_modules to [%r])' % ( + rewritten + ':' + ffi_var_name,) + error("%r does not name an existing file%s" % (build_file_name, ext)) + + mod_vars = {'__name__': '__cffi__', '__file__': build_file_name} + execfile(build_file_name, mod_vars) + + try: + ffi = mod_vars[ffi_var_name] + except KeyError: + error("%r: object %r not found in module" % (mod_spec, + ffi_var_name)) + if not isinstance(ffi, FFI): + ffi = ffi() # maybe it's a function instead of directly an ffi + if not isinstance(ffi, FFI): + error("%r is not an FFI instance (got %r)" % (mod_spec, + type(ffi).__name__)) + if not hasattr(ffi, '_assigned_source'): + error("%r: the set_source() method was not called" % (mod_spec,)) + module_name, source, source_extension, kwds = ffi._assigned_source + if ffi._windows_unicode: + kwds = kwds.copy() + ffi._apply_windows_unicode(kwds) + + if source is None: + _add_py_module(dist, ffi, module_name) + else: + _add_c_module(dist, ffi, module_name, source, source_extension, kwds) + +def _set_py_limited_api(Extension, kwds): + """ + Add py_limited_api to kwds if setuptools >= 26 is in use. + Do not alter the setting if it already exists. + Setuptools takes care of ignoring the flag on Python 2 and PyPy. + + CPython itself should ignore the flag in a debugging version + (by not listing .abi3.so in the extensions it supports), but + it doesn't so far, creating troubles. That's why we check + for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent + of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401) + + On Windows, with CPython <= 3.4, it's better not to use py_limited_api + because virtualenv *still* doesn't copy PYTHON3.DLL on these versions. + Recently (2020) we started shipping only >= 3.5 wheels, though. So + we'll give it another try and set py_limited_api on Windows >= 3.5. + """ + from cffi._shimmed_dist_utils import log + from cffi import recompiler + + if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') + and recompiler.USE_LIMITED_API): + import setuptools + try: + setuptools_major_version = int(setuptools.__version__.partition('.')[0]) + if setuptools_major_version >= 26: + kwds['py_limited_api'] = True + except ValueError: # certain development versions of setuptools + # If we don't know the version number of setuptools, we + # try to set 'py_limited_api' anyway. At worst, we get a + # warning. + kwds['py_limited_api'] = True + + if sysconfig.get_config_var("Py_GIL_DISABLED"): + if sys.version_info < (3, 15): + if kwds.get('py_limited_api'): + log.info("Ignoring py_limited_api=True for free-threaded build.") + kwds['py_limited_api'] = False + else: + kwds.setdefault("define_macros", []).append(("Py_TARGET_ABI3T", "0x030f0000")) + + if kwds.get('py_limited_api') is False: + # avoid setting Py_LIMITED_API if py_limited_api=False + # which _cffi_include.h does unless _CFFI_NO_LIMITED_API is defined + kwds.setdefault("define_macros", []).append(("_CFFI_NO_LIMITED_API", None)) + return kwds + +def _add_c_module(dist, ffi, module_name, source, source_extension, kwds): + # We are a setuptools extension. Need this build_ext for py_limited_api. + from setuptools.command.build_ext import build_ext + from cffi._shimmed_dist_utils import Extension, log, mkpath + from cffi import recompiler + + allsources = ['$PLACEHOLDER'] + allsources.extend(kwds.pop('sources', [])) + kwds = _set_py_limited_api(Extension, kwds) + ext = Extension(name=module_name, sources=allsources, **kwds) + + def make_mod(tmpdir, pre_run=None): + c_file = os.path.join(tmpdir, module_name + source_extension) + log.info("generating cffi module %r" % c_file) + mkpath(tmpdir) + # a setuptools-only, API-only hook: called with the "ext" and "ffi" + # arguments just before we turn the ffi into C code. To use it, + # subclass the 'distutils.command.build_ext.build_ext' class and + # add a method 'def pre_run(self, ext, ffi)'. + if pre_run is not None: + pre_run(ext, ffi) + updated = recompiler.make_c_source(ffi, module_name, source, c_file) + if not updated: + log.info("already up-to-date") + return c_file + + if dist.ext_modules is None: + dist.ext_modules = [] + dist.ext_modules.append(ext) + + base_class = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class): + def run(self): + if ext.sources[0] == '$PLACEHOLDER': + pre_run = getattr(self, 'pre_run', None) + ext.sources[0] = make_mod(self.build_temp, pre_run) + base_class.run(self) + dist.cmdclass['build_ext'] = build_ext_make_mod + # NB. multiple runs here will create multiple 'build_ext_make_mod' + # classes. Even in this case the 'build_ext' command should be + # run once; but just in case, the logic above does nothing if + # called again. + + +def _add_py_module(dist, ffi, module_name): + from setuptools.command.build_py import build_py + from setuptools.command.build_ext import build_ext + from cffi._shimmed_dist_utils import log, mkpath + from cffi import recompiler + + def generate_mod(py_file): + log.info("generating cffi module %r" % py_file) + mkpath(os.path.dirname(py_file)) + updated = recompiler.make_py_source(ffi, module_name, py_file) + if not updated: + log.info("already up-to-date") + + base_class = dist.cmdclass.get('build_py', build_py) + class build_py_make_mod(base_class): + def run(self): + base_class.run(self) + module_path = module_name.split('.') + module_path[-1] += '.py' + generate_mod(os.path.join(self.build_lib, *module_path)) + def get_source_files(self): + # This is called from 'setup.py sdist' only. Exclude + # the generate .py module in this case. + saved_py_modules = self.py_modules + try: + if saved_py_modules: + self.py_modules = [m for m in saved_py_modules + if m != module_name] + return base_class.get_source_files(self) + finally: + self.py_modules = saved_py_modules + dist.cmdclass['build_py'] = build_py_make_mod + + # distutils and setuptools have no notion I could find of a + # generated python module. If we don't add module_name to + # dist.py_modules, then things mostly work but there are some + # combination of options (--root and --record) that will miss + # the module. So we add it here, which gives a few apparently + # harmless warnings about not finding the file outside the + # build directory. + # Then we need to hack more in get_source_files(); see above. + if dist.py_modules is None: + dist.py_modules = [] + dist.py_modules.append(module_name) + + # the following is only for "build_ext -i" + base_class_2 = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class_2): + def run(self): + base_class_2.run(self) + if self.inplace: + # from get_ext_fullpath() in distutils/command/build_ext.py + module_path = module_name.split('.') + package = '.'.join(module_path[:-1]) + build_py = self.get_finalized_command('build_py') + package_dir = build_py.get_package_dir(package) + file_name = module_path[-1] + '.py' + generate_mod(os.path.join(package_dir, file_name)) + dist.cmdclass['build_ext'] = build_ext_make_mod + +def cffi_modules(dist, attr, value): + assert attr == 'cffi_modules' + if isinstance(value, basestring): + value = [value] + + for cffi_module in value: + add_cffi_module(dist, cffi_module) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py new file mode 100644 index 0000000..4d2ea43 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py @@ -0,0 +1,1058 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys +from . import model +from .error import VerificationError +from . import _imp_emulation as imp + + +class VCPythonEngine: + _class_key = 'x' + _gen_python_module = True + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self._struct_pending_verification = {} + self._types_of_builtin_functions = {} + + def patch_extension_kwds(self, kwds): + pass + + def find_module(self, module_name, path, so_suffixes): + try: + f, filename, descr = imp.find_module(module_name, path) + except ImportError: + return None + if f is not None: + f.close() + # Note that after a setuptools installation, there are both .py + # and .so files with the same basename. The code here relies on + # imp.find_module() locating the .so in priority. + if descr[0] not in so_suffixes: + return None + return filename + + def collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _do_collect_type(self, tp): + if ((not isinstance(tp, model.PrimitiveType) + or tp.name == 'long double') + and tp not in self._typesdict): + num = len(self._typesdict) + self._typesdict[tp] = num + + def write_source_to_f(self): + self.collect_types() + # + # The new module will have a _cffi_setup() function that receives + # objects from the ffi world, and that calls some setup code in + # the module. This setup code is split in several independent + # functions, e.g. one per constant. The functions are "chained" + # by ending in a tail call to each other. + # + # This is further split in two chained lists, depending on if we + # can do it at import-time or if we must wait for _cffi_setup() to + # provide us with the objects. This is needed because we + # need the values of the enum constants in order to build the + # that we may have to pass to _cffi_setup(). + # + # The following two 'chained_list_constants' items contains + # the head of these two chained lists, as a string that gives the + # call to do, if any. + self._chained_list_constants = ['((void)lib,0)', '((void)lib,0)'] + # + prnt = self._prnt + # first paste some standard set of lines that are mostly '#define' + prnt(cffimod_header) + prnt() + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate("decl") + # + # implement the function _cffi_setup_custom() as calling the + # head of the chained list. + self._generate_setup_custom() + prnt() + # + # produce the method table, including the entries for the + # generated Python->C function wrappers, which are done + # by generate_cpy_function_method(). + prnt('static PyMethodDef _cffi_methods[] = {') + self._generate("method") + prnt(' {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL},') + prnt(' {NULL, NULL, 0, NULL} /* Sentinel */') + prnt('};') + prnt() + # + # standard init. + modname = self.verifier.get_module_name() + constants = self._chained_list_constants[False] + prnt('static struct PyModuleDef _cffi_module_def = {') + prnt(' PyModuleDef_HEAD_INIT,') + prnt(' "%s",' % modname) + prnt(' NULL,') + prnt(' -1,') + prnt(' _cffi_methods,') + prnt(' NULL, NULL, NULL, NULL') + prnt('};') + prnt() + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = PyModule_Create(&_cffi_module_def);') + prnt(' if (lib == NULL)') + prnt(' return NULL;') + prnt(' if (%s < 0 || _cffi_init() < 0) {' % (constants,)) + prnt(' Py_DECREF(lib);') + prnt(' return NULL;') + prnt(' }') + prnt('#if Py_GIL_DISABLED') + prnt(' PyUnstable_Module_SetGIL(lib, Py_MOD_GIL_NOT_USED);') + prnt('#endif') + prnt(' return lib;') + prnt('}') + prnt() + + def load_library(self, flags=None): + # XXX review all usages of 'self' here! + # import it as a new extension module + imp.acquire_lock() + try: + if hasattr(sys, "getdlopenflags"): + previous_flags = sys.getdlopenflags() + try: + if hasattr(sys, "setdlopenflags") and flags is not None: + sys.setdlopenflags(flags) + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise VerificationError(error) + finally: + if hasattr(sys, "setdlopenflags"): + sys.setdlopenflags(previous_flags) + finally: + imp.release_lock() + # + # call loading_cpy_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # the C code will need the objects. Collect them in + # order in a list. + revmapping = dict([(value, key) + for (key, value) in self._typesdict.items()]) + lst = [revmapping[i] for i in range(len(revmapping))] + lst = list(map(self.ffi._get_cached_btype, lst)) + # + # build the FFILibrary class and instance and call _cffi_setup(). + # this will set up some fields like '_cffi_types', and only then + # it will invoke the chained list of functions that will really + # build (notably) the constant objects, as if they are + # pointers, and store them as attributes on the 'library' object. + class FFILibrary: + _cffi_python_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + list(self.__dict__) + library = FFILibrary() + if module._cffi_setup(lst, VerificationError, library): + import warnings + warnings.warn("reimporting %r might overwrite older definitions" + % (self.verifier.get_module_name())) + # + # finally, call the loaded_cpy_xxx() functions. This will perform + # the final adjustments, like copying the Python->C wrapper + # functions from the module to the 'library' object, and setting + # up the FFILibrary class with properties for the global C variables. + self._load(module, 'loaded', library=library) + module._cffi_original_ffi = self.ffi + module._cffi_types_of_builtin_funcs = self._types_of_builtin_functions + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif tp.is_complex_type(): + raise VerificationError( + "not implemented in verify(): complex types") + else: + converter = '(%s)_cffi_to_c_%s' % (tp.get_c_name(''), + tp.name.replace(' ', '_')) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif isinstance(tp, (model.StructOrUnion, model.EnumType)): + # a struct (not a struct pointer) as a function argument + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars, freelines): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + localvars.add('struct _cffi_freeme_s *large_args_free = NULL') + freelines.add('if (large_args_free != NULL)' + ' _cffi_free_array_arguments(large_args_free);') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' %s = ((size_t)datasize) <= 640 ? ' + 'alloca((size_t)datasize) : NULL;' % (tovar,)) + self._prnt(' if (_cffi_convert_array_argument(_cffi_type(%d), %s, ' + '(char **)&%s,' % (self._gettypenum(tp), fromvar, tovar)) + self._prnt(' datasize, &large_args_free) < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif tp.name != 'long double': + return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs: generates no code so far + + _generate_cpy_typedef_collecttype = _generate_nothing + _generate_cpy_typedef_decl = _generate_nothing + _generate_cpy_typedef_method = _generate_nothing + _loading_cpy_typedef = _loaded_noop + _loaded_cpy_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + self._do_collect_type(tp) + else: + # don't call _do_collect_type(tp) in this common case, + # otherwise test_autofilled_struct_as_argument fails + for type in tp.args: + self._do_collect_type(type) + self._do_collect_type(tp.result) + + def _generate_cpy_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + prnt(' %s;' % type.get_c_name(' x%d' % i, context)) + # + localvars = set() + freelines = set() + for type in tp.args: + self._extra_local_variables(type, localvars, freelines) + for decl in sorted(localvars): + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + prnt(' %s;' % tp.result.get_c_name(' result', context)) + prnt(' PyObject *pyresult;') + else: + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( + 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + prnt(' { %s%s(%s); }' % ( + result_code, name, + ', '.join(['x%d' % i for i in range(len(tp.args))]))) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' pyresult = %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + for freeline in freelines: + prnt(' ' + freeline) + prnt(' return pyresult;') + else: + for freeline in freelines: + prnt(' ' + freeline) + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + prnt() + + def _generate_cpy_function_method(self, tp, name): + if tp.ellipsis: + return + numargs = len(tp.args) + if numargs == 0: + meth = 'METH_NOARGS' + elif numargs == 1: + meth = 'METH_O' + else: + meth = 'METH_VARARGS' + self._prnt(' {"%s", _cffi_f_%s, %s, NULL},' % (name, name, meth)) + + _loading_cpy_function = _loaded_noop + + def _loaded_cpy_function(self, tp, name, module, library): + if tp.ellipsis: + return + func = getattr(module, name) + setattr(library, name, func) + self._types_of_builtin_functions[func] = tp + + # ---------- + # named structs + + _generate_cpy_struct_collecttype = _generate_nothing + def _generate_cpy_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + def _generate_cpy_struct_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'struct', name) + def _loading_cpy_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + def _loaded_cpy_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + _generate_cpy_union_collecttype = _generate_nothing + def _generate_cpy_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + def _generate_cpy_union_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'union', name) + def _loading_cpy_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + def _loaded_cpy_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('static PyObject *') + prnt('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static Py_ssize_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' (void)self; /* unused */') + prnt(' (void)noarg; /* unused */') + prnt(' return _cffi_get_struct_layout(nums);') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _generate_struct_or_union_method(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + self._prnt(' {"%s", %s, METH_NOARGS, NULL},' % (layoutfuncname, + layoutfuncname)) + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + function = getattr(module, layoutfuncname) + layout = function() + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + _generate_cpy_anonymous_collecttype = _generate_nothing + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _generate_cpy_anonymous_method(self, tp, name): + if not isinstance(tp, model.EnumType): + self._generate_struct_or_union_method(tp, '', name) + + def _loading_cpy_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_cpy_enum(tp, name, module) + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_cpy_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_cpy_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + vartp=None, delayed=True, size_too=False, + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + prnt(' PyObject *o;') + prnt(' int res;') + if not is_int: + prnt(' %s;' % (vartp or tp).get_c_name(' i', name)) + else: + assert category == 'const' + # + if check_value is not None: + self._check_int_constant_value(name, check_value) + # + if not is_int: + if category == 'var': + realexpr = '&' + name + else: + realexpr = name + prnt(' i = (%s);' % (realexpr,)) + prnt(' o = %s;' % (self._convert_expr_from_c(tp, 'i', + 'variable type'),)) + assert delayed + else: + prnt(' o = _cffi_from_c_int_const(%s);' % name) + prnt(' if (o == NULL)') + prnt(' return -1;') + if size_too: + prnt(' {') + prnt(' PyObject *o1 = o;') + prnt(' o = Py_BuildValue("On", o1, (Py_ssize_t)sizeof(%s));' + % (name,)) + prnt(' Py_DECREF(o1);') + prnt(' if (o == NULL)') + prnt(' return -1;') + prnt(' }') + prnt(' res = PyObject_SetAttrString(lib, "%s", o);' % name) + prnt(' Py_DECREF(o);') + prnt(' if (res < 0)') + prnt(' return -1;') + prnt(' return %s;' % self._chained_list_constants[delayed]) + self._chained_list_constants[delayed] = funcname + '(lib)' + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + if not is_int: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + _generate_cpy_constant_method = _generate_nothing + _loading_cpy_constant = _loaded_noop + _loaded_cpy_constant = _loaded_noop + + # ---------- + # enums + + def _check_int_constant_value(self, name, value, err_prefix=''): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' snprintf(buf, 63, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' snprintf(buf, 63, "%%lu", (unsigned long)(%s));' % + name) + prnt(' PyErr_Format(_cffi_VerificationError,') + prnt(' "%s%s has the real value %s, not %s",') + prnt(' "%s", "%s", buf, "%d");' % ( + err_prefix, name, value)) + prnt(' return -1;') + prnt(' }') + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_cpy_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator, delayed=False) + return + # + funcname = self._enum_funcname(prefix, name) + prnt = self._prnt + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue, + "enum %s: " % name) + prnt(' return %s;' % self._chained_list_constants[True]) + self._chained_list_constants[True] = funcname + '(lib)' + prnt('}') + prnt() + + _generate_cpy_enum_collecttype = _generate_nothing + _generate_cpy_enum_method = _generate_nothing + + def _loading_cpy_enum(self, tp, name, module): + if tp.partial: + enumvalues = [getattr(module, enumerator) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + + def _loaded_cpy_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + _generate_cpy_macro_collecttype = _generate_nothing + _generate_cpy_macro_method = _generate_nothing + _loading_cpy_macro = _loaded_noop + _loaded_cpy_macro = _loaded_noop + + # ---------- + # global variables + + def _generate_cpy_variable_collecttype(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + else: + tp_ptr = model.PointerType(tp) + self._do_collect_type(tp_ptr) + + def _generate_cpy_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_cpy_const(False, name, tp, vartp=tp_ptr, + size_too = tp.length_is_unknown()) + else: + tp_ptr = model.PointerType(tp) + self._generate_cpy_const(False, name, tp_ptr, category='var') + + _generate_cpy_variable_method = _generate_nothing + _loading_cpy_variable = _loaded_noop + + def _loaded_cpy_variable(self, tp, name, module, library): + value = getattr(library, name) + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length_is_unknown(): + assert isinstance(value, tuple) + (value, size) = value + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + ptr = value + delattr(library, name) + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + + # ---------- + + def _generate_setup_custom(self): + prnt = self._prnt + prnt('static int _cffi_setup_custom(PyObject *lib)') + prnt('{') + prnt(' return %s;' % self._chained_list_constants[True]) + prnt('}') + +cffimod_header = r''' +#include +#include + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyLong_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyLong_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyLong_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyLong_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyLong_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_NUM_EXPORTS 25 + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static int _cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + int was_alive = (_cffi_types != NULL); + (void)self; /* unused */ + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + if (_cffi_setup_custom(library) < 0) + return NULL; + return PyBool_FromLong(was_alive); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static int _cffi_convert_array_argument(CTypeDescrObject *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static void _cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +static int _cffi_init(void) +{ + PyObject *module, *c_api_object = NULL; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + goto failure; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + goto failure; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); + Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ +''' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py new file mode 100644 index 0000000..0209c79 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py @@ -0,0 +1,679 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os +import types + +from . import model +from .error import VerificationError + + +class VGenericEngine: + _class_key = 'g' + _gen_python_module = False + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self.export_symbols = [] + self._struct_pending_verification = {} + + def patch_extension_kwds(self, kwds): + # add 'export_symbols' to the dictionary. Note that we add the + # list before filling it. When we fill it, it will thus also show + # up in kwds['export_symbols']. + kwds.setdefault('export_symbols', self.export_symbols) + + def find_module(self, module_name, path, so_suffixes): + for so_suffix in so_suffixes: + basename = module_name + so_suffix + if path is None: + path = sys.path + for dirname in path: + filename = os.path.join(dirname, basename) + if os.path.isfile(filename): + return filename + + def collect_types(self): + pass # not needed in the generic engine + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self): + prnt = self._prnt + # first paste some standard set of lines that are mostly '#include' + prnt(cffimod_header) + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + # + # call generate_gen_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate('decl') + # + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + if sys.platform == 'win32': + if sys.version_info >= (3,): + prefix = 'PyInit_' + else: + prefix = 'init' + modname = self.verifier.get_module_name() + prnt("void %s%s(void) { }\n" % (prefix, modname)) + + def load_library(self, flags=0): + # import it with the CFFI backend + backend = self.ffi._backend + # needs to make a path that contains '/', on Posix + filename = os.path.join(os.curdir, self.verifier.modulefilename) + module = backend.load_library(filename, flags) + # + # call loading_gen_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + + # build the FFILibrary class and instance, this is a module subclass + # because modules are expected to have usually-constant-attributes and + # in PyPy this means the JIT is able to treat attributes as constant, + # which we want. + class FFILibrary(types.ModuleType): + _cffi_generic_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + library = FFILibrary("") + # + # finally, call the loaded_gen_xxx() functions. This will set + # up the 'library' object. + self._load(module, 'loaded', library=library) + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_gen_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_gen_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + # typedefs: generates no code so far + + _generate_gen_typedef_decl = _generate_nothing + _loading_gen_typedef = _loaded_noop + _loaded_gen_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_gen_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no _cffi_f_%s wrapper) + self._generate_gen_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + argnames = [] + for i, type in enumerate(tp.args): + indirection = '' + if isinstance(type, model.StructOrUnion): + indirection = '*' + argnames.append('%sx%d' % (indirection, i)) + context = 'argument of %s' % name + arglist = [type.get_c_name(' %s' % arg, context) + for type, arg in zip(tp.args, argnames)] + tpresult = tp.result + if isinstance(tpresult, model.StructOrUnion): + arglist.insert(0, tpresult.get_c_name(' *r', context)) + tpresult = model.void_type + arglist = ', '.join(arglist) or 'void' + wrappername = '_cffi_f_%s' % name + self.export_symbols.append(wrappername) + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + funcdecl = ' %s%s(%s)' % (abi, wrappername, arglist) + context = 'result of %s' % name + prnt(tpresult.get_c_name(funcdecl, context)) + prnt('{') + # + if isinstance(tp.result, model.StructOrUnion): + result_code = '*r = ' + elif not isinstance(tp.result, model.VoidType): + result_code = 'return ' + else: + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, ', '.join(argnames))) + prnt('}') + prnt() + + _loading_gen_function = _loaded_noop + + def _loaded_gen_function(self, tp, name, module, library): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + newfunction = self._load_constant(False, tp, name, module) + else: + indirections = [] + base_tp = tp + if (any(isinstance(typ, model.StructOrUnion) for typ in tp.args) + or isinstance(tp.result, model.StructOrUnion)): + indirect_args = [] + for i, typ in enumerate(tp.args): + if isinstance(typ, model.StructOrUnion): + typ = model.PointerType(typ) + indirections.append((i, typ)) + indirect_args.append(typ) + indirect_result = tp.result + if isinstance(indirect_result, model.StructOrUnion): + if indirect_result.fldtypes is None: + raise TypeError("'%s' is used as result type, " + "but is opaque" % ( + indirect_result._get_c_name(),)) + indirect_result = model.PointerType(indirect_result) + indirect_args.insert(0, indirect_result) + indirections.insert(0, ("result", indirect_result)) + indirect_result = model.void_type + tp = model.FunctionPtrType(tuple(indirect_args), + indirect_result, tp.ellipsis) + BFunc = self.ffi._get_cached_btype(tp) + wrappername = '_cffi_f_%s' % name + newfunction = module.load_function(BFunc, wrappername) + for i, typ in indirections: + newfunction = self._make_struct_wrapper(newfunction, i, typ, + base_tp) + setattr(library, name, newfunction) + type(library)._cffi_dir.append(name) + + def _make_struct_wrapper(self, oldfunc, i, tp, base_tp): + backend = self.ffi._backend + BType = self.ffi._get_cached_btype(tp) + if i == "result": + ffi = self.ffi + def newfunc(*args): + res = ffi.new(BType) + oldfunc(res, *args) + return res[0] + else: + def newfunc(*args): + args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] + return oldfunc(*args) + newfunc._cffi_base_type = base_tp + return newfunc + + # ---------- + # named structs + + def _generate_gen_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + + def _loading_gen_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + + def _loaded_gen_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_gen_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + + def _loading_gen_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + + def _loaded_gen_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + self.export_symbols.append(layoutfuncname) + prnt('intptr_t %s(intptr_t i)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static intptr_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' return nums[i];') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + BFunc = self.ffi._typeof_locked("intptr_t(*)(intptr_t)")[0] + function = module.load_function(BFunc, layoutfuncname) + layout = [] + num = 0 + while True: + x = function(num) + if x < 0: break + layout.append(x) + num += 1 + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_gen_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_gen_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _loading_gen_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_gen_enum(tp, name, module, '') + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_gen_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_gen_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_gen_const(self, is_int, name, tp=None, category='const', + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + self.export_symbols.append(funcname) + if check_value is not None: + assert is_int + assert category == 'const' + prnt('int %s(char *out_error)' % funcname) + prnt('{') + self._check_int_constant_value(name, check_value) + prnt(' return 0;') + prnt('}') + elif is_int: + assert category == 'const' + prnt('int %s(long long *out_value)' % funcname) + prnt('{') + prnt(' *out_value = (long long)(%s);' % (name,)) + prnt(' return (%s) <= 0;' % (name,)) + prnt('}') + else: + assert tp is not None + assert check_value is None + if category == 'var': + ampersand = '&' + else: + ampersand = '' + extra = '' + if category == 'const' and isinstance(tp, model.StructOrUnion): + extra = 'const *' + ampersand = '&' + prnt(tp.get_c_name(' %s%s(void)' % (extra, funcname), name)) + prnt('{') + prnt(' return (%s%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_gen_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_gen_const(is_int, name, tp) + + _loading_gen_constant = _loaded_noop + + def _load_constant(self, is_int, tp, name, module, check_value=None): + funcname = '_cffi_const_%s' % name + if check_value is not None: + assert is_int + self._load_known_int_constant(module, funcname) + value = check_value + elif is_int: + BType = self.ffi._typeof_locked("long long*")[0] + BFunc = self.ffi._typeof_locked("int(*)(long long*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType) + negative = function(p) + value = int(p[0]) + if value < 0 and not negative: + BLongLong = self.ffi._typeof_locked("long long")[0] + value += (1 << (8*self.ffi.sizeof(BLongLong))) + else: + assert check_value is None + fntypeextra = '(*)(void)' + if isinstance(tp, model.StructOrUnion): + fntypeextra = '*' + fntypeextra + BFunc = self.ffi._typeof_locked(tp.get_c_name(fntypeextra, name))[0] + function = module.load_function(BFunc, funcname) + value = function() + if isinstance(tp, model.StructOrUnion): + value = value[0] + return value + + def _loaded_gen_constant(self, tp, name, module, library): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + value = self._load_constant(is_int, tp, name, module) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # enums + + def _check_int_constant_value(self, name, value): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' sprintf(buf, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' sprintf(buf, "%%lu", (unsigned long)(%s));' % + name) + prnt(' sprintf(out_error, "%s has the real value %s, not %s",') + prnt(' "%s", buf, "%d");' % (name[:100], value)) + prnt(' return -1;') + prnt(' }') + + def _load_known_int_constant(self, module, funcname): + BType = self.ffi._typeof_locked("char[]")[0] + BFunc = self.ffi._typeof_locked("int(*)(char*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType, 256) + if function(p) < 0: + error = self.ffi.string(p) + if sys.version_info >= (3,): + error = str(error, 'utf-8') + raise VerificationError(error) + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_gen_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_gen_const(True, enumerator) + return + # + funcname = self._enum_funcname(prefix, name) + self.export_symbols.append(funcname) + prnt = self._prnt + prnt('int %s(char *out_error)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue) + prnt(' return 0;') + prnt('}') + prnt() + + def _loading_gen_enum(self, tp, name, module, prefix='enum'): + if tp.partial: + enumvalues = [self._load_constant(True, tp, enumerator, module) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + else: + funcname = self._enum_funcname(prefix, name) + self._load_known_int_constant(module, funcname) + + def _loaded_gen_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + type(library)._cffi_dir.append(enumerator) + + # ---------- + # macros: for now only for integers + + def _generate_gen_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_gen_const(True, name, check_value=check_value) + + _loading_gen_macro = _loaded_noop + + def _loaded_gen_macro(self, tp, name, module, library): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + value = self._load_constant(True, tp, name, module, + check_value=check_value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # global variables + + def _generate_gen_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + if tp.length_is_unknown(): + prnt = self._prnt + funcname = '_cffi_sizeof_%s' % (name,) + self.export_symbols.append(funcname) + prnt("size_t %s(void)" % funcname) + prnt("{") + prnt(" return sizeof(%s);" % (name,)) + prnt("}") + tp_ptr = model.PointerType(tp.item) + self._generate_gen_const(False, name, tp_ptr) + else: + tp_ptr = model.PointerType(tp) + self._generate_gen_const(False, name, tp_ptr, category='var') + + _loading_gen_variable = _loaded_noop + + def _loaded_gen_variable(self, tp, name, module, library): + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length_is_unknown(): + funcname = '_cffi_sizeof_%s' % (name,) + BFunc = self.ffi._typeof_locked('size_t(*)(void)')[0] + function = module.load_function(BFunc, funcname) + size = function() + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + tp_ptr = model.PointerType(tp.item) + value = self._load_constant(False, tp_ptr, name, module) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + funcname = '_cffi_var_%s' % name + BFunc = self.ffi._typeof_locked(tp.get_c_name('*(*)(void)', name))[0] + function = module.load_function(BFunc, funcname) + ptr = function() + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + +cffimod_header = r''' +#include +#include +#include +#include +#include /* XXX for ssize_t on some platforms */ + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif +''' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py new file mode 100644 index 0000000..96baadb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cffi/verifier.py @@ -0,0 +1,306 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os, binascii, shutil, io +from . import __version_verifier_modules__ +from . import ffiplatform +from .error import VerificationError + +if sys.version_info >= (3, 3): + import importlib.machinery + def _extension_suffixes(): + return importlib.machinery.EXTENSION_SUFFIXES[:] +else: + import imp + def _extension_suffixes(): + return [suffix for suffix, _, type in imp.get_suffixes() + if type == imp.C_EXTENSION] + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super().write(s) + + +class Verifier: + + def __init__(self, ffi, preamble, tmpdir=None, modulename=None, + ext_package=None, tag='', force_generic_engine=False, + source_extension='.c', flags=None, relative_to=None, **kwds): + if ffi._parser._uses_new_feature: + raise VerificationError( + "feature not supported with ffi.verify(), but only " + "with ffi.set_source(): %s" % (ffi._parser._uses_new_feature,)) + self.ffi = ffi + self.preamble = preamble + if not modulename: + flattened_kwds = ffiplatform.flatten(kwds) + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) + self._vengine.patch_extension_kwds(kwds) + self.flags = flags + self.kwds = self.make_relative_to(kwds, relative_to) + # + if modulename: + if tag: + raise TypeError("can't specify both 'modulename' and 'tag'") + else: + key = '\x00'.join(['%d.%d' % sys.version_info[:2], + __version_verifier_modules__, + preamble, flattened_kwds] + + ffi._cdefsources) + if sys.version_info >= (3,): + key = key.encode('utf-8') + k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) + k1 = k1.lstrip('0x').rstrip('L') + k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) + k2 = k2.lstrip('0').rstrip('L') + modulename = '_cffi_%s_%s%s%s' % (tag, self._vengine._class_key, + k1, k2) + suffix = _get_so_suffixes()[0] + self.tmpdir = tmpdir or _caller_dir_pycache() + self.sourcefilename = os.path.join(self.tmpdir, modulename + source_extension) + self.modulefilename = os.path.join(self.tmpdir, modulename + suffix) + self.ext_package = ext_package + self._has_source = False + self._has_module = False + + def write_source(self, file=None): + """Write the C source code. It is produced in 'self.sourcefilename', + which can be tweaked beforehand.""" + with self.ffi._lock: + if self._has_source and file is None: + raise VerificationError( + "source code already written") + self._write_source(file) + + def compile_module(self): + """Write the C source code (if not done already) and compile it. + This produces a dynamic link library in 'self.modulefilename'.""" + with self.ffi._lock: + if self._has_module: + raise VerificationError("module already compiled") + if not self._has_source: + self._write_source() + self._compile_module() + + def load_library(self): + """Get a C module from this Verifier instance. + Returns an instance of a FFILibrary class that behaves like the + objects returned by ffi.dlopen(), but that delegates all + operations to the C module. If necessary, the C code is written + and compiled first. + """ + with self.ffi._lock: + if not self._has_module: + self._locate_module() + if not self._has_module: + if not self._has_source: + self._write_source() + self._compile_module() + return self._load_library() + + def get_module_name(self): + basename = os.path.basename(self.modulefilename) + # kill both the .so extension and the other .'s, as introduced + # by Python 3: 'basename.cpython-33m.so' + basename = basename.split('.', 1)[0] + # and the _d added in Python 2 debug builds --- but try to be + # conservative and not kill a legitimate _d + if basename.endswith('_d') and hasattr(sys, 'gettotalrefcount'): + basename = basename[:-2] + return basename + + def get_extension(self): + if not self._has_source: + with self.ffi._lock: + if not self._has_source: + self._write_source() + sourcename = ffiplatform.maybe_relative_path(self.sourcefilename) + modname = self.get_module_name() + return ffiplatform.get_extension(sourcename, modname, **self.kwds) + + def generates_python_module(self): + return self._vengine._gen_python_module + + def make_relative_to(self, kwds, relative_to): + if relative_to and os.path.dirname(relative_to): + dirname = os.path.dirname(relative_to) + kwds = kwds.copy() + for key in ffiplatform.LIST_OF_FILE_NAMES: + if key in kwds: + lst = kwds[key] + if not isinstance(lst, (list, tuple)): + raise TypeError("keyword '%s' should be a list or tuple" + % (key,)) + lst = [os.path.join(dirname, fn) for fn in lst] + kwds[key] = lst + return kwds + + # ---------- + + def _locate_module(self): + if not os.path.isfile(self.modulefilename): + if self.ext_package: + try: + pkg = __import__(self.ext_package, None, None, ['__doc__']) + except ImportError: + return # cannot import the package itself, give up + # (e.g. it might be called differently before installation) + path = pkg.__path__ + else: + path = None + filename = self._vengine.find_module(self.get_module_name(), path, + _get_so_suffixes()) + if filename is None: + return + self.modulefilename = filename + self._vengine.collect_types() + self._has_module = True + + def _write_source_to(self, file): + self._vengine._f = file + try: + self._vengine.write_source_to_f() + finally: + del self._vengine._f + + def _write_source(self, file=None): + if file is not None: + self._write_source_to(file) + else: + # Write our source file to an in memory file. + f = NativeIO() + self._write_source_to(f) + source_data = f.getvalue() + + # Determine if this matches the current file + if os.path.exists(self.sourcefilename): + with open(self.sourcefilename, "r") as fp: + needs_written = fp.read() != source_data + else: + needs_written = True + + # Actually write the file out if it doesn't match + if needs_written: + _ensure_dir(self.sourcefilename) + with open(self.sourcefilename, "w") as fp: + fp.write(source_data) + + # Set this flag + self._has_source = True + + def _compile_module(self): + # compile this C source + tmpdir = os.path.dirname(self.sourcefilename) + outputfilename = ffiplatform.compile(tmpdir, self.get_extension()) + try: + same = ffiplatform.samefile(outputfilename, self.modulefilename) + except OSError: + same = False + if not same: + _ensure_dir(self.modulefilename) + shutil.move(outputfilename, self.modulefilename) + self._has_module = True + + def _load_library(self): + assert self._has_module + if self.flags is not None: + return self._vengine.load_library(self.flags) + else: + return self._vengine.load_library() + +# ____________________________________________________________ + +_FORCE_GENERIC_ENGINE = False # for tests + +def _locate_engine_class(ffi, force_generic_engine): + if _FORCE_GENERIC_ENGINE: + force_generic_engine = True + if not force_generic_engine: + if '__pypy__' in sys.builtin_module_names: + force_generic_engine = True + else: + try: + import _cffi_backend + except ImportError: + _cffi_backend = '?' + if ffi._backend is not _cffi_backend: + force_generic_engine = True + if force_generic_engine: + from . import vengine_gen + return vengine_gen.VGenericEngine + else: + from . import vengine_cpy + return vengine_cpy.VCPythonEngine + +# ____________________________________________________________ + +_TMPDIR = None + +def _caller_dir_pycache(): + if _TMPDIR: + return _TMPDIR + result = os.environ.get('CFFI_TMPDIR') + if result: + return result + filename = sys._getframe(2).f_code.co_filename + return os.path.abspath(os.path.join(os.path.dirname(filename), + '__pycache__')) + +def set_tmpdir(dirname): + """Set the temporary directory to use instead of __pycache__.""" + global _TMPDIR + _TMPDIR = dirname + +def cleanup_tmpdir(tmpdir=None, keep_so=False): + """Clean up the temporary directory by removing all files in it + called `_cffi_*.{c,so}` as well as the `build` subdirectory.""" + tmpdir = tmpdir or _caller_dir_pycache() + try: + filelist = os.listdir(tmpdir) + except OSError: + return + if keep_so: + suffix = '.c' # only remove .c files + else: + suffix = _get_so_suffixes()[0].lower() + for fn in filelist: + if fn.lower().startswith('_cffi_') and ( + fn.lower().endswith(suffix) or fn.lower().endswith('.c')): + try: + os.unlink(os.path.join(tmpdir, fn)) + except OSError: + pass + clean_dir = [os.path.join(tmpdir, 'build')] + for dir in clean_dir: + try: + for fn in os.listdir(dir): + fn = os.path.join(dir, fn) + if os.path.isdir(fn): + clean_dir.append(fn) + else: + os.unlink(fn) + except OSError: + pass + +def _get_so_suffixes(): + suffixes = _extension_suffixes() + if not suffixes: + # bah, no C_EXTENSION available. Occurs on pypy without cpyext + if sys.platform == 'win32': + suffixes = [".pyd"] + else: + suffixes = [".so"] + + return suffixes + +def _ensure_dir(filename): + dirname = os.path.dirname(filename) + if dirname and not os.path.isdir(dirname): + os.makedirs(dirname) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA new file mode 100644 index 0000000..7b78530 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA @@ -0,0 +1,83 @@ +Metadata-Version: 2.4 +Name: click +Version: 8.5.0 +Summary: Composable command line interface toolkit +Maintainer-email: Pallets +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +License-File: LICENSE.txt +Project-URL: Changes, https://click.palletsprojects.com/page/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/click/ + +
+ +# Click + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +## A Simple Example + +```python +import click + +@click.command() +@click.option("--count", default=1, help="Number of greetings.") +@click.option("--name", prompt="Your name", help="The person to greet.") +def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + +if __name__ == '__main__': + hello() +``` + +``` +$ python hello.py --count=3 +Your name: Click +Hello, Click! +Hello, Click! +Hello, Click! +``` + + +## Donate + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today][]. + +[please donate today]: https://palletsprojects.com/donate + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD new file mode 100644 index 0000000..b50c362 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD @@ -0,0 +1,22 @@ +click/__init__.py,sha256=Wr_FTTfUfMeIt-egXpUUeH-MWgt9tCnQ8k10isiZZMo,5145 +click/_compat.py,sha256=jbHaKWXPPl3GbGxTgzy8YT6hzSRAudDraqg2DrRtSRM,17939 +click/_termui_impl.py,sha256=iLl9m3pblHsQoW2KsmmDe8hMflXCi6cupfL7M5ESM2o,32805 +click/_textwrap.py,sha256=7Z0N7Vmn-66TNSTUwp6OXJbcUXRmYET9h9c2ucD8oQQ,6270 +click/_utils.py,sha256=eCZCtwJtsYD5QYkkNWJ8MY_8ABIjy8MczgMMyVY32rQ,996 +click/_winconsole.py,sha256=KSxfNbMlYRa6GOJuCLgsg2Pb3dVkgJNPqLJPae-Pa10,8543 +click/core.py,sha256=TGWmE8HEB9zpB6ThI7Es7F_g9iCIqLn4b6vUtgxLbXg,147845 +click/decorators.py,sha256=FgaTV2FWkfzfyceUu1FfYAnrNarW96dsAXvc4G2ujVU,21680 +click/exceptions.py,sha256=HvSY34G4auj_bYRR8-T8CU8Jwq_1-OcsRU4ezfozeEk,11862 +click/formatting.py,sha256=8SW2KGkvjfz9Q1NbeojMHuZBN0cfnQJDs4mqDP6oXms,10444 +click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923 +click/parser.py,sha256=oJ-fU_3mvxugIuNtHaCATZ56lgEmHRggjJiSqEgYrjA,19052 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=GFIvdd3_5lXpQ5eNGVMlXER4QoZ9P9Z_vcFUPT332gk,26172 +click/termui.py,sha256=jsgCK45FKPhz7EExlKXRYU6W2lC2xKcfYmCzJnYqfpw,35370 +click/testing.py,sha256=dvTmSbxTAA1mfIwN7_FZGXD6A0sPCFogcWWDmyMBAZo,27584 +click/types.py,sha256=HpiJBKen9oq61jDH-5wDikNF-i5nAcl2puUfEL37Ut0,46842 +click/utils.py,sha256=9D1XQ9wHJA5hL-76CmnSsWl7aQAUn4-An_zZfstILb8,21483 +click-8.5.0.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-8.5.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +click-8.5.0.dist-info/METADATA,sha256=6HvOC9GU3nDfuOcI4KawSDAJ8lYRgE6vh7_rY-bCBQE,2567 +click-8.5.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py new file mode 100644 index 0000000..a34ab02 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/__init__.py @@ -0,0 +1,144 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" + +from __future__ import annotations + +from .core import Argument as Argument +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import Option as Option +from .core import Parameter as Parameter +from .core import ParameterSource as ParameterSource +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import custom_version_option as custom_version_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchCommand as NoSuchCommand +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import get_pager_file as get_pager_file +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import open_file as open_file + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + from .core import _BaseCommand + + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + from .core import _MultiCommand + + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + if name == "OptionParser": + from .parser import _OptionParser + + warnings.warn( + "'OptionParser' is deprecated and will be removed in Click 9.0. The" + " old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return _OptionParser + + if name == "__version__": + import importlib.metadata + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Click 9.1. Use feature detection or" + " 'importlib.metadata.version(\"click\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("click") + + if name == "get_binary_stream": + from .utils import _get_binary_stream + + warnings.warn( + "'get_binary_stream' is deprecated and will be removed in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return _get_binary_stream + + if name == "get_text_stream": + from .utils import _get_text_stream + + warnings.warn( + "'get_text_stream' is deprecated and will be removed in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return _get_text_stream + + raise AttributeError(name) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py new file mode 100644 index 0000000..8507a50 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_compat.py @@ -0,0 +1,590 @@ +from __future__ import annotations + +import codecs +import collections.abc as cabc +import io +import os +import re +import sys +import typing as t +from types import TracebackType +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") +# One CSI escape sequence per the ECMA-48 grammar: parameter bytes (0x30-0x3F), +# intermediate bytes (0x20-0x2F), then a final byte (0x40-0x7E). Broader than the +# SGR codes Click emits, so foreign sequences (colon-delimited true-color, mouse +# reporting) are stripped too. +_ansi_re = re.compile(r"\033\[[0-?]*[ -/]*[@-~]") + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO[t.Any]) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write(b"") + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: str | None, errors: str | None +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO[t.Any], + encoding: str | None, + errors: str | None, + is_binary: t.Callable[[t.IO[t.Any], bool], bool], + find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: str | os.PathLike[str] | int, + mode: str, + encoding: str | None, + errors: str | None, +) -> t.IO[t.Any]: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, +) -> tuple[t.IO[t.Any], bool]: + binary = "b" in mode + filename = os.fspath(filename) + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: int | None = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO[t.Any], af), True + + +class _AtomicFile: + def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> _AtomicFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + elif hasattr(stream, "color"): + # ._termui_impl._PagerWriter handles stripping ansi itself, + # so we don't need to strip it here + return False + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# Double check is needed so mypy does not analyze this on Linux. +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None + ) -> t.TextIO | None: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO[t.Any]) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO | None], + wrapper_func: t.Callable[[], t.TextIO], +) -> t.Callable[[], t.TextIO | None]: + cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO | None: + stream = src_func() + + if stream is None: + return None + + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py new file mode 100644 index 0000000..70aabf8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py @@ -0,0 +1,972 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" + +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import math +import os +import shlex +import sys +import time +import typing as t +from gettext import gettext as _ +from io import StringIO +from pathlib import Path +from types import TracebackType + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: cabc.Iterable[V] | None, + length: int | None = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + label: str | None = None, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.hidden = hidden + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label: str = label or "" + + if file is None: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + file = StringIO() + + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width: int = width + self.autowidth: bool = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast("cabc.Iterable[V]", range(length)) + self.iter: cabc.Iterable[V] = iter(iterable) + self.length = length + self.pos: int = 0 + self.avg: list[float] = [] + self.last_eta: float + self.start: float + self.start = self.last_eta = time.time() + self.eta_known: bool = False + self.finished: bool = False + self.max_width: int | None = None + self.entered: bool = False + self.current_item: V | None = None + self._is_atty = isatty(self.file) + self._last_line: str | None = None + + def __enter__(self) -> ProgressBar[V]: + self.entered = True + self.render_progress() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.render_finish() + + def __iter__(self) -> cabc.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + # Steps stay pending until they add up to update_min_steps, so a length + # that is not a multiple of it keeps a sub-threshold remainder that + # never reaches pos. Apply it so the bar settles on its true position. + if self._completed_intervals: + self.make_step(self._completed_intervals) + self._completed_intervals = 0 + self.render_progress() + + if self.hidden or not self._is_atty: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return "{d}{day_label} {h:02}:{m:02}:{s:02}".format( + d=t, + day_label=_("d"), + h=hours, + m=minutes, + s=seconds, + ) + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + if self.hidden: + return + + if not self._is_atty: + # Only output the label once if the output is not a TTY. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + import shutil + + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width and self.max_width is not None: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: V | None = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> cabc.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if not self._is_atty: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +class _PagerWriter: + """Wrap a pager's output stream to strip ANSI styling when colors are + disabled. + + The wrapped stream is owned by the pager strategy that produced it, so this + wrapper never closes it: ``_pipepager`` closes its pipe to signal EOF to the + pager process, ``_tempfilepager`` closes and removes its temporary file, and + ``_nullpager`` leaves an external stream such as ``sys.stdout`` untouched. + + The ``color`` attribute lets :func:`click.echo` detect that ANSI stripping + is handled here, so it doesn't strip a second time (see + :func:`._compat.should_strip_ansi`). + """ + + def __init__(self, stream: t.TextIO, color: bool) -> None: + self._stream = stream + self.color = color + + def write(self, text: str) -> int: + if not self.color: + text = strip_ansi(text) + + return self._stream.write(text) + + def writelines(self, lines: cabc.Iterable[str]) -> None: + for line in lines: + self.write(line) + + def flush(self) -> None: + self._stream.flush() + + def close(self) -> None: + # The pager strategy owns the stream's lifecycle. Flush pending output, + # but never close the underlying stream from here. + self._stream.flush() + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + +def _resolve_pager_command(cmd_parts: list[str]) -> tuple[Path, list[str]] | None: + """Resolve a pager ``argv`` to an absolute command path and its parameters. + + The command is looked up with :func:`shutil.which`, which the + :mod:`subprocess` docs recommend on every platform for an unqualified name: + https://docs.python.org/3/library/subprocess.html#popen-constructor + + Returns ``None`` when there is no command to run or it is not on the path, + leaving the caller to pick a fallback. + """ + if not cmd_parts: + return None + + import shutil + + cmd_filepath = shutil.which(cmd_parts[0]) + + if not cmd_filepath: + return None + + # Normalize to an absolute path without resolving symlinks: multi-call + # binaries such as busybox derive their identity from the link they are + # invoked through, so resolving less -> busybox makes them misbehave. + # See https://github.com/pallets/click/issues/2943 and + # https://github.com/pallets/click/pull/2944 + return Path(cmd_filepath).absolute(), cmd_parts[1:] + + +def _pager_contextmanager( + color: bool | None = None, +) -> t.ContextManager[tuple[t.TextIO, bool | None]]: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if stdout is None: + stdout = StringIO() + + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, color) + + # Split using POSIX mode (the default) so that quote characters are + # stripped from tokens and quoted Windows paths are preserved. + # Non-POSIX mode retains quotes in tokens, and wrapping tokens + # with shlex.quote re-introduces quoting issues on Windows. + pager_cmd_parts = shlex.split(os.environ.get("PAGER", "")) + + if pager_cmd_parts: + # Piping to `more` on Windows adds spurious \r\n, so it gets the temp + # file strategy whatever the user asked for. + use_tempfile = WIN + else: + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, color) + + use_tempfile = WIN or sys.platform.startswith("os2") + pager_cmd_parts = ["more"] if use_tempfile else ["less"] + + resolved = _resolve_pager_command(pager_cmd_parts) + + if resolved is None: + # Page through stdout, which _nullpager leaves open for its owner. + return _nullpager(stdout, color) + + cmd_path, cmd_params = resolved + + if use_tempfile: + return _tempfilepager(cmd_path, cmd_params, color) + + return _pipepager(cmd_path, cmd_params, color) + + +@contextlib.contextmanager +def get_pager_file(color: bool | None = None) -> t.Generator[t.TextIO, None, None]: + """Context manager. + + Yields a writable file-like object which can be used as an output pager. + + .. versionadded:: 8.4.0 + + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + with _pager_contextmanager(color=color) as (stream, color): + # Every pager strategy yields a text stream, so the only thing left to + # do is strip ANSI styling when colors are disabled. A strategy yields + # None when it has no opinion on colors, which settles as off here. The + # wrapper does not close the stream: each strategy owns its stream's + # lifecycle. + writer = _PagerWriter(stream, color=bool(color)) + try: + yield t.cast(t.TextIO, writer) + finally: + writer.flush() + + +def _less_uses_raw_mode(less_env: str, cmd_params: list[str]) -> bool: + """Detect a raw-control-characters request in a ``less`` invocation. + + Parses the tokens of the ``LESS`` environment variable and the command + line: only short-option clusters (``-R``, ``-rX``, ``-XrY``) and the + ``--raw-control-chars`` long option request raw mode. Filenames and + long-option values may carry the letter ``r`` without being such a + request. + """ + try: + env_tokens = shlex.split(less_env) + except ValueError: + # Unbalanced quotes make ``LESS`` unparsable: fall back to whitespace + # splitting rather than failing the pager over it. + env_tokens = less_env.split() + tokens = env_tokens + cmd_params + + for token in tokens: + if token.startswith("--"): + if token.casefold() == "--raw-control-chars": + return True + elif token.startswith("-") and len(token) > 1: + if "r" in token[1:].casefold(): + return True + + return False + + +@contextlib.contextmanager +def _pipepager( + cmd_path: Path, cmd_params: list[str], color: bool | None = None +) -> t.Iterator[tuple[t.TextIO, bool | None]]: + """Page through text by feeding it to another program. + + Invokes the pager via :class:`subprocess.Popen` with an ``argv`` list. + + Invoking a pager through this might support colors: if piping to + ``less`` and the user hasn't decided on colors, ``LESS=-R`` is set + automatically. + """ + import subprocess + + # Make a local copy of the environment to not affect the global one. + env = dict(os.environ) + + # If we're piping to less and the user hasn't decided on colors, we enable + # them: either the invocation already requests raw control characters, or + # ``LESS=-R`` is injected. Match on the stem so a resolved ``less.exe`` is + # recognized like bare ``less``, case-insensitively on Windows since its + # filesystems are. POSIX keeps an exact match. + cmd_name = cmd_path.stem + if WIN: + # ``_pager_contextmanager`` currently routes every Windows invocation + # to ``_tempfilepager``, so this branch is not reachable on Windows + # today: it keeps the detection correct should that routing ever + # change. + cmd_name = cmd_name.casefold() + + if color is None and cmd_name == "less": + less_env = os.environ.get("LESS", "") + if _less_uses_raw_mode(less_env, cmd_params): + color = True + elif not less_env and not cmd_params: + env["LESS"] = "-R" + color = True + + c = subprocess.Popen( + [str(cmd_path)] + cmd_params, + shell=False, + stdin=subprocess.PIPE, + env=env, + errors="replace", + text=True, + ) + # With ``text=True``, ``c.stdin`` is already a text stream that encodes + # writes for the pager process (honoring ``errors="replace"``). + stdin = t.cast(t.TextIO, c.stdin) + try: + yield stdin, color + except BrokenPipeError: + # In case the pager exited unexpectedly, ignore the broken pipe error. + pass + except Exception as e: + # In case there is an exception we want to close the pager immediately + # and let the caller handle it. + # Otherwise the pager will keep running, and the user may not notice + # the error message, or worse yet it may leave the terminal in a broken state. + c.terminate() + raise e + finally: + # We must close stdin and wait for the pager to exit before we continue + try: + stdin.close() + # Close implies flush, so it might throw a BrokenPipeError if the pager + # process exited already. + except BrokenPipeError: + pass + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +@contextlib.contextmanager +def _tempfilepager( + cmd_path: Path, cmd_params: list[str], color: bool | None = None +) -> t.Iterator[tuple[t.TextIO, bool | None]]: + """Page through text by invoking a program on a temporary file. + + Used as the primary pager strategy on Windows (where piping to + ``more`` adds spurious ``\\r\\n``), and as a fallback on other + platforms. The command is invoked with any parameters from ``PAGER`` + followed by the temporary file name. + """ + import subprocess + import tempfile + + encoding = get_best_encoding(sys.stdout) + # On Windows, NamedTemporaryFile cannot be opened by another process + # while Python still has it open, so we use delete=False and clean up manually + # rather than using a contextmanager here. + f = tempfile.NamedTemporaryFile( + mode="w", encoding=encoding, errors="replace", delete=False + ) + try: + yield t.cast(t.TextIO, f), color + f.flush() + f.close() + subprocess.call([str(cmd_path), *cmd_params, f.name]) + finally: + # An error raised while paging skips the close() above, and Windows + # refuses to unlink a file the process still holds open. Closing here + # keeps that PermissionError from replacing the original exception. + # close() is idempotent, so this is a no-op on the success path. + f.close() + os.unlink(f.name) + + +@contextlib.contextmanager +def _nullpager( + stream: t.TextIO, color: bool | None = None +) -> t.Iterator[tuple[t.TextIO, bool | None]]: + """Simply print unformatted text. This is the ultimate fallback. + + The stream comes from elsewhere (typically ``sys.stdout``), so its lifecycle + is left untouched: :class:`_PagerWriter` never closes what it wraps, and it + is the only thing :func:`get_pager_file` hands to the caller. + """ + yield stream, color + + +class Editor: + def __init__( + self, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + + from shutil import which + + for editor in "sensible-editor", "vim", "nano": + if which(editor) is not None: + return editor + return "vi" + + def edit_files(self, filenames: cabc.Iterable[str | os.PathLike[str]]) -> None: + """Open files in the user's editor.""" + import shlex + import subprocess + + editor = self.get_editor() + environ: dict[str, str] | None = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + try: + # Split in POSIX mode (the default) for the same reasons as + # in pager(): strips quotes from tokens and preserves quoted + # Windows paths. + c = subprocess.Popen( + args=shlex.split(editor) + list(filenames), + env=environ, + ) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + @t.overload + def edit(self, text: bytes | bytearray) -> bytes | None: ... + + # We cannot know whether or not the type expected is str or bytes when None + # is passed, so str is returned as that was what was done before. + @t.overload + def edit(self, text: str | None) -> str | None: ... + + def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None: + import tempfile + + if text is None: + data: bytes | bytearray = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_files((name,)) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = ["explorer", "/select,", url] + try: + return subprocess.call(args) + except OSError: + return 127 + else: + try: + os.startfile(url) # type: ignore[attr-defined] + except OSError: + return 127 + return 0 + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = ["cygstart", os.path.dirname(url)] + else: + args = ["cygstart"] + if wait: + args.append("-w") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> None: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + +if sys.platform == "win32": + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + + if echo: + func = t.cast(t.Callable[[], str], msvcrt.getwche) + else: + func = t.cast(t.Callable[[], str], msvcrt.getwch) + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import termios + import tty + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + f: t.TextIO | None + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py new file mode 100644 index 0000000..82840f2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_textwrap.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +import collections.abc as cabc +import textwrap +from contextlib import contextmanager + +from ._compat import _ansi_re +from ._compat import term_len + + +def _truncate_visible(text: str, n: int) -> str: + """Return the longest prefix of ``text`` containing at most ``n`` visible + characters. + + ANSI escape sequences inside the prefix are kept intact and do not count + toward the visible width. A cut is never placed inside an escape sequence. + """ + if n <= 0: + return "" + + visible = 0 + i = 0 + cut = 0 + end = len(text) + while i < end: + m = _ansi_re.match(text, i) + if m is not None: + i = m.end() + continue + visible += 1 + i += 1 + cut = i + if visible >= n: + break + return text[:cut] + + +class TextWrapper(textwrap.TextWrapper): + """``textwrap.TextWrapper`` variant that measures widths by visible + character count. + + ANSI escape sequences embedded in chunks, indents, or the placeholder are + excluded from the width budget. Without this, styled help text (a styled + ``Usage:`` prefix, a colorized option name, ...) would be wrapped earlier + than its visible length warrants and tokens would split mid-word. + """ + + def _handle_long_word( + self, + reversed_chunks: list[str], + cur_line: list[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = _truncate_visible(last, space_left) + res = last[len(cut) :] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + def _wrap_chunks(self, chunks: list[str]) -> list[str]: + """Wrap chunks counting widths in visible characters. + + Mirrors the algorithm of :meth:`textwrap.TextWrapper._wrap_chunks` + with every width measurement routed through + :func:`click._compat.term_len` instead of :func:`len`, so ANSI escape + bytes in chunks, indents, or the placeholder do not inflate the count. + + .. seealso:: + :class:`textwrap.TextWrapper` in the Python standard library documentation: + https://docs.python.org/3/library/textwrap.html#textwrap.TextWrapper + + Reference implementation in CPython: + https://github.com/python/cpython/blob/main/Lib/textwrap.py + """ + lines: list[str] = [] + if self.width <= 0: + raise ValueError(f"invalid width {self.width!r} (must be > 0)") + if self.max_lines is not None: + if self.max_lines > 1: + indent = self.subsequent_indent + else: + indent = self.initial_indent + if term_len(indent) + term_len(self.placeholder.lstrip()) > self.width: + raise ValueError("placeholder too large for max width") + + chunks.reverse() + + while chunks: + cur_line: list[str] = [] + cur_len = 0 + + if lines: + indent = self.subsequent_indent + else: + indent = self.initial_indent + + width = self.width - term_len(indent) + + if self.drop_whitespace and chunks[-1].strip() == "" and lines: + del chunks[-1] + + while chunks: + n = term_len(chunks[-1]) + + if cur_len + n <= width: + cur_line.append(chunks.pop()) + cur_len += n + + else: + break + + if chunks and term_len(chunks[-1]) > width: + self._handle_long_word(chunks, cur_line, cur_len, width) + cur_len = sum(map(term_len, cur_line)) + + if self.drop_whitespace and cur_line and cur_line[-1].strip() == "": + cur_len -= term_len(cur_line[-1]) + del cur_line[-1] + + if cur_line: + if ( + self.max_lines is None + or len(lines) + 1 < self.max_lines + or ( + not chunks + or self.drop_whitespace + and len(chunks) == 1 + and not chunks[0].strip() + ) + and cur_len <= width + ): + lines.append(indent + "".join(cur_line)) + else: + while cur_line: + if ( + cur_line[-1].strip() + and cur_len + term_len(self.placeholder) <= width + ): + cur_line.append(self.placeholder) + lines.append(indent + "".join(cur_line)) + break + cur_len -= term_len(cur_line[-1]) + del cur_line[-1] + else: + if lines: + prev_line = lines[-1].rstrip() + if ( + term_len(prev_line) + term_len(self.placeholder) + <= self.width + ): + lines[-1] = prev_line + self.placeholder + break + lines.append(indent + self.placeholder.lstrip()) + break + + return lines + + @contextmanager + def extra_indent(self, indent: str) -> cabc.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py new file mode 100644 index 0000000..05ee2e9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_utils.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import enum +import typing as t + + +class Sentinel(enum.Enum): + """Enum used to define sentinel values. + + .. seealso:: + + `PEP 661 - Sentinel Values `_. + """ + + UNSET = object() + FLAG_NEEDS_VALUE = object() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + +UNSET: t.Literal[Sentinel.UNSET] = Sentinel.UNSET +"""Sentinel used to indicate that a value is not set.""" + +FLAG_NEEDS_VALUE: t.Literal[Sentinel.FLAG_NEEDS_VALUE] = Sentinel.FLAG_NEEDS_VALUE +"""Sentinel used to indicate an option was passed as a flag without a +value but is not a flag option. + +``Option.consume_value`` uses this to prompt or use the ``flag_value``. +""" + +T_UNSET: t.TypeAlias = t.Literal[Sentinel.UNSET] +"""Type hint for the :data:`UNSET` sentinel value.""" + +T_FLAG_NEEDS_VALUE: t.TypeAlias = t.Literal[Sentinel.FLAG_NEEDS_VALUE] +"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py new file mode 100644 index 0000000..d25178d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/_winconsole.py @@ -0,0 +1,297 @@ +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +from __future__ import annotations + +import collections.abc as cabc +import io +import sys +import time +import typing as t +from ctypes import Array +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR +from gettext import gettext as _ + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +if t.TYPE_CHECKING: + try: + # Using `typing_extensions.Buffer` instead of `collections.abc` + # on Windows for some reason does not have `Sized` implemented. + from collections.abc import Buffer # type: ignore + except ImportError: + from typing_extensions import Buffer + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ # noqa: RUF012 + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]: + buf = Py_buffer() + flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + out: Array[c_char] = buffer_type.from_address(buf.buf) + return out + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle: int | None) -> None: + self.handle = handle + + def isatty(self) -> t.Literal[True]: + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self) -> t.Literal[True]: + return True + + def readinto(self, b: Buffer) -> int: + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(_("Windows error: {error}").format(error=GetLastError())) + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self) -> t.Literal[True]: + return True + + @staticmethod + def _get_error_message(errno: int) -> str: + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return _("Windows error: {error}").format(error=errno) + + def write(self, b: Buffer) -> int: + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: cabc.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self) -> str: + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: cabc.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None +) -> t.TextIO | None: + if ( + get_buffer is None + or encoding not in {"utf-16-le", None} + or errors not in {"strict", None} + or not _is_console(f) + ): + return None + + func = _stream_factories.get(f.fileno()) + if func is None: + return None + + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py new file mode 100644 index 0000000..de129ec --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/core.py @@ -0,0 +1,3799 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import errno +import inspect +import os +import sys +import typing as t +from abc import ABC +from abc import abstractmethod +from collections import abc +from collections import Counter +from contextlib import AbstractContextManager +from contextlib import contextmanager +from contextlib import ExitStack +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat +from types import TracebackType + +from . import types +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import NoArgsIsHelpError +from .exceptions import NoSuchCommand +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _OptionParser +from .parser import _split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import _make_default_short_help +from .utils import _PacifyFlushWrapper +from .utils import echo +from .utils import make_str + +if t.TYPE_CHECKING: + from typing_extensions import Self + + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound="t.Callable[..., t.Any]") +V = t.TypeVar("V") + +# Reserved storage name of the automatic help option. No user parameter is +# expected to claim it. +_HELP_OPTION_STORAGE_NAME = "_click_default_help" + + +def _complete_visible_commands( + ctx: Context, incomplete: str +) -> cabc.Iterator[tuple[str, Command]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(Group, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_nested_chain( + base_command: Group, cmd_name: str, cmd: Command, register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, Group): + return + + if register: + message = ( + f"It is not possible to add the group {cmd_name!r} to another" + f" group {base_command.name!r} that is in chain mode." + ) + else: + message = ( + f"Found the group {cmd_name!r} as subcommand to another group " + f" {base_command.name!r} that is in chain mode. This is not supported." + ) + + raise RuntimeError(message) + + +def _format_deprecated_label(deprecated: bool | str) -> str: + """Return the parenthesized deprecation label shown in help text.""" + label = _("deprecated").upper() + if isinstance(deprecated, str): + return f"({label}: {deprecated})" + return f"({label})" + + +def _format_deprecated_suffix(deprecated: bool | str) -> str: + """Return the trailing reason for a ``DeprecationWarning`` message, + prefixed with a space, or an empty string when no reason was given. + """ + if isinstance(deprecated, str): + return f" {deprecated}" + return "" + + +def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size), strict=False)) + + +@contextmanager +def augment_usage_errors( + ctx: Context, param: Parameter | None = None +) -> cabc.Generator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: cabc.Sequence[Parameter], + declaration_order: cabc.Sequence[Parameter], +) -> list[Parameter]: + """Returns all declared parameters in the order they should be processed. + + The declared parameters are re-shuffled depending on the order in which + they were invoked, as well as the eagerness of each parameters. + + The invocation order takes precedence over the declaration order. I.e. the + order in which the user provided them to the CLI is respected. + + This behavior and its effect on callback evaluation is detailed at: + https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order + """ + + def sort_key(item: Parameter) -> tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.IntEnum): + """This is an :class:`~enum.IntEnum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + Members are ordered from most explicit to least explicit source. + This allows comparison to check if a value was explicitly provided: + + .. code-block:: python + + source = ctx.get_parameter_source("port") + if source < click.ParameterSource.DEFAULT_MAP: + ... # value was explicitly set + + .. versionchanged:: 8.3.3 + Use :class:`~enum.IntEnum` and reorder members from most to + least explicit. Supports comparison operators. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below that it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.2 + The ``protected_args`` attribute is deprecated and will be removed in + Click 9.0. ``args`` will contain remaining unparsed tokens. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: type[HelpFormatter] = HelpFormatter + + parent: Context | None + command: Command + info_name: str | None + params: dict[str, t.Any] + args: list[str] + _protected_args: list[str] + _opt_prefixes: set[str] + obj: t.Any + _meta: dict[str, t.Any] + default_map: cabc.MutableMapping[str, t.Any] | None + invoked_subcommand: str | None + terminal_width: int | None + max_content_width: int | None + allow_extra_args: bool + allow_interspersed_args: bool + ignore_unknown_options: bool + help_option_names: list[str] + token_normalize_func: t.Callable[[str], str] | None + resilient_parsing: bool + auto_envvar_prefix: str | None + color: bool | None + show_default: bool | None + _close_callbacks: list[t.Callable[[], t.Any]] + _depth: int + _parameter_source: dict[str, ParameterSource] + _param_default_explicit: dict[str, bool] + _exit_stack: ExitStack + + def __init__( + self, + command: Command, + parent: Context | None = None, + info_name: str | None = None, + obj: t.Any | None = None, + auto_envvar_prefix: str | None = None, + default_map: cabc.MutableMapping[str, t.Any] | None = None, + terminal_width: int | None = None, + max_content_width: int | None = None, + resilient_parsing: bool = False, + allow_extra_args: bool | None = None, + allow_interspersed_args: bool | None = None, + ignore_unknown_options: bool | None = None, + help_option_names: list[str] | None = None, + token_normalize_func: t.Callable[[str], str] | None = None, + color: bool | None = None, + show_default: bool | None = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params = {} + #: the leftover arguments. + self.args = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self._protected_args = [] + #: the collected prefixes of the command's options. + self._opt_prefixes = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj = obj + self._meta = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default = show_default + + self._close_callbacks = [] + self._depth = 0 + self._parameter_source = {} + # Tracks whether the option that currently owns each parameter slot in + # :attr:`params` had its ``default`` set explicitly by the user. Used + # to tie-break feature-switch groups where multiple options share a + # parameter name and both fall back to their default value. + # Refs: https://github.com/pallets/click/issues/3403 + self._param_default_explicit = {} + self._exit_stack = ExitStack() + + @property + def protected_args(self) -> list[str]: + import warnings + + warnings.warn( + "'protected_args' is deprecated and will be removed in Click 9.0." + " 'args' will contain remaining unparsed tokens.", + DeprecationWarning, + stacklevel=2, + ) + return self._protected_args + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> Self: + self._depth += 1 + push_context(self) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + self._depth -= 1 + exit_result: bool | None = None + if self._depth == 0: + exit_result = self._close_with_exception_info(exc_type, exc_value, tb) + pop_context() + + return exit_result + + @contextmanager + def scope(self, cleanup: bool = True) -> cabc.Generator[Context]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: AbstractContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._close_with_exception_info(None, None, None) + + def _close_with_exception_info( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + """Unwind the exit stack by calling its :meth:`__exit__` providing the exception + information to allow for exception handling by the various resources registered + using :meth;`with_resource` + + :return: Whatever ``exit_stack.__exit__()`` returns. + """ + exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + return exit_result + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> Context: + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: type[V]) -> V | None: + """Finds the closest object of a given type.""" + node: Context | None = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + def _default_map_has(self, name: str | None) -> bool: + """Check if :attr:`default_map` contains a real value for ``name``. + + Returns ``False`` when the key is absent, the map is ``None``, + ``name`` is ``None``, or the stored value is the internal + :data:`UNSET` sentinel. + """ + return ( + name is not None + and self.default_map is not None + and name in self.default_map + and self.default_map[name] is not UNSET + ) + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[False] = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def lookup_default(self, name: str, call: bool = True) -> t.Any | None: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if not self._default_map_has(name): + return None + + # Assert to make the type checker happy. + assert self.default_map is not None + value = self.default_map[name] + + if call and callable(value): + return value() + + return value + + def fail(self, message: str) -> t.NoReturn: + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> t.NoReturn: + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> t.NoReturn: + """Exits the application with a given exit code. + + .. versionchanged:: 8.2 + Callbacks and context managers registered with :meth:`call_on_close` + and :meth:`with_resource` are closed before exiting. + """ + self.close() + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: Command) -> Context: + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + @t.overload + def invoke( + self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> V: ... + + @t.overload + def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ... + + def invoke( + self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> t.Any | V: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + + .. versionchanged:: 3.2 + A new context is created, and missing arguments use default values. + """ + if isinstance(callback, Command): + other_cmd = callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + callback = t.cast("t.Callable[..., V]", other_cmd.callback) + + ctx = self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + default_value = param.get_default(ctx) + # We explicitly hide the :attr:`UNSET` value to the user, as we + # choose to make it an implementation detail. And because ``invoke`` + # has been designed as part of Click public API, we return ``None`` + # instead. Refs: + # https://github.com/pallets/click/issues/3066 + # https://github.com/pallets/click/issues/3065 + # https://github.com/pallets/click/pull/3068 + if default_value is UNSET: + default_value = None + kwargs[param.name] = param.type_cast_value(ctx, default_value) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = self + + with augment_usage_errors(self), ctx: + return callback(*args, **kwargs) + + def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(cmd, Command): + raise TypeError("Callback is not a command.") + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> ParameterSource | None: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class Command: + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the command is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. + + .. versionchanged:: 8.2 + This is the base class for all commands, not ``BaseCommand``. + ``deprecated`` can be set to a string as well to customize the + deprecation message. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: type[Context] = Context + + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + name: str | None + context_settings: cabc.MutableMapping[str, t.Any] + callback: t.Callable[..., t.Any] | None + params: list[Parameter] + help: str | None + epilog: str | None + options_metavar: str | None + short_help: str | None + add_help_option: bool + _help_option: Option | None + no_args_is_help: bool + hidden: bool + deprecated: bool | str + + def __init__( + self, + name: str | None, + context_settings: cabc.MutableMapping[str, t.Any] | None = None, + callback: t.Callable[..., t.Any] | None = None, + params: list[Parameter] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, + options_metavar: str | None = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool | str = False, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings = context_settings + + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self._help_option = None + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + return { + "name": self.name, + "params": [param.to_info_dict() for param in self.get_params(ctx)], + "help": self.help, + "epilog": self.epilog, + "short_help": self.short_help, + "hidden": self.hidden, + "deprecated": self.deprecated, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> list[Parameter]: + params = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + params = [*params, help_option] + + if __debug__: + import warnings + + opts = [opt for param in params for opt in param.opts] + opts_counter = Counter(opts) + duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1) + + for duplicate_opt in duplicate_opts: + warnings.warn( + ( + f"The parameter {duplicate_opt} is used more than once. " + "Remove its duplicate as parameters should be unique." + ), + stacklevel=3, + ) + + # Options may deliberately share a storage name to compete for + # the same value (feature switches), but an argument sharing a + # name silently overwrites the other parameter's value. + names_counter = Counter(param.name for param in params) + duplicate_names = ( + name for name, count in names_counter.items() if count > 1 + ) + + for duplicate_name in duplicate_names: + sharers = [param for param in params if param.name == duplicate_name] + + if help_option in sharers: + warnings.warn( + ( + f"The name {duplicate_name!r} is reserved for the " + "automatic help option. Give the parameter a " + "different name." + ), + stacklevel=3, + ) + elif any(isinstance(param, Argument) for param in sharers): + warnings.warn( + ( + f"The name {duplicate_name!r} is used by an argument " + "and another parameter. They will overwrite each " + "other's value during parsing. Give each parameter " + "a unique name." + ), + stacklevel=3, + ) + + return params + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> list[str]: + """Returns the names for the help option. + + Drops duplicates and names already reserved by another parameter. Order of + :attr:`Context.help_option_names` is preserved, so the result is stable. + + .. versionchanged:: 8.5.0 + Names keep their declaration order. + """ + all_names = dict.fromkeys(ctx.help_option_names) + for param in self.params: + for name in (*param.opts, *param.secondary_opts): + all_names.pop(name, None) + return list(all_names) + + def get_help_option(self, ctx: Context) -> Option | None: + """Returns the help option object. + + Skipped if :attr:`add_help_option` is ``False``. + + .. versionchanged:: 8.5.0 + The help option stores its value under the reserved name + ``_click_default_help``, so a parameter named ``help`` no + longer breaks parsing. + + .. versionchanged:: 8.1.8 + The help option is now cached to avoid creating it multiple times. + """ + help_option_names = self.get_help_option_names(ctx) + + if not help_option_names or not self.add_help_option: + return None + + # Cache the help option object in private _help_option attribute to + # avoid creating it multiple times. Not doing this will break the + # callback ordering by iter_params_for_processing(), which relies on + # object comparison. + if self._help_option is None: + # Avoid circular import. + from .decorators import help_option + + # The help option never exposes its value, so it uses a reserved + # storage name, keeping it clear of user parameters (like an + # argument named "help") that would otherwise clobber its value. + help_option(*help_option_names, _HELP_OPTION_STORAGE_NAME)(self) + self._help_option = self.params.pop() # type: ignore[assignment] + + return self._help_option + + def make_parser(self, ctx: Context) -> _OptionParser: + """Creates the underlying option parser for this command.""" + parser = _OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = _make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + text = f"{_(text)} {_format_deprecated_label(self.deprecated)}" + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_arguments` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_arguments(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + if self.help is not None: + # truncate the help text to the first form feed + text = inspect.cleandoc(self.help).partition("\f")[0] + else: + text = "" + + if self.deprecated: + label = _format_deprecated_label(self.deprecated) + text = f"{_(text)} {label}" if text else label + + if text: + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None and not isinstance(param, Argument): + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_arguments(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the arguments that have a help record into the formatter.""" + args = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None and isinstance(param, Argument): + args.append(rv) + + if args: + with formatter.section(_("Positional arguments")): + formatter.write_dl(args) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: Context | None = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + _, args = param.handle_parse_result(ctx, opts, args) + + # We now have all parameters' values into `ctx.params`, but the data may contain + # the `UNSET` sentinel. + # Convert `UNSET` to `None` to ensure that the user doesn't see `UNSET`. + # + # Waiting until after the initial parse to convert allows us to treat `UNSET` + # more like a missing value when multiple params use the same name. + # Refs: + # https://github.com/pallets/click/issues/3071 + # https://github.com/pallets/click/pull/3079 + for name, value in ctx.params.items(): + if value is UNSET: + ctx.params[name] = None + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" + ).format( + name=self.name, + extra_message=_format_deprecated_suffix(self.deprecated), + ) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: list[CompletionItem] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, Group) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx._protected_args + ) + + return results + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: t.Literal[True] = True, + **extra: t.Any, + ) -> t.NoReturn: ... + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: ... + + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt) as e: + echo(file=sys.stderr) + raise Abort() from e + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, _PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, _PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str | None = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + + .. versionchanged:: 8.2.0 + Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). + """ + if complete_var is None: + complete_name = prog_name.replace("-", "_").replace(".", "_") + complete_var = f"_{complete_name}_COMPLETE".upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: type) -> bool: + return issubclass(subclass, cls.__bases__[0]) + + def __instancecheck__(cls, instance: t.Any) -> bool: + return isinstance(instance, cls.__bases__[0]) + + +class _BaseCommand(Command, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Command`` instead. + """ + + +class Group(Command): + """A group is a command that nests other commands (or more groups). + + :param name: The name of the group command. + :param commands: Map names to :class:`Command` objects. Can be a list, which + will use :attr:`Command.name` as the keys. + :param invoke_without_command: Invoke the group's callback even if a + subcommand is not given. + :param no_args_is_help: If no arguments are given, show the group's help and + exit. Defaults to the opposite of ``invoke_without_command``. + :param subcommand_metavar: How to represent the subcommand argument in help. + The default will represent whether ``chain`` is set or not. + :param chain: Allow passing more than one subcommand argument. After parsing + a command's arguments, if any arguments remain another command will be + matched, and so on. + :param result_callback: A function to call after the group's and + subcommand's callbacks. The value returned by the subcommand is passed. + If ``chain`` is enabled, the value will be a list of values returned by + all the commands. If ``invoke_without_command`` is enabled, the value + will be the value returned by the group's callback, or an empty list if + ``chain`` is enabled. + :param kwargs: Other arguments passed to :class:`Command`. + + .. versionchanged:: 8.0 + The ``commands`` argument can be a list of command objects. + + .. versionchanged:: 8.2 + Merged with and replaces the ``MultiCommand`` base class. + """ + + allow_extra_args = True + allow_interspersed_args = False + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: type[Command] | None = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: type[Group | type] | None = None + # Literal[type] isn't valid, so use Type[type] + + commands: cabc.MutableMapping[str, Command] + invoke_without_command: bool + subcommand_metavar: str + chain: bool + _result_callback: t.Callable[..., t.Any] | None + + def __init__( + self, + name: str | None = None, + commands: cabc.MutableMapping[str, Command] + | cabc.Sequence[Command] + | None = None, + invoke_without_command: bool = False, + no_args_is_help: bool | None = None, + subcommand_metavar: str | None = None, + chain: bool = False, + result_callback: t.Callable[..., t.Any] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands = commands + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + # When the group can run without a subcommand, the leading command + # token is optional, so wrap it in brackets to reflect that. + if chain: + if invoke_without_command: + subcommand_metavar = "[COMMAND1] [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + elif invoke_without_command: + subcommand_metavar = "[COMMAND] [ARGS]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "A group in chain mode cannot have optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def add_command(self, cmd: Command, name: str | None = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_nested_chain(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command] | Command: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'command(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> Group: ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group] | Group: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'group(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> Group: + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(value: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + inner = old_callback(value, *args, **kwargs) + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv # type: ignore[return-value] + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + """Given a context and a command name, this returns a :class:`Command` + object if it exists or returns ``None``. + """ + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> list[str]: + """Returns a list of subcommand names in the order they should appear.""" + return sorted(self.commands) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx._protected_args = rest + ctx.args = [] + elif rest: + ctx._protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx._protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx._protected_args, *ctx.args] + ctx.args = [] + ctx._protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: list[str] + ) -> tuple[str | None, Command | None, list[str]]: + cmd_name = make_str(args[0]) + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if _split_opt(cmd_name)[0]: + self.parse_args(ctx, args) + raise NoSuchCommand(cmd_name, possibilities=self.commands, ctx=ctx) + return cmd_name if cmd else None, cmd, args[1:] + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class _MultiCommand(Group, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Group`` instead. + """ + + +class CommandCollection(Group): + """A :class:`Group` that looks up subcommands on other groups. If a command + is not found on this group, each registered source is checked in order. + Parameters on a source are not added to this group, and a source's callback + is not invoked when invoking its commands. In other words, this "flattens" + commands in many groups into this one group. + + :param name: The name of the group command. + :param sources: A list of :class:`Group` objects to look up commands from. + :param kwargs: Other arguments passed to :class:`Group`. + + .. versionchanged:: 8.2 + This is a subclass of ``Group``. Commands are looked up first on this + group, then each of its sources. + """ + + sources: list[Group] + + def __init__( + self, + name: str | None = None, + sources: list[Group] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + #: The list of registered groups. + self.sources = sources or [] + + def add_source(self, group: Group) -> None: + """Add a group as a source of commands.""" + self.sources.append(group) + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + rv = super().get_command(ctx, cmd_name) + + if rv is not None: + return rv + + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_nested_chain(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> list[str]: + rv: set[str] = set(super().list_commands(ctx)) + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: cabc.Iterable[V]) -> cabc.Iterator[V]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter(ABC): + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The latter is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: environment variable(s) that are used to provide a default value for + this parameter. This can be a string or a sequence of strings. If a sequence is + given, only the first non-empty environment variable is used for the parameter. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the argument is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. A deprecated parameter + cannot be required, a ValueError will be raised otherwise. + + .. versionchanged:: 8.2.0 + Introduction of ``deprecated``. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + name: str + opts: list[str] + secondary_opts: list[str] + # `Parameter.type` is annotated in `__init__` to avoid confusing mypy + required: bool + callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None + nargs: int + multiple: bool + expose_value: bool + default: t.Any | t.Callable[[], t.Any] | None + _default_explicit: bool + is_eager: bool + metavar: str | None + envvar: str | cabc.Sequence[str] | None + _custom_shell_complete: ( + t.Callable[[Context, Parameter, str], list[CompletionItem] | list[str]] | None + ) + deprecated: bool | str + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + type: types.ParamType[t.Any] | t.Any | None = None, + required: bool = False, + # XXX The default historically embed two concepts: + # - the declaration of a Parameter object carrying the default (handy to + # arbitrage the default value of coupled Parameters sharing the same + # self.name, like flag options), + # - and the actual value of the default. + # It is confusing and is the source of many issues discussed in: + # https://github.com/pallets/click/pull/3030 + # In the future, we might think of splitting it in two, not unlike + # Option.is_flag and Option.flag_value: we could have something like + # Parameter.is_default and Parameter.default_value. + default: t.Any | t.Callable[[], t.Any] | None = UNSET, + callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None, + nargs: int | None = None, + multiple: bool = False, + metavar: str | None = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: str | cabc.Sequence[str] | None = None, + shell_complete: t.Callable[ + [Context, Parameter, str], list[CompletionItem] | list[str] + ] + | None = None, + deprecated: bool | str = False, + ) -> None: + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type: types.ParamType[t.Any] = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + # Whether the user passed ``default`` explicitly to the constructor. + # Captured before any auto-derived default (like ``False`` for boolean + # flags in :class:`Option`) replaces the :data:`UNSET` sentinel, so it + # remains ``False`` when the default was inferred rather than chosen. + # Refs: https://github.com/pallets/click/issues/3403 + self._default_explicit = default is not UNSET + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + self.deprecated = deprecated + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + if required and deprecated: + raise ValueError( + f"The {self.param_type_name} '{self.human_readable_name}' " + "is deprecated and still required. A deprecated " + f"{self.param_type_name} cannot be required." + ) + + @staticmethod + def _hide_unset(value: t.Any) -> t.Any: + """Present the internal :data:`UNSET` sentinel as ``None`` at a boundary that + exposes a parameter's value (introspection, prompts), keeping the sentinel an + implementation detail. + """ + return None if value is UNSET else value + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`default` if it was not set. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self._hide_unset(self.default), + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + @abstractmethod + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str, list[str], list[str]]: ... + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(param=self, ctx=ctx) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) + + if value is None and not ctx._default_map_has(self.name): + value = self.default + + if call and callable(value): + value = value() + + return value + + @abstractmethod + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: ... + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, t.Any] + ) -> tuple[t.Any, ParameterSource]: + """Returns the parameter value produced by the parser. + + If the parser did not produce a value from user input, the value is either + sourced from the environment variable, the default map, or the parameter's + default value. In that order of precedence. + + If no value is found, an internal sentinel value is returned. + + :meta private: + """ + # Collect from the parse the value passed by the user to the CLI. + value = opts.get(self.name, UNSET) + # If the value is set, it means it was sourced from the command line by the + # parser, otherwise it left unset by default. + source = ( + ParameterSource.COMMANDLINE + if value is not UNSET + else ParameterSource.DEFAULT + ) + + if value is UNSET: + envvar_value = self.value_from_envvar(ctx) + if envvar_value is not None: + value = envvar_value + source = ParameterSource.ENVIRONMENT + + if value is UNSET: + default_map_value = ctx.lookup_default(self.name) + if default_map_value is not None or ctx._default_map_has(self.name): + value = default_map_value + source = ParameterSource.DEFAULT_MAP + + # A string from default_map must be split for multi-value + # parameters, matching value_from_envvar behavior. + if isinstance(value, str) and self.nargs != 1: + value = self.type.split_envvar_value(value) + + if value is UNSET: + default_value = self.get_default(ctx) + if default_value is not UNSET: + value = default_value + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the parameter's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + if self.multiple or self.nargs == -1: + return () + else: + return value + + def check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + # Define the conversion function based on nargs and type. + + if self.nargs == 1 or self.type.is_composite: + + def convert(value: t.Any) -> t.Any: + return self.type(value, param=self, ctx=ctx) + + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + """A value is considered missing if: + + - it is :attr:`UNSET`, + - or if it is an empty sequence while the parameter is suppose to have + non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is + set). + + :meta private: + """ + if value is UNSET: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + """Process the value of this parameter: + + 1. Type cast the value using :meth:`type_cast_value`. + 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise + :exc:`MissingParameter` if it is required. + 3. If a :attr:`callback` is set, call it to have the value replaced by the + result of the callback. If the value was not set, the callback receive + ``None``. This keep the legacy behavior as it was before the introduction of + the :attr:`UNSET` sentinel. + + :meta private: + """ + # shelter `type_cast_value` from ever seeing an `UNSET` value by handling the + # cases in which `UNSET` gets special treatment explicitly at this layer + # + # Refs: + # https://github.com/pallets/click/issues/3069 + if value is UNSET: + if self.multiple or self.nargs == -1: + value = () + else: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + # Legacy case: UNSET is not exposed directly to the callback, but converted + # to None. + if value is UNSET: + value = None + + # Search for parameters with UNSET values in the context. + unset_keys = {k: None for k, v in ctx.params.items() if v is UNSET} + # No UNSET values, call the callback as usual. + if not unset_keys: + value = self.callback(ctx, self, value) + + # Legacy case: provide a temporarily manipulated context to the callback + # to hide UNSET values as None. + # + # Refs: + # https://github.com/pallets/click/issues/3136 + # https://github.com/pallets/click/pull/3137 + else: + # Add another layer to the context stack to clearly hint that the + # context is temporarily modified. + with ctx: + # Update the context parameters to replace UNSET with None. + ctx.params.update(unset_keys) + # Feed these fake context parameters to the callback. + value = self.callback(ctx, self, value) + # Restore the UNSET values in the context parameters. + ctx.params.update( + { + k: UNSET + for k in unset_keys + # Only restore keys that are present and still None, in case + # the callback modified other parameters. + if k in ctx.params and ctx.params[k] is None + } + ) + + return value + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """Returns the value found in the environment variable(s) attached to this + parameter. + + Environment variables values are `always returned as strings + `_. + + This method returns ``None`` if: + + - the :attr:`envvar` property is not set on the :class:`Parameter`, + - the environment variable is not found in the environment, + - the variable is found in the environment but its value is empty (i.e. the + environment variable is present but has an empty string). + + If :attr:`envvar` is setup with multiple environment variables, + then only the first non-empty value is returned. + + .. caution:: + + The raw value extracted from the environment is not normalized and is + returned as-is. Any normalization or reconciliation is performed later by + the :class:`Parameter`'s :attr:`type`. + + :meta private: + """ + if not self.envvar: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + # Return the first non-empty value of the list of environment variables. + if rv: + return rv + # Else, absence of value is interpreted as an environment variable that + # is not set, so proceed to the next one. + + return None + + def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None: + """Process the raw environment variable string for this parameter. + + Returns the string as-is or splits it into a sequence of strings if the + parameter is expecting multiple values (i.e. its :attr:`nargs` property is set + to a value other than ``1``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + return self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] + ) -> tuple[t.Any, list[str]]: + """Process the value produced by the parser from user input. + + Always process the value through the Parameter's :attr:`type`, wherever it + comes from. + + If the parameter is deprecated, this method warn the user about it. But only if + the value has been explicitly set by the user (and as such, is not coming from + a default). + + :meta private: + """ + # Capture the slot's existing state before we mutate + # ``_parameter_source`` so the write decision below can compare our + # incoming source against the source of the option that already wrote + # the slot (if any). + existing_value = ctx.params.get(self.name, UNSET) + existing_source = ctx.get_parameter_source(self.name) + existing_default_explicit = ctx._param_default_explicit.get(self.name, False) + + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + + # Record the source before processing so eager callbacks and type + # conversion can inspect it. Restored after arbitration if this + # option loses a feature-switch group. + ctx.set_parameter_source(self.name, source) + + # Display a deprecation warning if necessary. + if ( + self.deprecated + and value is not UNSET + and source < ParameterSource.DEFAULT_MAP + ): + message = _( + "DeprecationWarning: The {param_type} {name!r} is deprecated." + "{extra_message}" + ).format( + param_type=self.param_type_name, + name=self.human_readable_name, + extra_message=_format_deprecated_suffix(self.deprecated), + ) + echo(style(message, fg="red"), err=True) + + # Process the value through the parameter's type. + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + # In resilient parsing mode, we do not want to fail the command if the + # value is incompatible with the parameter type, so we reset the value + # to UNSET, which will be interpreted as a missing value. + value = UNSET + + # Arbitrate the slot when several parameters target the same variable + # name (feature-switch groups). See: https://github.com/pallets/click/issues/3403 + slot_empty = existing_value is UNSET + more_explicit = existing_source is not None and source < existing_source + same_source = existing_source is not None and source == existing_source + auto_would_downgrade_explicit = ( + same_source + and source == ParameterSource.DEFAULT + and existing_default_explicit + and not self._default_explicit + ) + is_winner = ( + slot_empty + or more_explicit + or (same_source and not auto_would_downgrade_explicit) + ) + + if is_winner: + if self.expose_value: + ctx.params[self.name] = value + ctx._param_default_explicit[self.name] = self._default_explicit + elif existing_source is not None: + # Lost arbitration; restore the winning option's source. + ctx.set_parameter_source(self.name, existing_source) + # else: ctx.params[self.name] was populated by code that bypassed + # handle_parse_result (from another option's callback for example). Keep + # the provisional source recorded before process_value so downstream + # lookups don't return ``None``. + + return value, args + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + return None + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [] + + def get_error_hint(self, ctx: Context | None) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + + .. versionchanged:: 8.4.0 + ``ctx`` can be ``None``. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType[t.Any].shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast("list[CompletionItem]", results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page and error messages. + Normally, environment variables are not shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. A deprecated option cannot be + prompted. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + :param attrs: Other command arguments described in :class:`Parameter`. + + .. versionchanged:: 8.4.0 + Non-basic ``flag_value`` types (not ``str``, ``int``, ``float``, or + ``bool``) are passed through unchanged instead of being stringified. + Previously, ``type=click.UNPROCESSED`` was required to preserve them. + + .. versionchanged:: 8.2 + ``envvar`` used with ``flag_value`` will always use the ``flag_value``, + previously it would use the value of the environment variable. + + .. versionchanged:: 8.1 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given, for basic Python + types (``str``, ``int``, ``float``, ``bool``). + """ + + param_type_name = "option" + + prompt: str | None + confirmation_prompt: bool | str + prompt_required: bool + hide_input: bool + hidden: bool + + _flag_needs_value: bool + is_flag: bool + flag_value: t.Any + type: types.ParamType[t.Any] + default: t.Any | t.Callable[[], t.Any] | None + + count: bool + allow_from_autoenv: bool + help: str | None + show_default: bool | str | None + show_choices: bool + show_envvar: bool + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + show_default: bool | str | None = None, + prompt: bool | str = False, + confirmation_prompt: bool | str = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: bool | None = None, + flag_value: t.Any = UNSET, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: types.ParamType[t.Any] | t.Any | None = None, + help: str | None = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + deprecated: bool | str = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + super().__init__( + param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs + ) + + # Phase 1: prompt-related attributes. ``_infer_flag_kind`` reads ``self.prompt`` + # and ``self.prompt_required`` so this must run first. + if prompt is True: + if not self.name: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + if deprecated: + label = _format_deprecated_label(deprecated) + help = f"{help} {label}" if help else label + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # Phase 2: flag-kind inference. + self.is_flag, self._flag_needs_value = self._infer_flag_kind( + is_flag, flag_value + ) + + # Phase 3: type inference. Override the type set by :meth:`Parameter.__init__` + # when this option is a flag or a count. + self.type = self._pick_type(type, flag_value, count, self.is_flag) + + # Phase 4: store the raw ``flag_value`` and ``count`` settings. + # ``self.flag_value`` and ``self.default`` deliberately keep the :data:`UNSET` + # sentinel when the user didn't pass them. Auto-derived values are resolved + # lazily in :meth:`_resolve_lazy_default` and :attr:`flag_activation_value`. + # Keeping the raw values means ``is UNSET`` reliably answers "did the user pass + # this?": #3403 needs it for arbitration, and any future feature needing the + # same distinction can reuse it without reintroducing parallel "was it + # explicit?" tracking. + self.flag_value = flag_value + self.count = count + if count and self.default is UNSET: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + # Phase 5: validate. Raises on illegal kwarg combinations. + self._validate(prompt, deprecated) + + @property + def is_bool_flag(self) -> bool: + """``True`` when this option is a flag with a boolean type. + + Derived from :attr:`is_flag` and :attr:`type`; computed on access so it cannot + drift if a subclass replaces :attr:`type` after construction. + """ + return self.is_flag and isinstance(self.type, types.BoolParamType) + + @property + def flag_activation_value(self) -> t.Any: + """Value the function receives when this flag is activated on the command line. + + Resolves a missing :attr:`flag_value` to ``True`` for actual flag options and + ``None`` otherwise. Used by the parser bridge (:meth:`add_to_parser`) and the + runtime (:meth:`consume_value`) so :attr:`flag_value` can keep the :data:`UNSET` + sentinel for "did the user pass one?" introspection. + """ + if self.flag_value is UNSET: + return True if self.is_flag else None + return self.flag_value + + def _infer_flag_kind( + self, is_flag: bool | None, flag_value: t.Any + ) -> tuple[bool, bool]: + """Resolve ``is_flag`` and the parser hint ``_flag_needs_value``. + + Returns ``(is_flag, flag_needs_value)``, where ``_flag_needs_value`` tells the + parser this option is a flag that cannot be used standalone and needs a value. + The parser uses it to decide whether to treat the next CLI token as the flag's + value or as a new option. If ``prompt`` is enabled with + ``prompt_required=False``, it opens the door for an interactive value, hence the + initial condition. Ref: https://github.com/pallets/click/issues/3084 + """ + needs_value = self.prompt is not None and not self.prompt_required + + if is_flag is None: + # Implicitly a flag because flag_value was set. + if flag_value is not UNSET: + return True, needs_value + # Not a flag, but when used as a flag it shows a prompt. + if needs_value: + return False, needs_value + # Implicitly a flag because secondary options names were given. + if self.secondary_opts: + return True, needs_value + return False, needs_value + + if is_flag is False and not needs_value: + # Explicit ``is_flag=False`` with a flag-like value/default still makes the + # option flag-shaped to the parser. + needs_value = flag_value is not UNSET or self.default is UNSET + + return bool(is_flag), needs_value + + def _pick_type( + self, + type_arg: t.Any, + flag_value: t.Any, + count: bool, + is_flag: bool, + ) -> types.ParamType[t.Any]: + """Pick the final :class:`ParamType` for this option. + + :meth:`Parameter.__init__` already stored ``self.type`` from the explicit + ``type`` argument or from ``default``. This method either returns that as-is, or + overrides it for flag and count options (which are inferred from ``flag_value`` + rather than ``default``). + """ + if type_arg is not None: + return self.type + + if count: + return types.IntRange(min=0) + + if not is_flag: + return self.type + + # A flag without a flag_value is a boolean flag. + if flag_value is UNSET or isinstance(flag_value, bool): + return types.BoolParamType() + + guessed: types.ParamType[t.Any] = types.convert_type(None, flag_value) + if ( + isinstance(guessed, types.StringParamType) + and not isinstance(flag_value, str) + and flag_value is not None + ): + # The flag_value type couldn't be auto-detected (not str, int, float, or + # bool). Since flag_value is a programmer-provided Python object, not CLI + # input, pass it through unchanged instead of stringifying it. + return types.UNPROCESSED + return guessed + + def _resolve_lazy_default(self, value: t.Any) -> t.Any: + """Apply lazy auto-derivations to a default-style value. + + Shared between :meth:`get_default` (the runtime path) and :meth:`to_info_dict` + (the introspection path) so the two views cannot drift apart. Callables are + *not* invoked here: that is :meth:`get_default`'s responsibility. Rules: + + * ``UNSET`` resolves to ``False`` for a non-required boolean flag, and to ``()`` + for a non-required, non-prompted multi flag. + * ``True`` resolves to :attr:`flag_value` for a non-boolean flag (the "activate + this flag by default" shorthand). Boolean flags keep ``True`` as a literal. + """ + if value is UNSET and self.is_flag: + if self.multiple and not self.required and not self.prompt: + return () + if self.is_bool_flag and not self.required: + return False + if value is True and self.is_flag and not self.is_bool_flag: + # Use ``flag_activation_value`` so an unset ``flag_value`` resolves to + # ``True`` (the bool-flag activation value) rather than leaking the + # :data:`UNSET` sentinel. + return self.flag_activation_value + return value + + def _validate(self, prompt: bool | str, deprecated: bool | str) -> None: + """Raise :class:`TypeError` / :class:`ValueError` on illegal kwarg combinations. + + Called once, after every other attribute has been assigned, so each check can + read the final state. + """ + if not __debug__: + return + if deprecated and prompt: + raise ValueError("`deprecated` options cannot use `prompt`.") + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError("'prompt' with 'hide_input' is not valid for boolean flag.") + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> dict[str, t.Any]: + """ + .. versionchanged:: 8.5.0 + ``default`` and ``flag_value`` reflect the auto-derived values (``False`` + for unset boolean-flag defaults, ``True`` for unset boolean-flag activation + values, etc.) when no explicit value was passed, matching what the function + would receive at call time. + + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`flag_value` if it was not set. + """ + info_dict = super().to_info_dict() + info_dict.update( + default=self._hide_unset(self._resolve_lazy_default(self.default)), + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_activation_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """Return the default value for this option. + + Several auto-derived defaults are resolved lazily here rather than eagerly in + :meth:`__init__`. This keeps :attr:`default` raw at construction time, so + ``self.default is UNSET`` reliably answers "did the user pass a default?" for + feature-switch-group arbitration and for anything else that needs to distinguish + "absent" from a chosen value. The resolution rules live in + :meth:`_resolve_lazy_default`. + + .. versionchanged:: 8.5.0 + ``UNSET`` defaults for boolean and multi flags are now resolved here instead + of being coerced in :meth:`__init__`. Reading :attr:`default` directly + returns the user-supplied value (or ``UNSET`` if none was passed) rather + than the auto-derived one. + + .. versionchanged:: 8.3.3 + ``default=True`` is no longer substituted with ``flag_value`` for boolean + flags, fixing negative boolean flags like + ``flag_value=False, default=True``. + """ + raw = super().get_default(ctx, call=False) + value = self._resolve_lazy_default(raw) + # Only invoke the value as a callable when the lazy resolver passed it through + # unchanged. If the resolver substituted ``True`` -> :attr:`flag_value`, the + # result is the programmer-supplied ``flag_value`` (often a class or enum), + # which must NOT be instantiated here. See + # https://github.com/pallets/click/issues/3121. + if value is raw and call and callable(value): + value = value() + return value + + def get_error_hint(self, ctx: Context | None) -> str: + result = super().get_error_hint(ctx) + if self.show_envvar and self.envvar is not None: + result += f" (env var: '{self.envvar}')" + return result + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str, list[str], list[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(_("Name '{name}' defined twice").format(name=name)) + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(_split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + _( + "Boolean option {decl!r} cannot use the" + " same flag for true/false." + ).format(decl=decl) + ) + else: + possible_names.append(_split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return "", opts, secondary_opts + raise TypeError( + _( + "Could not determine name for option with declarations {decls!r}" + ).format(decls=decls) + ) + + if not opts and not secondary_opts: + raise TypeError( + _( + "No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + " you mean to pass '--{name}'?" + ).format(name=name) + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + # ``flag_activation_value`` resolves UNSET to the right parser-store + # constant (``True`` for bool flags) so the raw :attr:`flag_value` + # can keep the sentinel. + const=self.flag_activation_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: cabc.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar(ctx=ctx)}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + + extra = self.get_help_extra(ctx) + extra_items = [] + if "envvars" in extra: + extra_items.append( + _("env var: {var}").format(var=", ".join(extra["envvars"])) + ) + if "default" in extra: + extra_items.append(_("default: {default}").format(default=extra["default"])) + if "range" in extra: + extra_items.append(extra["range"]) + if "required" in extra: + extra_items.append(_(extra["required"])) + + if extra_items: + extra_str = "; ".join(extra_items) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra: + extra: types.OptionHelpExtra = {} + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + if isinstance(envvar, str): + extra["envvars"] = (envvar,) + else: + extra["envvars"] = tuple(str(d) for d in envvar) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or ( + show_default and (default_value not in (None, UNSET)) + ): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif isinstance(default_value, enum.Enum): + default_string = default_value.name + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = _split_opt( + (self.opts if default_value else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + elif isinstance(default_value, str) and default_value == "": + default_string = '""' + else: + default_string = str(default_value) + + if default_string: + extra["default"] = default_string + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra["range"] = range_str + + if self.required: + extra["required"] = "required" + + return extra + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to lock in the value before + # attempting any user interaction. + default = self.get_default(ctx) + + # A boolean flag can use a simplified [y/n] confirmation prompt. + if self.is_bool_flag: + # If we have no boolean default, we force the user to explicitly provide + # one. + if default in (UNSET, None): + default = None + # Nothing prevent you to declare an option that is simultaneously: + # 1) auto-detected as a boolean flag, + # 2) allowed to prompt, and + # 3) still declare a non-boolean default. + # This forced casting into a boolean is necessary to align any non-boolean + # default to the prompt, which is going to be a [y/n]-style confirmation + # because the option is still a boolean flag. That way, instead of [y/n], + # we get [Y/n] or [y/N] depending on the truthy value of the default. + # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249 + else: + default = bool(default) + return confirm(self.prompt, default) + + # If show_default is given, provide this to `prompt` as well, + # otherwise we use `prompt`'s default behavior + prompt_kwargs: t.Any = {} + if self.show_default is not None: + prompt_kwargs["show_default"] = self.show_default + + return prompt( + self.prompt, + # Use ``None`` to inform the prompt() function to reiterate until a valid + # value is provided by the user if we have no default. + default=self._hide_unset(default), + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + **prompt_kwargs, + ) + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """:class:`Option` resolves its environment variable the same way as + :func:`Parameter.resolve_envvar_value`, but it also supports + :attr:`Context.auto_envvar_prefix`. If we could not find an environment from + the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix` + to build dynamiccaly the environment variable name using the + :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template. + + :meta private: + """ + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None and self.name: + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Any: + """For :class:`Option`, this method processes the raw environment variable + string the same way as :func:`Parameter.value_from_envvar` does. + + But in the case of non-boolean flags, the value is analyzed to determine if the + flag is activated or not, and returns a boolean of its activation, or the + :attr:`flag_value` if the latter is set. + + This method also takes care of repeated options (i.e. options with + :attr:`multiple` set to ``True``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + # Absent environment variable or an empty string is interpreted as unset. + if rv is None: + return None + + # Non-boolean flags are more liberal in what they accept. But a flag being a + # flag, its envvar value still needs to be analyzed to determine if the flag is + # activated or not. + if self.is_flag and not self.is_bool_flag: + # An exact match against ``flag_value`` (a non-bool flag always has one + # explicitly set) returns it directly. Otherwise the value is analyzed as a + # boolean: a truthy reading activates the flag and the function receives + # ``flag_value``; a falsy reading produces ``False``; an unrecognized + # reading falls through as ``None``. Folding the substitution here means + # :meth:`consume_value` no longer has to repeat it in a source-checked + # branch. + if rv == self.flag_value: + return self.flag_value + parsed = types.BoolParamType.str_to_bool(rv) + if parsed is None: + return None + return self.flag_value if parsed else False + + # Split the envvar value if it is allowed to be repeated. + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0: + multi_rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment] + + return multi_rv + + return rv + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, Parameter] + ) -> tuple[t.Any, ParameterSource]: + """For :class:`Option`, the value can be collected from an interactive prompt + if the option is a flag that needs a value (and the :attr:`prompt` property is + set). + + Additionally, this method handles flag option that are activated without a + value, in which case the :attr:`flag_value` is returned. + + :meta private: + """ + value, source = super().consume_value(ctx, opts) + + # The parser emits a sentinel when a flag is allowed to be used without a value. + # Resolve it to a prompt or to the activation value depending on the option's + # configuration. + if value is FLAG_NEEDS_VALUE: + # If the option allows for a prompt, start an interaction with the user. + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + # Else the flag takes its activation value (resolves UNSET). + else: + value = self.flag_activation_value + source = ParameterSource.COMMANDLINE + + # Re-interpret a multiple option that the parser sent through as a list still + # containing the FLAG_NEEDS_VALUE sentinel, replacing each occurrence with the + # activation value. + elif ( + self.multiple + and value is not UNSET + and isinstance(value, cabc.Iterable) + and source < ParameterSource.DEFAULT_MAP + and any(v is FLAG_NEEDS_VALUE for v in value) + ): + value = [ + self.flag_activation_value if v is FLAG_NEEDS_VALUE else v + for v in value + ] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt for one to the user + # if prompting is enabled. + elif ( + (value is UNSET or source >= ParameterSource.DEFAULT_MAP) + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + # process_value has to be overridden on Options in order to capture + # `value == UNSET` cases before `type_cast_value()` gets called. + # + # Refs: + # https://github.com/pallets/click/issues/3069 + if self.is_flag and not self.required and self.is_bool_flag and value is UNSET: + value = False + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + # in the normal case, rely on Parameter.process_value + return super().process_value(ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the constructor of :class:`Parameter`. + + :param help: the help string. + + .. versionchanged:: 8.5.0 + Added the ``help`` parameter. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: cabc.Sequence[str], + required: bool | None = None, + help: str | None = None, + **attrs: t.Any, + ) -> None: + # Auto-detect the requirement status of the argument if not explicitly set. + if required is None: + # The argument gets automatically required if it has no explicit default + # value set and is setup to match at least one value. + if attrs.get("default", UNSET) is UNSET: + required = attrs.get("nargs", 1) > 0 + # If the argument has a default value, it is not required. + else: + required = False + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + deprecated = attrs.get("deprecated", False) + + if help: + help = inspect.cleandoc(help) + + if deprecated: + label = _format_deprecated_label(deprecated) + help = f"{help} {label}" if help else label + + self.help = help + + super().__init__(param_decls, required=required, **attrs) + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(help=self.help) + return info_dict + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(param=self, ctx=ctx) + if not var: + var = self.name.upper() + # Types like ``Choice`` and ``DateTime`` already surround their metavar + # with square brackets to enumerate the allowed values. Reuse those + # outer brackets as the optional-argument indicator instead of wrapping + # the metavar in a second pair, which would produce ``[[a|b|c]]``. + already_bracketed = var.startswith("[") and var.endswith("]") + if self.deprecated: + var += "!" + if not self.required and not already_bracketed: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str, list[str], list[str]]: + if not decls: + if not expose_value: + return "", [], [] + raise TypeError("Argument is marked as exposed, but does not have a name.") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + _( + "Arguments take exactly one parameter declaration, got" + " {length}: {decls}." + ).format(length=len(decls), decls=decls) + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [self.make_metavar(ctx)] + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + if self.help is None: + return None + + return self.make_metavar(ctx), self.help + + def get_error_hint(self, ctx: Context | None) -> str: + if ctx is not None: + return f"'{self.make_metavar(ctx)}'" + return f"'{self.human_readable_name}'" + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + raise AttributeError(name) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py new file mode 100644 index 0000000..cc42e5d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/decorators.py @@ -0,0 +1,627 @@ +from __future__ import annotations + +import inspect +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") +T = t.TypeVar("T") +_AnyCallable = t.Callable[..., t.Any] +FC = t.TypeVar("FC", bound="_AnyCallable | Command") + + +def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator( + object_type: type[T], ensure: bool = False +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + + obj: T | None + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: str | None = None +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@t.overload +def command(name: _AnyCallable) -> Command: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@t.overload +def command( + name: str | None, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@t.overload +def command( + name: None = None, + *, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def command( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Command]: ... + + +def command( + name: str | _AnyCallable | None = None, + cls: type[CmdType] | None = None, + **attrs: t.Any, +) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function, converted to + lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes + ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, + ``init_data_command`` becomes ``init-data``. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: The name of the command. Defaults to modifying the function's + name as described above. + :param cls: The command class to create. Defaults to :class:`Command`. + + .. versionchanged:: 8.2 + The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are + removed when generating the name. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Callable[[_AnyCallable], t.Any] | None = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = t.cast("type[CmdType]", Command) + + def decorator(f: _AnyCallable) -> CmdType: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + if t.TYPE_CHECKING: + assert cls is not None + assert not callable(name) + + if name is not None: + cmd_name = name + else: + cmd_name = f.__name__.lower().replace("_", "-") + cmd_left, sep, suffix = cmd_name.rpartition("-") + + if sep and suffix in {"command", "cmd", "group", "grp"}: + cmd_name = cmd_left + + cmd = cls(name=cmd_name, callback=f, params=params, **attrs) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +GrpType = t.TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@t.overload +def group(name: _AnyCallable) -> Group: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@t.overload +def group( + name: str | None, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@t.overload +def group( + name: None = None, + *, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def group( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Group]: ... + + +def group( + name: str | _AnyCallable | None = None, + cls: type[GrpType] | None = None, + **attrs: t.Any, +) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if cls is None: + cls = t.cast("type[GrpType]", Group) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument( + *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default argument class, refer to :class:`Argument` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Argument + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def option( + *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default option class, refer to :class:`Option` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Option + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", _("Do you want to continue?")) + kwargs.setdefault("help", _("Confirm the action without prompting.")) + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: str | None = None, + *param_decls: str, + package_name: str | None = None, + prog_name: str | None = None, + message: str | None = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. If the detected (or given) name does + not match an installed distribution, Click resolves it as an import + (top-level module) name via + :func:`importlib.metadata.packages_distributions`, so e.g. ``PIL`` + resolves to the ``Pillow`` distribution. + + .. note:: + The parameters and message variables accepted by this option are + frozen: no new slots will be added, to keep the common case simple + and predictable. If you need values it does not expose, such as a + file path, the Python version, or git metadata, use + :func:`custom_version_option` to render the output yourself. + + Rationale: `discussion #3527 + `_. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + + .. versionchanged:: 8.4.2 + When ``package_name`` does not match an installed distribution, + Click now resolves it as an import (top-level module). + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + nonlocal package_name + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + import importlib.metadata + + try: + version = importlib.metadata.version(package_name) + except importlib.metadata.PackageNotFoundError: + # The given name didn't match an installed distribution. + # Try resolving it as an import (top-level module) name, + # e.g. ``PIL`` is provided by the ``Pillow`` distribution. + distributions = importlib.metadata.packages_distributions().get( + package_name, [] + ) + if len(distributions) == 1: + package_name = distributions[0] + version = importlib.metadata.version(package_name) + elif len(distributions) > 1: + raise RuntimeError( + f"{package_name!r} maps to multiple installed" + f" distributions ({', '.join(distributions)})." + " Pass 'package_name' to disambiguate." + ) from None + else: + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + message % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def custom_version_option( + callback: t.Callable[[Context], str], + *param_decls: str, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option whose output is produced by ``callback``. + + This is the customizable companion to :func:`version_option`. Where + :func:`version_option` is intentionally limited to a fixed message and + a small set of values, this option calls ``callback`` to build the + whole string to print. Use it when you need values that + :func:`version_option` does not expose, such as a file path, the + Python version, or git metadata. + + :param callback: Called with the current :class:`Context` when the + option is invoked. Its return value is printed, then the program + exits. + :param param_decls: One or more option names. Defaults to the single + value ``--version``. + :param kwargs: Extra arguments are passed to :func:`option`. + + .. versionadded:: 8.5.0 + """ + + def show_version(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + echo(callback(ctx), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = show_version + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Pre-configured ``--help`` option which immediately prints the help page + and exits the program. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", show_help) + + return option(*param_decls, **kwargs) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py new file mode 100644 index 0000000..6272c38 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/exceptions.py @@ -0,0 +1,378 @@ +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .globals import resolve_color_default +from .utils import echo +from .utils import format_filename + +if t.TYPE_CHECKING: + from .core import Command + from .core import Context + from .core import Parameter + + +def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +def _format_possibilities(possibilities: list[str]) -> str: + possibility_str = ", ".join(repr(p) for p in sorted(possibilities)) + return ngettext( + "Did you mean {possibility}?", + "(Did you mean one of: {possibilities}?)", + len(possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code: t.ClassVar[int] = 1 + + show_color: t.Final[bool | None] + message: t.Final[str] + + def __init__(self, message: str) -> None: + super().__init__(message) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color = resolve_color_default() + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code: t.ClassVar[int] = 2 + + ctx: Context | None + cmd: t.Final[Command | None] + + def __init__(self, message: str, ctx: Context | None = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd = self.ctx.command if self.ctx else None + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + help_names = self.ctx.command.get_help_option_names(self.ctx) + # Pick the longest name (like ``--help`` over ``-h``) for + # readability in error messages. + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, + option=max(help_names, key=len), + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + param: Parameter | None + param_hint: cabc.Sequence[str] | str | None + + def __init__( + self, + message: str, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + param_type: t.Final[str | None] + + def __init__( + self, + message: str | None = None, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + param_type: str | None = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: cabc.Sequence[str] | str | None = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message( + param=self.param, ctx=self.ctx + ) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if Click attempted to handle an option that does not exist. + + .. versionadded:: 4.0 + """ + + option_name: t.Final[str] + possibilities: t.Final[list[str] | None] + + def __init__( + self, + option_name: str, + message: str | None = None, + possibilities: cabc.Iterable[str] | None = None, + ctx: Context | None = None, + ) -> None: + if message is None: + message = _("No such option {name!r}.").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + + if possibilities: + from difflib import get_close_matches + + possibilities_ = get_close_matches(option_name, possibilities) + else: + possibilities_ = None + self.possibilities = possibilities_ + + def format_message(self) -> str: + if not self.possibilities: + return self.message + return f"{self.message} {_format_possibilities(self.possibilities)}" + + +class NoSuchCommand(UsageError): + """Raised if Click attempted to handle a command that does not exist. + + .. versionadded:: 8.4.0 + """ + + command_name: t.Final[str] + possibilities: t.Final[list[str] | None] + + def __init__( + self, + command_name: str, + message: str | None = None, + possibilities: cabc.Iterable[str] | None = None, + ctx: Context | None = None, + ) -> None: + if message is None: + message = _("No such command {name!r}.").format(name=command_name) + + super().__init__(message, ctx) + self.command_name = command_name + + if possibilities: + from difflib import get_close_matches + + possibilities_ = get_close_matches(command_name, possibilities) + else: + possibilities_ = None + self.possibilities = possibilities_ + + def format_message(self) -> str: + if not self.possibilities: + return self.message + return f"{self.message} {_format_possibilities(self.possibilities)}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + option_name: t.Final[str] + + def __init__( + self, option_name: str, message: str, ctx: Context | None = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class NoArgsIsHelpError(UsageError): + ctx: Context + + def __init__(self, ctx: Context) -> None: + super().__init__(ctx.get_help(), ctx=ctx) + + def show(self, file: t.IO[t.Any] | None = None) -> None: + echo(self.format_message(), file=file, err=True, color=self.ctx.color) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + ui_filename: t.Final[str] + filename: t.Final[str] + + def __init__(self, filename: str, hint: str | None = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename = format_filename(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + exit_code: t.Final[int] + + def __init__(self, code: int = 0) -> None: + self.exit_code = code diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py new file mode 100644 index 0000000..c4aa2de --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/formatting.py @@ -0,0 +1,320 @@ +from __future__ import annotations + +import collections.abc as cabc +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import _split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: int | None = None + + +def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: + widths: dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: cabc.Iterable[tuple[str, str]], col_count: int +) -> cabc.Iterator[tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + + .. versionchanged:: 8.4.0 + Width is measured in visible characters. ANSI escape sequences in + ``text``, ``initial_indent``, or ``subsequent_indent`` no longer + count toward the width budget, so styled input wraps based on what + the user sees instead of raw byte length. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: list[tuple[int, bool, str]] = [] + buf: list[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + indent_increment: int + width: int + current_indent: int + buffer: list[str] + + def __init__( + self, + indent_increment: int = 2, + width: int | None = None, + max_width: int | None = None, + ) -> None: + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + import shutil + + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = "{usage} ".format(usage=_("Usage:")) + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if not args: + # Without args, the prefix's trailing space and the wrap_text + # call that would normally place args on the line are both + # unnecessary. Emit just the prefix line. + self.write(usage_prefix.rstrip(" ")) + self.write("\n") + return + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: cabc.Iterable[tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> cabc.Generator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> cabc.Generator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: cabc.Iterable[str]) -> tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = _split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py new file mode 100644 index 0000000..a2f9172 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/globals.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import typing as t +from threading import local + +if t.TYPE_CHECKING: + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: t.Literal[False] = False) -> Context: ... + + +@t.overload +def get_current_context(silent: bool = ...) -> Context | None: ... + + +def get_current_context(silent: bool = False) -> Context | None: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: Context) -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: bool | None = None) -> bool | None: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py new file mode 100644 index 0000000..4fcbf7c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/parser.py @@ -0,0 +1,533 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" + +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + from ._utils import T_FLAG_NEEDS_VALUE + from ._utils import T_UNSET + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + + +def _unpack_args( + args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] +) -> tuple[cabc.Sequence[str | cabc.Sequence[str | T_UNSET] | T_UNSET], list[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with ``UNSET``. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] + spos: int | None = None + + def _fetch(c: deque[str]) -> str | T_UNSET: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return UNSET + + while nargs_spec: + if spos is None: + nargs = nargs_spec.popleft() + else: + nargs = nargs_spec.pop() + + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x: list[str | T_UNSET] = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(UNSET) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def _split_opt(opt: str) -> tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def _normalize_opt(opt: str, ctx: Context | None) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = _split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +class _Option: + def __init__( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes: set[str] = set() + + for opt in opts: + prefix, value = _split_opt(opt) + if not prefix: + raise ValueError( + _("Invalid start character for option ({option})").format( + option=opt + ) + ) + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: t.Any, state: _ParsingState) -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class _Argument: + def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: str | cabc.Sequence[str | T_UNSET] | T_UNSET, + state: _ParsingState, + ) -> None: + if self.nargs > 1: + assert isinstance(value, cabc.Sequence) + holes = sum(x is UNSET for x in value) + if holes == len(value): + value = UNSET + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + # We failed to collect any argument value so we consider the argument as unset. + if value == (): + value = UNSET + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class _ParsingState: + def __init__(self, rargs: list[str]) -> None: + self.opts: dict[str, t.Any] = {} + self.largs: list[str] = [] + self.rargs = rargs + self.order: list[CoreParameter] = [] + + +class _OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + + .. deprecated:: 8.2 + Will be removed in Click 9.0. + """ + + def __init__(self, ctx: Context | None = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args: bool = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options: bool = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: dict[str, _Option] = {} + self._long_opt: dict[str, _Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: list[_Argument] = [] + + def add_option( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [_normalize_opt(opt, self.ctx) for opt in opts] + option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(_Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: list[str] + ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = _ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: _ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: _ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: str | None, state: _ParsingState + ) -> None: + if opt not in self._long_opt: + raise NoSuchOption(opt, possibilities=self._long_opt, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = UNSET + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: _ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = _normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = UNSET + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we recombine the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new large. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: _Option, state: _ParsingState + ) -> str | cabc.Sequence[str] | T_UNSET | T_FLAG_NEEDS_VALUE: + nargs = option.nargs + + value: str | cabc.Sequence[str] | T_UNSET | T_FLAG_NEEDS_VALUE + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = FLAG_NEEDS_VALUE + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = FLAG_NEEDS_VALUE + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: _ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = _normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) + + +def __getattr__(name: str) -> object: + import warnings + + if name in { + "OptionParser", + "Argument", + "Option", + "split_opt", + "normalize_opt", + "ParsingState", + }: + warnings.warn( + f"'parser.{name}' is deprecated and will be removed in Click 9.0." + " The old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[f"_{name}"] + + if name == "split_arg_string": + from .shell_completion import split_arg_string + + warnings.warn( + "Importing 'parser.split_arg_string' is deprecated, it will only be" + " available in 'shell_completion' in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return split_arg_string + + raise AttributeError(name) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py new file mode 100644 index 0000000..8a2668b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/shell_completion.py @@ -0,0 +1,801 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .utils import echo + + +def shell_complete( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> t.Literal[0, 1]: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + # Write bytes, otherwise Windows text stdout translates LF to CRLF and breaks. + if instruction == "source": + echo(comp.source().encode(), nl=False) + return 0 + + if instruction == "complete": + echo(comp.complete().encode()) + return 0 + + return 1 + + +if t.TYPE_CHECKING: + from typing_extensions import TypeVar + + # `Any` is used as default for backwards compatibility (instead of e.g. `str`) + _ValueT_co = TypeVar("_ValueT_co", covariant=True, default=t.Any) +else: + _ValueT_co = t.TypeVar("_ValueT_co", covariant=True) + + +class CompletionItem(t.Generic[_ValueT_co]): + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: _ValueT_co, + type: str = "plain", + help: str | None = None, + **kwargs: t.Any, + ) -> None: + self.value: _ValueT_co = value + self.type: str = type + self.help: str | None = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +# See ZshComplete.format_completion below, and issue #2703, before +# changing this script. +# +# (TL;DR: _describe is picky about the format, but this Zsh script snippet +# is already widely deployed. So freeze this script, and use clever-ish +# handling of colons in ZshComplet.format_completion.) +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + %(complete_func)s "$@" +else + # eval/source/. command, register function for later + compdef %(complete_func)s %(prog_name)s +fi +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + +# Compatible with Windows PowerShell 5.1+ and PowerShell (pwsh) 7+. +# Uses Register-ArgumentCompleter -Native, which receives the command AST +# as parsed by PowerShell. The command text is forwarded verbatim through +# COMP_WORDS so the Python side can reuse the same shlex-based splitting +# used by the bash/zsh completers. +_SOURCE_POWERSHELL = """\ +Register-ArgumentCompleter -Native -CommandName %(prog_name)s -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $prev_complete = $env:%(complete_var)s + $prev_words = $env:COMP_WORDS + $prev_cword = $env:COMP_CWORD + + $env:%(complete_var)s = 'powershell_complete' + $env:COMP_WORDS = $commandAst.ToString() + if ($wordToComplete) { + $env:COMP_CWORD = $commandAst.CommandElements.Count - 1 + } else { + $env:COMP_CWORD = $commandAst.CommandElements.Count + } + + try { + $response = & %(prog_name)s 2>$null + } finally { + $env:%(complete_var)s = $prev_complete + $env:COMP_WORDS = $prev_words + $env:COMP_CWORD = $prev_cword + } + + if (-not $response) { return } + + $prefix = "$wordToComplete*" + $lines = $response -split "`n" + for ($i = 0; $i + 2 -lt $lines.Count; $i += 3) { + $type = $lines[$i] + $value = $lines[$i + 1] + $descr = $lines[$i + 2] + if (-not $type) { continue } + + if ($type -eq 'plain') { + $tip = if ($descr -and $descr -ne '_') { $descr } else { $value } + [System.Management.Automation.CompletionResult]::new( + $value, $value, 'ParameterValue', $tip) + } elseif ($type -eq 'dir') { + Get-ChildItem -Directory -Path $prefix -ErrorAction SilentlyContinue | + ForEach-Object { + [System.Management.Automation.CompletionResult]::new( + $_.FullName, $_.Name, 'ProviderContainer', $_.FullName) + } + } elseif ($type -eq 'file') { + Get-ChildItem -Path $prefix -ErrorAction SilentlyContinue | + ForEach-Object { + $kind = 'ProviderItem' + if ($_.PSIsContainer) { $kind = 'ProviderContainer' } + [System.Management.Automation.CompletionResult]::new( + $_.FullName, $_.Name, $kind, $_.FullName) + } + } + } +} +""" + + +class _SourceVarsDict(t.TypedDict): + complete_func: str + complete_var: str + prog_name: str + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + cli: Command + ctx_args: cabc.MutableMapping[str, t.Any] + prog_name: str + complete_var: str + + def __init__( + self, + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> _SourceVarsDict: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> tuple[list[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: list[str], incomplete: str + ) -> list[CompletionItem[str]]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem[str]) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name: t.ClassVar[str] = "bash" + source_template: t.ClassVar[str] = _SOURCE_BASH + + @staticmethod + def _check_version() -> None: + import shutil + import subprocess + + bash_exe = shutil.which("bash") + + if bash_exe is None: + match = None + else: + output = subprocess.run( + [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'], + stdout=subprocess.PIPE, + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + echo( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ), + err=True, + ) + else: + echo( + _("Couldn't detect Bash version, shell completion is not supported."), + err=True, + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem[t.Any]) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name: t.ClassVar[str] = "zsh" + source_template: t.ClassVar[str] = _SOURCE_ZSH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem[str]) -> str: + help_ = item.help or "_" + # The zsh completion script uses `_describe` on items with help + # texts (which splits the item help from the item value at the + # first unescaped colon) and `compadd` on items without help + # text (which uses the item value as-is and does not support + # colon escaping). So escape colons in the item value if and + # only if the item help is not the sentinel "_" value, as used + # by the completion script. + # + # (The zsh completion script is potentially widely deployed, and + # thus harder to fix than this method.) + # + # See issue #1812 and issue #2703 for further context. + value = item.value.replace(":", r"\:") if help_ != "_" else item.value + return f"{item.type}\n{value}\n{help_}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name: t.ClassVar[str] = "fish" + source_template: t.ClassVar[str] = _SOURCE_FISH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + if incomplete: + incomplete = split_arg_string(incomplete)[0] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem[str]) -> str: + """ + .. versionchanged:: 8.4.2 + Escape newlines and replace tabs with spaces in the help text to + fix completion errors with multi-line help strings. + """ + # According to https://fishshell.com/docs/current/cmds/complete.html + # Command substitutions found in ARGUMENTS should return a newline- + # separated list of arguments, and each argument may optionally have a tab + # character followed by the argument description. + if item.help: + help_ = item.help.replace("\n", "\\n").replace("\t", " ") + return f"{item.type},{item.value}\t{help_}" + + return f"{item.type},{item.value}" + + +class PowerShellComplete(ShellComplete): + """Shell completion for PowerShell (Windows PowerShell 5.1+ and pwsh 7+). + + .. versionadded:: 8.5.0 + """ + + name: t.ClassVar[str] = "powershell" + source_template: t.ClassVar[str] = _SOURCE_POWERSHELL + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem[str]) -> str: + # PowerShell parses the response by splitting on newlines and + # consuming three lines per completion (type, value, help). + # Multi-line help text would break that framing, so collapse + # newlines and carriage returns to spaces. The literal "_" + # sentinel matches what ZshComplete uses for "no help text". + if item.help: + help_ = item.help.replace("\r", " ").replace("\n", " ") + else: + help_ = "_" + return f"{item.type}\n{item.value}\n{help_}" + + +_available_shells: t.Final[dict[str, type[ShellComplete]]] = { + "bash": BashComplete, + "fish": FishComplete, + "powershell": PowerShellComplete, + "zsh": ZshComplete, +} + +_ShellCompleteT = t.TypeVar("_ShellCompleteT", bound="ShellComplete") + + +def add_completion_class( + cls: type[_ShellCompleteT], name: str | None = None +) -> type[_ShellCompleteT]: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + return cls + + +@t.overload +def get_completion_class(shell: t.Literal["bash"]) -> type[BashComplete]: ... +@t.overload +def get_completion_class(shell: t.Literal["fish"]) -> type[FishComplete]: ... +@t.overload +def get_completion_class(shell: t.Literal["zsh"]) -> type[ZshComplete]: ... +@t.overload +def get_completion_class(shell: str) -> type[ShellComplete] | None: ... +def get_completion_class(shell: str) -> type[ShellComplete] | None: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def split_arg_string(string: str) -> list[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + + .. versionchanged:: 8.2 + Moved to ``shell_completion`` from ``parser``. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out: list[str] = [] + + try: + out.extend(lex) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + value = ctx.params.get(param.name) + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + break + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + args: list[str], +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: + args = ctx._protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, Group): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, args, parent=ctx, resilient_parsing=True + ) as sub_ctx: + ctx = sub_ctx + args = ctx._protected_args + ctx.args + else: + sub_ctx = ctx + + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) as sub_sub_ctx: + sub_ctx = sub_sub_ctx + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx._protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: list[str], incomplete: str +) -> tuple[Command | Parameter, str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py new file mode 100644 index 0000000..e1315f2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/termui.py @@ -0,0 +1,1014 @@ +from __future__ import annotations + +import collections.abc as cabc +import inspect +import io +import itertools +import os +import re +import sys +import typing as t +from contextlib import AbstractContextManager +from contextlib import redirect_stdout +from gettext import gettext as _ + +from . import _compat +from ._compat import isatty +from ._compat import strip_ansi +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import _LazyFile +from .utils import echo + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +_HIDDEN_INPUT_MASK = "'***'" + + +def _mask_hidden_input(message: str, value: str) -> str: + """Replace occurrences of ``value`` in ``message`` with a fixed mask. + + Both ``repr(value)`` (the form built-in :class:`ParamType` errors use + via ``{value!r}``) and the raw value are masked. The raw-value pass + uses word-boundary lookarounds so a substring like ``"1"`` does not + match inside ``"10"``, and ``"ent"`` does not match inside + ``"Authentication"``. The empty string is skipped to avoid matching + at every boundary. + """ + message = message.replace(repr(value), _HIDDEN_INPUT_MASK) + if value: + message = re.sub( + rf"(? str: + import getpass + + return getpass.getpass(prompt) + + +def _readline_prompt(func: t.Callable[[str], str], text: str, err: bool) -> str: + """Call a prompt function, passing the full prompt so readline can + handle line editing and cursor positioning correctly. + + The prompt is handed to *func* (such as :func:`input`) rather than + written through :func:`echo`, so it has to strip ANSI color and style + codes itself when the destination stream does not support them. Without + this the prompt would keep codes that :func:`echo` removes from the + rest of the output. + """ + stream = sys.stderr if err else sys.stdout + + # Look up ``should_strip_ansi`` on the module so that ``CliRunner``, + # which patches it there during test isolation, is honored. + if _compat.should_strip_ansi(stream, resolve_color_default()): + text = strip_ansi(text) + + if err: + with redirect_stdout(sys.stderr): + return func(text) + return func(text) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool | str = False, + default: object | None = None, + show_choices: bool = True, + type: object | None = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + default_preview = "" + if show_default: + if isinstance(show_default, str): + default_preview = f" [({show_default})]" + elif default is not None: + default_preview = f" [{_format_default(default)}]" + return f"{prompt}{default_preview}{suffix}" + + +def _format_default(default: V) -> V | str: + if isinstance(default, (io.IOBase, _LazyFile)): + name = getattr(default, "name", None) + + if name is not None: + return str(name) + + return default + + +@t.overload +def prompt( + text: str, + default: str | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: None = None, + value_proc: None = None, + prompt_suffix: str = ": ", + show_default: bool | str = True, + err: bool = False, + show_choices: bool = True, +) -> str: ... + + +@t.overload +def prompt( + text: str, + default: V | str | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: ParamType[V, str] | type[V] | None = None, + value_proc: t.Callable[[str], V] | None = None, + prompt_suffix: str = ": ", + show_default: bool | str = True, + err: bool = False, + show_choices: bool = True, +) -> V: ... + + +def prompt( + text: str, + default: V | str | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: ParamType[V, str] | type[V] | None = None, + value_proc: t.Callable[[str], V] | None = None, + prompt_suffix: str = ": ", + show_default: bool | str = True, + err: bool = False, + show_choices: bool = True, +) -> V: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + If this value is a string, it shows that string + in parentheses instead of the actual value. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionchanged:: 8.5.0 + Generically typed: the return type is narrowed by ``type``, + ``value_proc``, or ``default`` instead of being ``Any``. Runtime + behavior is unchanged. + + .. versionchanged:: 8.3.3 + ``show_default`` can be a string to show a custom value instead + of the actual default, matching the help text behavior. + + .. versionchanged:: 8.3.1 + A space is no longer appended to the prompt. + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + return _readline_prompt(f, text, err) + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + # Defaults of any type are accepted and round trip through + # value_proc like typed input, so the annotation is only + # accurate for typed input. + value = t.cast("str", default) + break + try: + result = value_proc(value) + except UsageError as e: + message = _mask_hidden_input(e.message, value) if hide_input else e.message + echo(_("Error: {message}").format(message=message), err=err) + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: bool | None = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.3.1 + A space is no longer appended to the prompt. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + value = _readline_prompt(visible_prompt_func, prompt, err).lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def get_pager_file( + color: bool | None = None, +) -> t.ContextManager[t.TextIO]: + """Context manager. + + Yields a writable file-like object which can be used as an output pager. + + .. versionadded:: 8.4.0 + + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + from ._termui_impl import get_pager_file + + color = resolve_color_default(color) + + return get_pager_file(color=color) + + +def echo_via_pager( + text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str, + color: bool | None = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast("cabc.Iterable[str]", text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + with get_pager_file(color=color) as pager: + for text in itertools.chain(text_generator, "\n"): + pager.write(text) + # Flush after each write so a slow generator streams to the pager + # incrementally rather than staying invisible until the pipe buffer + # fills (~8 KB). + pager.flush() + + +@t.overload +def progressbar( + *, + length: int, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[int]: ... + + +@t.overload +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: ... + + +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param hidden: hide the progressbar. Defaults to ``False``. When no tty is + detected, it will only print the progressbar label. Setting this to + ``False`` also disables that. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionadded:: 8.2 + The ``hidden`` argument. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + The ``update_min_steps`` parameter. + + .. versionadded:: 4.0 + The ``color`` parameter and ``update`` method. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + hidden=hidden, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + + # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor + echo("\033[2J\033[1;1H", nl=False) + + +def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str: + """Interprets a color value and returns the corresponding ANSI code.""" + if isinstance(color, str) and color in _ansi_colors: + return str(_ansi_colors[color] + offset) + + # bool is an int subclass: without the exclusion, True and False would + # silently render as the palette indices 1 and 0. + elif isinstance(color, int) and not isinstance(color, bool): + if 0 <= color <= 255: + return f"{38 + offset};5;{color:d}" + + elif ( + isinstance(color, (tuple, list)) + and len(color) == 3 + and all( + isinstance(c, int) and not isinstance(c, bool) and 0 <= c <= 255 + for c in color + ) + ): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + raise ValueError(_("Unknown color {colour!r}").format(colour=color)) + + +def style( + text: t.Any, + fg: int | tuple[int, int, int] | str | None = None, + bg: int | tuple[int, int, int] | str | None = None, + bold: bool | None = None, + dim: bool | None = None, + underline: bool | None = None, + overline: bool | None = None, + italic: bool | None = None, + blink: bool | None = None, + reverse: bool | None = None, + strikethrough: bool | None = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.5.0 + All invalid color values raise :exc:`ValueError`. 256-color index + ``0`` is no longer ignored. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg is not None: + bits.append(f"\033[{_interpret_color(fg)}m") + + if bg is not None: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Any | None = None, + file: t.IO[t.AnyStr] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +@t.overload +def edit( + text: bytes | bytearray, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = False, + extension: str = ".txt", +) -> bytes | None: ... + + +@t.overload +def edit( + text: str, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", +) -> str | None: ... + + +@t.overload +def edit( + text: None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str + | os.PathLike[str] + | cabc.Iterable[str | os.PathLike[str]] + | None = None, +) -> None: ... + + +def edit( + text: str | bytes | bytearray | None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str + | os.PathLike[str] + | cabc.Iterable[str | os.PathLike[str]] + | None = None, +) -> str | bytes | bytearray | None: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. It accepts a path + or any iterable of paths. If the editor supports + editing multiple files at once, a sequence of files may + be passed as well. Invoke `click.file` once per file + instead if multiple files cannot be managed at once or + editing the files serially is desired. + + .. versionchanged:: 8.2.0 + ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str`` + if the ``editor`` supports editing multiple files at once. + + .. versionchanged:: 8.5.0 + ``filename`` accepts ``os.PathLike`` values in addition to strings. + + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + if isinstance(filename, (str, os.PathLike)): + filename = (filename,) + + ed.edit_files(filenames=filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Callable[[bool], str] | None = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> AbstractContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: str | None = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py new file mode 100644 index 0000000..cc123d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/testing.py @@ -0,0 +1,798 @@ +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import io +import os +import pdb +import shlex +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import _compat +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from _typeshed import ReadableBuffer + + from .core import Command + +if sys.platform == "win32": + CaptureMode: t.TypeAlias = t.Literal["sys"] # pyright: ignore[reportRedeclaration] +else: + CaptureMode: t.TypeAlias = t.Literal["sys", "fd"] # pyright: ignore[reportRedeclaration] +ExceptionInfo: t.TypeAlias = tuple[type[BaseException], BaseException, TracebackType] + + +class EchoingStdin: + _input: t.BinaryIO + _output: t.BinaryIO + _paused: bool + + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> list[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> cabc.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: EchoingStdin | None) -> cabc.Generator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _FDCapture: + """Redirect a file descriptor to a temporary file for capture. + + Saves the current target of *targetfd* via :func:`os.dup`, then + redirects it to a temporary file via :func:`os.dup2`. On + :meth:`stop`, restores the original ``fd`` and returns the captured + bytes. Inspired by Pytest's ``FDCapture``. + + .. versionadded:: 8.4.0 + """ + + _targetfd: int + saved_fd: int + _tmpfile: t.BinaryIO | None + + def __init__(self, targetfd: int) -> None: + self._targetfd = targetfd + self.saved_fd = -1 + self._tmpfile = None + + def start(self) -> None: + self.saved_fd = os.dup(self._targetfd) + self._tmpfile = tempfile.TemporaryFile(buffering=0) + os.dup2(self._tmpfile.fileno(), self._targetfd) + + def stop(self) -> bytes: + assert self._tmpfile is not None, "_FDCapture.start() was not called" + os.dup2(self.saved_fd, self._targetfd) + os.close(self.saved_fd) + self.saved_fd = -1 + self._tmpfile.seek(0) + data = self._tmpfile.read() + self._tmpfile.close() + self._tmpfile = None + return data + + +class BytesIOCopy(io.BytesIO): + """Patch ``io.BytesIO`` to let the written stream be copied to another. + + .. versionadded:: 8.2 + """ + + copy_to: io.BytesIO + + def __init__(self, copy_to: io.BytesIO) -> None: + super().__init__() + self.copy_to = copy_to + + def flush(self) -> None: + super().flush() + self.copy_to.flush() + + def write(self, b: ReadableBuffer) -> int: + self.copy_to.write(b) + return super().write(b) + + +class StreamMixer: + """Mixes `` and `` streams. + + The result is available in the ``output`` attribute. + + .. versionadded:: 8.2 + """ + + output: io.BytesIO + stdout: BytesIOCopy + stderr: BytesIOCopy + + def __init__(self) -> None: + self.output = io.BytesIO() + self.stdout = BytesIOCopy(copy_to=self.output) + self.stderr = BytesIOCopy(copy_to=self.output) + + +class _NamedTextIOWrapper(io.TextIOWrapper): + """A :class:`~io.TextIOWrapper` with custom ``name`` and ``mode`` + that does not close its underlying buffer. + + When ``CliRunner`` runs in ``fd`` mode, ``_original_fd`` is patched to + point at the saved (pre-redirection) ``fd``, so C-level consumers that call + :meth:`fileno` (like ``faulthandler`` or ``subprocess``) keep working. In + the default ``sys`` mode ``_original_fd`` stays at ``-1`` and + :meth:`fileno` raises :exc:`io.UnsupportedOperation`, matching the + pre-``8.3.3`` behavior. + """ + + _name: str + _mode: str + _original_fd: int + + def __init__( + self, + buffer: t.BinaryIO, + name: str, + mode: str, + **kwargs: t.Any, + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + self._original_fd = -1 + + def close(self) -> None: + """The buffer this object contains belongs to some other object, + so prevent the default ``__del__`` implementation from closing + that buffer. + + .. versionadded:: 8.3.2 + """ + + def fileno(self) -> int: + """Return the file descriptor of the saved original stream when + ``CliRunner`` runs in ``fd`` mode. Otherwise delegate to + :class:`~io.TextIOWrapper`, which raises + :exc:`io.UnsupportedOperation` for a ``BytesIO``-backed buffer. + """ + if self._original_fd >= 0: + return self._original_fd + return super().fileno() + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: str | bytes | t.IO[t.Any] | None, charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast("t.IO[t.Any]", input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(input) + + +class Result: + """Holds the captured result of an invoked CLI script. + + :param runner: The runner that created the result + :param stdout_bytes: The standard output as bytes. + :param stderr_bytes: The standard error as bytes. + :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the + user would see it in its terminal. + :param return_value: The value returned from the invoked command. + :param exit_code: The exit code as integer. + :param exception: The exception that happened if one did. + :param exc_info: Exception information (exception type, exception instance, + traceback type). + + .. versionchanged:: 8.2 + ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and + ``mix_stderr`` has been removed. + + .. versionadded:: 8.0 + Added ``return_value``. + """ + + runner: CliRunner + stdout_bytes: bytes + stderr_bytes: bytes + output_bytes: bytes + return_value: t.Any + exit_code: int + exception: BaseException | None + exc_info: ExceptionInfo | None + + def __init__( + self, + runner: CliRunner, + stdout_bytes: bytes, + stderr_bytes: bytes, + output_bytes: bytes, + return_value: t.Any, + exit_code: int, + exception: BaseException | None, + exc_info: ExceptionInfo | None = None, + ) -> None: + self.runner = runner + self.stdout_bytes = stdout_bytes + self.stderr_bytes = stderr_bytes + self.output_bytes = output_bytes + self.return_value = return_value + self.exit_code = exit_code + self.exception = exception + self.exc_info = exc_info + + @property + def output(self) -> str: + """The terminal output as unicode string, as the user would see it. + + .. versionchanged:: 8.2 + No longer a proxy for ``self.stdout``. Now has its own independent stream + that is mixing `` and ``, in the order they were written. + """ + return self.output_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string. + + .. versionchanged:: 8.2 + No longer raise an exception, always returns the `` string. + """ + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from `` writes + to ``. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param catch_exceptions: Whether to catch any exceptions other than + ``SystemExit`` when running :meth:`~CliRunner.invoke`. + :param capture: Selects the output capture strategy. ``sys`` (default) + captures Python-level writes only and leaves + :meth:`sys.stdout.fileno` raising :exc:`io.UnsupportedOperation`, so + user code that calls :func:`os.dup2` on ``sys.stdout.fileno()`` cannot + clobber the host runner's stdout. ``fd`` redirects file descriptors + ``1`` and ``2`` via :func:`os.dup2` to a temporary file, also catching + output from stale stream references, C extensions, and subprocesses. + ``fd`` is not supported on Windows. + + .. versionchanged:: 8.4.0 + Added the ``capture`` parameter. The default ``sys`` mode no longer + exposes the original fd through :meth:`fileno`, reverting the change + introduced in ``8.3.3`` that broke Pytest's ``fd``-level capture + teardown. Use ``capture="fd"`` to restore that behavior with proper + isolation. :issue:`3384` + + .. versionchanged:: 8.2 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 8.2 + ``mix_stderr`` parameter has been removed. + """ + + charset: str + env: cabc.Mapping[str, str | None] + echo_stdin: bool + catch_exceptions: bool + capture: CaptureMode + + def __init__( + self, + charset: str = "utf-8", + env: cabc.Mapping[str, str | None] | None = None, + echo_stdin: bool = False, + catch_exceptions: bool = True, + capture: CaptureMode = "sys", + ) -> None: + if capture not in {"sys", "fd"}: + raise ValueError( + f"capture={capture!r} is not valid. Choose from 'sys' or 'fd'." + ) + if capture == "fd" and sys.platform == "win32": + raise ValueError( + f"capture={capture!r} is not supported on Windows. Use 'sys'." + ) + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + self.catch_exceptions = catch_exceptions + self.capture = capture + + def get_default_prog_name(self, cli: Command) -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: cabc.Mapping[str, str | None] | None = None + ) -> cabc.Mapping[str, str | None]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + color: bool = False, + ) -> cabc.Generator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up `` with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into `sys.stdin`. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + An additional output stream is returned, which is a mix of + `` and `` streams. + + .. versionchanged:: 8.2 + Always returns the `` stream. + + .. versionchanged:: 8.0 + `` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + stream_mixer = StreamMixer() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + stream_mixer.stdout, + encoding=self.charset, + name="", + mode="w", + ) + + sys.stderr = _NamedTextIOWrapper( + stream_mixer.stderr, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: str | None = None) -> str: + sys.stdout.write(prompt or "") + try: + val = next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: str | None = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + try: + return next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + old__compat_should_strip_ansi = _compat.should_strip_ansi + old_pdb_init = pdb.Pdb.__init__ + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + _compat.should_strip_ansi = should_strip_ansi + + def _patched_pdb_init( + self: pdb.Pdb, + completekey: str = "tab", + stdin: t.IO[str] | None = None, + stdout: t.IO[str] | None = None, + **kwargs: t.Any, + ) -> None: + """Default ``pdb.Pdb`` to real terminal streams during + ``CliRunner`` isolation. + + Without this patch, ``pdb.Pdb.__init__`` inherits from + ``cmd.Cmd`` which falls back to ``sys.stdin``/``sys.stdout`` + when no explicit streams are provided. During isolation + those are ``BytesIO``-backed wrappers, so the debugger + reads from an empty buffer and writes to captured output, + making interactive debugging impossible. + + By defaulting to ``sys.__stdin__``/``sys.__stdout__`` (the + original terminal streams Python preserves regardless of + redirection), debuggers can interact with the user while + ``click.echo`` output is still captured normally. + + This covers ``pdb.set_trace()``, ``breakpoint()``, + ``pdb.post_mortem()``, and debuggers that subclass + ``pdb.Pdb`` (ipdb, pdbpp). Explicit ``stdin``/``stdout`` + arguments are honored and not overridden. Debuggers that + do not subclass ``pdb.Pdb`` (pudb, debugpy) are not + covered. + """ + if stdin is None: + stdin = sys.__stdin__ + if stdout is None: + stdout = sys.__stdout__ + old_pdb_init( + self, completekey=completekey, stdin=stdin, stdout=stdout, **kwargs + ) + + pdb.Pdb.__init__ = _patched_pdb_init # type: ignore[assignment] + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + _compat.should_strip_ansi = old__compat_should_strip_ansi + formatting.FORCED_WIDTH = old_forced_width + pdb.Pdb.__init__ = old_pdb_init # type: ignore[method-assign] + + def invoke( + self, + cli: Command, + args: str | cabc.Sequence[str] | None = None, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + catch_exceptions: bool | None = None, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. If :data:`None`, the value + from :class:`CliRunner` is used. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + The result object has the ``output_bytes`` attribute with + the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would + see it in its terminal. + + .. versionchanged:: 8.2 + The result object always returns the ``stderr_bytes`` stream. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + if catch_exceptions is None: + catch_exceptions = self.catch_exceptions + + # Set up fd capture before isolation replaces sys.stdout and sys.stderr. + cap_out: _FDCapture | None = None + cap_err: _FDCapture | None = None + + if self.capture == "fd": + cap_out = _FDCapture(1) + cap_err = _FDCapture(2) + try: + cap_out.start() + cap_err.start() + except OSError: + cap_out = cap_err = None + + with self.isolation(input=input, env=env, color=color) as outstreams: + # Point the captured streams' fileno() at the saved (original) + # fd so that C-level consumers like faulthandler keep working + # while fd 1/2 are redirected to the capture tmpfile. + if cap_out is not None and cap_err is not None: + sys.stdout._original_fd = cap_out.saved_fd # type: ignore[union-attr] + sys.stderr._original_fd = cap_err.saved_fd # type: ignore[union-attr] + + return_value = None + exception: BaseException | None = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast("int | t.Any | None", e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + sys.stderr.flush() + + # Stop fd capture and merge the captured bytes into + # the stdout/stderr BytesIO streams. BytesIOCopy mirrors + # those writes into outstreams[2] automatically. + if cap_out is not None and cap_err is not None: + fd_out = cap_out.stop() + fd_err = cap_err.stop() + if fd_out: + outstreams[0].write(fd_out) + if fd_err: + outstreams[1].write(fd_err) + + stdout = outstreams[0].getvalue() + stderr = outstreams[1].getvalue() + output = outstreams[2].getvalue() + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + output_bytes=output, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: str | os.PathLike[str] | None = None + ) -> cabc.Generator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + .. warning:: + This helper predates Python 3 and modern pytest, and is not + thread-safe: it relies on :func:`os.chdir`, which mutates + process-global state, and :meth:`invoke` swaps the + process-global standard streams too. Parallelize tests with + processes (``pytest-xdist``), not threads. Locking the + runner (:pr:`3511`, :pr:`3520`, :pr:`3530`) and a + ``set_filesystem()`` API (:issue:`3123`) were declined: + neither removes the global-state mutation. See :issue:`3700` + and :issue:`3501`. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. deprecated:: 8.5.0 + Will be removed in Click 9.0. Use + :class:`tempfile.TemporaryDirectory` or pytest's + ``tmp_path`` fixture with absolute paths instead. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + import warnings + + warnings.warn( + "'isolated_filesystem' is deprecated and will be removed in Click" + " 9.0. Use 'tempfile.TemporaryDirectory' or pytest's 'tmp_path'" + " fixture with absolute paths instead.", + DeprecationWarning, + stacklevel=3, + ) + + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) + os.chdir(dt) + + try: + yield dt + finally: + os.chdir(cwd) + + if temp_dir is None: + import shutil + + try: + shutil.rmtree(dt) + except OSError: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py new file mode 100644 index 0000000..0eb8462 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/types.py @@ -0,0 +1,1422 @@ +from __future__ import annotations + +import abc +import collections.abc as cabc +import enum +import os +import stat +import sys +import typing as t +import uuid +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import _LazyFile +from .utils import _safecall +from .utils import format_filename + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + +_ValueT = t.TypeVar("_ValueT") +_ValueT_contra = t.TypeVar("_ValueT_contra", contravariant=True) +_ValueT_co = t.TypeVar("_ValueT_co", covariant=True) + +# The input type parameter of ParamType defaults to Any. TypeVar defaults +# (PEP 696) landed in typing on Python 3.13, so type checkers get the +# default from typing_extensions, which is never imported at runtime. On +# older Pythons the runtime TypeVar carries no default; +# ParamType.__class_getitem__ fills in the omitted parameter instead. +if t.TYPE_CHECKING: + _InputT_contra = te.TypeVar("_InputT_contra", contravariant=True, default=t.Any) +elif sys.version_info >= (3, 13): + _InputT_contra = t.TypeVar("_InputT_contra", contravariant=True, default=t.Any) +else: + _InputT_contra = t.TypeVar("_InputT_contra", contravariant=True) + +_FloatValueT = t.TypeVar("_FloatValueT", bound=float) +_FloatValueT_co = t.TypeVar("_FloatValueT_co", bound=float, covariant=True) + + +class ParamTypeInfoDict(t.TypedDict): + param_type: str + name: str + + +class ParamType(t.Generic[_ValueT_co, _InputT_contra], abc.ABC): + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + + .. versionchanged:: 8.4.0 + Now a generic abstract base class. Parameterize with the + converted value type (``ParamType[int]`` for an integer-returning + type) so that :meth:`convert` and downstream consumers carry the + narrowed return type. + + .. versionchanged:: 8.5.0 + Accepts a second optional type parameter for the input value type + that :meth:`convert` accepts (``ParamType[int, str]`` for a type + converting strings to integers), defaulting to ``Any``. + """ + + is_composite: t.ClassVar[bool] = False + arity: int = 1 # read-only + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[str | None] = None + + if sys.version_info < (3, 13): + # ``_InputT_contra`` carries its ``Any`` default only for type + # checkers: ``TypeVar(default=...)`` (PEP 696) is unavailable at + # runtime before Python 3.13. Fill in the omitted input type + # parameter by hand so ``ParamType[int]`` keeps working. + def __class_getitem__(cls, params: t.Any) -> t.Any: + if cls is ParamType: + if not isinstance(params, tuple): + params = (params,) + if len(params) == 1: + params = (*params, t.Any) + # Checkers cannot see Generic.__class_getitem__ through super(). + return super().__class_getitem__(params) # type: ignore[misc] + + def to_info_dict(self) -> ParamTypeInfoDict: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + @t.overload + def __call__( + self, + value: None, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> None: ... + + @t.overload + def __call__( + self, + value: _InputT_contra, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> _ValueT_co: ... + + def __call__( + self, + value: _InputT_contra | None, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> _ValueT_co | None: + if value is not None: + return self.convert(value, param, ctx) + return None + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: _InputT_contra, param: Parameter | None, ctx: Context | None + ) -> _ValueT_co: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + # The default returns the value as-is so subclasses that only customize + # metadata are not forced to redeclare ``convert``. + return t.cast("_ValueT_co", value) + + def split_envvar_value(self, rv: str) -> cabc.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.NoReturn: + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType[_ValueT_co]): + is_composite: t.ClassVar[bool] = True + + @property + @abc.abstractmethod + def arity(self) -> int: ... # type: ignore[override] + + +if t.TYPE_CHECKING: + # on Python 3.10 this will raise a TypeError + + class FuncParamTypeInfoDict( + ParamTypeInfoDict, + t.Generic[_ValueT_contra, _ValueT_co], + ): + func: t.Callable[[_ValueT_contra], _ValueT_co] +else: + + class FuncParamTypeInfoDict(ParamTypeInfoDict): + func: t.Callable[[t.Any], t.Any] + + +class FuncParamType(ParamType[_ValueT_co], t.Generic[_ValueT_contra, _ValueT_co]): + name: str + func: t.Callable[[_ValueT_contra], _ValueT_co] + + def __init__(self, func: t.Callable[[_ValueT_contra], _ValueT_co]) -> None: + self.name = func.__name__ + self.func = func + + def to_info_dict(self) -> FuncParamTypeInfoDict[_ValueT_contra, _ValueT_co]: + return {"func": self.func, **super().to_info_dict()} + + def convert( + self, value: _ValueT_contra, param: Parameter | None, ctx: Context | None + ) -> _ValueT_co: + try: + return self.func(value) + except ValueError as exc: + message = str(exc) + + if not message: + try: + message = str(value) + except UnicodeError: + message = t.cast("bytes", value).decode("utf-8", "replace") + + self.fail(message, param, ctx) + + +class UnprocessedParamType(ParamType[t.Any]): + name = "text" + + def convert( + self, value: _ValueT, param: Parameter | None, ctx: Context | None + ) -> _ValueT: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType[str]): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> str: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + return value.decode(enc) + except UnicodeError: + fs_enc = sys.getfilesystemencoding() + if fs_enc != enc: + try: + return value.decode(fs_enc) + except UnicodeError: + return value.decode("utf-8", "replace") + else: + return value.decode("utf-8", "replace") + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +if t.TYPE_CHECKING: + # on Python 3.10 this will raise a TypeError + + class ChoiceInfoDict(ParamTypeInfoDict, t.Generic[_ValueT_co]): + choices: tuple[_ValueT_co, ...] + case_sensitive: bool +else: + + class ChoiceInfoDict(ParamTypeInfoDict): + choices: tuple[t.Any, ...] + case_sensitive: bool + + +class Choice(ParamType[_ValueT_co], t.Generic[_ValueT_co]): + """The choice type allows a value to be checked against a fixed set + of supported values. + + You may pass any iterable value which will be converted to a tuple + and thus will only be iterated once. + + The resulting value will always be one of the originally passed choices. + See :meth:`normalize_choice` for more info on the mapping of strings + to choices. See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + + .. versionchanged:: 8.4.0 + Now generic in the choice value type. Parameterize with the type of + the choice values (``Choice[HashType]`` for an enum, ``Choice[str]`` + for plain strings) to enable type-checked consumers. + + .. versionchanged:: 8.2.0 + Non-``str`` ``choices`` are now supported. It can additionally be any + iterable. Before you were not recommended to pass anything but a list or + tuple. + + .. versionadded:: 8.2.0 + Choice normalization can be overridden via :meth:`normalize_choice`. + """ + + name: str = "choice" + + choices: tuple[_ValueT_co, ...] + case_sensitive: bool + + def __init__( + self, choices: cabc.Iterable[_ValueT_co], case_sensitive: bool = True + ) -> None: + self.choices = tuple(choices) + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> ChoiceInfoDict[_ValueT_co]: + return { + "choices": self.choices, + "case_sensitive": self.case_sensitive, + **super().to_info_dict(), + } + + def _normalized_mapping( + self, ctx: Context | None = None + ) -> cabc.Mapping[_ValueT_co, str]: + """ + Returns mapping where keys are the original choices and the values are + the normalized values that are accepted via the command line. + + This is a simple wrapper around :meth:`normalize_choice`, use that + instead which is supported. + """ + return { + choice: self.normalize_choice( + choice=choice, + ctx=ctx, + ) + for choice in self.choices + } + + def normalize_choice(self, choice: object, ctx: Context | None) -> str: + """ + Normalize a choice value, used to map a passed string to a choice. + Each choice must have a unique normalized value. + + By default uses :meth:`Context.token_normalize_func` and if not case + sensitive, convert it to a casefolded value. + + .. versionadded:: 8.2.0 + """ + normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice) + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(normed_value) + + if not self.case_sensitive: + normed_value = normed_value.casefold() + + return normed_value + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + if param.param_type_name == "option" and not param.show_choices: # type: ignore[attr-defined] + choice_metavars = [ + convert_type(type(choice)).name.upper() for choice in self.choices + ] + choices_str = "|".join([*dict.fromkeys(choice_metavars)]) + else: + choices_str = "|".join( + [str(i) for i in self._normalized_mapping(ctx=ctx).values()] + ) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str: + """ + Message shown when no choice is passed. + + .. versionchanged:: 8.2.0 Added ``ctx`` argument. + """ + return _("Choose from:\n\t{choices}").format( + choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values()) + ) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> _ValueT_co: + """ + For a given value from the parser, normalize it and find its + matching normalized value in the list of choices. Then return the + matched "original" choice. + """ + normed_value = self.normalize_choice(choice=value, ctx=ctx) + normalized_mapping = self._normalized_mapping(ctx=ctx) + + try: + return next( + original + for original, normalized in normalized_mapping.items() + if normalized == normed_value + ) + except StopIteration: + self.fail( + self.get_invalid_choice_message(value=value, ctx=ctx), + param=param, + ctx=ctx, + ) + + def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: + """Get the error message when the given choice is invalid. + + :param value: The invalid value. + + .. versionadded:: 8.2 + """ + choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) + return ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str) + + def __repr__(self) -> str: + return _("Choice({choices})").format(choices=list(self.choices)) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = [self.normalize_choice(choice, ctx) for choice in self.choices] + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTimeInfoDict(ParamTypeInfoDict): + formats: cabc.Sequence[str] + + +class DateTime(ParamType[datetime]): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + formats: cabc.Sequence[str] + + def __init__(self, formats: cabc.Sequence[str] | None = None): + self.formats = formats or [ + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", + ] + + def to_info_dict(self) -> DateTimeInfoDict: + return {"formats": self.formats, **super().to_info_dict()} + + def get_metavar(self, param: Parameter, ctx: Context) -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> datetime: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase( + ParamType[_ValueT_co], t.Generic[_ValueT_contra, _ValueT_co] +): + _number_class: t.Callable[[_ValueT_contra], _ValueT_co] + + def convert( + self, value: _ValueT_contra, param: Parameter | None, ctx: Context | None + ) -> _ValueT_co: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +if t.TYPE_CHECKING: + # on Python 3.10 this will raise a TypeError + + class NumberRangeInfoDict(ParamTypeInfoDict, t.Generic[_FloatValueT_co]): + min: _FloatValueT_co | None + max: _FloatValueT_co | None + min_open: bool + max_open: bool + clamp: bool +else: + + class NumberRangeInfoDict(ParamTypeInfoDict): + min: t.Any | None + max: t.Any | None + min_open: bool + max_open: bool + clamp: bool + + +class _NumberRangeBase( + _NumberParamTypeBase[_ValueT_contra, _FloatValueT_co], + t.Generic[_ValueT_contra, _FloatValueT_co], +): + min: _FloatValueT_co | None + max: _FloatValueT_co | None + min_open: bool + max_open: bool + clamp: bool + + def __init__( + self, + min: _FloatValueT_co | None = None, + max: _FloatValueT_co | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> NumberRangeInfoDict[_FloatValueT_co]: + return { + "min": self.min, + "max": self.max, + "min_open": self.min_open, + "max_open": self.max_open, + "clamp": self.clamp, + **super().to_info_dict(), + } + + def convert( + self, value: _ValueT_contra, param: Parameter | None, ctx: Context | None + ) -> _FloatValueT_co: + import operator + + rv = super().convert(value, param, ctx) + min = self.min + max = self.max + lt_min: bool = min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, min) + gt_max: bool = max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, max) + + if self.clamp: + if min is not None and lt_min: + return self._clamp(min, 1, self.min_open) + + if max is not None and gt_max: + return self._clamp(max, -1, self.max_open) + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + @abc.abstractmethod + def _clamp( + # Covariant type variables cannot be used in input positions, so we use a + # separate method-scoped type variable instead. + self: _NumberRangeBase[t.Any, _FloatValueT], + bound: _FloatValueT, + dir: t.Literal[1, -1], + open: bool, + ) -> _FloatValueT: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + ... + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase[t.SupportsInt | t.SupportsIndex, int]): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase[int, int], IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp(self, bound: int, dir: t.Literal[1, -1], open: bool) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase[t.SupportsFloat | t.SupportsIndex, float]): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase[float, float], FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: float | None = None, + max: float | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float: + if not open: + return bound + + # Could use math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType[bool]): + name = "boolean" + + bool_states: dict[str, bool] = { + "1": True, + "0": False, + "yes": True, + "no": False, + "true": True, + "false": False, + "on": True, + "off": False, + "t": True, + "f": False, + "y": True, + "n": False, + # Absence of value is considered False. + "": False, + } + """A mapping of string values to boolean states. + + Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES` + and extends it. + + .. caution:: + String values are lower-cased, as the ``str_to_bool`` comparison function + below is case-insensitive. + + .. warning:: + The mapping is not exhaustive, and does not cover all possible boolean strings + representations. It will remains as it is to avoid endless bikeshedding. + + Future work my be considered to make this mapping user-configurable from public + API. + """ + + @staticmethod + def str_to_bool(value: str | bool) -> bool | None: + """Convert a string to a boolean value. + + If the value is already a boolean, it is returned as-is. If the value is a + string, it is stripped of whitespaces and lower-cased, then checked against + the known boolean states pre-defined in the `BoolParamType.bool_states` mapping + above. + + Returns `None` if the value does not match any known boolean state. + """ + if isinstance(value, bool): + return value + return BoolParamType.bool_states.get(value.strip().lower()) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> bool: + normalized = self.str_to_bool(value) + if normalized is None: + self.fail( + _( + "{value!r} is not a valid boolean. Recognized values: {states}" + ).format(value=value, states=", ".join(sorted(self.bool_states))), + param, + ctx, + ) + return normalized + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType[uuid.UUID]): + name = "uuid" + + def convert( + self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None + ) -> uuid.UUID: + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class FileInfoDict(ParamTypeInfoDict): + mode: str + encoding: str | None + + +class File(ParamType[t.IO[t.Any]]): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Files can also be opened atomically in which case all writes go into a + separate file in the same folder and upon completion the file will + be moved over to the original location. This is useful if a file + regularly read by other users is modified. + + See :ref:`file-args` for more information. + + .. versionchanged:: 2.0 + Added the ``atomic`` parameter. + """ + + name = "filename" + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + mode: str + encoding: str | None + errors: str | None + lazy: bool | None + atomic: bool + + def __init__( + self, + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> FileInfoDict: + return { + "mode": self.mode, + "encoding": self.encoding, + **super().to_info_dict(), + } + + def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool: + if self.lazy is not None: + return self.lazy + if os.fspath(value) == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, + value: str | os.PathLike[str] | t.IO[t.Any], + param: Parameter | None, + ctx: Context | None, + ) -> t.IO[t.Any]: + if _is_file_like(value): + return value + + try: + lazy = self.resolve_lazy_flag(value) + + if lazy: + lf = _LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + if ctx is not None: + ctx.call_on_close(lf.close_intelligently) + + return t.cast("t.IO[t.Any]", lf) + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(_safecall(f.close)) + else: + ctx.call_on_close(_safecall(f.flush)) + + return f + except OSError as e: + self.fail( + f"'{format_filename(value)}': {e.strerror}", + param, + ctx, + ) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +def _is_file_like(value: t.Any) -> te.TypeIs[t.IO[t.Any]]: + return hasattr(value, "read") or hasattr(value, "write") + + +class PathInfoDict(ParamTypeInfoDict): + exists: bool + file_okay: bool + dir_okay: bool + writable: bool + readable: bool + allow_dash: bool + + +class Path(ParamType[str | bytes | os.PathLike[str]]): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``path_type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + exists: bool + file_okay: bool + dir_okay: bool + readable: bool + writable: bool + executable: bool + resolve_path: bool + allow_dash: bool + name: str + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: type | None = None, + executable: bool = False, + ) -> None: + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type: type | None = path_type + + if self.file_okay and not self.dir_okay: + self.name = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> PathInfoDict: + return { + "exists": self.exists, + "file_okay": self.file_okay, + "dir_okay": self.dir_okay, + "writable": self.writable, + "readable": self.readable, + "allow_dash": self.allow_dash, + **super().to_info_dict(), + } + + def coerce_path_result( + self, value: str | os.PathLike[str] + ) -> str | bytes | os.PathLike[str]: + if self.type is not None and not isinstance(value, self.type): + if self.type is str: + return os.fsdecode(value) + elif self.type is bytes: + return os.fsencode(value) + else: + return t.cast("os.PathLike[str]", self.type(value)) + + return value + + def convert( + self, + value: str | os.PathLike[str], + param: Parameter | None, + ctx: Context | None, + ) -> str | bytes | os.PathLike[str]: + rv = value + + dash = b"-" if isinstance(rv, bytes) else "-" + is_dash = self.file_okay and self.allow_dash and rv == dash + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} {filename!r} is a directory.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class TupleInfoDict(ParamTypeInfoDict): + types: cabc.Sequence[ParamTypeInfoDict] + + +class Tuple(CompositeParamType[tuple[t.Any, ...]]): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType[t.Any]]) -> None: + self.types: cabc.Sequence[ParamType[t.Any]] = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> TupleInfoDict: + return { + "types": [ty.to_info_dict() for ty in self.types], + **super().to_info_dict(), + } + + @property + def name(self) -> str: # type: ignore[override] + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore[override] + return len(self.types) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> tuple[t.Any, ...]: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple( + ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False) + ) + + +def _guess_type( + ty: type[t.Any] | ParamType[t.Any] | None, + default: t.Any | None, +) -> type[t.Any] | tuple[type[t.Any], ...] | ParamType[t.Any] | None: + """Infer a type from *ty* or *default*. + + Returns *ty* unchanged when it is not ``None``. Otherwise inspects + *default* to produce a ``type``, a ``tuple`` of types (for tuple + defaults), or ``None``. + """ + if ty is not None: + return ty + + if default is None: + return None + + if not isinstance(default, (tuple, list)): + return type(default) + + # If the default is empty, return None so convert_type falls + # through to STRING. + if not default: + return None + + item = default[0] + + # A sequence of iterables needs to detect the inner types. + # Can't call convert_type recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + return tuple(map(type, item)) + + return type(item) + + +@t.overload +def convert_type(ty: None, default: None = None) -> StringParamType: ... +@t.overload +def convert_type( + ty: type | ParamType[t.Any], default: t.Any | None = None +) -> ParamType[t.Any]: ... +@t.overload +def convert_type( + ty: t.Any | None, default: t.Any | None = None +) -> ParamType[t.Any]: ... +def convert_type( + ty: t.Any | None = None, default: t.Any | None = None +) -> ParamType[t.Any]: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed = _guess_type(ty, default) + is_guessed = guessed is not ty + + if isinstance(guessed, tuple): + return Tuple(guessed) + + if isinstance(guessed, ParamType): + return guessed + + if guessed is str or guessed is None: + return STRING + + if guessed is int: + return INT + + if guessed is float: + return FLOAT + + if guessed is bool: + return BOOL + + if is_guessed: + return STRING + + if __debug__: + try: + if issubclass(guessed, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({guessed})." + ) + except TypeError: + # guessed is an instance (correct), so issubclass fails. + pass + + return FuncParamType(guessed) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED: t.Final[UnprocessedParamType] = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING: t.Final[StringParamType] = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT: t.Final[IntParamType] = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT: t.Final[FloatParamType] = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL: t.Final[BoolParamType] = BoolParamType() + +#: A UUID parameter. +UUID: t.Final[UUIDParameterType] = UUIDParameterType() + + +class OptionHelpExtra(t.TypedDict, total=False): + envvars: tuple[str, ...] + default: str + range: str + required: str diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py new file mode 100644 index 0000000..b529eb0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/click/utils.py @@ -0,0 +1,688 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import sys +import typing as t +from functools import update_wrapper +from gettext import gettext as _ +from types import ModuleType +from types import TracebackType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import binary_streams +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def _safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: + """Wraps a function so that it swallows exceptions. + + :meta private: + """ + + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: + try: + return func(*args, **kwargs) + except Exception: + pass + return None + + return update_wrapper(wrapper, func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(sys.getfilesystemencoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def _make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string. + + :meta private: + """ + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class _LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + + :meta private: + """ + + name: str + mode: str + encoding: str | None + errors: str | None + atomic: bool + _f: t.IO[t.Any] | None + should_close: bool + + def __init__( + self, + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, + ) -> None: + self.name = os.fspath(filename) + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + + if self.name == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO[t.Any]: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> _LazyFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close_intelligently() + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class _KeepOpenFile: + """Proxy a file object but keep it open across a ``with`` block. + + Wraps a borrowed file (such as ``sys.stdin`` or ``sys.stdout``) so that + leaving a ``with`` block does not close it, as used by :func:`open_file` + for the ``-`` filename. The caller stays responsible for the file: an + explicit :meth:`close` still passes through to the wrapped object. + + Dunder methods are proxied explicitly: implicit special-method lookups + bypass :meth:`__getattr__`, because Python resolves them on the type rather + than the instance. + + :meta private: + """ + + _file: t.IO[t.Any] + + def __init__(self, file: t.IO[t.Any]) -> None: + self._file = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> _KeepOpenFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: object = None, + file: t.IO[t.Any] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 8.5.0 + Colorama is no longer used for color on Windows. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + return + + match message: + case str() | bytes() | bytearray(): + out = message + case None: + out = "" + case _: + out = str(message) + + if nl: + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + elif should_strip_ansi(file, resolve_color_default(color)): + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def _get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO: + """Returns a system stream for byte processing. + + .. deprecated:: 8.5.0 + Will be removed in Click 9.0. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + + :meta private: + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) + return opener() + + +def _get_text_stream( + name: t.Literal["stdin", "stdout", "stderr"], + encoding: str | None = None, + errors: str | None = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. + + .. deprecated:: 8.5.0 + Will be removed in Click 9.0. + + This usually returns a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + :meta private: + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) + return opener(encoding, errors) + + +def open_file( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO[t.Any]: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name or Path of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast( + "t.IO[t.Any]", _LazyFile(filename, mode, encoding, errors, atomic=atomic) + ) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast("t.IO[t.Any]", _KeepOpenFile(f)) + + return f + + +def format_filename( + filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], + shorten: bool = False, +) -> str: + """Format a filename as a string for display. Ensures the filename can be + displayed by replacing any invalid bytes or surrogate escapes in the name + with the replacement character ``�``. + + Invalid bytes or surrogate escapes will raise an error when written to a + stream with ``errors="strict"``. This will typically happen with ``stdout`` + when the locale is something like ``en_GB.UTF-8``. + + Many scenarios *are* safe to write surrogates though, due to PEP 538 and + PEP 540, including: + + - Writing to ``stderr``, which uses ``errors="backslashreplace"``. + - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens + stdout and stderr with ``errors="surrogateescape"``. + - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. + - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. + Python opens stdout and stderr with ``errors="surrogateescape"``. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + else: + filename = os.fspath(filename) + + if isinstance(filename, bytes): + filename = filename.decode(sys.getfilesystemencoding(), "replace") + else: + filename = filename.encode("utf-8", "surrogateescape").decode( + "utf-8", "replace" + ) + + return filename + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no effect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class _PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + + :meta private: + """ + + wrapped: t.IO[t.Any] + + def __init__(self, wrapped: t.IO[t.Any]) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: str | None = None, _main: ModuleType | None = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + # It is set to "" inside a Shiv or PEX zipapp. + if getattr(_main, "__package__", None) in {None, ""} or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: cabc.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> list[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out + + +def __getattr__(name: str) -> object: + import warnings + + if name in { + "LazyFile", + "KeepOpenFile", + "make_default_short_help", + "PacifyFlushWrapper", + "safecall", + "get_text_stream", + "get_binary_stream", + }: + warnings.warn( + f"'click.utils.{name}' is deprecated and will be removed in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[f"_{name}"] + + raise AttributeError(name) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA new file mode 100644 index 0000000..530b398 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA @@ -0,0 +1,110 @@ +Metadata-Version: 2.4 +Name: cryptography +Version: 50.0.1 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: BSD +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable +Classifier: Topic :: Security :: Cryptography +Requires-Dist: cffi>=2.0.0 ; platform_python_implementation != 'PyPy' +Requires-Dist: typing-extensions>=4.13.2 ; python_full_version < '3.11' +Requires-Dist: bcrypt>=3.1.5 ; extra == 'ssh' +Provides-Extra: ssh +License-File: LICENSE +License-File: LICENSE.APACHE +License-File: LICENSE.BSD +Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. +Author-email: The Python Cryptographic Authority and individual contributors +License-Expression: Apache-2.0 OR BSD-3-Clause +Requires-Python: >=3.9, !=3.9.0, !=3.9.1 +Description-Content-Type: text/x-rst; charset=UTF-8 +Project-URL: changelog, https://cryptography.io/en/latest/changelog/ +Project-URL: documentation, https://cryptography.io/ +Project-URL: homepage, https://github.com/pyca/cryptography +Project-URL: issues, https://github.com/pyca/cryptography/issues +Project-URL: source, https://github.com/pyca/cryptography/ + +pyca/cryptography +================= + +.. image:: https://img.shields.io/pypi/v/cryptography.svg + :target: https://pypi.org/project/cryptography/ + :alt: Latest Version + +.. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest + :target: https://cryptography.io + :alt: Latest Docs + +.. image:: https://github.com/pyca/cryptography/actions/workflows/ci.yml/badge.svg + :target: https://github.com/pyca/cryptography/actions/workflows/ci.yml?query=branch%3Amain + +``cryptography`` is a package which provides cryptographic recipes and +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.9+ and PyPy3 7.3.11+. + +``cryptography`` includes both high level recipes and low level interfaces to +common cryptographic algorithms such as symmetric ciphers, message digests, and +key derivation functions. For example, to encrypt something with +``cryptography``'s high level symmetric encryption recipe: + +.. code-block:: pycon + + >>> from cryptography.fernet import Fernet + >>> # Put this somewhere safe! + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") + >>> token + b'...' + >>> f.decrypt(token) + b'A really secret message. Not for prying eyes.' + +You can find more information in the `documentation`_. + +You can install ``cryptography`` with: + +.. code-block:: console + + $ pip install cryptography + +For full details see `the installation documentation`_. + +Discussion +~~~~~~~~~~ + +If you run into bugs, you can file them in our `issue tracker`_. + +We maintain a `cryptography-dev`_ mailing list for development discussion. + +You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get +involved. + +Security +~~~~~~~~ + +Need to report a security issue? Please consult our `security reporting`_ +documentation. + + +.. _`documentation`: https://cryptography.io/ +.. _`the installation documentation`: https://cryptography.io/en/latest/installation/ +.. _`issue tracker`: https://github.com/pyca/cryptography/issues +.. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`security reporting`: https://cryptography.io/en/latest/security/ + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD new file mode 100644 index 0000000..5a9bb03 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD @@ -0,0 +1,120 @@ +cryptography/__about__.py,sha256=SUJRVMWtZoUu-WdZLfY7p_s2xm5YQjNLFj80YrUP9F0,445 +cryptography/__init__.py,sha256=mthuUrTd4FROCpUYrTIqhjz6s6T9djAZrV7nZ1oMm2o,364 +cryptography/cobblestone.py,sha256=T_-F08-AUf3ljaypZeklNTV1hWObH57oo45mVSzk9wc,693 +cryptography/exceptions.py,sha256=835EWILc2fwxw-gyFMriciC2SqhViETB10LBSytnDIc,1087 +cryptography/fernet.py,sha256=2sLCPOmejhOVbudcS3QsM1PIuHbxCeRcRtWkncQ3mCo,7687 +cryptography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cryptography/utils.py,sha256=Ia7S0QZzS0ezwpIHQfY8G1e-o4GjrHijEXXjwAr1iuI,4635 +cryptography/hazmat/__init__.py,sha256=5IwrLWrVp0AjEr_4FdWG_V057NSJGY_W4egNNsuct0g,455 +cryptography/hazmat/_oid.py,sha256=Ohe2nUE6XjKx1UF6TAsGUy2v8GUJ-Vi9DS4yxSukt5U,18150 +cryptography/hazmat/asn1/__init__.py,sha256=YqYToHGFjLOKigRC00HGj6hrjEwbu1SPpAiu6-u-fao,812 +cryptography/hazmat/asn1/asn1.py,sha256=9mcfrmdvK7KtjhRYofMzAjW0u7nvqbfsvPfbhNQK1BQ,19152 +cryptography/hazmat/backends/__init__.py,sha256=O5jvKFQdZnXhKeqJ-HtulaEL9Ni7mr1mDzZY5kHlYhI,361 +cryptography/hazmat/backends/openssl/__init__.py,sha256=p3jmJfnCag9iE5sdMrN6VvVEu55u46xaS_IjoI0SrmA,305 +cryptography/hazmat/backends/openssl/backend.py,sha256=0NczFbiwGxfq9qi28oHYr08r_r7VQg4AXHjb0oFPwqA,10475 +cryptography/hazmat/bindings/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/bindings/_rust.abi3.so,sha256=PWQjZkv_6Sb9kdGRPRxd3aXDHuf_HK4G_ItxxHyVAaE,14389392 +cryptography/hazmat/bindings/_rust/__init__.pyi,sha256=uUnnvOq_uameYnBNpmsv3SRAVO0WQGpjI58aR1lL4Kw,2285 +cryptography/hazmat/bindings/_rust/_openssl.pyi,sha256=T_5TpYO8bghIPjAQ3BtAABqw-CcYskHp92wm5PNY6dc,228 +cryptography/hazmat/bindings/_rust/asn1.pyi,sha256=BrGjC8J6nwuS-r3EVcdXJB8ndotfY9mbQYOfpbPG0HA,354 +cryptography/hazmat/bindings/_rust/cobblestone.pyi,sha256=9lUf1W1hTrszEJ4YpScJNIPSD9BOM7o-C82-mtSDG9o,1325 +cryptography/hazmat/bindings/_rust/declarative_asn1.pyi,sha256=MBmOZzLTdmPJ6-1K6Aq3PTuHJ1MGgRfIu0hIgag7ocE,3866 +cryptography/hazmat/bindings/_rust/exceptions.pyi,sha256=exXr2xw_0pB1kk93cYbM3MohbzoUkjOms1ZMUi0uQZE,640 +cryptography/hazmat/bindings/_rust/ocsp.pyi,sha256=VPVWuKHI9EMs09ZLRYAGvR0Iz0mCMmEzXAkgJHovpoM,4020 +cryptography/hazmat/bindings/_rust/pkcs12.pyi,sha256=vEEd5wDiZvb8ZGFaziLCaWLzAwoG_tvPUxLQw5_uOl8,1605 +cryptography/hazmat/bindings/_rust/pkcs7.pyi,sha256=txGBJijqZshEcqra6byPNbnisIdlxzOSIHP2hl9arPs,1601 +cryptography/hazmat/bindings/_rust/test_support.pyi,sha256=PPhld-WkO743iXFPebeG0LtgK0aTzGdjcIsay1Gm5GE,757 +cryptography/hazmat/bindings/_rust/x509.pyi,sha256=LVT4htBNse5gijQh4GhVNYEbAO0E1ANgE28tWqw3g4I,10211 +cryptography/hazmat/bindings/_rust/openssl/__init__.pyi,sha256=O6GOyvDxt6ylicB2aFGuu5RcGZy9UjKnYqGR8rqzvdw,1578 +cryptography/hazmat/bindings/_rust/openssl/aead.pyi,sha256=7d--xdc0vuzRe4S2uuQaGm-9GGKv4NcSLQcLyWcaD6s,4582 +cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi,sha256=LhPzHWSXJq4grAJXn6zSvSSdV-aYIIscHDwIPlJGGPs,1315 +cryptography/hazmat/bindings/_rust/openssl/cmac.pyi,sha256=nPH0X57RYpsAkRowVpjQiHE566ThUTx7YXrsadmrmHk,564 +cryptography/hazmat/bindings/_rust/openssl/dh.pyi,sha256=Z3TC-G04-THtSdAOPLM1h2G7ml5bda1ElZUcn5wpuhk,1564 +cryptography/hazmat/bindings/_rust/openssl/dsa.pyi,sha256=qBtkgj2albt2qFcnZ9UDrhzoNhCVO7HTby5VSf1EXMI,1299 +cryptography/hazmat/bindings/_rust/openssl/ec.pyi,sha256=zJy0pRa5n-_p2dm45PxECB_-B6SVZyNKfjxFDpPqT38,1691 +cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi,sha256=VXfXd5G6hUivg399R1DYdmW3eTb0EebzDTqjRC2gaRw,532 +cryptography/hazmat/bindings/_rust/openssl/ed448.pyi,sha256=Yx49lqdnjsD7bxiDV1kcaMrDktug5evi5a6zerMiy2s,514 +cryptography/hazmat/bindings/_rust/openssl/hashes.pyi,sha256=bFe3T13UeyRInUJMD-R5ArGTGLqWDiWo2Lv2KGzUbf4,1076 +cryptography/hazmat/bindings/_rust/openssl/hmac.pyi,sha256=BXZn7NDjL3JAbYW0SQ8pg1iyC5DbQXVhUAiwsi8DFR8,702 +cryptography/hazmat/bindings/_rust/openssl/hpke.pyi,sha256=p_hGLn6YH-EarJF1V5bUsUtjipviEApcR5O0sUHJLtc,2914 +cryptography/hazmat/bindings/_rust/openssl/kdf.pyi,sha256=HiiLdEB9nqMsTvMwiFppQpfPz6fHUz9_gpjEB_XAOXA,6619 +cryptography/hazmat/bindings/_rust/openssl/keys.pyi,sha256=teIt8M6ZEMJrn4s3W0UnW0DZ-30Jd68WnSsKKG124l0,912 +cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi,sha256=mGCJEoQ4lxi4CROTBEuUzb9U4ky8n52L3iDvCOXNBRI,733 +cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi,sha256=H84XDW0TQ3LHhdGJD5uPaDT5ijEiK6MZytXYc8rBNf4,1421 +cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi,sha256=byoeus4-lIOlWlEiRQdCc00qCkXpOB5peBY6yo9ik8s,835 +cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi,sha256=_SW9NtQ5FDlAbdclFtWpT4lGmxKIKHpN-4j8J2BzYfQ,585 +cryptography/hazmat/bindings/_rust/openssl/rsa.pyi,sha256=2OQCNSXkxgc-3uw1xiCCloIQTV6p9_kK79Yu0rhZgPc,1364 +cryptography/hazmat/bindings/_rust/openssl/x25519.pyi,sha256=ewn4GpQyb7zPwE-ni7GtyQgMC0A1mLuqYsSyqv6nI_s,523 +cryptography/hazmat/bindings/_rust/openssl/x448.pyi,sha256=juTZTmli8jO_5Vcufg-vHvx_tCyezmSLIh_9PU3TczI,505 +cryptography/hazmat/bindings/openssl/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/bindings/openssl/_conditional.py,sha256=vfzi-xHdStAcJQjLqqOlSos94L8D_tqFtBe7Ovw-KtY,5611 +cryptography/hazmat/bindings/openssl/binding.py,sha256=FsuLxbTnrqW60-dAWtakgufeq7TnqaQai1Pc7Dowrjo,3706 +cryptography/hazmat/decrepit/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/ciphers/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/ciphers/algorithms.py,sha256=WSPjM2sXfrLDXrtN5R7N6rA1XF7b93I1e_lvD4yx2oA,3752 +cryptography/hazmat/decrepit/ciphers/modes.py,sha256=Oq_PEwCke5OLczOfr_vzAOJ6wPx-rlsvHAXPBuh5b9o,1649 +cryptography/hazmat/primitives/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/primitives/_asymmetric.py,sha256=RhgcouUB6HTiFDBrR1LxqkMjpUxIiNvQ1r_zJjRG6qQ,532 +cryptography/hazmat/primitives/_cipheralgorithm.py,sha256=Eh3i7lwedHfi0eLSsH93PZxQKzY9I6lkK67vL4V5tOc,1522 +cryptography/hazmat/primitives/_modes.py,sha256=_EiAOD8Jb6WpFXluwkcUT3ECz_TH8xjGvbQlGkNm59Q,3075 +cryptography/hazmat/primitives/_serialization.py,sha256=hi0xJblBAZ8pmZx2lWa-lruphxvvvG3g9DNK0G8Odfs,4440 +cryptography/hazmat/primitives/cmac.py,sha256=sz_s6H_cYnOvx-VNWdIKhRhe3Ymp8z8J0D3CBqOX3gg,338 +cryptography/hazmat/primitives/constant_time.py,sha256=xdunWT0nf8OvKdcqUhhlFKayGp4_PgVJRU2W1wLSr_A,422 +cryptography/hazmat/primitives/hashes.py,sha256=Srtwgc3_pFvZfIhRj9e1p4Wdi-F_PpleGHlqJARgtOg,7916 +cryptography/hazmat/primitives/hmac.py,sha256=RpB3z9z5skirCQrm7zQbtnp9pLMnAjrlTUvKqF5aDDc,423 +cryptography/hazmat/primitives/hpke.py,sha256=RsHissC5l-dTPn1p2JbcrIrAnDBrLqM5ugBFSGpmKu4,865 +cryptography/hazmat/primitives/keywrap.py,sha256=t9t8KZuzZ4Us30ASUhI27MZPPPKy4EERRDkAK-lM7TQ,735 +cryptography/hazmat/primitives/padding.py,sha256=QT-U-NvV2eQGO1wVPbDiNGNSc9keRDS-ig5cQOrLz0E,1865 +cryptography/hazmat/primitives/poly1305.py,sha256=P5EPQV-RB_FJPahpg01u0Ts4S_PnAmsroxIGXbGeRRo,355 +cryptography/hazmat/primitives/asymmetric/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/primitives/asymmetric/dh.py,sha256=Rnt2fLaDpQEjQSX8fNtlmTXOJHjlt_DKQZ1Vlfhdja4,5793 +cryptography/hazmat/primitives/asymmetric/dsa.py,sha256=kQzTViqAI9PiELNOBco_n6MfP3N9ehIPkSduOpupt0M,4482 +cryptography/hazmat/primitives/asymmetric/ec.py,sha256=LklVCLLxbbQzqiE6HRaYTRauLMphYUw3tE6c8oBIxYQ,10183 +cryptography/hazmat/primitives/asymmetric/ed25519.py,sha256=mbffLvs-3PgCDeg1rQXVXh7GLntlVL4ATjkIEc4pt5M,2998 +cryptography/hazmat/primitives/asymmetric/ed448.py,sha256=n0dK6y-VLxuw-zvrDxuPT4598hymgX-1Ry_jq9aUQhE,4002 +cryptography/hazmat/primitives/asymmetric/mldsa.py,sha256=LHMaaU0m66EsSrSodWf4e2kdmdDRTEGRC1y-fCKxGK0,13803 +cryptography/hazmat/primitives/asymmetric/mlkem.py,sha256=wvVkeFT-4of-VcUAER2-zI_5hXT6rwTJPMCQ8gF9GTA,7901 +cryptography/hazmat/primitives/asymmetric/padding.py,sha256=jbgajvGHYwkPjMDljHu5lxMBlDfmCxWUe2AF2S6KE10,3678 +cryptography/hazmat/primitives/asymmetric/rsa.py,sha256=lIE7lGe449W5URLTKCpvFGerITouKoZV9GwuTzScsSo,8492 +cryptography/hazmat/primitives/asymmetric/types.py,sha256=SjJhTrnFRLlTPdYrBD1poMi6cjYG-fh0fSIaQn3QUiw,2588 +cryptography/hazmat/primitives/asymmetric/utils.py,sha256=Qs8Re9GFPjW_tNp_73IeJEjPCf0slOIsWOxo6qymT6k,821 +cryptography/hazmat/primitives/asymmetric/x25519.py,sha256=lQcgUk-Piubj8ynFNWQeYme3tYZMgHQqkRKkIDOHLzo,3888 +cryptography/hazmat/primitives/asymmetric/x448.py,sha256=b37ig7k7poG6SJDry42j-ElCj5q4eQ0X5CFmN0Sn8z8,3913 +cryptography/hazmat/primitives/ciphers/__init__.py,sha256=eyEXmjk6_CZXaOPYDr7vAYGXr29QvzgWL2-4CSolLFs,680 +cryptography/hazmat/primitives/ciphers/aead.py,sha256=Fzlyx7w8KYQakzDp1zWgJnIr62zgZrgVh1u2h4exB54,634 +cryptography/hazmat/primitives/ciphers/algorithms.py,sha256=IGfCycYJOh1TvoI34rfw8KfJ_xmt1p_DpBht2Fghceo,3496 +cryptography/hazmat/primitives/ciphers/base.py,sha256=aBC7HHBBoixebmparVr0UlODs3VD0A7B6oz_AaRjDv8,4253 +cryptography/hazmat/primitives/ciphers/modes.py,sha256=H7AZSgJiSTBCi0grL97NxakS950JyFKYLsra8tjHPxQ,6003 +cryptography/hazmat/primitives/kdf/__init__.py,sha256=v3yiYBGU272EojNXbwfYZdbbfI9cVOCCG3nXhTDda3k,1037 +cryptography/hazmat/primitives/kdf/argon2.py,sha256=ZJx-enUlAA4o7b46C1wlzG_XAQQW0wIkFgUKkrM_wA8,632 +cryptography/hazmat/primitives/kdf/concatkdf.py,sha256=BFnOKS72txKF0yQvASr0Na7uhzvLHpUCckK5iNAC_aQ,591 +cryptography/hazmat/primitives/kdf/hkdf.py,sha256=M0lAEfRoc4kpp4-nwDj9yB-vNZukIOYEQrUlWsBNn9o,543 +cryptography/hazmat/primitives/kdf/kbkdf.py,sha256=C3w99vjFqhqzAxzFOH4zzvVoHkb5AC6Lz6AEQGV7d_g,737 +cryptography/hazmat/primitives/kdf/pbkdf2.py,sha256=Xchkk99s-Mk6b6KSCouqdk8FVyiNVOSFIenmnW3tLgQ,468 +cryptography/hazmat/primitives/kdf/scrypt.py,sha256=XyWUdUUmhuI9V6TqAPOvujCSMGv1XQdg0a21IWCmO-U,590 +cryptography/hazmat/primitives/kdf/x963kdf.py,sha256=QKjhRehuTsAstL384bKfvjuv1yfdamAHVhsBRWen2Vw,456 +cryptography/hazmat/primitives/serialization/__init__.py,sha256=fXcu49ugL9rxhpRl_wZjnMqE9DCkhCMcsRxfj_qTveg,2217 +cryptography/hazmat/primitives/serialization/base.py,sha256=SABrvo0S86QcoBlj7DpjeLWy2lzQMryiioJ6DrEYQVA,651 +cryptography/hazmat/primitives/serialization/pkcs12.py,sha256=mS9cFNG4afzvseoc5e1MWoY2VskfL8N8Y_OFjl67luY,5104 +cryptography/hazmat/primitives/serialization/pkcs7.py,sha256=mFM7OFuZ8cCxUGUoo4Cof6BnCHc_Ibx1AQjjfxnPlxo,13998 +cryptography/hazmat/primitives/serialization/ssh.py,sha256=FisXe_nUKdPGfqENVhKpmoazmaI8qH8Ge-VGbc0dykw,53592 +cryptography/hazmat/primitives/twofactor/__init__.py,sha256=tmMZGB-g4IU1r7lIFqASU019zr0uPp_wEBYcwdDCKCA,258 +cryptography/hazmat/primitives/twofactor/hotp.py,sha256=ivZo5BrcCGWLsqql4nZV0XXCjyGPi_iHfDFltGlOJwk,3256 +cryptography/hazmat/primitives/twofactor/totp.py,sha256=m5LPpRL00kp4zY8gTjr55Hfz9aMlPS53kHmVkSQCmdY,1652 +cryptography/x509/__init__.py,sha256=0KVNTmRtdYNX_i_ewsTNzbTm2e1qyfoOhc4emog5hL4,8253 +cryptography/x509/base.py,sha256=UIMKkwmsx10BR3_gnBRBCBTwQp1-O8wkfVm7azH-_mE,28311 +cryptography/x509/certificate_transparency.py,sha256=JqoOIDhlwInrYMFW6IFn77WJ0viF-PB_rlZV3vs9MYc,797 +cryptography/x509/extensions.py,sha256=-iXcwWnIL1zl5Ml3BVJFkEo_71ncXe84ehvqzW1sK4M,77975 +cryptography/x509/general_name.py,sha256=sP_rV11Qlpsk4x3XXGJY_Mv0Q_s9dtjeLckHsjpLQoQ,7836 +cryptography/x509/name.py,sha256=oI2w6VdY8zgw7qpNhlxEx512W0AhWDXQrDYf5EsM2PY,15566 +cryptography/x509/ocsp.py,sha256=Yey6NdFV1MPjop24Mj_VenjEpg3kUaMopSWOK0AbeBs,12699 +cryptography/x509/oid.py,sha256=BUzgXXGVWilkBkdKPTm9R4qElE9gAGHgdYPMZAp7PJo,931 +cryptography/x509/verification.py,sha256=gR2C2c-XZQtblZhT5T5vjSKOtCb74ef2alPVmEcwFlM,958 +cryptography-50.0.1.dist-info/METADATA,sha256=jptVcNpGhoSO5ca7g__8IkvN35eTE2cx3QUM4MBf_-I,4343 +cryptography-50.0.1.dist-info/WHEEL,sha256=ObQlzmyJjoP4uM3utl2uaQ06ufI4LKnDhg53v200B3o,109 +cryptography-50.0.1.dist-info/RECORD,, +cryptography-50.0.1.dist-info/licenses/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197 +cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360 +cryptography-50.0.1.dist-info/licenses/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532 +cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json,sha256=m9rCnuTZv9tCQeBPD9vRLlti_jFwa9OovtKMjK_LVaA,45685 +cryptography-50.0.1.dist-info/sboms/sbom.json,sha256=S9XP7BDLleOELcWlLWOrYuGy3RtuaXtFh5A-yN5T9yA,1206 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL new file mode 100644 index 0000000..08b80b3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: maturin (1.14.1) +Root-Is-Purelib: false +Tag: cp311-abi3-manylinux_2_34_x86_64 + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000..b11f379 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made +under the terms of *both* these licenses. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE new file mode 100644 index 0000000..62589ed --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD new file mode 100644 index 0000000..ec1a29d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD @@ -0,0 +1,27 @@ +Copyright (c) Individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of PyCA Cryptography nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json new file mode 100644 index 0000000..e7df0f2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json @@ -0,0 +1,1365 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:8937a838-8d73-4fdd-9a20-bac2da7b0240", + "metadata": { + "timestamp": "2026-08-25T19:33:44.118311124Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.9" + } + ], + "authors": [ + { + "name": "The cryptography developers", + "email": "cryptography-dev@python.org" + } + ], + "component": { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust#cryptography-rust@0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-rust", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-rust@0.50.1?download_url=file://.", + "components": [ + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust#cryptography-rust@0.50.1 bin-target-0", + "name": "cryptography_rust", + "version": "0.50.1", + "purl": "pkg:cargo/cryptography-rust@0.50.1?download_url=file://.#src/lib.rs" + } + ] + }, + "properties": [ + { + "name": "cdx:rustc:sbom:target:all_targets", + "value": "true" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-cffi#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-cffi", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-cffi@0.50.1?download_url=file://cryptography-cffi" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-crypto#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-crypto", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-crypto@0.50.1?download_url=file://cryptography-crypto" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-keepalive#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-keepalive", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-keepalive@0.50.1?download_url=file://cryptography-keepalive" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-key-parsing#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-key-parsing", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-key-parsing@0.50.1?download_url=file://cryptography-key-parsing" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-openssl#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-openssl", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-openssl@0.50.1?download_url=file://cryptography-openssl" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-x509", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-x509@0.50.1?download_url=file://cryptography-x509" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509-verification#0.50.1", + "author": "The cryptography developers ", + "name": "cryptography-x509-verification", + "version": "0.50.1", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-x509-verification@0.50.1?download_url=file://cryptography-x509-verification" + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "author": "Alex Gaynor ", + "name": "asn1", + "version": "0.24.1", + "description": "ASN.1 (DER) parser and writer for Rust.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1" + } + ], + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/asn1@0.24.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/alex/rust-asn1" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "author": "Alex Gaynor ", + "name": "asn1_derive", + "version": "0.24.1", + "description": "#[derive] support for asn1", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a" + } + ], + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/asn1_derive@0.24.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/alex/rust-asn1" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.23.0", + "author": "Marshall Pierce ", + "name": "base64", + "version": "0.23.0", + "description": "encodes and decodes base64 as bytes or utf8", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/base64@0.23.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/base64" + }, + { + "type": "vcs", + "url": "https://github.com/marshallpierce/rust-base64" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.13.1", + "author": "The Rust Project Developers", + "name": "bitflags", + "version": "2.13.1", + "description": "A macro to generate structures which behave like bitflags. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bitflags@2.13.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitflags" + }, + { + "type": "website", + "url": "https://github.com/bitflags/bitflags" + }, + { + "type": "vcs", + "url": "https://github.com/bitflags/bitflags" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.4.0", + "author": "Alex Crichton ", + "name": "cc", + "version": "1.4.0", + "description": "A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cc@1.4.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cc" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/cc-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "author": "Alex Crichton ", + "name": "cfg-if", + "version": "1.0.4", + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cfg-if@1.0.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/cfg-if" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "name": "find-msvc-tools", + "version": "0.1.9", + "description": "Find windows-specific tools, read MSVC versions from the registry and from COM interfaces", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/find-msvc-tools@0.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/find-msvc-tools" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "author": "Steven Fackler ", + "name": "foreign-types-shared", + "version": "0.1.1", + "description": "An internal crate used by foreign-types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/foreign-types-shared@0.1.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sfackler/foreign-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "author": "Steven Fackler ", + "name": "foreign-types", + "version": "0.3.2", + "description": "A framework for Rust wrappers over C APIs", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/foreign-types@0.3.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sfackler/foreign-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18", + "author": "David Tolnay ", + "name": "itoa", + "version": "1.0.18", + "description": "Fast integer primitive to string conversion", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itoa@1.0.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itoa" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/itoa" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189", + "name": "libc", + "version": "0.2.189", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.189", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4", + "author": "Aleksey Kladov ", + "name": "once_cell", + "version": "1.21.4", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "name": "openssl-macros", + "version": "0.1.1", + "description": "Internal macros used by the openssl crate.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/openssl-macros@0.1.1" + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117", + "author": "Alex Crichton , Steven Fackler ", + "name": "openssl-sys", + "version": "0.9.117", + "description": "FFI bindings to OpenSSL", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/openssl-sys@0.9.117", + "externalReferences": [ + { + "type": "other", + "url": "openssl" + }, + { + "type": "vcs", + "url": "https://github.com/rust-openssl/rust-openssl" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81", + "author": "Steven Fackler ", + "name": "openssl", + "version": "0.10.81", + "description": "OpenSSL bindings", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" + } + ], + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:cargo/openssl@0.10.81", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-openssl/rust-openssl" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pem@4.0.0", + "author": "Jonathan Creekmore ", + "name": "pem", + "version": "4.0.0", + "description": "Parse and encode PEM-encoded data.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/pem@4.0.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/pem/" + }, + { + "type": "website", + "url": "https://github.com/jcreekmore/pem-rs.git" + }, + { + "type": "vcs", + "url": "https://github.com/jcreekmore/pem-rs.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33", + "author": "Alex Crichton ", + "name": "pkg-config", + "version": "0.3.33", + "description": "A library to run the pkg-config system tool at build time in order to be used in Cargo build scripts. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pkg-config@0.3.33", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/pkg-config" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/pkg-config-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.14.0", + "name": "portable-atomic", + "version": "1.14.0", + "description": "Portable atomic types including support for 128-bit atomics, atomic float, etc. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/portable-atomic@1.14.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/portable-atomic" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "author": "David Tolnay , Alex Crichton ", + "name": "proc-macro2", + "version": "1.0.107", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.107", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-build-config", + "version": "0.29.0", + "description": "Build configuration for the PyO3 ecosystem", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-build-config@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-ffi", + "version": "0.29.0", + "description": "Python-API bindings for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-ffi@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros-backend", + "version": "0.29.0", + "description": "Code generation for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros-backend@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros", + "version": "0.29.0", + "description": "Proc macros for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3", + "version": "0.29.0", + "description": "Bindings to Python interpreter", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3@0.29.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crate/pyo3/" + }, + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "pyo3-python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "author": "David Tolnay ", + "name": "quote", + "version": "1.0.47", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.47", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.3.0", + "author": "Lukas Bergdoll ", + "name": "self_cell", + "version": "1.3.0", + "description": "Safe-to-use proc-macro-free self-referential structs in stable Rust.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR GPL-2.0-only" + } + ], + "purl": "pkg:cargo/self_cell@1.3.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/self_cell" + }, + { + "type": "vcs", + "url": "https://github.com/Voultapher/self_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@2.0.1", + "author": "comex , Fenhl , Adrian Taylor , Alex Touchet , Daniel Parks , Garrett Berg ", + "name": "shlex", + "version": "2.0.1", + "description": "Split a string into shell words, like Python's shlex.", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/shlex@2.0.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/comex/rust-shlex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119", + "author": "David Tolnay ", + "name": "syn", + "version": "2.0.119", + "description": "Parser for Rust source code", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.119", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5", + "author": "Dan Gohman ", + "name": "target-lexicon", + "version": "0.13.5", + "description": "LLVM target triple types", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/target-lexicon@0.13.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/target-lexicon/" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/target-lexicon" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "author": "David Tolnay ", + "name": "unicode-ident", + "version": "1.0.24", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + } + ], + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.24", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15", + "author": "Jim McGrath ", + "name": "vcpkg", + "version": "0.2.15", + "description": "A library to find native dependencies in a vcpkg tree at build time in order to be used in Cargo build scripts. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/vcpkg@0.2.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/vcpkg" + }, + { + "type": "vcs", + "url": "https://github.com/mcgoo/vcpkg-rs" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust#cryptography-rust@0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#base64@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-cffi#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-crypto#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-keepalive#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-key-parsing#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-openssl#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509-verification#0.50.1", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117", + "registry+https://github.com/rust-lang/crates.io-index#pem@4.0.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.3.0" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-cffi#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.4.0", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-crypto#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-keepalive#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-key-parsing#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-crypto#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-openssl#0.50.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509#0.50.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117", + "registry+https://github.com/rust-lang/crates.io-index#pem@4.0.0" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-openssl#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509-verification#0.50.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpHjnbGg/cryptography-50.0.1/src/rust/cryptography-x509#0.50.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.23.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.13.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.4.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "registry+https://github.com/rust-lang/crates.io-index#shlex@2.0.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.4.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189", + "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33", + "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.81", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.13.1", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189", + "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pem@4.0.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.23.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.14.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.189", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4", + "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.3.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@2.0.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.119", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15" + } + ] +} \ No newline at end of file diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json new file mode 100644 index 0000000..b35f4b1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json @@ -0,0 +1,43 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:8993a62f-53da-4d3f-b423-fc6d6d57604c", + "metadata": { + "timestamp": "2026-08-25T18:07:43Z" + }, + "components": [ + { + "type": "library", + "name": "openssl", + "version": "4.0.2", + "purl": "pkg:generic/openssl@4.0.2?download_url=https://github.com/openssl/openssl/releases/download/openssl-4.0.2/openssl-4.0.2.tar.gz", + "hashes": [ + { + "alg": "SHA-256", + "content": "736b467530f916737b7031310ccb21d8218c6229e61e8e160cd1d3458cd543a8" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://github.com/openssl/openssl/releases/download/openssl-4.0.2/openssl-4.0.2.tar.gz" + } + ], + "properties": [ + { + "name": "build:operating-system", + "value": "linux" + }, + { + "name": "build:architecture", + "value": "x86_64" + }, + { + "name": "build:flags", + "value": "no-zlib no-shared no-module no-comp no-apps no-docs no-sm2-precomp no-atexit enable-ec_nistp_64_gcc_128" + } + ] + } + ] +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py new file mode 100644 index 0000000..d6fc1ae --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +__all__ = [ + "__author__", + "__copyright__", + "__version__", +] + +__version__ = "50.0.1" + + +__author__ = "The Python Cryptographic Authority and individual contributors" +__copyright__ = f"Copyright 2013-2026 {__author__}" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py new file mode 100644 index 0000000..d374f75 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.__about__ import __author__, __copyright__, __version__ + +__all__ = [ + "__author__", + "__copyright__", + "__version__", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py new file mode 100644 index 0000000..09f67af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py @@ -0,0 +1,21 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import ( + cobblestone as _cobblestone, +) + +Cobblestone128Decryptor = _cobblestone.Cobblestone128Decryptor +Cobblestone128Encryptor = _cobblestone.Cobblestone128Encryptor +Cobblestone256Decryptor = _cobblestone.Cobblestone256Decryptor +Cobblestone256Encryptor = _cobblestone.Cobblestone256Encryptor + +__all__ = [ + "Cobblestone128Decryptor", + "Cobblestone128Encryptor", + "Cobblestone256Decryptor", + "Cobblestone256Encryptor", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py new file mode 100644 index 0000000..fe125ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions + +if typing.TYPE_CHECKING: + from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +_Reasons = rust_exceptions._Reasons + + +class UnsupportedAlgorithm(Exception): + def __init__(self, message: str, reason: _Reasons | None = None) -> None: + super().__init__(message) + self._reason = reason + + +class AlreadyFinalized(Exception): + pass + + +class AlreadyUpdated(Exception): + pass + + +class NotYetFinalized(Exception): + pass + + +class InvalidTag(Exception): + pass + + +class InvalidSignature(Exception): + pass + + +class InternalError(Exception): + def __init__( + self, msg: str, err_code: list[rust_openssl.OpenSSLError] + ) -> None: + super().__init__(msg) + self.err_code = err_code + + +class InvalidKey(Exception): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py new file mode 100644 index 0000000..582e594 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py @@ -0,0 +1,240 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import base64 +import binascii +import os +import time +import typing +from collections.abc import Iterable + +from cryptography import utils +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes, padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.hmac import HMAC + + +class InvalidToken(Exception): + pass + + +_MAX_CLOCK_SKEW = 60 + +# Hoisted to module level so each operation doesn't reconstruct them. +_PKCS7_128 = padding.PKCS7(128) +_SHA256 = hashes.SHA256() + + +class Fernet: + def __init__( + self, + key: bytes | str, + backend: typing.Any = None, + ) -> None: + try: + key = base64.urlsafe_b64decode(key) + except binascii.Error as exc: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) from exc + if len(key) != 32: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) + + self._signing_key = key[:16] + self._encryption_key = key[16:] + self._aes = algorithms.AES(self._encryption_key) + + @classmethod + def generate_key(cls) -> bytes: + return base64.urlsafe_b64encode(os.urandom(32)) + + def encrypt(self, data: bytes) -> bytes: + return self.encrypt_at_time(data, int(time.time())) + + def encrypt_at_time(self, data: bytes, current_time: int) -> bytes: + iv = os.urandom(16) + return self._encrypt_from_parts(data, current_time, iv) + + def _encrypt_from_parts( + self, data: bytes, current_time: int, iv: bytes + ) -> bytes: + utils._check_bytes("data", data) + + padder = _PKCS7_128.padder() + padded_data = padder.update(data) + padder.finalize() + encryptor = Cipher(self._aes, modes.CBC(iv)).encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + basic_parts = ( + b"\x80" + + current_time.to_bytes(length=8, byteorder="big") + + iv + + ciphertext + ) + + h = HMAC(self._signing_key, _SHA256) + h.update(basic_parts) + hmac = h.finalize() + return base64.urlsafe_b64encode(basic_parts + hmac) + + def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes: + timestamp, data = Fernet._get_unverified_token_data(token) + if ttl is None: + time_info = None + else: + time_info = (ttl, int(time.time())) + return self._decrypt_data(data, timestamp, time_info) + + def decrypt_at_time( + self, token: bytes | str, ttl: int, current_time: int + ) -> bytes: + if ttl is None: + raise ValueError( + "decrypt_at_time() can only be used with a non-None ttl" + ) + timestamp, data = Fernet._get_unverified_token_data(token) + return self._decrypt_data(data, timestamp, (ttl, current_time)) + + def extract_timestamp(self, token: bytes | str) -> int: + timestamp, data = Fernet._get_unverified_token_data(token) + # Verify the token was not tampered with. + self._verify_signature(data) + return timestamp + + @staticmethod + def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]: + if not isinstance(token, (str, bytes)): + raise TypeError("token must be bytes or str") + + try: + data = base64.urlsafe_b64decode(token) + except (TypeError, binascii.Error): + raise InvalidToken + + if not data or data[0] != 0x80: + raise InvalidToken + + if len(data) < 9: + raise InvalidToken + + timestamp = int.from_bytes(data[1:9], byteorder="big") + return timestamp, data + + def _verify_signature(self, data: bytes) -> None: + h = HMAC(self._signing_key, _SHA256) + h.update(memoryview(data)[:-32]) + try: + h.verify(data[-32:]) + except InvalidSignature: + raise InvalidToken + + def _decrypt_data( + self, + data: bytes, + timestamp: int, + time_info: tuple[int, int] | None, + ) -> bytes: + if time_info is not None: + ttl, current_time = time_info + if timestamp + ttl < current_time: + raise InvalidToken + + if current_time + _MAX_CLOCK_SKEW < timestamp: + raise InvalidToken + + self._verify_signature(data) + + mv = memoryview(data) + iv = data[9:25] + ciphertext = mv[25:-32] + decryptor = Cipher(self._aes, modes.CBC(iv)).decryptor() + plaintext_padded = decryptor.update(ciphertext) + try: + plaintext_padded += decryptor.finalize() + except ValueError: + raise InvalidToken + unpadder = _PKCS7_128.unpadder() + + unpadded = unpadder.update(plaintext_padded) + try: + unpadded += unpadder.finalize() + except ValueError: + raise InvalidToken + return unpadded + + +class MultiFernet: + def __init__(self, fernets: Iterable[Fernet]): + fernets = list(fernets) + if not fernets: + raise ValueError( + "MultiFernet requires at least one Fernet instance" + ) + self._fernets = fernets + + def encrypt(self, msg: bytes) -> bytes: + return self.encrypt_at_time(msg, int(time.time())) + + def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: + return self._fernets[0].encrypt_at_time(msg, current_time) + + def rotate(self, msg: bytes | str) -> bytes: + timestamp, data = Fernet._get_unverified_token_data(msg) + for f in self._fernets: + try: + p = f._decrypt_data(data, timestamp, None) + break + except InvalidToken: + pass + else: + raise InvalidToken + + iv = os.urandom(16) + return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) + + def decrypt(self, msg: bytes | str, ttl: int | None = None) -> bytes: + if ttl is None: + time_info = None + else: + time_info = (ttl, int(time.time())) + # Parse the token once rather than once per key. + timestamp, data = Fernet._get_unverified_token_data(msg) + for f in self._fernets: + try: + return f._decrypt_data(data, timestamp, time_info) + except InvalidToken: + pass + raise InvalidToken + + def decrypt_at_time( + self, msg: bytes | str, ttl: int, current_time: int + ) -> bytes: + if ttl is None: + raise ValueError( + "decrypt_at_time() can only be used with a non-None ttl" + ) + # Parse the token once rather than once per key. + timestamp, data = Fernet._get_unverified_token_data(msg) + for f in self._fernets: + try: + return f._decrypt_data(data, timestamp, (ttl, current_time)) + except InvalidToken: + pass + raise InvalidToken + + def extract_timestamp(self, msg: bytes | str) -> int: + # Parse the token once rather than once per key. + timestamp, data = Fernet._get_unverified_token_data(msg) + for f in self._fernets: + try: + f._verify_signature(data) + return timestamp + except InvalidToken: + pass + raise InvalidToken diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py new file mode 100644 index 0000000..b9f1187 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +""" +Hazardous Materials + +This is a "Hazardous Materials" module. You should ONLY use it if you're +100% absolutely sure that you know what you're doing because this module +is full of land mines, dragons, and dinosaurs with laser guns. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py new file mode 100644 index 0000000..244a1b3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py @@ -0,0 +1,373 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import ( + ObjectIdentifier as ObjectIdentifier, +) +from cryptography.hazmat.primitives import hashes + + +class ExtensionOID: + SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") + SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") + KEY_USAGE = ObjectIdentifier("2.5.29.15") + PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16") + SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") + ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") + BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") + NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") + CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") + CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") + POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") + AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") + POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") + EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") + FRESHEST_CRL = ObjectIdentifier("2.5.29.46") + INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") + ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28") + AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") + SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") + OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") + TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24") + CRL_NUMBER = ObjectIdentifier("2.5.29.20") + DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27") + PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier( + "1.3.6.1.4.1.11129.2.4.2" + ) + PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") + SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") + MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") + ADMISSIONS = ObjectIdentifier("1.3.36.8.3.3") + + +class OCSPExtensionOID: + NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2") + ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4") + + +class CRLEntryExtensionOID: + CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") + CRL_REASON = ObjectIdentifier("2.5.29.21") + INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") + + +class NameOID: + COMMON_NAME = ObjectIdentifier("2.5.4.3") + COUNTRY_NAME = ObjectIdentifier("2.5.4.6") + LOCALITY_NAME = ObjectIdentifier("2.5.4.7") + STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") + STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_IDENTIFIER = ObjectIdentifier("2.5.4.97") + ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") + ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") + SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") + SURNAME = ObjectIdentifier("2.5.4.4") + GIVEN_NAME = ObjectIdentifier("2.5.4.42") + TITLE = ObjectIdentifier("2.5.4.12") + INITIALS = ObjectIdentifier("2.5.4.43") + GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") + X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") + DN_QUALIFIER = ObjectIdentifier("2.5.4.46") + PSEUDONYM = ObjectIdentifier("2.5.4.65") + USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") + DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") + EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") + JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") + JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") + JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( + "1.3.6.1.4.1.311.60.2.1.2" + ) + BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") + POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") + POSTAL_CODE = ObjectIdentifier("2.5.4.17") + INN = ObjectIdentifier("1.2.643.3.131.1.1") + OGRN = ObjectIdentifier("1.2.643.100.1") + SNILS = ObjectIdentifier("1.2.643.100.3") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + UNSIGNED = ObjectIdentifier("1.3.6.1.5.5.7.25.1") + + +class SignatureAlgorithmOID: + RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") + RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") + # This is an alternate OID for RSA with SHA1 that is occasionally seen + _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") + RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") + RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") + RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") + RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") + RSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.13") + RSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.14") + RSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.15") + RSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.16") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") + ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") + ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") + ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") + ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") + ECDSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.9") + ECDSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.10") + ECDSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.11") + ECDSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.12") + DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") + DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") + DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") + DSA_WITH_SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.3.3") + DSA_WITH_SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.3.4") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + ML_DSA_44 = ObjectIdentifier("2.16.840.1.101.3.4.3.17") + ML_DSA_65 = ObjectIdentifier("2.16.840.1.101.3.4.3.18") + ML_DSA_87 = ObjectIdentifier("2.16.840.1.101.3.4.3.19") + GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3") + GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") + GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") + UNSIGNED = ObjectIdentifier("1.3.6.1.5.5.7.6.36") + + +_SIG_OIDS_TO_HASH: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = { + SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), + SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.RSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.RSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.RSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.RSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ED25519: None, + SignatureAlgorithmOID.ED448: None, + SignatureAlgorithmOID.ML_DSA_44: None, + SignatureAlgorithmOID.ML_DSA_65: None, + SignatureAlgorithmOID.ML_DSA_87: None, + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None, + SignatureAlgorithmOID.UNSIGNED: None, +} + + +class HashAlgorithmOID: + SHA1 = ObjectIdentifier("1.3.14.3.2.26") + SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.2.4") + SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.2.1") + SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.2.2") + SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.2.3") + SHA3_224 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.224") + SHA3_256 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.256") + SHA3_384 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.384") + SHA3_512 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.512") + SHA3_224_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.7") + SHA3_256_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.8") + SHA3_384_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.9") + SHA3_512_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.10") + + +class PublicKeyAlgorithmOID: + DSA = ObjectIdentifier("1.2.840.10040.4.1") + EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") + RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + X25519 = ObjectIdentifier("1.3.101.110") + X448 = ObjectIdentifier("1.3.101.111") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + ML_DSA_44 = ObjectIdentifier("2.16.840.1.101.3.4.3.17") + ML_DSA_65 = ObjectIdentifier("2.16.840.1.101.3.4.3.18") + ML_DSA_87 = ObjectIdentifier("2.16.840.1.101.3.4.3.19") + ML_KEM_768 = ObjectIdentifier("2.16.840.1.101.3.4.4.2") + ML_KEM_1024 = ObjectIdentifier("2.16.840.1.101.3.4.4.3") + + +class ExtendedKeyUsageOID: + SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") + CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") + CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") + EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") + TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") + OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") + ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0") + SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2") + KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5") + IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17") + BUNDLE_SECURITY = ObjectIdentifier("1.3.6.1.5.5.7.3.35") + CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") + + +class OtherNameFormOID: + PERMANENT_IDENTIFIER = ObjectIdentifier("1.3.6.1.5.5.7.8.3") + HW_MODULE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.4") + DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") + NAI_REALM = ObjectIdentifier("1.3.6.1.5.5.7.8.8") + SMTP_UTF8_MAILBOX = ObjectIdentifier("1.3.6.1.5.5.7.8.9") + ACP_NODE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.10") + BUNDLE_EID = ObjectIdentifier("1.3.6.1.5.5.7.8.11") + + +class AuthorityInformationAccessOID: + CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") + OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") + + +class SubjectInformationAccessOID: + CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") + + +class CertificatePoliciesOID: + CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") + CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") + ANY_POLICY = ObjectIdentifier("2.5.29.32.0") + + +class AttributeOID: + CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + + +_OID_NAMES = { + NameOID.COMMON_NAME: "commonName", + NameOID.COUNTRY_NAME: "countryName", + NameOID.LOCALITY_NAME: "localityName", + NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", + NameOID.STREET_ADDRESS: "streetAddress", + NameOID.ORGANIZATION_NAME: "organizationName", + NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", + NameOID.SERIAL_NUMBER: "serialNumber", + NameOID.SURNAME: "surname", + NameOID.GIVEN_NAME: "givenName", + NameOID.TITLE: "title", + NameOID.GENERATION_QUALIFIER: "generationQualifier", + NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", + NameOID.DN_QUALIFIER: "dnQualifier", + NameOID.PSEUDONYM: "pseudonym", + NameOID.USER_ID: "userID", + NameOID.DOMAIN_COMPONENT: "domainComponent", + NameOID.EMAIL_ADDRESS: "emailAddress", + NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", + NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( + "jurisdictionStateOrProvinceName" + ), + NameOID.BUSINESS_CATEGORY: "businessCategory", + NameOID.POSTAL_ADDRESS: "postalAddress", + NameOID.POSTAL_CODE: "postalCode", + NameOID.INN: "INN", + NameOID.OGRN: "OGRN", + NameOID.SNILS: "SNILS", + NameOID.UNSTRUCTURED_NAME: "unstructuredName", + SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", + SignatureAlgorithmOID.RSASSA_PSS: "rsassaPss", + SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", + SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", + SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", + SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", + SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", + SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", + SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", + SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", + SignatureAlgorithmOID.ED25519: "ed25519", + SignatureAlgorithmOID.ED448: "ed448", + SignatureAlgorithmOID.ML_DSA_44: "ML-DSA-44", + SignatureAlgorithmOID.ML_DSA_65: "ML-DSA-65", + SignatureAlgorithmOID.ML_DSA_87: "ML-DSA-87", + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: ( + "GOST R 34.11-94 with GOST R 34.10-2001" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" + ), + HashAlgorithmOID.SHA1: "sha1", + HashAlgorithmOID.SHA224: "sha224", + HashAlgorithmOID.SHA256: "sha256", + HashAlgorithmOID.SHA384: "sha384", + HashAlgorithmOID.SHA512: "sha512", + HashAlgorithmOID.SHA3_224: "sha3_224", + HashAlgorithmOID.SHA3_256: "sha3_256", + HashAlgorithmOID.SHA3_384: "sha3_384", + HashAlgorithmOID.SHA3_512: "sha3_512", + HashAlgorithmOID.SHA3_224_NIST: "sha3_224", + HashAlgorithmOID.SHA3_256_NIST: "sha3_256", + HashAlgorithmOID.SHA3_384_NIST: "sha3_384", + HashAlgorithmOID.SHA3_512_NIST: "sha3_512", + PublicKeyAlgorithmOID.DSA: "dsaEncryption", + PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", + PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", + PublicKeyAlgorithmOID.X25519: "X25519", + PublicKeyAlgorithmOID.X448: "X448", + ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", + ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", + ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", + ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", + ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", + ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", + ExtendedKeyUsageOID.SMARTCARD_LOGON: "msSmartcardLogin", + ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "pkInitKDC", + ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", + ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", + ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod", + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", + ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", + ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", + ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.PRECERT_POISON: "ctPoison", + ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", + ExtensionOID.ADMISSIONS: "Admissions", + CRLEntryExtensionOID.CRL_REASON: "cRLReason", + CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", + ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", + ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", + ExtensionOID.POLICY_MAPPINGS: "policyMappings", + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", + ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", + ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", + ExtensionOID.FRESHEST_CRL: "freshestCRL", + ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", + ExtensionOID.ISSUING_DISTRIBUTION_POINT: "issuingDistributionPoint", + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", + ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", + ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", + ExtensionOID.CRL_NUMBER: "cRLNumber", + ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator", + ExtensionOID.TLS_FEATURE: "TLSFeature", + AuthorityInformationAccessOID.OCSP: "OCSP", + AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", + SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", + CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", + CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", + OCSPExtensionOID.NONCE: "OCSPNonce", + AttributeOID.CHALLENGE_PASSWORD: "challengePassword", +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py new file mode 100644 index 0000000..d19e2a6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py @@ -0,0 +1,47 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.asn1.asn1 import ( + TLV, + BitString, + Default, + Explicit, + GeneralizedTime, + IA5String, + Implicit, + Null, + PrintableString, + SetOf, + Size, + UTCTime, + Variant, + decode_der, + encode_der, + sequence, + set, + value_set, +) + +__all__ = [ + "TLV", + "BitString", + "Default", + "Explicit", + "GeneralizedTime", + "IA5String", + "Implicit", + "Null", + "PrintableString", + "SetOf", + "Size", + "UTCTime", + "Variant", + "decode_der", + "encode_der", + "sequence", + "set", + "value_set", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py new file mode 100644 index 0000000..62d3811 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py @@ -0,0 +1,533 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import builtins +import dataclasses +import enum +import sys +import types +import typing + +if sys.version_info < (3, 11): + import typing_extensions + + LiteralString = typing_extensions.LiteralString +else: + LiteralString = typing.LiteralString + +from cryptography.hazmat.bindings._rust import declarative_asn1 +from cryptography.hazmat.bindings._rust import x509 as rust_x509 + +if sys.version_info < (3, 10): + NoneType = type(None) +else: + NoneType = types.NoneType # type: ignore[nonetype-type] + +T = typing.TypeVar("T", covariant=True) +U = typing.TypeVar("U") +Tag = typing.TypeVar("Tag", bound=LiteralString) + + +@dataclasses.dataclass(frozen=True) +class Variant(typing.Generic[U, Tag]): + """ + A tagged variant for CHOICE fields with the same underlying type. + + Use this when you have multiple CHOICE alternatives with the same type + and need to distinguish between them: + + foo: ( + Annotated[Variant[int, typing.Literal["IntA"]], Implicit(0)] + | Annotated[Variant[int, typing.Literal["IntB"]], Implicit(1)] + ) + + Usage: + example = Example(foo=Variant(5, "IntA")) + decoded.foo.value # The int value + decoded.foo.tag # "IntA" or "IntB" + """ + + value: U + tag: str + + +decode_der = declarative_asn1.decode_der +encode_der = declarative_asn1.encode_der + + +_X509_TYPES = ( + rust_x509.Certificate, + rust_x509.CertificateSigningRequest, + rust_x509.CertificateRevocationList, +) + + +def _check_x509_field_annotations( + field_type: typing.Any, + annotation: declarative_asn1.Annotation, + field_name: str, +) -> None: + if field_type in _X509_TYPES and isinstance(annotation.encoding, Implicit): + raise TypeError( + f"field '{field_name}' has an IMPLICIT annotation, but " + "IMPLICIT annotations are not supported for X.509 types." + ) + + +def _is_union(field_type: type) -> bool: + # NOTE: types.UnionType for `T | U`, typing.Union for `Union[T, U]`. + # TODO: Drop the `hasattr()` once the minimum supported Python version + # is >= 3.10. + union_types = ( + (types.UnionType, typing.Union) + if hasattr(types, "UnionType") + else (typing.Union,) + ) + return typing.get_origin(field_type) in union_types + + +def _resolve_type_aliases(field_type: typing.Any) -> typing.Any: + # Recursively resolve PEP 695 (`type X = ...`) type aliases (Python + # 3.12+) to their underlying value, so that the rest of the + # normalization logic never encounters an alias. Aliases can refer + # to other aliases and can appear at any level of nesting (e.g. + # inside `Annotated[...]`, unions, or `list[...]`). + if sys.version_info < (3, 12): + return field_type + + while isinstance(field_type, typing.TypeAliasType): + field_type = field_type.__value__ + + args = typing.get_args(field_type) + resolved_args = tuple(_resolve_type_aliases(arg) for arg in args) + if resolved_args == args: + # No aliases anywhere inside: return the type unchanged. + return field_type + + if _is_union(field_type): + # `X | Y` unions can't be rebuilt through their origin like + # other generics below: `typing.get_origin` returns + # `types.UnionType` for them, which is only subscriptable on + # Python 3.14+. Rebuilding through `typing.Union` also + # flattens any nested union introduced by an alias of a union + # (e.g. `Time | int` where `type Time = UTCTime | + # GeneralizedTime`), just like `typing.Union` would have done + # if the alias had been written inline. + return typing.Union[resolved_args] + + # An alias appeared inside a generic (e.g. `Annotated[Time, ...]`, + # `list[MyInt]`, or `SetOf[MyInt]`): re-parameterize the generic + # with the resolved arguments. Subscripting with a tuple is + # equivalent to subscripting with multiple arguments. + return typing.get_origin(field_type)[resolved_args] + + +def _extract_annotation( + metadata: tuple, field_name: str +) -> declarative_asn1.Annotation: + default = None + encoding = None + size = None + for raw_annotation in metadata: + if isinstance(raw_annotation, Default): + if default is not None: + raise TypeError( + f"multiple DEFAULT annotations found in field " + f"'{field_name}'" + ) + default = raw_annotation.value + elif isinstance(raw_annotation, declarative_asn1.Encoding): + if encoding is not None: + raise TypeError( + f"multiple IMPLICIT/EXPLICIT annotations found in field " + f"'{field_name}'" + ) + encoding = raw_annotation + elif isinstance(raw_annotation, declarative_asn1.Size): + if size is not None: + raise TypeError( + f"multiple SIZE annotations found in field '{field_name}'" + ) + size = raw_annotation + else: + raise TypeError(f"unsupported annotation: {raw_annotation}") + + return declarative_asn1.Annotation( + default=default, encoding=encoding, size=size + ) + + +def _normalize_field_type( + field_type: typing.Any, field_name: str +) -> declarative_asn1.AnnotatedType: + field_type = _resolve_type_aliases(field_type) + + # Strip the `Annotated[...]` off, and populate the annotation + # from it if it exists. + if typing.get_origin(field_type) is typing.Annotated: + annotation = _extract_annotation(field_type.__metadata__, field_name) + field_type, *_ = typing.get_args(field_type) + else: + annotation = declarative_asn1.Annotation() + + if annotation.size is not None and ( + typing.get_origin(field_type) not in (builtins.list, SetOf) + and field_type + not in ( + builtins.bytes, + builtins.str, + BitString, + IA5String, + PrintableString, + ) + ): + raise TypeError( + f"field '{field_name}' has a SIZE annotation, but SIZE " + "annotations are only supported for fields of types: " + "[SEQUENCE OF, SET OF, BIT STRING, OCTET STRING, UTF8String, " + "PrintableString, IA5String]" + ) + + if field_type is TLV: + if isinstance(annotation.encoding, Implicit): + raise TypeError( + f"field '{field_name}' has an IMPLICIT annotation, but " + "IMPLICIT annotations are not supported for TLV types." + ) + elif annotation.default is not None: + raise TypeError( + f"field '{field_name}' has a DEFAULT annotation, but " + "DEFAULT annotations are not supported for TLV types." + ) + + _check_x509_field_annotations(field_type, annotation, field_name) + + if hasattr(field_type, "__asn1_root__"): + root_type = field_type.__asn1_root__ + if not isinstance( + root_type, + ( + declarative_asn1.Type.Sequence, + declarative_asn1.Type.Set, + declarative_asn1.Type.ValueSet, + ), + ): + raise TypeError(f"unsupported root type: {root_type}") + return declarative_asn1.AnnotatedType( + typing.cast(declarative_asn1.Type, root_type), annotation + ) + elif _is_union(field_type): + union_args = typing.get_args(field_type) + if len(union_args) == 2 and NoneType in union_args: + # A Union between a type and None is an OPTIONAL + optional_type = ( + union_args[0] if union_args[1] is type(None) else union_args[1] + ) + if optional_type is TLV: + raise TypeError( + "optional TLV types (`TLV | None`) are not " + "currently supported" + ) + # For optional types, the annotation is associated with the + # union, so we check it against the inner type here. + _check_x509_field_annotations( + optional_type, annotation, field_name + ) + annotated_type = _normalize_field_type(optional_type, field_name) + + if not annotated_type.annotation.is_empty(): + raise TypeError( + "optional (`X | None`) types cannot have `X` " + "annotated: annotations must apply to the union " + "(i.e: `Annotated[X | None, annotation]`)" + ) + + if annotation.default is not None: + raise TypeError( + "optional (`X | None`) types should not have a DEFAULT " + "annotation" + ) + + rust_field_type = declarative_asn1.Type.Option(annotated_type) + + else: + # Otherwise, the Union is a CHOICE + if isinstance(annotation.encoding, Implicit): + # CHOICEs cannot be IMPLICIT. See X.680 section 31.2.9. + raise TypeError( + "CHOICE (`X | Y | ...`) types should not have an IMPLICIT " + "annotation" + ) + variants = [ + _type_to_variant(arg, field_name) + for arg in union_args + if arg is not type(None) + ] + + # Union types should either be all Variants + # (`Variant[..] | Variant[..] | etc`) or all non Variants + are_union_types_tagged = variants[0].tag_name is not None + if any( + (v.tag_name is not None) != are_union_types_tagged + for v in variants + ): + raise TypeError( + "When using `asn1.Variant` in a union, all the other " + "types in the union must also be `asn1.Variant`" + ) + + if are_union_types_tagged: + tags = {v.tag_name for v in variants} + if len(variants) != len(tags): + raise TypeError( + "When using `asn1.Variant` in a union, the tags used " + "must be unique" + ) + + rust_choice_type = declarative_asn1.Type.Choice(variants) + # If None is part of the union types, this is an OPTIONAL CHOICE + rust_field_type = ( + declarative_asn1.Type.Option( + declarative_asn1.AnnotatedType( + rust_choice_type, declarative_asn1.Annotation() + ) + ) + if NoneType in union_args + else rust_choice_type + ) + + elif typing.get_origin(field_type) is builtins.list: + inner_type = _normalize_field_type( + typing.get_args(field_type)[0], field_name + ) + rust_field_type = declarative_asn1.Type.SequenceOf(inner_type) + elif typing.get_origin(field_type) is SetOf: + inner_type = _normalize_field_type( + typing.get_args(field_type)[0], field_name + ) + rust_field_type = declarative_asn1.Type.SetOf(inner_type) + else: + rust_field_type = declarative_asn1.non_root_python_to_rust(field_type) + + return declarative_asn1.AnnotatedType(rust_field_type, annotation) + + +# Convert a type to a Variant. Used with types inside Union +# annotations (T1, T2, etc in `Union[T1, T2, ...]`). +def _type_to_variant( + t: typing.Any, field_name: str +) -> declarative_asn1.Variant: + is_annotated = typing.get_origin(t) is typing.Annotated + inner_type = typing.get_args(t)[0] if is_annotated else t + + # Check if this is a Variant[T, Tag] type + if typing.get_origin(inner_type) is Variant: + value_type, tag_literal = typing.get_args(inner_type) + if typing.get_origin(tag_literal) is not typing.Literal: + raise TypeError( + "When using `asn1.Variant` in a type annotation, the second " + "type parameter must be a `typing.Literal` type. E.g: " + '`Variant[int, typing.Literal["MyInt"]]`.' + ) + tag_name = typing.get_args(tag_literal)[0] + + if hasattr(value_type, "__asn1_root__"): + rust_type = value_type.__asn1_root__ + else: + rust_type = declarative_asn1.non_root_python_to_rust(value_type) + + if is_annotated: + ann_type = declarative_asn1.AnnotatedType( + rust_type, + _extract_annotation(t.__metadata__, field_name), + ) + else: + ann_type = declarative_asn1.AnnotatedType( + rust_type, + declarative_asn1.Annotation(), + ) + + return declarative_asn1.Variant(Variant, ann_type, tag_name) + else: + # Plain type (not a tagged Variant) + return declarative_asn1.Variant( + inner_type, + _normalize_field_type(t, field_name), + None, + ) + + +def _annotate_fields( + raw_fields: dict[str, type], +) -> dict[str, declarative_asn1.AnnotatedType]: + fields = {} + for field_name, field_type in raw_fields.items(): + # Recursively normalize the field type into something that the + # Rust code can understand. + annotated_field_type = _normalize_field_type(field_type, field_name) + fields[field_name] = annotated_field_type + + return fields + + +def _register_asn1_sequence(cls: type[U]) -> None: + raw_fields = typing.get_type_hints(cls, include_extras=True) + root = declarative_asn1.Type.Sequence(cls, _annotate_fields(raw_fields)) + + setattr(cls, "__asn1_root__", root) + + +def _register_asn1_set(cls: type[U]) -> None: + raw_fields = typing.get_type_hints(cls, include_extras=True) + root = declarative_asn1.Type.Set(cls, _annotate_fields(raw_fields)) + + setattr(cls, "__asn1_root__", root) + + +# Due to https://github.com/python/mypy/issues/19731, we can't define an alias +# for `dataclass_transform` that conditionally points to `typing` or +# `typing_extensions` depending on the Python version. We work around it by +# making the whole decorated class conditional on the Python version. +if sys.version_info < (3, 11): + + @typing_extensions.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # We use `dataclasses.dataclass` to add an __init__ method + # to the class with keyword-only parameters. + if sys.version_info >= (3, 10): + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + # `match_args` was added in Python 3.10 and defaults + # to True + match_args=False, + # `kw_only` was added in Python 3.10 and defaults to + # False + kw_only=True, + )(cls) + else: + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls + + @typing_extensions.dataclass_transform(kw_only_default=True) + def set(cls: type[U]) -> type[U]: + # We use `dataclasses.dataclass` to add an __init__ method + # to the class with keyword-only parameters. + if sys.version_info >= (3, 10): + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + # `match_args` was added in Python 3.10 and defaults + # to True + match_args=False, + # `kw_only` was added in Python 3.10 and defaults to + # False + kw_only=True, + )(cls) + else: + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + )(cls) + _register_asn1_set(dataclass_cls) + return dataclass_cls + +else: + + @typing.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # Only add an __init__ method, with keyword-only + # parameters. + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls + + @typing.dataclass_transform(kw_only_default=True) + def set(cls: type[U]) -> type[U]: + # Only add an __init__ method, with keyword-only + # parameters. + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + _register_asn1_set(dataclass_cls) + return dataclass_cls + + +def value_set( + value_type: type, +) -> typing.Callable[[type[U]], type[U]]: + """ + A class decorator that registers an `enum.Enum` subclass as an + ASN.1 value set of the given underlying type. All the member + values must be instances of `value_type`. Members are encoded as + their value; decoding fails if the decoded value does not match + any member. + """ + rust_type = declarative_asn1.non_root_python_to_rust(value_type) + + def decorator(cls: type[U]) -> type[U]: + if not issubclass(cls, enum.Enum): + raise TypeError( + "value sets can only be defined from enum.Enum subclasses" + ) + members = list(cls) + if not members: + raise TypeError( + f"value set '{cls.__name__}' must have at least one member" + ) + for member in members: + if not isinstance(member.value, value_type): + raise TypeError( + f"member '{member.name}' of value set '{cls.__name__}' " + f"must have a value of type " + f"'{value_type.__name__}', got: " + f"'{type(member.value).__name__}'" + ) + inner = declarative_asn1.AnnotatedType( + rust_type, declarative_asn1.Annotation() + ) + # Map from member value to member, used for O(1) lookups when + # decoding. This requires the member values to be hashable. + value_map = {member.value: member for member in members} + root = declarative_asn1.Type.ValueSet(cls, inner, value_map) + + setattr(cls, "__asn1_root__", root) + return cls + + return decorator + + +# TODO: replace with `Default[U]` once the min Python version is >= 3.12 +@dataclasses.dataclass(frozen=True) +class Default(typing.Generic[U]): + value: U + + +SetOf = declarative_asn1.SetOf + +Explicit = declarative_asn1.Encoding.Explicit +Implicit = declarative_asn1.Encoding.Implicit +Size = declarative_asn1.Size + +PrintableString = declarative_asn1.PrintableString +IA5String = declarative_asn1.IA5String +UTCTime = declarative_asn1.UTCTime +GeneralizedTime = declarative_asn1.GeneralizedTime +BitString = declarative_asn1.BitString +TLV = declarative_asn1.Tlv +Null = declarative_asn1.Null diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py new file mode 100644 index 0000000..b4400aa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from typing import Any + + +def default_backend() -> Any: + from cryptography.hazmat.backends.openssl.backend import backend + + return backend diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py new file mode 100644 index 0000000..51b0447 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.backends.openssl.backend import backend + +__all__ = ["backend"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py new file mode 100644 index 0000000..0d02c4f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py @@ -0,0 +1,309 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.bindings.openssl import binding +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.asymmetric.padding import ( + MGF1, + OAEP, + PSS, + PKCS1v15, +) +from cryptography.hazmat.primitives.ciphers import ( + CipherAlgorithm, +) +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, + Mode, +) + + +class Backend: + """ + OpenSSL API binding interfaces. + """ + + name = "openssl" + + # TripleDES encryption is disallowed/deprecated throughout 2023 in + # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). + _fips_ciphers = (AES,) + # Sometimes SHA1 is still permissible. That logic is contained + # within the various *_supported methods. + _fips_hashes = ( + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, + hashes.SHAKE128, + hashes.SHAKE256, + ) + _fips_ecdh_curves = ( + ec.SECP224R1, + ec.SECP256R1, + ec.SECP384R1, + ec.SECP521R1, + ) + _fips_rsa_min_key_size = 2048 + _fips_rsa_min_public_exponent = 65537 + _fips_dsa_min_modulus = 1 << 2048 + _fips_dh_min_key_size = 2048 + _fips_dh_min_modulus = 1 << _fips_dh_min_key_size + + def __init__(self) -> None: + self._binding = binding.Binding() + self._ffi = self._binding.ffi + self._lib = self._binding.lib + self._fips_enabled = rust_openssl.is_fips_enabled() + + def __repr__(self) -> str: + return ( + f"" + ) + + def openssl_assert(self, ok: bool) -> None: + return binding._openssl_assert(ok) + + def _enable_fips(self) -> None: + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + rust_openssl.enable_fips(rust_openssl._providers) + assert rust_openssl.is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() + + def openssl_version_text(self) -> str: + """ + Friendly string name of the loaded OpenSSL library. This is not + necessarily the same version as it was compiled against. + + Example: OpenSSL 3.2.1 30 Jan 2024 + """ + return rust_openssl.openssl_version_text() + + def openssl_version_number(self) -> int: + return rust_openssl.openssl_version() + + def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): + return False + + return rust_openssl.hashes.hash_supported(algorithm) + + def signature_hash_supported( + self, algorithm: hashes.HashAlgorithm + ) -> bool: + # Dedicated check for hashing algorithm use in message digest for + # signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption). + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + return self.hash_supported(algorithm) + + def scrypt_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Scrypt, "derive") + + def argon2_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Argon2id, "derive") + + def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + # FIPS mode still allows SHA1 for HMAC + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return True + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + return isinstance( + algorithm, + ( + hashes.MD5, + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, + ), + ) + return self.hash_supported(algorithm) + + def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: + if self._fips_enabled: + # FIPS mode requires AES. TripleDES is disallowed/deprecated in + # FIPS 140-3. + if not isinstance(cipher, self._fips_ciphers): + return False + + return rust_openssl.ciphers.cipher_supported(cipher, mode) + + def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: + return rust_openssl.capture_error_stack() + + def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + + return isinstance( + algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ) + + def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: + if isinstance(padding, PKCS1v15): + return True + elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): + # FIPS 186-4 only allows salt length == digest length for PSS + # It is technically acceptable to set an explicit salt length + # equal to the digest length and this will incorrectly fail, but + # since we don't do that in the tests and this method is + # private, we'll ignore that until we need to do otherwise. + if ( + self._fips_enabled + and padding._salt_length != PSS.DIGEST_LENGTH + ): + return False + return self.hash_supported(padding._mgf._algorithm) + elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): + return self._oaep_hash_supported( + padding._mgf._algorithm + ) and self._oaep_hash_supported(padding._algorithm) + else: + return False + + def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: + if self._fips_enabled and isinstance(padding, PKCS1v15): + return False + else: + return self.rsa_padding_supported(padding) + + def dsa_supported(self) -> bool: + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not self._fips_enabled + ) + + def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if not self.dsa_supported(): + return False + return self.signature_hash_supported(algorithm) + + def cmac_algorithm_supported(self, algorithm) -> bool: + return self.cipher_supported( + algorithm, CBC(b"\x00" * algorithm.block_size) + ) + + def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): + return False + + return rust_openssl.ec.curve_supported(curve) + + def elliptic_curve_signature_algorithm_supported( + self, + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, + curve: ec.EllipticCurve, + ) -> bool: + # We only support ECDSA right now. + if not isinstance(signature_algorithm, ec.ECDSA): + return False + + return self.elliptic_curve_supported(curve) and ( + isinstance(signature_algorithm.algorithm, asym_utils.Prehashed) + or self.hash_supported(signature_algorithm.algorithm) + ) + + def elliptic_curve_exchange_algorithm_supported( + self, algorithm: ec.ECDH, curve: ec.EllipticCurve + ) -> bool: + return self.elliptic_curve_supported(curve) and isinstance( + algorithm, ec.ECDH + ) + + def dh_supported(self) -> bool: + return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + + def dh_x942_serialization_supported(self) -> bool: + return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 + + def x25519_supported(self) -> bool: + return not self._fips_enabled + + def x448_supported(self) -> bool: + if self._fips_enabled: + return False + return ( + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + + def mlkem_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_IS_AWSLC + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER + ) + + def mldsa_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_IS_AWSLC + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER + ) + + def ed25519_supported(self) -> bool: + return True + + def ed448_supported(self) -> bool: + return ( + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + + def ecdsa_deterministic_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + and not self._fips_enabled + ) + + def poly1305_supported(self) -> bool: + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + return True + return not self._fips_enabled + + +backend = Backend() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so new file mode 100644 index 0000000..fddbd2a Binary files /dev/null and b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so differ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi new file mode 100644 index 0000000..c3148f1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives._serialization import ( + KeySerializationEncryptionBuilder, +) +from cryptography.utils import Buffer + +class PKCS7PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class PKCS7UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class Encoding: + PEM: typing.ClassVar[Encoding] + DER: typing.ClassVar[Encoding] + OpenSSH: typing.ClassVar[Encoding] + Raw: typing.ClassVar[Encoding] + X962: typing.ClassVar[Encoding] + SMIME: typing.ClassVar[Encoding] + +class PrivateFormat: + PKCS8: typing.ClassVar[PrivateFormat] + TraditionalOpenSSL: typing.ClassVar[PrivateFormat] + Raw: typing.ClassVar[PrivateFormat] + OpenSSH: typing.ClassVar[PrivateFormat] + PKCS12: typing.ClassVar[PrivateFormat] + def encryption_builder(self) -> KeySerializationEncryptionBuilder: ... + +class PublicFormat: + SubjectPublicKeyInfo: typing.ClassVar[PublicFormat] + PKCS1: typing.ClassVar[PublicFormat] + OpenSSH: typing.ClassVar[PublicFormat] + Raw: typing.ClassVar[PublicFormat] + CompressedPoint: typing.ClassVar[PublicFormat] + UncompressedPoint: typing.ClassVar[PublicFormat] + +class ParameterFormat: + PKCS3: typing.ClassVar[ParameterFormat] + +class ObjectIdentifier: + def __init__(self, value: str) -> None: ... + @property + def dotted_string(self) -> str: ... + @property + def _name(self) -> str: ... + +T = typing.TypeVar("T") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi new file mode 100644 index 0000000..3d4ea4e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi @@ -0,0 +1,8 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +lib: typing.Any +ffi: typing.Any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi new file mode 100644 index 0000000..3b5f208 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -0,0 +1,7 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +def decode_dss_signature(signature: bytes) -> tuple[int, int]: ... +def encode_dss_signature(r: int, s: int) -> bytes: ... +def parse_spki_for_data(data: bytes) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi new file mode 100644 index 0000000..95e9583 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi @@ -0,0 +1,33 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.utils import Buffer + +class Cobblestone128Encryptor: + def __init__(self, key: Buffer, context: Buffer) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def update(self, data: Buffer) -> bytes: ... + def update_into(self, data: Buffer, buf: Buffer) -> int: ... + def finalize(self) -> bytes: ... + +class Cobblestone128Decryptor: + def __init__(self, key: Buffer, context: Buffer) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def update_into(self, data: Buffer, buf: Buffer) -> int: ... + def finalize(self) -> bytes: ... + +class Cobblestone256Encryptor: + def __init__(self, key: Buffer, context: Buffer) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def update(self, data: Buffer) -> bytes: ... + def update_into(self, data: Buffer, buf: Buffer) -> int: ... + def finalize(self) -> bytes: ... + +class Cobblestone256Decryptor: + def __init__(self, key: Buffer, context: Buffer) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def update_into(self, data: Buffer, buf: Buffer) -> int: ... + def finalize(self) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi new file mode 100644 index 0000000..66fc065 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -0,0 +1,133 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +import typing + +T = typing.TypeVar("T") + +def decode_der(cls: type[T], value: bytes) -> T: ... +def encode_der(value: typing.Any) -> bytes: ... +def non_root_python_to_rust(cls: type) -> Type: ... + +# Type is a Rust enum with tuple variants. For now, we express the type +# annotations like this: +class Type: + Sequence: typing.ClassVar[type] + SequenceOf: typing.ClassVar[type] + Set: typing.ClassVar[type] + SetOf: typing.ClassVar[type] + Option: typing.ClassVar[type] + Choice: typing.ClassVar[type] + ValueSet: typing.ClassVar[type] + PyBool: typing.ClassVar[type] + PyInt: typing.ClassVar[type] + PyBytes: typing.ClassVar[type] + PyStr: typing.ClassVar[type] + +class Annotation: + default: typing.Any | None + encoding: Encoding | None + size: Size | None + def __new__( + cls, + default: typing.Any | None = None, + encoding: Encoding | None = None, + size: Size | None = None, + ) -> Annotation: ... + def is_empty(self) -> bool: ... + +# Encoding is a Rust enum with tuple variants. For now, we express the type +# annotations like this: +class Encoding: + Implicit: typing.ClassVar[type] + Explicit: typing.ClassVar[type] + +class Size: + min: int + max: int | None + + def __new__(cls, min: int, max: int | None) -> Size: ... + @staticmethod + def exact(n: int) -> Size: ... + +class AnnotatedType: + inner: Type + annotation: Annotation + + def __new__(cls, inner: Type, annotation: Annotation) -> AnnotatedType: ... + +class AnnotatedTypeObject: + annotated_type: AnnotatedType + value: typing.Any + + def __new__( + cls, annotated_type: AnnotatedType, value: typing.Any + ) -> AnnotatedTypeObject: ... + +class Variant: + python_class: type + ann_type: AnnotatedType + tag_name: str | None + + def __new__( + cls, + python_class: type, + ann_type: AnnotatedType, + tag_name: str | None, + ) -> Variant: ... + +class PrintableString: + def __new__(cls, inner: str) -> PrintableString: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def as_str(self) -> str: ... + +class IA5String: + def __new__(cls, inner: str) -> IA5String: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def as_str(self) -> str: ... + +class UTCTime: + def __new__(cls, inner: datetime.datetime) -> UTCTime: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def as_datetime(self) -> datetime.datetime: ... + +class GeneralizedTime: + def __new__(cls, inner: datetime.datetime) -> GeneralizedTime: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def as_datetime(self) -> datetime.datetime: ... + +class BitString: + def __new__(cls, data: bytes, padding_bits: int) -> BitString: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def as_bytes(self) -> bytes: ... + def padding_bits(self) -> int: ... + +class Tlv: + @property + def tag_bytes(self) -> bytes: ... + @property + def data(self) -> memoryview: ... + def parse(self, cls: type): ... + +class SetOf(typing.Generic[T]): + def __new__(cls, inner: list[T]) -> SetOf[T]: ... + def as_list(self) -> list[T]: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + +class Null: + def __new__(cls) -> Null: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi new file mode 100644 index 0000000..09f46b1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +class _Reasons: + BACKEND_MISSING_INTERFACE: _Reasons + UNSUPPORTED_HASH: _Reasons + UNSUPPORTED_CIPHER: _Reasons + UNSUPPORTED_PADDING: _Reasons + UNSUPPORTED_MGF: _Reasons + UNSUPPORTED_PUBLIC_KEY_ALGORITHM: _Reasons + UNSUPPORTED_ELLIPTIC_CURVE: _Reasons + UNSUPPORTED_SERIALIZATION: _Reasons + UNSUPPORTED_X509: _Reasons + UNSUPPORTED_EXCHANGE_ALGORITHM: _Reasons + UNSUPPORTED_DIFFIE_HELLMAN: _Reasons + UNSUPPORTED_MAC: _Reasons diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi new file mode 100644 index 0000000..103e96c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -0,0 +1,117 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +from collections.abc import Iterator + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.x509 import ocsp + +class OCSPRequest: + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def extensions(self) -> x509.Extensions: ... + +class OCSPResponse: + @property + def responses(self) -> Iterator[OCSPSingleResponse]: ... + @property + def response_status(self) -> ocsp.OCSPResponseStatus: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_response_bytes(self) -> bytes: ... + @property + def certificates(self) -> list[x509.Certificate]: ... + @property + def responder_key_hash(self) -> bytes | None: ... + @property + def responder_name(self) -> x509.Name | None: ... + @property + def produced_at(self) -> datetime.datetime: ... + @property + def produced_at_utc(self) -> datetime.datetime: ... + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def single_extensions(self) -> x509.Extensions: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + +class OCSPSingleResponse: + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + +def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ... +def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ... +def create_ocsp_request( + builder: ocsp.OCSPRequestBuilder, +) -> ocsp.OCSPRequest: ... +def create_ocsp_response( + status: ocsp.OCSPResponseStatus, + builder: ocsp.OCSPResponseBuilder | None, + private_key: PrivateKeyTypes | None, + hash_algorithm: hashes.HashAlgorithm | None, +) -> ocsp.OCSPResponse: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi new file mode 100644 index 0000000..e41ba78 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -0,0 +1,82 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.bindings._rust.openssl import ( + aead, + ciphers, + cmac, + dh, + dsa, + ec, + ed448, + ed25519, + hashes, + hmac, + hpke, + kdf, + keys, + keywrap, + mldsa, + mlkem, + poly1305, + rsa, + x448, + x25519, +) + +__all__ = [ + "aead", + "ciphers", + "cmac", + "dh", + "dsa", + "ec", + "ed448", + "ed25519", + "hashes", + "hmac", + "hpke", + "kdf", + "keys", + "keywrap", + "mldsa", + "mlkem", + "openssl_version", + "openssl_version_text", + "poly1305", + "raise_openssl_error", + "rsa", + "x448", + "x25519", +] + +CRYPTOGRAPHY_IS_LIBRESSL: bool +CRYPTOGRAPHY_IS_BORINGSSL: bool +CRYPTOGRAPHY_IS_AWSLC: bool +CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_330_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_350_OR_GREATER: bool + +class Providers: ... + +_legacy_provider_loaded: bool +_providers: Providers + +def openssl_version() -> int: ... +def openssl_version_text() -> str: ... +def raise_openssl_error() -> typing.NoReturn: ... +def capture_error_stack() -> list[OpenSSLError]: ... +def is_fips_enabled() -> bool: ... +def enable_fips(providers: Providers) -> None: ... + +class OpenSSLError: + @property + def lib(self) -> int: ... + @property + def reason(self) -> int: ... + @property + def reason_text(self) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi new file mode 100644 index 0000000..eb44608 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -0,0 +1,189 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Sequence + +from cryptography.utils import Buffer + +class AESGCM: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class ChaCha20Poly1305: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESCCM: + def __init__(self, key: Buffer, tag_length: int = 16) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + def encrypt_into( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + def decrypt_into( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + buf: Buffer, + ) -> int: ... + +class AESOCB3: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESGCMSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi new file mode 100644 index 0000000..a48fb01 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADEncryptionContext: ... +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADDecryptionContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +def cipher_supported( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> bool: ... +def _advance( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... +def _advance_aad( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... + +class CipherContext: ... +class AEADEncryptionContext: ... +class AEADDecryptionContext: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi new file mode 100644 index 0000000..9c03508 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers + +class CMAC: + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> CMAC: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi new file mode 100644 index 0000000..08733d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -0,0 +1,51 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import dh + +MIN_MODULUS_SIZE: int + +class DHPrivateKey: ... +class DHPublicKey: ... +class DHParameters: ... + +class DHPrivateNumbers: + def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ... + def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DHPublicNumbers: ... + +class DHPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DHParameterNumbers + ) -> None: ... + def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DHParameterNumbers: ... + +class DHParameterNumbers: + def __init__(self, p: int, g: int, q: int | None = None) -> None: ... + def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ... + @property + def p(self) -> int: ... + @property + def g(self) -> int: ... + @property + def q(self) -> int | None: ... + +def generate_parameters( + generator: int, key_size: int, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_pem_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_der_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi new file mode 100644 index 0000000..0922a4c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -0,0 +1,41 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import dsa + +class DSAPrivateKey: ... +class DSAPublicKey: ... +class DSAParameters: ... + +class DSAPrivateNumbers: + def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DSAPublicNumbers: ... + def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ... + +class DSAPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DSAParameterNumbers + ) -> None: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DSAParameterNumbers: ... + def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ... + +class DSAParameterNumbers: + def __init__(self, p: int, q: int, g: int) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def g(self) -> int: ... + def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ... + +def generate_parameters(key_size: int) -> dsa.DSAParameters: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi new file mode 100644 index 0000000..5c3b7bf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import ec + +class ECPrivateKey: ... +class ECPublicKey: ... + +class EllipticCurvePrivateNumbers: + def __init__( + self, private_value: int, public_numbers: EllipticCurvePublicNumbers + ) -> None: ... + def private_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePrivateKey: ... + @property + def private_value(self) -> int: ... + @property + def public_numbers(self) -> EllipticCurvePublicNumbers: ... + +class EllipticCurvePublicNumbers: + def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ... + def public_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePublicKey: ... + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @property + def curve(self) -> ec.EllipticCurve: ... + def __eq__(self, other: object) -> bool: ... + +def curve_supported(curve: ec.EllipticCurve) -> bool: ... +def generate_private_key( + curve: ec.EllipticCurve, backend: typing.Any = None +) -> ec.EllipticCurvePrivateKey: ... +def from_private_numbers( + numbers: ec.EllipticCurvePrivateNumbers, +) -> ec.EllipticCurvePrivateKey: ... +def from_public_numbers( + numbers: ec.EllipticCurvePublicNumbers, +) -> ec.EllipticCurvePublicKey: ... +def from_public_bytes( + curve: ec.EllipticCurve, data: bytes +) -> ec.EllipticCurvePublicKey: ... +def derive_private_key( + private_value: int, curve: ec.EllipticCurve +) -> ec.EllipticCurvePrivateKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi new file mode 100644 index 0000000..f85b3d1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.utils import Buffer + +class Ed25519PrivateKey: ... +class Ed25519PublicKey: ... + +def generate_key() -> ed25519.Ed25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed25519.Ed25519PrivateKey: ... +def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi new file mode 100644 index 0000000..c8ca0ec --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ed448 +from cryptography.utils import Buffer + +class Ed448PrivateKey: ... +class Ed448PublicKey: ... + +def generate_key() -> ed448.Ed448PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed448.Ed448PrivateKey: ... +def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi new file mode 100644 index 0000000..106b531 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer + +class Hash(hashes.HashContext): + def __init__( + self, algorithm: hashes.HashAlgorithm, backend: typing.Any = None + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def copy(self) -> Hash: ... + @staticmethod + def hash(algorithm: hashes.HashAlgorithm, data: Buffer) -> bytes: ... + +def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ... + +class XOFHash: + def __init__(self, algorithm: hashes.ExtendableOutputFunction) -> None: ... + @property + def algorithm(self) -> hashes.ExtendableOutputFunction: ... + def update(self, data: Buffer) -> None: ... + def squeeze(self, length: int) -> bytes: ... + def copy(self) -> XOFHash: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi new file mode 100644 index 0000000..3883d1b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -0,0 +1,22 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer + +class HMAC(hashes.HashContext): + def __init__( + self, + key: Buffer, + algorithm: hashes.HashAlgorithm, + backend: typing.Any = None, + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> HMAC: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi new file mode 100644 index 0000000..7ea1711 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi @@ -0,0 +1,109 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ec, mlkem, x25519 +from cryptography.utils import Buffer + +class KEM: + X25519: KEM + P256: KEM + P384: KEM + P521: KEM + MLKEM768: KEM + MLKEM1024: KEM + MLKEM768_X25519: KEM + MLKEM1024_P384: KEM + def enc_length(self) -> int: ... + +class KDF: + HKDF_SHA256: KDF + HKDF_SHA384: KDF + HKDF_SHA512: KDF + SHAKE128: KDF + SHAKE256: KDF + +class AEAD: + AES_128_GCM: AEAD + AES_256_GCM: AEAD + CHACHA20_POLY1305: AEAD + +class MLKEM768X25519PrivateKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM768PrivateKey, + x25519_key: x25519.X25519PrivateKey, + ) -> None: ... + def public_key(self) -> MLKEM768X25519PublicKey: ... + +class MLKEM768X25519PublicKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM768PublicKey, + x25519_key: x25519.X25519PublicKey, + ) -> None: ... + +class MLKEM1024P384PrivateKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM1024PrivateKey, + p384_key: ec.EllipticCurvePrivateKey, + ) -> None: ... + def public_key(self) -> MLKEM1024P384PublicKey: ... + +class MLKEM1024P384PublicKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM1024PublicKey, + p384_key: ec.EllipticCurvePublicKey, + ) -> None: ... + +class Suite: + def __init__(self, kem: KEM, kdf: KDF, aead: AEAD) -> None: ... + def encrypt( + self, + plaintext: Buffer, + public_key: x25519.X25519PublicKey + | ec.EllipticCurvePublicKey + | mlkem.MLKEM768PublicKey + | mlkem.MLKEM1024PublicKey + | MLKEM768X25519PublicKey + | MLKEM1024P384PublicKey, + info: Buffer | None = None, + ) -> bytes: ... + def decrypt( + self, + ciphertext: Buffer, + private_key: x25519.X25519PrivateKey + | ec.EllipticCurvePrivateKey + | mlkem.MLKEM768PrivateKey + | mlkem.MLKEM1024PrivateKey + | MLKEM768X25519PrivateKey + | MLKEM1024P384PrivateKey, + info: Buffer | None = None, + ) -> bytes: ... + +def _encrypt_with_aad( + suite: Suite, + plaintext: Buffer, + public_key: x25519.X25519PublicKey + | ec.EllipticCurvePublicKey + | mlkem.MLKEM768PublicKey + | mlkem.MLKEM1024PublicKey + | MLKEM768X25519PublicKey + | MLKEM1024P384PublicKey, + info: Buffer | None = None, + aad: Buffer | None = None, +) -> bytes: ... +def _decrypt_with_aad( + suite: Suite, + ciphertext: Buffer, + private_key: x25519.X25519PrivateKey + | ec.EllipticCurvePrivateKey + | mlkem.MLKEM768PrivateKey + | mlkem.MLKEM1024PrivateKey + | MLKEM768X25519PrivateKey + | MLKEM1024P384PrivateKey, + info: Buffer | None = None, + aad: Buffer | None = None, +) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi new file mode 100644 index 0000000..bb37b96 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.hashes import HashAlgorithm +from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, Mode +from cryptography.utils import Buffer + +class PBKDF2HMAC: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes, + iterations: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Scrypt: + def __init__( + self, + salt: bytes, + length: int, + n: int, + r: int, + p: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Argon2d: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class Argon2i: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class Argon2id: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class HKDF: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes | None, + info: bytes | None, + backend: typing.Any = None, + ): ... + @staticmethod + def extract( + algorithm: HashAlgorithm, salt: bytes | None, key_material: Buffer + ) -> bytes: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class HKDFExpand: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + info: bytes | None, + backend: typing.Any = None, + ): ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class X963KDF: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + sharedinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class ConcatKDFHash: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + otherinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class ConcatKDFHMAC: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes | None, + otherinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class KBKDFHMAC: + def __init__( + self, + algorithm: HashAlgorithm, + mode: Mode, + length: int, + rlen: int, + llen: int | None, + location: CounterLocation, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, + backend: typing.Any = None, + *, + break_location: int | None = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class KBKDFCMAC: + def __init__( + self, + algorithm: typing.Any, + mode: Mode, + length: int, + rlen: int, + llen: int | None, + location: CounterLocation, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, + backend: typing.Any = None, + *, + break_location: int | None = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi new file mode 100644 index 0000000..404057e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) +from cryptography.utils import Buffer + +def load_der_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_pem_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_der_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... +def load_pem_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi new file mode 100644 index 0000000..dd445ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi @@ -0,0 +1,28 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.utils import Buffer + +def aes_key_wrap( + wrapping_key: Buffer, + key_to_wrap: Buffer, + backend: typing.Any = None, +) -> bytes: ... +def aes_key_unwrap( + wrapping_key: Buffer, + wrapped_key: Buffer, + backend: typing.Any = None, +) -> bytes: ... +def aes_key_wrap_with_padding( + wrapping_key: Buffer, + key_to_wrap: Buffer, + backend: typing.Any = None, +) -> bytes: ... +def aes_key_unwrap_with_padding( + wrapping_key: Buffer, + wrapped_key: Buffer, + backend: typing.Any = None, +) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi new file mode 100644 index 0000000..cea9cb1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi @@ -0,0 +1,35 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import mldsa +from cryptography.utils import Buffer + +class MLDSA44PrivateKey: ... +class MLDSA44PublicKey: ... +class MLDSA65PrivateKey: ... +class MLDSA65PublicKey: ... +class MLDSA87PrivateKey: ... +class MLDSA87PublicKey: ... + +class MLDSAMuHasher: + def __init__( + self, + public_key: mldsa.MLDSA44PublicKey + | mldsa.MLDSA65PublicKey + | mldsa.MLDSA87PublicKey, + context: Buffer | None = None, + ) -> None: ... + def update(self, data: Buffer) -> None: ... + def copy(self) -> MLDSAMuHasher: ... + def finalize(self) -> bytes: ... + +def generate_mldsa44_key() -> mldsa.MLDSA44PrivateKey: ... +def from_mldsa44_public_bytes(data: bytes) -> mldsa.MLDSA44PublicKey: ... +def from_mldsa44_seed_bytes(data: Buffer) -> mldsa.MLDSA44PrivateKey: ... +def generate_mldsa65_key() -> mldsa.MLDSA65PrivateKey: ... +def from_mldsa65_public_bytes(data: bytes) -> mldsa.MLDSA65PublicKey: ... +def from_mldsa65_seed_bytes(data: Buffer) -> mldsa.MLDSA65PrivateKey: ... +def generate_mldsa87_key() -> mldsa.MLDSA87PrivateKey: ... +def from_mldsa87_public_bytes(data: bytes) -> mldsa.MLDSA87PublicKey: ... +def from_mldsa87_seed_bytes(data: Buffer) -> mldsa.MLDSA87PrivateKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi new file mode 100644 index 0000000..768a340 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import mlkem +from cryptography.utils import Buffer + +class MLKEM768PrivateKey: ... +class MLKEM768PublicKey: ... +class MLKEM1024PrivateKey: ... +class MLKEM1024PublicKey: ... + +def generate_mlkem768_key() -> mlkem.MLKEM768PrivateKey: ... +def from_mlkem768_seed_bytes(data: Buffer) -> mlkem.MLKEM768PrivateKey: ... +def from_mlkem768_public_bytes(data: Buffer) -> mlkem.MLKEM768PublicKey: ... +def generate_mlkem1024_key() -> mlkem.MLKEM1024PrivateKey: ... +def from_mlkem1024_seed_bytes(data: Buffer) -> mlkem.MLKEM1024PrivateKey: ... +def from_mlkem1024_public_bytes(data: Buffer) -> mlkem.MLKEM1024PublicKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi new file mode 100644 index 0000000..45a2a39 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.utils import Buffer + +class Poly1305: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_tag(key: Buffer, data: Buffer) -> bytes: ... + @staticmethod + def verify_tag(key: Buffer, data: Buffer, tag: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, tag: bytes) -> None: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi new file mode 100644 index 0000000..ef7752d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi @@ -0,0 +1,55 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import rsa + +class RSAPrivateKey: ... +class RSAPublicKey: ... + +class RSAPrivateNumbers: + def __init__( + self, + p: int, + q: int, + d: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_numbers: RSAPublicNumbers, + ) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def d(self) -> int: ... + @property + def dmp1(self) -> int: ... + @property + def dmq1(self) -> int: ... + @property + def iqmp(self) -> int: ... + @property + def public_numbers(self) -> RSAPublicNumbers: ... + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> rsa.RSAPrivateKey: ... + +class RSAPublicNumbers: + def __init__(self, e: int, n: int) -> None: ... + @property + def n(self) -> int: ... + @property + def e(self) -> int: ... + def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ... + +def generate_private_key( + public_exponent: int, + key_size: int, +) -> rsa.RSAPrivateKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi new file mode 100644 index 0000000..38d2add --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.utils import Buffer + +class X25519PrivateKey: ... +class X25519PublicKey: ... + +def generate_key() -> x25519.X25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> x25519.X25519PrivateKey: ... +def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi new file mode 100644 index 0000000..3ac0980 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import x448 +from cryptography.utils import Buffer + +class X448PrivateKey: ... +class X448PublicKey: ... + +def generate_key() -> x448.X448PrivateKey: ... +def from_private_bytes(data: Buffer) -> x448.X448PrivateKey: ... +def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi new file mode 100644 index 0000000..b25becb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.serialization import ( + KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PKCS12KeyAndCertificates, + PKCS12PrivateKeyTypes, +) +from cryptography.utils import Buffer + +class PKCS12Certificate: + def __init__( + self, cert: x509.Certificate, friendly_name: bytes | None + ) -> None: ... + @property + def friendly_name(self) -> bytes | None: ... + @property + def certificate(self) -> x509.Certificate: ... + +def load_key_and_certificates( + data: Buffer, + password: Buffer | None, + backend: typing.Any = None, +) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], +]: ... +def load_pkcs12( + data: bytes, + password: bytes | None, + backend: typing.Any = None, +) -> PKCS12KeyAndCertificates: ... +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[x509.Certificate | PKCS12Certificate] | None, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi new file mode 100644 index 0000000..358b135 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -0,0 +1,50 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import pkcs7 + +def serialize_certificates( + certs: list[x509.Certificate], + encoding: serialization.Encoding, +) -> bytes: ... +def encrypt_and_serialize( + builder: pkcs7.PKCS7EnvelopeBuilder, + content_encryption_algorithm: pkcs7.ContentEncryptionAlgorithm, + encoding: serialization.Encoding, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def sign_and_serialize( + builder: pkcs7.PKCS7SignatureBuilder, + encoding: serialization.Encoding, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_der( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_pem( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_smime( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def load_pem_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_der_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi new file mode 100644 index 0000000..c6c6d0b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.utils import Buffer + +class TestCertificate: + not_after_tag: int + not_before_tag: int + issuer_value_tags: list[int] + subject_value_tags: list[int] + +def test_parse_certificate(data: bytes) -> TestCertificate: ... +def pkcs7_verify( + encoding: serialization.Encoding, + sig: bytes, + msg: Buffer | None, + certs: list[x509.Certificate], + options: list[pkcs7.PKCS7Options], +) -> None: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi new file mode 100644 index 0000000..4438cc7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi @@ -0,0 +1,313 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +import typing +from collections.abc import Iterator + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.ec import ECDSA +from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, + PrivateKeyTypes, +) +from cryptography.x509 import certificate_transparency + +def load_pem_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_der_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_pem_x509_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_pem_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_der_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_pem_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def load_der_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def encode_name_bytes(name: x509.Name) -> bytes: ... +def parse_name_bytes(data: bytes) -> x509.Name: ... +def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... +def create_x509_certificate( + builder: x509.CertificateBuilder, + private_key: PrivateKeyTypes | None, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.Certificate: ... +def create_x509_csr( + builder: x509.CertificateSigningRequestBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.CertificateSigningRequest: ... +def create_revoked_certificate( + builder: x509.RevokedCertificateBuilder, +) -> x509.RevokedCertificate: ... +def create_x509_crl( + builder: x509.CertificateRevocationListBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.CertificateRevocationList: ... + +class Sct: + @property + def version(self) -> certificate_transparency.Version: ... + @property + def log_id(self) -> bytes: ... + @property + def timestamp(self) -> datetime.datetime: ... + @property + def entry_type(self) -> certificate_transparency.LogEntryType: ... + @property + def signature_hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def signature_algorithm( + self, + ) -> certificate_transparency.SignatureAlgorithm: ... + @property + def signature(self) -> bytes: ... + @property + def extension_bytes(self) -> bytes: ... + +class Certificate: + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + @property + def serial_number(self) -> int: ... + @property + def version(self) -> x509.Version: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def public_key_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def not_valid_before(self) -> datetime.datetime: ... + @property + def not_valid_before_utc(self) -> datetime.datetime: ... + @property + def not_valid_after(self) -> datetime.datetime: ... + @property + def not_valid_after_utc(self) -> datetime.datetime: ... + @property + def issuer(self) -> x509.Name: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certificate_bytes(self) -> bytes: ... + @property + def tbs_precertificate_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def verify_directly_issued_by(self, issuer: Certificate) -> None: ... + +class RevokedCertificate: + @property + def serial_number(self) -> int: ... + @property + def revocation_date(self) -> datetime.datetime: ... + @property + def revocation_date_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + +class CertificateRevocationList: + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + def get_revoked_certificate_by_serial_number( + self, serial_number: int + ) -> x509.RevokedCertificate | None: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def issuer(self) -> x509.Name: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def last_update(self) -> datetime.datetime: ... + @property + def last_update_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certlist_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __len__(self) -> int: ... + @typing.overload + def __getitem__(self, idx: int) -> x509.RevokedCertificate: ... + @typing.overload + def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ... + def __iter__(self) -> Iterator[x509.RevokedCertificate]: ... + def is_signature_valid( + self, public_key: CertificateIssuerPublicKeyTypes + ) -> bool: ... + +class CertificateSigningRequest: + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def attributes(self) -> x509.Attributes: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certrequest_bytes(self) -> bytes: ... + @property + def is_signature_valid(self) -> bool: ... + +class PolicyBuilder: + def time(self, time: datetime.datetime) -> PolicyBuilder: ... + def store(self, store: Store) -> PolicyBuilder: ... + def max_chain_depth(self, max_chain_depth: int) -> PolicyBuilder: ... + def extension_policies( + self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy + ) -> PolicyBuilder: ... + def build_client_verifier(self) -> ClientVerifier: ... + def build_server_verifier( + self, subject: x509.verification.Subject + ) -> ServerVerifier: ... + +class Policy: + @property + def max_chain_depth(self) -> int: ... + @property + def subject(self) -> x509.verification.Subject | None: ... + @property + def validation_time(self) -> datetime.datetime: ... + @property + def extended_key_usage(self) -> x509.ObjectIdentifier: ... + @property + def minimum_rsa_modulus(self) -> int: ... + +class Criticality: + CRITICAL: Criticality + AGNOSTIC: Criticality + NON_CRITICAL: Criticality + +T = typing.TypeVar("T", contravariant=True, bound=x509.ExtensionType) + +MaybeExtensionValidatorCallback = typing.Callable[ + [ + Policy, + x509.Certificate, + T | None, + ], + None, +] + +PresentExtensionValidatorCallback = typing.Callable[ + [Policy, x509.Certificate, T], + None, +] + +class ExtensionPolicy: + @staticmethod + def permit_all() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ca() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ee() -> ExtensionPolicy: ... + def require_not_present( + self, extension_type: type[x509.ExtensionType] + ) -> ExtensionPolicy: ... + def may_be_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: MaybeExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + def require_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: PresentExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + +class VerifiedClient: + @property + def subjects(self) -> list[x509.GeneralName] | None: ... + @property + def chain(self) -> list[x509.Certificate]: ... + +class ClientVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> VerifiedClient: ... + +class ServerVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> list[x509.Certificate]: ... + +class Store: + def __init__(self, certs: list[x509.Certificate]) -> None: ... + +class VerificationError(Exception): ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py new file mode 100644 index 0000000..1e447a5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py @@ -0,0 +1,199 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + + +def cryptography_has_set_cert_cb() -> list[str]: + return [ + "SSL_CTX_set_cert_cb", + "SSL_set_cert_cb", + ] + + +def cryptography_has_ssl_st() -> list[str]: + return [ + "SSL_ST_BEFORE", + "SSL_ST_OK", + "SSL_ST_INIT", + "SSL_ST_RENEGOTIATE", + ] + + +def cryptography_has_tls_st() -> list[str]: + return [ + "TLS_ST_BEFORE", + "TLS_ST_OK", + ] + + +def cryptography_has_ssl_sigalgs() -> list[str]: + return [ + "SSL_CTX_set1_sigalgs_list", + ] + + +def cryptography_has_psk() -> list[str]: + return [ + "SSL_CTX_use_psk_identity_hint", + "SSL_CTX_set_psk_server_callback", + "SSL_CTX_set_psk_client_callback", + ] + + +def cryptography_has_psk_tlsv13() -> list[str]: + return [ + "SSL_CTX_set_psk_find_session_callback", + "SSL_CTX_set_psk_use_session_callback", + "Cryptography_SSL_SESSION_new", + "SSL_CIPHER_find", + "SSL_SESSION_set1_master_key", + "SSL_SESSION_set_cipher", + "SSL_SESSION_set_protocol_version", + ] + + +def cryptography_has_custom_ext() -> list[str]: + return [ + "SSL_CTX_add_client_custom_ext", + "SSL_CTX_add_server_custom_ext", + "SSL_extension_supported", + ] + + +def cryptography_has_tlsv13_functions() -> list[str]: + return [ + "SSL_CTX_set_ciphersuites", + ] + + +def cryptography_has_tlsv13_hs_functions() -> list[str]: + return [ + "SSL_VERIFY_POST_HANDSHAKE", + "SSL_verify_client_post_handshake", + "SSL_CTX_set_post_handshake_auth", + "SSL_set_post_handshake_auth", + "SSL_SESSION_get_max_early_data", + "SSL_write_early_data", + "SSL_read_early_data", + "SSL_CTX_set_max_early_data", + ] + + +def cryptography_has_ssl_verify_client_post_handshake() -> list[str]: + return [ + "SSL_verify_client_post_handshake", + ] + + +def cryptography_has_engine() -> list[str]: + return [ + "ENGINE_by_id", + "ENGINE_init", + "ENGINE_finish", + "ENGINE_get_default_RAND", + "ENGINE_set_default_RAND", + "ENGINE_unregister_RAND", + "ENGINE_ctrl_cmd", + "ENGINE_free", + "ENGINE_get_name", + "ENGINE_ctrl_cmd_string", + "ENGINE_load_builtin_engines", + "ENGINE_load_private_key", + "ENGINE_load_public_key", + "SSL_CTX_set_client_cert_engine", + ] + + +def cryptography_has_verified_chain() -> list[str]: + return [ + "SSL_get0_verified_chain", + ] + + +def cryptography_has_srtp() -> list[str]: + return [ + "SSL_CTX_set_tlsext_use_srtp", + "SSL_set_tlsext_use_srtp", + "SSL_get_selected_srtp_profile", + ] + + +def cryptography_has_dtls_get_data_mtu() -> list[str]: + return [ + "DTLS_get_data_mtu", + ] + + +def cryptography_has_ssl_cookie() -> list[str]: + return [ + "SSL_OP_COOKIE_EXCHANGE", + "DTLS1_COOKIE_LENGTH", + "DTLSv1_listen", + "SSL_CTX_set_cookie_generate_cb", + "SSL_CTX_set_cookie_verify_cb", + ] + + +def cryptography_has_prime_checks() -> list[str]: + return [ + "BN_prime_checks_for_size", + ] + + +def cryptography_has_unexpected_eof_while_reading() -> list[str]: + return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] + + +def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: + return [ + "SSL_OP_IGNORE_UNEXPECTED_EOF", + ] + + +def cryptography_has_get_extms_support() -> list[str]: + return ["SSL_get_extms_support"] + + +def cryptography_has_ssl_get0_group_name() -> list[str]: + return ["SSL_get0_group_name"] + + +# This is a mapping of +# {condition: function-returning-names-dependent-on-that-condition} so we can +# loop over them and delete unsupported names at runtime. It will be removed +# when cffi supports #if in cdef. We use functions instead of just a dict of +# lists so we can use coverage to measure which are used. +CONDITIONAL_NAMES = { + "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, + "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, + "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, + "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, + "Cryptography_HAS_PSK": cryptography_has_psk, + "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, + "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, + "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, + "Cryptography_HAS_TLSv1_3_HS_FUNCTIONS": ( + cryptography_has_tlsv13_hs_functions + ), + "Cryptography_HAS_SSL_VERIFY_CLIENT_POST_HANDSHAKE": ( + cryptography_has_ssl_verify_client_post_handshake + ), + "Cryptography_HAS_ENGINE": cryptography_has_engine, + "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, + "Cryptography_HAS_SRTP": cryptography_has_srtp, + "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, + "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, + "Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks, + "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( + cryptography_has_unexpected_eof_while_reading + ), + "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( + cryptography_has_ssl_op_ignore_unexpected_eof + ), + "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, + "Cryptography_HAS_SSL_GET0_GROUP_NAME": ( + cryptography_has_ssl_get0_group_name + ), +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py new file mode 100644 index 0000000..6e13df1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py @@ -0,0 +1,107 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import threading +import types +import typing +from collections.abc import Callable, Mapping + +import cryptography +from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings._rust import _openssl, openssl +from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES + + +def _openssl_assert(ok: bool) -> None: + if not ok: + errors = openssl.capture_error_stack() + + raise InternalError( + "Unknown OpenSSL error. This error is commonly encountered when " + "another library is not cleaning up the OpenSSL error stack. If " + "you are using cryptography with another library that uses " + "OpenSSL try disabling it before reporting a bug. Otherwise " + "please file an issue at https://github.com/pyca/cryptography/" + "issues with information on how to reproduce " + f"this. ({errors!r})", + errors, + ) + + +def build_conditional_library( + lib: typing.Any, + conditional_names: Mapping[str, Callable[[], list[str]]], +) -> typing.Any: + conditional_lib = types.ModuleType("lib") + conditional_lib._original_lib = lib # type: ignore[attr-defined] + excluded_names = set() + for condition, names_cb in conditional_names.items(): + if not getattr(lib, condition): + excluded_names.update(names_cb()) + + for attr in dir(lib): + if attr not in excluded_names: + setattr(conditional_lib, attr, getattr(lib, attr)) + + return conditional_lib + + +class Binding: + """ + OpenSSL API wrapper. + """ + + lib: typing.ClassVar[typing.Any] = None + ffi: typing.Any = _openssl.ffi + _lib_loaded = False + _init_lock = threading.Lock() + + def __init__(self) -> None: + self._ensure_ffi_initialized() + + @classmethod + def _ensure_ffi_initialized(cls) -> None: + with cls._init_lock: + if not cls._lib_loaded: + cls.lib = build_conditional_library( + _openssl.lib, CONDITIONAL_NAMES + ) + cls._lib_loaded = True + + @classmethod + def init_static_locks(cls) -> None: + cls._ensure_ffi_initialized() + + +def _verify_package_version(version: str) -> None: + # Occasionally we run into situations where the version of the Python + # package does not match the version of the shared object that is loaded. + # This may occur in environments where multiple versions of cryptography + # are installed and available in the python path. To avoid errors cropping + # up later this code checks that the currently imported package and the + # shared object that were loaded have the same version and raise an + # ImportError if they do not + so_package_version = _openssl.ffi.string( + _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION + ) + if version.encode("ascii") != so_package_version: + raise ImportError( + "The version of cryptography does not match the loaded " + "shared object. This can happen if you have multiple copies of " + "cryptography installed in your Python path. Please try creating " + "a new virtual environment to resolve this issue. " + f"Loaded python version: {version}, " + f"shared object version: {so_package_version}" + ) + + _openssl_assert( + _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), + ) + + +_verify_package_version(cryptography.__version__) + +Binding.init_static_locks() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py new file mode 100644 index 0000000..72dad04 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py @@ -0,0 +1,145 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import warnings + +from cryptography import utils +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, + _verify_key_size, +) + + +class ARC4(CipherAlgorithm): + name = "RC4" + key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class TripleDES(BlockCipherAlgorithm): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key: bytes): + # Check the key type before the length-based deprecation warnings so + # an invalid key type doesn't trigger a spurious warning. + utils._check_byteslike("key", key) + if len(key) == 8: + warnings.warn( + "Single-key TripleDES (8-byte keys) is deprecated and " + "support will be removed in a future release. Use 24-byte " + "keys instead (e.g., key + key + key).", + utils.DeprecatedIn47, + stacklevel=2, + ) + key = key + key + key + elif len(key) == 16: + warnings.warn( + "Two-key TripleDES (16-byte keys) is deprecated and " + "support will be removed in a future release. Use 24-byte " + "keys instead (e.g., key + key[:8]).", + utils.DeprecatedIn47, + stacklevel=2, + ) + key = key + key[:8] + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# Not actually supported, marker for tests +class _DES: + key_size = 64 + + +class Blowfish(BlockCipherAlgorithm): + name = "Blowfish" + block_size = 64 + key_sizes = frozenset(range(32, 449, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class CAST5(BlockCipherAlgorithm): + name = "CAST5" + block_size = 64 + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SEED(BlockCipherAlgorithm): + name = "SEED" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class IDEA(BlockCipherAlgorithm): + name = "IDEA" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class Camellia(BlockCipherAlgorithm): + name = "camellia" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# This class only allows RC2 with a 128-bit key. No support for +# effective key bits or other key sizes is provided. +class RC2(BlockCipherAlgorithm): + name = "RC2" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py new file mode 100644 index 0000000..1786bb0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py @@ -0,0 +1,53 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.primitives._modes import ( + ModeWithInitializationVector, + _check_iv_and_key_length, +) + + +class OFB(ModeWithInitializationVector): + name = "OFB" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class CFB(ModeWithInitializationVector): + name = "CFB" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class CFB8(ModeWithInitializationVector): + name = "CFB8" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py new file mode 100644 index 0000000..ea55ffd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py @@ -0,0 +1,19 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +# This exists to break an import cycle. It is normally accessible from the +# asymmetric padding module. + + +class AsymmetricPadding(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this padding (e.g. "PSS", "PKCS1"). + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py new file mode 100644 index 0000000..305a9fd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -0,0 +1,60 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils + +# This exists to break an import cycle. It is normally accessible from the +# ciphers module. + + +class CipherAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this mode (e.g. "AES", "Camellia"). + """ + + @property + @abc.abstractmethod + def key_sizes(self) -> frozenset[int]: + """ + Valid key sizes for this algorithm in bits + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The size of the key being used as an integer in bits (e.g. 128, 256). + """ + + +class BlockCipherAlgorithm(CipherAlgorithm): + key: utils.Buffer + + @property + @abc.abstractmethod + def block_size(self) -> int: + """ + The size of a block as an integer in bits (e.g. 64, 128). + """ + + +def _verify_key_size( + algorithm: CipherAlgorithm, key: utils.Buffer +) -> utils.Buffer: + # Verify that the key is instance of bytes + utils._check_byteslike("key", key) + + # Verify that the key size matches the expected key size + if len(key) * 8 not in algorithm.key_sizes: + raise ValueError( + f"Invalid key size ({len(key) * 8}) for {algorithm.name}." + ) + return key diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py new file mode 100644 index 0000000..deae8bc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py @@ -0,0 +1,105 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) + + +class Mode(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this mode (e.g. "ECB", "CBC"). + """ + + @abc.abstractmethod + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + """ + Checks that all the necessary invariants of this (mode, algorithm) + combination are met. + """ + + +class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def initialization_vector(self) -> utils.Buffer: + """ + The value of the initialization vector for this mode as bytes. + """ + + +class ModeWithTweak(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tweak(self) -> utils.Buffer: + """ + The value of the tweak for this mode as bytes. + """ + + +class ModeWithNonce(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def nonce(self) -> utils.Buffer: + """ + The value of the nonce for this mode as bytes. + """ + + +class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> bytes | None: + """ + The value of the tag supplied to the constructor of this mode. + """ + + +def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: + if algorithm.key_size > 256 and algorithm.name == "AES": + raise ValueError( + "Only 128, 192, and 256 bit keys are allowed for this AES mode" + ) + + +def _check_iv_length( + self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm +) -> None: + iv_len = len(self.initialization_vector) + if iv_len * 8 != algorithm.block_size: + raise ValueError(f"Invalid IV size ({iv_len}) for {self.name}.") + + +def _check_nonce_length( + nonce: utils.Buffer, name: str, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{name} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + if len(nonce) * 8 != algorithm.block_size: + raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.") + + +def _check_iv_and_key_length( + self: ModeWithInitializationVector, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{self} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + _check_aes_key_length(self, algorithm) + _check_iv_length(self, algorithm) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py new file mode 100644 index 0000000..9c3c474 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.hazmat.bindings._rust import Encoding as Encoding +from cryptography.hazmat.bindings._rust import ( + ParameterFormat as ParameterFormat, +) +from cryptography.hazmat.bindings._rust import PrivateFormat as PrivateFormat +from cryptography.hazmat.bindings._rust import PublicFormat as PublicFormat +from cryptography.hazmat.primitives.hashes import HashAlgorithm + +# This exists to break an import cycle. These classes are normally accessible +# from the serialization module. + + +class PBES(utils.Enum): + PBESv1SHA1And3KeyTripleDESCBC = "PBESv1 using SHA1 and 3-Key TripleDES" + PBESv2SHA256AndAES256CBC = "PBESv2 using SHA256 PBKDF2 and AES256 CBC" + + +class KeySerializationEncryption(metaclass=abc.ABCMeta): + pass + + +class BestAvailableEncryption(KeySerializationEncryption): + def __init__(self, password: bytes): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +class NoEncryption(KeySerializationEncryption): + pass + + +class KeySerializationEncryptionBuilder: + def __init__( + self, + format: PrivateFormat, + *, + _kdf_rounds: int | None = None, + _hmac_hash: HashAlgorithm | None = None, + _key_cert_algorithm: PBES | None = None, + ) -> None: + self._format = format + + self._kdf_rounds = _kdf_rounds + self._hmac_hash = _hmac_hash + self._key_cert_algorithm = _key_cert_algorithm + + def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder: + if self._kdf_rounds is not None: + raise ValueError("kdf_rounds already set") + + if not isinstance(rounds, int): + raise TypeError("kdf_rounds must be an integer") + + if rounds < 1: + raise ValueError("kdf_rounds must be a positive integer") + + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def hmac_hash( + self, algorithm: HashAlgorithm + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "hmac_hash only supported with PrivateFormat.PKCS12" + ) + + if self._hmac_hash is not None: + raise ValueError("hmac_hash already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=algorithm, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def key_cert_algorithm( + self, algorithm: PBES + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "key_cert_algorithm only supported with PrivateFormat.PKCS12" + ) + if self._key_cert_algorithm is not None: + raise ValueError("key_cert_algorithm already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=algorithm, + ) + + def build(self, password: bytes) -> KeySerializationEncryption: + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + return _KeySerializationEncryption( + self._format, + password, + kdf_rounds=self._kdf_rounds, + hmac_hash=self._hmac_hash, + key_cert_algorithm=self._key_cert_algorithm, + ) + + +class _KeySerializationEncryption(KeySerializationEncryption): + def __init__( + self, + format: PrivateFormat, + password: bytes, + *, + kdf_rounds: int | None, + hmac_hash: HashAlgorithm | None, + key_cert_algorithm: PBES | None, + ): + self._format = format + self.password = password + + self._kdf_rounds = kdf_rounds + self._hmac_hash = hmac_hash + self._key_cert_algorithm = key_cert_algorithm diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py new file mode 100644 index 0000000..814fdd3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py @@ -0,0 +1,253 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization + +_FFDH_DEPRECATION_MSG = ( + "Diffie-Hellman over finite fields (FFDH) is deprecated and support " + "will be removed in a future release. Use a more modern key exchange " + "algorithm." +) + +generate_parameters = rust_openssl.dh.generate_parameters + + +DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers +DHPublicNumbers = rust_openssl.dh.DHPublicNumbers +DHParameterNumbers = rust_openssl.dh.DHParameterNumbers + + +class DHParameters(metaclass=abc.ABCMeta): + @abc.abstractmethod + def generate_private_key(self) -> DHPrivateKey: + """ + Generates and returns a DHPrivateKey. + """ + + @abc.abstractmethod + def parameter_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.ParameterFormat, + ) -> bytes: + """ + Returns the parameters serialized as bytes. + """ + + @abc.abstractmethod + def parameter_numbers(self) -> DHParameterNumbers: + """ + Returns a DHParameterNumbers. + """ + + +DHParametersWithSerialization = DHParameters +DHParameters.register(rust_openssl.dh.DHParameters) + + +class DHPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self) -> DHParameters: + """ + The DHParameters object associated with this public key. + """ + + @abc.abstractmethod + def public_numbers(self) -> DHPublicNumbers: + """ + Returns a DHPublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> DHPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DHPublicKey: + """ + Returns a deep copy. + """ + + +DHPublicKeyWithSerialization = DHPublicKey +DHPublicKey.register(rust_openssl.dh.DHPublicKey) + + +class DHPrivateKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self) -> DHPublicKey: + """ + The DHPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self) -> DHParameters: + """ + The DHParameters object associated with this private key. + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: DHPublicKey) -> bytes: + """ + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. + """ + + @abc.abstractmethod + def private_numbers(self) -> DHPrivateNumbers: + """ + Returns a DHPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> DHPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DHPrivateKey: + """ + Returns a deep copy. + """ + + +DHPrivateKeyWithSerialization = DHPrivateKey +DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) + +# Aliases that do not emit the deprecation warning on attribute access, for +# internal use (e.g. the unions in +# cryptography.hazmat.primitives.asymmetric.types, which are evaluated at +# import time). +_DHPublicKey = DHPublicKey +_DHPrivateKey = DHPrivateKey + +utils.deprecated( + generate_parameters, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="generate_parameters", +) + +utils.deprecated( + DHPrivateNumbers, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPrivateNumbers", +) + +utils.deprecated( + DHPublicNumbers, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPublicNumbers", +) + +utils.deprecated( + DHParameterNumbers, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHParameterNumbers", +) + +utils.deprecated( + DHParameters, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHParameters", +) + +utils.deprecated( + DHParameters, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHParametersWithSerialization", +) + +utils.deprecated( + DHPublicKey, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPublicKey", +) + +utils.deprecated( + DHPublicKey, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPublicKeyWithSerialization", +) + +utils.deprecated( + DHPrivateKey, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPrivateKey", +) + +utils.deprecated( + DHPrivateKey, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="DHPrivateKeyWithSerialization", +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py new file mode 100644 index 0000000..f245557 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -0,0 +1,179 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.utils import Buffer + + +class DSAParameters(metaclass=abc.ABCMeta): + @abc.abstractmethod + def generate_private_key(self) -> DSAPrivateKey: + """ + Generates and returns a DSAPrivateKey. + """ + + @abc.abstractmethod + def parameter_numbers(self) -> DSAParameterNumbers: + """ + Returns a DSAParameterNumbers. + """ + + +DSAParametersWithNumbers = DSAParameters +DSAParameters.register(rust_openssl.dsa.DSAParameters) + + +class DSAPrivateKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self) -> DSAPublicKey: + """ + The DSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self) -> DSAParameters: + """ + The DSAParameters object associated with this private key. + """ + + @abc.abstractmethod + def sign( + self, + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> bytes: + """ + Signs the data + """ + + @abc.abstractmethod + def private_numbers(self) -> DSAPrivateNumbers: + """ + Returns a DSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> DSAPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DSAPrivateKey: + """ + Returns a deep copy. + """ + + +DSAPrivateKeyWithSerialization = DSAPrivateKey +DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) + + +class DSAPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self) -> DSAParameters: + """ + The DSAParameters object associated with this public key. + """ + + @abc.abstractmethod + def public_numbers(self) -> DSAPublicNumbers: + """ + Returns a DSAPublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> DSAPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DSAPublicKey: + """ + Returns a deep copy. + """ + + +DSAPublicKeyWithSerialization = DSAPublicKey +DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) + +DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers +DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers +DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers + + +def generate_parameters( + key_size: int, backend: typing.Any = None +) -> DSAParameters: + if key_size not in (1024, 2048, 3072, 4096): + raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.") + + return rust_openssl.dsa.generate_parameters(key_size) + + +def generate_private_key( + key_size: int, backend: typing.Any = None +) -> DSAPrivateKey: + parameters = generate_parameters(key_size) + return parameters.generate_private_key() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py new file mode 100644 index 0000000..39e6751 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py @@ -0,0 +1,369 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat._oid import ObjectIdentifier +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils + + +class EllipticCurveOID: + SECP192R1 = ObjectIdentifier("1.2.840.10045.3.1.1") + SECP224R1 = ObjectIdentifier("1.3.132.0.33") + SECP256K1 = ObjectIdentifier("1.3.132.0.10") + SECP256R1 = ObjectIdentifier("1.2.840.10045.3.1.7") + SECP384R1 = ObjectIdentifier("1.3.132.0.34") + SECP521R1 = ObjectIdentifier("1.3.132.0.35") + BRAINPOOLP256R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.7") + BRAINPOOLP384R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.11") + BRAINPOOLP512R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.13") + + +class EllipticCurve(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + The name of the curve. e.g. secp256r1. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @property + @abc.abstractmethod + def group_order(self) -> int: + """ + The order of the curve's group. + """ + + +class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm( + self, + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: + """ + The digest algorithm used with this signature. + """ + + +class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def exchange( + self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey + ) -> bytes: + """ + Performs a key exchange operation using the provided algorithm with the + provided peer's public key. + """ + + @abc.abstractmethod + def public_key(self) -> EllipticCurvePublicKey: + """ + The EllipticCurvePublicKey for this private key. + """ + + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: + """ + The EllipticCurve that this key is on. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @abc.abstractmethod + def sign( + self, + data: utils.Buffer, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> bytes: + """ + Signs the data + """ + + @abc.abstractmethod + def private_numbers(self) -> EllipticCurvePrivateNumbers: + """ + Returns an EllipticCurvePrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> EllipticCurvePrivateKey: + """ + Returns a deep copy. + """ + + +EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey +EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) + + +class EllipticCurvePublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: + """ + The EllipticCurve that this key is on. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @abc.abstractmethod + def public_numbers(self) -> EllipticCurvePublicNumbers: + """ + Returns an EllipticCurvePublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: utils.Buffer, + data: utils.Buffer, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @classmethod + def from_encoded_point( + cls, curve: EllipticCurve, data: bytes + ) -> EllipticCurvePublicKey: + utils._check_bytes("data", data) + + if len(data) == 0: + raise ValueError("data must not be an empty byte string") + + if data[0] not in [0x02, 0x03, 0x04]: + raise ValueError("Unsupported elliptic curve point type") + + return rust_openssl.ec.from_public_bytes(curve, data) + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> EllipticCurvePublicKey: + """ + Returns a deep copy. + """ + + +EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey +EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) + +EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers +EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers + + +class SECP521R1(EllipticCurve): + name = "secp521r1" + key_size = 521 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa: E501 + + +class SECP384R1(EllipticCurve): + name = "secp384r1" + key_size = 384 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 # noqa: E501 + + +class SECP256R1(EllipticCurve): + name = "secp256r1" + key_size = 256 + group_order = ( + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + ) + + +class SECP256K1(EllipticCurve): + name = "secp256k1" + key_size = 256 + group_order = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + ) + + +class SECP224R1(EllipticCurve): + name = "secp224r1" + key_size = 224 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D + + +class SECP192R1(EllipticCurve): + name = "secp192r1" + key_size = 192 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831 + + +class BrainpoolP256R1(EllipticCurve): + name = "brainpoolP256r1" + key_size = 256 + group_order = ( + 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 + ) + + +class BrainpoolP384R1(EllipticCurve): + name = "brainpoolP384r1" + key_size = 384 + group_order = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565 # noqa: E501 + + +class BrainpoolP512R1(EllipticCurve): + name = "brainpoolP512r1" + key_size = 512 + group_order = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069 # noqa: E501 + + +_CURVE_TYPES: dict[str, EllipticCurve] = { + "prime192v1": SECP192R1(), + "prime256v1": SECP256R1(), + "secp192r1": SECP192R1(), + "secp224r1": SECP224R1(), + "secp256r1": SECP256R1(), + "secp384r1": SECP384R1(), + "secp521r1": SECP521R1(), + "secp256k1": SECP256K1(), + "brainpoolP256r1": BrainpoolP256R1(), + "brainpoolP384r1": BrainpoolP384R1(), + "brainpoolP512r1": BrainpoolP512R1(), +} + + +class ECDSA(EllipticCurveSignatureAlgorithm): + def __init__( + self, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + deterministic_signing: bool = False, + ): + from cryptography.hazmat.backends.openssl.backend import backend + + if ( + deterministic_signing + and not backend.ecdsa_deterministic_supported() + ): + raise UnsupportedAlgorithm( + "ECDSA with deterministic signature (RFC 6979) is not " + "supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + self._algorithm = algorithm + self._deterministic_signing = deterministic_signing + + @property + def algorithm( + self, + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: + return self._algorithm + + @property + def deterministic_signing( + self, + ) -> bool: + return self._deterministic_signing + + +generate_private_key = rust_openssl.ec.generate_private_key + + +def derive_private_key( + private_value: int, + curve: EllipticCurve, + backend: typing.Any = None, +) -> EllipticCurvePrivateKey: + if not isinstance(private_value, int): + raise TypeError("private_value must be an integer type.") + + if private_value <= 0: + raise ValueError("private_value must be a positive integer.") + + return rust_openssl.ec.derive_private_key(private_value, curve) + + +class ECDH: + pass + + +_OID_TO_CURVE = { + EllipticCurveOID.SECP192R1: SECP192R1, + EllipticCurveOID.SECP224R1: SECP224R1, + EllipticCurveOID.SECP256K1: SECP256K1, + EllipticCurveOID.SECP256R1: SECP256R1, + EllipticCurveOID.SECP384R1: SECP384R1, + EllipticCurveOID.SECP521R1: SECP521R1, + EllipticCurveOID.BRAINPOOLP256R1: BrainpoolP256R1, + EllipticCurveOID.BRAINPOOLP384R1: BrainpoolP384R1, + EllipticCurveOID.BRAINPOOLP512R1: BrainpoolP512R1, +} + + +def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: + try: + return _OID_TO_CURVE[oid] + except KeyError: + raise LookupError( + "The provided object identifier has no matching elliptic " + "curve class" + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py new file mode 100644 index 0000000..70aec5b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class Ed25519PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: + return rust_openssl.ed25519.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: Buffer, data: Buffer) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed25519PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed25519PublicKey: + """ + Returns a deep copy. + """ + + +Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) + + +class Ed25519PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> Ed25519PrivateKey: + return rust_openssl.ed25519.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> Ed25519PrivateKey: + return rust_openssl.ed25519.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> Ed25519PublicKey: + """ + The Ed25519PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def sign(self, data: Buffer) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed25519PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed25519PrivateKey: + """ + Returns a deep copy. + """ + + +Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py new file mode 100644 index 0000000..9ecb478 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -0,0 +1,143 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class Ed448PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: Buffer, data: Buffer) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed448PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed448PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "ed448"): + Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) + + +class Ed448PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> Ed448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> Ed448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> Ed448PublicKey: + """ + The Ed448PublicKey derived from the private key. + """ + + @abc.abstractmethod + def sign(self, data: Buffer) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> Ed448PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed448PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py new file mode 100644 index 0000000..beba90f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py @@ -0,0 +1,505 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class MLDSA44PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA44PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa44_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,312 bytes for MLDSA-44. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def verify_mu( + self, + signature: Buffer, + mu: Buffer, + ) -> None: + """ + Verify the signature over a precomputed mu (message representative). + + mu must be 64 bytes. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA44PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA44PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA44PublicKey.register(rust_openssl.mldsa.MLDSA44PublicKey) + + +class MLDSA44PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA44PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa44_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA44PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa44_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA44PublicKey: + """ + The MLDSA44PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def sign_mu(self, mu: Buffer) -> bytes: + """ + Signs a precomputed mu (message representative). + + mu must be 64 bytes and already incorporates the context, so no + context is accepted here. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA44PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA44PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA44PrivateKey.register(rust_openssl.mldsa.MLDSA44PrivateKey) + + +class MLDSA65PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA65PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa65_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,952 bytes for MLDSA-65. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def verify_mu( + self, + signature: Buffer, + mu: Buffer, + ) -> None: + """ + Verify the signature over a precomputed mu (message representative). + + mu must be 64 bytes. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA65PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA65PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA65PublicKey.register(rust_openssl.mldsa.MLDSA65PublicKey) + + +class MLDSA65PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA65PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa65_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA65PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa65_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA65PublicKey: + """ + The MLDSA65PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def sign_mu(self, mu: Buffer) -> bytes: + """ + Signs a precomputed mu (message representative). + + mu must be 64 bytes and already incorporates the context, so no + context is accepted here. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA65PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA65PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA65PrivateKey.register(rust_openssl.mldsa.MLDSA65PrivateKey) + + +class MLDSA87PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA87PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa87_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 2,592 bytes for MLDSA-87. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def verify_mu( + self, + signature: Buffer, + mu: Buffer, + ) -> None: + """ + Verify the signature over a precomputed mu (message representative). + + mu must be 64 bytes. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA87PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA87PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA87PublicKey.register(rust_openssl.mldsa.MLDSA87PublicKey) + + +class MLDSA87PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA87PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa87_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA87PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa87_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA87PublicKey: + """ + The MLDSA87PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def sign_mu(self, mu: Buffer) -> bytes: + """ + Signs a precomputed mu (message representative). + + mu must be 64 bytes and already incorporates the context, so no + context is accepted here. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA87PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA87PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA87PrivateKey.register(rust_openssl.mldsa.MLDSA87PrivateKey) + MLDSAMuHasher = rust_openssl.mldsa.MLDSAMuHasher diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py new file mode 100644 index 0000000..64bca11 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py @@ -0,0 +1,278 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class MLKEM768PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: Buffer) -> MLKEM768PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem768_public_bytes(data) + + @abc.abstractmethod + def encapsulate(self) -> tuple[bytes, bytes]: + """ + Encapsulate: returns (shared_secret, ciphertext). + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,184 bytes for ML-KEM-768. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM768PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM768PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM768PublicKey.register(rust_openssl.mlkem.MLKEM768PublicKey) + + +class MLKEM768PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLKEM768PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.generate_mlkem768_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLKEM768PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem768_seed_bytes(data) + + @abc.abstractmethod + def decapsulate(self, ciphertext: Buffer) -> bytes: + """ + Decapsulate: returns shared_secret. + """ + + @abc.abstractmethod + def public_key(self) -> MLKEM768PublicKey: + """ + The MLKEM768PublicKey derived from this private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key (64-byte seed). + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM768PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM768PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM768PrivateKey.register(rust_openssl.mlkem.MLKEM768PrivateKey) + + +class MLKEM1024PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: Buffer) -> MLKEM1024PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem1024_public_bytes(data) + + @abc.abstractmethod + def encapsulate(self) -> tuple[bytes, bytes]: + """ + Encapsulate: returns (shared_secret, ciphertext). + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,568 bytes for ML-KEM-1024. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM1024PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM1024PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM1024PublicKey.register(rust_openssl.mlkem.MLKEM1024PublicKey) + + +class MLKEM1024PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLKEM1024PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.generate_mlkem1024_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLKEM1024PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem1024_seed_bytes(data) + + @abc.abstractmethod + def decapsulate(self, ciphertext: Buffer) -> bytes: + """ + Decapsulate: returns shared_secret. + """ + + @abc.abstractmethod + def public_key(self) -> MLKEM1024PublicKey: + """ + The MLKEM1024PublicKey derived from this private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key (64-byte seed). + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM1024PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM1024PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM1024PrivateKey.register(rust_openssl.mlkem.MLKEM1024PrivateKey) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py new file mode 100644 index 0000000..1fd2873 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py @@ -0,0 +1,141 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives._asymmetric import ( + AsymmetricPadding as AsymmetricPadding, +) +from cryptography.hazmat.primitives.asymmetric import rsa + + +class PKCS1v15(AsymmetricPadding): + name = "EMSA-PKCS1-v1_5" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PKCS1v15): + return NotImplemented + + return True + + +class _MaxLength: + "Sentinel value for `MAX_LENGTH`." + + +class _Auto: + "Sentinel value for `AUTO`." + + +class _DigestLength: + "Sentinel value for `DIGEST_LENGTH`." + + +class PSS(AsymmetricPadding): + MAX_LENGTH = _MaxLength() + AUTO = _Auto() + DIGEST_LENGTH = _DigestLength() + name = "EMSA-PSS" + _salt_length: int | _MaxLength | _Auto | _DigestLength + + def __init__( + self, + mgf: MGF, + salt_length: int | _MaxLength | _Auto | _DigestLength, + ) -> None: + self._mgf = mgf + + if not isinstance( + salt_length, (int, _MaxLength, _Auto, _DigestLength) + ): + raise TypeError( + "salt_length must be an integer, MAX_LENGTH, " + "DIGEST_LENGTH, or AUTO" + ) + + if isinstance(salt_length, int) and salt_length < 0: + raise ValueError("salt_length must be zero or greater.") + + self._salt_length = salt_length + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PSS): + return NotImplemented + + return ( + self._mgf == other._mgf and self._salt_length == other._salt_length + ) + + @property + def mgf(self) -> MGF: + return self._mgf + + +class OAEP(AsymmetricPadding): + name = "EME-OAEP" + + def __init__( + self, + mgf: MGF, + algorithm: hashes.HashAlgorithm, + label: bytes | None, + ): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._mgf = mgf + self._algorithm = algorithm + self._label = label + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OAEP): + return NotImplemented + + return ( + self._mgf == other._mgf + and self._algorithm == other._algorithm + and self._label == other._label + ) + + @property + def algorithm(self) -> hashes.HashAlgorithm: + return self._algorithm + + @property + def mgf(self) -> MGF: + return self._mgf + + +class MGF(metaclass=abc.ABCMeta): + _algorithm: hashes.HashAlgorithm + + +class MGF1(MGF): + def __init__(self, algorithm: hashes.HashAlgorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._algorithm = algorithm + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MGF1): + return NotImplemented + + return self._algorithm == other._algorithm + + +def calculate_max_pss_salt_length( + key: rsa.RSAPrivateKey | rsa.RSAPublicKey, + hash_algorithm: hashes.HashAlgorithm, +) -> int: + if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + raise TypeError("key must be an RSA public or private key") + # bit length - 1 per RFC 3447 + emlen = (key.key_size + 6) // 8 + salt_length = emlen - hash_algorithm.digest_size - 2 + assert salt_length >= 0 + return salt_length diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py new file mode 100644 index 0000000..d730ceb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -0,0 +1,295 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import random +import typing +from math import gcd, lcm + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils + + +class RSAPrivateKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: + """ + Decrypts the provided ciphertext. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_key(self) -> RSAPublicKey: + """ + The RSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def sign( + self, + data: bytes, + padding: AsymmetricPadding, + algorithm: asym_utils.Prehashed + | hashes.HashAlgorithm + | asym_utils.NoDigestInfo, + ) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def private_numbers(self) -> RSAPrivateNumbers: + """ + Returns an RSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> RSAPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> RSAPrivateKey: + """ + Returns a deep copy. + """ + + +RSAPrivateKeyWithSerialization = RSAPrivateKey +RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) + + +class RSAPublicKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: + """ + Encrypts the given plaintext. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_numbers(self) -> RSAPublicNumbers: + """ + Returns an RSAPublicNumbers + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: bytes, + data: bytes, + padding: AsymmetricPadding, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @abc.abstractmethod + def recover_data_from_signature( + self, + signature: bytes, + padding: AsymmetricPadding, + algorithm: hashes.HashAlgorithm | asym_utils.NoDigestInfo | None, + ) -> bytes: + """ + Recovers the original data from the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> RSAPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> RSAPublicKey: + """ + Returns a deep copy. + """ + + +RSAPublicKeyWithSerialization = RSAPublicKey +RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) + +RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers +RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers + + +def generate_private_key( + public_exponent: int, + key_size: int, + backend: typing.Any = None, +) -> RSAPrivateKey: + _verify_rsa_parameters(public_exponent, key_size) + return rust_openssl.rsa.generate_private_key(public_exponent, key_size) + + +def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: + if public_exponent not in (3, 65537): + raise ValueError( + "public_exponent must be either 3 (for legacy compatibility) or " + "65537. Almost everyone should choose 65537 here!" + ) + + if key_size < 1024: + raise ValueError("key_size must be at least 1024-bits.") + + +def _modinv(e: int, m: int) -> int: + """ + Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 + """ + x1, x2 = 1, 0 + a, b = e, m + while b > 0: + q, r = divmod(a, b) + xn = x1 - q * x2 + a, b, x1, x2 = b, r, x2, xn + return x1 % m + + +def rsa_crt_iqmp(p: int, q: int) -> int: + """ + Compute the CRT (q ** -1) % p value from RSA primes p and q. + """ + if p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return _modinv(q, p) + + +def rsa_crt_dmp1(private_exponent: int, p: int) -> int: + """ + Compute the CRT private_exponent % (p - 1) value from the RSA + private_exponent (d) and p. + """ + if private_exponent <= 1 or p <= 1: + raise ValueError("Values can't be <= 1") + return private_exponent % (p - 1) + + +def rsa_crt_dmq1(private_exponent: int, q: int) -> int: + """ + Compute the CRT private_exponent % (q - 1) value from the RSA + private_exponent (d) and q. + """ + if private_exponent <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return private_exponent % (q - 1) + + +def rsa_recover_private_exponent(e: int, p: int, q: int) -> int: + """ + Compute the RSA private_exponent (d) given the public exponent (e) + and the RSA primes p and q. + + This uses the Carmichael totient function to generate the + smallest possible working value of the private exponent. + """ + # This lambda_n is the Carmichael totient function. + # The original RSA paper uses the Euler totient function + # here: phi_n = (p - 1) * (q - 1) + # Either version of the private exponent will work, but the + # one generated by the older formulation may be larger + # than necessary. (lambda_n always divides phi_n) + if e <= 1 or p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return _modinv(e, lcm(p - 1, q - 1)) + + +# Controls the number of iterations rsa_recover_prime_factors will perform +# to obtain the prime factors. +_MAX_RECOVERY_ATTEMPTS = 500 + + +def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: + """ + Compute factors p and q from the private exponent d. We assume that n has + no more than two factors. This function is adapted from code in PyCrypto. + """ + # reject invalid values early + if d <= 1 or e <= 1: + raise ValueError("d, e can't be <= 1") + if 17 != pow(17, e * d, n): + raise ValueError("n, d, e don't match") + # See 8.2.2(i) in Handbook of Applied Cryptography. + ktot = d * e - 1 + # The quantity d*e-1 is a multiple of phi(n), even, + # and can be represented as t*2^s. + t = ktot + while t % 2 == 0: + t = t // 2 + # Cycle through all multiplicative inverses in Zn. + # The algorithm is non-deterministic, but there is a 50% chance + # any candidate a leads to successful factoring. + # See "Digitalized Signatures and Public Key Functions as Intractable + # as Factorization", M. Rabin, 1979 + spotted = False + tries = 0 + while not spotted and tries < _MAX_RECOVERY_ATTEMPTS: + a = random.randint(2, n - 1) + tries += 1 + k = t + # Cycle through all values a^{t*2^i}=a^k + while k < ktot: + cand = pow(a, k, n) + # Check if a^k is a non-trivial root of unity (mod n) + if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: + # We have found a number such that (cand-1)(cand+1)=0 (mod n). + # Either of the terms divides n. + p = gcd(cand + 1, n) + spotted = True + break + k *= 2 + if not spotted: + raise ValueError("Unable to compute factors p and q from exponent d.") + # Found ! + q, r = divmod(n, p) + assert r == 0 + p, q = sorted((p, q), reverse=True) + return (p, q) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py new file mode 100644 index 0000000..dbf4b74 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py @@ -0,0 +1,94 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.primitives.asymmetric import ( + dh, + dsa, + ec, + ed448, + ed25519, + mldsa, + mlkem, + rsa, + x448, + x25519, +) + +# Every asymmetric key type. These use the private DH aliases so that +# importing this module doesn't trigger the FFDH deprecation warning. +PublicKeyTypes = typing.Union[ + dh._DHPublicKey, + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + mldsa.MLDSA44PublicKey, + mldsa.MLDSA65PublicKey, + mldsa.MLDSA87PublicKey, + mlkem.MLKEM768PublicKey, + mlkem.MLKEM1024PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] +# Every asymmetric key type +PrivateKeyTypes = typing.Union[ + dh._DHPrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + mldsa.MLDSA44PrivateKey, + mldsa.MLDSA65PrivateKey, + mldsa.MLDSA87PrivateKey, + mlkem.MLKEM768PrivateKey, + mlkem.MLKEM1024PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + x25519.X25519PrivateKey, + x448.X448PrivateKey, +] +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate public key types +CertificateIssuerPrivateKeyTypes = typing.Union[ + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + mldsa.MLDSA44PrivateKey, + mldsa.MLDSA65PrivateKey, + mldsa.MLDSA87PrivateKey, +] +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate private key types +CertificateIssuerPublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + mldsa.MLDSA44PublicKey, + mldsa.MLDSA65PublicKey, + mldsa.MLDSA87PublicKey, +] +# This type removes DHPublicKey. x448/x25519/mlkem can be a public key +# but cannot be used in signing so they are allowed here. +CertificatePublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + mldsa.MLDSA44PublicKey, + mldsa.MLDSA65PublicKey, + mldsa.MLDSA87PublicKey, + mlkem.MLKEM768PublicKey, + mlkem.MLKEM1024PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py new file mode 100644 index 0000000..c01c342 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py @@ -0,0 +1,28 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.primitives import hashes + +decode_dss_signature = asn1.decode_dss_signature +encode_dss_signature = asn1.encode_dss_signature + + +class NoDigestInfo: + pass + + +class Prehashed: + def __init__(self, algorithm: hashes.HashAlgorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of HashAlgorithm.") + + self._algorithm = algorithm + self._digest_size = algorithm.digest_size + + @property + def digest_size(self) -> int: + return self._digest_size diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py new file mode 100644 index 0000000..7498998 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -0,0 +1,134 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class X25519PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> X25519PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x25519.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> X25519PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X25519PublicKey: + """ + Returns a deep copy. + """ + + +X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) + + +class X25519PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> X25519PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + return rust_openssl.x25519.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> X25519PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x25519.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> X25519PublicKey: + """ + Returns the public key associated with this private key + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X25519PublicKey) -> bytes: + """ + Performs a key exchange operation using the provided peer's public key. + """ + + @abc.abstractmethod + def __copy__(self) -> X25519PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X25519PrivateKey: + """ + Returns a deep copy. + """ + + +X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py new file mode 100644 index 0000000..b9dc826 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py @@ -0,0 +1,137 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class X448PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> X448PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> X448PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X448PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + X448PublicKey.register(rust_openssl.x448.X448PublicKey) + + +class X448PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> X448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> X448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> X448PublicKey: + """ + Returns the public key associated with this private key + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X448PublicKey) -> bytes: + """ + Performs a key exchange operation using the provided peer's public key. + """ + + @abc.abstractmethod + def __copy__(self) -> X448PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X448PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py new file mode 100644 index 0000000..10c15d0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) +from cryptography.hazmat.primitives.ciphers.base import ( + AEADCipherContext, + AEADDecryptionContext, + AEADEncryptionContext, + Cipher, + CipherContext, +) + +__all__ = [ + "AEADCipherContext", + "AEADDecryptionContext", + "AEADEncryptionContext", + "BlockCipherAlgorithm", + "Cipher", + "CipherAlgorithm", + "CipherContext", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py new file mode 100644 index 0000000..c8a582d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = [ + "AESCCM", + "AESGCM", + "AESGCMSIV", + "AESOCB3", + "AESSIV", + "ChaCha20Poly1305", +] + +AESGCM = rust_openssl.aead.AESGCM +ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 +AESCCM = rust_openssl.aead.AESCCM +AESSIV = rust_openssl.aead.AESSIV +AESOCB3 = rust_openssl.aead.AESOCB3 +AESGCMSIV = rust_openssl.aead.AESGCMSIV diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py new file mode 100644 index 0000000..6b20dd5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -0,0 +1,138 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + ARC4 as ARC4, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + CAST5 as CAST5, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + IDEA as IDEA, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + SEED as SEED, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Blowfish as Blowfish, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Camellia as Camellia, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + TripleDES as TripleDES, +) +from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size +from cryptography.hazmat.primitives.ciphers import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) + + +class AES(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + # 512 added to support AES-256-XTS, which uses 512-bit keys + key_sizes = frozenset([128, 192, 256, 512]) + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class AES128(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([128]) + key_size = 128 + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + +class AES256(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([256]) + key_size = 256 + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + +utils.deprecated( + Camellia, + __name__, + "Camellia has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.Camellia and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 49.0.0.", + utils.DeprecatedIn43, + name="Camellia", +) + + +utils.deprecated( + ARC4, + __name__, + "ARC4 has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="ARC4", +) + + +utils.deprecated( + TripleDES, + __name__, + "TripleDES has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="TripleDES", +) + + +class ChaCha20(CipherAlgorithm): + name = "ChaCha20" + key_sizes = frozenset([256]) + + def __init__(self, key: utils.Buffer, nonce: utils.Buffer): + self.key = _verify_key_size(self, key) + utils._check_byteslike("nonce", nonce) + + if len(nonce) != 16: + raise ValueError("nonce must be 128-bits (16 bytes)") + + self._nonce = nonce + + @property + def nonce(self) -> utils.Buffer: + return self._nonce + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SM4(BlockCipherAlgorithm): + name = "SM4" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py new file mode 100644 index 0000000..24fceea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py @@ -0,0 +1,146 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.utils import Buffer + + +class CipherContext(metaclass=abc.ABCMeta): + @abc.abstractmethod + def update(self, data: Buffer) -> bytes: + """ + Processes the provided bytes through the cipher and returns the results + as bytes. + """ + + @abc.abstractmethod + def update_into(self, data: Buffer, buf: Buffer) -> int: + """ + Processes the provided bytes and writes the resulting data into the + provided buffer. Returns the number of bytes written. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Returns the results of processing the final block as bytes. + """ + + @abc.abstractmethod + def reset_nonce(self, nonce: bytes) -> None: + """ + Resets the nonce for the cipher context to the provided value. + Raises an exception if it does not support reset or if the + provided nonce does not have a valid length. + """ + + +class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): + @abc.abstractmethod + def authenticate_additional_data(self, data: Buffer) -> None: + """ + Authenticates the provided bytes. + """ + + +class AEADDecryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): + @abc.abstractmethod + def finalize_with_tag(self, tag: bytes) -> bytes: + """ + Returns the results of processing the final block as bytes and allows + delayed passing of the authentication tag. + """ + + +class AEADEncryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> bytes: + """ + Returns tag bytes. This is only available after encryption is + finalized. + """ + + +Mode = typing.TypeVar( + "Mode", bound=typing.Optional[modes.Mode], covariant=True +) + + +class Cipher(typing.Generic[Mode]): + def __init__( + self, + algorithm: CipherAlgorithm, + mode: Mode, + backend: typing.Any = None, + ) -> None: + if not isinstance(algorithm, CipherAlgorithm): + raise TypeError("Expected interface of CipherAlgorithm.") + + if mode is not None: + # mypy needs this assert to narrow the type from our generic + # type. Maybe it won't some time in the future. + assert isinstance(mode, modes.Mode) + mode.validate_for_algorithm(algorithm) + + self.algorithm = algorithm + self.mode = mode + + @typing.overload + def encryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADEncryptionContext: ... + + @typing.overload + def encryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: ... + + def encryptor(self): + if isinstance(self.mode, modes.ModeWithAuthenticationTag): + if self.mode.tag is not None: + raise ValueError( + "Authentication tag must be None when encrypting." + ) + + return rust_openssl.ciphers.create_encryption_ctx( + self.algorithm, self.mode + ) + + @typing.overload + def decryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADDecryptionContext: ... + + @typing.overload + def decryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: ... + + def decryptor(self): + return rust_openssl.ciphers.create_decryption_ctx( + self.algorithm, self.mode + ) + + +_CIPHER_TYPE = Cipher[ + typing.Union[ + modes.ModeWithNonce, + modes.ModeWithTweak, + modes.ECB, + modes.ModeWithInitializationVector, + None, + ] +] + +CipherContext.register(rust_openssl.ciphers.CipherContext) +AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext) +AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py new file mode 100644 index 0000000..c2008e9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py @@ -0,0 +1,192 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.decrepit.ciphers.modes import CFB as CFB +from cryptography.hazmat.decrepit.ciphers.modes import CFB8 as CFB8 +from cryptography.hazmat.decrepit.ciphers.modes import OFB as OFB +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) +from cryptography.hazmat.primitives._modes import ( + Mode as Mode, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithAuthenticationTag as ModeWithAuthenticationTag, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithInitializationVector as ModeWithInitializationVector, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithNonce as ModeWithNonce, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithTweak as ModeWithTweak, +) +from cryptography.hazmat.primitives._modes import ( + _check_aes_key_length, + _check_iv_and_key_length, + _check_nonce_length, +) +from cryptography.hazmat.primitives.ciphers import algorithms + + +class CBC(ModeWithInitializationVector): + name = "CBC" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class XTS(ModeWithTweak): + name = "XTS" + + def __init__(self, tweak: utils.Buffer): + utils._check_byteslike("tweak", tweak) + + if len(tweak) != 16: + raise ValueError("tweak must be 128-bits (16 bytes)") + + self._tweak = tweak + + @property + def tweak(self) -> utils.Buffer: + return self._tweak + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + if isinstance(algorithm, (algorithms.AES128, algorithms.AES256)): + raise TypeError( + "The AES128 and AES256 classes do not support XTS, please use " + "the standard AES class instead." + ) + + if algorithm.key_size not in (256, 512): + raise ValueError( + "The XTS specification requires a 256-bit key for AES-128-XTS" + " and 512-bit key for AES-256-XTS" + ) + + +class ECB(Mode): + name = "ECB" + + validate_for_algorithm = _check_aes_key_length + + +class CTR(ModeWithNonce): + name = "CTR" + + def __init__(self, nonce: utils.Buffer): + utils._check_byteslike("nonce", nonce) + self._nonce = nonce + + @property + def nonce(self) -> utils.Buffer: + return self._nonce + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + _check_aes_key_length(self, algorithm) + _check_nonce_length(self.nonce, self.name, algorithm) + + +class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): + name = "GCM" + _MAX_ENCRYPTED_BYTES = (2**39 - 256) // 8 + _MAX_AAD_BYTES = (2**64 - 1) // 8 + + def __init__( + self, + initialization_vector: utils.Buffer, + tag: bytes | None = None, + min_tag_length: int = 16, + ): + # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive + # This is a sane limit anyway so we'll enforce it here. + utils._check_byteslike("initialization_vector", initialization_vector) + if len(initialization_vector) < 8 or len(initialization_vector) > 128: + raise ValueError( + "initialization_vector must be between 8 and 128 bytes (64 " + "and 1024 bits)." + ) + self._initialization_vector = initialization_vector + if min_tag_length < 4: + raise ValueError("min_tag_length must be >= 4") + if tag is not None: + utils._check_bytes("tag", tag) + if len(tag) < min_tag_length: + raise ValueError( + f"Authentication tag must be {min_tag_length} bytes or " + "longer." + ) + self._tag = tag + self._min_tag_length = min_tag_length + + @property + def tag(self) -> bytes | None: + return self._tag + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + _check_aes_key_length(self, algorithm) + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + "GCM requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + block_size_bytes = algorithm.block_size // 8 + if self._tag is not None and len(self._tag) > block_size_bytes: + raise ValueError( + f"Authentication tag cannot be more than {block_size_bytes} " + "bytes." + ) + + +utils.deprecated( + OFB, + __name__, + "OFB has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.OFB and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="OFB", +) + + +utils.deprecated( + CFB, + __name__, + "CFB has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.CFB and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="CFB", +) + + +utils.deprecated( + CFB8, + __name__, + "CFB8 has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.CFB8 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="CFB8", +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py new file mode 100644 index 0000000..2c67ce2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = ["CMAC"] +CMAC = rust_openssl.cmac.CMAC diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py new file mode 100644 index 0000000..3975c71 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import hmac + + +def bytes_eq(a: bytes, b: bytes) -> bool: + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") + + return hmac.compare_digest(a, b) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py new file mode 100644 index 0000000..50ed355 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py @@ -0,0 +1,357 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import sys + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.utils import Buffer + +__all__ = [ + "MD5", + "SHA1", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512_224", + "SHA512_256", + "SHAKE128", + "SHAKE256", + "SM3", + "BLAKE2b", + "BLAKE2s", + "ExtendableOutputFunction", + "Hash", + "HashAlgorithm", + "HashContext", + "XOFHash", +] + + +class HashAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this algorithm (e.g. "sha256", "md5"). + """ + + @property + @abc.abstractmethod + def digest_size(self) -> int: + """ + The size of the resulting digest in bytes. + """ + + @property + @abc.abstractmethod + def block_size(self) -> int | None: + """ + The internal block size of the hash function, or None if the hash + function does not use blocks internally (e.g. SHA3). + """ + + +class HashContext(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm(self) -> HashAlgorithm: + """ + A HashAlgorithm that will be used by this context. + """ + + @abc.abstractmethod + def update(self, data: Buffer) -> None: + """ + Processes the provided bytes through the hash. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Finalizes the hash context and returns the hash digest as bytes. + """ + + @abc.abstractmethod + def copy(self) -> HashContext: + """ + Return a HashContext that is a copy of the current context. + """ + + +Hash = rust_openssl.hashes.Hash +HashContext.register(Hash) + +XOFHash = rust_openssl.hashes.XOFHash + + +class ExtendableOutputFunction(metaclass=abc.ABCMeta): + """ + An interface for extendable output functions. + """ + + +class SHA1(HashAlgorithm): + name = "sha1" + digest_size = 20 + block_size = 64 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA1): + return NotImplemented + + return True + + +class SHA512_224(HashAlgorithm): # noqa: N801 + name = "sha512-224" + digest_size = 28 + block_size = 128 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA512_224): + return NotImplemented + + return True + + +class SHA512_256(HashAlgorithm): # noqa: N801 + name = "sha512-256" + digest_size = 32 + block_size = 128 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA512_256): + return NotImplemented + + return True + + +class SHA224(HashAlgorithm): + name = "sha224" + digest_size = 28 + block_size = 64 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA224): + return NotImplemented + + return True + + +class SHA256(HashAlgorithm): + name = "sha256" + digest_size = 32 + block_size = 64 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA256): + return NotImplemented + + return True + + +class SHA384(HashAlgorithm): + name = "sha384" + digest_size = 48 + block_size = 128 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA384): + return NotImplemented + + return True + + +class SHA512(HashAlgorithm): + name = "sha512" + digest_size = 64 + block_size = 128 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA512): + return NotImplemented + + return True + + +class SHA3_224(HashAlgorithm): # noqa: N801 + name = "sha3-224" + digest_size = 28 + block_size = None + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA3_224): + return NotImplemented + + return True + + +class SHA3_256(HashAlgorithm): # noqa: N801 + name = "sha3-256" + digest_size = 32 + block_size = None + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA3_256): + return NotImplemented + + return True + + +class SHA3_384(HashAlgorithm): # noqa: N801 + name = "sha3-384" + digest_size = 48 + block_size = None + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA3_384): + return NotImplemented + + return True + + +class SHA3_512(HashAlgorithm): # noqa: N801 + name = "sha3-512" + digest_size = 64 + block_size = None + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHA3_512): + return NotImplemented + + return True + + +class SHAKE128(HashAlgorithm, ExtendableOutputFunction): + name = "shake128" + block_size = None + + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): + raise TypeError("digest_size must be an integer") + + if digest_size < 1: + raise ValueError("digest_size must be a positive integer") + + self._digest_size = digest_size + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHAKE128): + return NotImplemented + + return self._digest_size == other._digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + @classmethod + def xof(cls): + return cls(sys.maxsize) + + +class SHAKE256(HashAlgorithm, ExtendableOutputFunction): + name = "shake256" + block_size = None + + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): + raise TypeError("digest_size must be an integer") + + if digest_size < 1: + raise ValueError("digest_size must be a positive integer") + + self._digest_size = digest_size + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SHAKE256): + return NotImplemented + + return self._digest_size == other._digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + @classmethod + def xof(cls): + return cls(sys.maxsize) + + +class MD5(HashAlgorithm): + name = "md5" + digest_size = 16 + block_size = 64 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MD5): + return NotImplemented + + return True + + +class BLAKE2b(HashAlgorithm): + name = "blake2b" + _max_digest_size = 64 + _min_digest_size = 1 + block_size = 128 + + def __init__(self, digest_size: int): + if digest_size != 64: + raise ValueError("Digest size must be 64") + + self._digest_size = digest_size + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BLAKE2b): + return NotImplemented + + return self._digest_size == other._digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class BLAKE2s(HashAlgorithm): + name = "blake2s" + block_size = 64 + _max_digest_size = 32 + _min_digest_size = 1 + + def __init__(self, digest_size: int): + if digest_size != 32: + raise ValueError("Digest size must be 32") + + self._digest_size = digest_size + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BLAKE2s): + return NotImplemented + + return self._digest_size == other._digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class SM3(HashAlgorithm): + name = "sm3" + digest_size = 32 + block_size = 64 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SM3): + return NotImplemented + + return True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py new file mode 100644 index 0000000..a9442d5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import hashes + +__all__ = ["HMAC"] + +HMAC = rust_openssl.hmac.HMAC +hashes.HashContext.register(HMAC) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py new file mode 100644 index 0000000..c802808 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +AEAD = rust_openssl.hpke.AEAD +KDF = rust_openssl.hpke.KDF +KEM = rust_openssl.hpke.KEM +MLKEM768X25519PrivateKey = rust_openssl.hpke.MLKEM768X25519PrivateKey +MLKEM768X25519PublicKey = rust_openssl.hpke.MLKEM768X25519PublicKey +MLKEM1024P384PrivateKey = rust_openssl.hpke.MLKEM1024P384PrivateKey +MLKEM1024P384PublicKey = rust_openssl.hpke.MLKEM1024P384PublicKey +Suite = rust_openssl.hpke.Suite + +__all__ = [ + "AEAD", + "KDF", + "KEM", + "MLKEM768X25519PrivateKey", + "MLKEM768X25519PublicKey", + "MLKEM1024P384PrivateKey", + "MLKEM1024P384PublicKey", + "Suite", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 0000000..26c45bd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py @@ -0,0 +1,32 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.utils import Buffer + + +class KeyDerivationFunction(metaclass=abc.ABCMeta): + @abc.abstractmethod + def derive(self, key_material: bytes) -> bytes: + """ + Deterministically generates and returns a new key based on the existing + key material. + """ + + @abc.abstractmethod + def derive_into(self, key_material: bytes, buffer: Buffer) -> None: + """ + Deterministically generates a new key based on the existing key + material and stores it in the provided buffer. + """ + + @abc.abstractmethod + def verify(self, key_material: bytes, expected_key: bytes) -> None: + """ + Checks whether the key generated by the key material matches the + expected derived key. Raises an exception if they do not match. + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py new file mode 100644 index 0000000..03e84d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +Argon2d = rust_openssl.kdf.Argon2d +Argon2i = rust_openssl.kdf.Argon2i +Argon2id = rust_openssl.kdf.Argon2id +KeyDerivationFunction.register(Argon2d) +KeyDerivationFunction.register(Argon2i) +KeyDerivationFunction.register(Argon2id) + +__all__ = ["Argon2d", "Argon2i", "Argon2id"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py new file mode 100644 index 0000000..398dc5d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -0,0 +1,16 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +ConcatKDFHash = rust_openssl.kdf.ConcatKDFHash +ConcatKDFHMAC = rust_openssl.kdf.ConcatKDFHMAC + +KeyDerivationFunction.register(ConcatKDFHash) +KeyDerivationFunction.register(ConcatKDFHMAC) + +__all__ = ["ConcatKDFHMAC", "ConcatKDFHash"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py new file mode 100644 index 0000000..1e162d9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py @@ -0,0 +1,16 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +HKDF = rust_openssl.kdf.HKDF +HKDFExpand = rust_openssl.kdf.HKDFExpand + +KeyDerivationFunction.register(HKDF) +KeyDerivationFunction.register(HKDFExpand) + +__all__ = ["HKDF", "HKDFExpand"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py new file mode 100644 index 0000000..e559df8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +class Mode(utils.Enum): + CounterMode = "ctr" + + +class CounterLocation(utils.Enum): + BeforeFixed = "before_fixed" + AfterFixed = "after_fixed" + MiddleFixed = "middle_fixed" + + +KBKDFHMAC = rust_openssl.kdf.KBKDFHMAC +KeyDerivationFunction.register(KBKDFHMAC) + +KBKDFCMAC = rust_openssl.kdf.KBKDFCMAC +KeyDerivationFunction.register(KBKDFCMAC) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 0000000..771d80d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +PBKDF2HMAC = rust_openssl.kdf.PBKDF2HMAC +KeyDerivationFunction.register(PBKDF2HMAC) + +__all__ = ["PBKDF2HMAC"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py new file mode 100644 index 0000000..f791cee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py @@ -0,0 +1,19 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import sys + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +# This is used by the scrypt tests to skip tests that require more memory +# than the MEM_LIMIT +_MEM_LIMIT = sys.maxsize // 2 + +Scrypt = rust_openssl.kdf.Scrypt +KeyDerivationFunction.register(Scrypt) + +__all__ = ["Scrypt"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py new file mode 100644 index 0000000..8c4e2d3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +X963KDF = rust_openssl.kdf.X963KDF +KeyDerivationFunction.register(X963KDF) + +__all__ = ["X963KDF"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py new file mode 100644 index 0000000..123d97b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py @@ -0,0 +1,25 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = [ + "InvalidUnwrap", + "aes_key_unwrap", + "aes_key_unwrap_with_padding", + "aes_key_wrap", + "aes_key_wrap_with_padding", +] + + +class InvalidUnwrap(Exception): + pass + + +aes_key_wrap = rust_openssl.keywrap.aes_key_wrap +aes_key_unwrap = rust_openssl.keywrap.aes_key_unwrap +aes_key_wrap_with_padding = rust_openssl.keywrap.aes_key_wrap_with_padding +aes_key_unwrap_with_padding = rust_openssl.keywrap.aes_key_unwrap_with_padding diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py new file mode 100644 index 0000000..f9cd1f1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py @@ -0,0 +1,69 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.hazmat.bindings._rust import ( + ANSIX923PaddingContext, + ANSIX923UnpaddingContext, + PKCS7PaddingContext, + PKCS7UnpaddingContext, +) + + +class PaddingContext(metaclass=abc.ABCMeta): + @abc.abstractmethod + def update(self, data: utils.Buffer) -> bytes: + """ + Pads the provided bytes and returns any available data as bytes. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Finalize the padding, returns bytes. + """ + + +def _byte_padding_check(block_size: int) -> None: + if not (0 <= block_size <= 2040): + raise ValueError("block_size must be in range(0, 2041).") + + if block_size % 8 != 0: + raise ValueError("block_size must be a multiple of 8.") + + +class PKCS7: + def __init__(self, block_size: int): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self) -> PaddingContext: + return PKCS7PaddingContext(self.block_size) + + def unpadder(self) -> PaddingContext: + return PKCS7UnpaddingContext(self.block_size) + + +PaddingContext.register(PKCS7PaddingContext) +PaddingContext.register(PKCS7UnpaddingContext) + + +class ANSIX923: + def __init__(self, block_size: int): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self) -> PaddingContext: + return ANSIX923PaddingContext(self.block_size) + + def unpadder(self) -> PaddingContext: + return ANSIX923UnpaddingContext(self.block_size) + + +PaddingContext.register(ANSIX923PaddingContext) +PaddingContext.register(ANSIX923UnpaddingContext) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py new file mode 100644 index 0000000..7f5a77a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py @@ -0,0 +1,11 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = ["Poly1305"] + +Poly1305 = rust_openssl.poly1305.Poly1305 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py new file mode 100644 index 0000000..40beb21 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py @@ -0,0 +1,85 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.primitives._serialization import ( + BestAvailableEncryption, + Encoding, + KeySerializationEncryption, + NoEncryption, + ParameterFormat, + PrivateFormat, + PublicFormat, + _KeySerializationEncryption, +) +from cryptography.hazmat.primitives.asymmetric.dh import _FFDH_DEPRECATION_MSG +from cryptography.hazmat.primitives.serialization.base import ( + load_der_parameters, + load_der_private_key, + load_der_public_key, + load_pem_parameters, + load_pem_private_key, + load_pem_public_key, +) +from cryptography.hazmat.primitives.serialization.ssh import ( + SSHCertificate, + SSHCertificateBuilder, + SSHCertificateType, + SSHCertPrivateKeyTypes, + SSHCertPublicKeyTypes, + SSHPrivateKeyTypes, + SSHPublicKeyTypes, + load_ssh_private_key, + load_ssh_public_identity, + load_ssh_public_key, + ssh_key_fingerprint, +) + +__all__ = [ + "BestAvailableEncryption", + "Encoding", + "KeySerializationEncryption", + "NoEncryption", + "ParameterFormat", + "PrivateFormat", + "PublicFormat", + "SSHCertPrivateKeyTypes", + "SSHCertPublicKeyTypes", + "SSHCertificate", + "SSHCertificateBuilder", + "SSHCertificateType", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", + "_KeySerializationEncryption", + "load_der_parameters", + "load_der_private_key", + "load_der_public_key", + "load_pem_parameters", + "load_pem_private_key", + "load_pem_public_key", + "load_ssh_private_key", + "load_ssh_public_identity", + "load_ssh_public_key", + "ssh_key_fingerprint", +] + +# These can only load FFDH parameters, so the functions themselves are +# deprecated alongside the rest of FFDH. +utils.deprecated( + load_pem_parameters, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="load_pem_parameters", +) + +utils.deprecated( + load_der_parameters, + __name__, + _FFDH_DEPRECATION_MSG, + utils.DeprecatedIn50, + name="load_der_parameters", +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py new file mode 100644 index 0000000..f2b503b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py @@ -0,0 +1,16 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +load_pem_private_key = rust_openssl.keys.load_pem_private_key +load_der_private_key = rust_openssl.keys.load_der_private_key + +load_pem_public_key = rust_openssl.keys.load_pem_public_key +load_der_public_key = rust_openssl.keys.load_der_public_key + +load_pem_parameters = rust_openssl.dh.from_pem_parameters +load_der_parameters = rust_openssl.dh.from_der_parameters diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py new file mode 100644 index 0000000..58884ff --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -0,0 +1,176 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives._serialization import PBES as PBES +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + rsa, +) +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes + +__all__ = [ + "PBES", + "PKCS12Certificate", + "PKCS12KeyAndCertificates", + "PKCS12PrivateKeyTypes", + "load_key_and_certificates", + "load_pkcs12", + "serialize_java_truststore", + "serialize_key_and_certificates", +] + +PKCS12PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, +] + + +PKCS12Certificate = rust_pkcs12.PKCS12Certificate + + +class PKCS12KeyAndCertificates: + def __init__( + self, + key: PrivateKeyTypes | None, + cert: PKCS12Certificate | None, + additional_certs: list[PKCS12Certificate], + ): + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + ), + ): + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) + if cert is not None and not isinstance(cert, PKCS12Certificate): + raise TypeError("cert must be a PKCS12Certificate object or None") + if not all( + isinstance(add_cert, PKCS12Certificate) + for add_cert in additional_certs + ): + raise TypeError( + "all values in additional_certs must be PKCS12Certificate" + " objects" + ) + self._key = key + self._cert = cert + self._additional_certs = additional_certs + + @property + def key(self) -> PrivateKeyTypes | None: + return self._key + + @property + def cert(self) -> PKCS12Certificate | None: + return self._cert + + @property + def additional_certs(self) -> list[PKCS12Certificate]: + return self._additional_certs + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PKCS12KeyAndCertificates): + return NotImplemented + + return ( + self.key == other.key + and self.cert == other.cert + and self.additional_certs == other.additional_certs + ) + + def __hash__(self) -> int: + return hash((self.key, self.cert, tuple(self.additional_certs))) + + def __repr__(self) -> str: + fmt = ( + "" + ) + return fmt.format(self.key, self.cert, self.additional_certs) + + +load_key_and_certificates = rust_pkcs12.load_key_and_certificates +load_pkcs12 = rust_pkcs12.load_pkcs12 + + +_PKCS12CATypes = typing.Union[ + x509.Certificate, + PKCS12Certificate, +] + + +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if not certs: + raise ValueError("You must supply at least one cert") + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + return rust_pkcs12.serialize_java_truststore(certs, encryption_algorithm) + + +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[_PKCS12CATypes] | None, + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + ), + ): + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + if key is None and cert is None and not cas: + raise ValueError("You must supply at least one of key, cert, or cas") + + return rust_pkcs12.serialize_key_and_certificates( + name, key, cert, cas, encryption_algorithm + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py new file mode 100644 index 0000000..76b667a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -0,0 +1,412 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import email.base64mime +import email.generator +import email.message +import email.policy +import io +import typing +from collections.abc import Iterable + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa +from cryptography.hazmat.primitives.ciphers import ( + algorithms, +) +from cryptography.utils import _check_byteslike + +load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates + +load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates + +serialize_certificates = rust_pkcs7.serialize_certificates + +PKCS7HashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, +] + +PKCS7PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey +] + +ContentEncryptionAlgorithm = typing.Union[ + type[algorithms.AES128], type[algorithms.AES256] +] + + +class PKCS7Options(utils.Enum): + Text = "Add text/plain MIME type" + Binary = "Don't translate input data into canonical MIME format" + DetachedSignature = "Don't embed data in the PKCS7 structure" + NoCapabilities = "Don't embed SMIME capabilities" + NoAttributes = "Don't embed authenticatedAttributes" + NoCerts = "Don't embed signer certificate" + + +class PKCS7SignatureBuilder: + def __init__( + self, + data: utils.Buffer | None = None, + signers: list[ + tuple[ + x509.Certificate, + PKCS7PrivateKeyTypes, + PKCS7HashTypes, + padding.PSS | padding.PKCS1v15 | None, + ] + ] = [], + additional_certs: list[x509.Certificate] = [], + ): + self._data = data + self._signers = signers + self._additional_certs = additional_certs + + def set_data(self, data: utils.Buffer) -> PKCS7SignatureBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7SignatureBuilder(data, self._signers) + + def add_signer( + self, + certificate: x509.Certificate, + private_key: PKCS7PrivateKeyTypes, + hash_algorithm: PKCS7HashTypes, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ) -> PKCS7SignatureBuilder: + if not isinstance( + hash_algorithm, + ( + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ): + raise TypeError( + "hash_algorithm must be one of hashes.SHA224, " + "SHA256, SHA384, or SHA512" + ) + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance( + private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey) + ): + raise TypeError("Only RSA & EC keys are supported at this time.") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + return PKCS7SignatureBuilder( + self._data, + [ + *self._signers, + (certificate, private_key, hash_algorithm, rsa_padding), + ], + ) + + def add_certificate( + self, certificate: x509.Certificate + ) -> PKCS7SignatureBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + return PKCS7SignatureBuilder( + self._data, self._signers, [*self._additional_certs, certificate] + ) + + def sign( + self, + encoding: serialization.Encoding, + options: Iterable[PKCS7Options], + backend: typing.Any = None, + ) -> bytes: + if len(self._signers) == 0: + raise ValueError("Must have at least one signer") + if self._data is None: + raise ValueError("You must add data to sign") + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Text is a meaningless option unless it is accompanied by + # DetachedSignature + if ( + PKCS7Options.Text in options + and PKCS7Options.DetachedSignature not in options + ): + raise ValueError( + "When passing the Text option you must also pass " + "DetachedSignature" + ) + + if PKCS7Options.Text in options and encoding in ( + serialization.Encoding.DER, + serialization.Encoding.PEM, + ): + raise ValueError( + "The Text option is only available for SMIME serialization" + ) + + # No attributes implies no capabilities so we'll error if you try to + # pass both. + if ( + PKCS7Options.NoAttributes in options + and PKCS7Options.NoCapabilities in options + ): + raise ValueError( + "NoAttributes is a superset of NoCapabilities. Do not pass " + "both values." + ) + + return rust_pkcs7.sign_and_serialize(self, encoding, options) + + +class PKCS7EnvelopeBuilder: + def __init__( + self, + *, + _data: bytes | None = None, + _recipients: list[x509.Certificate] | None = None, + _content_encryption_algorithm: ContentEncryptionAlgorithm + | None = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()): + raise UnsupportedAlgorithm( + "RSA with PKCS1 v1.5 padding is not supported by this version" + " of OpenSSL.", + _Reasons.UNSUPPORTED_PADDING, + ) + self._data = _data + self._recipients = _recipients if _recipients is not None else [] + self._content_encryption_algorithm = _content_encryption_algorithm + + def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7EnvelopeBuilder( + _data=data, + _recipients=self._recipients, + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def add_recipient( + self, + certificate: x509.Certificate, + ) -> PKCS7EnvelopeBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance(certificate.public_key(), rsa.RSAPublicKey): + raise TypeError("Only RSA keys are supported at this time.") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=[ + *self._recipients, + certificate, + ], + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def set_content_encryption_algorithm( + self, content_encryption_algorithm: ContentEncryptionAlgorithm + ) -> PKCS7EnvelopeBuilder: + if self._content_encryption_algorithm is not None: + raise ValueError("Content encryption algo may only be set once") + if content_encryption_algorithm not in { + algorithms.AES128, + algorithms.AES256, + }: + raise TypeError("Only AES128 and AES256 are supported") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=self._recipients, + _content_encryption_algorithm=content_encryption_algorithm, + ) + + def encrypt( + self, + encoding: serialization.Encoding, + options: Iterable[PKCS7Options], + ) -> bytes: + if len(self._recipients) == 0: + raise ValueError("Must have at least one recipient") + if self._data is None: + raise ValueError("You must add data to encrypt") + + # The default content encryption algorithm is AES-128-CBC, which the + # S/MIME v3.2 RFC specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) + # however rest of S/MIME v3.2 is not currently supported + content_encryption_algorithm = ( + self._content_encryption_algorithm or algorithms.AES128 + ) + + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Only allow options that make sense for encryption + if any( + opt not in [PKCS7Options.Text, PKCS7Options.Binary] + for opt in options + ): + raise ValueError( + "Only the following options are supported for encryption: " + "Text, Binary" + ) + elif PKCS7Options.Text in options and PKCS7Options.Binary in options: + # OpenSSL accepts both options at the same time, but ignores Text. + # We fail defensively to avoid unexpected outputs. + raise ValueError( + "Cannot use Binary and Text options at the same time" + ) + + return rust_pkcs7.encrypt_and_serialize( + self, content_encryption_algorithm, encoding, options + ) + + +pkcs7_decrypt_der = rust_pkcs7.decrypt_der +pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem +pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime + + +def _smime_signed_encode( + data: bytes, signature: bytes, micalg: str, text_mode: bool +) -> bytes: + # This function works pretty hard to replicate what OpenSSL does + # precisely. For good and for ill. + + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header( + "Content-Type", + "multipart/signed", + protocol="application/x-pkcs7-signature", + micalg=micalg, + ) + + m.preamble = "This is an S/MIME signed message\n" + + msg_part = OpenSSLMimePart() + msg_part.set_payload(data) + if text_mode: + msg_part.add_header("Content-Type", "text/plain") + m.attach(msg_part) + + sig_part = email.message.MIMEPart() + sig_part.add_header( + "Content-Type", "application/x-pkcs7-signature", name="smime.p7s" + ) + sig_part.add_header("Content-Transfer-Encoding", "base64") + sig_part.add_header( + "Content-Disposition", "attachment", filename="smime.p7s" + ) + sig_part.set_payload( + email.base64mime.body_encode(signature, maxlinelen=65) + ) + del sig_part["MIME-Version"] + m.attach(sig_part) + + fp = io.BytesIO() + g = email.generator.BytesGenerator( + fp, + maxheaderlen=0, + mangle_from_=False, + policy=m.policy.clone(linesep="\r\n"), + ) + g.flatten(m) + return fp.getvalue() + + +def _smime_enveloped_encode(data: bytes) -> bytes: + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header("Content-Disposition", "attachment", filename="smime.p7m") + m.add_header( + "Content-Type", + "application/pkcs7-mime", + smime_type="enveloped-data", + name="smime.p7m", + ) + m.add_header("Content-Transfer-Encoding", "base64") + + m.set_payload(email.base64mime.body_encode(data, maxlinelen=65)) + + return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0)) + + +def _smime_enveloped_decode(data: bytes) -> bytes: + m = email.message_from_bytes(data) + if m.get_content_type() not in { + "application/x-pkcs7-mime", + "application/pkcs7-mime", + }: + raise ValueError("Not an S/MIME enveloped message") + return bytes(m.get_payload(decode=True)) + + +def _smime_remove_text_headers(data: bytes) -> bytes: + m = email.message_from_bytes(data) + # Using get() instead of get_content_type() since it has None as default, + # where the latter has "text/plain". Both methods are case-insensitive. + content_type = m.get("content-type") + if content_type is None: + raise ValueError( + "Decrypted MIME data has no 'Content-Type' header. " + "Please remove the 'Text' option to parse it manually." + ) + if "text/plain" not in content_type: + raise ValueError( + f"Decrypted MIME data content type is '{content_type}', not " + "'text/plain'. Remove the 'Text' option to parse it manually." + ) + return bytes(m.get_payload(decode=True)) + + +class OpenSSLMimePart(email.message.MIMEPart): + # A MIMEPart subclass that replicates OpenSSL's behavior of not including + # a newline if there are no headers. + def _write_headers(self, generator) -> None: + if list(self.raw_items()): + generator._write_headers(self) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py new file mode 100644 index 0000000..230e147 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py @@ -0,0 +1,1615 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import binascii +import enum +import os +import re +import typing +import warnings +from dataclasses import dataclass + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed25519, + padding, + rsa, +) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.ciphers import ( + AEADDecryptionContext, + Cipher, + algorithms, + modes, +) +from cryptography.hazmat.primitives.serialization import ( + Encoding, + KeySerializationEncryption, + NoEncryption, + PrivateFormat, + PublicFormat, + _KeySerializationEncryption, +) + +try: + from bcrypt import kdf as _bcrypt_kdf + + _bcrypt_supported = True +except ImportError: + _bcrypt_supported = False + + def _bcrypt_kdf( + password: bytes, + salt: bytes, + desired_key_bytes: int, + rounds: int, + ignore_few_rounds: bool = False, + ) -> bytes: + raise UnsupportedAlgorithm("Need bcrypt module") + + +_SSH_ED25519 = b"ssh-ed25519" +_SSH_RSA = b"ssh-rsa" +_SSH_DSA = b"ssh-dss" +_ECDSA_NISTP256 = b"ecdsa-sha2-nistp256" +_ECDSA_NISTP384 = b"ecdsa-sha2-nistp384" +_ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" +_CERT_SUFFIX = b"-cert-v01@openssh.com" + +# U2F application string suffixed pubkey +_SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" +_SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" + +# These are not key types, only algorithms, so they cannot appear +# as a public key type +_SSH_RSA_SHA256 = b"rsa-sha2-256" +_SSH_RSA_SHA512 = b"rsa-sha2-512" + +_SSH_PUBKEY_RC = re.compile(rb"\A(\S+)[ \t]+(\S+)") +_SK_MAGIC = b"openssh-key-v1\0" +_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----" +_SK_END = b"-----END OPENSSH PRIVATE KEY-----" +_BCRYPT = b"bcrypt" +_NONE = b"none" +_DEFAULT_CIPHER = b"aes256-ctr" +_DEFAULT_ROUNDS = 16 + +# re is only way to work on bytes-like data +_PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) + +# padding for max blocksize +_PADDING = memoryview(bytearray(range(1, 1 + 16))) + + +@dataclass +class _SSHCipher: + alg: type[algorithms.AES] + key_len: int + mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM] + block_len: int + iv_len: int + tag_len: int | None + is_aead: bool + + +# ciphers that are actually used in key wrapping +_SSH_CIPHERS: dict[bytes, _SSHCipher] = { + b"aes256-ctr": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CTR, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-cbc": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CBC, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-gcm@openssh.com": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.GCM, + block_len=16, + iv_len=12, + tag_len=16, + is_aead=True, + ), +} + +# map local curve name to key type +_ECDSA_KEY_TYPE = { + "secp256r1": _ECDSA_NISTP256, + "secp384r1": _ECDSA_NISTP384, + "secp521r1": _ECDSA_NISTP521, +} + + +def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes: + if isinstance(key, ec.EllipticCurvePrivateKey): + key_type = _ecdsa_key_type(key.public_key()) + elif isinstance(key, ec.EllipticCurvePublicKey): + key_type = _ecdsa_key_type(key) + elif isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + key_type = _SSH_RSA + elif isinstance(key, (dsa.DSAPrivateKey, dsa.DSAPublicKey)): + key_type = _SSH_DSA + elif isinstance( + key, (ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey) + ): + key_type = _SSH_ED25519 + else: + raise ValueError("Unsupported key type") + + return key_type + + +def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: + """Return SSH key_type and curve_name for private key.""" + curve = public_key.curve + if curve.name not in _ECDSA_KEY_TYPE: + raise ValueError( + f"Unsupported curve for ssh private key: {curve.name!r}" + ) + return _ECDSA_KEY_TYPE[curve.name] + + +def _check_block_size(data: utils.Buffer, block_len: int) -> None: + """Require data to be full blocks""" + if not data or len(data) % block_len != 0: + raise ValueError("Corrupt data: missing padding") + + +def _check_empty(data: utils.Buffer) -> None: + """All data should have been parsed.""" + if data: + raise ValueError("Corrupt data: unparsed data") + + +def _init_cipher( + ciphername: bytes, + password: bytes | None, + salt: bytes, + rounds: int, +) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: + """Generate key + iv and return cipher.""" + if not password: + raise TypeError( + "Key is password-protected, but password was not provided." + ) + + ciph = _SSH_CIPHERS[ciphername] + seed = _bcrypt_kdf( + password, salt, ciph.key_len + ciph.iv_len, rounds, True + ) + return Cipher( + ciph.alg(seed[: ciph.key_len]), + ciph.mode(seed[ciph.key_len :]), + ) + + +def _get_u32(data: memoryview) -> tuple[int, memoryview]: + """Uint32""" + if len(data) < 4: + raise ValueError("Invalid data") + return int.from_bytes(data[:4], byteorder="big"), data[4:] + + +def _get_u64(data: memoryview) -> tuple[int, memoryview]: + """Uint64""" + if len(data) < 8: + raise ValueError("Invalid data") + return int.from_bytes(data[:8], byteorder="big"), data[8:] + + +def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]: + """Bytes with u32 length prefix""" + n, data = _get_u32(data) + if n > len(data): + raise ValueError("Invalid data") + return data[:n], data[n:] + + +def _get_mpint(data: memoryview) -> tuple[int, memoryview]: + """Big integer.""" + val, data = _get_sshstr(data) + if val and val[0] > 0x7F: + raise ValueError("Invalid data") + return int.from_bytes(val, "big"), data + + +def _to_mpint(val: int) -> bytes: + """Storage format for signed bigint.""" + if val < 0: + raise ValueError("negative mpint not allowed") + if not val: + return b"" + nbytes = (val.bit_length() + 8) // 8 + return utils.int_to_bytes(val, nbytes) + + +class _FragList: + """Build recursive structure without data copy.""" + + flist: list[utils.Buffer] + + def __init__(self, init: list[utils.Buffer] | None = None) -> None: + self.flist = [] + if init: + self.flist.extend(init) + + def put_raw(self, val: utils.Buffer) -> None: + """Add plain bytes""" + self.flist.append(val) + + def put_u32(self, val: int) -> None: + """Big-endian uint32""" + self.flist.append(val.to_bytes(length=4, byteorder="big")) + + def put_u64(self, val: int) -> None: + """Big-endian uint64""" + self.flist.append(val.to_bytes(length=8, byteorder="big")) + + def put_sshstr(self, val: bytes | _FragList) -> None: + """Bytes prefixed with u32 length""" + if isinstance(val, (bytes, memoryview, bytearray)): + self.put_u32(len(val)) + self.flist.append(val) + else: + self.put_u32(val.size()) + self.flist.extend(val.flist) + + def put_mpint(self, val: int) -> None: + """Big-endian bigint prefixed with u32 length""" + self.put_sshstr(_to_mpint(val)) + + def size(self) -> int: + """Current number of bytes""" + return sum(map(len, self.flist)) + + def render(self, dstbuf: memoryview, pos: int = 0) -> int: + """Write into bytearray""" + for frag in self.flist: + flen = len(frag) + start, pos = pos, pos + flen + dstbuf[start:pos] = frag + return pos + + def tobytes(self) -> bytes: + """Return as bytes""" + buf = memoryview(bytearray(self.size())) + self.render(buf) + return buf.tobytes() + + +class _SSHFormatRSA: + """Format for RSA keys. + + Public: + mpint e, n + Private: + mpint n, e, d, iqmp, p, q + """ + + def get_public( + self, data: memoryview + ) -> tuple[tuple[int, int], memoryview]: + """RSA public fields""" + e, data = _get_mpint(data) + n, data = _get_mpint(data) + return (e, n), data + + def load_public( + self, data: memoryview + ) -> tuple[rsa.RSAPublicKey, memoryview]: + """Make RSA public key from data.""" + (e, n), data = self.get_public(data) + public_numbers = rsa.RSAPublicNumbers(e, n) + public_key = public_numbers.public_key() + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[rsa.RSAPrivateKey, memoryview]: + """Make RSA private key from data.""" + n, data = _get_mpint(data) + e, data = _get_mpint(data) + d, data = _get_mpint(data) + iqmp, data = _get_mpint(data) + p, data = _get_mpint(data) + q, data = _get_mpint(data) + + if (e, n) != pubfields: + raise ValueError("Corrupt data: rsa field mismatch") + dmp1 = rsa.rsa_crt_dmp1(d, p) + dmq1 = rsa.rsa_crt_dmq1(d, q) + public_numbers = rsa.RSAPublicNumbers(e, n) + private_numbers = rsa.RSAPrivateNumbers( + p, q, d, dmp1, dmq1, iqmp, public_numbers + ) + private_key = private_numbers.private_key( + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation + ) + return private_key, data + + def encode_public( + self, public_key: rsa.RSAPublicKey, f_pub: _FragList + ) -> None: + """Write RSA public key""" + pubn = public_key.public_numbers() + f_pub.put_mpint(pubn.e) + f_pub.put_mpint(pubn.n) + + def encode_private( + self, private_key: rsa.RSAPrivateKey, f_priv: _FragList + ) -> None: + """Write RSA private key""" + private_numbers = private_key.private_numbers() + public_numbers = private_numbers.public_numbers + + f_priv.put_mpint(public_numbers.n) + f_priv.put_mpint(public_numbers.e) + + f_priv.put_mpint(private_numbers.d) + f_priv.put_mpint(private_numbers.iqmp) + f_priv.put_mpint(private_numbers.p) + f_priv.put_mpint(private_numbers.q) + + +class _SSHFormatDSA: + """Format for DSA keys. + + Public: + mpint p, q, g, y + Private: + mpint p, q, g, y, x + """ + + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: + """DSA public fields""" + p, data = _get_mpint(data) + q, data = _get_mpint(data) + g, data = _get_mpint(data) + y, data = _get_mpint(data) + return (p, q, g, y), data + + def load_public( + self, data: memoryview + ) -> tuple[dsa.DSAPublicKey, memoryview]: + """Make DSA public key from data.""" + (p, q, g, y), data = self.get_public(data) + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + public_key = public_numbers.public_key() + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[dsa.DSAPrivateKey, memoryview]: + """Make DSA private key from data.""" + (p, q, g, y), data = self.get_public(data) + x, data = _get_mpint(data) + + if (p, q, g, y) != pubfields: + raise ValueError("Corrupt data: dsa field mismatch") + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + private_numbers = dsa.DSAPrivateNumbers(x, public_numbers) + private_key = private_numbers.private_key() + return private_key, data + + def encode_public( + self, public_key: dsa.DSAPublicKey, f_pub: _FragList + ) -> None: + """Write DSA public key""" + public_numbers = public_key.public_numbers() + parameter_numbers = public_numbers.parameter_numbers + self._validate(public_numbers) + + f_pub.put_mpint(parameter_numbers.p) + f_pub.put_mpint(parameter_numbers.q) + f_pub.put_mpint(parameter_numbers.g) + f_pub.put_mpint(public_numbers.y) + + def encode_private( + self, private_key: dsa.DSAPrivateKey, f_priv: _FragList + ) -> None: + """Write DSA private key""" + self.encode_public(private_key.public_key(), f_priv) + f_priv.put_mpint(private_key.private_numbers().x) + + def _validate(self, public_numbers: dsa.DSAPublicNumbers) -> None: + parameter_numbers = public_numbers.parameter_numbers + if parameter_numbers.p.bit_length() != 1024: + raise ValueError("SSH supports only 1024 bit DSA keys") + + +class _SSHFormatECDSA: + """Format for ECDSA keys. + + Public: + str curve + bytes point + Private: + str curve + bytes point + mpint secret + """ + + def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve): + self.ssh_curve_name = ssh_curve_name + self.curve = curve + + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview, memoryview], memoryview]: + """ECDSA public fields""" + curve, data = _get_sshstr(data) + point, data = _get_sshstr(data) + if curve != self.ssh_curve_name: + raise ValueError("Curve name mismatch") + if len(point) == 0: + raise ValueError("Invalid EC point: empty data") + if point[0] != 4: + raise NotImplementedError("Need uncompressed point") + return (curve, point), data + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + (_, point), data = self.get_public(data) + public_key = ec.EllipticCurvePublicKey.from_encoded_point( + self.curve, point.tobytes() + ) + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: + """Make ECDSA private key from data.""" + (curve_name, point), data = self.get_public(data) + secret, data = _get_mpint(data) + + if (curve_name, point) != pubfields: + raise ValueError("Corrupt data: ecdsa field mismatch") + private_key = ec.derive_private_key(secret, self.curve) + return private_key, data + + def encode_public( + self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList + ) -> None: + """Write ECDSA public key""" + point = public_key.public_bytes( + Encoding.X962, PublicFormat.UncompressedPoint + ) + f_pub.put_sshstr(self.ssh_curve_name) + f_pub.put_sshstr(point) + + def encode_private( + self, private_key: ec.EllipticCurvePrivateKey, f_priv: _FragList + ) -> None: + """Write ECDSA private key""" + public_key = private_key.public_key() + private_numbers = private_key.private_numbers() + + self.encode_public(public_key, f_priv) + f_priv.put_mpint(private_numbers.private_value) + + +class _SSHFormatEd25519: + """Format for Ed25519 keys. + + Public: + bytes point + Private: + bytes point + bytes secret_and_point + """ + + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview], memoryview]: + """Ed25519 public fields""" + point, data = _get_sshstr(data) + return (point,), data + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + (point,), data = self.get_public(data) + public_key = ed25519.Ed25519PublicKey.from_public_bytes( + point.tobytes() + ) + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: + """Make Ed25519 private key from data.""" + (point,), data = self.get_public(data) + keypair, data = _get_sshstr(data) + + secret = keypair[:32] + point2 = keypair[32:] + if point != point2 or (point,) != pubfields: + raise ValueError("Corrupt data: ed25519 field mismatch") + private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) + return private_key, data + + def encode_public( + self, public_key: ed25519.Ed25519PublicKey, f_pub: _FragList + ) -> None: + """Write Ed25519 public key""" + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_pub.put_sshstr(raw_public_key) + + def encode_private( + self, private_key: ed25519.Ed25519PrivateKey, f_priv: _FragList + ) -> None: + """Write Ed25519 private key""" + public_key = private_key.public_key() + raw_private_key = private_key.private_bytes( + Encoding.Raw, PrivateFormat.Raw, NoEncryption() + ) + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_keypair = _FragList([raw_private_key, raw_public_key]) + + self.encode_public(public_key, f_priv) + f_priv.put_sshstr(f_keypair) + + +def load_application(data) -> tuple[memoryview, memoryview]: + """ + U2F application strings + """ + application, data = _get_sshstr(data) + if not application.tobytes().startswith(b"ssh:"): + raise ValueError( + "U2F application string does not start with b'ssh:' " + f"({application})" + ) + return application, data + + +class _SSHFormatSKEd25519: + """ + The format of a sk-ssh-ed25519@openssh.com public key is: + + string "sk-ssh-ed25519@openssh.com" + string public key + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ssh-ed25519 private keys cannot be loaded" + ) + + +class _SSHFormatSKECDSA: + """ + The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: + + string "sk-ecdsa-sha2-nistp256@openssh.com" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ecdsa-sha2-nistp256 private keys cannot be loaded" + ) + + +_KEY_FORMATS = { + _SSH_RSA: _SSHFormatRSA(), + _SSH_DSA: _SSHFormatDSA(), + _SSH_ED25519: _SSHFormatEd25519(), + _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), + _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), + _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), + _SK_SSH_ED25519: _SSHFormatSKEd25519(), + _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), +} + + +def _lookup_kformat(key_type: utils.Buffer): + """Return valid format or throw error""" + if not isinstance(key_type, bytes): + key_type = memoryview(key_type).tobytes() + if key_type in _KEY_FORMATS: + return _KEY_FORMATS[key_type] + raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}") + + +SSHPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ed25519.Ed25519PrivateKey, +] + + +def load_ssh_private_key( + data: utils.Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> SSHPrivateKeyTypes: + """Load private key from OpenSSH custom encoding.""" + utils._check_byteslike("data", data) + if password is not None: + utils._check_bytes("password", password) + + m = _PEM_RC.search(data) + if not m: + raise ValueError("Not OpenSSH private key format") + p1 = m.start(1) + p2 = m.end(1) + data = binascii.a2b_base64(memoryview(data)[p1:p2]) + if not data.startswith(_SK_MAGIC): + raise ValueError("Not OpenSSH private key format") + data = memoryview(data)[len(_SK_MAGIC) :] + + # parse header + ciphername, data = _get_sshstr(data) + kdfname, data = _get_sshstr(data) + kdfoptions, data = _get_sshstr(data) + nkeys, data = _get_u32(data) + if nkeys != 1: + raise ValueError("Only one key supported") + + # load public key data + pubdata, data = _get_sshstr(data) + pub_key_type, pubdata = _get_sshstr(pubdata) + kformat = _lookup_kformat(pub_key_type) + pubfields, pubdata = kformat.get_public(pubdata) + _check_empty(pubdata) + + if ciphername != _NONE or kdfname != _NONE: + ciphername_bytes = ciphername.tobytes() + if ciphername_bytes not in _SSH_CIPHERS: + raise UnsupportedAlgorithm( + f"Unsupported cipher: {ciphername_bytes!r}" + ) + if kdfname != _BCRYPT: + raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}") + blklen = _SSH_CIPHERS[ciphername_bytes].block_len + tag_len = _SSH_CIPHERS[ciphername_bytes].tag_len + # load secret data + edata, data = _get_sshstr(data) + # see https://bugzilla.mindrot.org/show_bug.cgi?id=3553 for + # information about how OpenSSH handles AEAD tags + if _SSH_CIPHERS[ciphername_bytes].is_aead: + tag = bytes(data) + if len(tag) != tag_len: + raise ValueError("Corrupt data: invalid tag length for cipher") + else: + _check_empty(data) + _check_block_size(edata, blklen) + salt, kbuf = _get_sshstr(kdfoptions) + rounds, kbuf = _get_u32(kbuf) + _check_empty(kbuf) + ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds) + dec = ciph.decryptor() + edata = memoryview(dec.update(edata)) + if _SSH_CIPHERS[ciphername_bytes].is_aead: + assert isinstance(dec, AEADDecryptionContext) + _check_empty(dec.finalize_with_tag(tag)) + else: + # _check_block_size requires data to be a full block so there + # should be no output from finalize + _check_empty(dec.finalize()) + else: + if password: + raise TypeError( + "Password was given but private key is not encrypted." + ) + # load secret data + edata, data = _get_sshstr(data) + _check_empty(data) + blklen = 8 + _check_block_size(edata, blklen) + ck1, edata = _get_u32(edata) + ck2, edata = _get_u32(edata) + if ck1 != ck2: + raise ValueError("Corrupt data: broken checksum") + + # load per-key struct + key_type, edata = _get_sshstr(edata) + if key_type != pub_key_type: + raise ValueError("Corrupt data: key type mismatch") + private_key, edata = kformat.load_private( + edata, + pubfields, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) + # We don't use the comment + _, edata = _get_sshstr(edata) + + # yes, SSH does padding check *after* all other parsing is done. + # need to follow as it writes zero-byte padding too. + if edata != _PADDING[: len(edata)]: + raise ValueError("Corrupt data: invalid padding") + + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) + + return private_key + + +def _serialize_ssh_private_key( + private_key: SSHPrivateKeyTypes, + password: bytes, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: + """Serialize private key with OpenSSH custom encoding.""" + utils._check_bytes("password", password) + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, + ) + + key_type = _get_ssh_key_type(private_key) + kformat = _lookup_kformat(key_type) + + # setup parameters + f_kdfoptions = _FragList() + if password: + ciphername = _DEFAULT_CIPHER + blklen = _SSH_CIPHERS[ciphername].block_len + kdfname = _BCRYPT + rounds = _DEFAULT_ROUNDS + if ( + isinstance(encryption_algorithm, _KeySerializationEncryption) + and encryption_algorithm._kdf_rounds is not None + ): + rounds = encryption_algorithm._kdf_rounds + salt = os.urandom(16) + f_kdfoptions.put_sshstr(salt) + f_kdfoptions.put_u32(rounds) + ciph = _init_cipher(ciphername, password, salt, rounds) + else: + ciphername = kdfname = _NONE + blklen = 8 + ciph = None + nkeys = 1 + checkval = os.urandom(4) + comment = b"" + + # encode public and private parts together + f_public_key = _FragList() + f_public_key.put_sshstr(key_type) + kformat.encode_public(private_key.public_key(), f_public_key) + + f_secrets = _FragList([checkval, checkval]) + f_secrets.put_sshstr(key_type) + kformat.encode_private(private_key, f_secrets) + f_secrets.put_sshstr(comment) + f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)]) + + # top-level structure + f_main = _FragList() + f_main.put_raw(_SK_MAGIC) + f_main.put_sshstr(ciphername) + f_main.put_sshstr(kdfname) + f_main.put_sshstr(f_kdfoptions) + f_main.put_u32(nkeys) + f_main.put_sshstr(f_public_key) + f_main.put_sshstr(f_secrets) + + # copy result info bytearray + slen = f_secrets.size() + mlen = f_main.size() + buf = memoryview(bytearray(mlen + blklen)) + f_main.render(buf) + ofs = mlen - slen + + # encrypt in-place + if ciph is not None: + ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:]) + + return bytes(buf[:mlen]) + + +SSHPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + dsa.DSAPublicKey, + ed25519.Ed25519PublicKey, +] + +SSHCertPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, +] + + +class SSHCertificateType(enum.Enum): + USER = 1 + HOST = 2 + + +class SSHCertificate: + def __init__( + self, + _nonce: memoryview, + _public_key: SSHPublicKeyTypes, + _serial: int, + _cctype: int, + _key_id: memoryview, + _valid_principals: list[bytes], + _valid_after: int, + _valid_before: int, + _critical_options: dict[bytes, bytes], + _extensions: dict[bytes, bytes], + _sig_type: memoryview, + _sig_key: memoryview, + _inner_sig_type: memoryview, + _signature: memoryview, + _tbs_cert_body: memoryview, + _cert_key_type: bytes, + _cert_body: memoryview, + ): + self._nonce = _nonce + self._public_key = _public_key + self._serial = _serial + try: + self._type = SSHCertificateType(_cctype) + except ValueError: + raise ValueError("Invalid certificate type") + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_after = _valid_after + self._valid_before = _valid_before + self._critical_options = _critical_options + self._extensions = _extensions + self._sig_type = _sig_type + self._sig_key = _sig_key + self._inner_sig_type = _inner_sig_type + self._signature = _signature + self._cert_key_type = _cert_key_type + self._cert_body = _cert_body + self._tbs_cert_body = _tbs_cert_body + + @property + def nonce(self) -> bytes: + return bytes(self._nonce) + + def public_key(self) -> SSHCertPublicKeyTypes: + # make mypy happy until we remove DSA support entirely and + # the underlying union won't have a disallowed type + return typing.cast(SSHCertPublicKeyTypes, self._public_key) + + @property + def serial(self) -> int: + return self._serial + + @property + def type(self) -> SSHCertificateType: + return self._type + + @property + def key_id(self) -> bytes: + return bytes(self._key_id) + + @property + def valid_principals(self) -> list[bytes]: + return self._valid_principals + + @property + def valid_before(self) -> int: + return self._valid_before + + @property + def valid_after(self) -> int: + return self._valid_after + + @property + def critical_options(self) -> dict[bytes, bytes]: + return self._critical_options + + @property + def extensions(self) -> dict[bytes, bytes]: + return self._extensions + + def signature_key(self) -> SSHCertPublicKeyTypes: + sigformat = _lookup_kformat(self._sig_type) + signature_key, sigkey_rest = sigformat.load_public(self._sig_key) + _check_empty(sigkey_rest) + return signature_key + + def public_bytes(self) -> bytes: + return ( + bytes(self._cert_key_type) + + b" " + + binascii.b2a_base64(bytes(self._cert_body), newline=False) + ) + + def verify_cert_signature(self) -> None: + signature_key = self.signature_key() + if isinstance(signature_key, ed25519.Ed25519PublicKey): + signature_key.verify( + bytes(self._signature), bytes(self._tbs_cert_body) + ) + elif isinstance(signature_key, ec.EllipticCurvePublicKey): + # The signature is encoded as a pair of big-endian integers + r, data = _get_mpint(self._signature) + s, data = _get_mpint(data) + _check_empty(data) + computed_sig = asym_utils.encode_dss_signature(r, s) + hash_alg = _get_ec_hash_alg(signature_key.curve) + signature_key.verify( + computed_sig, bytes(self._tbs_cert_body), ec.ECDSA(hash_alg) + ) + else: + assert isinstance(signature_key, rsa.RSAPublicKey) + if self._inner_sig_type == _SSH_RSA: + hash_alg = hashes.SHA1() + elif self._inner_sig_type == _SSH_RSA_SHA256: + hash_alg = hashes.SHA256() + else: + assert self._inner_sig_type == _SSH_RSA_SHA512 + hash_alg = hashes.SHA512() + signature_key.verify( + bytes(self._signature), + bytes(self._tbs_cert_body), + padding.PKCS1v15(), + hash_alg, + ) + + +def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: + if isinstance(curve, ec.SECP256R1): + return hashes.SHA256() + elif isinstance(curve, ec.SECP384R1): + return hashes.SHA384() + else: + assert isinstance(curve, ec.SECP521R1) + return hashes.SHA512() + + +def _load_ssh_public_identity( + data: utils.Buffer, + _legacy_dsa_allowed=False, +) -> SSHCertificate | SSHPublicKeyTypes: + utils._check_byteslike("data", data) + + m = _SSH_PUBKEY_RC.match(data) + if not m: + raise ValueError("Invalid line format") + key_type = orig_key_type = m.group(1) + key_body = m.group(2) + with_cert = False + if key_type.endswith(_CERT_SUFFIX): + with_cert = True + key_type = key_type[: -len(_CERT_SUFFIX)] + if key_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA keys aren't supported in SSH certificates" + ) + kformat = _lookup_kformat(key_type) + + try: + rest = memoryview(binascii.a2b_base64(key_body)) + except (TypeError, binascii.Error): + raise ValueError("Invalid format") + + if with_cert: + cert_body = rest + inner_key_type, rest = _get_sshstr(rest) + if inner_key_type != orig_key_type: + raise ValueError("Invalid key format") + if with_cert: + nonce, rest = _get_sshstr(rest) + public_key, rest = kformat.load_public(rest) + if with_cert: + serial, rest = _get_u64(rest) + cctype, rest = _get_u32(rest) + key_id, rest = _get_sshstr(rest) + principals, rest = _get_sshstr(rest) + valid_principals = [] + while principals: + principal, principals = _get_sshstr(principals) + valid_principals.append(bytes(principal)) + valid_after, rest = _get_u64(rest) + valid_before, rest = _get_u64(rest) + crit_options, rest = _get_sshstr(rest) + critical_options = _parse_exts_opts(crit_options) + exts, rest = _get_sshstr(rest) + extensions = _parse_exts_opts(exts) + # Get the reserved field, which is unused. + _, rest = _get_sshstr(rest) + sig_key_raw, rest = _get_sshstr(rest) + sig_type, sig_key = _get_sshstr(sig_key_raw) + if sig_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA signatures aren't supported in SSH certificates" + ) + # Get the entire cert body and subtract the signature + tbs_cert_body = cert_body[: -len(rest)] + signature_raw, rest = _get_sshstr(rest) + _check_empty(rest) + inner_sig_type, sig_rest = _get_sshstr(signature_raw) + # RSA certs can have multiple algorithm types + if ( + sig_type == _SSH_RSA + and inner_sig_type + not in [_SSH_RSA_SHA256, _SSH_RSA_SHA512, _SSH_RSA] + ) or (sig_type != _SSH_RSA and inner_sig_type != sig_type): + raise ValueError("Signature key type does not match") + signature, sig_rest = _get_sshstr(sig_rest) + _check_empty(sig_rest) + return SSHCertificate( + nonce, + public_key, + serial, + cctype, + key_id, + valid_principals, + valid_after, + valid_before, + critical_options, + extensions, + sig_type, + sig_key, + inner_sig_type, + signature, + tbs_cert_body, + orig_key_type, + cert_body, + ) + else: + _check_empty(rest) + return public_key + + +def load_ssh_public_identity( + data: utils.Buffer, +) -> SSHCertificate | SSHPublicKeyTypes: + return _load_ssh_public_identity(data) + + +def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: + result: dict[bytes, bytes] = {} + last_name = None + while exts_opts: + name, exts_opts = _get_sshstr(exts_opts) + bname: bytes = bytes(name) + if bname in result: + raise ValueError("Duplicate name") + if last_name is not None and bname < last_name: + raise ValueError("Fields not lexically sorted") + value, exts_opts = _get_sshstr(exts_opts) + if len(value) > 0: + value, extra = _get_sshstr(value) + if len(extra) > 0: + raise ValueError("Unexpected extra data after value") + result[bname] = bytes(value) + last_name = bname + return result + + +def ssh_key_fingerprint( + key: SSHPublicKeyTypes, + hash_algorithm: hashes.MD5 | hashes.SHA1 | hashes.SHA256, +) -> bytes: + if not isinstance( + hash_algorithm, + (hashes.MD5, hashes.SHA1, hashes.SHA256), + ): + raise TypeError("hash_algorithm must be either MD5, SHA1, or SHA256") + + key_type = _get_ssh_key_type(key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(key, f_pub) + + ssh_binary_data = f_pub.tobytes() + + # Hash the binary data + hash_obj = hashes.Hash(hash_algorithm) + hash_obj.update(ssh_binary_data) + return hash_obj.finalize() + + +def load_ssh_public_key( + data: utils.Buffer, backend: typing.Any = None +) -> SSHPublicKeyTypes: + cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) + public_key: SSHPublicKeyTypes + if isinstance(cert_or_key, SSHCertificate): + public_key = cert_or_key.public_key() + else: + public_key = cert_or_key + + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) + return public_key + + +def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: + """One-line public key format for OpenSSH""" + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, + ) + key_type = _get_ssh_key_type(public_key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(public_key, f_pub) + + pub = binascii.b2a_base64(f_pub.tobytes()).strip() + return b"".join([key_type, b" ", pub]) + + +SSHCertPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, +] + + +# This is an undocumented limit enforced in the openssh codebase for sshd and +# ssh-keygen, but it is undefined in the ssh certificates spec. +_SSHKEY_CERT_MAX_PRINCIPALS = 256 + + +class SSHCertificateBuilder: + def __init__( + self, + _public_key: SSHCertPublicKeyTypes | None = None, + _serial: int | None = None, + _type: SSHCertificateType | None = None, + _key_id: bytes | None = None, + _valid_principals: list[bytes] = [], + _valid_for_all_principals: bool = False, + _valid_before: int | None = None, + _valid_after: int | None = None, + _critical_options: list[tuple[bytes, bytes]] = [], + _extensions: list[tuple[bytes, bytes]] = [], + ): + self._public_key = _public_key + self._serial = _serial + self._type = _type + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_for_all_principals = _valid_for_all_principals + self._valid_before = _valid_before + self._valid_after = _valid_after + self._critical_options = _critical_options + self._extensions = _extensions + + def public_key( + self, public_key: SSHCertPublicKeyTypes + ) -> SSHCertificateBuilder: + if not isinstance( + public_key, + ( + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, + ), + ): + raise TypeError("Unsupported key type") + if self._public_key is not None: + raise ValueError("public_key already set") + + return SSHCertificateBuilder( + _public_key=public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def serial(self, serial: int) -> SSHCertificateBuilder: + if not isinstance(serial, int): + raise TypeError("serial must be an integer") + if not 0 <= serial < 2**64: + raise ValueError("serial must be between 0 and 2**64") + if self._serial is not None: + raise ValueError("serial already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def type(self, type: SSHCertificateType) -> SSHCertificateBuilder: + if not isinstance(type, SSHCertificateType): + raise TypeError("type must be an SSHCertificateType") + if self._type is not None: + raise ValueError("type already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def key_id(self, key_id: bytes) -> SSHCertificateBuilder: + if not isinstance(key_id, bytes): + raise TypeError("key_id must be bytes") + if self._key_id is not None: + raise ValueError("key_id already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_principals( + self, valid_principals: list[bytes] + ) -> SSHCertificateBuilder: + if self._valid_for_all_principals: + raise ValueError( + "Principals can't be set because the cert is valid " + "for all principals" + ) + if ( + not all(isinstance(x, bytes) for x in valid_principals) + or not valid_principals + ): + raise TypeError( + "principals must be a list of bytes and can't be empty" + ) + if self._valid_principals: + raise ValueError("valid_principals already set") + + if len(valid_principals) > _SSHKEY_CERT_MAX_PRINCIPALS: + raise ValueError( + "Reached or exceeded the maximum number of valid_principals" + ) + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_for_all_principals(self): + if self._valid_principals: + raise ValueError( + "valid_principals already set, can't set " + "valid_for_all_principals" + ) + if self._valid_for_all_principals: + raise ValueError("valid_for_all_principals already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=True, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder: + if not isinstance(valid_before, (int, float)): + raise TypeError("valid_before must be an int or float") + valid_before = int(valid_before) + if valid_before < 0 or valid_before >= 2**64: + raise ValueError("valid_before must [0, 2**64)") + if self._valid_before is not None: + raise ValueError("valid_before already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder: + if not isinstance(valid_after, (int, float)): + raise TypeError("valid_after must be an int or float") + valid_after = int(valid_after) + if valid_after < 0 or valid_after >= 2**64: + raise ValueError("valid_after must [0, 2**64)") + if self._valid_after is not None: + raise ValueError("valid_after already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def add_critical_option( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._critical_options]: + raise ValueError("Duplicate critical option name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=[*self._critical_options, (name, value)], + _extensions=self._extensions, + ) + + def add_extension( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._extensions]: + raise ValueError("Duplicate extension name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=[*self._extensions, (name, value)], + ) + + def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: + if not isinstance( + private_key, + ( + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, + ), + ): + raise TypeError("Unsupported private key type") + + if self._public_key is None: + raise ValueError("public_key must be set") + + # Not required + serial = 0 if self._serial is None else self._serial + + if self._type is None: + raise ValueError("type must be set") + + # Not required + key_id = b"" if self._key_id is None else self._key_id + + # A zero length list is valid, but means the certificate + # is valid for any principal of the specified type. We require + # the user to explicitly set valid_for_all_principals to get + # that behavior. + if not self._valid_principals and not self._valid_for_all_principals: + raise ValueError( + "valid_principals must be set if valid_for_all_principals " + "is False" + ) + + if self._valid_before is None: + raise ValueError("valid_before must be set") + + if self._valid_after is None: + raise ValueError("valid_after must be set") + + if self._valid_after > self._valid_before: + raise ValueError("valid_after must be earlier than valid_before") + + # lexically sort our byte strings + self._critical_options.sort(key=lambda x: x[0]) + self._extensions.sort(key=lambda x: x[0]) + + key_type = _get_ssh_key_type(self._public_key) + cert_prefix = key_type + _CERT_SUFFIX + + # Marshal the bytes to be signed + nonce = os.urandom(32) + kformat = _lookup_kformat(key_type) + f = _FragList() + f.put_sshstr(cert_prefix) + f.put_sshstr(nonce) + kformat.encode_public(self._public_key, f) + f.put_u64(serial) + f.put_u32(self._type.value) + f.put_sshstr(key_id) + fprincipals = _FragList() + for p in self._valid_principals: + fprincipals.put_sshstr(p) + f.put_sshstr(fprincipals.tobytes()) + f.put_u64(self._valid_after) + f.put_u64(self._valid_before) + fcrit = _FragList() + for name, value in self._critical_options: + fcrit.put_sshstr(name) + if len(value) > 0: + foptval = _FragList() + foptval.put_sshstr(value) + fcrit.put_sshstr(foptval.tobytes()) + else: + fcrit.put_sshstr(value) + f.put_sshstr(fcrit.tobytes()) + fext = _FragList() + for name, value in self._extensions: + fext.put_sshstr(name) + if len(value) > 0: + fextval = _FragList() + fextval.put_sshstr(value) + fext.put_sshstr(fextval.tobytes()) + else: + fext.put_sshstr(value) + f.put_sshstr(fext.tobytes()) + f.put_sshstr(b"") # RESERVED FIELD + # encode CA public key + ca_type = _get_ssh_key_type(private_key) + caformat = _lookup_kformat(ca_type) + caf = _FragList() + caf.put_sshstr(ca_type) + caformat.encode_public(private_key.public_key(), caf) + f.put_sshstr(caf.tobytes()) + # Sigs according to the rules defined for the CA's public key + # (RFC4253 section 6.6 for ssh-rsa, RFC5656 for ECDSA, + # and RFC8032 for Ed25519). + if isinstance(private_key, ed25519.Ed25519PrivateKey): + signature = private_key.sign(f.tobytes()) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + elif isinstance(private_key, ec.EllipticCurvePrivateKey): + hash_alg = _get_ec_hash_alg(private_key.curve) + signature = private_key.sign(f.tobytes(), ec.ECDSA(hash_alg)) + r, s = asym_utils.decode_dss_signature(signature) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsigblob = _FragList() + fsigblob.put_mpint(r) + fsigblob.put_mpint(s) + fsig.put_sshstr(fsigblob.tobytes()) + f.put_sshstr(fsig.tobytes()) + + else: + assert isinstance(private_key, rsa.RSAPrivateKey) + # Just like Golang, we're going to use SHA512 for RSA + # https://cs.opensource.google/go/x/crypto/+/refs/tags/ + # v0.4.0:ssh/certs.go;l=445 + # RFC 8332 defines SHA256 and 512 as options + fsig = _FragList() + fsig.put_sshstr(_SSH_RSA_SHA512) + signature = private_key.sign( + f.tobytes(), padding.PKCS1v15(), hashes.SHA512() + ) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + + cert_data = binascii.b2a_base64(f.tobytes()).strip() + # load_ssh_public_identity returns a union, but this is + # guaranteed to be an SSHCertificate, so we cast to make + # mypy happy. + return typing.cast( + SSHCertificate, + load_ssh_public_identity(b"".join([cert_prefix, b" ", cert_data])), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py new file mode 100644 index 0000000..c1af423 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + + +class InvalidToken(Exception): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py new file mode 100644 index 0000000..21fb000 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py @@ -0,0 +1,101 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import base64 +import typing +from urllib.parse import quote, urlencode + +from cryptography.hazmat.primitives import constant_time, hmac +from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.utils import Buffer + +HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] + + +def _generate_uri( + hotp: HOTP, + type_name: str, + account_name: str, + issuer: str | None, + extra_parameters: list[tuple[str, int]], +) -> str: + parameters = [ + ("digits", hotp._length), + ("secret", base64.b32encode(hotp._key)), + ("algorithm", hotp._algorithm.name.upper()), + ] + + if issuer is not None: + parameters.append(("issuer", issuer)) + + parameters.extend(extra_parameters) + + label = ( + f"{quote(issuer)}:{quote(account_name)}" + if issuer + else quote(account_name) + ) + return f"otpauth://{type_name}/{label}?{urlencode(parameters)}" + + +class HOTP: + def __init__( + self, + key: Buffer, + length: int, + algorithm: HOTPHashTypes, + backend: typing.Any = None, + enforce_key_length: bool = True, + ) -> None: + if len(key) < 16 and enforce_key_length is True: + raise ValueError("Key length has to be at least 128 bits.") + + if not isinstance(length, int): + raise TypeError("Length parameter must be an integer type.") + + if length < 6 or length > 8: + raise ValueError("Length of HOTP has to be between 6 and 8.") + + if not isinstance(algorithm, (SHA1, SHA256, SHA512)): + raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") + + self._key = key + self._length = length + self._algorithm = algorithm + + def generate(self, counter: int) -> bytes: + if not isinstance(counter, int): + raise TypeError("Counter parameter must be an integer type.") + + truncated_value = self._dynamic_truncate(counter) + hotp = truncated_value % (10**self._length) + return "{0:0{1}}".format(hotp, self._length).encode() + + def verify(self, hotp: bytes, counter: int) -> None: + if not constant_time.bytes_eq(self.generate(counter), hotp): + raise InvalidToken("Supplied HOTP value does not match.") + + def _dynamic_truncate(self, counter: int) -> int: + ctx = hmac.HMAC(self._key, self._algorithm) + + try: + ctx.update(counter.to_bytes(length=8, byteorder="big")) + except OverflowError: + raise ValueError(f"Counter must be between 0 and {2**64 - 1}.") + + hmac_value = ctx.finalize() + + offset = hmac_value[len(hmac_value) - 1] & 0b1111 + p = hmac_value[offset : offset + 4] + return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF + + def get_provisioning_uri( + self, account_name: str, counter: int, issuer: str | None + ) -> str: + return _generate_uri( + self, "hotp", account_name, issuer, [("counter", int(counter))] + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py new file mode 100644 index 0000000..10c725c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py @@ -0,0 +1,56 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.hotp import ( + HOTP, + HOTPHashTypes, + _generate_uri, +) +from cryptography.utils import Buffer + + +class TOTP: + def __init__( + self, + key: Buffer, + length: int, + algorithm: HOTPHashTypes, + time_step: int, + backend: typing.Any = None, + enforce_key_length: bool = True, + ): + self._time_step = time_step + self._hotp = HOTP( + key, length, algorithm, enforce_key_length=enforce_key_length + ) + + def generate(self, time: int | float) -> bytes: + if not isinstance(time, (int, float)): + raise TypeError( + "Time parameter must be an integer type or float type." + ) + + counter = int(time / self._time_step) + return self._hotp.generate(counter) + + def verify(self, totp: bytes, time: int) -> None: + if not constant_time.bytes_eq(self.generate(time), totp): + raise InvalidToken("Supplied TOTP value does not match.") + + def get_provisioning_uri( + self, account_name: str, issuer: str | None + ) -> str: + return _generate_uri( + self._hotp, + "totp", + account_name, + issuer, + [("period", int(self._time_step))], + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py new file mode 100644 index 0000000..90bd42d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/utils.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import enum +import sys +import types +import typing +import warnings +from collections.abc import Sequence + + +# We use a UserWarning subclass, instead of DeprecationWarning, because CPython +# decided deprecation warnings should be invisible by default. +class CryptographyDeprecationWarning(UserWarning): + pass + + +# Several APIs were deprecated with no specific end-of-life date because of the +# ubiquity of their use. They should not be removed until we agree on when that +# cycle ends. +DeprecatedIn36 = CryptographyDeprecationWarning +DeprecatedIn40 = CryptographyDeprecationWarning +DeprecatedIn41 = CryptographyDeprecationWarning +DeprecatedIn42 = CryptographyDeprecationWarning +DeprecatedIn43 = CryptographyDeprecationWarning +DeprecatedIn47 = CryptographyDeprecationWarning +DeprecatedIn50 = CryptographyDeprecationWarning + + +# If you're wondering why we don't use `Buffer`, it's because `Buffer` would +# be more accurately named: Bufferable. It means something which has an +# `__buffer__`. Which means you can't actually treat the result as a buffer +# (and do things like take a `len()`). +Buffer = typing.Union[bytes, bytearray, memoryview] + + +def _check_bytes(name: str, value: bytes) -> None: + if not isinstance(value, bytes): + raise TypeError(f"{name} must be bytes") + + +def _check_byteslike(name: str, value: Buffer) -> None: + if isinstance(value, (bytes, bytearray, memoryview)): + return + try: + memoryview(value) + except TypeError: + raise TypeError(f"{name} must be bytes-like") + + +def int_to_bytes(integer: int, length: int | None = None) -> bytes: + if length == 0: + raise ValueError("length argument can't be 0") + return integer.to_bytes( + length or (integer.bit_length() + 7) // 8 or 1, "big" + ) + + +class InterfaceNotImplemented(Exception): + pass + + +class _DeprecatedValue: + def __init__(self, value: object, message: str, warning_class): + self.value = value + self.message = message + self.warning_class = warning_class + + +class _ModuleWithDeprecations(types.ModuleType): + def __init__(self, module: types.ModuleType): + super().__init__(module.__name__) + self.__dict__["_module"] = module + + def __getattr__(self, name: str) -> typing.Any: + obj = getattr(self._module, name) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + obj = obj.value + else: + # Cache non-deprecated attributes in our own `__dict__` so that + # subsequent lookups are ordinary module attribute accesses and + # don't pay for this `__getattr__` (which would otherwise defeat + # CPython's LOAD_ATTR module caching for every attribute of the + # module). `__setattr__` and `__delattr__` keep the cache + # coherent. + self.__dict__[name] = obj + return obj + + def __setattr__(self, attr: str, value: object) -> None: + if isinstance(value, _DeprecatedValue): + self.__dict__.pop(attr, None) + else: + self.__dict__[attr] = value + setattr(self._module, attr, value) + + def __delattr__(self, attr: str) -> None: + obj = getattr(self._module, attr) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + + self.__dict__.pop(attr, None) + delattr(self._module, attr) + + def __dir__(self) -> Sequence[str]: + return ["_module", *dir(self._module)] + + +def deprecated( + value: object, + module_name: str, + message: str, + warning_class: type[Warning], + name: str | None = None, +) -> _DeprecatedValue: + module = sys.modules[module_name] + if not isinstance(module, _ModuleWithDeprecations): + sys.modules[module_name] = module = _ModuleWithDeprecations(module) + dv = _DeprecatedValue(value, message, warning_class) + # Maintain backwards compatibility with `name is None` for pyOpenSSL. + if name is not None: + setattr(module, name, dv) + return dv + + +# Python 3.10 changed representation of enums. We use well-defined object +# representation and string representation from Python 3.9. +class Enum(enum.Enum): + def __repr__(self) -> str: + return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>" + + def __str__(self) -> str: + return f"{self.__class__.__name__}.{self._name_}" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py new file mode 100644 index 0000000..6442b00 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py @@ -0,0 +1,270 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.x509 import certificate_transparency, oid, verification +from cryptography.x509.base import ( + Attribute, + AttributeNotFound, + Attributes, + Certificate, + CertificateBuilder, + CertificateRevocationList, + CertificateRevocationListBuilder, + CertificateSigningRequest, + CertificateSigningRequestBuilder, + InvalidVersion, + RevokedCertificate, + RevokedCertificateBuilder, + Version, + load_der_x509_certificate, + load_der_x509_crl, + load_der_x509_csr, + load_pem_x509_certificate, + load_pem_x509_certificates, + load_pem_x509_crl, + load_pem_x509_csr, + random_serial_number, +) +from cryptography.x509.extensions import ( + AccessDescription, + Admission, + Admissions, + AuthorityInformationAccess, + AuthorityKeyIdentifier, + BasicConstraints, + CertificateIssuer, + CertificatePolicies, + CRLDistributionPoints, + CRLNumber, + CRLReason, + DeltaCRLIndicator, + DistributionPoint, + DuplicateExtension, + ExtendedKeyUsage, + Extension, + ExtensionNotFound, + Extensions, + ExtensionType, + FreshestCRL, + GeneralNames, + InhibitAnyPolicy, + InvalidityDate, + IssuerAlternativeName, + IssuingDistributionPoint, + KeyUsage, + MSCertificateTemplate, + NameConstraints, + NamingAuthority, + NoticeReference, + OCSPAcceptableResponses, + OCSPNoCheck, + OCSPNonce, + PolicyConstraints, + PolicyInformation, + PrecertificateSignedCertificateTimestamps, + PrecertPoison, + PrivateKeyUsagePeriod, + ProfessionInfo, + ReasonFlags, + SignedCertificateTimestamps, + SubjectAlternativeName, + SubjectInformationAccess, + SubjectKeyIdentifier, + TLSFeature, + TLSFeatureType, + UnrecognizedExtension, + UserNotice, +) +from cryptography.x509.general_name import ( + DirectoryName, + DNSName, + GeneralName, + IPAddress, + OtherName, + RegisteredID, + RFC822Name, + UniformResourceIdentifier, + UnsupportedGeneralNameType, +) +from cryptography.x509.name import ( + Name, + NameAttribute, + RelativeDistinguishedName, +) +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, + CertificatePoliciesOID, + CRLEntryExtensionOID, + ExtendedKeyUsageOID, + ExtensionOID, + NameOID, + ObjectIdentifier, + PublicKeyAlgorithmOID, + SignatureAlgorithmOID, +) + +OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS +OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER +OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS +OID_CERTIFICATE_POLICIES = ExtensionOID.CERTIFICATE_POLICIES +OID_CRL_DISTRIBUTION_POINTS = ExtensionOID.CRL_DISTRIBUTION_POINTS +OID_EXTENDED_KEY_USAGE = ExtensionOID.EXTENDED_KEY_USAGE +OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL +OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY +OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME +OID_KEY_USAGE = ExtensionOID.KEY_USAGE +OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD +OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS +OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK +OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS +OID_POLICY_MAPPINGS = ExtensionOID.POLICY_MAPPINGS +OID_SUBJECT_ALTERNATIVE_NAME = ExtensionOID.SUBJECT_ALTERNATIVE_NAME +OID_SUBJECT_DIRECTORY_ATTRIBUTES = ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES +OID_SUBJECT_INFORMATION_ACCESS = ExtensionOID.SUBJECT_INFORMATION_ACCESS +OID_SUBJECT_KEY_IDENTIFIER = ExtensionOID.SUBJECT_KEY_IDENTIFIER + +OID_DSA_WITH_SHA1 = SignatureAlgorithmOID.DSA_WITH_SHA1 +OID_DSA_WITH_SHA224 = SignatureAlgorithmOID.DSA_WITH_SHA224 +OID_DSA_WITH_SHA256 = SignatureAlgorithmOID.DSA_WITH_SHA256 +OID_ECDSA_WITH_SHA1 = SignatureAlgorithmOID.ECDSA_WITH_SHA1 +OID_ECDSA_WITH_SHA224 = SignatureAlgorithmOID.ECDSA_WITH_SHA224 +OID_ECDSA_WITH_SHA256 = SignatureAlgorithmOID.ECDSA_WITH_SHA256 +OID_ECDSA_WITH_SHA384 = SignatureAlgorithmOID.ECDSA_WITH_SHA384 +OID_ECDSA_WITH_SHA512 = SignatureAlgorithmOID.ECDSA_WITH_SHA512 +OID_RSA_WITH_MD5 = SignatureAlgorithmOID.RSA_WITH_MD5 +OID_RSA_WITH_SHA1 = SignatureAlgorithmOID.RSA_WITH_SHA1 +OID_RSA_WITH_SHA224 = SignatureAlgorithmOID.RSA_WITH_SHA224 +OID_RSA_WITH_SHA256 = SignatureAlgorithmOID.RSA_WITH_SHA256 +OID_RSA_WITH_SHA384 = SignatureAlgorithmOID.RSA_WITH_SHA384 +OID_RSA_WITH_SHA512 = SignatureAlgorithmOID.RSA_WITH_SHA512 +OID_RSASSA_PSS = SignatureAlgorithmOID.RSASSA_PSS + +OID_COMMON_NAME = NameOID.COMMON_NAME +OID_COUNTRY_NAME = NameOID.COUNTRY_NAME +OID_DOMAIN_COMPONENT = NameOID.DOMAIN_COMPONENT +OID_DN_QUALIFIER = NameOID.DN_QUALIFIER +OID_EMAIL_ADDRESS = NameOID.EMAIL_ADDRESS +OID_GENERATION_QUALIFIER = NameOID.GENERATION_QUALIFIER +OID_GIVEN_NAME = NameOID.GIVEN_NAME +OID_LOCALITY_NAME = NameOID.LOCALITY_NAME +OID_ORGANIZATIONAL_UNIT_NAME = NameOID.ORGANIZATIONAL_UNIT_NAME +OID_ORGANIZATION_NAME = NameOID.ORGANIZATION_NAME +OID_PSEUDONYM = NameOID.PSEUDONYM +OID_SERIAL_NUMBER = NameOID.SERIAL_NUMBER +OID_STATE_OR_PROVINCE_NAME = NameOID.STATE_OR_PROVINCE_NAME +OID_SURNAME = NameOID.SURNAME +OID_TITLE = NameOID.TITLE + +OID_CLIENT_AUTH = ExtendedKeyUsageOID.CLIENT_AUTH +OID_CODE_SIGNING = ExtendedKeyUsageOID.CODE_SIGNING +OID_EMAIL_PROTECTION = ExtendedKeyUsageOID.EMAIL_PROTECTION +OID_OCSP_SIGNING = ExtendedKeyUsageOID.OCSP_SIGNING +OID_SERVER_AUTH = ExtendedKeyUsageOID.SERVER_AUTH +OID_TIME_STAMPING = ExtendedKeyUsageOID.TIME_STAMPING + +OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY +OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER +OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE + +OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE + +OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS +OID_OCSP = AuthorityInformationAccessOID.OCSP + +__all__ = [ + "OID_CA_ISSUERS", + "OID_OCSP", + "AccessDescription", + "Admission", + "Admissions", + "Attribute", + "AttributeNotFound", + "Attributes", + "AuthorityInformationAccess", + "AuthorityKeyIdentifier", + "BasicConstraints", + "CRLDistributionPoints", + "CRLNumber", + "CRLReason", + "Certificate", + "CertificateBuilder", + "CertificateIssuer", + "CertificatePolicies", + "CertificateRevocationList", + "CertificateRevocationListBuilder", + "CertificateSigningRequest", + "CertificateSigningRequestBuilder", + "DNSName", + "DeltaCRLIndicator", + "DirectoryName", + "DistributionPoint", + "DuplicateExtension", + "ExtendedKeyUsage", + "Extension", + "ExtensionNotFound", + "ExtensionType", + "Extensions", + "FreshestCRL", + "GeneralName", + "GeneralNames", + "IPAddress", + "InhibitAnyPolicy", + "InvalidVersion", + "InvalidityDate", + "IssuerAlternativeName", + "IssuingDistributionPoint", + "KeyUsage", + "MSCertificateTemplate", + "Name", + "NameAttribute", + "NameConstraints", + "NameOID", + "NamingAuthority", + "NoticeReference", + "OCSPAcceptableResponses", + "OCSPNoCheck", + "OCSPNonce", + "ObjectIdentifier", + "OtherName", + "PolicyConstraints", + "PolicyInformation", + "PrecertPoison", + "PrecertificateSignedCertificateTimestamps", + "PrivateKeyUsagePeriod", + "ProfessionInfo", + "PublicKeyAlgorithmOID", + "RFC822Name", + "ReasonFlags", + "RegisteredID", + "RelativeDistinguishedName", + "RevokedCertificate", + "RevokedCertificateBuilder", + "SignatureAlgorithmOID", + "SignedCertificateTimestamps", + "SubjectAlternativeName", + "SubjectInformationAccess", + "SubjectKeyIdentifier", + "TLSFeature", + "TLSFeatureType", + "UniformResourceIdentifier", + "UnrecognizedExtension", + "UnsupportedGeneralNameType", + "UserNotice", + "Version", + "certificate_transparency", + "load_der_x509_certificate", + "load_der_x509_crl", + "load_der_x509_csr", + "load_pem_x509_certificate", + "load_pem_x509_certificates", + "load_pem_x509_crl", + "load_pem_x509_csr", + "oid", + "random_serial_number", + "verification", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py new file mode 100644 index 0000000..3ded5dc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py @@ -0,0 +1,834 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import datetime +import os +import typing +from collections.abc import Iterable + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + mldsa, + mlkem, + padding, + rsa, + x448, + x25519, +) +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, + CertificatePublicKeyTypes, +) +from cryptography.x509.extensions import ( + Extension, + ExtensionType, + _make_sequence_methods, +) +from cryptography.x509.name import Name, _ASN1Type +from cryptography.x509.oid import ObjectIdentifier + +_EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1) + +# This must be kept in sync with sign.rs's list of allowable types in +# identify_hash_type +_AllowedHashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, +] + + +class AttributeNotFound(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +def _reject_duplicate_extension( + extension: Extension[ExtensionType], + extensions: list[Extension[ExtensionType]], +) -> None: + # This is quadratic in the number of extensions + for e in extensions: + if e.oid == extension.oid: + raise ValueError("This extension has already been set.") + + +def _reject_duplicate_attribute( + oid: ObjectIdentifier, + attributes: list[tuple[ObjectIdentifier, bytes, int | None]], +) -> None: + # This is quadratic in the number of attributes + for attr_oid, _, _ in attributes: + if attr_oid == oid: + raise ValueError("This attribute has already been set.") + + +def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime: + """Normalizes a datetime to a naive datetime in UTC. + + time -- datetime to normalize. Assumed to be in UTC if not timezone + aware. + """ + if time.tzinfo is not None: + offset = time.utcoffset() + offset = offset if offset else datetime.timedelta() + return time.replace(tzinfo=None) - offset + else: + return time + + +class Attribute: + def __init__( + self, + oid: ObjectIdentifier, + value: bytes, + _type: int = _ASN1Type.UTF8String.value, + ) -> None: + self._oid = oid + self._value = value + self._type = _type + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Attribute): + return NotImplemented + + return ( + self.oid == other.oid + and self.value == other.value + and self._type == other._type + ) + + def __hash__(self) -> int: + return hash((self.oid, self.value, self._type)) + + +class Attributes: + def __init__( + self, + attributes: Iterable[Attribute], + ) -> None: + self._attributes = list(attributes) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_attributes") + + def __repr__(self) -> str: + return f"" + + def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute: + for attr in self: + if attr.oid == oid: + return attr + + raise AttributeNotFound(f"No {oid} attribute was found", oid) + + +class Version(utils.Enum): + v1 = 0 + v3 = 2 + + +class InvalidVersion(Exception): + def __init__(self, msg: str, parsed_version: int) -> None: + super().__init__(msg) + self.parsed_version = parsed_version + + +Certificate = rust_x509.Certificate +RevokedCertificate = rust_x509.RevokedCertificate + + +CertificateRevocationList = rust_x509.CertificateRevocationList +CertificateSigningRequest = rust_x509.CertificateSigningRequest + + +load_pem_x509_certificate = rust_x509.load_pem_x509_certificate +load_der_x509_certificate = rust_x509.load_der_x509_certificate + +load_pem_x509_certificates = rust_x509.load_pem_x509_certificates + +load_pem_x509_csr = rust_x509.load_pem_x509_csr +load_der_x509_csr = rust_x509.load_der_x509_csr + +load_pem_x509_crl = rust_x509.load_pem_x509_crl +load_der_x509_crl = rust_x509.load_der_x509_crl + + +class CertificateSigningRequestBuilder: + def __init__( + self, + subject_name: Name | None = None, + extensions: list[Extension[ExtensionType]] = [], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [], + ): + """ + Creates an empty X.509 certificate request (v1). + """ + self._subject_name = subject_name + self._extensions = extensions + self._attributes = attributes + + def subject_name(self, name: Name) -> CertificateSigningRequestBuilder: + """ + Sets the certificate requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._subject_name is not None: + raise ValueError("The subject name may only be set once.") + return CertificateSigningRequestBuilder( + name, self._extensions, self._attributes + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateSigningRequestBuilder: + """ + Adds an X.509 extension to the certificate request. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return CertificateSigningRequestBuilder( + self._subject_name, + [*self._extensions, extension], + self._attributes, + ) + + def add_attribute( + self, + oid: ObjectIdentifier, + value: bytes, + *, + _tag: _ASN1Type | None = None, + ) -> CertificateSigningRequestBuilder: + """ + Adds an X.509 attribute with an OID and associated value. + """ + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + + if not isinstance(value, bytes): + raise TypeError("value must be bytes") + + if _tag is not None and not isinstance(_tag, _ASN1Type): + raise TypeError("tag must be _ASN1Type") + + _reject_duplicate_attribute(oid, self._attributes) + + if _tag is not None: + tag = _tag.value + else: + tag = None + + return CertificateSigningRequestBuilder( + self._subject_name, + self._extensions, + [*self._attributes, (oid, value, tag)], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> CertificateSigningRequest: + """ + Signs the request using the requestor's private key. + """ + if self._subject_name is None: + raise ValueError("A CertificateSigningRequest must have a subject") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_csr( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + +class CertificateBuilder: + _extensions: list[Extension[ExtensionType]] + + def __init__( + self, + issuer_name: Name | None = None, + subject_name: Name | None = None, + public_key: CertificatePublicKeyTypes | None = None, + serial_number: int | None = None, + not_valid_before: datetime.datetime | None = None, + not_valid_after: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + public_key_rsa_padding: type[padding.PSS] | None = None, + ) -> None: + self._version = Version.v3 + self._issuer_name = issuer_name + self._subject_name = subject_name + self._public_key = public_key + self._serial_number = serial_number + self._not_valid_before = not_valid_before + self._not_valid_after = not_valid_after + self._extensions = extensions + self._public_key_rsa_padding = public_key_rsa_padding + + def issuer_name(self, name: Name) -> CertificateBuilder: + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._issuer_name is not None: + raise ValueError("The issuer name may only be set once.") + return CertificateBuilder( + name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + self._public_key_rsa_padding, + ) + + def subject_name(self, name: Name) -> CertificateBuilder: + """ + Sets the requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._subject_name is not None: + raise ValueError("The subject name may only be set once.") + return CertificateBuilder( + self._issuer_name, + name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + self._public_key_rsa_padding, + ) + + def public_key( + self, + key: CertificatePublicKeyTypes, + *, + rsa_padding: type[padding.PSS] | None = None, + ) -> CertificateBuilder: + """ + Sets the requestor's public key (as found in the signing request). + """ + if not isinstance( + key, + ( + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + mldsa.MLDSA44PublicKey, + mldsa.MLDSA65PublicKey, + mldsa.MLDSA87PublicKey, + mlkem.MLKEM768PublicKey, + mlkem.MLKEM1024PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, + ), + ): + raise TypeError( + "Expecting one of DSAPublicKey, RSAPublicKey," + " EllipticCurvePublicKey, Ed25519PublicKey," + " Ed448PublicKey, MLDSA44PublicKey, MLDSA65PublicKey," + " MLDSA87PublicKey, MLKEM768PublicKey, MLKEM1024PublicKey," + " X25519PublicKey or X448PublicKey." + ) + if rsa_padding is not None: + if rsa_padding is not padding.PSS: + raise TypeError( + "rsa_padding must be the PSS class, not an instance" + ) + if not isinstance(key, rsa.RSAPublicKey): + raise TypeError( + "rsa_padding is only supported with RSA public keys" + ) + if self._public_key is not None: + raise ValueError("The public key may only be set once.") + return CertificateBuilder( + self._issuer_name, + self._subject_name, + key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + rsa_padding, + ) + + def serial_number(self, number: int) -> CertificateBuilder: + """ + Sets the certificate serial number. + """ + if not isinstance(number, int): + raise TypeError("Serial number must be of integral type.") + if self._serial_number is not None: + raise ValueError("The serial number may only be set once.") + if number <= 0: + raise ValueError("The serial number should be positive.") + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if number.bit_length() >= 160: # As defined in RFC 5280 + raise ValueError( + "The serial number should not be more than 159 bits." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + self._public_key_rsa_padding, + ) + + def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder: + """ + Sets the certificate activation time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._not_valid_before is not None: + raise ValueError("The not valid before may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The not valid before date must be on or after" + " 1950 January 1)." + ) + if self._not_valid_after is not None and time > self._not_valid_after: + raise ValueError( + "The not valid before date must be before the not valid after " + "date." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + time, + self._not_valid_after, + self._extensions, + self._public_key_rsa_padding, + ) + + def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: + """ + Sets the certificate expiration time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._not_valid_after is not None: + raise ValueError("The not valid after may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The not valid after date must be on or after 1950 January 1." + ) + if ( + self._not_valid_before is not None + and time < self._not_valid_before + ): + raise ValueError( + "The not valid after date must be after the not valid before " + "date." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + time, + self._extensions, + self._public_key_rsa_padding, + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateBuilder: + """ + Adds an X.509 extension to the certificate. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + [*self._extensions, extension], + self._public_key_rsa_padding, + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> Certificate: + """ + Signs the certificate using the CA's private key. + """ + if self._subject_name is None: + raise ValueError("A certificate must have a subject name") + + if self._issuer_name is None: + raise ValueError("A certificate must have an issuer name") + + if self._serial_number is None: + raise ValueError("A certificate must have a serial number") + + if self._not_valid_before is None: + raise ValueError("A certificate must have a not valid before time") + + if self._not_valid_after is None: + raise ValueError("A certificate must have a not valid after time") + + if self._public_key is None: + raise ValueError("A certificate must have a public key") + + if private_key is None: + raise TypeError("Need private key to sign certificate") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_certificate( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + def create_unsigned(self) -> Certificate: + """ + Creates an unsigned certificate, per RFC 9925. + """ + if self._subject_name is None: + raise ValueError("A certificate must have a subject name") + + if self._issuer_name is None: + raise ValueError("A certificate must have an issuer name") + + if self._serial_number is None: + raise ValueError("A certificate must have a serial number") + + if self._not_valid_before is None: + raise ValueError("A certificate must have a not valid before time") + + if self._not_valid_after is None: + raise ValueError("A certificate must have a not valid after time") + + if self._public_key is None: + raise ValueError("A certificate must have a public key") + + return rust_x509.create_x509_certificate( + self, + private_key=None, + hash_algorithm=None, + rsa_padding=None, + ecdsa_deterministic=None, + ) + + +class CertificateRevocationListBuilder: + _extensions: list[Extension[ExtensionType]] + _revoked_certificates: list[RevokedCertificate] + + def __init__( + self, + issuer_name: Name | None = None, + last_update: datetime.datetime | None = None, + next_update: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + revoked_certificates: list[RevokedCertificate] = [], + ): + self._issuer_name = issuer_name + self._last_update = last_update + self._next_update = next_update + self._extensions = extensions + self._revoked_certificates = revoked_certificates + + def issuer_name( + self, issuer_name: Name + ) -> CertificateRevocationListBuilder: + if not isinstance(issuer_name, Name): + raise TypeError("Expecting x509.Name object.") + if self._issuer_name is not None: + raise ValueError("The issuer name may only be set once.") + return CertificateRevocationListBuilder( + issuer_name, + self._last_update, + self._next_update, + self._extensions, + self._revoked_certificates, + ) + + def last_update( + self, last_update: datetime.datetime + ) -> CertificateRevocationListBuilder: + if not isinstance(last_update, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._last_update is not None: + raise ValueError("Last update may only be set once.") + last_update = _convert_to_naive_utc_time(last_update) + if last_update < _EARLIEST_UTC_TIME: + raise ValueError( + "The last update date must be on or after 1950 January 1." + ) + if self._next_update is not None and last_update > self._next_update: + raise ValueError( + "The last update date must be before the next update date." + ) + return CertificateRevocationListBuilder( + self._issuer_name, + last_update, + self._next_update, + self._extensions, + self._revoked_certificates, + ) + + def next_update( + self, next_update: datetime.datetime + ) -> CertificateRevocationListBuilder: + if not isinstance(next_update, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._next_update is not None: + raise ValueError("Last update may only be set once.") + next_update = _convert_to_naive_utc_time(next_update) + if next_update < _EARLIEST_UTC_TIME: + raise ValueError( + "The last update date must be on or after 1950 January 1." + ) + if self._last_update is not None and next_update < self._last_update: + raise ValueError( + "The next update date must be after the last update date." + ) + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + next_update, + self._extensions, + self._revoked_certificates, + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateRevocationListBuilder: + """ + Adds an X.509 extension to the certificate revocation list. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + self._next_update, + [*self._extensions, extension], + self._revoked_certificates, + ) + + def add_revoked_certificate( + self, revoked_certificate: RevokedCertificate + ) -> CertificateRevocationListBuilder: + """ + Adds a revoked certificate to the CRL. + """ + if not isinstance(revoked_certificate, RevokedCertificate): + raise TypeError("Must be an instance of RevokedCertificate") + + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + self._next_update, + self._extensions, + [*self._revoked_certificates, revoked_certificate], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> CertificateRevocationList: + if self._issuer_name is None: + raise ValueError("A CRL must have an issuer name") + + if self._last_update is None: + raise ValueError("A CRL must have a last update time") + + if self._next_update is None: + raise ValueError("A CRL must have a next update time") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_crl( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + +class RevokedCertificateBuilder: + def __init__( + self, + serial_number: int | None = None, + revocation_date: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + ): + self._serial_number = serial_number + self._revocation_date = revocation_date + self._extensions = extensions + + def serial_number(self, number: int) -> RevokedCertificateBuilder: + if not isinstance(number, int): + raise TypeError("Serial number must be of integral type.") + if self._serial_number is not None: + raise ValueError("The serial number may only be set once.") + if number <= 0: + raise ValueError("The serial number should be positive") + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if number.bit_length() >= 160: # As defined in RFC 5280 + raise ValueError( + "The serial number should not be more than 159 bits." + ) + return RevokedCertificateBuilder( + number, self._revocation_date, self._extensions + ) + + def revocation_date( + self, time: datetime.datetime + ) -> RevokedCertificateBuilder: + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._revocation_date is not None: + raise ValueError("The revocation date may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The revocation date must be on or after 1950 January 1." + ) + return RevokedCertificateBuilder( + self._serial_number, time, self._extensions + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> RevokedCertificateBuilder: + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + return RevokedCertificateBuilder( + self._serial_number, + self._revocation_date, + [*self._extensions, extension], + ) + + def build(self, backend: typing.Any = None) -> RevokedCertificate: + if self._serial_number is None: + raise ValueError("A revoked certificate must have a serial number") + if self._revocation_date is None: + raise ValueError( + "A revoked certificate must have a revocation date" + ) + return rust_x509.create_revoked_certificate(self) + + +def random_serial_number() -> int: + return int.from_bytes(os.urandom(20), "big") >> 1 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py new file mode 100644 index 0000000..fb66cc6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py @@ -0,0 +1,35 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 + + +class LogEntryType(utils.Enum): + X509_CERTIFICATE = 0 + PRE_CERTIFICATE = 1 + + +class Version(utils.Enum): + v1 = 0 + + +class SignatureAlgorithm(utils.Enum): + """ + Signature algorithms that are valid for SCTs. + + These are exactly the same as SignatureAlgorithm in RFC 5246 (TLS 1.2). + + See: + """ + + ANONYMOUS = 0 + RSA = 1 + DSA = 2 + ECDSA = 3 + + +SignedCertificateTimestamp = rust_x509.Sct diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py new file mode 100644 index 0000000..dc7728e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py @@ -0,0 +1,2533 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import datetime +import hashlib +import ipaddress +import typing +from collections.abc import Iterable, Iterator + +from cryptography import utils +from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives import _serialization, constant_time +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, +) +from cryptography.x509.certificate_transparency import ( + SignedCertificateTimestamp, +) +from cryptography.x509.general_name import ( + DirectoryName, + DNSName, + GeneralName, + IPAddress, + OtherName, + RegisteredID, + RFC822Name, + UniformResourceIdentifier, + _IPAddressTypes, +) +from cryptography.x509.name import Name, RelativeDistinguishedName +from cryptography.x509.oid import ( + CRLEntryExtensionOID, + ExtensionOID, + ObjectIdentifier, + OCSPExtensionOID, +) + +ExtensionTypeVar = typing.TypeVar( + "ExtensionTypeVar", bound="ExtensionType", covariant=True +) + + +def _key_identifier_from_public_key( + public_key: CertificatePublicKeyTypes, +) -> bytes: + if isinstance(public_key, RSAPublicKey): + data = public_key.public_bytes( + _serialization.Encoding.DER, + _serialization.PublicFormat.PKCS1, + ) + elif isinstance(public_key, EllipticCurvePublicKey): + data = public_key.public_bytes( + _serialization.Encoding.X962, + _serialization.PublicFormat.UncompressedPoint, + ) + else: + # This is a very slow way to do this. + serialized = public_key.public_bytes( + _serialization.Encoding.DER, + _serialization.PublicFormat.SubjectPublicKeyInfo, + ) + data = asn1.parse_spki_for_data(serialized) + + return hashlib.sha1(data).digest() + + +def _make_sequence_methods(field_name: str): + def len_method(self) -> int: + return len(getattr(self, field_name)) + + def iter_method(self): + return iter(getattr(self, field_name)) + + def getitem_method(self, idx): + return getattr(self, field_name)[idx] + + return len_method, iter_method, getitem_method + + +class DuplicateExtension(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +class ExtensionNotFound(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +class ExtensionType(metaclass=abc.ABCMeta): + oid: typing.ClassVar[ObjectIdentifier] + + def public_bytes(self) -> bytes: + """ + Serializes the extension type to DER. + """ + raise NotImplementedError( + f"public_bytes is not implemented for extension type {self!r}" + ) + + +class Extensions: + def __init__(self, extensions: Iterable[Extension[ExtensionType]]) -> None: + self._extensions = list(extensions) + + def get_extension_for_oid( + self, oid: ObjectIdentifier + ) -> Extension[ExtensionType]: + for ext in self: + if ext.oid == oid: + return ext + + raise ExtensionNotFound(f"No {oid} extension was found", oid) + + def get_extension_for_class( + self, extclass: type[ExtensionTypeVar] + ) -> Extension[ExtensionTypeVar]: + if extclass is UnrecognizedExtension: + raise TypeError( + "UnrecognizedExtension can't be used with " + "get_extension_for_class because more than one instance of the" + " class may be present." + ) + + for ext in self: + if isinstance(ext.value, extclass): + return ext + + raise ExtensionNotFound( + f"No {extclass} extension was found", extclass.oid + ) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions") + + def __repr__(self) -> str: + return f"" + + +class CRLNumber(ExtensionType): + oid = ExtensionOID.CRL_NUMBER + + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): + raise TypeError("crl_number must be an integer") + + self._crl_number = crl_number + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLNumber): + return NotImplemented + + return self.crl_number == other.crl_number + + def __hash__(self) -> int: + return hash(self.crl_number) + + def __repr__(self) -> str: + return f"" + + @property + def crl_number(self) -> int: + return self._crl_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AuthorityKeyIdentifier(ExtensionType): + oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER + + def __init__( + self, + key_identifier: bytes | None, + authority_cert_issuer: Iterable[GeneralName] | None, + authority_cert_serial_number: int | None, + ) -> None: + if (authority_cert_issuer is None) != ( + authority_cert_serial_number is None + ): + raise ValueError( + "authority_cert_issuer and authority_cert_serial_number " + "must both be present or both None" + ) + + if authority_cert_issuer is not None: + authority_cert_issuer = list(authority_cert_issuer) + if not all( + isinstance(x, GeneralName) for x in authority_cert_issuer + ): + raise TypeError( + "authority_cert_issuer must be a list of GeneralName " + "objects" + ) + + if authority_cert_serial_number is not None and not isinstance( + authority_cert_serial_number, int + ): + raise TypeError("authority_cert_serial_number must be an integer") + + self._key_identifier = key_identifier + self._authority_cert_issuer = authority_cert_issuer + self._authority_cert_serial_number = authority_cert_serial_number + + # This takes a subset of CertificatePublicKeyTypes because an issuer + # cannot have an X25519/X448 key. This introduces some unfortunate + # asymmetry that requires typing users to explicitly + # narrow their type, but we should make this accurate and not just + # convenient. + @classmethod + def from_issuer_public_key( + cls, public_key: CertificateIssuerPublicKeyTypes + ) -> AuthorityKeyIdentifier: + digest = _key_identifier_from_public_key(public_key) + return cls( + key_identifier=digest, + authority_cert_issuer=None, + authority_cert_serial_number=None, + ) + + @classmethod + def from_issuer_subject_key_identifier( + cls, ski: SubjectKeyIdentifier + ) -> AuthorityKeyIdentifier: + return cls( + key_identifier=ski.digest, + authority_cert_issuer=None, + authority_cert_serial_number=None, + ) + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AuthorityKeyIdentifier): + return NotImplemented + + return ( + self.key_identifier == other.key_identifier + and self.authority_cert_issuer == other.authority_cert_issuer + and self.authority_cert_serial_number + == other.authority_cert_serial_number + ) + + def __hash__(self) -> int: + if self.authority_cert_issuer is None: + aci = None + else: + aci = tuple(self.authority_cert_issuer) + return hash( + (self.key_identifier, aci, self.authority_cert_serial_number) + ) + + @property + def key_identifier(self) -> bytes | None: + return self._key_identifier + + @property + def authority_cert_issuer( + self, + ) -> list[GeneralName] | None: + return self._authority_cert_issuer + + @property + def authority_cert_serial_number(self) -> int | None: + return self._authority_cert_serial_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SubjectKeyIdentifier(ExtensionType): + oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER + + def __init__(self, digest: bytes) -> None: + self._digest = digest + + @classmethod + def from_public_key( + cls, public_key: CertificatePublicKeyTypes + ) -> SubjectKeyIdentifier: + return cls(_key_identifier_from_public_key(public_key)) + + @property + def digest(self) -> bytes: + return self._digest + + @property + def key_identifier(self) -> bytes: + return self._digest + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectKeyIdentifier): + return NotImplemented + + return constant_time.bytes_eq(self.digest, other.digest) + + def __hash__(self) -> int: + return hash(self.digest) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AuthorityInformationAccess(ExtensionType): + oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS + + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AuthorityInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __hash__(self) -> int: + return hash(tuple(self._descriptions)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SubjectInformationAccess(ExtensionType): + oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS + + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __hash__(self) -> int: + return hash(tuple(self._descriptions)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AccessDescription: + def __init__( + self, access_method: ObjectIdentifier, access_location: GeneralName + ) -> None: + if not isinstance(access_method, ObjectIdentifier): + raise TypeError("access_method must be an ObjectIdentifier") + + if not isinstance(access_location, GeneralName): + raise TypeError("access_location must be a GeneralName") + + self._access_method = access_method + self._access_location = access_location + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AccessDescription): + return NotImplemented + + return ( + self.access_method == other.access_method + and self.access_location == other.access_location + ) + + def __hash__(self) -> int: + return hash((self.access_method, self.access_location)) + + @property + def access_method(self) -> ObjectIdentifier: + return self._access_method + + @property + def access_location(self) -> GeneralName: + return self._access_location + + +class BasicConstraints(ExtensionType): + oid = ExtensionOID.BASIC_CONSTRAINTS + + def __init__(self, ca: bool, path_length: int | None) -> None: + if not isinstance(ca, bool): + raise TypeError("ca must be a boolean value") + + if path_length is not None and not ca: + raise ValueError("path_length must be None when ca is False") + + if path_length is not None and ( + not isinstance(path_length, int) or path_length < 0 + ): + raise TypeError( + "path_length must be a non-negative integer or None" + ) + + self._ca = ca + self._path_length = path_length + + @property + def ca(self) -> bool: + return self._ca + + @property + def path_length(self) -> int | None: + return self._path_length + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BasicConstraints): + return NotImplemented + + return self.ca == other.ca and self.path_length == other.path_length + + def __hash__(self) -> int: + return hash((self.ca, self.path_length)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class DeltaCRLIndicator(ExtensionType): + oid = ExtensionOID.DELTA_CRL_INDICATOR + + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): + raise TypeError("crl_number must be an integer") + + self._crl_number = crl_number + + @property + def crl_number(self) -> int: + return self._crl_number + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DeltaCRLIndicator): + return NotImplemented + + return self.crl_number == other.crl_number + + def __hash__(self) -> int: + return hash(self.crl_number) + + def __repr__(self) -> str: + return f"" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CRLDistributionPoints(ExtensionType): + oid = ExtensionOID.CRL_DISTRIBUTION_POINTS + + def __init__( + self, distribution_points: Iterable[DistributionPoint] + ) -> None: + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_distribution_points" + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLDistributionPoints): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __hash__(self) -> int: + return hash(tuple(self._distribution_points)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class FreshestCRL(ExtensionType): + oid = ExtensionOID.FRESHEST_CRL + + def __init__( + self, distribution_points: Iterable[DistributionPoint] + ) -> None: + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_distribution_points" + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, FreshestCRL): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __hash__(self) -> int: + return hash(tuple(self._distribution_points)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class DistributionPoint: + def __init__( + self, + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + reasons: frozenset[ReasonFlags] | None, + crl_issuer: Iterable[GeneralName] | None, + ) -> None: + if full_name and relative_name: + raise ValueError( + "You cannot provide both full_name and relative_name, at " + "least one must be None." + ) + if not full_name and not relative_name and not crl_issuer: + raise ValueError( + "Either full_name, relative_name or crl_issuer must be " + "provided." + ) + + if full_name is not None: + full_name = list(full_name) + if not all(isinstance(x, GeneralName) for x in full_name): + raise TypeError( + "full_name must be a list of GeneralName objects" + ) + + if relative_name: + if not isinstance(relative_name, RelativeDistinguishedName): + raise TypeError( + "relative_name must be a RelativeDistinguishedName" + ) + + if crl_issuer is not None: + crl_issuer = list(crl_issuer) + if not all(isinstance(x, GeneralName) for x in crl_issuer): + raise TypeError( + "crl_issuer must be None or a list of general names" + ) + + if reasons and ( + not isinstance(reasons, frozenset) + or not all(isinstance(x, ReasonFlags) for x in reasons) + ): + raise TypeError("reasons must be None or frozenset of ReasonFlags") + + if reasons and ( + ReasonFlags.unspecified in reasons + or ReasonFlags.remove_from_crl in reasons + ): + raise ValueError( + "unspecified and remove_from_crl are not valid reasons in a " + "DistributionPoint" + ) + + self._full_name = full_name + self._relative_name = relative_name + self._reasons = reasons + self._crl_issuer = crl_issuer + + def __repr__(self) -> str: + return ( + "".format(self) + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DistributionPoint): + return NotImplemented + + return ( + self.full_name == other.full_name + and self.relative_name == other.relative_name + and self.reasons == other.reasons + and self.crl_issuer == other.crl_issuer + ) + + def __hash__(self) -> int: + if self.full_name is not None: + fn: tuple[GeneralName, ...] | None = tuple(self.full_name) + else: + fn = None + + if self.crl_issuer is not None: + crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer) + else: + crl_issuer = None + + return hash((fn, self.relative_name, self.reasons, crl_issuer)) + + @property + def full_name(self) -> list[GeneralName] | None: + return self._full_name + + @property + def relative_name(self) -> RelativeDistinguishedName | None: + return self._relative_name + + @property + def reasons(self) -> frozenset[ReasonFlags] | None: + return self._reasons + + @property + def crl_issuer(self) -> list[GeneralName] | None: + return self._crl_issuer + + +class ReasonFlags(utils.Enum): + unspecified = "unspecified" + key_compromise = "keyCompromise" + ca_compromise = "cACompromise" + affiliation_changed = "affiliationChanged" + superseded = "superseded" + cessation_of_operation = "cessationOfOperation" + certificate_hold = "certificateHold" + privilege_withdrawn = "privilegeWithdrawn" + aa_compromise = "aACompromise" + remove_from_crl = "removeFromCRL" + + +# These are distribution point bit string mappings. Not to be confused with +# CRLReason reason flags bit string mappings. +# ReasonFlags ::= BIT STRING { +# unused (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# privilegeWithdrawn (7), +# aACompromise (8) } +_REASON_BIT_MAPPING = { + 1: ReasonFlags.key_compromise, + 2: ReasonFlags.ca_compromise, + 3: ReasonFlags.affiliation_changed, + 4: ReasonFlags.superseded, + 5: ReasonFlags.cessation_of_operation, + 6: ReasonFlags.certificate_hold, + 7: ReasonFlags.privilege_withdrawn, + 8: ReasonFlags.aa_compromise, +} + +_CRLREASONFLAGS = { + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.privilege_withdrawn: 7, + ReasonFlags.aa_compromise: 8, +} + +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + ReasonFlags.unspecified: 0, + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.remove_from_crl: 8, + ReasonFlags.privilege_withdrawn: 9, + ReasonFlags.aa_compromise: 10, +} + + +class PolicyConstraints(ExtensionType): + oid = ExtensionOID.POLICY_CONSTRAINTS + + def __init__( + self, + require_explicit_policy: int | None, + inhibit_policy_mapping: int | None, + ) -> None: + if require_explicit_policy is not None and not isinstance( + require_explicit_policy, int + ): + raise TypeError( + "require_explicit_policy must be a non-negative integer or " + "None" + ) + + if inhibit_policy_mapping is not None and not isinstance( + inhibit_policy_mapping, int + ): + raise TypeError( + "inhibit_policy_mapping must be a non-negative integer or None" + ) + + if inhibit_policy_mapping is None and require_explicit_policy is None: + raise ValueError( + "At least one of require_explicit_policy and " + "inhibit_policy_mapping must not be None" + ) + + self._require_explicit_policy = require_explicit_policy + self._inhibit_policy_mapping = inhibit_policy_mapping + + def __repr__(self) -> str: + return ( + "".format(self) + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PolicyConstraints): + return NotImplemented + + return ( + self.require_explicit_policy == other.require_explicit_policy + and self.inhibit_policy_mapping == other.inhibit_policy_mapping + ) + + def __hash__(self) -> int: + return hash( + (self.require_explicit_policy, self.inhibit_policy_mapping) + ) + + @property + def require_explicit_policy(self) -> int | None: + return self._require_explicit_policy + + @property + def inhibit_policy_mapping(self) -> int | None: + return self._inhibit_policy_mapping + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CertificatePolicies(ExtensionType): + oid = ExtensionOID.CERTIFICATE_POLICIES + + def __init__(self, policies: Iterable[PolicyInformation]) -> None: + policies = list(policies) + if not all(isinstance(x, PolicyInformation) for x in policies): + raise TypeError( + "Every item in the policies list must be a PolicyInformation" + ) + + self._policies = policies + + __len__, __iter__, __getitem__ = _make_sequence_methods("_policies") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CertificatePolicies): + return NotImplemented + + return self._policies == other._policies + + def __hash__(self) -> int: + return hash(tuple(self._policies)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PolicyInformation: + def __init__( + self, + policy_identifier: ObjectIdentifier, + policy_qualifiers: Iterable[str | UserNotice] | None, + ) -> None: + if not isinstance(policy_identifier, ObjectIdentifier): + raise TypeError("policy_identifier must be an ObjectIdentifier") + + self._policy_identifier = policy_identifier + + if policy_qualifiers is not None: + policy_qualifiers = list(policy_qualifiers) + if not all( + isinstance(x, (str, UserNotice)) for x in policy_qualifiers + ): + raise TypeError( + "policy_qualifiers must be a list of strings and/or " + "UserNotice objects or None" + ) + + self._policy_qualifiers = policy_qualifiers + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PolicyInformation): + return NotImplemented + + return ( + self.policy_identifier == other.policy_identifier + and self.policy_qualifiers == other.policy_qualifiers + ) + + def __hash__(self) -> int: + if self.policy_qualifiers is not None: + pq = tuple(self.policy_qualifiers) + else: + pq = None + + return hash((self.policy_identifier, pq)) + + @property + def policy_identifier(self) -> ObjectIdentifier: + return self._policy_identifier + + @property + def policy_qualifiers( + self, + ) -> list[str | UserNotice] | None: + return self._policy_qualifiers + + +class UserNotice: + def __init__( + self, + notice_reference: NoticeReference | None, + explicit_text: str | None, + ) -> None: + if notice_reference and not isinstance( + notice_reference, NoticeReference + ): + raise TypeError( + "notice_reference must be None or a NoticeReference" + ) + + self._notice_reference = notice_reference + self._explicit_text = explicit_text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UserNotice): + return NotImplemented + + return ( + self.notice_reference == other.notice_reference + and self.explicit_text == other.explicit_text + ) + + def __hash__(self) -> int: + return hash((self.notice_reference, self.explicit_text)) + + @property + def notice_reference(self) -> NoticeReference | None: + return self._notice_reference + + @property + def explicit_text(self) -> str | None: + return self._explicit_text + + +class NoticeReference: + def __init__( + self, + organization: str | None, + notice_numbers: Iterable[int], + ) -> None: + self._organization = organization + notice_numbers = list(notice_numbers) + if not all(isinstance(x, int) for x in notice_numbers): + raise TypeError("notice_numbers must be a list of integers") + + self._notice_numbers = notice_numbers + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NoticeReference): + return NotImplemented + + return ( + self.organization == other.organization + and self.notice_numbers == other.notice_numbers + ) + + def __hash__(self) -> int: + return hash((self.organization, tuple(self.notice_numbers))) + + @property + def organization(self) -> str | None: + return self._organization + + @property + def notice_numbers(self) -> list[int]: + return self._notice_numbers + + +class ExtendedKeyUsage(ExtensionType): + oid = ExtensionOID.EXTENDED_KEY_USAGE + + def __init__(self, usages: Iterable[ObjectIdentifier]) -> None: + usages = list(usages) + if not all(isinstance(x, ObjectIdentifier) for x in usages): + raise TypeError( + "Every item in the usages list must be an ObjectIdentifier" + ) + + self._usages = usages + + __len__, __iter__, __getitem__ = _make_sequence_methods("_usages") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ExtendedKeyUsage): + return NotImplemented + + return self._usages == other._usages + + def __hash__(self) -> int: + return hash(tuple(self._usages)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPNoCheck(ExtensionType): + oid = ExtensionOID.OCSP_NO_CHECK + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPNoCheck): + return NotImplemented + + return True + + def __hash__(self) -> int: + return hash(OCSPNoCheck) + + def __repr__(self) -> str: + return "" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrecertPoison(ExtensionType): + oid = ExtensionOID.PRECERT_POISON + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrecertPoison): + return NotImplemented + + return True + + def __hash__(self) -> int: + return hash(PrecertPoison) + + def __repr__(self) -> str: + return "" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class TLSFeature(ExtensionType): + oid = ExtensionOID.TLS_FEATURE + + def __init__(self, features: Iterable[TLSFeatureType]) -> None: + features = list(features) + if ( + not all(isinstance(x, TLSFeatureType) for x in features) + or len(features) == 0 + ): + raise TypeError( + "features must be a list of elements from the TLSFeatureType " + "enum" + ) + + self._features = features + + __len__, __iter__, __getitem__ = _make_sequence_methods("_features") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TLSFeature): + return NotImplemented + + return self._features == other._features + + def __hash__(self) -> int: + return hash(tuple(self._features)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class TLSFeatureType(utils.Enum): + # status_request is defined in RFC 6066 and is used for what is commonly + # called OCSP Must-Staple when present in the TLS Feature extension in an + # X.509 certificate. + status_request = 5 + # status_request_v2 is defined in RFC 6961 and allows multiple OCSP + # responses to be provided. It is not currently in use by clients or + # servers. + status_request_v2 = 17 + + +_TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType} + + +class InhibitAnyPolicy(ExtensionType): + oid = ExtensionOID.INHIBIT_ANY_POLICY + + def __init__(self, skip_certs: int) -> None: + if not isinstance(skip_certs, int): + raise TypeError("skip_certs must be an integer") + + if skip_certs < 0: + raise ValueError("skip_certs must be a non-negative integer") + + self._skip_certs = skip_certs + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InhibitAnyPolicy): + return NotImplemented + + return self.skip_certs == other.skip_certs + + def __hash__(self) -> int: + return hash(self.skip_certs) + + @property + def skip_certs(self) -> int: + return self._skip_certs + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class KeyUsage(ExtensionType): + oid = ExtensionOID.KEY_USAGE + + def __init__( + self, + digital_signature: bool, + content_commitment: bool, + key_encipherment: bool, + data_encipherment: bool, + key_agreement: bool, + key_cert_sign: bool, + crl_sign: bool, + encipher_only: bool, + decipher_only: bool, + ) -> None: + if not key_agreement and (encipher_only or decipher_only): + raise ValueError( + "encipher_only and decipher_only can only be true when " + "key_agreement is true" + ) + + self._digital_signature = digital_signature + self._content_commitment = content_commitment + self._key_encipherment = key_encipherment + self._data_encipherment = data_encipherment + self._key_agreement = key_agreement + self._key_cert_sign = key_cert_sign + self._crl_sign = crl_sign + self._encipher_only = encipher_only + self._decipher_only = decipher_only + + @property + def digital_signature(self) -> bool: + return self._digital_signature + + @property + def content_commitment(self) -> bool: + return self._content_commitment + + @property + def key_encipherment(self) -> bool: + return self._key_encipherment + + @property + def data_encipherment(self) -> bool: + return self._data_encipherment + + @property + def key_agreement(self) -> bool: + return self._key_agreement + + @property + def key_cert_sign(self) -> bool: + return self._key_cert_sign + + @property + def crl_sign(self) -> bool: + return self._crl_sign + + @property + def encipher_only(self) -> bool: + if not self.key_agreement: + raise ValueError( + "encipher_only is undefined unless key_agreement is true" + ) + else: + return self._encipher_only + + @property + def decipher_only(self) -> bool: + if not self.key_agreement: + raise ValueError( + "decipher_only is undefined unless key_agreement is true" + ) + else: + return self._decipher_only + + def __repr__(self) -> str: + try: + encipher_only = self.encipher_only + decipher_only = self.decipher_only + except ValueError: + # Users found None confusing because even though encipher/decipher + # have no meaning unless key_agreement is true, to construct an + # instance of the class you still need to pass False. + encipher_only = False + decipher_only = False + + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, KeyUsage): + return NotImplemented + + return ( + self.digital_signature == other.digital_signature + and self.content_commitment == other.content_commitment + and self.key_encipherment == other.key_encipherment + and self.data_encipherment == other.data_encipherment + and self.key_agreement == other.key_agreement + and self.key_cert_sign == other.key_cert_sign + and self.crl_sign == other.crl_sign + and self._encipher_only == other._encipher_only + and self._decipher_only == other._decipher_only + ) + + def __hash__(self) -> int: + return hash( + ( + self.digital_signature, + self.content_commitment, + self.key_encipherment, + self.data_encipherment, + self.key_agreement, + self.key_cert_sign, + self.crl_sign, + self._encipher_only, + self._decipher_only, + ) + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrivateKeyUsagePeriod(ExtensionType): + oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD + + def __init__( + self, + not_before: datetime.datetime | None, + not_after: datetime.datetime | None, + ) -> None: + if ( + not isinstance(not_before, datetime.datetime) + and not_before is not None + ): + raise TypeError("not_before must be a datetime.datetime or None") + + if ( + not isinstance(not_after, datetime.datetime) + and not_after is not None + ): + raise TypeError("not_after must be a datetime.datetime or None") + + if not_before is None and not_after is None: + raise ValueError( + "At least one of not_before and not_after must not be None" + ) + + if ( + not_before is not None + and not_after is not None + and not_before > not_after + ): + raise ValueError("not_before must be before not_after") + + self._not_before = not_before + self._not_after = not_after + + @property + def not_before(self) -> datetime.datetime | None: + return self._not_before + + @property + def not_after(self) -> datetime.datetime | None: + return self._not_after + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrivateKeyUsagePeriod): + return NotImplemented + + return ( + self.not_before == other.not_before + and self.not_after == other.not_after + ) + + def __hash__(self) -> int: + return hash((self.not_before, self.not_after)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class NameConstraints(ExtensionType): + oid = ExtensionOID.NAME_CONSTRAINTS + + def __init__( + self, + permitted_subtrees: Iterable[GeneralName] | None, + excluded_subtrees: Iterable[GeneralName] | None, + ) -> None: + if permitted_subtrees is not None: + permitted_subtrees = list(permitted_subtrees) + if not permitted_subtrees: + raise ValueError( + "permitted_subtrees must be a non-empty list or None" + ) + if not all(isinstance(x, GeneralName) for x in permitted_subtrees): + raise TypeError( + "permitted_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_tree(permitted_subtrees) + + if excluded_subtrees is not None: + excluded_subtrees = list(excluded_subtrees) + if not excluded_subtrees: + raise ValueError( + "excluded_subtrees must be a non-empty list or None" + ) + if not all(isinstance(x, GeneralName) for x in excluded_subtrees): + raise TypeError( + "excluded_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_tree(excluded_subtrees) + + if permitted_subtrees is None and excluded_subtrees is None: + raise ValueError( + "At least one of permitted_subtrees and excluded_subtrees " + "must not be None" + ) + + self._permitted_subtrees = permitted_subtrees + self._excluded_subtrees = excluded_subtrees + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NameConstraints): + return NotImplemented + + return ( + self.excluded_subtrees == other.excluded_subtrees + and self.permitted_subtrees == other.permitted_subtrees + ) + + def _validate_tree(self, tree: Iterable[GeneralName]) -> None: + self._validate_ip_name(tree) + self._validate_dns_name(tree) + + def _validate_ip_name(self, tree: Iterable[GeneralName]) -> None: + if any( + isinstance(name, IPAddress) + and not isinstance( + name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network) + ) + for name in tree + ): + raise TypeError( + "IPAddress name constraints must be an IPv4Network or" + " IPv6Network object" + ) + + def _validate_dns_name(self, tree: Iterable[GeneralName]) -> None: + if any( + isinstance(name, DNSName) and "*" in name.value for name in tree + ): + raise ValueError( + "DNSName name constraints must not contain the '*' wildcard" + " character" + ) + + def __repr__(self) -> str: + return ( + f"" + ) + + def __hash__(self) -> int: + if self.permitted_subtrees is not None: + ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees) + else: + ps = None + + if self.excluded_subtrees is not None: + es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees) + else: + es = None + + return hash((ps, es)) + + @property + def permitted_subtrees( + self, + ) -> list[GeneralName] | None: + return self._permitted_subtrees + + @property + def excluded_subtrees( + self, + ) -> list[GeneralName] | None: + return self._excluded_subtrees + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class Extension(typing.Generic[ExtensionTypeVar]): + def __init__( + self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar + ) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + + if not isinstance(critical, bool): + raise TypeError("critical must be a boolean value") + + self._oid = oid + self._critical = critical + self._value = value + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def critical(self) -> bool: + return self._critical + + @property + def value(self) -> ExtensionTypeVar: + return self._value + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Extension): + return NotImplemented + + return ( + self.oid == other.oid + and self.critical == other.critical + and self.value == other.value + ) + + def __hash__(self) -> int: + return hash((self.oid, self.critical, self.value)) + + +class GeneralNames: + def __init__(self, general_names: Iterable[GeneralName]) -> None: + general_names = list(general_names) + if not all(isinstance(x, GeneralName) for x in general_names): + raise TypeError( + "Every item in the general_names list must be an " + "object conforming to the GeneralName interface" + ) + + self._general_names = general_names + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + # Return the value of each GeneralName, except for OtherName instances + # which we return directly because it has two important properties not + # just one value. + objs = (i for i in self if isinstance(i, type)) + if type != OtherName: + return [i.value for i in objs] # type: ignore[return-value,unused-ignore] + return list(objs) # type: ignore[return-value,unused-ignore] + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, GeneralNames): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(tuple(self._general_names)) + + +class SubjectAlternativeName(ExtensionType): + oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class IssuerAlternativeName(ExtensionType): + oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IssuerAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CertificateIssuer(ExtensionType): + oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CertificateIssuer): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CRLReason(ExtensionType): + oid = CRLEntryExtensionOID.CRL_REASON + + def __init__(self, reason: ReasonFlags) -> None: + if not isinstance(reason, ReasonFlags): + raise TypeError("reason must be an element from ReasonFlags") + + self._reason = reason + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLReason): + return NotImplemented + + return self.reason == other.reason + + def __hash__(self) -> int: + return hash(self.reason) + + @property + def reason(self) -> ReasonFlags: + return self._reason + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class InvalidityDate(ExtensionType): + oid = CRLEntryExtensionOID.INVALIDITY_DATE + + def __init__(self, invalidity_date: datetime.datetime) -> None: + if not isinstance(invalidity_date, datetime.datetime): + raise TypeError("invalidity_date must be a datetime.datetime") + + self._invalidity_date = invalidity_date + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InvalidityDate): + return NotImplemented + + return self.invalidity_date == other.invalidity_date + + def __hash__(self) -> int: + return hash(self.invalidity_date) + + @property + def invalidity_date(self) -> datetime.datetime: + return self._invalidity_date + + @property + def invalidity_date_utc(self) -> datetime.datetime: + if self._invalidity_date.tzinfo is None: + return self._invalidity_date.replace(tzinfo=datetime.timezone.utc) + else: + return self._invalidity_date.astimezone(tz=datetime.timezone.utc) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrecertificateSignedCertificateTimestamps(ExtensionType): + oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS + + def __init__( + self, + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], + ) -> None: + signed_certificate_timestamps = list(signed_certificate_timestamps) + if not all( + isinstance(sct, SignedCertificateTimestamp) + for sct in signed_certificate_timestamps + ): + raise TypeError( + "Every item in the signed_certificate_timestamps list must be " + "a SignedCertificateTimestamp" + ) + self._signed_certificate_timestamps = signed_certificate_timestamps + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_signed_certificate_timestamps" + ) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(tuple(self._signed_certificate_timestamps)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrecertificateSignedCertificateTimestamps): + return NotImplemented + + return ( + self._signed_certificate_timestamps + == other._signed_certificate_timestamps + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SignedCertificateTimestamps(ExtensionType): + oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS + + def __init__( + self, + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], + ) -> None: + signed_certificate_timestamps = list(signed_certificate_timestamps) + if not all( + isinstance(sct, SignedCertificateTimestamp) + for sct in signed_certificate_timestamps + ): + raise TypeError( + "Every item in the signed_certificate_timestamps list must be " + "a SignedCertificateTimestamp" + ) + self._signed_certificate_timestamps = signed_certificate_timestamps + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_signed_certificate_timestamps" + ) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(tuple(self._signed_certificate_timestamps)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SignedCertificateTimestamps): + return NotImplemented + + return ( + self._signed_certificate_timestamps + == other._signed_certificate_timestamps + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPNonce(ExtensionType): + oid = OCSPExtensionOID.NONCE + + def __init__(self, nonce: bytes) -> None: + if not isinstance(nonce, bytes): + raise TypeError("nonce must be bytes") + + self._nonce = nonce + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPNonce): + return NotImplemented + + return self.nonce == other.nonce + + def __hash__(self) -> int: + return hash(self.nonce) + + def __repr__(self) -> str: + return f"" + + @property + def nonce(self) -> bytes: + return self._nonce + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPAcceptableResponses(ExtensionType): + oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES + + def __init__(self, responses: Iterable[ObjectIdentifier]) -> None: + responses = list(responses) + if any(not isinstance(r, ObjectIdentifier) for r in responses): + raise TypeError("All responses must be ObjectIdentifiers") + + self._responses = responses + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPAcceptableResponses): + return NotImplemented + + return self._responses == other._responses + + def __hash__(self) -> int: + return hash(tuple(self._responses)) + + def __repr__(self) -> str: + return f"" + + def __iter__(self) -> Iterator[ObjectIdentifier]: + return iter(self._responses) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class IssuingDistributionPoint(ExtensionType): + oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT + + def __init__( + self, + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + only_contains_user_certs: bool, + only_contains_ca_certs: bool, + only_some_reasons: frozenset[ReasonFlags] | None, + indirect_crl: bool, + only_contains_attribute_certs: bool, + ) -> None: + if full_name is not None: + full_name = list(full_name) + + if only_some_reasons and ( + not isinstance(only_some_reasons, frozenset) + or not all(isinstance(x, ReasonFlags) for x in only_some_reasons) + ): + raise TypeError( + "only_some_reasons must be None or frozenset of ReasonFlags" + ) + + if only_some_reasons and ( + ReasonFlags.unspecified in only_some_reasons + or ReasonFlags.remove_from_crl in only_some_reasons + ): + raise ValueError( + "unspecified and remove_from_crl are not valid reasons in an " + "IssuingDistributionPoint" + ) + + if not ( + isinstance(only_contains_user_certs, bool) + and isinstance(only_contains_ca_certs, bool) + and isinstance(indirect_crl, bool) + and isinstance(only_contains_attribute_certs, bool) + ): + raise TypeError( + "only_contains_user_certs, only_contains_ca_certs, " + "indirect_crl and only_contains_attribute_certs " + "must all be boolean." + ) + + # Per RFC5280 Section 5.2.5, the Issuing Distribution Point extension + # in a CRL can have only one of onlyContainsUserCerts, + # onlyContainsCACerts, onlyContainsAttributeCerts set to TRUE. + crl_constraints = [ + only_contains_user_certs, + only_contains_ca_certs, + only_contains_attribute_certs, + ] + + if len([x for x in crl_constraints if x]) > 1: + raise ValueError( + "Only one of the following can be set to True: " + "only_contains_user_certs, only_contains_ca_certs, " + "only_contains_attribute_certs" + ) + + if not any( + [ + only_contains_user_certs, + only_contains_ca_certs, + indirect_crl, + only_contains_attribute_certs, + full_name, + relative_name, + only_some_reasons, + ] + ): + raise ValueError( + "Cannot create empty extension: " + "if only_contains_user_certs, only_contains_ca_certs, " + "indirect_crl, and only_contains_attribute_certs are all False" + ", then either full_name, relative_name, or only_some_reasons " + "must have a value." + ) + + self._only_contains_user_certs = only_contains_user_certs + self._only_contains_ca_certs = only_contains_ca_certs + self._indirect_crl = indirect_crl + self._only_contains_attribute_certs = only_contains_attribute_certs + self._only_some_reasons = only_some_reasons + self._full_name = full_name + self._relative_name = relative_name + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IssuingDistributionPoint): + return NotImplemented + + return ( + self.full_name == other.full_name + and self.relative_name == other.relative_name + and self.only_contains_user_certs == other.only_contains_user_certs + and self.only_contains_ca_certs == other.only_contains_ca_certs + and self.only_some_reasons == other.only_some_reasons + and self.indirect_crl == other.indirect_crl + and self.only_contains_attribute_certs + == other.only_contains_attribute_certs + ) + + def __hash__(self) -> int: + if self.full_name is not None: + full_name: tuple[GeneralName, ...] | None = tuple(self.full_name) + else: + full_name = None + + return hash( + ( + full_name, + self.relative_name, + self.only_contains_user_certs, + self.only_contains_ca_certs, + self.only_some_reasons, + self.indirect_crl, + self.only_contains_attribute_certs, + ) + ) + + @property + def full_name(self) -> list[GeneralName] | None: + return self._full_name + + @property + def relative_name(self) -> RelativeDistinguishedName | None: + return self._relative_name + + @property + def only_contains_user_certs(self) -> bool: + return self._only_contains_user_certs + + @property + def only_contains_ca_certs(self) -> bool: + return self._only_contains_ca_certs + + @property + def only_some_reasons( + self, + ) -> frozenset[ReasonFlags] | None: + return self._only_some_reasons + + @property + def indirect_crl(self) -> bool: + return self._indirect_crl + + @property + def only_contains_attribute_certs(self) -> bool: + return self._only_contains_attribute_certs + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class MSCertificateTemplate(ExtensionType): + oid = ExtensionOID.MS_CERTIFICATE_TEMPLATE + + def __init__( + self, + template_id: ObjectIdentifier, + major_version: int | None, + minor_version: int | None, + ) -> None: + if not isinstance(template_id, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._template_id = template_id + if ( + major_version is not None and not isinstance(major_version, int) + ) or ( + minor_version is not None and not isinstance(minor_version, int) + ): + raise TypeError( + "major_version and minor_version must be integers or None" + ) + self._major_version = major_version + self._minor_version = minor_version + + @property + def template_id(self) -> ObjectIdentifier: + return self._template_id + + @property + def major_version(self) -> int | None: + return self._major_version + + @property + def minor_version(self) -> int | None: + return self._minor_version + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MSCertificateTemplate): + return NotImplemented + + return ( + self.template_id == other.template_id + and self.major_version == other.major_version + and self.minor_version == other.minor_version + ) + + def __hash__(self) -> int: + return hash((self.template_id, self.major_version, self.minor_version)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class NamingAuthority: + def __init__( + self, + id: ObjectIdentifier | None, + url: str | None, + text: str | None, + ) -> None: + if id is not None and not isinstance(id, ObjectIdentifier): + raise TypeError("id must be an ObjectIdentifier") + + if url is not None and not isinstance(url, str): + raise TypeError("url must be a str") + + if text is not None and not isinstance(text, str): + raise TypeError("text must be a str") + + self._id = id + self._url = url + self._text = text + + @property + def id(self) -> ObjectIdentifier | None: + return self._id + + @property + def url(self) -> str | None: + return self._url + + @property + def text(self) -> str | None: + return self._text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamingAuthority): + return NotImplemented + + return ( + self.id == other.id + and self.url == other.url + and self.text == other.text + ) + + def __hash__(self) -> int: + return hash( + ( + self.id, + self.url, + self.text, + ) + ) + + +class ProfessionInfo: + def __init__( + self, + naming_authority: NamingAuthority | None, + profession_items: Iterable[str], + profession_oids: Iterable[ObjectIdentifier] | None, + registration_number: str | None, + add_profession_info: bytes | None, + ) -> None: + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_items = list(profession_items) + if not all(isinstance(item, str) for item in profession_items): + raise TypeError( + "Every item in the profession_items list must be a str" + ) + + if profession_oids is not None: + profession_oids = list(profession_oids) + if not all( + isinstance(oid, ObjectIdentifier) for oid in profession_oids + ): + raise TypeError( + "Every item in the profession_oids list must be an " + "ObjectIdentifier" + ) + + if registration_number is not None and not isinstance( + registration_number, str + ): + raise TypeError("registration_number must be a str") + + if add_profession_info is not None and not isinstance( + add_profession_info, bytes + ): + raise TypeError("add_profession_info must be bytes") + + self._naming_authority = naming_authority + self._profession_items = profession_items + self._profession_oids = profession_oids + self._registration_number = registration_number + self._add_profession_info = add_profession_info + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_items(self) -> list[str]: + return self._profession_items + + @property + def profession_oids(self) -> list[ObjectIdentifier] | None: + return self._profession_oids + + @property + def registration_number(self) -> str | None: + return self._registration_number + + @property + def add_profession_info(self) -> bytes | None: + return self._add_profession_info + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ProfessionInfo): + return NotImplemented + + return ( + self.naming_authority == other.naming_authority + and self.profession_items == other.profession_items + and self.profession_oids == other.profession_oids + and self.registration_number == other.registration_number + and self.add_profession_info == other.add_profession_info + ) + + def __hash__(self) -> int: + if self.profession_oids is not None: + profession_oids = tuple(self.profession_oids) + else: + profession_oids = None + return hash( + ( + self.naming_authority, + tuple(self.profession_items), + profession_oids, + self.registration_number, + self.add_profession_info, + ) + ) + + +class Admission: + def __init__( + self, + admission_authority: GeneralName | None, + naming_authority: NamingAuthority | None, + profession_infos: Iterable[ProfessionInfo], + ) -> None: + if admission_authority is not None and not isinstance( + admission_authority, GeneralName + ): + raise TypeError("admission_authority must be a GeneralName") + + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_infos = list(profession_infos) + if not all( + isinstance(info, ProfessionInfo) for info in profession_infos + ): + raise TypeError( + "Every item in the profession_infos list must be a " + "ProfessionInfo" + ) + + self._admission_authority = admission_authority + self._naming_authority = naming_authority + self._profession_infos = profession_infos + + @property + def admission_authority(self) -> GeneralName | None: + return self._admission_authority + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_infos(self) -> list[ProfessionInfo]: + return self._profession_infos + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admission): + return NotImplemented + + return ( + self.admission_authority == other.admission_authority + and self.naming_authority == other.naming_authority + and self.profession_infos == other.profession_infos + ) + + def __hash__(self) -> int: + return hash( + ( + self.admission_authority, + self.naming_authority, + tuple(self.profession_infos), + ) + ) + + +class Admissions(ExtensionType): + oid = ExtensionOID.ADMISSIONS + + def __init__( + self, + authority: GeneralName | None, + admissions: Iterable[Admission], + ) -> None: + if authority is not None and not isinstance(authority, GeneralName): + raise TypeError("authority must be a GeneralName") + + admissions = list(admissions) + if not all( + isinstance(admission, Admission) for admission in admissions + ): + raise TypeError( + "Every item in the contents_of_admissions list must be an " + "Admission" + ) + + self._authority = authority + self._admissions = admissions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_admissions") + + @property + def authority(self) -> GeneralName | None: + return self._authority + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admissions): + return NotImplemented + + return ( + self.authority == other.authority + and self._admissions == other._admissions + ) + + def __hash__(self) -> int: + return hash((self.authority, tuple(self._admissions))) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class UnrecognizedExtension(ExtensionType): + def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._oid = oid + self._value = value + + @property + def oid(self) -> ObjectIdentifier: # type: ignore[override] + return self._oid + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UnrecognizedExtension): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __hash__(self) -> int: + return hash((self.oid, self.value)) + + def public_bytes(self) -> bytes: + return self.value diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py new file mode 100644 index 0000000..672f287 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py @@ -0,0 +1,281 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import ipaddress +import typing +from email.utils import parseaddr + +from cryptography.x509.name import Name +from cryptography.x509.oid import ObjectIdentifier + +_IPAddressTypes = typing.Union[ + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network, +] + + +class UnsupportedGeneralNameType(Exception): + pass + + +class GeneralName(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def value(self) -> typing.Any: + """ + Return the value of the object + """ + + +class RFC822Name(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "RFC822Name values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + name, address = parseaddr(value) + if name or not address: + # parseaddr has found a name (e.g. Name ) or the entire + # value is an empty string. + raise ValueError("Invalid rfc822name value") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> RFC822Name: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RFC822Name): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class DNSName(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "DNSName values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> DNSName: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DNSName): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class UniformResourceIdentifier(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "URI values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> UniformResourceIdentifier: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UniformResourceIdentifier): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class DirectoryName(GeneralName): + def __init__(self, value: Name) -> None: + if not isinstance(value, Name): + raise TypeError("value must be a Name") + + self._value = value + + @property + def value(self) -> Name: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DirectoryName): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class RegisteredID(GeneralName): + def __init__(self, value: ObjectIdentifier) -> None: + if not isinstance(value, ObjectIdentifier): + raise TypeError("value must be an ObjectIdentifier") + + self._value = value + + @property + def value(self) -> ObjectIdentifier: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RegisteredID): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class IPAddress(GeneralName): + def __init__(self, value: _IPAddressTypes) -> None: + if not isinstance( + value, + ( + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network, + ), + ): + raise TypeError( + "value must be an instance of ipaddress.IPv4Address, " + "ipaddress.IPv6Address, ipaddress.IPv4Network, or " + "ipaddress.IPv6Network" + ) + + self._value = value + + @property + def value(self) -> _IPAddressTypes: + return self._value + + def _packed(self) -> bytes: + if isinstance( + self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address) + ): + return self.value.packed + else: + return ( + self.value.network_address.packed + self.value.netmask.packed + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IPAddress): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class OtherName(GeneralName): + def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None: + if not isinstance(type_id, ObjectIdentifier): + raise TypeError("type_id must be an ObjectIdentifier") + if not isinstance(value, bytes): + raise TypeError("value must be a binary string") + + self._type_id = type_id + self._value = value + + @property + def type_id(self) -> ObjectIdentifier: + return self._type_id + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OtherName): + return NotImplemented + + return self.type_id == other.type_id and self.value == other.value + + def __hash__(self) -> int: + return hash((self.type_id, self.value)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py new file mode 100644 index 0000000..2e54edc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py @@ -0,0 +1,489 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import binascii +import re +import sys +import typing +import warnings +from collections.abc import Iterable, Iterator + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.oid import NameOID, ObjectIdentifier + + +class _ASN1Type(utils.Enum): + BitString = 3 + OctetString = 4 + UTF8String = 12 + NumericString = 18 + PrintableString = 19 + T61String = 20 + IA5String = 22 + UTCTime = 23 + GeneralizedTime = 24 + VisibleString = 26 + UniversalString = 28 + BMPString = 30 + + +_ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} +_NAMEOID_DEFAULT_TYPE: dict[ObjectIdentifier, _ASN1Type] = { + NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, + NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, + NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, + NameOID.DN_QUALIFIER: _ASN1Type.PrintableString, + NameOID.EMAIL_ADDRESS: _ASN1Type.IA5String, + NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String, +} + +# Type alias +_OidNameMap = typing.Mapping[ObjectIdentifier, str] +_NameOidMap = typing.Mapping[str, ObjectIdentifier] + +#: Short attribute names from RFC 4514: +#: https://tools.ietf.org/html/rfc4514#page-7 +_NAMEOID_TO_NAME: _OidNameMap = { + NameOID.COMMON_NAME: "CN", + NameOID.LOCALITY_NAME: "L", + NameOID.STATE_OR_PROVINCE_NAME: "ST", + NameOID.ORGANIZATION_NAME: "O", + NameOID.ORGANIZATIONAL_UNIT_NAME: "OU", + NameOID.COUNTRY_NAME: "C", + NameOID.STREET_ADDRESS: "STREET", + NameOID.DOMAIN_COMPONENT: "DC", + NameOID.USER_ID: "UID", +} +_NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} + +_NAMEOID_LENGTH_LIMIT = { + NameOID.COUNTRY_NAME: (2, 2), + NameOID.JURISDICTION_COUNTRY_NAME: (2, 2), + NameOID.COMMON_NAME: (1, 64), +} + + +def _escape_dn_value(val: str | bytes) -> str: + """Escape special characters in RFC4514 Distinguished Name value.""" + + if not val: + return "" + + # RFC 4514 Section 2.4 defines the value as being the # (U+0023) character + # followed by the hexadecimal encoding of the octets. + if isinstance(val, bytes): + return "#" + binascii.hexlify(val).decode("utf8") + + # See https://tools.ietf.org/html/rfc4514#section-2.4 + val = val.replace("\\", "\\\\") + val = val.replace('"', '\\"') + val = val.replace("+", "\\+") + val = val.replace(",", "\\,") + val = val.replace(";", "\\;") + val = val.replace("<", "\\<") + val = val.replace(">", "\\>") + val = val.replace("\0", "\\00") + + if val[0] == "#" or (val[0] == " " and len(val) > 1): + val = "\\" + val + if val[-1] == " ": + val = val[:-1] + "\\ " + + return val + + +def _unescape_dn_value(val: str) -> str: + if not val: + return "" + + # See https://tools.ietf.org/html/rfc4514#section-3 + + # special = escaped / SPACE / SHARP / EQUALS + # escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE + def sub(m): + val = m.group(0) + # Special character escape + if len(val) == 2: + return val[1:] + + # Unicode string of hex + return binascii.unhexlify(val.replace("\\", "")).decode() + + return _RFC4514NameParser._PAIR_MULTI_RE.sub(sub, val) + + +NameAttributeValueType = typing.TypeVar( + "NameAttributeValueType", + typing.Union[str, bytes], + str, + bytes, + covariant=True, +) + + +class NameAttribute(typing.Generic[NameAttributeValueType]): + def __init__( + self, + oid: ObjectIdentifier, + value: NameAttributeValueType, + _type: _ASN1Type | None = None, + *, + _validate: bool = True, + ) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + if _type == _ASN1Type.BitString: + if oid != NameOID.X500_UNIQUE_IDENTIFIER: + raise TypeError( + "oid must be X500_UNIQUE_IDENTIFIER for BitString type." + ) + if not isinstance(value, bytes): + raise TypeError("value must be bytes for BitString") + elif not isinstance(value, str): + raise TypeError("value argument must be a str") + + length_limits = _NAMEOID_LENGTH_LIMIT.get(oid) + if length_limits is not None: + min_length, max_length = length_limits + assert isinstance(value, str) + c_len = len(value.encode("utf8")) + if c_len < min_length or c_len > max_length: + msg = ( + f"Attribute's length must be >= {min_length} and " + f"<= {max_length}, but it was {c_len}" + ) + if _validate is True: + raise ValueError(msg) + else: + warnings.warn(msg, stacklevel=2) + + # The appropriate ASN1 string type varies by OID and is defined across + # multiple RFCs including 2459, 3280, and 5280. In general UTF8String + # is preferred (2459), but 3280 and 5280 specify several OIDs with + # alternate types. This means when we see the sentinel value we need + # to look up whether the OID has a non-UTF8 type. If it does, set it + # to that. Otherwise, UTF8! + if _type is None: + _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String) + + if not isinstance(_type, _ASN1Type): + raise TypeError("_type must be from the _ASN1Type enum") + + self._oid = oid + self._value: NameAttributeValueType = value + self._type: _ASN1Type = _type + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> NameAttributeValueType: + return self._value + + @property + def rfc4514_attribute_name(self) -> str: + """ + The short attribute name (for example "CN") if available, + otherwise the OID dotted string. + """ + return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + + Use short attribute name if available, otherwise fall back to OID + dotted string. + """ + attr_name = ( + attr_name_overrides.get(self.oid) if attr_name_overrides else None + ) + if attr_name is None: + attr_name = self.rfc4514_attribute_name + + return f"{attr_name}={_escape_dn_value(self.value)}" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NameAttribute): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __hash__(self) -> int: + return hash((self.oid, self.value)) + + def __repr__(self) -> str: + return f"" + + +class RelativeDistinguishedName: + def __init__(self, attributes: Iterable[NameAttribute[str | bytes]]): + attributes = list(attributes) + if not attributes: + raise ValueError("a relative distinguished name cannot be empty") + if not all(isinstance(x, NameAttribute) for x in attributes): + raise TypeError("attributes must be an iterable of NameAttribute") + + # Keep list and frozenset to preserve attribute order where it matters + self._attributes = attributes + self._attribute_set = frozenset(attributes) + + if len(self._attribute_set) != len(attributes): + raise ValueError("duplicate attributes are not allowed") + + def get_attributes_for_oid( + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: + return [i for i in self if i.oid == oid] + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + + Within each RDN, attributes are joined by '+', although that is rarely + used in certificates. + """ + return "+".join( + attr.rfc4514_string(attr_name_overrides) + for attr in self._attributes + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RelativeDistinguishedName): + return NotImplemented + + return self._attribute_set == other._attribute_set + + def __hash__(self) -> int: + return hash(self._attribute_set) + + def __iter__(self) -> Iterator[NameAttribute[str | bytes]]: + return iter(self._attributes) + + def __len__(self) -> int: + return len(self._attributes) + + def __repr__(self) -> str: + return f"" + + +class Name: + @typing.overload + def __init__( + self, attributes: Iterable[NameAttribute[str | bytes]] + ) -> None: ... + + @typing.overload + def __init__( + self, attributes: Iterable[RelativeDistinguishedName] + ) -> None: ... + + def __init__( + self, + attributes: Iterable[ + NameAttribute[str | bytes] | RelativeDistinguishedName + ], + ) -> None: + attributes = list(attributes) + if all(isinstance(x, NameAttribute) for x in attributes): + self._attributes = [ + RelativeDistinguishedName([typing.cast(NameAttribute, x)]) + for x in attributes + ] + elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): + self._attributes = typing.cast( + list[RelativeDistinguishedName], attributes + ) + else: + raise TypeError( + "attributes must be a list of NameAttribute" + " or a list RelativeDistinguishedName" + ) + + @classmethod + def from_rfc4514_string( + cls, + data: str, + attr_name_overrides: _NameOidMap | None = None, + ) -> Name: + return _RFC4514NameParser(data, attr_name_overrides or {}).parse() + + @classmethod + def from_bytes(cls, data: bytes) -> Name: + """ + Parse a DER encoded X.509 name (the inverse of ``public_bytes``). + """ + return rust_x509.parse_name_bytes(data) + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + For example 'CN=foobar.com,O=Foo Corp,C=US' + + An X.509 name is a two-level structure: a list of sets of attributes. + Each list element is separated by ',' and within each list element, set + elements are separated by '+'. The latter is almost never used in + real world certificates. According to RFC4514 section 2.1 the + RDNSequence must be reversed when converting to string representation. + """ + return ",".join( + attr.rfc4514_string(attr_name_overrides) + for attr in reversed(self._attributes) + ) + + def get_attributes_for_oid( + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: + return [i for i in self if i.oid == oid] + + @property + def rdns(self) -> list[RelativeDistinguishedName]: + return self._attributes + + def public_bytes(self, backend: typing.Any = None) -> bytes: + return rust_x509.encode_name_bytes(self) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Name): + return NotImplemented + + return self._attributes == other._attributes + + def __hash__(self) -> int: + # TODO: this is relatively expensive, if this looks like a bottleneck + # for you, consider optimizing! + return hash(tuple(self._attributes)) + + def __iter__(self) -> Iterator[NameAttribute[str | bytes]]: + for rdn in self._attributes: + yield from rdn + + def __len__(self) -> int: + return sum(len(rdn) for rdn in self._attributes) + + def __repr__(self) -> str: + return f"" + + +class _RFC4514NameParser: + _OID_RE = re.compile(r"(0|([1-9]\d*))(\.(0|([1-9]\d*)))+") + _DESCR_RE = re.compile(r"[a-zA-Z][a-zA-Z\d-]*") + + _ESCAPE_SPECIAL = r"[\\ #=\"\+,;<>]" + _ESCAPE_HEX = r"[\da-zA-Z]{2}" + _PAIR = rf"\\({_ESCAPE_SPECIAL}|{_ESCAPE_HEX})" + _PAIR_MULTI_RE = re.compile(rf"(\\{_ESCAPE_SPECIAL})|((\\{_ESCAPE_HEX})+)") + _LUTF1 = r"[\x01-\x1f\x21\x24-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _SUTF1 = r"[\x01-\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _TUTF1 = r"[\x01-\x1F\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _UTFMB = rf"[\x80-{chr(sys.maxunicode)}]" + _LEADCHAR = rf"{_LUTF1}|{_UTFMB}" + _STRINGCHAR = rf"{_SUTF1}|{_UTFMB}" + _TRAILCHAR = rf"{_TUTF1}|{_UTFMB}" + _STRING_RE = re.compile( + rf""" + ( + ({_LEADCHAR}|{_PAIR}) + ( + ({_STRINGCHAR}|{_PAIR})* + ({_TRAILCHAR}|{_PAIR}) + )? + )? + """, + re.VERBOSE, + ) + _HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+") + + def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None: + self._data = data + self._idx = 0 + + self._attr_name_overrides = attr_name_overrides + + def _has_data(self) -> bool: + return self._idx < len(self._data) + + def _peek(self) -> str | None: + if self._has_data(): + return self._data[self._idx] + return None + + def _read_char(self, ch: str) -> None: + if self._peek() != ch: + raise ValueError + self._idx += 1 + + def _read_re(self, pat) -> str: + match = pat.match(self._data, pos=self._idx) + if match is None: + raise ValueError + val = match.group() + self._idx += len(val) + return val + + def parse(self) -> Name: + """ + Parses the `data` string and converts it to a Name. + + According to RFC4514 section 2.1 the RDNSequence must be + reversed when converting to string representation. So, when + we parse it, we need to reverse again to get the RDNs on the + correct order. + """ + + if not self._has_data(): + return Name([]) + + rdns = [self._parse_rdn()] + + while self._has_data(): + self._read_char(",") + rdns.append(self._parse_rdn()) + + return Name(reversed(rdns)) + + def _parse_rdn(self) -> RelativeDistinguishedName: + nas = [self._parse_na()] + while self._peek() == "+": + self._read_char("+") + nas.append(self._parse_na()) + + return RelativeDistinguishedName(nas) + + def _parse_na(self) -> NameAttribute[str]: + try: + oid_value = self._read_re(self._OID_RE) + except ValueError: + name = self._read_re(self._DESCR_RE) + oid = self._attr_name_overrides.get( + name, _NAME_TO_NAMEOID.get(name) + ) + if oid is None: + raise ValueError + else: + oid = ObjectIdentifier(oid_value) + + self._read_char("=") + if self._peek() == "#": + value = self._read_re(self._HEXSTRING_RE) + value = binascii.unhexlify(value[1:]).decode() + else: + raw_value = self._read_re(self._STRING_RE) + value = _unescape_dn_value(raw_value) + + return NameAttribute(oid, value) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py new file mode 100644 index 0000000..f61ed80 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py @@ -0,0 +1,379 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import datetime +from collections.abc import Iterable + +from cryptography import utils, x509 +from cryptography.hazmat.bindings._rust import ocsp +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, +) +from cryptography.x509.base import _reject_duplicate_extension + + +class OCSPResponderEncoding(utils.Enum): + HASH = "By Hash" + NAME = "By Name" + + +class OCSPResponseStatus(utils.Enum): + SUCCESSFUL = 0 + MALFORMED_REQUEST = 1 + INTERNAL_ERROR = 2 + TRY_LATER = 3 + SIG_REQUIRED = 5 + UNAUTHORIZED = 6 + + +_ALLOWED_HASHES = ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, +) + + +def _verify_algorithm(algorithm: hashes.HashAlgorithm) -> None: + if not isinstance(algorithm, _ALLOWED_HASHES): + raise ValueError( + "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512" + ) + + +class OCSPCertStatus(utils.Enum): + GOOD = 0 + REVOKED = 1 + UNKNOWN = 2 + + +class _SingleResponse: + def __init__( + self, + resp: tuple[x509.Certificate, x509.Certificate] | None, + resp_hash: tuple[bytes, bytes, int] | None, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ): + _verify_algorithm(algorithm) + if not isinstance(this_update, datetime.datetime): + raise TypeError("this_update must be a datetime object") + if next_update is not None and not isinstance( + next_update, datetime.datetime + ): + raise TypeError("next_update must be a datetime object or None") + + self._resp = resp + self._resp_hash = resp_hash + self._algorithm = algorithm + self._this_update = this_update + self._next_update = next_update + + if not isinstance(cert_status, OCSPCertStatus): + raise TypeError( + "cert_status must be an item from the OCSPCertStatus enum" + ) + if cert_status is not OCSPCertStatus.REVOKED: + if revocation_time is not None: + raise ValueError( + "revocation_time can only be provided if the certificate " + "is revoked" + ) + if revocation_reason is not None: + raise ValueError( + "revocation_reason can only be provided if the certificate" + " is revoked" + ) + else: + if not isinstance(revocation_time, datetime.datetime): + raise TypeError("revocation_time must be a datetime object") + + if revocation_reason is not None and not isinstance( + revocation_reason, x509.ReasonFlags + ): + raise TypeError( + "revocation_reason must be an item from the ReasonFlags " + "enum or None" + ) + + self._cert_status = cert_status + self._revocation_time = revocation_time + self._revocation_reason = revocation_reason + + +OCSPRequest = ocsp.OCSPRequest +OCSPResponse = ocsp.OCSPResponse +OCSPSingleResponse = ocsp.OCSPSingleResponse + + +class OCSPRequestBuilder: + def __init__( + self, + request: tuple[ + x509.Certificate, x509.Certificate, hashes.HashAlgorithm + ] + | None = None, + request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm] + | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], + ) -> None: + self._request = request + self._request_hash = request_hash + self._extensions = extensions + + def add_certificate( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + _verify_algorithm(algorithm) + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + return OCSPRequestBuilder( + (cert, issuer, algorithm), self._request_hash, self._extensions + ) + + def add_certificate_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + _verify_algorithm(algorithm) + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + return OCSPRequestBuilder( + self._request, + (issuer_name_hash, issuer_key_hash, serial_number, algorithm), + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPRequestBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPRequestBuilder( + self._request, self._request_hash, [*self._extensions, extension] + ) + + def build(self) -> OCSPRequest: + if self._request is None and self._request_hash is None: + raise ValueError("You must add a certificate before building") + + return ocsp.create_ocsp_request(self) + + +class OCSPResponseBuilder: + def __init__( + self, + response: _SingleResponse | None = None, + responder_id: tuple[x509.Certificate, OCSPResponderEncoding] + | None = None, + certs: list[x509.Certificate] | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], + ): + self._response = response + self._responder_id = responder_id + self._certs = certs + self._extensions = extensions + + def add_response( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + singleresp = _SingleResponse( + (cert, issuer), + None, + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def add_response_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + _verify_algorithm(algorithm) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + singleresp = _SingleResponse( + None, + (issuer_name_hash, issuer_key_hash, serial_number), + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def responder_id( + self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate + ) -> OCSPResponseBuilder: + if self._responder_id is not None: + raise ValueError("responder_id can only be set once") + if not isinstance(responder_cert, x509.Certificate): + raise TypeError("responder_cert must be a Certificate") + if not isinstance(encoding, OCSPResponderEncoding): + raise TypeError( + "encoding must be an element from OCSPResponderEncoding" + ) + + return OCSPResponseBuilder( + self._response, + (responder_cert, encoding), + self._certs, + self._extensions, + ) + + def certificates( + self, certs: Iterable[x509.Certificate] + ) -> OCSPResponseBuilder: + if self._certs is not None: + raise ValueError("certificates may only be set once") + certs = list(certs) + if len(certs) == 0: + raise ValueError("certs must not be an empty list") + if not all(isinstance(x, x509.Certificate) for x in certs): + raise TypeError("certs must be a list of Certificates") + return OCSPResponseBuilder( + self._response, + self._responder_id, + certs, + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPResponseBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPResponseBuilder( + self._response, + self._responder_id, + self._certs, + [*self._extensions, extension], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: hashes.HashAlgorithm | None, + ) -> OCSPResponse: + if self._response is None: + raise ValueError("You must add a response before signing") + if self._responder_id is None: + raise ValueError("You must add a responder_id before signing") + + return ocsp.create_ocsp_response( + OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm + ) + + @classmethod + def build_unsuccessful( + cls, response_status: OCSPResponseStatus + ) -> OCSPResponse: + if not isinstance(response_status, OCSPResponseStatus): + raise TypeError( + "response_status must be an item from OCSPResponseStatus" + ) + if response_status is OCSPResponseStatus.SUCCESSFUL: + raise ValueError("response_status cannot be SUCCESSFUL") + + return ocsp.create_ocsp_response(response_status, None, None, None) + + +load_der_ocsp_request = ocsp.load_der_ocsp_request +load_der_ocsp_response = ocsp.load_der_ocsp_response diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py new file mode 100644 index 0000000..520fc7a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py @@ -0,0 +1,37 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat._oid import ( + AttributeOID, + AuthorityInformationAccessOID, + CertificatePoliciesOID, + CRLEntryExtensionOID, + ExtendedKeyUsageOID, + ExtensionOID, + NameOID, + ObjectIdentifier, + OCSPExtensionOID, + OtherNameFormOID, + PublicKeyAlgorithmOID, + SignatureAlgorithmOID, + SubjectInformationAccessOID, +) + +__all__ = [ + "AttributeOID", + "AuthorityInformationAccessOID", + "CRLEntryExtensionOID", + "CertificatePoliciesOID", + "ExtendedKeyUsageOID", + "ExtensionOID", + "NameOID", + "OCSPExtensionOID", + "ObjectIdentifier", + "OtherNameFormOID", + "PublicKeyAlgorithmOID", + "SignatureAlgorithmOID", + "SubjectInformationAccessOID", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py new file mode 100644 index 0000000..2db4324 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.general_name import DNSName, IPAddress + +__all__ = [ + "ClientVerifier", + "Criticality", + "ExtensionPolicy", + "Policy", + "PolicyBuilder", + "ServerVerifier", + "Store", + "Subject", + "VerificationError", + "VerifiedClient", +] + +Store = rust_x509.Store +Subject = typing.Union[DNSName, IPAddress] +VerifiedClient = rust_x509.VerifiedClient +ClientVerifier = rust_x509.ClientVerifier +ServerVerifier = rust_x509.ServerVerifier +PolicyBuilder = rust_x509.PolicyBuilder +Policy = rust_x509.Policy +ExtensionPolicy = rust_x509.ExtensionPolicy +Criticality = rust_x509.Criticality +VerificationError = rust_x509.VerificationError diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py new file mode 100644 index 0000000..dde24a0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py @@ -0,0 +1,51 @@ +from typing import Any, Optional + +from .main import dotenv_values, find_dotenv, get_key, load_dotenv, set_key, unset_key + + +def load_ipython_extension(ipython: Any) -> None: + from .ipython import load_ipython_extension + + load_ipython_extension(ipython) + + +def get_cli_string( + path: Optional[str] = None, + action: Optional[str] = None, + key: Optional[str] = None, + value: Optional[str] = None, + quote: Optional[str] = None, +): + """Returns a string suitable for running as a shell script. + + Useful for converting a arguments passed to a fabric task + to be passed to a `local` or `run` command. + """ + command = ["dotenv"] + if quote: + command.append(f"-q {quote}") + if path: + command.append(f"-f {path}") + if action: + command.append(action) + if key: + command.append(key) + if value: + if " " in value: + command.append(f'"{value}"') + else: + command.append(value) + + return " ".join(command).strip() + + +__all__ = [ + "get_cli_string", + "load_dotenv", + "dotenv_values", + "get_key", + "set_key", + "unset_key", + "find_dotenv", + "load_ipython_extension", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py new file mode 100644 index 0000000..3977f55 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py @@ -0,0 +1,6 @@ +"""Entry point for cli, enables execution with `python -m dotenv`""" + +from .cli import cli + +if __name__ == "__main__": + cli() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py new file mode 100644 index 0000000..79613e2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/cli.py @@ -0,0 +1,247 @@ +import json +import os +import shlex +import sys +from contextlib import contextmanager +from typing import IO, Any, Dict, Iterator, List, Optional + +if sys.platform == "win32": + from subprocess import Popen + +try: + import click +except ImportError: + sys.stderr.write( + "It seems python-dotenv is not installed with cli option. \n" + 'Run pip install "python-dotenv[cli]" to fix this.' + ) + sys.exit(1) + +from .main import dotenv_values, set_key, unset_key +from .version import __version__ + + +def enumerate_env() -> Optional[str]: + """ + Return a path for the ${pwd}/.env file. + + If pwd does not exist, return None. + """ + try: + cwd = os.getcwd() + except FileNotFoundError: + return None + path = os.path.join(cwd, ".env") + return path + + +@click.group() +@click.option( + "-f", + "--file", + default=enumerate_env(), + type=click.Path(file_okay=True), + help="Location of the .env file, defaults to .env file in current working directory.", +) +@click.option( + "-q", + "--quote", + default="always", + type=click.Choice(["always", "never", "auto"]), + help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.", +) +@click.option( + "-e", + "--export", + default=False, + type=click.BOOL, + help="Whether to write the dot file as an executable bash script.", +) +@click.version_option(version=__version__) +@click.pass_context +def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None: + """This script is used to set, get or unset values from a .env file.""" + ctx.obj = {"QUOTE": quote, "EXPORT": export, "FILE": file} + + +@contextmanager +def stream_file(path: os.PathLike) -> Iterator[IO[str]]: + """ + Open a file and yield the corresponding (decoded) stream. + + Exits with error code 2 if the file cannot be opened. + """ + + try: + with open(path) as stream: + yield stream + except OSError as exc: + print(f"Error opening env file: {exc}", file=sys.stderr) + sys.exit(2) + + +@cli.command(name="list") +@click.pass_context +@click.option( + "--format", + "output_format", + default="simple", + type=click.Choice(["simple", "json", "shell", "export"]), + help="The format in which to display the list. Default format is simple, " + "which displays name=value without quotes.", +) +def list_values(ctx: click.Context, output_format: str) -> None: + """Display all the stored key/value.""" + file = ctx.obj["FILE"] + + with stream_file(file) as stream: + values = dotenv_values(stream=stream) + + if output_format == "json": + click.echo(json.dumps(values, indent=2, sort_keys=True)) + else: + prefix = "export " if output_format == "export" else "" + for k in sorted(values): + v = values[k] + if v is not None: + if output_format in ("export", "shell"): + v = shlex.quote(v) + click.echo(f"{prefix}{k}={v}") + + +@cli.command(name="set") +@click.pass_context +@click.argument("key", required=True) +@click.argument("value", required=True) +def set_value(ctx: click.Context, key: Any, value: Any) -> None: + """ + Store the given key/value. + + This doesn't follow symlinks, to avoid accidentally modifying a file at a + potentially untrusted path. + """ + + file = ctx.obj["FILE"] + quote = ctx.obj["QUOTE"] + export = ctx.obj["EXPORT"] + success, key, value = set_key(file, key, value, quote, export) + if success: + click.echo(f"{key}={value}") + else: + sys.exit(1) + + +@cli.command() +@click.pass_context +@click.argument("key", required=True) +def get(ctx: click.Context, key: Any) -> None: + """Retrieve the value for the given key.""" + file = ctx.obj["FILE"] + + with stream_file(file) as stream: + values = dotenv_values(stream=stream) + + stored_value = values.get(key) + if stored_value: + click.echo(stored_value) + else: + sys.exit(1) + + +@cli.command() +@click.pass_context +@click.argument("key", required=True) +def unset(ctx: click.Context, key: Any) -> None: + """ + Removes the given key. + + This doesn't follow symlinks, to avoid accidentally modifying a file at a + potentially untrusted path. + """ + file = ctx.obj["FILE"] + quote = ctx.obj["QUOTE"] + success, key = unset_key(file, key, quote) + if success: + click.echo(f"Successfully removed {key}") + else: + sys.exit(1) + + +@cli.command( + context_settings={ + "allow_extra_args": True, + "allow_interspersed_args": False, + "ignore_unknown_options": True, + } +) +@click.pass_context +@click.option( + "--override/--no-override", + default=True, + help="Override variables from the environment file with those from the .env file.", +) +@click.argument("commandline", nargs=-1, type=click.UNPROCESSED) +def run(ctx: click.Context, override: bool, commandline: tuple[str, ...]) -> None: + """Run command with environment variables present.""" + file = ctx.obj["FILE"] + if not os.path.isfile(file): + raise click.BadParameter( + f"Invalid value for '-f' \"{file}\" does not exist.", ctx=ctx + ) + dotenv_as_dict = { + k: v + for (k, v) in dotenv_values(file).items() + if v is not None and (override or k not in os.environ) + } + + if not commandline: + click.echo("No command given.") + sys.exit(1) + + run_command([*commandline, *ctx.args], dotenv_as_dict) + + +def run_command(command: List[str], env: Dict[str, str]) -> None: + """Replace the current process with the specified command. + + Replaces the current process with the specified command and the variables from `env` + added in the current environment variables. + + Parameters + ---------- + command: List[str] + The command and it's parameters + env: Dict + The additional environment variables + + Returns + ------- + None + This function does not return any value. It replaces the current process with the new one. + + """ + # copy the current environment variables and add the vales from + # `env` + cmd_env = os.environ.copy() + cmd_env.update(env) + + if sys.platform == "win32": + # execvpe on Windows returns control immediately + # rather than once the command has finished. + try: + p = Popen( + command, universal_newlines=True, bufsize=0, shell=False, env=cmd_env + ) + except FileNotFoundError: + print(f"Command not found: {command[0]}", file=sys.stderr) + sys.exit(1) + + _, _ = p.communicate() + + sys.exit(p.returncode) + else: + try: + os.execvpe(command[0], args=command, env=cmd_env) + except FileNotFoundError: + print(f"Command not found: {command[0]}", file=sys.stderr) + sys.exit(1) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py new file mode 100644 index 0000000..4e7edbb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py @@ -0,0 +1,50 @@ +from IPython.core.magic import Magics, line_magic, magics_class # type: ignore +from IPython.core.magic_arguments import ( + argument, + magic_arguments, + parse_argstring, +) # type: ignore + +from .main import find_dotenv, load_dotenv + + +@magics_class +class IPythonDotEnv(Magics): + @magic_arguments() + @argument( + "-o", + "--override", + action="store_true", + help="Indicate to override existing variables", + ) + @argument( + "-v", + "--verbose", + action="store_true", + help="Indicate function calls to be verbose", + ) + @argument( + "dotenv_path", + nargs="?", + type=str, + default=".env", + help="Search in increasingly higher folders for the `dotenv_path`", + ) + @line_magic + def dotenv(self, line): + args = parse_argstring(self.dotenv, line) + # Locate the .env file + dotenv_path = args.dotenv_path + try: + dotenv_path = find_dotenv(dotenv_path, True, True) + except IOError: + print("cannot find .env file") + return + + # Load the .env file + load_dotenv(dotenv_path, verbose=args.verbose, override=args.override) + + +def load_ipython_extension(ipython): + """Register the %dotenv magic.""" + ipython.register_magics(IPythonDotEnv) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py new file mode 100644 index 0000000..3123690 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/main.py @@ -0,0 +1,487 @@ +import io +import logging +import os +import pathlib +import stat +import sys +import tempfile +from collections import OrderedDict +from contextlib import contextmanager +from typing import IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, Union + +from .parser import Binding, parse_stream +from .variables import parse_variables + +# A type alias for a string path to be used for the paths in this file. +# These paths may flow to `open()` and `os.replace()`. +StrPath = Union[str, "os.PathLike[str]"] + +logger = logging.getLogger(__name__) + + +def _load_dotenv_disabled() -> bool: + """ + Determine if dotenv loading has been disabled. + """ + if "PYTHON_DOTENV_DISABLED" not in os.environ: + return False + value = os.environ["PYTHON_DOTENV_DISABLED"].casefold() + return value in {"1", "true", "t", "yes", "y"} + + +def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]: + for mapping in mappings: + if mapping.error: + logger.warning( + "python-dotenv could not parse statement starting at line %s", + mapping.original.line, + ) + yield mapping + + +class DotEnv: + def __init__( + self, + dotenv_path: Optional[StrPath], + stream: Optional[IO[str]] = None, + verbose: bool = False, + encoding: Optional[str] = None, + interpolate: bool = True, + override: bool = True, + ) -> None: + self.dotenv_path: Optional[StrPath] = dotenv_path + self.stream: Optional[IO[str]] = stream + self._dict: Optional[Dict[str, Optional[str]]] = None + self.verbose: bool = verbose + self.encoding: Optional[str] = encoding + self.interpolate: bool = interpolate + self.override: bool = override + + @contextmanager + def _get_stream(self) -> Iterator[IO[str]]: + if self.dotenv_path and _is_file_or_fifo(self.dotenv_path): + with open(self.dotenv_path, encoding=self.encoding) as stream: + yield stream + elif self.stream is not None: + yield self.stream + else: + if self.verbose: + logger.info( + "python-dotenv could not find configuration file %s.", + self.dotenv_path or ".env", + ) + yield io.StringIO("") + + def dict(self) -> Dict[str, Optional[str]]: + """Return dotenv as dict""" + if self._dict is not None: + return self._dict + + raw_values = self.parse() + + if self.interpolate: + self._dict = OrderedDict( + resolve_variables(raw_values, override=self.override) + ) + else: + self._dict = OrderedDict(raw_values) + + return self._dict + + def parse(self) -> Iterator[Tuple[str, Optional[str]]]: + with self._get_stream() as stream: + for mapping in with_warn_for_invalid_lines(parse_stream(stream)): + if mapping.key is not None: + yield mapping.key, mapping.value + + def set_as_environment_variables(self) -> bool: + """ + Load the current dotenv as system environment variable. + """ + if not self.dict(): + return False + + for k, v in self.dict().items(): + if k in os.environ and not self.override: + continue + if v is not None: + os.environ[k] = v + + return True + + def get(self, key: str) -> Optional[str]: + """ """ + data = self.dict() + + if key in data: + return data[key] + + if self.verbose: + logger.warning("Key %s not found in %s.", key, self.dotenv_path) + + return None + + +def get_key( + dotenv_path: StrPath, + key_to_get: str, + encoding: Optional[str] = "utf-8", +) -> Optional[str]: + """ + Get the value of a given key from the given .env. + + Returns `None` if the key isn't found or doesn't have a value. + """ + return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get) + + +@contextmanager +def rewrite( + path: StrPath, + encoding: Optional[str], + follow_symlinks: bool = False, +) -> Iterator[Tuple[IO[str], IO[str]]]: + if follow_symlinks: + path = os.path.realpath(path) + + try: + source: IO[str] = open(path, encoding=encoding) + try: + path_stat = os.lstat(path) + original_mode: Optional[int] = ( + stat.S_IMODE(path_stat.st_mode) + if stat.S_ISREG(path_stat.st_mode) + else None + ) + except BaseException: + source.close() + raise + except FileNotFoundError: + source = io.StringIO("") + original_mode = None + + with tempfile.NamedTemporaryFile( + mode="w", + encoding=encoding, + delete=False, + prefix=".tmp_", + dir=os.path.dirname(os.path.abspath(path)), + ) as dest: + dest_path = pathlib.Path(dest.name) + error = None + + try: + with source: + yield (source, dest) + except BaseException as err: + error = err + + if error is None: + try: + if original_mode is not None: + os.chmod(dest_path, original_mode) + + os.replace(dest_path, path) + except BaseException: + dest_path.unlink(missing_ok=True) + raise + else: + dest_path.unlink(missing_ok=True) + raise error from None + + +def set_key( + dotenv_path: StrPath, + key_to_set: str, + value_to_set: str, + quote_mode: str = "always", + export: bool = False, + encoding: Optional[str] = "utf-8", + follow_symlinks: bool = False, +) -> Tuple[Optional[bool], str, str]: + """ + Adds or Updates a key/value to the given .env + + The target .env file is created if it doesn't exist. + + This function doesn't follow symlinks by default, to avoid accidentally + modifying a file at a potentially untrusted path. If you don't need this + protection and need symlinks to be followed, use `follow_symlinks`. + """ + if quote_mode not in ("always", "auto", "never"): + raise ValueError(f"Unknown quote_mode: {quote_mode}") + + quote = quote_mode == "always" or ( + quote_mode == "auto" and not value_to_set.isalnum() + ) + + if quote: + # The single-quoted-value parser decodes `\\` and `\'`, so both have to + # be escaped here for the value to survive a write/read round-trip. + # Backslashes first, otherwise the backslash added by the quote + # escaping would be escaped in turn. + escaped = value_to_set.replace("\\", "\\\\").replace("'", "\\'") + value_out = f"'{escaped}'" + else: + value_out = value_to_set + if export: + line_out = f"export {key_to_set}={value_out}\n" + else: + line_out = f"{key_to_set}={value_out}\n" + + with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as ( + source, + dest, + ): + replaced = False + missing_newline = False + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_set: + dest.write(line_out) + replaced = True + else: + dest.write(mapping.original.string) + missing_newline = not mapping.original.string.endswith("\n") + if not replaced: + if missing_newline: + dest.write("\n") + dest.write(line_out) + + return True, key_to_set, value_to_set + + +def unset_key( + dotenv_path: StrPath, + key_to_unset: str, + quote_mode: str = "always", + encoding: Optional[str] = "utf-8", + follow_symlinks: bool = False, +) -> Tuple[Optional[bool], str]: + """ + Removes a given key from the given `.env` file. + + If the .env path given doesn't exist, fails. + If the given key doesn't exist in the .env, fails. + + This function doesn't follow symlinks by default, to avoid accidentally + modifying a file at a potentially untrusted path. If you don't need this + protection and need symlinks to be followed, use `follow_symlinks`. + """ + if not os.path.exists(dotenv_path): + logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path) + return None, key_to_unset + + removed = False + with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as ( + source, + dest, + ): + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_unset: + removed = True + else: + dest.write(mapping.original.string) + + if not removed: + logger.warning( + "Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path + ) + return None, key_to_unset + + return removed, key_to_unset + + +def resolve_variables( + values: Iterable[Tuple[str, Optional[str]]], + override: bool, +) -> Mapping[str, Optional[str]]: + new_values: Dict[str, Optional[str]] = {} + + for name, value in values: + if value is None: + result = None + else: + atoms = parse_variables(value) + env: Dict[str, Optional[str]] = {} + if override: + env.update(os.environ) # type: ignore + env.update(new_values) + else: + env.update(new_values) + env.update(os.environ) # type: ignore + result = "".join(atom.resolve(env) for atom in atoms) + + new_values[name] = result + + return new_values + + +def _walk_to_root(path: str) -> Iterator[str]: + """ + Yield directories starting from the given directory up to the root + """ + if not os.path.exists(path): + raise IOError("Starting path not found") + + if os.path.isfile(path): + path = os.path.dirname(path) + + last_dir = None + current_dir = os.path.abspath(path) + while last_dir != current_dir: + yield current_dir + parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir)) + last_dir, current_dir = current_dir, parent_dir + + +def find_dotenv( + filename: str = ".env", + raise_error_if_not_found: bool = False, + usecwd: bool = False, +) -> str: + """ + Search in increasingly higher folders for the given file + + Returns path to the file if found, or an empty string otherwise + """ + + def _is_interactive(): + """Decide whether this is running in a REPL or IPython notebook""" + if hasattr(sys, "ps1") or hasattr(sys, "ps2"): + return True + try: + main = __import__("__main__", None, None, fromlist=["__file__"]) + except ModuleNotFoundError: + return False + return not hasattr(main, "__file__") + + def _is_debugger(): + return sys.gettrace() is not None + + if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False): + # Should work without __file__, e.g. in REPL or IPython notebook. + path = os.getcwd() + else: + # will work for .py files + frame = sys._getframe() + current_file = __file__ + + while frame.f_code.co_filename == current_file or not os.path.exists( + frame.f_code.co_filename + ): + assert frame.f_back is not None + frame = frame.f_back + frame_filename = frame.f_code.co_filename + path = os.path.dirname(os.path.abspath(frame_filename)) + + for dirname in _walk_to_root(path): + check_path = os.path.join(dirname, filename) + if _is_file_or_fifo(check_path): + return check_path + + if raise_error_if_not_found: + raise IOError("File not found") + + return "" + + +def load_dotenv( + dotenv_path: Optional[StrPath] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + override: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> bool: + """Parse a .env file and then load all the variables found as environment variables. + + Parameters: + dotenv_path: Absolute or relative path to .env file. + stream: Text stream (such as `io.StringIO`) with .env content, used if + `dotenv_path` is `None`. + verbose: Whether to output a warning the .env file is missing. + override: Whether to override the system environment variables with the variables + from the `.env` file. + interpolate: Whether to interpolate variables using POSIX variable expansion. + encoding: Encoding to be used to read the file. + Returns: + Bool: True if at least one environment variable is set else False + + If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the + .env file with its default parameters. If you need to change the default parameters + of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result + to this function as `dotenv_path`. + + If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value, + .env loading is disabled. + """ + if _load_dotenv_disabled(): + logger.debug( + "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable" + ) + return False + + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + dotenv = DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=override, + encoding=encoding, + ) + return dotenv.set_as_environment_variables() + + +def dotenv_values( + dotenv_path: Optional[StrPath] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> Dict[str, Optional[str]]: + """ + Parse a .env file and return its content as a dict. + + The returned dict will have `None` values for keys without values in the .env file. + For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in + `{"foo": None}` + + Parameters: + dotenv_path: Absolute or relative path to the .env file. + stream: `StringIO` object with .env content, used if `dotenv_path` is `None`. + verbose: Whether to output a warning if the .env file is missing. + interpolate: Whether to interpolate variables using POSIX variable expansion. + encoding: Encoding to be used to read the file. + + If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the + .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + return DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=True, + encoding=encoding, + ).dict() + + +def _is_file_or_fifo(path: StrPath) -> bool: + """ + Return True if `path` exists and is either a regular file or a FIFO. + """ + if os.path.isfile(path): + return True + + try: + st = os.stat(path) + except (FileNotFoundError, OSError): + return False + + return stat.S_ISFIFO(st.st_mode) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py new file mode 100644 index 0000000..02648ab --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/parser.py @@ -0,0 +1,184 @@ +import codecs +import re +from typing import ( + IO, + Iterator, + Match, + NamedTuple, + Optional, + Pattern, + Sequence, +) + + +def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]: + return re.compile(string, re.UNICODE | extra_flags) + + +_newline = make_regex(r"(\r\n|\n|\r)") +_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE) +_whitespace = make_regex(r"[^\S\r\n]*") +_export = make_regex(r"(?:export[^\S\r\n]+)?") +_single_quoted_key = make_regex(r"'([^']+)'") +_unquoted_key = make_regex(r"([^=\#\s]+)") +_equal_sign = make_regex(r"(=[^\S\r\n]*)") +# A backslash always escapes the character after it, so that an escaped +# backslash (`\\`) is not mistaken for the start of an escaped quote. +_single_quoted_value = make_regex(r"'((?:\\.|[^'\\])*)'", extra_flags=re.DOTALL) +_double_quoted_value = make_regex(r'"((?:\\.|[^"\\])*)"', extra_flags=re.DOTALL) +_unquoted_value = make_regex(r"([^\r\n]*)") +_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?") +_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)") +_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?") +_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]") +_single_quote_escapes = make_regex(r"\\[\\']") + + +class Original(NamedTuple): + string: str + line: int + + +class Binding(NamedTuple): + key: Optional[str] + value: Optional[str] + original: Original + error: bool + + +class Position: + def __init__(self, chars: int, line: int) -> None: + self.chars = chars + self.line = line + + @classmethod + def start(cls) -> "Position": + return cls(chars=0, line=1) + + def set(self, other: "Position") -> None: + self.chars = other.chars + self.line = other.line + + def advance(self, string: str) -> None: + self.chars += len(string) + self.line += len(re.findall(_newline, string)) + + +class Error(Exception): + pass + + +class Reader: + def __init__(self, stream: IO[str]) -> None: + self.string = stream.read().removeprefix("\ufeff") + self.position = Position.start() + self.mark = Position.start() + + def has_next(self) -> bool: + return self.position.chars < len(self.string) + + def set_mark(self) -> None: + self.mark.set(self.position) + + def get_marked(self) -> Original: + return Original( + string=self.string[self.mark.chars : self.position.chars], + line=self.mark.line, + ) + + def peek(self, count: int) -> str: + return self.string[self.position.chars : self.position.chars + count] + + def read(self, count: int) -> str: + result = self.string[self.position.chars : self.position.chars + count] + if len(result) < count: + raise Error("read: End of string") + self.position.advance(result) + return result + + def read_regex(self, regex: Pattern[str]) -> Sequence[str]: + match = regex.match(self.string, self.position.chars) + if match is None: + raise Error("read_regex: Pattern not found") + self.position.advance(self.string[match.start() : match.end()]) + return match.groups() + + +def decode_escapes(regex: Pattern[str], string: str) -> str: + def decode_match(match: Match[str]) -> str: + return codecs.decode(match.group(0), "unicode-escape") # type: ignore + + return regex.sub(decode_match, string) + + +def parse_key(reader: Reader) -> Optional[str]: + char = reader.peek(1) + if char == "#": + return None + elif char == "'": + (key,) = reader.read_regex(_single_quoted_key) + else: + (key,) = reader.read_regex(_unquoted_key) + return key + + +def parse_unquoted_value(reader: Reader) -> str: + (part,) = reader.read_regex(_unquoted_value) + return re.sub(r"\s+#.*", "", part).rstrip() + + +def parse_value(reader: Reader) -> str: + char = reader.peek(1) + if char == "'": + (value,) = reader.read_regex(_single_quoted_value) + return decode_escapes(_single_quote_escapes, value) + elif char == '"': + (value,) = reader.read_regex(_double_quoted_value) + return decode_escapes(_double_quote_escapes, value) + elif char in ("", "\n", "\r"): + return "" + else: + return parse_unquoted_value(reader) + + +def parse_binding(reader: Reader) -> Binding: + reader.set_mark() + try: + reader.read_regex(_multiline_whitespace) + if not reader.has_next(): + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=False, + ) + reader.read_regex(_export) + key = parse_key(reader) + reader.read_regex(_whitespace) + if reader.peek(1) == "=": + reader.read_regex(_equal_sign) + value: Optional[str] = parse_value(reader) + else: + value = None + reader.read_regex(_comment) + reader.read_regex(_end_of_line) + return Binding( + key=key, + value=value, + original=reader.get_marked(), + error=False, + ) + except Error: + reader.read_regex(_rest_of_line) + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=True, + ) + + +def parse_stream(stream: IO[str]) -> Iterator[Binding]: + reader = Reader(stream) + while reader.has_next(): + yield parse_binding(reader) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed new file mode 100644 index 0000000..7632ecf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py new file mode 100644 index 0000000..667f2f2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/variables.py @@ -0,0 +1,86 @@ +import re +from abc import ABCMeta, abstractmethod +from typing import Iterator, Mapping, Optional, Pattern + +_posix_variable: Pattern[str] = re.compile( + r""" + \$\{ + (?P[^\}:]*) + (?::- + (?P[^\}]*) + )? + \} + """, + re.VERBOSE, +) + + +class Atom(metaclass=ABCMeta): + def __ne__(self, other: object) -> bool: + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + return not result + + @abstractmethod + def resolve(self, env: Mapping[str, Optional[str]]) -> str: ... + + +class Literal(Atom): + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return f"Literal(value={self.value})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return self.value == other.value + + def __hash__(self) -> int: + return hash((self.__class__, self.value)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + return self.value + + +class Variable(Atom): + def __init__(self, name: str, default: Optional[str]) -> None: + self.name = name + self.default = default + + def __repr__(self) -> str: + return f"Variable(name={self.name}, default={self.default})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.name, self.default) == (other.name, other.default) + + def __hash__(self) -> int: + return hash((self.__class__, self.name, self.default)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + default = self.default if self.default is not None else "" + result = env.get(self.name, default) + return result if result is not None else "" + + +def parse_variables(value: str) -> Iterator[Atom]: + cursor = 0 + + for match in _posix_variable.finditer(value): + (start, end) = match.span() + name = match["name"] + default = match["default"] + + if start > cursor: + yield Literal(value=value[cursor:start]) + + yield Variable(name=name, default=default) + cursor = end + + length = len(value) + if cursor < length: + yield Literal(value=value[cursor:length]) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py new file mode 100644 index 0000000..10aa336 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/dotenv/version.py @@ -0,0 +1 @@ +__version__ = "1.2.3" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA new file mode 100644 index 0000000..8a2f639 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA @@ -0,0 +1,202 @@ +Metadata-Version: 2.4 +Name: h11 +Version: 0.16.0 +Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 +Home-page: https://github.com/python-hyper/h11 +Author: Nathaniel J. Smith +Author-email: njs@pobox.com +License: MIT +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: System :: Networking +Requires-Python: >=3.8 +License-File: LICENSE.txt +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: requires-python +Dynamic: summary + +h11 +=== + +.. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master + :target: https://travis-ci.org/python-hyper/h11 + :alt: Automated test status + +.. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/h11 + :alt: Test coverage + +.. image:: https://readthedocs.org/projects/h11/badge/?version=latest + :target: http://h11.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +This is a little HTTP/1.1 library written from scratch in Python, +heavily inspired by `hyper-h2 `_. + +It's a "bring-your-own-I/O" library; h11 contains no IO code +whatsoever. This means you can hook h11 up to your favorite network +API, and that could be anything you want: synchronous, threaded, +asynchronous, or your own implementation of `RFC 6214 +`_ -- h11 won't judge you. +(Compare this to the current state of the art, where every time a `new +network API `_ comes along then someone +gets to start over reimplementing the entire HTTP protocol from +scratch.) Cory Benfield made an `excellent blog post describing the +benefits of this approach +`_, or if you like video +then here's his `PyCon 2016 talk on the same theme +`_. + +This also means that h11 is not immediately useful out of the box: +it's a toolkit for building programs that speak HTTP, not something +that could directly replace ``requests`` or ``twisted.web`` or +whatever. But h11 makes it much easier to implement something like +``requests`` or ``twisted.web``. + +At a high level, working with h11 goes like this: + +1) First, create an ``h11.Connection`` object to track the state of a + single HTTP/1.1 connection. + +2) When you read data off the network, pass it to + ``conn.receive_data(...)``; you'll get back a list of objects + representing high-level HTTP "events". + +3) When you want to send a high-level HTTP event, create the + corresponding "event" object and pass it to ``conn.send(...)``; + this will give you back some bytes that you can then push out + through the network. + +For example, a client might instantiate and then send a +``h11.Request`` object, then zero or more ``h11.Data`` objects for the +request body (e.g., if this is a POST), and then a +``h11.EndOfMessage`` to indicate the end of the message. Then the +server would then send back a ``h11.Response``, some ``h11.Data``, and +its own ``h11.EndOfMessage``. If either side violates the protocol, +you'll get a ``h11.ProtocolError`` exception. + +h11 is suitable for implementing both servers and clients, and has a +pleasantly symmetric API: the events you send as a client are exactly +the ones that you receive as a server and vice-versa. + +`Here's an example of a tiny HTTP client +`_ + +It also has `a fine manual `_. + +FAQ +--- + +*Whyyyyy?* + +I wanted to play with HTTP in `Curio +`__ and `Trio +`__, which at the time didn't have any +HTTP libraries. So I thought, no big deal, Python has, like, a dozen +different implementations of HTTP, surely I can find one that's +reusable. I didn't find one, but I did find Cory's call-to-arms +blog-post. So I figured, well, fine, if I have to implement HTTP from +scratch, at least I can make sure no-one *else* has to ever again. + +*Should I use it?* + +Maybe. You should be aware that it's a very young project. But, it's +feature complete and has an exhaustive test-suite and complete docs, +so the next step is for people to try using it and see how it goes +:-). If you do then please let us know -- if nothing else we'll want +to talk to you before making any incompatible changes! + +*What are the features/limitations?* + +Roughly speaking, it's trying to be a robust, complete, and non-hacky +implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230: +HTTP/1.1 Message Syntax and Routing +`_. That is, it mostly focuses on +implementing HTTP at the level of taking bytes on and off the wire, +and the headers related to that, and tries to be anal about spec +conformance. It doesn't know about higher-level concerns like URL +routing, conditional GETs, cross-origin cookie policies, or content +negotiation. But it does know how to take care of framing, +cross-version differences in keep-alive handling, and the "obsolete +line folding" rule, so you can focus your energies on the hard / +interesting parts for your application, and it tries to support the +full specification in the sense that any useful HTTP/1.1 conformant +application should be able to use h11. + +It's pure Python, and has no dependencies outside of the standard +library. + +It has a test suite with 100.0% coverage for both statements and +branches. + +Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3. +The last Python 2-compatible version was h11 0.11.x. +(Originally it had a Cython wrapper for `http-parser +`_ and a beautiful nested state +machine implemented with ``yield from`` to postprocess the output. But +I had to take these out -- the new *parser* needs fewer lines-of-code +than the old *parser wrapper*, is written in pure Python, uses no +exotic language syntax, and has more features. It's sad, really; that +old state machine was really slick. I just need a few sentences here +to mourn that.) + +I don't know how fast it is. I haven't benchmarked or profiled it yet, +so it's probably got a few pointless hot spots, and I've been trying +to err on the side of simplicity and robustness instead of +micro-optimization. But at the architectural level I tried hard to +avoid fundamentally bad decisions, e.g., I believe that all the +parsing algorithms remain linear-time even in the face of pathological +input like slowloris, and there are no byte-by-byte loops. (I also +believe that it maintains bounded memory usage in the face of +arbitrary/pathological input.) + +The whole library is ~800 lines-of-code. You can read and understand +the whole thing in less than an hour. Most of the energy invested in +this so far has been spent on trying to keep things simple by +minimizing special-cases and ad hoc state manipulation; even though it +is now quite small and simple, I'm still annoyed that I haven't +figured out how to make it even smaller and simpler. (Unfortunately, +HTTP does not lend itself to simplicity.) + +The API is ~feature complete and I don't expect the general outlines +to change much, but you can't judge an API's ergonomics until you +actually document and use it, so I'd expect some changes in the +details. + +*How do I try it?* + +.. code-block:: sh + + $ pip install h11 + $ git clone git@github.com:python-hyper/h11 + $ cd h11/examples + $ python basic-client.py + +and go from there. + +*License?* + +MIT + +*Code of conduct?* + +Contributors are requested to follow our `code of conduct +`_ in +all project spaces. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD new file mode 100644 index 0000000..0ec9e3e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD @@ -0,0 +1,17 @@ +h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507 +h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815 +h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863 +h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792 +h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412 +h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590 +h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252 +h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231 +h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888 +h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686 +h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081 +h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 +h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124 +h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348 +h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91 +h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4 +h11-0.16.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL new file mode 100644 index 0000000..1eb3c49 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (78.1.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..8f080ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Nathaniel J. Smith and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt new file mode 100644 index 0000000..0d24def --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt @@ -0,0 +1 @@ +h11 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py new file mode 100644 index 0000000..989e92c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/__init__.py @@ -0,0 +1,62 @@ +# A highish-level implementation of the HTTP/1.1 wire protocol (RFC 7230), +# containing no networking code at all, loosely modelled on hyper-h2's generic +# implementation of HTTP/2 (and in particular the h2.connection.H2Connection +# class). There's still a bunch of subtle details you need to get right if you +# want to make this actually useful, because it doesn't implement all the +# semantics to check that what you're asking to write to the wire is sensible, +# but at least it gets you out of dealing with the wire itself. + +from h11._connection import Connection, NEED_DATA, PAUSED +from h11._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from h11._state import ( + CLIENT, + CLOSED, + DONE, + ERROR, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from h11._util import LocalProtocolError, ProtocolError, RemoteProtocolError +from h11._version import __version__ + +PRODUCT_ID = "python-h11/" + __version__ + + +__all__ = ( + "Connection", + "NEED_DATA", + "PAUSED", + "ConnectionClosed", + "Data", + "EndOfMessage", + "Event", + "InformationalResponse", + "Request", + "Response", + "CLIENT", + "CLOSED", + "DONE", + "ERROR", + "IDLE", + "MUST_CLOSE", + "SEND_BODY", + "SEND_RESPONSE", + "SERVER", + "SWITCHED_PROTOCOL", + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py new file mode 100644 index 0000000..933587f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_abnf.py @@ -0,0 +1,132 @@ +# We use native strings for all the re patterns, to take advantage of string +# formatting, and then convert to bytestrings when compiling the final re +# objects. + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#whitespace +# OWS = *( SP / HTAB ) +# ; optional whitespace +OWS = r"[ \t]*" + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.token.separators +# token = 1*tchar +# +# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +# / DIGIT / ALPHA +# ; any VCHAR, except delimiters +token = r"[-!#$%&'*+.^_`|~0-9a-zA-Z]+" + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#header.fields +# field-name = token +field_name = token + +# The standard says: +# +# field-value = *( field-content / obs-fold ) +# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] +# field-vchar = VCHAR / obs-text +# obs-fold = CRLF 1*( SP / HTAB ) +# ; obsolete line folding +# ; see Section 3.2.4 +# +# https://tools.ietf.org/html/rfc5234#appendix-B.1 +# +# VCHAR = %x21-7E +# ; visible (printing) characters +# +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.quoted-string +# obs-text = %x80-FF +# +# However, the standard definition of field-content is WRONG! It disallows +# fields containing a single visible character surrounded by whitespace, +# e.g. "foo a bar". +# +# See: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 +# +# So our definition of field_content attempts to fix it up... +# +# Also, we allow lots of control characters, because apparently people assume +# that they're legal in practice (e.g., google analytics makes cookies with +# \x01 in them!): +# https://github.com/python-hyper/h11/issues/57 +# We still don't allow NUL or whitespace, because those are often treated as +# meta-characters and letting them through can lead to nasty issues like SSRF. +vchar = r"[\x21-\x7e]" +vchar_or_obs_text = r"[^\x00\s]" +field_vchar = vchar_or_obs_text +field_content = r"{field_vchar}+(?:[ \t]+{field_vchar}+)*".format(**globals()) + +# We handle obs-fold at a different level, and our fixed-up field_content +# already grows to swallow the whole value, so ? instead of * +field_value = r"({field_content})?".format(**globals()) + +# header-field = field-name ":" OWS field-value OWS +header_field = ( + r"(?P{field_name})" + r":" + r"{OWS}" + r"(?P{field_value})" + r"{OWS}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#request.line +# +# request-line = method SP request-target SP HTTP-version CRLF +# method = token +# HTTP-version = HTTP-name "/" DIGIT "." DIGIT +# HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive +# +# request-target is complicated (see RFC 7230 sec 5.3) -- could be path, full +# URL, host+port (for connect), or even "*", but in any case we are guaranteed +# that it contists of the visible printing characters. +method = token +request_target = r"{vchar}+".format(**globals()) +http_version = r"HTTP/(?P[0-9]\.[0-9])" +request_line = ( + r"(?P{method})" + r" " + r"(?P{request_target})" + r" " + r"{http_version}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#status.line +# +# status-line = HTTP-version SP status-code SP reason-phrase CRLF +# status-code = 3DIGIT +# reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +status_code = r"[0-9]{3}" +reason_phrase = r"([ \t]|{vchar_or_obs_text})*".format(**globals()) +status_line = ( + r"{http_version}" + r" " + r"(?P{status_code})" + # However, there are apparently a few too many servers out there that just + # leave out the reason phrase: + # https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036 + # https://github.com/seanmonstar/httparse/issues/29 + # so make it optional. ?: is a non-capturing group. + r"(?: (?P{reason_phrase}))?".format(**globals()) +) + +HEXDIG = r"[0-9A-Fa-f]" +# Actually +# +# chunk-size = 1*HEXDIG +# +# but we impose an upper-limit to avoid ridiculosity. len(str(2**64)) == 20 +chunk_size = r"({HEXDIG}){{1,20}}".format(**globals()) +# Actually +# +# chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) +# +# but we aren't parsing the things so we don't really care. +chunk_ext = r";.*" +chunk_header = ( + r"(?P{chunk_size})" + r"(?P{chunk_ext})?" + r"{OWS}\r\n".format( + **globals() + ) # Even though the specification does not allow for extra whitespaces, + # we are lenient with trailing whitespaces because some servers on the wild use it. +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py new file mode 100644 index 0000000..e37d82a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_connection.py @@ -0,0 +1,659 @@ +# This contains the main Connection class. Everything in h11 revolves around +# this. +from typing import ( + Any, + Callable, + cast, + Dict, + List, + Optional, + overload, + Tuple, + Type, + Union, +) + +from ._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from ._headers import get_comma_header, has_expect_100_continue, set_comma_header +from ._readers import READERS, ReadersType +from ._receivebuffer import ReceiveBuffer +from ._state import ( + _SWITCH_CONNECT, + _SWITCH_UPGRADE, + CLIENT, + ConnectionState, + DONE, + ERROR, + MIGHT_SWITCH_PROTOCOL, + SEND_BODY, + SERVER, + SWITCHED_PROTOCOL, +) +from ._util import ( # Import the internal things we need + LocalProtocolError, + RemoteProtocolError, + Sentinel, +) +from ._writers import WRITERS, WritersType + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = ["Connection", "NEED_DATA", "PAUSED"] + + +class NEED_DATA(Sentinel, metaclass=Sentinel): + pass + + +class PAUSED(Sentinel, metaclass=Sentinel): + pass + + +# If we ever have this much buffered without it making a complete parseable +# event, we error out. The only time we really buffer is when reading the +# request/response line + headers together, so this is effectively the limit on +# the size of that. +# +# Some precedents for defaults: +# - node.js: 80 * 1024 +# - tomcat: 8 * 1024 +# - IIS: 16 * 1024 +# - Apache: <8 KiB per line> +DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024 + + +# RFC 7230's rules for connection lifecycles: +# - If either side says they want to close the connection, then the connection +# must close. +# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close +# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive +# (and even this is a mess -- e.g. if you're implementing a proxy then +# sending Connection: keep-alive is forbidden). +# +# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So +# our rule is: +# - If someone says Connection: close, we will close +# - If someone uses HTTP/1.0, we will close. +def _keep_alive(event: Union[Request, Response]) -> bool: + connection = get_comma_header(event.headers, b"connection") + if b"close" in connection: + return False + if getattr(event, "http_version", b"1.1") < b"1.1": + return False + return True + + +def _body_framing( + request_method: bytes, event: Union[Request, Response] +) -> Tuple[str, Union[Tuple[()], Tuple[int]]]: + # Called when we enter SEND_BODY to figure out framing information for + # this body. + # + # These are the only two events that can trigger a SEND_BODY state: + assert type(event) in (Request, Response) + # Returns one of: + # + # ("content-length", count) + # ("chunked", ()) + # ("http/1.0", ()) + # + # which are (lookup key, *args) for constructing body reader/writer + # objects. + # + # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3 + # + # Step 1: some responses always have an empty body, regardless of what the + # headers say. + if type(event) is Response: + if ( + event.status_code in (204, 304) + or request_method == b"HEAD" + or (request_method == b"CONNECT" and 200 <= event.status_code < 300) + ): + return ("content-length", (0,)) + # Section 3.3.3 also lists another case -- responses with status_code + # < 200. For us these are InformationalResponses, not Responses, so + # they can't get into this function in the first place. + assert event.status_code >= 200 + + # Step 2: check for Transfer-Encoding (T-E beats C-L): + transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") + if transfer_encodings: + assert transfer_encodings == [b"chunked"] + return ("chunked", ()) + + # Step 3: check for Content-Length + content_lengths = get_comma_header(event.headers, b"content-length") + if content_lengths: + return ("content-length", (int(content_lengths[0]),)) + + # Step 4: no applicable headers; fallback/default depends on type + if type(event) is Request: + return ("content-length", (0,)) + else: + return ("http/1.0", ()) + + +################################################################ +# +# The main Connection class +# +################################################################ + + +class Connection: + """An object encapsulating the state of an HTTP connection. + + Args: + our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If + you're implementing a server, pass :data:`h11.SERVER`. + + max_incomplete_event_size (int): + The maximum number of bytes we're willing to buffer of an + incomplete event. In practice this mostly sets a limit on the + maximum size of the request/response line + headers. If this is + exceeded, then :meth:`next_event` will raise + :exc:`RemoteProtocolError`. + + """ + + def __init__( + self, + our_role: Type[Sentinel], + max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) -> None: + self._max_incomplete_event_size = max_incomplete_event_size + # State and role tracking + if our_role not in (CLIENT, SERVER): + raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}") + self.our_role = our_role + self.their_role: Type[Sentinel] + if our_role is CLIENT: + self.their_role = SERVER + else: + self.their_role = CLIENT + self._cstate = ConnectionState() + + # Callables for converting data->events or vice-versa given the + # current state + self._writer = self._get_io_object(self.our_role, None, WRITERS) + self._reader = self._get_io_object(self.their_role, None, READERS) + + # Holds any unprocessed received data + self._receive_buffer = ReceiveBuffer() + # If this is true, then it indicates that the incoming connection was + # closed *after* the end of whatever's in self._receive_buffer: + self._receive_buffer_closed = False + + # Extra bits of state that don't fit into the state machine. + # + # These two are only used to interpret framing headers for figuring + # out how to read/write response bodies. their_http_version is also + # made available as a convenient public API. + self.their_http_version: Optional[bytes] = None + self._request_method: Optional[bytes] = None + # This is pure flow-control and doesn't at all affect the set of legal + # transitions, so no need to bother ConnectionState with it: + self.client_is_waiting_for_100_continue = False + + @property + def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]: + """A dictionary like:: + + {CLIENT: , SERVER: } + + See :ref:`state-machine` for details. + + """ + return dict(self._cstate.states) + + @property + def our_state(self) -> Type[Sentinel]: + """The current state of whichever role we are playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.our_role] + + @property + def their_state(self) -> Type[Sentinel]: + """The current state of whichever role we are NOT playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.their_role] + + @property + def they_are_waiting_for_100_continue(self) -> bool: + return self.their_role is CLIENT and self.client_is_waiting_for_100_continue + + def start_next_cycle(self) -> None: + """Attempt to reset our connection state for a new request/response + cycle. + + If both client and server are in :data:`DONE` state, then resets them + both to :data:`IDLE` state in preparation for a new request/response + cycle on this same connection. Otherwise, raises a + :exc:`LocalProtocolError`. + + See :ref:`keepalive-and-pipelining`. + + """ + old_states = dict(self._cstate.states) + self._cstate.start_next_cycle() + self._request_method = None + # self.their_http_version gets left alone, since it presumably lasts + # beyond a single request/response cycle + assert not self.client_is_waiting_for_100_continue + self._respond_to_state_changes(old_states) + + def _process_error(self, role: Type[Sentinel]) -> None: + old_states = dict(self._cstate.states) + self._cstate.process_error(role) + self._respond_to_state_changes(old_states) + + def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]: + if type(event) is InformationalResponse and event.status_code == 101: + return _SWITCH_UPGRADE + if type(event) is Response: + if ( + _SWITCH_CONNECT in self._cstate.pending_switch_proposals + and 200 <= event.status_code < 300 + ): + return _SWITCH_CONNECT + return None + + # All events go through here + def _process_event(self, role: Type[Sentinel], event: Event) -> None: + # First, pass the event through the state machine to make sure it + # succeeds. + old_states = dict(self._cstate.states) + if role is CLIENT and type(event) is Request: + if event.method == b"CONNECT": + self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) + if get_comma_header(event.headers, b"upgrade"): + self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) + server_switch_event = None + if role is SERVER: + server_switch_event = self._server_switch_event(event) + self._cstate.process_event(role, type(event), server_switch_event) + + # Then perform the updates triggered by it. + + if type(event) is Request: + self._request_method = event.method + + if role is self.their_role and type(event) in ( + Request, + Response, + InformationalResponse, + ): + event = cast(Union[Request, Response, InformationalResponse], event) + self.their_http_version = event.http_version + + # Keep alive handling + # + # RFC 7230 doesn't really say what one should do if Connection: close + # shows up on a 1xx InformationalResponse. I think the idea is that + # this is not supposed to happen. In any case, if it does happen, we + # ignore it. + if type(event) in (Request, Response) and not _keep_alive( + cast(Union[Request, Response], event) + ): + self._cstate.process_keep_alive_disabled() + + # 100-continue + if type(event) is Request and has_expect_100_continue(event): + self.client_is_waiting_for_100_continue = True + if type(event) in (InformationalResponse, Response): + self.client_is_waiting_for_100_continue = False + if role is CLIENT and type(event) in (Data, EndOfMessage): + self.client_is_waiting_for_100_continue = False + + self._respond_to_state_changes(old_states, event) + + def _get_io_object( + self, + role: Type[Sentinel], + event: Optional[Event], + io_dict: Union[ReadersType, WritersType], + ) -> Optional[Callable[..., Any]]: + # event may be None; it's only used when entering SEND_BODY + state = self._cstate.states[role] + if state is SEND_BODY: + # Special case: the io_dict has a dict of reader/writer factories + # that depend on the request/response framing. + framing_type, args = _body_framing( + cast(bytes, self._request_method), cast(Union[Request, Response], event) + ) + return io_dict[SEND_BODY][framing_type](*args) # type: ignore[index] + else: + # General case: the io_dict just has the appropriate reader/writer + # for this state + return io_dict.get((role, state)) # type: ignore[return-value] + + # This must be called after any action that might have caused + # self._cstate.states to change. + def _respond_to_state_changes( + self, + old_states: Dict[Type[Sentinel], Type[Sentinel]], + event: Optional[Event] = None, + ) -> None: + # Update reader/writer + if self.our_state != old_states[self.our_role]: + self._writer = self._get_io_object(self.our_role, event, WRITERS) + if self.their_state != old_states[self.their_role]: + self._reader = self._get_io_object(self.their_role, event, READERS) + + @property + def trailing_data(self) -> Tuple[bytes, bool]: + """Data that has been received, but not yet processed, represented as + a tuple with two elements, where the first is a byte-string containing + the unprocessed data itself, and the second is a bool that is True if + the receive connection was closed. + + See :ref:`switching-protocols` for discussion of why you'd want this. + """ + return (bytes(self._receive_buffer), self._receive_buffer_closed) + + def receive_data(self, data: bytes) -> None: + """Add data to our internal receive buffer. + + This does not actually do any processing on the data, just stores + it. To trigger processing, you have to call :meth:`next_event`. + + Args: + data (:term:`bytes-like object`): + The new data that was just received. + + Special case: If *data* is an empty byte-string like ``b""``, + then this indicates that the remote side has closed the + connection (end of file). Normally this is convenient, because + standard Python APIs like :meth:`file.read` or + :meth:`socket.recv` use ``b""`` to indicate end-of-file, while + other failures to read are indicated using other mechanisms + like raising :exc:`TimeoutError`. When using such an API you + can just blindly pass through whatever you get from ``read`` + to :meth:`receive_data`, and everything will work. + + But, if you have an API where reading an empty string is a + valid non-EOF condition, then you need to be aware of this and + make sure to check for such strings and avoid passing them to + :meth:`receive_data`. + + Returns: + Nothing, but after calling this you should call :meth:`next_event` + to parse the newly received data. + + Raises: + RuntimeError: + Raised if you pass an empty *data*, indicating EOF, and then + pass a non-empty *data*, indicating more data that somehow + arrived after the EOF. + + (Calling ``receive_data(b"")`` multiple times is fine, + and equivalent to calling it once.) + + """ + if data: + if self._receive_buffer_closed: + raise RuntimeError("received close, then received more data?") + self._receive_buffer += data + else: + self._receive_buffer_closed = True + + def _extract_next_receive_event( + self, + ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + state = self.their_state + # We don't pause immediately when they enter DONE, because even in + # DONE state we can still process a ConnectionClosed() event. But + # if we have data in our buffer, then we definitely aren't getting + # a ConnectionClosed() immediately and we need to pause. + if state is DONE and self._receive_buffer: + return PAUSED + if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL: + return PAUSED + assert self._reader is not None + event = self._reader(self._receive_buffer) + if event is None: + if not self._receive_buffer and self._receive_buffer_closed: + # In some unusual cases (basically just HTTP/1.0 bodies), EOF + # triggers an actual protocol event; in that case, we want to + # return that event, and then the state will change and we'll + # get called again to generate the actual ConnectionClosed(). + if hasattr(self._reader, "read_eof"): + event = self._reader.read_eof() + else: + event = ConnectionClosed() + if event is None: + event = NEED_DATA + return event # type: ignore[no-any-return] + + def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + """Parse the next event out of our receive buffer, update our internal + state, and return it. + + This is a mutating operation -- think of it like calling :func:`next` + on an iterator. + + Returns: + : One of three things: + + 1) An event object -- see :ref:`events`. + + 2) The special constant :data:`NEED_DATA`, which indicates that + you need to read more data from your socket and pass it to + :meth:`receive_data` before this method will be able to return + any more events. + + 3) The special constant :data:`PAUSED`, which indicates that we + are not in a state where we can process incoming data (usually + because the peer has finished their part of the current + request/response cycle, and you have not yet called + :meth:`start_next_cycle`). See :ref:`flow-control` for details. + + Raises: + RemoteProtocolError: + The peer has misbehaved. You should close the connection + (possibly after sending some kind of 4xx response). + + Once this method returns :class:`ConnectionClosed` once, then all + subsequent calls will also return :class:`ConnectionClosed`. + + If this method raises any exception besides :exc:`RemoteProtocolError` + then that's a bug -- if it happens please file a bug report! + + If this method raises any exception then it also sets + :attr:`Connection.their_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + + if self.their_state is ERROR: + raise RemoteProtocolError("Can't receive data when peer state is ERROR") + try: + event = self._extract_next_receive_event() + if event not in [NEED_DATA, PAUSED]: + self._process_event(self.their_role, cast(Event, event)) + if event is NEED_DATA: + if len(self._receive_buffer) > self._max_incomplete_event_size: + # 431 is "Request header fields too large" which is pretty + # much the only situation where we can get here + raise RemoteProtocolError( + "Receive buffer too long", error_status_hint=431 + ) + if self._receive_buffer_closed: + # We're still trying to complete some event, but that's + # never going to happen because no more data is coming + raise RemoteProtocolError("peer unexpectedly closed connection") + return event + except BaseException as exc: + self._process_error(self.their_role) + if isinstance(exc, LocalProtocolError): + exc._reraise_as_remote_protocol_error() + else: + raise + + @overload + def send(self, event: ConnectionClosed) -> None: + ... + + @overload + def send( + self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] + ) -> bytes: + ... + + @overload + def send(self, event: Event) -> Optional[bytes]: + ... + + def send(self, event: Event) -> Optional[bytes]: + """Convert a high-level event into bytes that can be sent to the peer, + while updating our internal state machine. + + Args: + event: The :ref:`event ` to send. + + Returns: + If ``type(event) is ConnectionClosed``, then returns + ``None``. Otherwise, returns a :term:`bytes-like object`. + + Raises: + LocalProtocolError: + Sending this event at this time would violate our + understanding of the HTTP/1.1 protocol. + + If this method raises any exception then it also sets + :attr:`Connection.our_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + data_list = self.send_with_data_passthrough(event) + if data_list is None: + return None + else: + return b"".join(data_list) + + def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]: + """Identical to :meth:`send`, except that in situations where + :meth:`send` returns a single :term:`bytes-like object`, this instead + returns a list of them -- and when sending a :class:`Data` event, this + list is guaranteed to contain the exact object you passed in as + :attr:`Data.data`. See :ref:`sendfile` for discussion. + + """ + if self.our_state is ERROR: + raise LocalProtocolError("Can't send data when our state is ERROR") + try: + if type(event) is Response: + event = self._clean_up_response_headers_for_sending(event) + # We want to call _process_event before calling the writer, + # because if someone tries to do something invalid then this will + # give a sensible error message, while our writers all just assume + # they will only receive valid events. But, _process_event might + # change self._writer. So we have to do a little dance: + writer = self._writer + self._process_event(self.our_role, event) + if type(event) is ConnectionClosed: + return None + else: + # In any situation where writer is None, process_event should + # have raised ProtocolError + assert writer is not None + data_list: List[bytes] = [] + writer(event, data_list.append) + return data_list + except: + self._process_error(self.our_role) + raise + + def send_failed(self) -> None: + """Notify the state machine that we failed to send the data it gave + us. + + This causes :attr:`Connection.our_state` to immediately become + :data:`ERROR` -- see :ref:`error-handling` for discussion. + + """ + self._process_error(self.our_role) + + # When sending a Response, we take responsibility for a few things: + # + # - Sometimes you MUST set Connection: close. We take care of those + # times. (You can also set it yourself if you want, and if you do then + # we'll respect that and close the connection at the right time. But you + # don't have to worry about that unless you want to.) + # + # - The user has to set Content-Length if they want it. Otherwise, for + # responses that have bodies (e.g. not HEAD), then we will automatically + # select the right mechanism for streaming a body of unknown length, + # which depends on depending on the peer's HTTP version. + # + # This function's *only* responsibility is making sure headers are set up + # right -- everything downstream just looks at the headers. There are no + # side channels. + def _clean_up_response_headers_for_sending(self, response: Response) -> Response: + assert type(response) is Response + + headers = response.headers + need_close = False + + # HEAD requests need some special handling: they always act like they + # have Content-Length: 0, and that's how _body_framing treats + # them. But their headers are supposed to match what we would send if + # the request was a GET. (Technically there is one deviation allowed: + # we're allowed to leave out the framing headers -- see + # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as + # easy to get them right.) + method_for_choosing_headers = cast(bytes, self._request_method) + if method_for_choosing_headers == b"HEAD": + method_for_choosing_headers = b"GET" + framing_type, _ = _body_framing(method_for_choosing_headers, response) + if framing_type in ("chunked", "http/1.0"): + # This response has a body of unknown length. + # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked + # If our peer is HTTP/1.0, we use no framing headers, and close the + # connection afterwards. + # + # Make sure to clear Content-Length (in principle user could have + # set both and then we ignored Content-Length b/c + # Transfer-Encoding overwrote it -- this would be naughty of them, + # but the HTTP spec says that if our peer does this then we have + # to fix it instead of erroring out, so we'll accord the user the + # same respect). + headers = set_comma_header(headers, b"content-length", []) + if self.their_http_version is None or self.their_http_version < b"1.1": + # Either we never got a valid request and are sending back an + # error (their_http_version is None), so we assume the worst; + # or else we did get a valid HTTP/1.0 request, so we know that + # they don't understand chunked encoding. + headers = set_comma_header(headers, b"transfer-encoding", []) + # This is actually redundant ATM, since currently we + # unconditionally disable keep-alive when talking to HTTP/1.0 + # peers. But let's be defensive just in case we add + # Connection: keep-alive support later: + if self._request_method != b"HEAD": + need_close = True + else: + headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"]) + + if not self._cstate.keep_alive or need_close: + # Make sure Connection: close is set + connection = set(get_comma_header(headers, b"connection")) + connection.discard(b"keep-alive") + connection.add(b"close") + headers = set_comma_header(headers, b"connection", sorted(connection)) + + return Response( + headers=headers, + status_code=response.status_code, + http_version=response.http_version, + reason=response.reason, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py new file mode 100644 index 0000000..ca1c3ad --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_events.py @@ -0,0 +1,369 @@ +# High level events that make up HTTP/1.1 conversations. Loosely inspired by +# the corresponding events in hyper-h2: +# +# http://python-hyper.org/h2/en/stable/api.html#events +# +# Don't subclass these. Stuff will break. + +import re +from abc import ABC +from dataclasses import dataclass +from typing import List, Tuple, Union + +from ._abnf import method, request_target +from ._headers import Headers, normalize_and_validate +from ._util import bytesify, LocalProtocolError, validate + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "Event", + "Request", + "InformationalResponse", + "Response", + "Data", + "EndOfMessage", + "ConnectionClosed", +] + +method_re = re.compile(method.encode("ascii")) +request_target_re = re.compile(request_target.encode("ascii")) + + +class Event(ABC): + """ + Base class for h11 events. + """ + + __slots__ = () + + +@dataclass(init=False, frozen=True) +class Request(Event): + """The beginning of an HTTP request. + + Fields: + + .. attribute:: method + + An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: target + + The target of an HTTP request, e.g. ``b"/index.html"``, or one of the + more exotic formats described in `RFC 7320, section 5.3 + `_. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + """ + + __slots__ = ("method", "headers", "target", "http_version") + + method: bytes + headers: Headers + target: bytes + http_version: bytes + + def __init__( + self, + *, + method: Union[bytes, str], + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + target: Union[bytes, str], + http_version: Union[bytes, str] = b"1.1", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "method", bytesify(method)) + object.__setattr__(self, "target", bytesify(target)) + object.__setattr__(self, "http_version", bytesify(http_version)) + else: + object.__setattr__(self, "method", method) + object.__setattr__(self, "target", target) + object.__setattr__(self, "http_version", http_version) + + # "A server MUST respond with a 400 (Bad Request) status code to any + # HTTP/1.1 request message that lacks a Host header field and to any + # request message that contains more than one Host header field or a + # Host header field with an invalid field-value." + # -- https://tools.ietf.org/html/rfc7230#section-5.4 + host_count = 0 + for name, value in self.headers: + if name == b"host": + host_count += 1 + if self.http_version == b"1.1" and host_count == 0: + raise LocalProtocolError("Missing mandatory Host: header") + if host_count > 1: + raise LocalProtocolError("Found multiple Host: headers") + + validate(method_re, self.method, "Illegal method characters") + validate(request_target_re, self.target, "Illegal target characters") + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class _ResponseBase(Event): + __slots__ = ("headers", "http_version", "reason", "status_code") + + headers: Headers + http_version: bytes + reason: bytes + status_code: int + + def __init__( + self, + *, + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + status_code: int, + http_version: Union[bytes, str] = b"1.1", + reason: Union[bytes, str] = b"", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "reason", bytesify(reason)) + object.__setattr__(self, "http_version", bytesify(http_version)) + if not isinstance(status_code, int): + raise LocalProtocolError("status code must be integer") + # Because IntEnum objects are instances of int, but aren't + # duck-compatible (sigh), see gh-72. + object.__setattr__(self, "status_code", int(status_code)) + else: + object.__setattr__(self, "reason", reason) + object.__setattr__(self, "http_version", http_version) + object.__setattr__(self, "status_code", status_code) + + self.__post_init__() + + def __post_init__(self) -> None: + pass + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class InformationalResponse(_ResponseBase): + """An HTTP informational response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`InformationalResponse`, this is always in the range [100, + 200). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for + details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (100 <= self.status_code < 200): + raise LocalProtocolError( + "InformationalResponse status_code should be in range " + "[100, 200), not {}".format(self.status_code) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Response(_ResponseBase): + """The beginning of an HTTP response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`Response`, this is always in the range [200, + 1000). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (200 <= self.status_code < 1000): + raise LocalProtocolError( + "Response status_code should be in range [200, 1000), not {}".format( + self.status_code + ) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Data(Event): + """Part of an HTTP message body. + + Fields: + + .. attribute:: data + + A :term:`bytes-like object` containing part of a message body. Or, if + using the ``combine=False`` argument to :meth:`Connection.send`, then + any object that your socket writing code knows what to do with, and for + which calling :func:`len` returns the number of bytes that will be + written -- see :ref:`sendfile` for details. + + .. attribute:: chunk_start + + A marker that indicates whether this data object is from the start of a + chunked transfer encoding chunk. This field is ignored when when a Data + event is provided to :meth:`Connection.send`: it is only valid on + events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + .. attribute:: chunk_end + + A marker that indicates whether this data object is the last for a + given chunked transfer encoding chunk. This field is ignored when when + a Data event is provided to :meth:`Connection.send`: it is only valid + on events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + """ + + __slots__ = ("data", "chunk_start", "chunk_end") + + data: bytes + chunk_start: bool + chunk_end: bool + + def __init__( + self, data: bytes, chunk_start: bool = False, chunk_end: bool = False + ) -> None: + object.__setattr__(self, "data", data) + object.__setattr__(self, "chunk_start", chunk_start) + object.__setattr__(self, "chunk_end", chunk_end) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that +# are forbidden to be sent in a trailer, since processing them as if they were +# present in the header section might bypass external security filters." +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part +# Unfortunately, the list of forbidden fields is long and vague :-/ +@dataclass(init=False, frozen=True) +class EndOfMessage(Event): + """The end of an HTTP message. + + Fields: + + .. attribute:: headers + + Default value: ``[]`` + + Any trailing headers attached to this message, represented as a list of + (name, value) pairs. See :ref:`the header normalization rules + ` for details. + + Must be empty unless ``Transfer-Encoding: chunked`` is in use. + + """ + + __slots__ = ("headers",) + + headers: Headers + + def __init__( + self, + *, + headers: Union[ + Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None + ] = None, + _parsed: bool = False, + ) -> None: + super().__init__() + if headers is None: + headers = Headers([]) + elif not isinstance(headers, Headers): + headers = normalize_and_validate(headers, _parsed=_parsed) + + object.__setattr__(self, "headers", headers) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(frozen=True) +class ConnectionClosed(Event): + """This event indicates that the sender has closed their outgoing + connection. + + Note that this does not necessarily mean that they can't *receive* further + data, because TCP connections are composed to two one-way channels which + can be closed independently. See :ref:`closing` for details. + + No fields. + """ + + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py new file mode 100644 index 0000000..31da3e2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_headers.py @@ -0,0 +1,282 @@ +import re +from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union + +from ._abnf import field_name, field_value +from ._util import bytesify, LocalProtocolError, validate + +if TYPE_CHECKING: + from ._events import Request + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +CONTENT_LENGTH_MAX_DIGITS = 20 # allow up to 1 billion TB - 1 + + +# Facts +# ----- +# +# Headers are: +# keys: case-insensitive ascii +# values: mixture of ascii and raw bytes +# +# "Historically, HTTP has allowed field content with text in the ISO-8859-1 +# charset [ISO-8859-1], supporting other charsets only through use of +# [RFC2047] encoding. In practice, most HTTP header field values use only a +# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD +# limit their field values to US-ASCII octets. A recipient SHOULD treat other +# octets in field content (obs-text) as opaque data." +# And it deprecates all non-ascii values +# +# Leading/trailing whitespace in header names is forbidden +# +# Values get leading/trailing whitespace stripped +# +# Content-Disposition actually needs to contain unicode semantically; to +# accomplish this it has a terrifically weird way of encoding the filename +# itself as ascii (and even this still has lots of cross-browser +# incompatibilities) +# +# Order is important: +# "a proxy MUST NOT change the order of these field values when forwarding a +# message" +# (and there are several headers where the order indicates a preference) +# +# Multiple occurences of the same header: +# "A sender MUST NOT generate multiple header fields with the same field name +# in a message unless either the entire field value for that header field is +# defined as a comma-separated list [or the header is Set-Cookie which gets a +# special exception]" - RFC 7230. (cookies are in RFC 6265) +# +# So every header aside from Set-Cookie can be merged by b", ".join if it +# occurs repeatedly. But, of course, they can't necessarily be split by +# .split(b","), because quoting. +# +# Given all this mess (case insensitive, duplicates allowed, order is +# important, ...), there doesn't appear to be any standard way to handle +# headers in Python -- they're almost like dicts, but... actually just +# aren't. For now we punt and just use a super simple representation: headers +# are a list of pairs +# +# [(name1, value1), (name2, value2), ...] +# +# where all entries are bytestrings, names are lowercase and have no +# leading/trailing whitespace, and values are bytestrings with no +# leading/trailing whitespace. Searching and updating are done via naive O(n) +# methods. +# +# Maybe a dict-of-lists would be better? + +_content_length_re = re.compile(rb"[0-9]+") +_field_name_re = re.compile(field_name.encode("ascii")) +_field_value_re = re.compile(field_value.encode("ascii")) + + +class Headers(Sequence[Tuple[bytes, bytes]]): + """ + A list-like interface that allows iterating over headers as byte-pairs + of (lowercased-name, value). + + Internally we actually store the representation as three-tuples, + including both the raw original casing, in order to preserve casing + over-the-wire, and the lowercased name, for case-insensitive comparisions. + + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert r.headers == [ + (b"host", b"example.org"), + (b"connection", b"keep-alive") + ] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive") + ] + """ + + __slots__ = "_full_items" + + def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None: + self._full_items = full_items + + def __bool__(self) -> bool: + return bool(self._full_items) + + def __eq__(self, other: object) -> bool: + return list(self) == list(other) # type: ignore + + def __len__(self) -> int: + return len(self._full_items) + + def __repr__(self) -> str: + return "" % repr(list(self)) + + def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override] + _, name, value = self._full_items[idx] + return (name, value) + + def raw_items(self) -> List[Tuple[bytes, bytes]]: + return [(raw_name, value) for raw_name, _, value in self._full_items] + + +HeaderTypes = Union[ + List[Tuple[bytes, bytes]], + List[Tuple[bytes, str]], + List[Tuple[str, bytes]], + List[Tuple[str, str]], +] + + +@overload +def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers: + ... + + +@overload +def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers: + ... + + +@overload +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + ... + + +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + new_headers = [] + seen_content_length = None + saw_transfer_encoding = False + for name, value in headers: + # For headers coming out of the parser, we can safely skip some steps, + # because it always returns bytes and has already run these regexes + # over the data: + if not _parsed: + name = bytesify(name) + value = bytesify(value) + validate(_field_name_re, name, "Illegal header name {!r}", name) + validate(_field_value_re, value, "Illegal header value {!r}", value) + assert isinstance(name, bytes) + assert isinstance(value, bytes) + + raw_name = name + name = name.lower() + if name == b"content-length": + lengths = {length.strip() for length in value.split(b",")} + if len(lengths) != 1: + raise LocalProtocolError("conflicting Content-Length headers") + value = lengths.pop() + validate(_content_length_re, value, "bad Content-Length") + if len(value) > CONTENT_LENGTH_MAX_DIGITS: + raise LocalProtocolError("bad Content-Length") + if seen_content_length is None: + seen_content_length = value + new_headers.append((raw_name, name, value)) + elif seen_content_length != value: + raise LocalProtocolError("conflicting Content-Length headers") + elif name == b"transfer-encoding": + # "A server that receives a request message with a transfer coding + # it does not understand SHOULD respond with 501 (Not + # Implemented)." + # https://tools.ietf.org/html/rfc7230#section-3.3.1 + if saw_transfer_encoding: + raise LocalProtocolError( + "multiple Transfer-Encoding headers", error_status_hint=501 + ) + # "All transfer-coding names are case-insensitive" + # -- https://tools.ietf.org/html/rfc7230#section-4 + value = value.lower() + if value != b"chunked": + raise LocalProtocolError( + "Only Transfer-Encoding: chunked is supported", + error_status_hint=501, + ) + saw_transfer_encoding = True + new_headers.append((raw_name, name, value)) + else: + new_headers.append((raw_name, name, value)) + return Headers(new_headers) + + +def get_comma_header(headers: Headers, name: bytes) -> List[bytes]: + # Should only be used for headers whose value is a list of + # comma-separated, case-insensitive values. + # + # The header name `name` is expected to be lower-case bytes. + # + # Connection: meets these criteria (including cast insensitivity). + # + # Content-Length: technically is just a single value (1*DIGIT), but the + # standard makes reference to implementations that do multiple values, and + # using this doesn't hurt. Ditto, case insensitivity doesn't things either + # way. + # + # Transfer-Encoding: is more complex (allows for quoted strings), so + # splitting on , is actually wrong. For example, this is legal: + # + # Transfer-Encoding: foo; options="1,2", chunked + # + # and should be parsed as + # + # foo; options="1,2" + # chunked + # + # but this naive function will parse it as + # + # foo; options="1 + # 2" + # chunked + # + # However, this is okay because the only thing we are going to do with + # any Transfer-Encoding is reject ones that aren't just "chunked", so + # both of these will be treated the same anyway. + # + # Expect: the only legal value is the literal string + # "100-continue". Splitting on commas is harmless. Case insensitive. + # + out: List[bytes] = [] + for _, found_name, found_raw_value in headers._full_items: + if found_name == name: + found_raw_value = found_raw_value.lower() + for found_split_value in found_raw_value.split(b","): + found_split_value = found_split_value.strip() + if found_split_value: + out.append(found_split_value) + return out + + +def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers: + # The header name `name` is expected to be lower-case bytes. + # + # Note that when we store the header we use title casing for the header + # names, in order to match the conventional HTTP header style. + # + # Simply calling `.title()` is a blunt approach, but it's correct + # here given the cases where we're using `set_comma_header`... + # + # Connection, Content-Length, Transfer-Encoding. + new_headers: List[Tuple[bytes, bytes]] = [] + for found_raw_name, found_name, found_raw_value in headers._full_items: + if found_name != name: + new_headers.append((found_raw_name, found_raw_value)) + for new_value in new_values: + new_headers.append((name.title(), new_value)) + return normalize_and_validate(new_headers) + + +def has_expect_100_continue(request: "Request") -> bool: + # https://tools.ietf.org/html/rfc7231#section-5.1.1 + # "A server that receives a 100-continue expectation in an HTTP/1.0 request + # MUST ignore that expectation." + if request.http_version < b"1.1": + return False + expect = get_comma_header(request.headers, b"expect") + return b"100-continue" in expect diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py new file mode 100644 index 0000000..576804c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_readers.py @@ -0,0 +1,250 @@ +# Code to read HTTP data +# +# Strategy: each reader is a callable which takes a ReceiveBuffer object, and +# either: +# 1) consumes some of it and returns an Event +# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate() +# and it might raise a LocalProtocolError, so simpler just to always use +# this) +# 3) returns None, meaning "I need more data" +# +# If they have a .read_eof attribute, then this will be called if an EOF is +# received -- but this is optional. Either way, the actual ConnectionClosed +# event will be generated afterwards. +# +# READERS is a dict describing how to pick a reader. It maps states to either: +# - a reader +# - or, for body readers, a dict of per-framing reader factories + +import re +from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union + +from ._abnf import chunk_header, header_field, request_line, status_line +from ._events import Data, EndOfMessage, InformationalResponse, Request, Response +from ._receivebuffer import ReceiveBuffer +from ._state import ( + CLIENT, + CLOSED, + DONE, + IDLE, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, +) +from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate + +__all__ = ["READERS"] + +header_field_re = re.compile(header_field.encode("ascii")) +obs_fold_re = re.compile(rb"[ \t]+") + + +def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]: + it = iter(lines) + last: Optional[bytes] = None + for line in it: + match = obs_fold_re.match(line) + if match: + if last is None: + raise LocalProtocolError("continuation line at start of headers") + if not isinstance(last, bytearray): + # Cast to a mutable type, avoiding copy on append to ensure O(n) time + last = bytearray(last) + last += b" " + last += line[match.end() :] + else: + if last is not None: + yield last + last = line + if last is not None: + yield last + + +def _decode_header_lines( + lines: Iterable[bytes], +) -> Iterable[Tuple[bytes, bytes]]: + for line in _obsolete_line_fold(lines): + matches = validate(header_field_re, line, "illegal header line: {!r}", line) + yield (matches["field_name"], matches["field_value"]) + + +request_line_re = re.compile(request_line.encode("ascii")) + + +def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no request line received") + matches = validate( + request_line_re, lines[0], "illegal request line: {!r}", lines[0] + ) + return Request( + headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches + ) + + +status_line_re = re.compile(status_line.encode("ascii")) + + +def maybe_read_from_SEND_RESPONSE_server( + buf: ReceiveBuffer, +) -> Union[InformationalResponse, Response, None]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no response line received") + matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0]) + http_version = ( + b"1.1" if matches["http_version"] is None else matches["http_version"] + ) + reason = b"" if matches["reason"] is None else matches["reason"] + status_code = int(matches["status_code"]) + class_: Union[Type[InformationalResponse], Type[Response]] = ( + InformationalResponse if status_code < 200 else Response + ) + return class_( + headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + status_code=status_code, + reason=reason, + http_version=http_version, + ) + + +class ContentLengthReader: + def __init__(self, length: int) -> None: + self._length = length + self._remaining = length + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._remaining == 0: + return EndOfMessage() + data = buf.maybe_extract_at_most(self._remaining) + if data is None: + return None + self._remaining -= len(data) + return Data(data=data) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(received {} bytes, expected {})".format( + self._length - self._remaining, self._length + ) + ) + + +chunk_header_re = re.compile(chunk_header.encode("ascii")) + + +class ChunkedReader: + def __init__(self) -> None: + self._bytes_in_chunk = 0 + # After reading a chunk, we have to throw away the trailing \r\n. + # This tracks the bytes that we need to match and throw away. + self._bytes_to_discard = b"" + self._reading_trailer = False + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._reading_trailer: + lines = buf.maybe_extract_lines() + if lines is None: + return None + return EndOfMessage(headers=list(_decode_header_lines(lines))) + if self._bytes_to_discard: + data = buf.maybe_extract_at_most(len(self._bytes_to_discard)) + if data is None: + return None + if data != self._bytes_to_discard[: len(data)]: + raise LocalProtocolError( + f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})" + ) + self._bytes_to_discard = self._bytes_to_discard[len(data) :] + if self._bytes_to_discard: + return None + # else, fall through and read some more + assert self._bytes_to_discard == b"" + if self._bytes_in_chunk == 0: + # We need to refill our chunk count + chunk_header = buf.maybe_extract_next_line() + if chunk_header is None: + return None + matches = validate( + chunk_header_re, + chunk_header, + "illegal chunk header: {!r}", + chunk_header, + ) + # XX FIXME: we discard chunk extensions. Does anyone care? + self._bytes_in_chunk = int(matches["chunk_size"], base=16) + if self._bytes_in_chunk == 0: + self._reading_trailer = True + return self(buf) + chunk_start = True + else: + chunk_start = False + assert self._bytes_in_chunk > 0 + data = buf.maybe_extract_at_most(self._bytes_in_chunk) + if data is None: + return None + self._bytes_in_chunk -= len(data) + if self._bytes_in_chunk == 0: + self._bytes_to_discard = b"\r\n" + chunk_end = True + else: + chunk_end = False + return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(incomplete chunked read)" + ) + + +class Http10Reader: + def __call__(self, buf: ReceiveBuffer) -> Optional[Data]: + data = buf.maybe_extract_at_most(999999999) + if data is None: + return None + return Data(data=data) + + def read_eof(self) -> EndOfMessage: + return EndOfMessage() + + +def expect_nothing(buf: ReceiveBuffer) -> None: + if buf: + raise LocalProtocolError("Got data when expecting EOF") + return None + + +ReadersType = Dict[ + Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]], + Union[Callable[..., Any], Dict[str, Callable[..., Any]]], +] + +READERS: ReadersType = { + (CLIENT, IDLE): maybe_read_from_IDLE_client, + (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server, + (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server, + (CLIENT, DONE): expect_nothing, + (CLIENT, MUST_CLOSE): expect_nothing, + (CLIENT, CLOSED): expect_nothing, + (SERVER, DONE): expect_nothing, + (SERVER, MUST_CLOSE): expect_nothing, + (SERVER, CLOSED): expect_nothing, + SEND_BODY: { + "chunked": ChunkedReader, + "content-length": ContentLengthReader, + "http/1.0": Http10Reader, + }, +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py new file mode 100644 index 0000000..e5c4e08 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py @@ -0,0 +1,153 @@ +import re +import sys +from typing import List, Optional, Union + +__all__ = ["ReceiveBuffer"] + + +# Operations we want to support: +# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable), +# or wait until there is one +# - read at-most-N bytes +# Goals: +# - on average, do this fast +# - worst case, do this in O(n) where n is the number of bytes processed +# Plan: +# - store bytearray, offset, how far we've searched for a separator token +# - use the how-far-we've-searched data to avoid rescanning +# - while doing a stream of uninterrupted processing, advance offset instead +# of constantly copying +# WARNING: +# - I haven't benchmarked or profiled any of this yet. +# +# Note that starting in Python 3.4, deleting the initial n bytes from a +# bytearray is amortized O(n), thanks to some excellent work by Antoine +# Martin: +# +# https://bugs.python.org/issue19087 +# +# This means that if we only supported 3.4+, we could get rid of the code here +# involving self._start and self.compress, because it's doing exactly the same +# thing that bytearray now does internally. +# +# BUT unfortunately, we still support 2.7, and reading short segments out of a +# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually +# delete this code. Yet: +# +# https://pythonclock.org/ +# +# (Two things to double-check first though: make sure PyPy also has the +# optimization, and benchmark to make sure it's a win, since we do have a +# slightly clever thing where we delay calling compress() until we've +# processed a whole event, which could in theory be slightly more efficient +# than the internal bytearray support.) +blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE) + + +class ReceiveBuffer: + def __init__(self) -> None: + self._data = bytearray() + self._next_line_search = 0 + self._multiple_lines_search = 0 + + def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": + self._data += byteslike + return self + + def __bool__(self) -> bool: + return bool(len(self)) + + def __len__(self) -> int: + return len(self._data) + + # for @property unprocessed_data + def __bytes__(self) -> bytes: + return bytes(self._data) + + def _extract(self, count: int) -> bytearray: + # extracting an initial slice of the data buffer and return it + out = self._data[:count] + del self._data[:count] + + self._next_line_search = 0 + self._multiple_lines_search = 0 + + return out + + def maybe_extract_at_most(self, count: int) -> Optional[bytearray]: + """ + Extract a fixed number of bytes from the buffer. + """ + out = self._data[:count] + if not out: + return None + + return self._extract(count) + + def maybe_extract_next_line(self) -> Optional[bytearray]: + """ + Extract the first line, if it is completed in the buffer. + """ + # Only search in buffer space that we've not already looked at. + search_start_index = max(0, self._next_line_search - 1) + partial_idx = self._data.find(b"\r\n", search_start_index) + + if partial_idx == -1: + self._next_line_search = len(self._data) + return None + + # + 2 is to compensate len(b"\r\n") + idx = partial_idx + 2 + + return self._extract(idx) + + def maybe_extract_lines(self) -> Optional[List[bytearray]]: + """ + Extract everything up to the first blank line, and return a list of lines. + """ + # Handle the case where we have an immediate empty line. + if self._data[:1] == b"\n": + self._extract(1) + return [] + + if self._data[:2] == b"\r\n": + self._extract(2) + return [] + + # Only search in buffer space that we've not already looked at. + match = blank_line_regex.search(self._data, self._multiple_lines_search) + if match is None: + self._multiple_lines_search = max(0, len(self._data) - 2) + return None + + # Truncate the buffer and return it. + idx = match.span(0)[-1] + out = self._extract(idx) + lines = out.split(b"\n") + + for line in lines: + if line.endswith(b"\r"): + del line[-1] + + assert lines[-2] == lines[-1] == b"" + + del lines[-2:] + + return lines + + # In theory we should wait until `\r\n` before starting to validate + # incoming data. However it's interesting to detect (very) invalid data + # early given they might not even contain `\r\n` at all (hence only + # timeout will get rid of them). + # This is not a 100% effective detection but more of a cheap sanity check + # allowing for early abort in some useful cases. + # This is especially interesting when peer is messing up with HTTPS and + # sent us a TLS stream where we were expecting plain HTTP given all + # versions of TLS so far start handshake with a 0x16 message type code. + def is_next_line_obviously_invalid_request_line(self) -> bool: + try: + # HTTP header line must not contain non-printable characters + # and should not start with a space + return self._data[0] < 0x21 + except IndexError: + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py new file mode 100644 index 0000000..3ad444b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_state.py @@ -0,0 +1,365 @@ +################################################################ +# The core state machine +################################################################ +# +# Rule 1: everything that affects the state machine and state transitions must +# live here in this file. As much as possible goes into the table-based +# representation, but for the bits that don't quite fit, the actual code and +# state must nonetheless live here. +# +# Rule 2: this file does not know about what role we're playing; it only knows +# about HTTP request/response cycles in the abstract. This ensures that we +# don't cheat and apply different rules to local and remote parties. +# +# +# Theory of operation +# =================== +# +# Possibly the simplest way to think about this is that we actually have 5 +# different state machines here. Yes, 5. These are: +# +# 1) The client state, with its complicated automaton (see the docs) +# 2) The server state, with its complicated automaton (see the docs) +# 3) The keep-alive state, with possible states {True, False} +# 4) The SWITCH_CONNECT state, with possible states {False, True} +# 5) The SWITCH_UPGRADE state, with possible states {False, True} +# +# For (3)-(5), the first state listed is the initial state. +# +# (1)-(3) are stored explicitly in member variables. The last +# two are stored implicitly in the pending_switch_proposals set as: +# (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals) +# (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals) +# +# And each of these machines has two different kinds of transitions: +# +# a) Event-triggered +# b) State-triggered +# +# Event triggered is the obvious thing that you'd think it is: some event +# happens, and if it's the right event at the right time then a transition +# happens. But there are somewhat complicated rules for which machines can +# "see" which events. (As a rule of thumb, if a machine "sees" an event, this +# means two things: the event can affect the machine, and if the machine is +# not in a state where it expects that event then it's an error.) These rules +# are: +# +# 1) The client machine sees all h11.events objects emitted by the client. +# +# 2) The server machine sees all h11.events objects emitted by the server. +# +# It also sees the client's Request event. +# +# And sometimes, server events are annotated with a _SWITCH_* event. For +# example, we can have a (Response, _SWITCH_CONNECT) event, which is +# different from a regular Response event. +# +# 3) The keep-alive machine sees the process_keep_alive_disabled() event +# (which is derived from Request/Response events), and this event +# transitions it from True -> False, or from False -> False. There's no way +# to transition back. +# +# 4&5) The _SWITCH_* machines transition from False->True when we get a +# Request that proposes the relevant type of switch (via +# process_client_switch_proposals), and they go from True->False when we +# get a Response that has no _SWITCH_* annotation. +# +# So that's event-triggered transitions. +# +# State-triggered transitions are less standard. What they do here is couple +# the machines together. The way this works is, when certain *joint* +# configurations of states are achieved, then we automatically transition to a +# new *joint* state. So, for example, if we're ever in a joint state with +# +# client: DONE +# keep-alive: False +# +# then the client state immediately transitions to: +# +# client: MUST_CLOSE +# +# This is fundamentally different from an event-based transition, because it +# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state +# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive +# transitioned True -> False. Either way, once this precondition is satisfied, +# this transition is immediately triggered. +# +# What if two conflicting state-based transitions get enabled at the same +# time? In practice there's only one case where this arises (client DONE -> +# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by +# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition. +# +# Implementation +# -------------- +# +# The event-triggered transitions for the server and client machines are all +# stored explicitly in a table. Ditto for the state-triggered transitions that +# involve just the server and client state. +# +# The transitions for the other machines, and the state-triggered transitions +# that involve the other machines, are written out as explicit Python code. +# +# It'd be nice if there were some cleaner way to do all this. This isn't +# *too* terrible, but I feel like it could probably be better. +# +# WARNING +# ------- +# +# The script that generates the state machine diagrams for the docs knows how +# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS +# tables. But it can't automatically read the transitions that are written +# directly in Python code. So if you touch those, you need to also update the +# script to keep it in sync! +from typing import cast, Dict, Optional, Set, Tuple, Type, Union + +from ._events import * +from ._util import LocalProtocolError, Sentinel + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "CLIENT", + "SERVER", + "IDLE", + "SEND_RESPONSE", + "SEND_BODY", + "DONE", + "MUST_CLOSE", + "CLOSED", + "MIGHT_SWITCH_PROTOCOL", + "SWITCHED_PROTOCOL", + "ERROR", +] + + +class CLIENT(Sentinel, metaclass=Sentinel): + pass + + +class SERVER(Sentinel, metaclass=Sentinel): + pass + + +# States +class IDLE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_RESPONSE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_BODY(Sentinel, metaclass=Sentinel): + pass + + +class DONE(Sentinel, metaclass=Sentinel): + pass + + +class MUST_CLOSE(Sentinel, metaclass=Sentinel): + pass + + +class CLOSED(Sentinel, metaclass=Sentinel): + pass + + +class ERROR(Sentinel, metaclass=Sentinel): + pass + + +# Switch types +class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel): + pass + + +EventTransitionType = Dict[ + Type[Sentinel], + Dict[ + Type[Sentinel], + Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]], + ], +] + +EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = { + CLIENT: { + IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED}, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + MIGHT_SWITCH_PROTOCOL: {}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, + SERVER: { + IDLE: { + ConnectionClosed: CLOSED, + Response: SEND_BODY, + # Special case: server sees client Request events, in this form + (Request, CLIENT): SEND_RESPONSE, + }, + SEND_RESPONSE: { + InformationalResponse: SEND_RESPONSE, + Response: SEND_BODY, + (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL, + (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL, + }, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, +} + +StateTransitionType = Dict[ + Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]] +] + +# NB: there are also some special-case state-triggered transitions hard-coded +# into _fire_state_triggered_transitions below. +STATE_TRIGGERED_TRANSITIONS: StateTransitionType = { + # (Client state, Server state) -> new states + # Protocol negotiation + (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL}, + # Socket shutdown + (CLOSED, DONE): {SERVER: MUST_CLOSE}, + (CLOSED, IDLE): {SERVER: MUST_CLOSE}, + (ERROR, DONE): {SERVER: MUST_CLOSE}, + (DONE, CLOSED): {CLIENT: MUST_CLOSE}, + (IDLE, CLOSED): {CLIENT: MUST_CLOSE}, + (DONE, ERROR): {CLIENT: MUST_CLOSE}, +} + + +class ConnectionState: + def __init__(self) -> None: + # Extra bits of state that don't quite fit into the state model. + + # If this is False then it enables the automatic DONE -> MUST_CLOSE + # transition. Don't set this directly; call .keep_alive_disabled() + self.keep_alive = True + + # This is a subset of {UPGRADE, CONNECT}, containing the proposals + # made by the client for switching protocols. + self.pending_switch_proposals: Set[Type[Sentinel]] = set() + + self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE} + + def process_error(self, role: Type[Sentinel]) -> None: + self.states[role] = ERROR + self._fire_state_triggered_transitions() + + def process_keep_alive_disabled(self) -> None: + self.keep_alive = False + self._fire_state_triggered_transitions() + + def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None: + self.pending_switch_proposals.add(switch_event) + self._fire_state_triggered_transitions() + + def process_event( + self, + role: Type[Sentinel], + event_type: Type[Event], + server_switch_event: Optional[Type[Sentinel]] = None, + ) -> None: + _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type + if server_switch_event is not None: + assert role is SERVER + if server_switch_event not in self.pending_switch_proposals: + raise LocalProtocolError( + "Received server _SWITCH_UPGRADE event without a pending proposal" + ) + _event_type = (event_type, server_switch_event) + if server_switch_event is None and _event_type is Response: + self.pending_switch_proposals = set() + self._fire_event_triggered_transitions(role, _event_type) + # Special case: the server state does get to see Request + # events. + if _event_type is Request: + assert role is CLIENT + self._fire_event_triggered_transitions(SERVER, (Request, CLIENT)) + self._fire_state_triggered_transitions() + + def _fire_event_triggered_transitions( + self, + role: Type[Sentinel], + event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], + ) -> None: + state = self.states[role] + try: + new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type] + except KeyError: + event_type = cast(Type[Event], event_type) + raise LocalProtocolError( + "can't handle event type {} when role={} and state={}".format( + event_type.__name__, role, self.states[role] + ) + ) from None + self.states[role] = new_state + + def _fire_state_triggered_transitions(self) -> None: + # We apply these rules repeatedly until converging on a fixed point + while True: + start_states = dict(self.states) + + # It could happen that both these special-case transitions are + # enabled at the same time: + # + # DONE -> MIGHT_SWITCH_PROTOCOL + # DONE -> MUST_CLOSE + # + # For example, this will always be true of a HTTP/1.0 client + # requesting CONNECT. If this happens, the protocol switch takes + # priority. From there the client will either go to + # SWITCHED_PROTOCOL, in which case it's none of our business when + # they close the connection, or else the server will deny the + # request, in which case the client will go back to DONE and then + # from there to MUST_CLOSE. + if self.pending_switch_proposals: + if self.states[CLIENT] is DONE: + self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL + + if not self.pending_switch_proposals: + if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL: + self.states[CLIENT] = DONE + + if not self.keep_alive: + for role in (CLIENT, SERVER): + if self.states[role] is DONE: + self.states[role] = MUST_CLOSE + + # Tabular state-triggered transitions + joint_state = (self.states[CLIENT], self.states[SERVER]) + changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {}) + self.states.update(changes) + + if self.states == start_states: + # Fixed point reached + return + + def start_next_cycle(self) -> None: + if self.states != {CLIENT: DONE, SERVER: DONE}: + raise LocalProtocolError( + f"not in a reusable state. self.states={self.states}" + ) + # Can't reach DONE/DONE with any of these active, but still, let's be + # sure. + assert self.keep_alive + assert not self.pending_switch_proposals + self.states = {CLIENT: IDLE, SERVER: IDLE} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py new file mode 100644 index 0000000..6718445 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_util.py @@ -0,0 +1,135 @@ +from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union + +__all__ = [ + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "validate", + "bytesify", +] + + +class ProtocolError(Exception): + """Exception indicating a violation of the HTTP/1.1 protocol. + + This as an abstract base class, with two concrete base classes: + :exc:`LocalProtocolError`, which indicates that you tried to do something + that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which + indicates that the remote peer tried to do something that HTTP/1.1 says is + illegal. See :ref:`error-handling` for details. + + In addition to the normal :exc:`Exception` features, it has one attribute: + + .. attribute:: error_status_hint + + This gives a suggestion as to what status code a server might use if + this error occurred as part of a request. + + For a :exc:`RemoteProtocolError`, this is useful as a suggestion for + how you might want to respond to a misbehaving peer, if you're + implementing a server. + + For a :exc:`LocalProtocolError`, this can be taken as a suggestion for + how your peer might have responded to *you* if h11 had allowed you to + continue. + + The default is 400 Bad Request, a generic catch-all for protocol + violations. + + """ + + def __init__(self, msg: str, error_status_hint: int = 400) -> None: + if type(self) is ProtocolError: + raise TypeError("tried to directly instantiate ProtocolError") + Exception.__init__(self, msg) + self.error_status_hint = error_status_hint + + +# Strategy: there are a number of public APIs where a LocalProtocolError can +# be raised (send(), all the different event constructors, ...), and only one +# public API where RemoteProtocolError can be raised +# (receive_data()). Therefore we always raise LocalProtocolError internally, +# and then receive_data will translate this into a RemoteProtocolError. +# +# Internally: +# LocalProtocolError is the generic "ProtocolError". +# Externally: +# LocalProtocolError is for local errors and RemoteProtocolError is for +# remote errors. +class LocalProtocolError(ProtocolError): + def _reraise_as_remote_protocol_error(self) -> NoReturn: + # After catching a LocalProtocolError, use this method to re-raise it + # as a RemoteProtocolError. This method must be called from inside an + # except: block. + # + # An easy way to get an equivalent RemoteProtocolError is just to + # modify 'self' in place. + self.__class__ = RemoteProtocolError # type: ignore + # But the re-raising is somewhat non-trivial -- you might think that + # now that we've modified the in-flight exception object, that just + # doing 'raise' to re-raise it would be enough. But it turns out that + # this doesn't work, because Python tracks the exception type + # (exc_info[0]) separately from the exception object (exc_info[1]), + # and we only modified the latter. So we really do need to re-raise + # the new type explicitly. + # On py3, the traceback is part of the exception object, so our + # in-place modification preserved it and we can just re-raise: + raise self + + +class RemoteProtocolError(ProtocolError): + pass + + +def validate( + regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any +) -> Dict[str, bytes]: + match = regex.fullmatch(data) + if not match: + if format_args: + msg = msg.format(*format_args) + raise LocalProtocolError(msg) + return match.groupdict() + + +# Sentinel values +# +# - Inherit identity-based comparison and hashing from object +# - Have a nice repr +# - Have a *bonus property*: type(sentinel) is sentinel +# +# The bonus property is useful if you want to take the return value from +# next_event() and do some sort of dispatch based on type(event). + +_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel") + + +class Sentinel(type): + def __new__( + cls: Type[_T_Sentinel], + name: str, + bases: Tuple[type, ...], + namespace: Dict[str, Any], + **kwds: Any + ) -> _T_Sentinel: + assert bases == (Sentinel,) + v = super().__new__(cls, name, bases, namespace, **kwds) + v.__class__ = v # type: ignore + return v + + def __repr__(self) -> str: + return self.__name__ + + +# Used for methods, request targets, HTTP versions, header names, and header +# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always +# returns bytes. +def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes: + # Fast-path: + if type(s) is bytes: + return s + if isinstance(s, str): + s = s.encode("ascii") + if isinstance(s, int): + raise TypeError("expected bytes-like object, not int") + return bytes(s) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py new file mode 100644 index 0000000..76e7327 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_version.py @@ -0,0 +1,16 @@ +# This file must be kept very simple, because it is consumed from several +# places -- it is imported by h11/__init__.py, execfile'd by setup.py, etc. + +# We use a simple scheme: +# 1.0.0 -> 1.0.0+dev -> 1.1.0 -> 1.1.0+dev +# where the +dev versions are never released into the wild, they're just what +# we stick into the VCS in between releases. +# +# This is compatible with PEP 440: +# http://legacy.python.org/dev/peps/pep-0440/ +# via the use of the "local suffix" "+dev", which is disallowed on index +# servers and causes 1.0.0+dev to sort after plain 1.0.0, which is what we +# want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* +# 1.0.0.) + +__version__ = "0.16.0" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py new file mode 100644 index 0000000..939cdb9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/_writers.py @@ -0,0 +1,145 @@ +# Code to read HTTP data +# +# Strategy: each writer takes an event + a write-some-bytes function, which is +# calls. +# +# WRITERS is a dict describing how to pick a reader. It maps states to either: +# - a writer +# - or, for body writers, a dict of framin-dependent writer factories + +from typing import Any, Callable, Dict, List, Tuple, Type, Union + +from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response +from ._headers import Headers +from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER +from ._util import LocalProtocolError, Sentinel + +__all__ = ["WRITERS"] + +Writer = Callable[[bytes], Any] + + +def write_headers(headers: Headers, write: Writer) -> None: + # "Since the Host field-value is critical information for handling a + # request, a user agent SHOULD generate Host as the first header field + # following the request-line." - RFC 7230 + raw_items = headers._full_items + for raw_name, name, value in raw_items: + if name == b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + for raw_name, name, value in raw_items: + if name != b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + write(b"\r\n") + + +def write_request(request: Request, write: Writer) -> None: + if request.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) + write_headers(request.headers, write) + + +# Shared between InformationalResponse and Response +def write_any_response( + response: Union[InformationalResponse, Response], write: Writer +) -> None: + if response.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + status_bytes = str(response.status_code).encode("ascii") + # We don't bother sending ascii status messages like "OK"; they're + # optional and ignored by the protocol. (But the space after the numeric + # status code is mandatory.) + # + # XX FIXME: could at least make an effort to pull out the status message + # from stdlib's http.HTTPStatus table. Or maybe just steal their enums + # (either by import or copy/paste). We already accept them as status codes + # since they're of type IntEnum < int. + write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) + write_headers(response.headers, write) + + +class BodyWriter: + def __call__(self, event: Event, write: Writer) -> None: + if type(event) is Data: + self.send_data(event.data, write) + elif type(event) is EndOfMessage: + self.send_eom(event.headers, write) + else: # pragma: no cover + assert False + + def send_data(self, data: bytes, write: Writer) -> None: + pass + + def send_eom(self, headers: Headers, write: Writer) -> None: + pass + + +# +# These are all careful not to do anything to 'data' except call len(data) and +# write(data). This allows us to transparently pass-through funny objects, +# like placeholder objects referring to files on disk that will be sent via +# sendfile(2). +# +class ContentLengthWriter(BodyWriter): + def __init__(self, length: int) -> None: + self._length = length + + def send_data(self, data: bytes, write: Writer) -> None: + self._length -= len(data) + if self._length < 0: + raise LocalProtocolError("Too much data for declared Content-Length") + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if self._length != 0: + raise LocalProtocolError("Too little data for declared Content-Length") + if headers: + raise LocalProtocolError("Content-Length and trailers don't mix") + + +class ChunkedWriter(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + # if we encoded 0-length data in the naive way, it would look like an + # end-of-message. + if not data: + return + write(b"%x\r\n" % len(data)) + write(data) + write(b"\r\n") + + def send_eom(self, headers: Headers, write: Writer) -> None: + write(b"0\r\n") + write_headers(headers, write) + + +class Http10Writer(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if headers: + raise LocalProtocolError("can't send trailers to HTTP/1.0 client") + # no need to close the socket ourselves, that will be taken care of by + # Connection: close machinery + + +WritersType = Dict[ + Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]], + Union[ + Dict[str, Type[BodyWriter]], + Callable[[Union[InformationalResponse, Response], Writer], None], + Callable[[Request, Writer], None], + ], +] + +WRITERS: WritersType = { + (CLIENT, IDLE): write_request, + (SERVER, IDLE): write_any_response, + (SERVER, SEND_RESPONSE): write_any_response, + SEND_BODY: { + "chunked": ChunkedWriter, + "content-length": ContentLengthWriter, + "http/1.0": Http10Writer, + }, +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/h11/py.typed @@ -0,0 +1 @@ +Marker diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA new file mode 100644 index 0000000..8056834 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA @@ -0,0 +1,625 @@ +Metadata-Version: 2.4 +Name: httpcore +Version: 1.0.9 +Summary: A minimal low-level HTTP client. +Project-URL: Documentation, https://www.encode.io/httpcore +Project-URL: Homepage, https://www.encode.io/httpcore/ +Project-URL: Source, https://github.com/encode/httpcore +Author-email: Tom Christie +License-Expression: BSD-3-Clause +License-File: LICENSE.md +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Web Environment +Classifier: Framework :: AsyncIO +Classifier: Framework :: Trio +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.8 +Requires-Dist: certifi +Requires-Dist: h11>=0.16 +Provides-Extra: asyncio +Requires-Dist: anyio<5.0,>=4.0; extra == 'asyncio' +Provides-Extra: http2 +Requires-Dist: h2<5,>=3; extra == 'http2' +Provides-Extra: socks +Requires-Dist: socksio==1.*; extra == 'socks' +Provides-Extra: trio +Requires-Dist: trio<1.0,>=0.22.0; extra == 'trio' +Description-Content-Type: text/markdown + +# HTTP Core + +[![Test Suite](https://github.com/encode/httpcore/workflows/Test%20Suite/badge.svg)](https://github.com/encode/httpcore/actions) +[![Package version](https://badge.fury.io/py/httpcore.svg)](https://pypi.org/project/httpcore/) + +> *Do one thing, and do it well.* + +The HTTP Core package provides a minimal low-level HTTP client, which does +one thing only. Sending HTTP requests. + +It does not provide any high level model abstractions over the API, +does not handle redirects, multipart uploads, building authentication headers, +transparent HTTP caching, URL parsing, session cookie handling, +content or charset decoding, handling JSON, environment based configuration +defaults, or any of that Jazz. + +Some things HTTP Core does do: + +* Sending HTTP requests. +* Thread-safe / task-safe connection pooling. +* HTTP(S) proxy & SOCKS proxy support. +* Supports HTTP/1.1 and HTTP/2. +* Provides both sync and async interfaces. +* Async backend support for `asyncio` and `trio`. + +## Requirements + +Python 3.8+ + +## Installation + +For HTTP/1.1 only support, install with: + +```shell +$ pip install httpcore +``` + +There are also a number of optional extras available... + +```shell +$ pip install httpcore['asyncio,trio,http2,socks'] +``` + +## Sending requests + +Send an HTTP request: + +```python +import httpcore + +response = httpcore.request("GET", "https://www.example.com/") + +print(response) +# +print(response.status) +# 200 +print(response.headers) +# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...] +print(response.content) +# b'\n\n\nExample Domain\n\n\n ...' +``` + +The top-level `httpcore.request()` function is provided for convenience. In practice whenever you're working with `httpcore` you'll want to use the connection pooling functionality that it provides. + +```python +import httpcore + +http = httpcore.ConnectionPool() +response = http.request("GET", "https://www.example.com/") +``` + +Once you're ready to get going, [head over to the documentation](https://www.encode.io/httpcore/). + +## Motivation + +You *probably* don't want to be using HTTP Core directly. It might make sense if +you're writing something like a proxy service in Python, and you just want +something at the lowest possible level, but more typically you'll want to use +a higher level client library, such as `httpx`. + +The motivation for `httpcore` is: + +* To provide a reusable low-level client library, that other packages can then build on top of. +* To provide a *really clear interface split* between the networking code and client logic, + so that each is easier to understand and reason about in isolation. + +## Dependencies + +The `httpcore` package has the following dependencies... + +* `h11` +* `certifi` + +And the following optional extras... + +* `anyio` - Required by `pip install httpcore['asyncio']`. +* `trio` - Required by `pip install httpcore['trio']`. +* `h2` - Required by `pip install httpcore['http2']`. +* `socksio` - Required by `pip install httpcore['socks']`. + +## Versioning + +We use [SEMVER for our versioning policy](https://semver.org/). + +For changes between package versions please see our [project changelog](CHANGELOG.md). + +We recommend pinning your requirements either the most current major version, or a more specific version range: + +```python +pip install 'httpcore==1.*' +``` +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## Version 1.0.9 (April 24th, 2025) + +- Resolve https://github.com/advisories/GHSA-vqfr-h8mv-ghfj with h11 dependency update. (#1008) + +## Version 1.0.8 (April 11th, 2025) + +- Fix `AttributeError` when importing on Python 3.14. (#1005) + +## Version 1.0.7 (November 15th, 2024) + +- Support `proxy=…` configuration on `ConnectionPool()`. (#974) + +## Version 1.0.6 (October 1st, 2024) + +- Relax `trio` dependency pinning. (#956) +- Handle `trio` raising `NotImplementedError` on unsupported platforms. (#955) +- Handle mapping `ssl.SSLError` to `httpcore.ConnectError`. (#918) + +## 1.0.5 (March 27th, 2024) + +- Handle `EndOfStream` exception for anyio backend. (#899) +- Allow trio `0.25.*` series in package dependancies. (#903) + +## 1.0.4 (February 21st, 2024) + +- Add `target` request extension. (#888) +- Fix support for connection `Upgrade` and `CONNECT` when some data in the stream has been read. (#882) + +## 1.0.3 (February 13th, 2024) + +- Fix support for async cancellations. (#880) +- Fix trace extension when used with socks proxy. (#849) +- Fix SSL context for connections using the "wss" scheme (#869) + +## 1.0.2 (November 10th, 2023) + +- Fix `float("inf")` timeouts in `Event.wait` function. (#846) + +## 1.0.1 (November 3rd, 2023) + +- Fix pool timeout to account for the total time spent retrying. (#823) +- Raise a neater RuntimeError when the correct async deps are not installed. (#826) +- Add support for synchronous TLS-in-TLS streams. (#840) + +## 1.0.0 (October 6th, 2023) + +From version 1.0 our async support is now optional, as the package has minimal dependencies by default. + +For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`. + +The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/. + +- Async support becomes fully optional. (#809) +- Add support for Python 3.12. (#807) + +## 0.18.0 (September 8th, 2023) + +- Add support for HTTPS proxies. (#745, #786) +- Drop Python 3.7 support. (#727) +- Handle `sni_hostname` extension with SOCKS proxy. (#774) +- Handle HTTP/1.1 half-closed connections gracefully. (#641) +- Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#762) + +## 0.17.3 (July 5th, 2023) + +- Support async cancellations, ensuring that the connection pool is left in a clean state when cancellations occur. (#726) +- The networking backend interface has [been added to the public API](https://www.encode.io/httpcore/network-backends). Some classes which were previously private implementation detail are now part of the top-level public API. (#699) +- Graceful handling of HTTP/2 GoAway frames, with requests being transparently retried on a new connection. (#730) +- Add exceptions when a synchronous `trace callback` is passed to an asynchronous request or an asynchronous `trace callback` is passed to a synchronous request. (#717) +- Drop Python 3.7 support. (#727) + +## 0.17.2 (May 23th, 2023) + +- Add `socket_options` argument to `ConnectionPool` and `HTTProxy` classes. (#668) +- Improve logging with per-module logger names. (#690) +- Add `sni_hostname` request extension. (#696) +- Resolve race condition during import of `anyio` package. (#692) +- Enable TCP_NODELAY for all synchronous sockets. (#651) + +## 0.17.1 (May 17th, 2023) + +- If 'retries' is set, then allow retries if an SSL handshake error occurs. (#669) +- Improve correctness of tracebacks on network exceptions, by raising properly chained exceptions. (#678) +- Prevent connection-hanging behaviour when HTTP/2 connections are closed by a server-sent 'GoAway' frame. (#679) +- Fix edge-case exception when removing requests from the connection pool. (#680) +- Fix pool timeout edge-case. (#688) + +## 0.17.0 (March 16th, 2023) + +- Add DEBUG level logging. (#648) +- Respect HTTP/2 max concurrent streams when settings updates are sent by server. (#652) +- Increase the allowable HTTP header size to 100kB. (#647) +- Add `retries` option to SOCKS proxy classes. (#643) + +## 0.16.3 (December 20th, 2022) + +- Allow `ws` and `wss` schemes. Allows us to properly support websocket upgrade connections. (#625) +- Forwarding HTTP proxies use a connection-per-remote-host. Required by some proxy implementations. (#637) +- Don't raise `RuntimeError` when closing a connection pool with active connections. Removes some error cases when cancellations are used. (#631) +- Lazy import `anyio`, so that it's no longer a hard dependancy, and isn't imported if unused. (#639) + +## 0.16.2 (November 25th, 2022) + +- Revert 'Fix async cancellation behaviour', which introduced race conditions. (#627) +- Raise `RuntimeError` if attempting to us UNIX domain sockets on Windows. (#619) + +## 0.16.1 (November 17th, 2022) + +- Fix HTTP/1.1 interim informational responses, such as "100 Continue". (#605) + +## 0.16.0 (October 11th, 2022) + +- Support HTTP/1.1 informational responses. (#581) +- Fix async cancellation behaviour. (#580) +- Support `h11` 0.14. (#579) + +## 0.15.0 (May 17th, 2022) + +- Drop Python 3.6 support (#535) +- Ensure HTTP proxy CONNECT requests include `timeout` configuration. (#506) +- Switch to explicit `typing.Optional` for type hints. (#513) +- For `trio` map OSError exceptions to `ConnectError`. (#543) + +## 0.14.7 (February 4th, 2022) + +- Requests which raise a PoolTimeout need to be removed from the pool queue. (#502) +- Fix AttributeError that happened when Socks5Connection were terminated. (#501) + +## 0.14.6 (February 1st, 2022) + +- Fix SOCKS support for `http://` URLs. (#492) +- Resolve race condition around exceptions during streaming a response. (#491) + +## 0.14.5 (January 18th, 2022) + +- SOCKS proxy support. (#478) +- Add proxy_auth argument to HTTPProxy. (#481) +- Improve error message on 'RemoteProtocolError' exception when server disconnects without sending a response. (#479) + +## 0.14.4 (January 5th, 2022) + +- Support HTTP/2 on HTTPS tunnelling proxies. (#468) +- Fix proxy headers missing on HTTP forwarding. (#456) +- Only instantiate SSL context if required. (#457) +- More robust HTTP/2 handling. (#253, #439, #440, #441) + +## 0.14.3 (November 17th, 2021) + +- Fix race condition when removing closed connections from the pool. (#437) + +## 0.14.2 (November 16th, 2021) + +- Failed connections no longer remain in the pool. (Pull #433) + +## 0.14.1 (November 12th, 2021) + +- `max_connections` becomes optional. (Pull #429) +- `certifi` is now included in the install dependancies. (Pull #428) +- `h2` is now strictly optional. (Pull #428) + +## 0.14.0 (November 11th, 2021) + +The 0.14 release is a complete reworking of `httpcore`, comprehensively addressing some underlying issues in the connection pooling, as well as substantially redesigning the API to be more user friendly. + +Some of the lower-level API design also makes the components more easily testable in isolation, and the package now has 100% test coverage. + +See [discussion #419](https://github.com/encode/httpcore/discussions/419) for a little more background. + +There's some other neat bits in there too, such as the "trace" extension, which gives a hook into inspecting the internal events that occur during the request/response cycle. This extension is needed for the HTTPX cli, in order to... + +* Log the point at which the connection is established, and the IP/port on which it is made. +* Determine if the outgoing request should log as HTTP/1.1 or HTTP/2, rather than having to assume it's HTTP/2 if the --http2 flag was passed. (Which may not actually be true.) +* Log SSL version info / certificate info. + +Note that `curio` support is not currently available in 0.14.0. If you're using `httpcore` with `curio` please get in touch, so we can assess if we ought to prioritize it as a feature or not. + +## 0.13.7 (September 13th, 2021) + +- Fix broken error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #403) + +## 0.13.6 (June 15th, 2021) + +### Fixed + +- Close sockets when read or write timeouts occur. (Pull #365) + +## 0.13.5 (June 14th, 2021) + +### Fixed + +- Resolved niggles with AnyIO EOF behaviours. (Pull #358, #362) + +## 0.13.4 (June 9th, 2021) + +### Added + +- Improved error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #354) + +### Fixed + +- Switched to `anyio` as the default backend implementation when running with `asyncio`. Resolves some awkward [TLS timeout issues](https://github.com/encode/httpx/discussions/1511). + +## 0.13.3 (May 6th, 2021) + +### Added + +- Support HTTP/2 prior knowledge, using `httpcore.SyncConnectionPool(http1=False)`. (Pull #333) + +### Fixed + +- Handle cases where environment does not provide `select.poll` support. (Pull #331) + +## 0.13.2 (April 29th, 2021) + +### Added + +- Improve error message for specific case of `RemoteProtocolError` where server disconnects without sending a response. (Pull #313) + +## 0.13.1 (April 28th, 2021) + +### Fixed + +- More resiliant testing for closed connections. (Pull #311) +- Don't raise exceptions on ungraceful connection closes. (Pull #310) + +## 0.13.0 (April 21st, 2021) + +The 0.13 release updates the core API in order to match the HTTPX Transport API, +introduced in HTTPX 0.18 onwards. + +An example of making requests with the new interface is: + +```python +with httpcore.SyncConnectionPool() as http: + status_code, headers, stream, extensions = http.handle_request( + method=b'GET', + url=(b'https', b'example.org', 443, b'/'), + headers=[(b'host', b'example.org'), (b'user-agent', b'httpcore')] + stream=httpcore.ByteStream(b''), + extensions={} + ) + body = stream.read() + print(status_code, body) +``` + +### Changed + +- The `.request()` method is now `handle_request()`. (Pull #296) +- The `.arequest()` method is now `.handle_async_request()`. (Pull #296) +- The `headers` argument is no longer optional. (Pull #296) +- The `stream` argument is no longer optional. (Pull #296) +- The `ext` argument is now named `extensions`, and is no longer optional. (Pull #296) +- The `"reason"` extension keyword is now named `"reason_phrase"`. (Pull #296) +- The `"reason_phrase"` and `"http_version"` extensions now use byte strings for their values. (Pull #296) +- The `httpcore.PlainByteStream()` class becomes `httpcore.ByteStream()`. (Pull #296) + +### Added + +- Streams now support a `.read()` interface. (Pull #296) + +### Fixed + +- Task cancellation no longer leaks connections from the connection pool. (Pull #305) + +## 0.12.3 (December 7th, 2020) + +### Fixed + +- Abort SSL connections on close rather than waiting for remote EOF when using `asyncio`. (Pull #167) +- Fix exception raised in case of connect timeouts when using the `anyio` backend. (Pull #236) +- Fix `Host` header precedence for `:authority` in HTTP/2. (Pull #241, #243) +- Handle extra edge case when detecting for socket readability when using `asyncio`. (Pull #242, #244) +- Fix `asyncio` SSL warning when using proxy tunneling. (Pull #249) + +## 0.12.2 (November 20th, 2020) + +### Fixed + +- Properly wrap connect errors on the asyncio backend. (Pull #235) +- Fix `ImportError` occurring on Python 3.9 when using the HTTP/1.1 sync client in a multithreaded context. (Pull #237) + +## 0.12.1 (November 7th, 2020) + +### Added + +- Add connect retries. (Pull #221) + +### Fixed + +- Tweak detection of dropped connections, resolving an issue with open files limits on Linux. (Pull #185) +- Avoid leaking connections when establishing an HTTP tunnel to a proxy has failed. (Pull #223) +- Properly wrap OS errors when using `trio`. (Pull #225) + +## 0.12.0 (October 6th, 2020) + +### Changed + +- HTTP header casing is now preserved, rather than always sent in lowercase. (#216 and python-hyper/h11#104) + +### Added + +- Add Python 3.9 to officially supported versions. + +### Fixed + +- Gracefully handle a stdlib asyncio bug when a connection is closed while it is in a paused-for-reading state. (#201) + +## 0.11.1 (September 28nd, 2020) + +### Fixed + +- Add await to async semaphore release() coroutine (#197) +- Drop incorrect curio classifier (#192) + +## 0.11.0 (September 22nd, 2020) + +The Transport API with 0.11.0 has a couple of significant changes. + +Firstly we've moved changed the request interface in order to allow extensions, which will later enable us to support features +such as trailing headers, HTTP/2 server push, and CONNECT/Upgrade connections. + +The interface changes from: + +```python +def request(method, url, headers, stream, timeout): + return (http_version, status_code, reason, headers, stream) +``` + +To instead including an optional dictionary of extensions on the request and response: + +```python +def request(method, url, headers, stream, ext): + return (status_code, headers, stream, ext) +``` + +Having an open-ended extensions point will allow us to add later support for various optional features, that wouldn't otherwise be supported without these API changes. + +In particular: + +* Trailing headers support. +* HTTP/2 Server Push +* sendfile. +* Exposing raw connection on CONNECT, Upgrade, HTTP/2 bi-di streaming. +* Exposing debug information out of the API, including template name, template context. + +Currently extensions are limited to: + +* request: `timeout` - Optional. Timeout dictionary. +* response: `http_version` - Optional. Include the HTTP version used on the response. +* response: `reason` - Optional. Include the reason phrase used on the response. Only valid with HTTP/1.*. + +See https://github.com/encode/httpx/issues/1274#issuecomment-694884553 for the history behind this. + +Secondly, the async version of `request` is now namespaced as `arequest`. + +This allows concrete transports to support both sync and async implementations on the same class. + +### Added + +- Add curio support. (Pull #168) +- Add anyio support, with `backend="anyio"`. (Pull #169) + +### Changed + +- Update the Transport API to use 'ext' for optional extensions. (Pull #190) +- Update the Transport API to use `.request` and `.arequest` so implementations can support both sync and async. (Pull #189) + +## 0.10.2 (August 20th, 2020) + +### Added + +- Added Unix Domain Socket support. (Pull #139) + +### Fixed + +- Always include the port on proxy CONNECT requests. (Pull #154) +- Fix `max_keepalive_connections` configuration. (Pull #153) +- Fixes behaviour in HTTP/1.1 where server disconnects can be used to signal the end of the response body. (Pull #164) + +## 0.10.1 (August 7th, 2020) + +- Include `max_keepalive_connections` on `AsyncHTTPProxy`/`SyncHTTPProxy` classes. + +## 0.10.0 (August 7th, 2020) + +The most notable change in the 0.10.0 release is that HTTP/2 support is now fully optional. + +Use either `pip install httpcore` for HTTP/1.1 support only, or `pip install httpcore[http2]` for HTTP/1.1 and HTTP/2 support. + +### Added + +- HTTP/2 support becomes optional. (Pull #121, #130) +- Add `local_address=...` support. (Pull #100, #134) +- Add `PlainByteStream`, `IteratorByteStream`, `AsyncIteratorByteStream`. The `AsyncByteSteam` and `SyncByteStream` classes are now pure interface classes. (#133) +- Add `LocalProtocolError`, `RemoteProtocolError` exceptions. (Pull #129) +- Add `UnsupportedProtocol` exception. (Pull #128) +- Add `.get_connection_info()` method. (Pull #102, #137) +- Add better TRACE logs. (Pull #101) + +### Changed + +- `max_keepalive` is deprecated in favour of `max_keepalive_connections`. (Pull #140) + +### Fixed + +- Improve handling of server disconnects. (Pull #112) + +## 0.9.1 (May 27th, 2020) + +### Fixed + +- Proper host resolution for sync case, including IPv6 support. (Pull #97) +- Close outstanding connections when connection pool is closed. (Pull #98) + +## 0.9.0 (May 21th, 2020) + +### Changed + +- URL port becomes an `Optional[int]` instead of `int`. (Pull #92) + +### Fixed + +- Honor HTTP/2 max concurrent streams settings. (Pull #89, #90) +- Remove incorrect debug log. (Pull #83) + +## 0.8.4 (May 11th, 2020) + +### Added + +- Logging via HTTPCORE_LOG_LEVEL and HTTPX_LOG_LEVEL environment variables +and TRACE level logging. (Pull #79) + +### Fixed + +- Reuse of connections on HTTP/2 in close concurrency situations. (Pull #81) + +## 0.8.3 (May 6rd, 2020) + +### Fixed + +- Include `Host` and `Accept` headers on proxy "CONNECT" requests. +- De-duplicate any headers also contained in proxy_headers. +- HTTP/2 flag not being passed down to proxy connections. + +## 0.8.2 (May 3rd, 2020) + +### Fixed + +- Fix connections using proxy forwarding requests not being added to the +connection pool properly. (Pull #70) + +## 0.8.1 (April 30th, 2020) + +### Changed + +- Allow inherintance of both `httpcore.AsyncByteStream`, `httpcore.SyncByteStream` without type conflicts. + +## 0.8.0 (April 30th, 2020) + +### Fixed + +- Fixed tunnel proxy support. + +### Added + +- New `TimeoutException` base class. + +## 0.7.0 (March 5th, 2020) + +- First integration with HTTPX. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD new file mode 100644 index 0000000..d9a731a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD @@ -0,0 +1,36 @@ +httpcore/__init__.py,sha256=9kT_kqChCCJUTHww24ZmR_ezcdbpRYWksD-gYNzkZP8,3445 +httpcore/_api.py,sha256=unZmeDschBWCGCPCwkS3Wot9euK6bg_kKxLtGTxw214,3146 +httpcore/_exceptions.py,sha256=looCKga3_YVYu3s-d3L9RMPRJyhsY7fiuuGxvkOD0c0,1184 +httpcore/_models.py,sha256=IO2CcXcdpovRcLTdGFGB6RyBZdEm2h_TOmoCc4rEKho,17623 +httpcore/_ssl.py,sha256=srqmSNU4iOUvWF-SrJvb8G_YEbHFELOXQOwdDIBTS9c,187 +httpcore/_synchronization.py,sha256=zSi13mAColBnknjZBknUC6hKNDQT4C6ijnezZ-r0T2s,9434 +httpcore/_trace.py,sha256=ck6ZoIzYTkdNAIfq5MGeKqBXDtqjOX-qfYwmZFbrGco,3952 +httpcore/_utils.py,sha256=_RLgXYOAYC350ikALV59GZ68IJrdocRZxPs9PjmzdFY,1537 +httpcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpcore/_async/__init__.py,sha256=EWdl2v4thnAHzJpqjU4h2a8DUiGAvNiWrkii9pfhTf0,1221 +httpcore/_async/connection.py,sha256=6OcPXqMEfc0BU38_-iHUNDd1vKSTc2UVT09XqNb_BOk,8449 +httpcore/_async/connection_pool.py,sha256=DOIQ2s2ZCf9qfwxhzMprTPLqCL8OxGXiKF6qRHxvVyY,17307 +httpcore/_async/http11.py,sha256=-qM9bV7PjSQF5vxs37-eUXOIFwbIjPcZbNliuX9TtBw,13880 +httpcore/_async/http2.py,sha256=azX1fcmtXaIwjputFlZ4vd92J8xwjGOa9ax9QIv4394,23936 +httpcore/_async/http_proxy.py,sha256=2zVkrlv-Ds-rWGaqaXlrhEJiAQFPo23BT3Gq_sWoBXU,14701 +httpcore/_async/interfaces.py,sha256=jTiaWL83pgpGC9ziv90ZfwaKNMmHwmOalzaKiuTxATo,4455 +httpcore/_async/socks_proxy.py,sha256=lLKgLlggPfhFlqi0ODeBkOWvt9CghBBUyqsnsU1tx6Q,13841 +httpcore/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpcore/_backends/anyio.py,sha256=x8PgEhXRC8bVqsdzk_YJx8Y6d9Tub06CuUSwnbmtqoY,5252 +httpcore/_backends/auto.py,sha256=zO136PKZmsaTDK-HRk84eA-MUg8_2wJf4NvmK432Aio,1662 +httpcore/_backends/base.py,sha256=aShgRdZnMmRhFWHetjumlM73f8Kz1YOAyCUP_4kHslA,3042 +httpcore/_backends/mock.py,sha256=er9T436uSe7NLrfiLa4x6Nuqg5ivQ693CxWYCWsgbH4,4077 +httpcore/_backends/sync.py,sha256=bhE4d9iK9Umxdsdsgm2EfKnXaBms2WggGYU-7jmUujU,7977 +httpcore/_backends/trio.py,sha256=LHu4_Mr5MswQmmT3yE4oLgf9b_JJfeVS4BjDxeJc7Ro,5996 +httpcore/_sync/__init__.py,sha256=JBDIgXt5la1LCJ1sLQeKhjKFpLnpNr8Svs6z2ni3fgg,1141 +httpcore/_sync/connection.py,sha256=9exGOb3PB-Mp2T1-sckSeL2t-tJ_9-NXomV8ihmWCgU,8238 +httpcore/_sync/connection_pool.py,sha256=a-T8LTsUxc7r0Ww1atfHSDoWPjQ0fA8Ul7S3-F0Mj70,16955 +httpcore/_sync/http11.py,sha256=IFobD1Md5JFlJGKWnh1_Q3epikUryI8qo09v8MiJIEA,13476 +httpcore/_sync/http2.py,sha256=AxU4yhcq68Bn5vqdJYtiXKYUj7nvhYbxz3v4rT4xnvA,23400 +httpcore/_sync/http_proxy.py,sha256=_al_6crKuEZu2wyvu493RZImJdBJnj5oGKNjLOJL2Zo,14463 +httpcore/_sync/interfaces.py,sha256=snXON42vUDHO5JBJvo8D4VWk2Wat44z2OXXHDrjbl94,4344 +httpcore/_sync/socks_proxy.py,sha256=zegZW9Snqj2_992DFJa8_CppOVBkVL4AgwduRkStakQ,13614 +httpcore-1.0.9.dist-info/METADATA,sha256=_i1P2mGZEol4d54M8n88BFxTGGP83Zh-rMdPOhjUHCE,21529 +httpcore-1.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +httpcore-1.0.9.dist-info/licenses/LICENSE.md,sha256=_ctZFUx0y6uhahEkL3dAvqnyPW_rVUeRfYxflKgDkqU,1518 +httpcore-1.0.9.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..311b2b5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2020, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py new file mode 100644 index 0000000..9a92dc4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py @@ -0,0 +1,141 @@ +from ._api import request, stream +from ._async import ( + AsyncConnectionInterface, + AsyncConnectionPool, + AsyncHTTP2Connection, + AsyncHTTP11Connection, + AsyncHTTPConnection, + AsyncHTTPProxy, + AsyncSOCKSProxy, +) +from ._backends.base import ( + SOCKET_OPTION, + AsyncNetworkBackend, + AsyncNetworkStream, + NetworkBackend, + NetworkStream, +) +from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream +from ._backends.sync import SyncBackend +from ._exceptions import ( + ConnectError, + ConnectionNotAvailable, + ConnectTimeout, + LocalProtocolError, + NetworkError, + PoolTimeout, + ProtocolError, + ProxyError, + ReadError, + ReadTimeout, + RemoteProtocolError, + TimeoutException, + UnsupportedProtocol, + WriteError, + WriteTimeout, +) +from ._models import URL, Origin, Proxy, Request, Response +from ._ssl import default_ssl_context +from ._sync import ( + ConnectionInterface, + ConnectionPool, + HTTP2Connection, + HTTP11Connection, + HTTPConnection, + HTTPProxy, + SOCKSProxy, +) + +# The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed. +try: + from ._backends.anyio import AnyIOBackend +except ImportError: # pragma: nocover + + class AnyIOBackend: # type: ignore + def __init__(self, *args, **kwargs): # type: ignore + msg = ( + "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed." + ) + raise RuntimeError(msg) + + +# The 'httpcore.TrioBackend' class is conditional on 'trio' being installed. +try: + from ._backends.trio import TrioBackend +except ImportError: # pragma: nocover + + class TrioBackend: # type: ignore + def __init__(self, *args, **kwargs): # type: ignore + msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed." + raise RuntimeError(msg) + + +__all__ = [ + # top-level requests + "request", + "stream", + # models + "Origin", + "URL", + "Request", + "Response", + "Proxy", + # async + "AsyncHTTPConnection", + "AsyncConnectionPool", + "AsyncHTTPProxy", + "AsyncHTTP11Connection", + "AsyncHTTP2Connection", + "AsyncConnectionInterface", + "AsyncSOCKSProxy", + # sync + "HTTPConnection", + "ConnectionPool", + "HTTPProxy", + "HTTP11Connection", + "HTTP2Connection", + "ConnectionInterface", + "SOCKSProxy", + # network backends, implementations + "SyncBackend", + "AnyIOBackend", + "TrioBackend", + # network backends, mock implementations + "AsyncMockBackend", + "AsyncMockStream", + "MockBackend", + "MockStream", + # network backends, interface + "AsyncNetworkStream", + "AsyncNetworkBackend", + "NetworkStream", + "NetworkBackend", + # util + "default_ssl_context", + "SOCKET_OPTION", + # exceptions + "ConnectionNotAvailable", + "ProxyError", + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "UnsupportedProtocol", + "TimeoutException", + "PoolTimeout", + "ConnectTimeout", + "ReadTimeout", + "WriteTimeout", + "NetworkError", + "ConnectError", + "ReadError", + "WriteError", +] + +__version__ = "1.0.9" + + +__locals = locals() +for __name in __all__: + # Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14 + if not __name.startswith(("__", "SOCKET_OPTION")): + setattr(__locals[__name], "__module__", "httpcore") # noqa diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py new file mode 100644 index 0000000..38b961d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_api.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import contextlib +import typing + +from ._models import URL, Extensions, HeaderTypes, Response +from ._sync.connection_pool import ConnectionPool + + +def request( + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, +) -> Response: + """ + Sends an HTTP request, returning the response. + + ``` + response = httpcore.request("GET", "https://www.example.com/") + ``` + + Arguments: + method: The HTTP method for the request. Typically one of `"GET"`, + `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`. + url: The URL of the HTTP request. Either as an instance of `httpcore.URL`, + or as str/bytes. + headers: The HTTP request headers. Either as a dictionary of str/bytes, + or as a list of two-tuples of str/bytes. + content: The content of the request body. Either as bytes, + or as a bytes iterator. + extensions: A dictionary of optional extra information included on the request. + Possible keys include `"timeout"`. + + Returns: + An instance of `httpcore.Response`. + """ + with ConnectionPool() as pool: + return pool.request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + + +@contextlib.contextmanager +def stream( + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, +) -> typing.Iterator[Response]: + """ + Sends an HTTP request, returning the response within a content manager. + + ``` + with httpcore.stream("GET", "https://www.example.com/") as response: + ... + ``` + + When using the `stream()` function, the body of the response will not be + automatically read. If you want to access the response body you should + either use `content = response.read()`, or `for chunk in response.iter_content()`. + + Arguments: + method: The HTTP method for the request. Typically one of `"GET"`, + `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`. + url: The URL of the HTTP request. Either as an instance of `httpcore.URL`, + or as str/bytes. + headers: The HTTP request headers. Either as a dictionary of str/bytes, + or as a list of two-tuples of str/bytes. + content: The content of the request body. Either as bytes, + or as a bytes iterator. + extensions: A dictionary of optional extra information included on the request. + Possible keys include `"timeout"`. + + Returns: + An instance of `httpcore.Response`. + """ + with ConnectionPool() as pool: + with pool.stream( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) as response: + yield response diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py new file mode 100644 index 0000000..88dc7f0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py @@ -0,0 +1,39 @@ +from .connection import AsyncHTTPConnection +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .http_proxy import AsyncHTTPProxy +from .interfaces import AsyncConnectionInterface + +try: + from .http2 import AsyncHTTP2Connection +except ImportError: # pragma: nocover + + class AsyncHTTP2Connection: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use http2 support, but the `h2` package is not " + "installed. Use 'pip install httpcore[http2]'." + ) + + +try: + from .socks_proxy import AsyncSOCKSProxy +except ImportError: # pragma: nocover + + class AsyncSOCKSProxy: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use SOCKS support, but the `socksio` package is not " + "installed. Use 'pip install httpcore[socks]'." + ) + + +__all__ = [ + "AsyncHTTPConnection", + "AsyncConnectionPool", + "AsyncHTTPProxy", + "AsyncHTTP11Connection", + "AsyncHTTP2Connection", + "AsyncConnectionInterface", + "AsyncSOCKSProxy", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py new file mode 100644 index 0000000..b42581d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import itertools +import logging +import ssl +import types +import typing + +from .._backends.auto import AutoBackend +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream +from .._exceptions import ConnectError, ConnectTimeout +from .._models import Origin, Request, Response +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. + + +logger = logging.getLogger("httpcore.connection") + + +def exponential_backoff(factor: float) -> typing.Iterator[float]: + """ + Generate a geometric sequence that has a ratio of 2 and starts with 0. + + For example: + - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...` + - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...` + """ + yield 0 + for n in itertools.count(): + yield factor * 2**n + + +class AsyncHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + origin: Origin, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._origin = origin + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend: AsyncNetworkBackend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._connection: AsyncConnectionInterface | None = None + self._connect_failed: bool = False + self._request_lock = AsyncLock() + self._socket_options = socket_options + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection to {self._origin}" + ) + + try: + async with self._request_lock: + if self._connection is None: + stream = await self._connect(request) + + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except BaseException as exc: + self._connect_failed = True + raise exc + + return await self._connection.handle_async_request(request) + + async def _connect(self, request: Request) -> AsyncNetworkStream: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + retries_left = self._retries + delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR) + + while True: + try: + if self._uds is None: + kwargs = { + "host": self._origin.host.decode("ascii"), + "port": self._origin.port, + "local_address": self._local_address, + "timeout": timeout, + "socket_options": self._socket_options, + } + async with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = await self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + else: + kwargs = { + "path": self._uds, + "timeout": timeout, + "socket_options": self._socket_options, + } + async with Trace( + "connect_unix_socket", logger, request, kwargs + ) as trace: + stream = await self._network_backend.connect_unix_socket( + **kwargs + ) + trace.return_value = stream + + if self._origin.scheme in (b"https", b"wss"): + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + return stream + except (ConnectError, ConnectTimeout): + if retries_left <= 0: + raise + retries_left -= 1 + delay = next(delays) + async with Trace("retry", logger, request, kwargs) as trace: + await self._network_backend.sleep(delay) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + async def aclose(self) -> None: + if self._connection is not None: + async with Trace("close", logger, None, {}): + await self._connection.aclose() + + def is_available(self) -> bool: + if self._connection is None: + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTPConnection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py new file mode 100644 index 0000000..96e973d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py @@ -0,0 +1,420 @@ +from __future__ import annotations + +import ssl +import sys +import types +import typing + +from .._backends.auto import AutoBackend +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend +from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol +from .._models import Origin, Proxy, Request, Response +from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock +from .connection import AsyncHTTPConnection +from .interfaces import AsyncConnectionInterface, AsyncRequestInterface + + +class AsyncPoolRequest: + def __init__(self, request: Request) -> None: + self.request = request + self.connection: AsyncConnectionInterface | None = None + self._connection_acquired = AsyncEvent() + + def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None: + self.connection = connection + self._connection_acquired.set() + + def clear_connection(self) -> None: + self.connection = None + self._connection_acquired = AsyncEvent() + + async def wait_for_connection( + self, timeout: float | None = None + ) -> AsyncConnectionInterface: + if self.connection is None: + await self._connection_acquired.wait(timeout=timeout) + assert self.connection is not None + return self.connection + + def is_queued(self) -> bool: + return self.connection is None + + +class AsyncConnectionPool(AsyncRequestInterface): + """ + A connection pool for making HTTP requests. + """ + + def __init__( + self, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish a + connection. + local_address: Local address to connect from. Can also be used to connect + using a particular address family. Using `local_address="0.0.0.0"` + will connect using an `AF_INET` address (IPv4), while using + `local_address="::"` will connect using an `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + socket_options: Socket options that have to be included + in the TCP socket when the connection was established. + """ + self._ssl_context = ssl_context + self._proxy = proxy + self._max_connections = ( + sys.maxsize if max_connections is None else max_connections + ) + self._max_keepalive_connections = ( + sys.maxsize + if max_keepalive_connections is None + else max_keepalive_connections + ) + self._max_keepalive_connections = min( + self._max_connections, self._max_keepalive_connections + ) + + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._socket_options = socket_options + + # The mutable state on a connection pool is the queue of incoming requests, + # and the set of connections that are servicing those requests. + self._connections: list[AsyncConnectionInterface] = [] + self._requests: list[AsyncPoolRequest] = [] + + # We only mutate the state of the connection pool within an 'optional_thread_lock' + # context. This holds a threading lock unless we're running in async mode, + # in which case it is a no-op. + self._optional_thread_lock = AsyncThreadLock() + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import AsyncSocks5Connection + + return AsyncSocks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import AsyncForwardHTTPConnection + + return AsyncForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import AsyncTunnelHTTPConnection + + return AsyncTunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + return AsyncHTTPConnection( + origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + retries=self._retries, + local_address=self._local_address, + uds=self._uds, + network_backend=self._network_backend, + socket_options=self._socket_options, + ) + + @property + def connections(self) -> list[AsyncConnectionInterface]: + """ + Return a list of the connections currently in the pool. + + For example: + + ```python + >>> pool.connections + [ + , + , + , + ] + ``` + """ + return list(self._connections) + + async def handle_async_request(self, request: Request) -> Response: + """ + Send an HTTP request, and return an HTTP response. + + This is the core implementation that is called into by `.request()` or `.stream()`. + """ + scheme = request.url.scheme.decode() + if scheme == "": + raise UnsupportedProtocol( + "Request URL is missing an 'http://' or 'https://' protocol." + ) + if scheme not in ("http", "https", "ws", "wss"): + raise UnsupportedProtocol( + f"Request URL has an unsupported protocol '{scheme}://'." + ) + + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("pool", None) + + with self._optional_thread_lock: + # Add the incoming request to our request queue. + pool_request = AsyncPoolRequest(request) + self._requests.append(pool_request) + + try: + while True: + with self._optional_thread_lock: + # Assign incoming requests to available connections, + # closing or creating new connections as required. + closing = self._assign_requests_to_connections() + await self._close_connections(closing) + + # Wait until this request has an assigned connection. + connection = await pool_request.wait_for_connection(timeout=timeout) + + try: + # Send the request on the assigned connection. + response = await connection.handle_async_request( + pool_request.request + ) + except ConnectionNotAvailable: + # In some cases a connection may initially be available to + # handle a request, but then become unavailable. + # + # In this case we clear the connection and try again. + pool_request.clear_connection() + else: + break # pragma: nocover + + except BaseException as exc: + with self._optional_thread_lock: + # For any exception or cancellation we remove the request from + # the queue, and then re-assign requests to connections. + self._requests.remove(pool_request) + closing = self._assign_requests_to_connections() + + await self._close_connections(closing) + raise exc from None + + # Return the response. Note that in this case we still have to manage + # the point at which the response is closed. + assert isinstance(response.stream, typing.AsyncIterable) + return Response( + status=response.status, + headers=response.headers, + content=PoolByteStream( + stream=response.stream, pool_request=pool_request, pool=self + ), + extensions=response.extensions, + ) + + def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]: + """ + Manage the state of the connection pool, assigning incoming + requests to connections as available. + + Called whenever a new request is added or removed from the pool. + + Any closing connections are returned, allowing the I/O for closing + those connections to be handled seperately. + """ + closing_connections = [] + + # First we handle cleaning up any connections that are closed, + # have expired their keep-alive, or surplus idle connections. + for connection in list(self._connections): + if connection.is_closed(): + # log: "removing closed connection" + self._connections.remove(connection) + elif connection.has_expired(): + # log: "closing expired connection" + self._connections.remove(connection) + closing_connections.append(connection) + elif ( + connection.is_idle() + and len([connection.is_idle() for connection in self._connections]) + > self._max_keepalive_connections + ): + # log: "closing idle connection" + self._connections.remove(connection) + closing_connections.append(connection) + + # Assign queued requests to connections. + queued_requests = [request for request in self._requests if request.is_queued()] + for pool_request in queued_requests: + origin = pool_request.request.url.origin + available_connections = [ + connection + for connection in self._connections + if connection.can_handle_request(origin) and connection.is_available() + ] + idle_connections = [ + connection for connection in self._connections if connection.is_idle() + ] + + # There are three cases for how we may be able to handle the request: + # + # 1. There is an existing connection that can handle the request. + # 2. We can create a new connection to handle the request. + # 3. We can close an idle connection and then create a new connection + # to handle the request. + if available_connections: + # log: "reusing existing connection" + connection = available_connections[0] + pool_request.assign_to_connection(connection) + elif len(self._connections) < self._max_connections: + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + elif idle_connections: + # log: "closing idle connection" + connection = idle_connections[0] + self._connections.remove(connection) + closing_connections.append(connection) + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + + return closing_connections + + async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None: + # Close connections which have been removed from the pool. + with AsyncShieldCancellation(): + for connection in closing: + await connection.aclose() + + async def aclose(self) -> None: + # Explicitly close the connection pool. + # Clears all existing requests and connections. + with self._optional_thread_lock: + closing_connections = list(self._connections) + self._connections = [] + await self._close_connections(closing_connections) + + async def __aenter__(self) -> AsyncConnectionPool: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + with self._optional_thread_lock: + request_is_queued = [request.is_queued() for request in self._requests] + connection_is_idle = [ + connection.is_idle() for connection in self._connections + ] + + num_active_requests = request_is_queued.count(False) + num_queued_requests = request_is_queued.count(True) + num_active_connections = connection_is_idle.count(False) + num_idle_connections = connection_is_idle.count(True) + + requests_info = ( + f"Requests: {num_active_requests} active, {num_queued_requests} queued" + ) + connection_info = ( + f"Connections: {num_active_connections} active, {num_idle_connections} idle" + ) + + return f"<{class_name} [{requests_info} | {connection_info}]>" + + +class PoolByteStream: + def __init__( + self, + stream: typing.AsyncIterable[bytes], + pool_request: AsyncPoolRequest, + pool: AsyncConnectionPool, + ) -> None: + self._stream = stream + self._pool_request = pool_request + self._pool = pool + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + try: + async for part in self._stream: + yield part + except BaseException as exc: + await self.aclose() + raise exc from None + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + with AsyncShieldCancellation(): + if hasattr(self._stream, "aclose"): + await self._stream.aclose() + + with self._pool._optional_thread_lock: + self._pool._requests.remove(self._pool_request) + closing = self._pool._assign_requests_to_connections() + + await self._pool._close_connections(closing) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py new file mode 100644 index 0000000..e6d6d70 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py @@ -0,0 +1,379 @@ +from __future__ import annotations + +import enum +import logging +import ssl +import time +import types +import typing + +import h11 + +from .._backends.base import AsyncNetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, + WriteError, + map_exceptions, +) +from .._models import Origin, Request, Response +from .._synchronization import AsyncLock, AsyncShieldCancellation +from .._trace import Trace +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.http11") + + +# A subset of `h11.Event` types supported by `_send_event` +H11SendEvent = typing.Union[ + h11.Request, + h11.Data, + h11.EndOfMessage, +] + + +class HTTPConnectionState(enum.IntEnum): + NEW = 0 + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class AsyncHTTP11Connection(AsyncConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024 + + def __init__( + self, + origin: Origin, + stream: AsyncNetworkStream, + keepalive_expiry: float | None = None, + ) -> None: + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None + self._state = HTTPConnectionState.NEW + self._state_lock = AsyncLock() + self._request_count = 0 + self._h11_state = h11.Connection( + our_role=h11.CLIENT, + max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE, + ) + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + async with self._state_lock: + if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): + self._request_count += 1 + self._state = HTTPConnectionState.ACTIVE + self._expire_at = None + else: + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request} + try: + async with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: + await self._send_request_headers(**kwargs) + async with Trace("send_request_body", logger, request, kwargs) as trace: + await self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + async with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + ( + http_version, + status, + reason_phrase, + headers, + trailing_data, + ) = await self._receive_response_headers(**kwargs) + trace.return_value = ( + http_version, + status, + reason_phrase, + headers, + ) + + network_stream = self._network_stream + + # CONNECT or Upgrade request + if (status == 101) or ( + (request.method == b"CONNECT") and (200 <= status < 300) + ): + network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data) + + return Response( + status=status, + headers=headers, + content=HTTP11ConnectionByteStream(self, request), + extensions={ + "http_version": http_version, + "reason_phrase": reason_phrase, + "network_stream": network_stream, + }, + ) + except BaseException as exc: + with AsyncShieldCancellation(): + async with Trace("response_closed", logger, request) as trace: + await self._response_closed() + raise exc + + # Sending the request... + + async def _send_request_headers(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with map_exceptions({h11.LocalProtocolError: LocalProtocolError}): + event = h11.Request( + method=request.method, + target=request.url.target, + headers=request.headers, + ) + await self._send_event(event, timeout=timeout) + + async def _send_request_body(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + assert isinstance(request.stream, typing.AsyncIterable) + async for chunk in request.stream: + event = h11.Data(data=chunk) + await self._send_event(event, timeout=timeout) + + await self._send_event(h11.EndOfMessage(), timeout=timeout) + + async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: + bytes_to_send = self._h11_state.send(event) + if bytes_to_send is not None: + await self._network_stream.write(bytes_to_send, timeout=timeout) + + # Receiving the response... + + async def _receive_response_headers( + self, request: Request + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = await self._receive_event(timeout=timeout) + if isinstance(event, h11.Response): + break + if ( + isinstance(event, h11.InformationalResponse) + and event.status_code == 101 + ): + break + + http_version = b"HTTP/" + event.http_version + + # h11 version 0.11+ supports a `raw_items` interface to get the + # raw header casing, rather than the enforced lowercase headers. + headers = event.headers.raw_items() + + trailing_data, _ = self._h11_state.trailing_data + + return http_version, event.status_code, event.reason, headers, trailing_data + + async def _receive_response_body( + self, request: Request + ) -> typing.AsyncIterator[bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = await self._receive_event(timeout=timeout) + if isinstance(event, h11.Data): + yield bytes(event.data) + elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): + break + + async def _receive_event( + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: + while True: + with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): + event = self._h11_state.next_event() + + if event is h11.NEED_DATA: + data = await self._network_stream.read( + self.READ_NUM_BYTES, timeout=timeout + ) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE: + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) + + self._h11_state.receive_data(data) + else: + # mypy fails to narrow the type in the above if statement above + return event # type: ignore[return-value] + + async def _response_closed(self) -> None: + async with self._state_lock: + if ( + self._h11_state.our_state is h11.DONE + and self._h11_state.their_state is h11.DONE + ): + self._state = HTTPConnectionState.IDLE + self._h11_state.start_next_cycle() + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + else: + await self.aclose() + + # Once the connection is no longer required... + + async def aclose(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._state = HTTPConnectionState.CLOSED + await self._network_stream.aclose() + + # The AsyncConnectionInterface methods provide information about the state of + # the connection, allowing for a connection pooling implementation to + # determine when to reuse and when to close the connection... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + # Note that HTTP/1.1 connections in the "NEW" state are not treated as + # being "available". The control flow which created the connection will + # be able to send an outgoing request, but the connection will not be + # acquired from the connection pool for any other request. + return self._state == HTTPConnectionState.IDLE + + def has_expired(self) -> bool: + now = time.monotonic() + keepalive_expired = self._expire_at is not None and now > self._expire_at + + # If the HTTP connection is idle but the socket is readable, then the + # only valid state is that the socket is about to return b"", indicating + # a server-initiated disconnect. + server_disconnected = ( + self._state == HTTPConnectionState.IDLE + and self._network_stream.get_extra_info("is_readable") + ) + + return keepalive_expired or server_disconnected + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/1.1, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTP11Connection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + +class HTTP11ConnectionByteStream: + def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None: + self._connection = connection + self._request = request + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + kwargs = {"request": self._request} + try: + async with Trace("receive_response_body", logger, self._request, kwargs): + async for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + async with Trace("response_closed", logger, self._request): + await self._connection._response_closed() + + +class AsyncHTTP11UpgradeStream(AsyncNetworkStream): + def __init__(self, stream: AsyncNetworkStream, leading_data: bytes) -> None: + self._stream = stream + self._leading_data = leading_data + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._leading_data: + buffer = self._leading_data[:max_bytes] + self._leading_data = self._leading_data[max_bytes:] + return buffer + else: + return await self._stream.read(max_bytes, timeout) + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + await self._stream.write(buffer, timeout) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + return await self._stream.start_tls(ssl_context, server_hostname, timeout) + + def get_extra_info(self, info: str) -> typing.Any: + return self._stream.get_extra_info(info) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py new file mode 100644 index 0000000..dbd0bee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py @@ -0,0 +1,592 @@ +from __future__ import annotations + +import enum +import logging +import time +import types +import typing + +import h2.config +import h2.connection +import h2.events +import h2.exceptions +import h2.settings + +from .._backends.base import AsyncNetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, +) +from .._models import Origin, Request, Response +from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation +from .._trace import Trace +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.http2") + + +def has_body_headers(request: Request) -> bool: + return any( + k.lower() == b"content-length" or k.lower() == b"transfer-encoding" + for k, v in request.headers + ) + + +class HTTPConnectionState(enum.IntEnum): + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class AsyncHTTP2Connection(AsyncConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + CONFIG = h2.config.H2Configuration(validate_inbound_headers=False) + + def __init__( + self, + origin: Origin, + stream: AsyncNetworkStream, + keepalive_expiry: float | None = None, + ): + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._h2_state = h2.connection.H2Connection(config=self.CONFIG) + self._state = HTTPConnectionState.IDLE + self._expire_at: float | None = None + self._request_count = 0 + self._init_lock = AsyncLock() + self._state_lock = AsyncLock() + self._read_lock = AsyncLock() + self._write_lock = AsyncLock() + self._sent_connection_init = False + self._used_all_stream_ids = False + self._connection_error = False + + # Mapping from stream ID to response stream events. + self._events: dict[ + int, + list[ + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, + ], + ] = {} + + # Connection terminated events are stored as state since + # we need to handle them for all streams. + self._connection_terminated: h2.events.ConnectionTerminated | None = None + + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + # This cannot occur in normal operation, since the connection pool + # will only send requests on connections that handle them. + # It's in place simply for resilience as a guard against incorrect + # usage, for anyone working directly with httpcore connections. + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + async with self._state_lock: + if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): + self._request_count += 1 + self._expire_at = None + self._state = HTTPConnectionState.ACTIVE + else: + raise ConnectionNotAvailable() + + async with self._init_lock: + if not self._sent_connection_init: + try: + sci_kwargs = {"request": request} + async with Trace( + "send_connection_init", logger, request, sci_kwargs + ): + await self._send_connection_init(**sci_kwargs) + except BaseException as exc: + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + self._sent_connection_init = True + + # Initially start with just 1 until the remote server provides + # its max_concurrent_streams value + self._max_streams = 1 + + local_settings_max_streams = ( + self._h2_state.local_settings.max_concurrent_streams + ) + self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams) + + for _ in range(local_settings_max_streams - self._max_streams): + await self._max_streams_semaphore.acquire() + + await self._max_streams_semaphore.acquire() + + try: + stream_id = self._h2_state.get_next_available_stream_id() + self._events[stream_id] = [] + except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover + self._used_all_stream_ids = True + self._request_count -= 1 + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request, "stream_id": stream_id} + async with Trace("send_request_headers", logger, request, kwargs): + await self._send_request_headers(request=request, stream_id=stream_id) + async with Trace("send_request_body", logger, request, kwargs): + await self._send_request_body(request=request, stream_id=stream_id) + async with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + status, headers = await self._receive_response( + request=request, stream_id=stream_id + ) + trace.return_value = (status, headers) + + return Response( + status=status, + headers=headers, + content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id), + extensions={ + "http_version": b"HTTP/2", + "network_stream": self._network_stream, + "stream_id": stream_id, + }, + ) + except BaseException as exc: # noqa: PIE786 + with AsyncShieldCancellation(): + kwargs = {"stream_id": stream_id} + async with Trace("response_closed", logger, request, kwargs): + await self._response_closed(stream_id=stream_id) + + if isinstance(exc, h2.exceptions.ProtocolError): + # One case where h2 can raise a protocol error is when a + # closed frame has been seen by the state machine. + # + # This happens when one stream is reading, and encounters + # a GOAWAY event. Other flows of control may then raise + # a protocol error at any point they interact with the 'h2_state'. + # + # In this case we'll have stored the event, and should raise + # it as a RemoteProtocolError. + if self._connection_terminated: # pragma: nocover + raise RemoteProtocolError(self._connection_terminated) + # If h2 raises a protocol error in some other state then we + # must somehow have made a protocol violation. + raise LocalProtocolError(exc) # pragma: nocover + + raise exc + + async def _send_connection_init(self, request: Request) -> None: + """ + The HTTP/2 connection requires some initial setup before we can start + using individual request/response streams on it. + """ + # Need to set these manually here instead of manipulating via + # __setitem__() otherwise the H2Connection will emit SettingsUpdate + # frames in addition to sending the undesired defaults. + self._h2_state.local_settings = h2.settings.Settings( + client=True, + initial_values={ + # Disable PUSH_PROMISE frames from the server since we don't do anything + # with them for now. Maybe when we support caching? + h2.settings.SettingCodes.ENABLE_PUSH: 0, + # These two are taken from h2 for safe defaults + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536, + }, + ) + + # Some websites (*cough* Yahoo *cough*) balk at this setting being + # present in the initial handshake since it's not defined in the original + # RFC despite the RFC mandating ignoring settings you don't know about. + del self._h2_state.local_settings[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] + + self._h2_state.initiate_connection() + self._h2_state.increment_flow_control_window(2**24) + await self._write_outgoing_data(request) + + # Sending the request... + + async def _send_request_headers(self, request: Request, stream_id: int) -> None: + """ + Send the request headers to a given stream ID. + """ + end_stream = not has_body_headers(request) + + # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'. + # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require + # HTTP/1.1 style headers, and map them appropriately if we end up on + # an HTTP/2 connection. + authority = [v for k, v in request.headers if k.lower() == b"host"][0] + + headers = [ + (b":method", request.method), + (b":authority", authority), + (b":scheme", request.url.scheme), + (b":path", request.url.target), + ] + [ + (k.lower(), v) + for k, v in request.headers + if k.lower() + not in ( + b"host", + b"transfer-encoding", + ) + ] + + self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) + await self._write_outgoing_data(request) + + async def _send_request_body(self, request: Request, stream_id: int) -> None: + """ + Iterate over the request body sending it to a given stream ID. + """ + if not has_body_headers(request): + return + + assert isinstance(request.stream, typing.AsyncIterable) + async for data in request.stream: + await self._send_stream_data(request, stream_id, data) + await self._send_end_stream(request, stream_id) + + async def _send_stream_data( + self, request: Request, stream_id: int, data: bytes + ) -> None: + """ + Send a single chunk of data in one or more data frames. + """ + while data: + max_flow = await self._wait_for_outgoing_flow(request, stream_id) + chunk_size = min(len(data), max_flow) + chunk, data = data[:chunk_size], data[chunk_size:] + self._h2_state.send_data(stream_id, chunk) + await self._write_outgoing_data(request) + + async def _send_end_stream(self, request: Request, stream_id: int) -> None: + """ + Send an empty data frame on on a given stream ID with the END_STREAM flag set. + """ + self._h2_state.end_stream(stream_id) + await self._write_outgoing_data(request) + + # Receiving the response... + + async def _receive_response( + self, request: Request, stream_id: int + ) -> tuple[int, list[tuple[bytes, bytes]]]: + """ + Return the response status code and headers for a given stream ID. + """ + while True: + event = await self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.ResponseReceived): + break + + status_code = 200 + headers = [] + assert event.headers is not None + for k, v in event.headers: + if k == b":status": + status_code = int(v.decode("ascii", errors="ignore")) + elif not k.startswith(b":"): + headers.append((k, v)) + + return (status_code, headers) + + async def _receive_response_body( + self, request: Request, stream_id: int + ) -> typing.AsyncIterator[bytes]: + """ + Iterator that returns the bytes of the response body for a given stream ID. + """ + while True: + event = await self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.DataReceived): + assert event.flow_controlled_length is not None + assert event.data is not None + amount = event.flow_controlled_length + self._h2_state.acknowledge_received_data(amount, stream_id) + await self._write_outgoing_data(request) + yield event.data + elif isinstance(event, h2.events.StreamEnded): + break + + async def _receive_stream_event( + self, request: Request, stream_id: int + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: + """ + Return the next available event for a given stream ID. + + Will read more data from the network if required. + """ + while not self._events.get(stream_id): + await self._receive_events(request, stream_id) + event = self._events[stream_id].pop(0) + if isinstance(event, h2.events.StreamReset): + raise RemoteProtocolError(event) + return event + + async def _receive_events( + self, request: Request, stream_id: int | None = None + ) -> None: + """ + Read some data from the network until we see one or more events + for a given stream ID. + """ + async with self._read_lock: + if self._connection_terminated is not None: + last_stream_id = self._connection_terminated.last_stream_id + if stream_id and last_stream_id and stream_id > last_stream_id: + self._request_count -= 1 + raise ConnectionNotAvailable() + raise RemoteProtocolError(self._connection_terminated) + + # This conditional is a bit icky. We don't want to block reading if we've + # actually got an event to return for a given stream. We need to do that + # check *within* the atomic read lock. Though it also need to be optional, + # because when we call it from `_wait_for_outgoing_flow` we *do* want to + # block until we've available flow control, event when we have events + # pending for the stream ID we're attempting to send on. + if stream_id is None or not self._events.get(stream_id): + events = await self._read_incoming_data(request) + for event in events: + if isinstance(event, h2.events.RemoteSettingsChanged): + async with Trace( + "receive_remote_settings", logger, request + ) as trace: + await self._receive_remote_settings_change(event) + trace.return_value = event + + elif isinstance( + event, + ( + h2.events.ResponseReceived, + h2.events.DataReceived, + h2.events.StreamEnded, + h2.events.StreamReset, + ), + ): + if event.stream_id in self._events: + self._events[event.stream_id].append(event) + + elif isinstance(event, h2.events.ConnectionTerminated): + self._connection_terminated = event + + await self._write_outgoing_data(request) + + async def _receive_remote_settings_change( + self, event: h2.events.RemoteSettingsChanged + ) -> None: + max_concurrent_streams = event.changed_settings.get( + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ) + if max_concurrent_streams: + new_max_streams = min( + max_concurrent_streams.new_value, + self._h2_state.local_settings.max_concurrent_streams, + ) + if new_max_streams and new_max_streams != self._max_streams: + while new_max_streams > self._max_streams: + await self._max_streams_semaphore.release() + self._max_streams += 1 + while new_max_streams < self._max_streams: + await self._max_streams_semaphore.acquire() + self._max_streams -= 1 + + async def _response_closed(self, stream_id: int) -> None: + await self._max_streams_semaphore.release() + del self._events[stream_id] + async with self._state_lock: + if self._connection_terminated and not self._events: + await self.aclose() + + elif self._state == HTTPConnectionState.ACTIVE and not self._events: + self._state = HTTPConnectionState.IDLE + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + if self._used_all_stream_ids: # pragma: nocover + await self.aclose() + + async def aclose(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._h2_state.close_connection() + self._state = HTTPConnectionState.CLOSED + await self._network_stream.aclose() + + # Wrappers around network read/write operations... + + async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + if self._read_exception is not None: + raise self._read_exception # pragma: nocover + + try: + data = await self._network_stream.read(self.READ_NUM_BYTES, timeout) + if data == b"": + raise RemoteProtocolError("Server disconnected") + except Exception as exc: + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future reads. + # (For example, this means that a single read timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._read_exception = exc + self._connection_error = True + raise exc + + events: list[h2.events.Event] = self._h2_state.receive_data(data) + + return events + + async def _write_outgoing_data(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + async with self._write_lock: + data_to_send = self._h2_state.data_to_send() + + if self._write_exception is not None: + raise self._write_exception # pragma: nocover + + try: + await self._network_stream.write(data_to_send, timeout) + except Exception as exc: # pragma: nocover + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future write. + # (For example, this means that a single write timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._write_exception = exc + self._connection_error = True + raise exc + + # Flow control... + + async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int: + """ + Returns the maximum allowable outgoing flow for a given stream. + + If the allowable flow is zero, then waits on the network until + WindowUpdated frames have increased the flow rate. + https://tools.ietf.org/html/rfc7540#section-6.9 + """ + local_flow: int = self._h2_state.local_flow_control_window(stream_id) + max_frame_size: int = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + while flow == 0: + await self._receive_events(request) + local_flow = self._h2_state.local_flow_control_window(stream_id) + max_frame_size = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + return flow + + # Interface for connection pooling... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + return ( + self._state != HTTPConnectionState.CLOSED + and not self._connection_error + and not self._used_all_stream_ids + and not ( + self._h2_state.state_machine.state + == h2.connection.ConnectionState.CLOSED + ) + ) + + def has_expired(self) -> bool: + now = time.monotonic() + return self._expire_at is not None and now > self._expire_at + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/2, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTP2Connection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + +class HTTP2ConnectionByteStream: + def __init__( + self, connection: AsyncHTTP2Connection, request: Request, stream_id: int + ) -> None: + self._connection = connection + self._request = request + self._stream_id = stream_id + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + kwargs = {"request": self._request, "stream_id": self._stream_id} + try: + async with Trace("receive_response_body", logger, self._request, kwargs): + async for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + async with Trace("response_closed", logger, self._request, kwargs): + await self._connection._response_closed(stream_id=self._stream_id) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py new file mode 100644 index 0000000..cc9d920 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import base64 +import logging +import ssl +import typing + +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend +from .._exceptions import ProxyError +from .._models import ( + URL, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, +) +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .connection import AsyncHTTPConnection +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] + + +logger = logging.getLogger("httpcore.proxy") + + +def merge_headers( + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: + """ + Append default_headers and override_headers, de-duplicating if a key exists + in both cases. + """ + default_headers = [] if default_headers is None else list(default_headers) + override_headers = [] if override_headers is None else list(override_headers) + has_override = set(key.lower() for key, value in override_headers) + default_headers = [ + (key, value) + for key, value in default_headers + if key.lower() not in has_override + ] + return default_headers + override_headers + + +class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + proxy_auth: Any proxy authentication as a two-tuple of + (username, password). May be either bytes or ascii-only str. + proxy_headers: Any HTTP headers to use for the proxy requests. + For example `{"Proxy-Authorization": "Basic :"}`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + local_address=local_address, + uds=uds, + socket_options=socket_options, + ) + + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if ( + self._proxy_url.scheme == b"http" and proxy_ssl_context is not None + ): # pragma: no cover + raise RuntimeError( + "The `proxy_ssl_context` argument is not allowed for the http scheme" + ) + + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + if proxy_auth is not None: + username = enforce_bytes(proxy_auth[0], name="proxy_auth") + password = enforce_bytes(proxy_auth[1], name="proxy_auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self._proxy_headers = [ + (b"Proxy-Authorization", authorization) + ] + self._proxy_headers + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + if origin.scheme == b"http": + return AsyncForwardHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + proxy_ssl_context=self._proxy_ssl_context, + ) + return AsyncTunnelHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + ssl_context=self._ssl_context, + proxy_ssl_context=self._proxy_ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class AsyncForwardHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + ) -> None: + self._connection = AsyncHTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._remote_origin = remote_origin + + async def handle_async_request(self, request: Request) -> Response: + headers = merge_headers(self._proxy_headers, request.headers) + url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=bytes(request.url), + ) + proxy_request = Request( + method=request.method, + url=url, + headers=headers, + content=request.stream, + extensions=request.extensions, + ) + return await self._connection.handle_async_request(proxy_request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + await self._connection.aclose() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + +class AsyncTunnelHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._connection: AsyncConnectionInterface = AsyncHTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._connect_lock = AsyncLock() + self._connected = False + + async def handle_async_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("connect", None) + + async with self._connect_lock: + if not self._connected: + target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port) + + connect_url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=target, + ) + connect_headers = merge_headers( + [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers + ) + connect_request = Request( + method=b"CONNECT", + url=connect_url, + headers=connect_headers, + extensions=request.extensions, + ) + connect_response = await self._connection.handle_async_request( + connect_request + ) + + if connect_response.status < 200 or connect_response.status > 299: + reason_bytes = connect_response.extensions.get("reason_phrase", b"") + reason_str = reason_bytes.decode("ascii", errors="ignore") + msg = "%d %s" % (connect_response.status, reason_str) + await self._connection.aclose() + raise ProxyError(msg) + + stream = connect_response.extensions["network_stream"] + + # Upgrade the stream to SSL + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + + self._connected = True + return await self._connection.handle_async_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + await self._connection.aclose() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py new file mode 100644 index 0000000..361583b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import contextlib +import typing + +from .._models import ( + URL, + Extensions, + HeaderTypes, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, + include_request_headers, +) + + +class AsyncRequestInterface: + async def request( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> Response: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = await self.handle_async_request(request) + try: + await response.aread() + finally: + await response.aclose() + return response + + @contextlib.asynccontextmanager + async def stream( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.AsyncIterator[Response]: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = await self.handle_async_request(request) + try: + yield response + finally: + await response.aclose() + + async def handle_async_request(self, request: Request) -> Response: + raise NotImplementedError() # pragma: nocover + + +class AsyncConnectionInterface(AsyncRequestInterface): + async def aclose(self) -> None: + raise NotImplementedError() # pragma: nocover + + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def can_handle_request(self, origin: Origin) -> bool: + raise NotImplementedError() # pragma: nocover + + def is_available(self) -> bool: + """ + Return `True` if the connection is currently able to accept an + outgoing request. + + An HTTP/1.1 connection will only be available if it is currently idle. + + An HTTP/2 connection will be available so long as the stream ID space is + not yet exhausted, and the connection is not in an error state. + + While the connection is being established we may not yet know if it is going + to result in an HTTP/1.1 or HTTP/2 connection. The connection should be + treated as being available, but might ultimately raise `NewConnectionRequired` + required exceptions if multiple requests are attempted over a connection + that ends up being established as HTTP/1.1. + """ + raise NotImplementedError() # pragma: nocover + + def has_expired(self) -> bool: + """ + Return `True` if the connection is in a state where it should be closed. + + This either means that the connection is idle and it has passed the + expiry time on its keep-alive, or that server has sent an EOF. + """ + raise NotImplementedError() # pragma: nocover + + def is_idle(self) -> bool: + """ + Return `True` if the connection is currently idle. + """ + raise NotImplementedError() # pragma: nocover + + def is_closed(self) -> bool: + """ + Return `True` if the connection has been closed. + + Used when a response is closed to determine if the connection may be + returned to the connection pool or not. + """ + raise NotImplementedError() # pragma: nocover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py new file mode 100644 index 0000000..b363f55 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import logging +import ssl + +import socksio + +from .._backends.auto import AutoBackend +from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream +from .._exceptions import ConnectionNotAvailable, ProxyError +from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.socks") + + +AUTH_METHODS = { + b"\x00": "NO AUTHENTICATION REQUIRED", + b"\x01": "GSSAPI", + b"\x02": "USERNAME/PASSWORD", + b"\xff": "NO ACCEPTABLE METHODS", +} + +REPLY_CODES = { + b"\x00": "Succeeded", + b"\x01": "General SOCKS server failure", + b"\x02": "Connection not allowed by ruleset", + b"\x03": "Network unreachable", + b"\x04": "Host unreachable", + b"\x05": "Connection refused", + b"\x06": "TTL expired", + b"\x07": "Command not supported", + b"\x08": "Address type not supported", +} + + +async def _init_socks5_connection( + stream: AsyncNetworkStream, + *, + host: bytes, + port: int, + auth: tuple[bytes, bytes] | None = None, +) -> None: + conn = socksio.socks5.SOCKS5Connection() + + # Auth method request + auth_method = ( + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + if auth is None + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + ) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Auth method response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) + if response.method != auth_method: + requested = AUTH_METHODS.get(auth_method, "UNKNOWN") + responded = AUTH_METHODS.get(response.method, "UNKNOWN") + raise ProxyError( + f"Requested {requested} from proxy server, but got {responded}." + ) + + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + # Username/password request + assert auth is not None + username, password = auth + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Username/password response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) + if not response.success: + raise ProxyError("Invalid username/password") + + # Connect request + conn.send( + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) + ) + ) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Connect response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: + reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") + raise ProxyError(f"Proxy Server could not connect: {reply_code}.") + + +class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + network_backend: AsyncNetworkBackend | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + ) + self._ssl_context = ssl_context + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if proxy_auth is not None: + username, password = proxy_auth + username_bytes = enforce_bytes(username, name="proxy_auth") + password_bytes = enforce_bytes(password, name="proxy_auth") + self._proxy_auth: tuple[bytes, bytes] | None = ( + username_bytes, + password_bytes, + ) + else: + self._proxy_auth = None + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + return AsyncSocks5Connection( + proxy_origin=self._proxy_url.origin, + remote_origin=origin, + proxy_auth=self._proxy_auth, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class AsyncSocks5Connection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: AsyncNetworkBackend | None = None, + ) -> None: + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._proxy_auth = proxy_auth + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + + self._network_backend: AsyncNetworkBackend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._connect_lock = AsyncLock() + self._connection: AsyncConnectionInterface | None = None + self._connect_failed = False + + async def handle_async_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + async with self._connect_lock: + if self._connection is None: + try: + # Connect to the proxy + kwargs = { + "host": self._proxy_origin.host.decode("ascii"), + "port": self._proxy_origin.port, + "timeout": timeout, + } + async with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = await self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + + # Connect to the remote host using socks5 + kwargs = { + "stream": stream, + "host": self._remote_origin.host.decode("ascii"), + "port": self._remote_origin.port, + "auth": self._proxy_auth, + } + async with Trace( + "setup_socks5_connection", logger, request, kwargs + ) as trace: + await _init_socks5_connection(**kwargs) + trace.return_value = stream + + # Upgrade the stream to SSL + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or ( + self._http2 and not self._http1 + ): # pragma: nocover + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except Exception as exc: + self._connect_failed = True + raise exc + elif not self._connection.is_available(): # pragma: nocover + raise ConnectionNotAvailable() + + return await self._connection.handle_async_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + if self._connection is not None: + await self._connection.aclose() + + def is_available(self) -> bool: + if self._connection is None: # pragma: nocover + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._remote_origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: # pragma: nocover + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py new file mode 100644 index 0000000..a140095 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import ssl +import typing + +import anyio + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .._utils import is_socket_readable +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class AnyIOStream(AsyncNetworkStream): + def __init__(self, stream: anyio.abc.ByteStream) -> None: + self._stream = stream + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map = { + TimeoutError: ReadTimeout, + anyio.BrokenResourceError: ReadError, + anyio.ClosedResourceError: ReadError, + anyio.EndOfStream: ReadError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + try: + return await self._stream.receive(max_bytes=max_bytes) + except anyio.EndOfStream: # pragma: nocover + return b"" + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + exc_map = { + TimeoutError: WriteTimeout, + anyio.BrokenResourceError: WriteError, + anyio.ClosedResourceError: WriteError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + await self._stream.send(item=buffer) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + exc_map = { + TimeoutError: ConnectTimeout, + anyio.BrokenResourceError: ConnectError, + anyio.EndOfStream: ConnectError, + ssl.SSLError: ConnectError, + } + with map_exceptions(exc_map): + try: + with anyio.fail_after(timeout): + ssl_stream = await anyio.streams.tls.TLSStream.wrap( + self._stream, + ssl_context=ssl_context, + hostname=server_hostname, + standard_compatible=False, + server_side=False, + ) + except Exception as exc: # pragma: nocover + await self.aclose() + raise exc + return AnyIOStream(ssl_stream) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object": + return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None) + if info == "client_addr": + return self._stream.extra(anyio.abc.SocketAttribute.local_address, None) + if info == "server_addr": + return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None) + if info == "socket": + return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None) + if info == "is_readable": + sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None) + return is_socket_readable(sock) + return None + + +class AnyIOBackend(AsyncNetworkBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + exc_map = { + TimeoutError: ConnectTimeout, + OSError: ConnectError, + anyio.BrokenResourceError: ConnectError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + stream: anyio.abc.ByteStream = await anyio.connect_tcp( + remote_host=host, + remote_port=port, + local_host=local_address, + ) + # By default TCP sockets opened in `asyncio` include TCP_NODELAY. + for option in socket_options: + stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return AnyIOStream(stream) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + exc_map = { + TimeoutError: ConnectTimeout, + OSError: ConnectError, + anyio.BrokenResourceError: ConnectError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + stream: anyio.abc.ByteStream = await anyio.connect_unix(path) + for option in socket_options: + stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return AnyIOStream(stream) + + async def sleep(self, seconds: float) -> None: + await anyio.sleep(seconds) # pragma: nocover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py new file mode 100644 index 0000000..49f0e69 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import typing + +from .._synchronization import current_async_library +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class AutoBackend(AsyncNetworkBackend): + async def _init_backend(self) -> None: + if not (hasattr(self, "_backend")): + backend = current_async_library() + if backend == "trio": + from .trio import TrioBackend + + self._backend: AsyncNetworkBackend = TrioBackend() + else: + from .anyio import AnyIOBackend + + self._backend = AnyIOBackend() + + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + await self._init_backend() + return await self._backend.connect_tcp( + host, + port, + timeout=timeout, + local_address=local_address, + socket_options=socket_options, + ) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + await self._init_backend() + return await self._backend.connect_unix_socket( + path, timeout=timeout, socket_options=socket_options + ) + + async def sleep(self, seconds: float) -> None: # pragma: nocover + await self._init_backend() + return await self._backend.sleep(seconds) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py new file mode 100644 index 0000000..cf55c8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import ssl +import time +import typing + +SOCKET_OPTION = typing.Union[ + typing.Tuple[int, int, int], + typing.Tuple[int, int, typing.Union[bytes, bytearray]], + typing.Tuple[int, int, None, int], +] + + +class NetworkStream: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + raise NotImplementedError() # pragma: nocover + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + raise NotImplementedError() # pragma: nocover + + def close(self) -> None: + raise NotImplementedError() # pragma: nocover + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def get_extra_info(self, info: str) -> typing.Any: + return None # pragma: nocover + + +class NetworkBackend: + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def sleep(self, seconds: float) -> None: + time.sleep(seconds) # pragma: nocover + + +class AsyncNetworkStream: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + raise NotImplementedError() # pragma: nocover + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + raise NotImplementedError() # pragma: nocover + + async def aclose(self) -> None: + raise NotImplementedError() # pragma: nocover + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + def get_extra_info(self, info: str) -> typing.Any: + return None # pragma: nocover + + +class AsyncNetworkBackend: + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + async def sleep(self, seconds: float) -> None: + raise NotImplementedError() # pragma: nocover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py new file mode 100644 index 0000000..9b6edca --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import ssl +import typing + +from .._exceptions import ReadError +from .base import ( + SOCKET_OPTION, + AsyncNetworkBackend, + AsyncNetworkStream, + NetworkBackend, + NetworkStream, +) + + +class MockSSLObject: + def __init__(self, http2: bool): + self._http2 = http2 + + def selected_alpn_protocol(self) -> str: + return "h2" if self._http2 else "http/1.1" + + +class MockStream(NetworkStream): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + self._closed = False + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._closed: + raise ReadError("Connection closed") + if not self._buffer: + return b"" + return self._buffer.pop(0) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + pass + + def close(self) -> None: + self._closed = True + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + return self + + def get_extra_info(self, info: str) -> typing.Any: + return MockSSLObject(http2=self._http2) if info == "ssl_object" else None + + def __repr__(self) -> str: + return "" + + +class MockBackend(NetworkBackend): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + return MockStream(list(self._buffer), http2=self._http2) + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + return MockStream(list(self._buffer), http2=self._http2) + + def sleep(self, seconds: float) -> None: + pass + + +class AsyncMockStream(AsyncNetworkStream): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + self._closed = False + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._closed: + raise ReadError("Connection closed") + if not self._buffer: + return b"" + return self._buffer.pop(0) + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + pass + + async def aclose(self) -> None: + self._closed = True + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + return self + + def get_extra_info(self, info: str) -> typing.Any: + return MockSSLObject(http2=self._http2) if info == "ssl_object" else None + + def __repr__(self) -> str: + return "" + + +class AsyncMockBackend(AsyncNetworkBackend): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + return AsyncMockStream(list(self._buffer), http2=self._http2) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + return AsyncMockStream(list(self._buffer), http2=self._http2) + + async def sleep(self, seconds: float) -> None: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py new file mode 100644 index 0000000..4018a09 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +import functools +import socket +import ssl +import sys +import typing + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ExceptionMapping, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .._utils import is_socket_readable +from .base import SOCKET_OPTION, NetworkBackend, NetworkStream + + +class TLSinTLSStream(NetworkStream): # pragma: no cover + """ + Because the standard `SSLContext.wrap_socket` method does + not work for `SSLSocket` objects, we need this class + to implement TLS stream using an underlying `SSLObject` + instance in order to support TLS on top of TLS. + """ + + # Defined in RFC 8449 + TLS_RECORD_SIZE = 16384 + + def __init__( + self, + sock: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ): + self._sock = sock + self._incoming = ssl.MemoryBIO() + self._outgoing = ssl.MemoryBIO() + + self.ssl_obj = ssl_context.wrap_bio( + incoming=self._incoming, + outgoing=self._outgoing, + server_hostname=server_hostname, + ) + + self._sock.settimeout(timeout) + self._perform_io(self.ssl_obj.do_handshake) + + def _perform_io( + self, + func: typing.Callable[..., typing.Any], + ) -> typing.Any: + ret = None + + while True: + errno = None + try: + ret = func() + except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e: + errno = e.errno + + self._sock.sendall(self._outgoing.read()) + + if errno == ssl.SSL_ERROR_WANT_READ: + buf = self._sock.recv(self.TLS_RECORD_SIZE) + + if buf: + self._incoming.write(buf) + else: + self._incoming.write_eof() + if errno is None: + return ret + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + return typing.cast( + bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes)) + ) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + while buffer: + nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer)) + buffer = buffer[nsent:] + + def close(self) -> None: + self._sock.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + raise NotImplementedError() + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object": + return self.ssl_obj + if info == "client_addr": + return self._sock.getsockname() + if info == "server_addr": + return self._sock.getpeername() + if info == "socket": + return self._sock + if info == "is_readable": + return is_socket_readable(self._sock) + return None + + +class SyncStream(NetworkStream): + def __init__(self, sock: socket.socket) -> None: + self._sock = sock + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + return self._sock.recv(max_bytes) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} + with map_exceptions(exc_map): + while buffer: + self._sock.settimeout(timeout) + n = self._sock.send(buffer) + buffer = buffer[n:] + + def close(self) -> None: + self._sock.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + with map_exceptions(exc_map): + try: + if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover + # If the underlying socket has already been upgraded + # to the TLS layer (i.e. is an instance of SSLSocket), + # we need some additional smarts to support TLS-in-TLS. + return TLSinTLSStream( + self._sock, ssl_context, server_hostname, timeout + ) + else: + self._sock.settimeout(timeout) + sock = ssl_context.wrap_socket( + self._sock, server_hostname=server_hostname + ) + except Exception as exc: # pragma: nocover + self.close() + raise exc + return SyncStream(sock) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object" and isinstance(self._sock, ssl.SSLSocket): + return self._sock._sslobj # type: ignore + if info == "client_addr": + return self._sock.getsockname() + if info == "server_addr": + return self._sock.getpeername() + if info == "socket": + return self._sock + if info == "is_readable": + return is_socket_readable(self._sock) + return None + + +class SyncBackend(NetworkBackend): + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + # Note that we automatically include `TCP_NODELAY` + # in addition to any other custom socket options. + if socket_options is None: + socket_options = [] # pragma: no cover + address = (host, port) + source_address = None if local_address is None else (local_address, 0) + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + + with map_exceptions(exc_map): + sock = socket.create_connection( + address, + timeout, + source_address=source_address, + ) + for option in socket_options: + sock.setsockopt(*option) # pragma: no cover + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SyncStream(sock) + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: # pragma: nocover + if sys.platform == "win32": + raise RuntimeError( + "Attempted to connect to a UNIX socket on a Windows system." + ) + if socket_options is None: + socket_options = [] + + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + with map_exceptions(exc_map): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + for option in socket_options: + sock.setsockopt(*option) + sock.settimeout(timeout) + sock.connect(path) + return SyncStream(sock) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py new file mode 100644 index 0000000..6f53f5f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import ssl +import typing + +import trio + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ExceptionMapping, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class TrioStream(AsyncNetworkStream): + def __init__(self, stream: trio.abc.Stream) -> None: + self._stream = stream + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ReadTimeout, + trio.BrokenResourceError: ReadError, + trio.ClosedResourceError: ReadError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + data: bytes = await self._stream.receive_some(max_bytes=max_bytes) + return data + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: WriteTimeout, + trio.BrokenResourceError: WriteError, + trio.ClosedResourceError: WriteError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + await self._stream.send_all(data=buffer) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + } + ssl_stream = trio.SSLStream( + self._stream, + ssl_context=ssl_context, + server_hostname=server_hostname, + https_compatible=True, + server_side=False, + ) + with map_exceptions(exc_map): + try: + with trio.fail_after(timeout_or_inf): + await ssl_stream.do_handshake() + except Exception as exc: # pragma: nocover + await self.aclose() + raise exc + return TrioStream(ssl_stream) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object" and isinstance(self._stream, trio.SSLStream): + # Type checkers cannot see `_ssl_object` attribute because trio._ssl.SSLStream uses __getattr__/__setattr__. + # Tracked at https://github.com/python-trio/trio/issues/542 + return self._stream._ssl_object # type: ignore[attr-defined] + if info == "client_addr": + return self._get_socket_stream().socket.getsockname() + if info == "server_addr": + return self._get_socket_stream().socket.getpeername() + if info == "socket": + stream = self._stream + while isinstance(stream, trio.SSLStream): + stream = stream.transport_stream + assert isinstance(stream, trio.SocketStream) + return stream.socket + if info == "is_readable": + socket = self.get_extra_info("socket") + return socket.is_readable() + return None + + def _get_socket_stream(self) -> trio.SocketStream: + stream = self._stream + while isinstance(stream, trio.SSLStream): + stream = stream.transport_stream + assert isinstance(stream, trio.SocketStream) + return stream + + +class TrioBackend(AsyncNetworkBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + # By default for TCP sockets, trio enables TCP_NODELAY. + # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream + if socket_options is None: + socket_options = [] # pragma: no cover + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + OSError: ConnectError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + stream: trio.abc.Stream = await trio.open_tcp_stream( + host=host, port=port, local_address=local_address + ) + for option in socket_options: + stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return TrioStream(stream) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + OSError: ConnectError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + stream: trio.abc.Stream = await trio.open_unix_socket(path) + for option in socket_options: + stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return TrioStream(stream) + + async def sleep(self, seconds: float) -> None: + await trio.sleep(seconds) # pragma: nocover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py new file mode 100644 index 0000000..bc28d44 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py @@ -0,0 +1,81 @@ +import contextlib +import typing + +ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]] + + +@contextlib.contextmanager +def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]: + try: + yield + except Exception as exc: # noqa: PIE786 + for from_exc, to_exc in map.items(): + if isinstance(exc, from_exc): + raise to_exc(exc) from exc + raise # pragma: nocover + + +class ConnectionNotAvailable(Exception): + pass + + +class ProxyError(Exception): + pass + + +class UnsupportedProtocol(Exception): + pass + + +class ProtocolError(Exception): + pass + + +class RemoteProtocolError(ProtocolError): + pass + + +class LocalProtocolError(ProtocolError): + pass + + +# Timeout errors + + +class TimeoutException(Exception): + pass + + +class PoolTimeout(TimeoutException): + pass + + +class ConnectTimeout(TimeoutException): + pass + + +class ReadTimeout(TimeoutException): + pass + + +class WriteTimeout(TimeoutException): + pass + + +# Network errors + + +class NetworkError(Exception): + pass + + +class ConnectError(NetworkError): + pass + + +class ReadError(NetworkError): + pass + + +class WriteError(NetworkError): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py new file mode 100644 index 0000000..8a65f13 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_models.py @@ -0,0 +1,516 @@ +from __future__ import annotations + +import base64 +import ssl +import typing +import urllib.parse + +# Functions for typechecking... + + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] +HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None] + +Extensions = typing.MutableMapping[str, typing.Any] + + +def enforce_bytes(value: bytes | str, *, name: str) -> bytes: + """ + Any arguments that are ultimately represented as bytes can be specified + either as bytes or as strings. + + However we enforce that any string arguments must only contain characters in + the plain ASCII range. chr(0)...chr(127). If you need to use characters + outside that range then be precise, and use a byte-wise argument. + """ + if isinstance(value, str): + try: + return value.encode("ascii") + except UnicodeEncodeError: + raise TypeError(f"{name} strings may not include unicode characters.") + elif isinstance(value, bytes): + return value + + seen_type = type(value).__name__ + raise TypeError(f"{name} must be bytes or str, but got {seen_type}.") + + +def enforce_url(value: URL | bytes | str, *, name: str) -> URL: + """ + Type check for URL parameters. + """ + if isinstance(value, (bytes, str)): + return URL(value) + elif isinstance(value, URL): + return value + + seen_type = type(value).__name__ + raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.") + + +def enforce_headers( + value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str +) -> list[tuple[bytes, bytes]]: + """ + Convienence function that ensure all items in request or response headers + are either bytes or strings in the plain ASCII range. + """ + if value is None: + return [] + elif isinstance(value, typing.Mapping): + return [ + ( + enforce_bytes(k, name="header name"), + enforce_bytes(v, name="header value"), + ) + for k, v in value.items() + ] + elif isinstance(value, typing.Sequence): + return [ + ( + enforce_bytes(k, name="header name"), + enforce_bytes(v, name="header value"), + ) + for k, v in value + ] + + seen_type = type(value).__name__ + raise TypeError( + f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}." + ) + + +def enforce_stream( + value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None, + *, + name: str, +) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]: + if value is None: + return ByteStream(b"") + elif isinstance(value, bytes): + return ByteStream(value) + return value + + +# * https://tools.ietf.org/html/rfc3986#section-3.2.3 +# * https://url.spec.whatwg.org/#url-miscellaneous +# * https://url.spec.whatwg.org/#scheme-state +DEFAULT_PORTS = { + b"ftp": 21, + b"http": 80, + b"https": 443, + b"ws": 80, + b"wss": 443, +} + + +def include_request_headers( + headers: list[tuple[bytes, bytes]], + *, + url: "URL", + content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes], +) -> list[tuple[bytes, bytes]]: + headers_set = set(k.lower() for k, v in headers) + + if b"host" not in headers_set: + default_port = DEFAULT_PORTS.get(url.scheme) + if url.port is None or url.port == default_port: + header_value = url.host + else: + header_value = b"%b:%d" % (url.host, url.port) + headers = [(b"Host", header_value)] + headers + + if ( + content is not None + and b"content-length" not in headers_set + and b"transfer-encoding" not in headers_set + ): + if isinstance(content, bytes): + content_length = str(len(content)).encode("ascii") + headers += [(b"Content-Length", content_length)] + else: + headers += [(b"Transfer-Encoding", b"chunked")] # pragma: nocover + + return headers + + +# Interfaces for byte streams... + + +class ByteStream: + """ + A container for non-streaming content, and that supports both sync and async + stream iteration. + """ + + def __init__(self, content: bytes) -> None: + self._content = content + + def __iter__(self) -> typing.Iterator[bytes]: + yield self._content + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + yield self._content + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{len(self._content)} bytes]>" + + +class Origin: + def __init__(self, scheme: bytes, host: bytes, port: int) -> None: + self.scheme = scheme + self.host = host + self.port = port + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, Origin) + and self.scheme == other.scheme + and self.host == other.host + and self.port == other.port + ) + + def __str__(self) -> str: + scheme = self.scheme.decode("ascii") + host = self.host.decode("ascii") + port = str(self.port) + return f"{scheme}://{host}:{port}" + + +class URL: + """ + Represents the URL against which an HTTP request may be made. + + The URL may either be specified as a plain string, for convienence: + + ```python + url = httpcore.URL("https://www.example.com/") + ``` + + Or be constructed with explicitily pre-parsed components: + + ```python + url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/') + ``` + + Using this second more explicit style allows integrations that are using + `httpcore` to pass through URLs that have already been parsed in order to use + libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures + that URL parsing is treated identically at both the networking level and at any + higher layers of abstraction. + + The four components are important here, as they allow the URL to be precisely + specified in a pre-parsed format. They also allow certain types of request to + be created that could not otherwise be expressed. + + For example, an HTTP request to `http://www.example.com/` forwarded via a proxy + at `http://localhost:8080`... + + ```python + # Constructs an HTTP request with a complete URL as the target: + # GET https://www.example.com/ HTTP/1.1 + url = httpcore.URL( + scheme=b'http', + host=b'localhost', + port=8080, + target=b'https://www.example.com/' + ) + request = httpcore.Request( + method="GET", + url=url + ) + ``` + + Another example is constructing an `OPTIONS *` request... + + ```python + # Constructs an 'OPTIONS *' HTTP request: + # OPTIONS * HTTP/1.1 + url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*') + request = httpcore.Request(method="OPTIONS", url=url) + ``` + + This kind of request is not possible to formulate with a URL string, + because the `/` delimiter is always used to demark the target from the + host/port portion of the URL. + + For convenience, string-like arguments may be specified either as strings or + as bytes. However, once a request is being issue over-the-wire, the URL + components are always ultimately required to be a bytewise representation. + + In order to avoid any ambiguity over character encodings, when strings are used + as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`. + If you require a bytewise representation that is outside this range you must + handle the character encoding directly, and pass a bytes instance. + """ + + def __init__( + self, + url: bytes | str = "", + *, + scheme: bytes | str = b"", + host: bytes | str = b"", + port: int | None = None, + target: bytes | str = b"", + ) -> None: + """ + Parameters: + url: The complete URL as a string or bytes. + scheme: The URL scheme as a string or bytes. + Typically either `"http"` or `"https"`. + host: The URL host as a string or bytes. Such as `"www.example.com"`. + port: The port to connect to. Either an integer or `None`. + target: The target of the HTTP request. Such as `"/items?search=red"`. + """ + if url: + parsed = urllib.parse.urlparse(enforce_bytes(url, name="url")) + self.scheme = parsed.scheme + self.host = parsed.hostname or b"" + self.port = parsed.port + self.target = (parsed.path or b"/") + ( + b"?" + parsed.query if parsed.query else b"" + ) + else: + self.scheme = enforce_bytes(scheme, name="scheme") + self.host = enforce_bytes(host, name="host") + self.port = port + self.target = enforce_bytes(target, name="target") + + @property + def origin(self) -> Origin: + default_port = { + b"http": 80, + b"https": 443, + b"ws": 80, + b"wss": 443, + b"socks5": 1080, + b"socks5h": 1080, + }[self.scheme] + return Origin( + scheme=self.scheme, host=self.host, port=self.port or default_port + ) + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, URL) + and other.scheme == self.scheme + and other.host == self.host + and other.port == self.port + and other.target == self.target + ) + + def __bytes__(self) -> bytes: + if self.port is None: + return b"%b://%b%b" % (self.scheme, self.host, self.target) + return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target) + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(scheme={self.scheme!r}, " + f"host={self.host!r}, port={self.port!r}, target={self.target!r})" + ) + + +class Request: + """ + An HTTP request. + """ + + def __init__( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, + ) -> None: + """ + Parameters: + method: The HTTP request method, either as a string or bytes. + For example: `GET`. + url: The request URL, either as a `URL` instance, or as a string or bytes. + For example: `"https://www.example.com".` + headers: The HTTP request headers. + content: The content of the request body. + extensions: A dictionary of optional extra information included on + the request. Possible keys include `"timeout"`, and `"trace"`. + """ + self.method: bytes = enforce_bytes(method, name="method") + self.url: URL = enforce_url(url, name="url") + self.headers: list[tuple[bytes, bytes]] = enforce_headers( + headers, name="headers" + ) + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") + ) + self.extensions = {} if extensions is None else extensions + + if "target" in self.extensions: + self.url = URL( + scheme=self.url.scheme, + host=self.url.host, + port=self.url.port, + target=self.extensions["target"], + ) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.method!r}]>" + + +class Response: + """ + An HTTP response. + """ + + def __init__( + self, + status: int, + *, + headers: HeaderTypes = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, + ) -> None: + """ + Parameters: + status: The HTTP status code of the response. For example `200`. + headers: The HTTP response headers. + content: The content of the response body. + extensions: A dictionary of optional extra information included on + the responseself.Possible keys include `"http_version"`, + `"reason_phrase"`, and `"network_stream"`. + """ + self.status: int = status + self.headers: list[tuple[bytes, bytes]] = enforce_headers( + headers, name="headers" + ) + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") + ) + self.extensions = {} if extensions is None else extensions + + self._stream_consumed = False + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + if isinstance(self.stream, typing.Iterable): + raise RuntimeError( + "Attempted to access 'response.content' on a streaming response. " + "Call 'response.read()' first." + ) + else: + raise RuntimeError( + "Attempted to access 'response.content' on a streaming response. " + "Call 'await response.aread()' first." + ) + return self._content + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.status}]>" + + # Sync interface... + + def read(self) -> bytes: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to read an asynchronous response using 'response.read()'. " + "You should use 'await response.aread()' instead." + ) + if not hasattr(self, "_content"): + self._content = b"".join([part for part in self.iter_stream()]) + return self._content + + def iter_stream(self) -> typing.Iterator[bytes]: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to stream an asynchronous response using 'for ... in " + "response.iter_stream()'. " + "You should use 'async for ... in response.aiter_stream()' instead." + ) + if self._stream_consumed: + raise RuntimeError( + "Attempted to call 'for ... in response.iter_stream()' more than once." + ) + self._stream_consumed = True + for chunk in self.stream: + yield chunk + + def close(self) -> None: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to close an asynchronous response using 'response.close()'. " + "You should use 'await response.aclose()' instead." + ) + if hasattr(self.stream, "close"): + self.stream.close() + + # Async interface... + + async def aread(self) -> bytes: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to read an synchronous response using " + "'await response.aread()'. " + "You should use 'response.read()' instead." + ) + if not hasattr(self, "_content"): + self._content = b"".join([part async for part in self.aiter_stream()]) + return self._content + + async def aiter_stream(self) -> typing.AsyncIterator[bytes]: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to stream an synchronous response using 'async for ... in " + "response.aiter_stream()'. " + "You should use 'for ... in response.iter_stream()' instead." + ) + if self._stream_consumed: + raise RuntimeError( + "Attempted to call 'async for ... in response.aiter_stream()' " + "more than once." + ) + self._stream_consumed = True + async for chunk in self.stream: + yield chunk + + async def aclose(self) -> None: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to close a synchronous response using " + "'await response.aclose()'. " + "You should use 'response.close()' instead." + ) + if hasattr(self.stream, "aclose"): + await self.stream.aclose() + + +class Proxy: + def __init__( + self, + url: URL | bytes | str, + auth: tuple[bytes | str, bytes | str] | None = None, + headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + ): + self.url = enforce_url(url, name="url") + self.headers = enforce_headers(headers, name="headers") + self.ssl_context = ssl_context + + if auth is not None: + username = enforce_bytes(auth[0], name="auth") + password = enforce_bytes(auth[1], name="auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self.auth: tuple[bytes, bytes] | None = (username, password) + self.headers = [(b"Proxy-Authorization", authorization)] + self.headers + else: + self.auth = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py new file mode 100644 index 0000000..c99c5a6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py @@ -0,0 +1,9 @@ +import ssl + +import certifi + + +def default_ssl_context() -> ssl.SSLContext: + context = ssl.create_default_context() + context.load_verify_locations(certifi.where()) + return context diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py new file mode 100644 index 0000000..b476d76 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py @@ -0,0 +1,39 @@ +from .connection import HTTPConnection +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .http_proxy import HTTPProxy +from .interfaces import ConnectionInterface + +try: + from .http2 import HTTP2Connection +except ImportError: # pragma: nocover + + class HTTP2Connection: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use http2 support, but the `h2` package is not " + "installed. Use 'pip install httpcore[http2]'." + ) + + +try: + from .socks_proxy import SOCKSProxy +except ImportError: # pragma: nocover + + class SOCKSProxy: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use SOCKS support, but the `socksio` package is not " + "installed. Use 'pip install httpcore[socks]'." + ) + + +__all__ = [ + "HTTPConnection", + "ConnectionPool", + "HTTPProxy", + "HTTP11Connection", + "HTTP2Connection", + "ConnectionInterface", + "SOCKSProxy", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py new file mode 100644 index 0000000..363f8be --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import itertools +import logging +import ssl +import types +import typing + +from .._backends.sync import SyncBackend +from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream +from .._exceptions import ConnectError, ConnectTimeout +from .._models import Origin, Request, Response +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. + + +logger = logging.getLogger("httpcore.connection") + + +def exponential_backoff(factor: float) -> typing.Iterator[float]: + """ + Generate a geometric sequence that has a ratio of 2 and starts with 0. + + For example: + - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...` + - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...` + """ + yield 0 + for n in itertools.count(): + yield factor * 2**n + + +class HTTPConnection(ConnectionInterface): + def __init__( + self, + origin: Origin, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._origin = origin + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend: NetworkBackend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._connection: ConnectionInterface | None = None + self._connect_failed: bool = False + self._request_lock = Lock() + self._socket_options = socket_options + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection to {self._origin}" + ) + + try: + with self._request_lock: + if self._connection is None: + stream = self._connect(request) + + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except BaseException as exc: + self._connect_failed = True + raise exc + + return self._connection.handle_request(request) + + def _connect(self, request: Request) -> NetworkStream: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + retries_left = self._retries + delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR) + + while True: + try: + if self._uds is None: + kwargs = { + "host": self._origin.host.decode("ascii"), + "port": self._origin.port, + "local_address": self._local_address, + "timeout": timeout, + "socket_options": self._socket_options, + } + with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + else: + kwargs = { + "path": self._uds, + "timeout": timeout, + "socket_options": self._socket_options, + } + with Trace( + "connect_unix_socket", logger, request, kwargs + ) as trace: + stream = self._network_backend.connect_unix_socket( + **kwargs + ) + trace.return_value = stream + + if self._origin.scheme in (b"https", b"wss"): + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + return stream + except (ConnectError, ConnectTimeout): + if retries_left <= 0: + raise + retries_left -= 1 + delay = next(delays) + with Trace("retry", logger, request, kwargs) as trace: + self._network_backend.sleep(delay) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def close(self) -> None: + if self._connection is not None: + with Trace("close", logger, None, {}): + self._connection.close() + + def is_available(self) -> bool: + if self._connection is None: + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTPConnection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py new file mode 100644 index 0000000..9ccfa53 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py @@ -0,0 +1,420 @@ +from __future__ import annotations + +import ssl +import sys +import types +import typing + +from .._backends.sync import SyncBackend +from .._backends.base import SOCKET_OPTION, NetworkBackend +from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol +from .._models import Origin, Proxy, Request, Response +from .._synchronization import Event, ShieldCancellation, ThreadLock +from .connection import HTTPConnection +from .interfaces import ConnectionInterface, RequestInterface + + +class PoolRequest: + def __init__(self, request: Request) -> None: + self.request = request + self.connection: ConnectionInterface | None = None + self._connection_acquired = Event() + + def assign_to_connection(self, connection: ConnectionInterface | None) -> None: + self.connection = connection + self._connection_acquired.set() + + def clear_connection(self) -> None: + self.connection = None + self._connection_acquired = Event() + + def wait_for_connection( + self, timeout: float | None = None + ) -> ConnectionInterface: + if self.connection is None: + self._connection_acquired.wait(timeout=timeout) + assert self.connection is not None + return self.connection + + def is_queued(self) -> bool: + return self.connection is None + + +class ConnectionPool(RequestInterface): + """ + A connection pool for making HTTP requests. + """ + + def __init__( + self, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish a + connection. + local_address: Local address to connect from. Can also be used to connect + using a particular address family. Using `local_address="0.0.0.0"` + will connect using an `AF_INET` address (IPv4), while using + `local_address="::"` will connect using an `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + socket_options: Socket options that have to be included + in the TCP socket when the connection was established. + """ + self._ssl_context = ssl_context + self._proxy = proxy + self._max_connections = ( + sys.maxsize if max_connections is None else max_connections + ) + self._max_keepalive_connections = ( + sys.maxsize + if max_keepalive_connections is None + else max_keepalive_connections + ) + self._max_keepalive_connections = min( + self._max_connections, self._max_keepalive_connections + ) + + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._socket_options = socket_options + + # The mutable state on a connection pool is the queue of incoming requests, + # and the set of connections that are servicing those requests. + self._connections: list[ConnectionInterface] = [] + self._requests: list[PoolRequest] = [] + + # We only mutate the state of the connection pool within an 'optional_thread_lock' + # context. This holds a threading lock unless we're running in async mode, + # in which case it is a no-op. + self._optional_thread_lock = ThreadLock() + + def create_connection(self, origin: Origin) -> ConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import Socks5Connection + + return Socks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import ForwardHTTPConnection + + return ForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import TunnelHTTPConnection + + return TunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + return HTTPConnection( + origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + retries=self._retries, + local_address=self._local_address, + uds=self._uds, + network_backend=self._network_backend, + socket_options=self._socket_options, + ) + + @property + def connections(self) -> list[ConnectionInterface]: + """ + Return a list of the connections currently in the pool. + + For example: + + ```python + >>> pool.connections + [ + , + , + , + ] + ``` + """ + return list(self._connections) + + def handle_request(self, request: Request) -> Response: + """ + Send an HTTP request, and return an HTTP response. + + This is the core implementation that is called into by `.request()` or `.stream()`. + """ + scheme = request.url.scheme.decode() + if scheme == "": + raise UnsupportedProtocol( + "Request URL is missing an 'http://' or 'https://' protocol." + ) + if scheme not in ("http", "https", "ws", "wss"): + raise UnsupportedProtocol( + f"Request URL has an unsupported protocol '{scheme}://'." + ) + + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("pool", None) + + with self._optional_thread_lock: + # Add the incoming request to our request queue. + pool_request = PoolRequest(request) + self._requests.append(pool_request) + + try: + while True: + with self._optional_thread_lock: + # Assign incoming requests to available connections, + # closing or creating new connections as required. + closing = self._assign_requests_to_connections() + self._close_connections(closing) + + # Wait until this request has an assigned connection. + connection = pool_request.wait_for_connection(timeout=timeout) + + try: + # Send the request on the assigned connection. + response = connection.handle_request( + pool_request.request + ) + except ConnectionNotAvailable: + # In some cases a connection may initially be available to + # handle a request, but then become unavailable. + # + # In this case we clear the connection and try again. + pool_request.clear_connection() + else: + break # pragma: nocover + + except BaseException as exc: + with self._optional_thread_lock: + # For any exception or cancellation we remove the request from + # the queue, and then re-assign requests to connections. + self._requests.remove(pool_request) + closing = self._assign_requests_to_connections() + + self._close_connections(closing) + raise exc from None + + # Return the response. Note that in this case we still have to manage + # the point at which the response is closed. + assert isinstance(response.stream, typing.Iterable) + return Response( + status=response.status, + headers=response.headers, + content=PoolByteStream( + stream=response.stream, pool_request=pool_request, pool=self + ), + extensions=response.extensions, + ) + + def _assign_requests_to_connections(self) -> list[ConnectionInterface]: + """ + Manage the state of the connection pool, assigning incoming + requests to connections as available. + + Called whenever a new request is added or removed from the pool. + + Any closing connections are returned, allowing the I/O for closing + those connections to be handled seperately. + """ + closing_connections = [] + + # First we handle cleaning up any connections that are closed, + # have expired their keep-alive, or surplus idle connections. + for connection in list(self._connections): + if connection.is_closed(): + # log: "removing closed connection" + self._connections.remove(connection) + elif connection.has_expired(): + # log: "closing expired connection" + self._connections.remove(connection) + closing_connections.append(connection) + elif ( + connection.is_idle() + and len([connection.is_idle() for connection in self._connections]) + > self._max_keepalive_connections + ): + # log: "closing idle connection" + self._connections.remove(connection) + closing_connections.append(connection) + + # Assign queued requests to connections. + queued_requests = [request for request in self._requests if request.is_queued()] + for pool_request in queued_requests: + origin = pool_request.request.url.origin + available_connections = [ + connection + for connection in self._connections + if connection.can_handle_request(origin) and connection.is_available() + ] + idle_connections = [ + connection for connection in self._connections if connection.is_idle() + ] + + # There are three cases for how we may be able to handle the request: + # + # 1. There is an existing connection that can handle the request. + # 2. We can create a new connection to handle the request. + # 3. We can close an idle connection and then create a new connection + # to handle the request. + if available_connections: + # log: "reusing existing connection" + connection = available_connections[0] + pool_request.assign_to_connection(connection) + elif len(self._connections) < self._max_connections: + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + elif idle_connections: + # log: "closing idle connection" + connection = idle_connections[0] + self._connections.remove(connection) + closing_connections.append(connection) + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + + return closing_connections + + def _close_connections(self, closing: list[ConnectionInterface]) -> None: + # Close connections which have been removed from the pool. + with ShieldCancellation(): + for connection in closing: + connection.close() + + def close(self) -> None: + # Explicitly close the connection pool. + # Clears all existing requests and connections. + with self._optional_thread_lock: + closing_connections = list(self._connections) + self._connections = [] + self._close_connections(closing_connections) + + def __enter__(self) -> ConnectionPool: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + with self._optional_thread_lock: + request_is_queued = [request.is_queued() for request in self._requests] + connection_is_idle = [ + connection.is_idle() for connection in self._connections + ] + + num_active_requests = request_is_queued.count(False) + num_queued_requests = request_is_queued.count(True) + num_active_connections = connection_is_idle.count(False) + num_idle_connections = connection_is_idle.count(True) + + requests_info = ( + f"Requests: {num_active_requests} active, {num_queued_requests} queued" + ) + connection_info = ( + f"Connections: {num_active_connections} active, {num_idle_connections} idle" + ) + + return f"<{class_name} [{requests_info} | {connection_info}]>" + + +class PoolByteStream: + def __init__( + self, + stream: typing.Iterable[bytes], + pool_request: PoolRequest, + pool: ConnectionPool, + ) -> None: + self._stream = stream + self._pool_request = pool_request + self._pool = pool + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + try: + for part in self._stream: + yield part + except BaseException as exc: + self.close() + raise exc from None + + def close(self) -> None: + if not self._closed: + self._closed = True + with ShieldCancellation(): + if hasattr(self._stream, "close"): + self._stream.close() + + with self._pool._optional_thread_lock: + self._pool._requests.remove(self._pool_request) + closing = self._pool._assign_requests_to_connections() + + self._pool._close_connections(closing) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py new file mode 100644 index 0000000..ebd3a97 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py @@ -0,0 +1,379 @@ +from __future__ import annotations + +import enum +import logging +import ssl +import time +import types +import typing + +import h11 + +from .._backends.base import NetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, + WriteError, + map_exceptions, +) +from .._models import Origin, Request, Response +from .._synchronization import Lock, ShieldCancellation +from .._trace import Trace +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.http11") + + +# A subset of `h11.Event` types supported by `_send_event` +H11SendEvent = typing.Union[ + h11.Request, + h11.Data, + h11.EndOfMessage, +] + + +class HTTPConnectionState(enum.IntEnum): + NEW = 0 + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class HTTP11Connection(ConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024 + + def __init__( + self, + origin: Origin, + stream: NetworkStream, + keepalive_expiry: float | None = None, + ) -> None: + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None + self._state = HTTPConnectionState.NEW + self._state_lock = Lock() + self._request_count = 0 + self._h11_state = h11.Connection( + our_role=h11.CLIENT, + max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE, + ) + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + with self._state_lock: + if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): + self._request_count += 1 + self._state = HTTPConnectionState.ACTIVE + self._expire_at = None + else: + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request} + try: + with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: + self._send_request_headers(**kwargs) + with Trace("send_request_body", logger, request, kwargs) as trace: + self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + ( + http_version, + status, + reason_phrase, + headers, + trailing_data, + ) = self._receive_response_headers(**kwargs) + trace.return_value = ( + http_version, + status, + reason_phrase, + headers, + ) + + network_stream = self._network_stream + + # CONNECT or Upgrade request + if (status == 101) or ( + (request.method == b"CONNECT") and (200 <= status < 300) + ): + network_stream = HTTP11UpgradeStream(network_stream, trailing_data) + + return Response( + status=status, + headers=headers, + content=HTTP11ConnectionByteStream(self, request), + extensions={ + "http_version": http_version, + "reason_phrase": reason_phrase, + "network_stream": network_stream, + }, + ) + except BaseException as exc: + with ShieldCancellation(): + with Trace("response_closed", logger, request) as trace: + self._response_closed() + raise exc + + # Sending the request... + + def _send_request_headers(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with map_exceptions({h11.LocalProtocolError: LocalProtocolError}): + event = h11.Request( + method=request.method, + target=request.url.target, + headers=request.headers, + ) + self._send_event(event, timeout=timeout) + + def _send_request_body(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + assert isinstance(request.stream, typing.Iterable) + for chunk in request.stream: + event = h11.Data(data=chunk) + self._send_event(event, timeout=timeout) + + self._send_event(h11.EndOfMessage(), timeout=timeout) + + def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: + bytes_to_send = self._h11_state.send(event) + if bytes_to_send is not None: + self._network_stream.write(bytes_to_send, timeout=timeout) + + # Receiving the response... + + def _receive_response_headers( + self, request: Request + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = self._receive_event(timeout=timeout) + if isinstance(event, h11.Response): + break + if ( + isinstance(event, h11.InformationalResponse) + and event.status_code == 101 + ): + break + + http_version = b"HTTP/" + event.http_version + + # h11 version 0.11+ supports a `raw_items` interface to get the + # raw header casing, rather than the enforced lowercase headers. + headers = event.headers.raw_items() + + trailing_data, _ = self._h11_state.trailing_data + + return http_version, event.status_code, event.reason, headers, trailing_data + + def _receive_response_body( + self, request: Request + ) -> typing.Iterator[bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = self._receive_event(timeout=timeout) + if isinstance(event, h11.Data): + yield bytes(event.data) + elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): + break + + def _receive_event( + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: + while True: + with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): + event = self._h11_state.next_event() + + if event is h11.NEED_DATA: + data = self._network_stream.read( + self.READ_NUM_BYTES, timeout=timeout + ) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE: + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) + + self._h11_state.receive_data(data) + else: + # mypy fails to narrow the type in the above if statement above + return event # type: ignore[return-value] + + def _response_closed(self) -> None: + with self._state_lock: + if ( + self._h11_state.our_state is h11.DONE + and self._h11_state.their_state is h11.DONE + ): + self._state = HTTPConnectionState.IDLE + self._h11_state.start_next_cycle() + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + else: + self.close() + + # Once the connection is no longer required... + + def close(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._state = HTTPConnectionState.CLOSED + self._network_stream.close() + + # The ConnectionInterface methods provide information about the state of + # the connection, allowing for a connection pooling implementation to + # determine when to reuse and when to close the connection... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + # Note that HTTP/1.1 connections in the "NEW" state are not treated as + # being "available". The control flow which created the connection will + # be able to send an outgoing request, but the connection will not be + # acquired from the connection pool for any other request. + return self._state == HTTPConnectionState.IDLE + + def has_expired(self) -> bool: + now = time.monotonic() + keepalive_expired = self._expire_at is not None and now > self._expire_at + + # If the HTTP connection is idle but the socket is readable, then the + # only valid state is that the socket is about to return b"", indicating + # a server-initiated disconnect. + server_disconnected = ( + self._state == HTTPConnectionState.IDLE + and self._network_stream.get_extra_info("is_readable") + ) + + return keepalive_expired or server_disconnected + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/1.1, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTP11Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + +class HTTP11ConnectionByteStream: + def __init__(self, connection: HTTP11Connection, request: Request) -> None: + self._connection = connection + self._request = request + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + kwargs = {"request": self._request} + try: + with Trace("receive_response_body", logger, self._request, kwargs): + for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with ShieldCancellation(): + self.close() + raise exc + + def close(self) -> None: + if not self._closed: + self._closed = True + with Trace("response_closed", logger, self._request): + self._connection._response_closed() + + +class HTTP11UpgradeStream(NetworkStream): + def __init__(self, stream: NetworkStream, leading_data: bytes) -> None: + self._stream = stream + self._leading_data = leading_data + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._leading_data: + buffer = self._leading_data[:max_bytes] + self._leading_data = self._leading_data[max_bytes:] + return buffer + else: + return self._stream.read(max_bytes, timeout) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + self._stream.write(buffer, timeout) + + def close(self) -> None: + self._stream.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + return self._stream.start_tls(ssl_context, server_hostname, timeout) + + def get_extra_info(self, info: str) -> typing.Any: + return self._stream.get_extra_info(info) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py new file mode 100644 index 0000000..ddcc189 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py @@ -0,0 +1,592 @@ +from __future__ import annotations + +import enum +import logging +import time +import types +import typing + +import h2.config +import h2.connection +import h2.events +import h2.exceptions +import h2.settings + +from .._backends.base import NetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, +) +from .._models import Origin, Request, Response +from .._synchronization import Lock, Semaphore, ShieldCancellation +from .._trace import Trace +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.http2") + + +def has_body_headers(request: Request) -> bool: + return any( + k.lower() == b"content-length" or k.lower() == b"transfer-encoding" + for k, v in request.headers + ) + + +class HTTPConnectionState(enum.IntEnum): + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class HTTP2Connection(ConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + CONFIG = h2.config.H2Configuration(validate_inbound_headers=False) + + def __init__( + self, + origin: Origin, + stream: NetworkStream, + keepalive_expiry: float | None = None, + ): + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._h2_state = h2.connection.H2Connection(config=self.CONFIG) + self._state = HTTPConnectionState.IDLE + self._expire_at: float | None = None + self._request_count = 0 + self._init_lock = Lock() + self._state_lock = Lock() + self._read_lock = Lock() + self._write_lock = Lock() + self._sent_connection_init = False + self._used_all_stream_ids = False + self._connection_error = False + + # Mapping from stream ID to response stream events. + self._events: dict[ + int, + list[ + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, + ], + ] = {} + + # Connection terminated events are stored as state since + # we need to handle them for all streams. + self._connection_terminated: h2.events.ConnectionTerminated | None = None + + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + # This cannot occur in normal operation, since the connection pool + # will only send requests on connections that handle them. + # It's in place simply for resilience as a guard against incorrect + # usage, for anyone working directly with httpcore connections. + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + with self._state_lock: + if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): + self._request_count += 1 + self._expire_at = None + self._state = HTTPConnectionState.ACTIVE + else: + raise ConnectionNotAvailable() + + with self._init_lock: + if not self._sent_connection_init: + try: + sci_kwargs = {"request": request} + with Trace( + "send_connection_init", logger, request, sci_kwargs + ): + self._send_connection_init(**sci_kwargs) + except BaseException as exc: + with ShieldCancellation(): + self.close() + raise exc + + self._sent_connection_init = True + + # Initially start with just 1 until the remote server provides + # its max_concurrent_streams value + self._max_streams = 1 + + local_settings_max_streams = ( + self._h2_state.local_settings.max_concurrent_streams + ) + self._max_streams_semaphore = Semaphore(local_settings_max_streams) + + for _ in range(local_settings_max_streams - self._max_streams): + self._max_streams_semaphore.acquire() + + self._max_streams_semaphore.acquire() + + try: + stream_id = self._h2_state.get_next_available_stream_id() + self._events[stream_id] = [] + except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover + self._used_all_stream_ids = True + self._request_count -= 1 + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request, "stream_id": stream_id} + with Trace("send_request_headers", logger, request, kwargs): + self._send_request_headers(request=request, stream_id=stream_id) + with Trace("send_request_body", logger, request, kwargs): + self._send_request_body(request=request, stream_id=stream_id) + with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + status, headers = self._receive_response( + request=request, stream_id=stream_id + ) + trace.return_value = (status, headers) + + return Response( + status=status, + headers=headers, + content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id), + extensions={ + "http_version": b"HTTP/2", + "network_stream": self._network_stream, + "stream_id": stream_id, + }, + ) + except BaseException as exc: # noqa: PIE786 + with ShieldCancellation(): + kwargs = {"stream_id": stream_id} + with Trace("response_closed", logger, request, kwargs): + self._response_closed(stream_id=stream_id) + + if isinstance(exc, h2.exceptions.ProtocolError): + # One case where h2 can raise a protocol error is when a + # closed frame has been seen by the state machine. + # + # This happens when one stream is reading, and encounters + # a GOAWAY event. Other flows of control may then raise + # a protocol error at any point they interact with the 'h2_state'. + # + # In this case we'll have stored the event, and should raise + # it as a RemoteProtocolError. + if self._connection_terminated: # pragma: nocover + raise RemoteProtocolError(self._connection_terminated) + # If h2 raises a protocol error in some other state then we + # must somehow have made a protocol violation. + raise LocalProtocolError(exc) # pragma: nocover + + raise exc + + def _send_connection_init(self, request: Request) -> None: + """ + The HTTP/2 connection requires some initial setup before we can start + using individual request/response streams on it. + """ + # Need to set these manually here instead of manipulating via + # __setitem__() otherwise the H2Connection will emit SettingsUpdate + # frames in addition to sending the undesired defaults. + self._h2_state.local_settings = h2.settings.Settings( + client=True, + initial_values={ + # Disable PUSH_PROMISE frames from the server since we don't do anything + # with them for now. Maybe when we support caching? + h2.settings.SettingCodes.ENABLE_PUSH: 0, + # These two are taken from h2 for safe defaults + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536, + }, + ) + + # Some websites (*cough* Yahoo *cough*) balk at this setting being + # present in the initial handshake since it's not defined in the original + # RFC despite the RFC mandating ignoring settings you don't know about. + del self._h2_state.local_settings[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] + + self._h2_state.initiate_connection() + self._h2_state.increment_flow_control_window(2**24) + self._write_outgoing_data(request) + + # Sending the request... + + def _send_request_headers(self, request: Request, stream_id: int) -> None: + """ + Send the request headers to a given stream ID. + """ + end_stream = not has_body_headers(request) + + # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'. + # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require + # HTTP/1.1 style headers, and map them appropriately if we end up on + # an HTTP/2 connection. + authority = [v for k, v in request.headers if k.lower() == b"host"][0] + + headers = [ + (b":method", request.method), + (b":authority", authority), + (b":scheme", request.url.scheme), + (b":path", request.url.target), + ] + [ + (k.lower(), v) + for k, v in request.headers + if k.lower() + not in ( + b"host", + b"transfer-encoding", + ) + ] + + self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) + self._write_outgoing_data(request) + + def _send_request_body(self, request: Request, stream_id: int) -> None: + """ + Iterate over the request body sending it to a given stream ID. + """ + if not has_body_headers(request): + return + + assert isinstance(request.stream, typing.Iterable) + for data in request.stream: + self._send_stream_data(request, stream_id, data) + self._send_end_stream(request, stream_id) + + def _send_stream_data( + self, request: Request, stream_id: int, data: bytes + ) -> None: + """ + Send a single chunk of data in one or more data frames. + """ + while data: + max_flow = self._wait_for_outgoing_flow(request, stream_id) + chunk_size = min(len(data), max_flow) + chunk, data = data[:chunk_size], data[chunk_size:] + self._h2_state.send_data(stream_id, chunk) + self._write_outgoing_data(request) + + def _send_end_stream(self, request: Request, stream_id: int) -> None: + """ + Send an empty data frame on on a given stream ID with the END_STREAM flag set. + """ + self._h2_state.end_stream(stream_id) + self._write_outgoing_data(request) + + # Receiving the response... + + def _receive_response( + self, request: Request, stream_id: int + ) -> tuple[int, list[tuple[bytes, bytes]]]: + """ + Return the response status code and headers for a given stream ID. + """ + while True: + event = self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.ResponseReceived): + break + + status_code = 200 + headers = [] + assert event.headers is not None + for k, v in event.headers: + if k == b":status": + status_code = int(v.decode("ascii", errors="ignore")) + elif not k.startswith(b":"): + headers.append((k, v)) + + return (status_code, headers) + + def _receive_response_body( + self, request: Request, stream_id: int + ) -> typing.Iterator[bytes]: + """ + Iterator that returns the bytes of the response body for a given stream ID. + """ + while True: + event = self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.DataReceived): + assert event.flow_controlled_length is not None + assert event.data is not None + amount = event.flow_controlled_length + self._h2_state.acknowledge_received_data(amount, stream_id) + self._write_outgoing_data(request) + yield event.data + elif isinstance(event, h2.events.StreamEnded): + break + + def _receive_stream_event( + self, request: Request, stream_id: int + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: + """ + Return the next available event for a given stream ID. + + Will read more data from the network if required. + """ + while not self._events.get(stream_id): + self._receive_events(request, stream_id) + event = self._events[stream_id].pop(0) + if isinstance(event, h2.events.StreamReset): + raise RemoteProtocolError(event) + return event + + def _receive_events( + self, request: Request, stream_id: int | None = None + ) -> None: + """ + Read some data from the network until we see one or more events + for a given stream ID. + """ + with self._read_lock: + if self._connection_terminated is not None: + last_stream_id = self._connection_terminated.last_stream_id + if stream_id and last_stream_id and stream_id > last_stream_id: + self._request_count -= 1 + raise ConnectionNotAvailable() + raise RemoteProtocolError(self._connection_terminated) + + # This conditional is a bit icky. We don't want to block reading if we've + # actually got an event to return for a given stream. We need to do that + # check *within* the atomic read lock. Though it also need to be optional, + # because when we call it from `_wait_for_outgoing_flow` we *do* want to + # block until we've available flow control, event when we have events + # pending for the stream ID we're attempting to send on. + if stream_id is None or not self._events.get(stream_id): + events = self._read_incoming_data(request) + for event in events: + if isinstance(event, h2.events.RemoteSettingsChanged): + with Trace( + "receive_remote_settings", logger, request + ) as trace: + self._receive_remote_settings_change(event) + trace.return_value = event + + elif isinstance( + event, + ( + h2.events.ResponseReceived, + h2.events.DataReceived, + h2.events.StreamEnded, + h2.events.StreamReset, + ), + ): + if event.stream_id in self._events: + self._events[event.stream_id].append(event) + + elif isinstance(event, h2.events.ConnectionTerminated): + self._connection_terminated = event + + self._write_outgoing_data(request) + + def _receive_remote_settings_change( + self, event: h2.events.RemoteSettingsChanged + ) -> None: + max_concurrent_streams = event.changed_settings.get( + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ) + if max_concurrent_streams: + new_max_streams = min( + max_concurrent_streams.new_value, + self._h2_state.local_settings.max_concurrent_streams, + ) + if new_max_streams and new_max_streams != self._max_streams: + while new_max_streams > self._max_streams: + self._max_streams_semaphore.release() + self._max_streams += 1 + while new_max_streams < self._max_streams: + self._max_streams_semaphore.acquire() + self._max_streams -= 1 + + def _response_closed(self, stream_id: int) -> None: + self._max_streams_semaphore.release() + del self._events[stream_id] + with self._state_lock: + if self._connection_terminated and not self._events: + self.close() + + elif self._state == HTTPConnectionState.ACTIVE and not self._events: + self._state = HTTPConnectionState.IDLE + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + if self._used_all_stream_ids: # pragma: nocover + self.close() + + def close(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._h2_state.close_connection() + self._state = HTTPConnectionState.CLOSED + self._network_stream.close() + + # Wrappers around network read/write operations... + + def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + if self._read_exception is not None: + raise self._read_exception # pragma: nocover + + try: + data = self._network_stream.read(self.READ_NUM_BYTES, timeout) + if data == b"": + raise RemoteProtocolError("Server disconnected") + except Exception as exc: + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future reads. + # (For example, this means that a single read timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._read_exception = exc + self._connection_error = True + raise exc + + events: list[h2.events.Event] = self._h2_state.receive_data(data) + + return events + + def _write_outgoing_data(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with self._write_lock: + data_to_send = self._h2_state.data_to_send() + + if self._write_exception is not None: + raise self._write_exception # pragma: nocover + + try: + self._network_stream.write(data_to_send, timeout) + except Exception as exc: # pragma: nocover + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future write. + # (For example, this means that a single write timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._write_exception = exc + self._connection_error = True + raise exc + + # Flow control... + + def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int: + """ + Returns the maximum allowable outgoing flow for a given stream. + + If the allowable flow is zero, then waits on the network until + WindowUpdated frames have increased the flow rate. + https://tools.ietf.org/html/rfc7540#section-6.9 + """ + local_flow: int = self._h2_state.local_flow_control_window(stream_id) + max_frame_size: int = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + while flow == 0: + self._receive_events(request) + local_flow = self._h2_state.local_flow_control_window(stream_id) + max_frame_size = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + return flow + + # Interface for connection pooling... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + return ( + self._state != HTTPConnectionState.CLOSED + and not self._connection_error + and not self._used_all_stream_ids + and not ( + self._h2_state.state_machine.state + == h2.connection.ConnectionState.CLOSED + ) + ) + + def has_expired(self) -> bool: + now = time.monotonic() + return self._expire_at is not None and now > self._expire_at + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/2, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTP2Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + +class HTTP2ConnectionByteStream: + def __init__( + self, connection: HTTP2Connection, request: Request, stream_id: int + ) -> None: + self._connection = connection + self._request = request + self._stream_id = stream_id + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + kwargs = {"request": self._request, "stream_id": self._stream_id} + try: + with Trace("receive_response_body", logger, self._request, kwargs): + for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with ShieldCancellation(): + self.close() + raise exc + + def close(self) -> None: + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + with Trace("response_closed", logger, self._request, kwargs): + self._connection._response_closed(stream_id=self._stream_id) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py new file mode 100644 index 0000000..ecca88f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import base64 +import logging +import ssl +import typing + +from .._backends.base import SOCKET_OPTION, NetworkBackend +from .._exceptions import ProxyError +from .._models import ( + URL, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, +) +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .connection import HTTPConnection +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] + + +logger = logging.getLogger("httpcore.proxy") + + +def merge_headers( + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: + """ + Append default_headers and override_headers, de-duplicating if a key exists + in both cases. + """ + default_headers = [] if default_headers is None else list(default_headers) + override_headers = [] if override_headers is None else list(override_headers) + has_override = set(key.lower() for key, value in override_headers) + default_headers = [ + (key, value) + for key, value in default_headers + if key.lower() not in has_override + ] + return default_headers + override_headers + + +class HTTPProxy(ConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + proxy_auth: Any proxy authentication as a two-tuple of + (username, password). May be either bytes or ascii-only str. + proxy_headers: Any HTTP headers to use for the proxy requests. + For example `{"Proxy-Authorization": "Basic :"}`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + local_address=local_address, + uds=uds, + socket_options=socket_options, + ) + + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if ( + self._proxy_url.scheme == b"http" and proxy_ssl_context is not None + ): # pragma: no cover + raise RuntimeError( + "The `proxy_ssl_context` argument is not allowed for the http scheme" + ) + + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + if proxy_auth is not None: + username = enforce_bytes(proxy_auth[0], name="proxy_auth") + password = enforce_bytes(proxy_auth[1], name="proxy_auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self._proxy_headers = [ + (b"Proxy-Authorization", authorization) + ] + self._proxy_headers + + def create_connection(self, origin: Origin) -> ConnectionInterface: + if origin.scheme == b"http": + return ForwardHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + proxy_ssl_context=self._proxy_ssl_context, + ) + return TunnelHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + ssl_context=self._ssl_context, + proxy_ssl_context=self._proxy_ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class ForwardHTTPConnection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + ) -> None: + self._connection = HTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._remote_origin = remote_origin + + def handle_request(self, request: Request) -> Response: + headers = merge_headers(self._proxy_headers, request.headers) + url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=bytes(request.url), + ) + proxy_request = Request( + method=request.method, + url=url, + headers=headers, + content=request.stream, + extensions=request.extensions, + ) + return self._connection.handle_request(proxy_request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + self._connection.close() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + +class TunnelHTTPConnection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._connection: ConnectionInterface = HTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._connect_lock = Lock() + self._connected = False + + def handle_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("connect", None) + + with self._connect_lock: + if not self._connected: + target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port) + + connect_url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=target, + ) + connect_headers = merge_headers( + [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers + ) + connect_request = Request( + method=b"CONNECT", + url=connect_url, + headers=connect_headers, + extensions=request.extensions, + ) + connect_response = self._connection.handle_request( + connect_request + ) + + if connect_response.status < 200 or connect_response.status > 299: + reason_bytes = connect_response.extensions.get("reason_phrase", b"") + reason_str = reason_bytes.decode("ascii", errors="ignore") + msg = "%d %s" % (connect_response.status, reason_str) + self._connection.close() + raise ProxyError(msg) + + stream = connect_response.extensions["network_stream"] + + # Upgrade the stream to SSL + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + + self._connected = True + return self._connection.handle_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + self._connection.close() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py new file mode 100644 index 0000000..e673d4c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import contextlib +import typing + +from .._models import ( + URL, + Extensions, + HeaderTypes, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, + include_request_headers, +) + + +class RequestInterface: + def request( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> Response: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = self.handle_request(request) + try: + response.read() + finally: + response.close() + return response + + @contextlib.contextmanager + def stream( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.Iterator[Response]: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = self.handle_request(request) + try: + yield response + finally: + response.close() + + def handle_request(self, request: Request) -> Response: + raise NotImplementedError() # pragma: nocover + + +class ConnectionInterface(RequestInterface): + def close(self) -> None: + raise NotImplementedError() # pragma: nocover + + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def can_handle_request(self, origin: Origin) -> bool: + raise NotImplementedError() # pragma: nocover + + def is_available(self) -> bool: + """ + Return `True` if the connection is currently able to accept an + outgoing request. + + An HTTP/1.1 connection will only be available if it is currently idle. + + An HTTP/2 connection will be available so long as the stream ID space is + not yet exhausted, and the connection is not in an error state. + + While the connection is being established we may not yet know if it is going + to result in an HTTP/1.1 or HTTP/2 connection. The connection should be + treated as being available, but might ultimately raise `NewConnectionRequired` + required exceptions if multiple requests are attempted over a connection + that ends up being established as HTTP/1.1. + """ + raise NotImplementedError() # pragma: nocover + + def has_expired(self) -> bool: + """ + Return `True` if the connection is in a state where it should be closed. + + This either means that the connection is idle and it has passed the + expiry time on its keep-alive, or that server has sent an EOF. + """ + raise NotImplementedError() # pragma: nocover + + def is_idle(self) -> bool: + """ + Return `True` if the connection is currently idle. + """ + raise NotImplementedError() # pragma: nocover + + def is_closed(self) -> bool: + """ + Return `True` if the connection has been closed. + + Used when a response is closed to determine if the connection may be + returned to the connection pool or not. + """ + raise NotImplementedError() # pragma: nocover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py new file mode 100644 index 0000000..0ca96dd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import logging +import ssl + +import socksio + +from .._backends.sync import SyncBackend +from .._backends.base import NetworkBackend, NetworkStream +from .._exceptions import ConnectionNotAvailable, ProxyError +from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.socks") + + +AUTH_METHODS = { + b"\x00": "NO AUTHENTICATION REQUIRED", + b"\x01": "GSSAPI", + b"\x02": "USERNAME/PASSWORD", + b"\xff": "NO ACCEPTABLE METHODS", +} + +REPLY_CODES = { + b"\x00": "Succeeded", + b"\x01": "General SOCKS server failure", + b"\x02": "Connection not allowed by ruleset", + b"\x03": "Network unreachable", + b"\x04": "Host unreachable", + b"\x05": "Connection refused", + b"\x06": "TTL expired", + b"\x07": "Command not supported", + b"\x08": "Address type not supported", +} + + +def _init_socks5_connection( + stream: NetworkStream, + *, + host: bytes, + port: int, + auth: tuple[bytes, bytes] | None = None, +) -> None: + conn = socksio.socks5.SOCKS5Connection() + + # Auth method request + auth_method = ( + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + if auth is None + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + ) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Auth method response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) + if response.method != auth_method: + requested = AUTH_METHODS.get(auth_method, "UNKNOWN") + responded = AUTH_METHODS.get(response.method, "UNKNOWN") + raise ProxyError( + f"Requested {requested} from proxy server, but got {responded}." + ) + + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + # Username/password request + assert auth is not None + username, password = auth + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Username/password response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) + if not response.success: + raise ProxyError("Invalid username/password") + + # Connect request + conn.send( + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) + ) + ) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Connect response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: + reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") + raise ProxyError(f"Proxy Server could not connect: {reply_code}.") + + +class SOCKSProxy(ConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + network_backend: NetworkBackend | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + ) + self._ssl_context = ssl_context + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if proxy_auth is not None: + username, password = proxy_auth + username_bytes = enforce_bytes(username, name="proxy_auth") + password_bytes = enforce_bytes(password, name="proxy_auth") + self._proxy_auth: tuple[bytes, bytes] | None = ( + username_bytes, + password_bytes, + ) + else: + self._proxy_auth = None + + def create_connection(self, origin: Origin) -> ConnectionInterface: + return Socks5Connection( + proxy_origin=self._proxy_url.origin, + remote_origin=origin, + proxy_auth=self._proxy_auth, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class Socks5Connection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: NetworkBackend | None = None, + ) -> None: + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._proxy_auth = proxy_auth + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + + self._network_backend: NetworkBackend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._connect_lock = Lock() + self._connection: ConnectionInterface | None = None + self._connect_failed = False + + def handle_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + with self._connect_lock: + if self._connection is None: + try: + # Connect to the proxy + kwargs = { + "host": self._proxy_origin.host.decode("ascii"), + "port": self._proxy_origin.port, + "timeout": timeout, + } + with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + + # Connect to the remote host using socks5 + kwargs = { + "stream": stream, + "host": self._remote_origin.host.decode("ascii"), + "port": self._remote_origin.port, + "auth": self._proxy_auth, + } + with Trace( + "setup_socks5_connection", logger, request, kwargs + ) as trace: + _init_socks5_connection(**kwargs) + trace.return_value = stream + + # Upgrade the stream to SSL + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or ( + self._http2 and not self._http1 + ): # pragma: nocover + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except Exception as exc: + self._connect_failed = True + raise exc + elif not self._connection.is_available(): # pragma: nocover + raise ConnectionNotAvailable() + + return self._connection.handle_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + if self._connection is not None: + self._connection.close() + + def is_available(self) -> bool: + if self._connection is None: # pragma: nocover + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._remote_origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: # pragma: nocover + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py new file mode 100644 index 0000000..2ecc9e9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py @@ -0,0 +1,318 @@ +from __future__ import annotations + +import threading +import types + +from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions + +# Our async synchronization primatives use either 'anyio' or 'trio' depending +# on if they're running under asyncio or trio. + +try: + import trio +except (ImportError, NotImplementedError): # pragma: nocover + trio = None # type: ignore + +try: + import anyio +except ImportError: # pragma: nocover + anyio = None # type: ignore + + +def current_async_library() -> str: + # Determine if we're running under trio or asyncio. + # See https://sniffio.readthedocs.io/en/latest/ + try: + import sniffio + except ImportError: # pragma: nocover + environment = "asyncio" + else: + environment = sniffio.current_async_library() + + if environment not in ("asyncio", "trio"): # pragma: nocover + raise RuntimeError("Running under an unsupported async environment.") + + if environment == "asyncio" and anyio is None: # pragma: nocover + raise RuntimeError( + "Running with asyncio requires installation of 'httpcore[asyncio]'." + ) + + if environment == "trio" and trio is None: # pragma: nocover + raise RuntimeError( + "Running with trio requires installation of 'httpcore[trio]'." + ) + + return environment + + +class AsyncLock: + """ + This is a standard lock. + + In the sync case `Lock` provides thread locking. + In the async case `AsyncLock` provides async locking. + """ + + def __init__(self) -> None: + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a lock with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_lock = trio.Lock() + elif self._backend == "asyncio": + self._anyio_lock = anyio.Lock() + + async def __aenter__(self) -> AsyncLock: + if not self._backend: + self.setup() + + if self._backend == "trio": + await self._trio_lock.acquire() + elif self._backend == "asyncio": + await self._anyio_lock.acquire() + + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self._backend == "trio": + self._trio_lock.release() + elif self._backend == "asyncio": + self._anyio_lock.release() + + +class AsyncThreadLock: + """ + This is a threading-only lock for no-I/O contexts. + + In the sync case `ThreadLock` provides thread locking. + In the async case `AsyncThreadLock` is a no-op. + """ + + def __enter__(self) -> AsyncThreadLock: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + pass + + +class AsyncEvent: + def __init__(self) -> None: + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a lock with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_event = trio.Event() + elif self._backend == "asyncio": + self._anyio_event = anyio.Event() + + def set(self) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + self._trio_event.set() + elif self._backend == "asyncio": + self._anyio_event.set() + + async def wait(self, timeout: float | None = None) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout} + timeout_or_inf = float("inf") if timeout is None else timeout + with map_exceptions(trio_exc_map): + with trio.fail_after(timeout_or_inf): + await self._trio_event.wait() + elif self._backend == "asyncio": + anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout} + with map_exceptions(anyio_exc_map): + with anyio.fail_after(timeout): + await self._anyio_event.wait() + + +class AsyncSemaphore: + def __init__(self, bound: int) -> None: + self._bound = bound + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a semaphore with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_semaphore = trio.Semaphore( + initial_value=self._bound, max_value=self._bound + ) + elif self._backend == "asyncio": + self._anyio_semaphore = anyio.Semaphore( + initial_value=self._bound, max_value=self._bound + ) + + async def acquire(self) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + await self._trio_semaphore.acquire() + elif self._backend == "asyncio": + await self._anyio_semaphore.acquire() + + async def release(self) -> None: + if self._backend == "trio": + self._trio_semaphore.release() + elif self._backend == "asyncio": + self._anyio_semaphore.release() + + +class AsyncShieldCancellation: + # For certain portions of our codebase where we're dealing with + # closing connections during exception handling we want to shield + # the operation from being cancelled. + # + # with AsyncShieldCancellation(): + # ... # clean-up operations, shielded from cancellation. + + def __init__(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a shielded scope with the correct implementation. + """ + self._backend = current_async_library() + + if self._backend == "trio": + self._trio_shield = trio.CancelScope(shield=True) + elif self._backend == "asyncio": + self._anyio_shield = anyio.CancelScope(shield=True) + + def __enter__(self) -> AsyncShieldCancellation: + if self._backend == "trio": + self._trio_shield.__enter__() + elif self._backend == "asyncio": + self._anyio_shield.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self._backend == "trio": + self._trio_shield.__exit__(exc_type, exc_value, traceback) + elif self._backend == "asyncio": + self._anyio_shield.__exit__(exc_type, exc_value, traceback) + + +# Our thread-based synchronization primitives... + + +class Lock: + """ + This is a standard lock. + + In the sync case `Lock` provides thread locking. + In the async case `AsyncLock` provides async locking. + """ + + def __init__(self) -> None: + self._lock = threading.Lock() + + def __enter__(self) -> Lock: + self._lock.acquire() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self._lock.release() + + +class ThreadLock: + """ + This is a threading-only lock for no-I/O contexts. + + In the sync case `ThreadLock` provides thread locking. + In the async case `AsyncThreadLock` is a no-op. + """ + + def __init__(self) -> None: + self._lock = threading.Lock() + + def __enter__(self) -> ThreadLock: + self._lock.acquire() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self._lock.release() + + +class Event: + def __init__(self) -> None: + self._event = threading.Event() + + def set(self) -> None: + self._event.set() + + def wait(self, timeout: float | None = None) -> None: + if timeout == float("inf"): # pragma: no cover + timeout = None + if not self._event.wait(timeout=timeout): + raise PoolTimeout() # pragma: nocover + + +class Semaphore: + def __init__(self, bound: int) -> None: + self._semaphore = threading.Semaphore(value=bound) + + def acquire(self) -> None: + self._semaphore.acquire() + + def release(self) -> None: + self._semaphore.release() + + +class ShieldCancellation: + # Thread-synchronous codebases don't support cancellation semantics. + # We have this class because we need to mirror the async and sync + # cases within our package, but it's just a no-op. + def __enter__(self) -> ShieldCancellation: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py new file mode 100644 index 0000000..5f1cd7c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import inspect +import logging +import types +import typing + +from ._models import Request + + +class Trace: + def __init__( + self, + name: str, + logger: logging.Logger, + request: Request | None = None, + kwargs: dict[str, typing.Any] | None = None, + ) -> None: + self.name = name + self.logger = logger + self.trace_extension = ( + None if request is None else request.extensions.get("trace") + ) + self.debug = self.logger.isEnabledFor(logging.DEBUG) + self.kwargs = kwargs or {} + self.return_value: typing.Any = None + self.should_trace = self.debug or self.trace_extension is not None + self.prefix = self.logger.name.split(".")[-1] + + def trace(self, name: str, info: dict[str, typing.Any]) -> None: + if self.trace_extension is not None: + prefix_and_name = f"{self.prefix}.{name}" + ret = self.trace_extension(prefix_and_name, info) + if inspect.iscoroutine(ret): # pragma: no cover + raise TypeError( + "If you are using a synchronous interface, " + "the callback of the `trace` extension should " + "be a normal function instead of an asynchronous function." + ) + + if self.debug: + if not info or "return_value" in info and info["return_value"] is None: + message = name + else: + args = " ".join([f"{key}={value!r}" for key, value in info.items()]) + message = f"{name} {args}" + self.logger.debug(message) + + def __enter__(self) -> Trace: + if self.should_trace: + info = self.kwargs + self.trace(f"{self.name}.started", info) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self.should_trace: + if exc_value is None: + info = {"return_value": self.return_value} + self.trace(f"{self.name}.complete", info) + else: + info = {"exception": exc_value} + self.trace(f"{self.name}.failed", info) + + async def atrace(self, name: str, info: dict[str, typing.Any]) -> None: + if self.trace_extension is not None: + prefix_and_name = f"{self.prefix}.{name}" + coro = self.trace_extension(prefix_and_name, info) + if not inspect.iscoroutine(coro): # pragma: no cover + raise TypeError( + "If you're using an asynchronous interface, " + "the callback of the `trace` extension should " + "be an asynchronous function rather than a normal function." + ) + await coro + + if self.debug: + if not info or "return_value" in info and info["return_value"] is None: + message = name + else: + args = " ".join([f"{key}={value!r}" for key, value in info.items()]) + message = f"{name} {args}" + self.logger.debug(message) + + async def __aenter__(self) -> Trace: + if self.should_trace: + info = self.kwargs + await self.atrace(f"{self.name}.started", info) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self.should_trace: + if exc_value is None: + info = {"return_value": self.return_value} + await self.atrace(f"{self.name}.complete", info) + else: + info = {"exception": exc_value} + await self.atrace(f"{self.name}.failed", info) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py new file mode 100644 index 0000000..c44ff93 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import select +import socket +import sys + + +def is_socket_readable(sock: socket.socket | None) -> bool: + """ + Return whether a socket, as identifed by its file descriptor, is readable. + "A socket is readable" means that the read buffer isn't empty, i.e. that calling + .recv() on it would immediately return some data. + """ + # NOTE: we want check for readability without actually attempting to read, because + # we don't want to block forever if it's not readable. + + # In the case that the socket no longer exists, or cannot return a file + # descriptor, we treat it as being readable, as if it the next read operation + # on it is ready to return the terminating `b""`. + sock_fd = None if sock is None else sock.fileno() + if sock_fd is None or sock_fd < 0: # pragma: nocover + return True + + # The implementation below was stolen from: + # https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471-L478 + # See also: https://github.com/encode/httpcore/pull/193#issuecomment-703129316 + + # Use select.select on Windows, and when poll is unavailable and select.poll + # everywhere else. (E.g. When eventlet is in use. See #327) + if ( + sys.platform == "win32" or getattr(select, "poll", None) is None + ): # pragma: nocover + rready, _, _ = select.select([sock_fd], [], [], 0) + return bool(rready) + p = select.poll() + p.register(sock_fd, select.POLLIN) + return bool(p.poll(0)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpcore/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA new file mode 100644 index 0000000..b0d2b19 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA @@ -0,0 +1,203 @@ +Metadata-Version: 2.3 +Name: httpx +Version: 0.28.1 +Summary: The next generation HTTP client. +Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md +Project-URL: Documentation, https://www.python-httpx.org +Project-URL: Homepage, https://github.com/encode/httpx +Project-URL: Source, https://github.com/encode/httpx +Author-email: Tom Christie +License: BSD-3-Clause +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: AsyncIO +Classifier: Framework :: Trio +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.8 +Requires-Dist: anyio +Requires-Dist: certifi +Requires-Dist: httpcore==1.* +Requires-Dist: idna +Provides-Extra: brotli +Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli' +Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli' +Provides-Extra: cli +Requires-Dist: click==8.*; extra == 'cli' +Requires-Dist: pygments==2.*; extra == 'cli' +Requires-Dist: rich<14,>=10; extra == 'cli' +Provides-Extra: http2 +Requires-Dist: h2<5,>=3; extra == 'http2' +Provides-Extra: socks +Requires-Dist: socksio==1.*; extra == 'socks' +Provides-Extra: zstd +Requires-Dist: zstandard>=0.18.0; extra == 'zstd' +Description-Content-Type: text/markdown + +

+ HTTPX +

+ +

HTTPX - A next-generation HTTP client for Python.

+ +

+ + Test Suite + + + Package version + +

+ +HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**. + +--- + +Install HTTPX using pip: + +```shell +$ pip install httpx +``` + +Now, let's get started: + +```pycon +>>> import httpx +>>> r = httpx.get('https://www.example.org/') +>>> r + +>>> r.status_code +200 +>>> r.headers['content-type'] +'text/html; charset=UTF-8' +>>> r.text +'\n\n\nExample Domain...' +``` + +Or, using the command-line client. + +```shell +$ pip install 'httpx[cli]' # The command line client is an optional dependency. +``` + +Which now allows us to use HTTPX directly from the command-line... + +

+ httpx --help +

+ +Sending a request... + +

+ httpx http://httpbin.org/json +

+ +## Features + +HTTPX builds on the well-established usability of `requests`, and gives you: + +* A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/). +* An integrated command-line client. +* HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/). +* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/). +* Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/transports/#wsgi-transport) or [ASGI applications](https://www.python-httpx.org/advanced/transports/#asgi-transport). +* Strict timeouts everywhere. +* Fully type annotated. +* 100% test coverage. + +Plus all the standard features of `requests`... + +* International Domains and URLs +* Keep-Alive & Connection Pooling +* Sessions with Cookie Persistence +* Browser-style SSL Verification +* Basic/Digest Authentication +* Elegant Key/Value Cookies +* Automatic Decompression +* Automatic Content Decoding +* Unicode Response Bodies +* Multipart File Uploads +* HTTP(S) Proxy Support +* Connection Timeouts +* Streaming Downloads +* .netrc Support +* Chunked Requests + +## Installation + +Install with pip: + +```shell +$ pip install httpx +``` + +Or, to include the optional HTTP/2 support, use: + +```shell +$ pip install httpx[http2] +``` + +HTTPX requires Python 3.8+. + +## Documentation + +Project documentation is available at [https://www.python-httpx.org/](https://www.python-httpx.org/). + +For a run-through of all the basics, head over to the [QuickStart](https://www.python-httpx.org/quickstart/). + +For more advanced topics, see the [Advanced Usage](https://www.python-httpx.org/advanced/) section, the [async support](https://www.python-httpx.org/async/) section, or the [HTTP/2](https://www.python-httpx.org/http2/) section. + +The [Developer Interface](https://www.python-httpx.org/api/) provides a comprehensive API reference. + +To find out about tools that integrate with HTTPX, see [Third Party Packages](https://www.python-httpx.org/third_party_packages/). + +## Contribute + +If you want to contribute with HTTPX check out the [Contributing Guide](https://www.python-httpx.org/contributing/) to learn how to start. + +## Dependencies + +The HTTPX project relies on these excellent libraries: + +* `httpcore` - The underlying transport implementation for `httpx`. + * `h11` - HTTP/1.1 support. +* `certifi` - SSL certificates. +* `idna` - Internationalized domain name support. +* `sniffio` - Async library autodetection. + +As well as these optional installs: + +* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)* +* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)* +* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)* +* `click` - Command line client support. *(Optional, with `httpx[cli]`)* +* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)* +* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)* + +A huge amount of credit is due to `requests` for the API layout that +much of this work follows, as well as to `urllib3` for plenty of design +inspiration around the lower-level networking details. + +--- + +

HTTPX is BSD licensed code.
Designed & crafted with care.

— 🦋 —

+ +## Release Information + +### Fixed + +* Reintroduced supposedly-private `URLTypes` shortcut. (#2673) + + +--- + +[Full changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD new file mode 100644 index 0000000..eecd0e7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD @@ -0,0 +1,29 @@ +httpx/__init__.py,sha256=CsaZe6yZj0rHg6322AWKWHGTMVr9txgEfD5P3_Rrz60,2171 +httpx/__version__.py,sha256=LoUyYeOXTieGzuP_64UL0wxdtxjuu_QbOvE7NOg-IqU,108 +httpx/_api.py,sha256=r_Zgs4jIpcPJLqK5dbbSayqo_iVMKFaxZCd-oOHxLEs,11743 +httpx/_auth.py,sha256=Yr3QwaUSK17rGYx-7j-FdicFIzz4Y9FFV-1F4-7RXX4,11891 +httpx/_client.py,sha256=xD-UG67-WMkeltAAOeGGj-cZ2RRTAm19sWRxlFY7_40,65714 +httpx/_config.py,sha256=pPp2U-wicfcKsF-KYRE1LYdt3e6ERGeIoXZ8Gjo3LWc,8547 +httpx/_content.py,sha256=LGGzrJTR3OvN4Mb1GVVNLXkXJH-6oKlwAttO9p5w_yg,8161 +httpx/_decoders.py,sha256=p0dX8I0NEHexs3UGp4SsZutiMhsXrrWl6-GnqVb0iKM,12041 +httpx/_exceptions.py,sha256=bxW7fxzgVMAdNTbwT0Vnq04gJDW1_gI_GFiQPuMyjL0,8527 +httpx/_main.py,sha256=Cg9GMabiTT_swaDfUgIRitSwxLRMSwUDOm7LdSGqlA4,15626 +httpx/_models.py,sha256=4__Guyv1gLxuZChwim8kfQNiIOcJ9acreFOSurvZfms,44700 +httpx/_multipart.py,sha256=KOHEZZl6oohg9mPaKyyu345qq1rJLg35TUG3YAzXB3Y,9843 +httpx/_status_codes.py,sha256=DYn-2ufBgMeXy5s8x3_TB7wjAuAAMewTakPrm5rXEsc,5639 +httpx/_types.py,sha256=Jyh41GQq7AOev8IOWKDAg7zCbvHAfufmW5g_PiTtErY,2965 +httpx/_urlparse.py,sha256=ZAmH47ONfkxrrj-PPYhGeiHjb6AjKCS-ANWIN4OL_KY,18546 +httpx/_urls.py,sha256=dX99VR1DSOHpgo9Aq7PzYO4FKdxqKjwyNp8grf8dHN0,21550 +httpx/_utils.py,sha256=_TVeqAKvxJkKHdz7dFeb4s0LZqQXgeFkXSgfiHBK_1o,8285 +httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpx/_transports/__init__.py,sha256=GbUoBSAOp7z-l-9j5YhMhR3DMIcn6FVLhj072O3Nnno,275 +httpx/_transports/asgi.py,sha256=HRfiDYMPt4wQH2gFgHZg4c-i3sblo6bL5GTqcET-xz8,5501 +httpx/_transports/base.py,sha256=kZS_VMbViYfF570pogUCJ1bulz-ybfL51Pqs9yktebU,2523 +httpx/_transports/default.py,sha256=AzeaRUyVwCccTyyNJexDf0n1dFfzzydpdIQgvw7PLnk,13983 +httpx/_transports/mock.py,sha256=PTo0d567RITXxGrki6kN7_67wwAxfwiMDcuXJiZCjEo,1232 +httpx/_transports/wsgi.py,sha256=NcPX3Xap_EwCFZWO_OaSyQNuInCYx1QMNbO8GAei6jY,4825 +httpx-0.28.1.dist-info/METADATA,sha256=_rubD48-gNV8gZnDBPNcQzboWB0dGNeYPJJ2a4J5OyU,7052 +httpx-0.28.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87 +httpx-0.28.1.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37 +httpx-0.28.1.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508 +httpx-0.28.1.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL new file mode 100644 index 0000000..21aaa72 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.26.3 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt new file mode 100644 index 0000000..8ae9600 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +httpx = httpx:main diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..ab79d16 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md @@ -0,0 +1,12 @@ +Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py new file mode 100644 index 0000000..e9addde --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__init__.py @@ -0,0 +1,105 @@ +from .__version__ import __description__, __title__, __version__ +from ._api import * +from ._auth import * +from ._client import * +from ._config import * +from ._content import * +from ._exceptions import * +from ._models import * +from ._status_codes import * +from ._transports import * +from ._types import * +from ._urls import * + +try: + from ._main import main +except ImportError: # pragma: no cover + + def main() -> None: # type: ignore + import sys + + print( + "The httpx command line client could not run because the required " + "dependencies were not installed.\nMake sure you've installed " + "everything with: pip install 'httpx[cli]'" + ) + sys.exit(1) + + +__all__ = [ + "__description__", + "__title__", + "__version__", + "ASGITransport", + "AsyncBaseTransport", + "AsyncByteStream", + "AsyncClient", + "AsyncHTTPTransport", + "Auth", + "BaseTransport", + "BasicAuth", + "ByteStream", + "Client", + "CloseError", + "codes", + "ConnectError", + "ConnectTimeout", + "CookieConflict", + "Cookies", + "create_ssl_context", + "DecodingError", + "delete", + "DigestAuth", + "get", + "head", + "Headers", + "HTTPError", + "HTTPStatusError", + "HTTPTransport", + "InvalidURL", + "Limits", + "LocalProtocolError", + "main", + "MockTransport", + "NetRCAuth", + "NetworkError", + "options", + "patch", + "PoolTimeout", + "post", + "ProtocolError", + "Proxy", + "ProxyError", + "put", + "QueryParams", + "ReadError", + "ReadTimeout", + "RemoteProtocolError", + "request", + "Request", + "RequestError", + "RequestNotRead", + "Response", + "ResponseNotRead", + "stream", + "StreamClosed", + "StreamConsumed", + "StreamError", + "SyncByteStream", + "Timeout", + "TimeoutException", + "TooManyRedirects", + "TransportError", + "UnsupportedProtocol", + "URL", + "USE_CLIENT_DEFAULT", + "WriteError", + "WriteTimeout", + "WSGITransport", +] + + +__locals = locals() +for __name in __all__: + if not __name.startswith("__"): + setattr(__locals[__name], "__module__", "httpx") # noqa diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py new file mode 100644 index 0000000..801bfac --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/__version__.py @@ -0,0 +1,3 @@ +__title__ = "httpx" +__description__ = "A next generation HTTP client, for Python 3." +__version__ = "0.28.1" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py new file mode 100644 index 0000000..c3cda1e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_api.py @@ -0,0 +1,438 @@ +from __future__ import annotations + +import typing +from contextlib import contextmanager + +from ._client import Client +from ._config import DEFAULT_TIMEOUT_CONFIG +from ._models import Response +from ._types import ( + AuthTypes, + CookieTypes, + HeaderTypes, + ProxyTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestFiles, + TimeoutTypes, +) +from ._urls import URL + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + + +__all__ = [ + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "request", + "stream", +] + + +def request( + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> Response: + """ + Sends an HTTP request. + + **Parameters:** + + * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`, + `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`. + * **url** - URL for the new `Request` object. + * **params** - *(optional)* Query parameters to include in the URL, as a + string, dictionary, or sequence of two-tuples. + * **content** - *(optional)* Binary content to include in the body of the + request, as bytes or a byte iterator. + * **data** - *(optional)* Form data to include in the body of the request, + as a dictionary. + * **files** - *(optional)* A dictionary of upload files to include in the + body of the request. + * **json** - *(optional)* A JSON serializable object to include in the body + of the request. + * **headers** - *(optional)* Dictionary of HTTP headers to include in the + request. + * **cookies** - *(optional)* Dictionary of Cookie items to include in the + request. + * **auth** - *(optional)* An authentication class to use when sending the + request. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + the request. + * **follow_redirects** - *(optional)* Enables or disables HTTP redirects. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + + **Returns:** `Response` + + Usage: + + ``` + >>> import httpx + >>> response = httpx.request('GET', 'https://httpbin.org/get') + >>> response + + ``` + """ + with Client( + cookies=cookies, + proxy=proxy, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) as client: + return client.request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + auth=auth, + follow_redirects=follow_redirects, + ) + + +@contextmanager +def stream( + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> typing.Iterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + with Client( + cookies=cookies, + proxy=proxy, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) as client: + with client.stream( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + auth=auth, + follow_redirects=follow_redirects, + ) as response: + yield response + + +def get( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `GET` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `GET` requests should not include a request body. + """ + return request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def options( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `OPTIONS` requests should not include a request body. + """ + return request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def head( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `HEAD` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `HEAD` requests should not include a request body. + """ + return request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def post( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def put( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def patch( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def delete( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> Response: + """ + Sends a `DELETE` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `DELETE` requests should not include a request body. + """ + return request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py new file mode 100644 index 0000000..b03971a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_auth.py @@ -0,0 +1,348 @@ +from __future__ import annotations + +import hashlib +import os +import re +import time +import typing +from base64 import b64encode +from urllib.request import parse_http_list + +from ._exceptions import ProtocolError +from ._models import Cookies, Request, Response +from ._utils import to_bytes, to_str, unquote + +if typing.TYPE_CHECKING: # pragma: no cover + from hashlib import _Hash + + +__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"] + + +class Auth: + """ + Base class for all authentication schemes. + + To implement a custom authentication scheme, subclass `Auth` and override + the `.auth_flow()` method. + + If the authentication scheme does I/O such as disk access or network calls, or uses + synchronization primitives such as locks, you should override `.sync_auth_flow()` + and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized + implementations that will be used by `Client` and `AsyncClient` respectively. + """ + + requires_request_body = False + requires_response_body = False + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + """ + Execute the authentication flow. + + To dispatch a request, `yield` it: + + ``` + yield request + ``` + + The client will `.send()` the response back into the flow generator. You can + access it like so: + + ``` + response = yield request + ``` + + A `return` (or reaching the end of the generator) will result in the + client returning the last response obtained from the server. + + You can dispatch as many requests as is necessary. + """ + yield request + + def sync_auth_flow( + self, request: Request + ) -> typing.Generator[Request, Response, None]: + """ + Execute the authentication flow synchronously. + + By default, this defers to `.auth_flow()`. You should override this method + when the authentication scheme does I/O and/or uses concurrency primitives. + """ + if self.requires_request_body: + request.read() + + flow = self.auth_flow(request) + request = next(flow) + + while True: + response = yield request + if self.requires_response_body: + response.read() + + try: + request = flow.send(response) + except StopIteration: + break + + async def async_auth_flow( + self, request: Request + ) -> typing.AsyncGenerator[Request, Response]: + """ + Execute the authentication flow asynchronously. + + By default, this defers to `.auth_flow()`. You should override this method + when the authentication scheme does I/O and/or uses concurrency primitives. + """ + if self.requires_request_body: + await request.aread() + + flow = self.auth_flow(request) + request = next(flow) + + while True: + response = yield request + if self.requires_response_body: + await response.aread() + + try: + request = flow.send(response) + except StopIteration: + break + + +class FunctionAuth(Auth): + """ + Allows the 'auth' argument to be passed as a simple callable function, + that takes the request, and returns a new, modified request. + """ + + def __init__(self, func: typing.Callable[[Request], Request]) -> None: + self._func = func + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + yield self._func(request) + + +class BasicAuth(Auth): + """ + Allows the 'auth' argument to be passed as a (username, password) pair, + and uses HTTP Basic authentication. + """ + + def __init__(self, username: str | bytes, password: str | bytes) -> None: + self._auth_header = self._build_auth_header(username, password) + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + request.headers["Authorization"] = self._auth_header + yield request + + def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str: + userpass = b":".join((to_bytes(username), to_bytes(password))) + token = b64encode(userpass).decode() + return f"Basic {token}" + + +class NetRCAuth(Auth): + """ + Use a 'netrc' file to lookup basic auth credentials based on the url host. + """ + + def __init__(self, file: str | None = None) -> None: + # Lazily import 'netrc'. + # There's no need for us to load this module unless 'NetRCAuth' is being used. + import netrc + + self._netrc_info = netrc.netrc(file) + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + auth_info = self._netrc_info.authenticators(request.url.host) + if auth_info is None or not auth_info[2]: + # The netrc file did not have authentication credentials for this host. + yield request + else: + # Build a basic auth header with credentials from the netrc file. + request.headers["Authorization"] = self._build_auth_header( + username=auth_info[0], password=auth_info[2] + ) + yield request + + def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str: + userpass = b":".join((to_bytes(username), to_bytes(password))) + token = b64encode(userpass).decode() + return f"Basic {token}" + + +class DigestAuth(Auth): + _ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = { + "MD5": hashlib.md5, + "MD5-SESS": hashlib.md5, + "SHA": hashlib.sha1, + "SHA-SESS": hashlib.sha1, + "SHA-256": hashlib.sha256, + "SHA-256-SESS": hashlib.sha256, + "SHA-512": hashlib.sha512, + "SHA-512-SESS": hashlib.sha512, + } + + def __init__(self, username: str | bytes, password: str | bytes) -> None: + self._username = to_bytes(username) + self._password = to_bytes(password) + self._last_challenge: _DigestAuthChallenge | None = None + self._nonce_count = 1 + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + if self._last_challenge: + request.headers["Authorization"] = self._build_auth_header( + request, self._last_challenge + ) + + response = yield request + + if response.status_code != 401 or "www-authenticate" not in response.headers: + # If the response is not a 401 then we don't + # need to build an authenticated request. + return + + for auth_header in response.headers.get_list("www-authenticate"): + if auth_header.lower().startswith("digest "): + break + else: + # If the response does not include a 'WWW-Authenticate: Digest ...' + # header, then we don't need to build an authenticated request. + return + + self._last_challenge = self._parse_challenge(request, response, auth_header) + self._nonce_count = 1 + + request.headers["Authorization"] = self._build_auth_header( + request, self._last_challenge + ) + if response.cookies: + Cookies(response.cookies).set_cookie_header(request=request) + yield request + + def _parse_challenge( + self, request: Request, response: Response, auth_header: str + ) -> _DigestAuthChallenge: + """ + Returns a challenge from a Digest WWW-Authenticate header. + These take the form of: + `Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"` + """ + scheme, _, fields = auth_header.partition(" ") + + # This method should only ever have been called with a Digest auth header. + assert scheme.lower() == "digest" + + header_dict: dict[str, str] = {} + for field in parse_http_list(fields): + key, value = field.strip().split("=", 1) + header_dict[key] = unquote(value) + + try: + realm = header_dict["realm"].encode() + nonce = header_dict["nonce"].encode() + algorithm = header_dict.get("algorithm", "MD5") + opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None + qop = header_dict["qop"].encode() if "qop" in header_dict else None + return _DigestAuthChallenge( + realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop + ) + except KeyError as exc: + message = "Malformed Digest WWW-Authenticate header" + raise ProtocolError(message, request=request) from exc + + def _build_auth_header( + self, request: Request, challenge: _DigestAuthChallenge + ) -> str: + hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()] + + def digest(data: bytes) -> bytes: + return hash_func(data).hexdigest().encode() + + A1 = b":".join((self._username, challenge.realm, self._password)) + + path = request.url.raw_path + A2 = b":".join((request.method.encode(), path)) + # TODO: implement auth-int + HA2 = digest(A2) + + nc_value = b"%08x" % self._nonce_count + cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce) + self._nonce_count += 1 + + HA1 = digest(A1) + if challenge.algorithm.lower().endswith("-sess"): + HA1 = digest(b":".join((HA1, challenge.nonce, cnonce))) + + qop = self._resolve_qop(challenge.qop, request=request) + if qop is None: + # Following RFC 2069 + digest_data = [HA1, challenge.nonce, HA2] + else: + # Following RFC 2617/7616 + digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2] + + format_args = { + "username": self._username, + "realm": challenge.realm, + "nonce": challenge.nonce, + "uri": path, + "response": digest(b":".join(digest_data)), + "algorithm": challenge.algorithm.encode(), + } + if challenge.opaque: + format_args["opaque"] = challenge.opaque + if qop: + format_args["qop"] = b"auth" + format_args["nc"] = nc_value + format_args["cnonce"] = cnonce + + return "Digest " + self._get_header_value(format_args) + + def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes: + s = str(nonce_count).encode() + s += nonce + s += time.ctime().encode() + s += os.urandom(8) + + return hashlib.sha1(s).hexdigest()[:16].encode() + + def _get_header_value(self, header_fields: dict[str, bytes]) -> str: + NON_QUOTED_FIELDS = ("algorithm", "qop", "nc") + QUOTED_TEMPLATE = '{}="{}"' + NON_QUOTED_TEMPLATE = "{}={}" + + header_value = "" + for i, (field, value) in enumerate(header_fields.items()): + if i > 0: + header_value += ", " + template = ( + QUOTED_TEMPLATE + if field not in NON_QUOTED_FIELDS + else NON_QUOTED_TEMPLATE + ) + header_value += template.format(field, to_str(value)) + + return header_value + + def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None: + if qop is None: + return None + qops = re.split(b", ?", qop) + if b"auth" in qops: + return b"auth" + + if qops == [b"auth-int"]: + raise NotImplementedError("Digest auth-int support is not yet implemented") + + message = f'Unexpected qop value "{qop!r}" in digest auth' + raise ProtocolError(message, request=request) + + +class _DigestAuthChallenge(typing.NamedTuple): + realm: bytes + nonce: bytes + algorithm: str + opaque: bytes | None + qop: bytes | None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py new file mode 100644 index 0000000..2249231 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_client.py @@ -0,0 +1,2019 @@ +from __future__ import annotations + +import datetime +import enum +import logging +import time +import typing +import warnings +from contextlib import asynccontextmanager, contextmanager +from types import TracebackType + +from .__version__ import __version__ +from ._auth import Auth, BasicAuth, FunctionAuth +from ._config import ( + DEFAULT_LIMITS, + DEFAULT_MAX_REDIRECTS, + DEFAULT_TIMEOUT_CONFIG, + Limits, + Proxy, + Timeout, +) +from ._decoders import SUPPORTED_DECODERS +from ._exceptions import ( + InvalidURL, + RemoteProtocolError, + TooManyRedirects, + request_context, +) +from ._models import Cookies, Headers, Request, Response +from ._status_codes import codes +from ._transports.base import AsyncBaseTransport, BaseTransport +from ._transports.default import AsyncHTTPTransport, HTTPTransport +from ._types import ( + AsyncByteStream, + AuthTypes, + CertTypes, + CookieTypes, + HeaderTypes, + ProxyTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestExtensions, + RequestFiles, + SyncByteStream, + TimeoutTypes, +) +from ._urls import URL, QueryParams +from ._utils import URLPattern, get_environment_proxies + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + +__all__ = ["USE_CLIENT_DEFAULT", "AsyncClient", "Client"] + +# The type annotation for @classmethod and context managers here follows PEP 484 +# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods +T = typing.TypeVar("T", bound="Client") +U = typing.TypeVar("U", bound="AsyncClient") + + +def _is_https_redirect(url: URL, location: URL) -> bool: + """ + Return 'True' if 'location' is a HTTPS upgrade of 'url' + """ + if url.host != location.host: + return False + + return ( + url.scheme == "http" + and _port_or_default(url) == 80 + and location.scheme == "https" + and _port_or_default(location) == 443 + ) + + +def _port_or_default(url: URL) -> int | None: + if url.port is not None: + return url.port + return {"http": 80, "https": 443}.get(url.scheme) + + +def _same_origin(url: URL, other: URL) -> bool: + """ + Return 'True' if the given URLs share the same origin. + """ + return ( + url.scheme == other.scheme + and url.host == other.host + and _port_or_default(url) == _port_or_default(other) + ) + + +class UseClientDefault: + """ + For some parameters such as `auth=...` and `timeout=...` we need to be able + to indicate the default "unset" state, in a way that is distinctly different + to using `None`. + + The default "unset" state indicates that whatever default is set on the + client should be used. This is different to setting `None`, which + explicitly disables the parameter, possibly overriding a client default. + + For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature. + Omitting the `timeout` parameter will send a request using whatever default + timeout has been configured on the client. Including `timeout=None` will + ensure no timeout is used. + + Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant, + but it is used internally when a parameter is not included. + """ + + +USE_CLIENT_DEFAULT = UseClientDefault() + + +logger = logging.getLogger("httpx") + +USER_AGENT = f"python-httpx/{__version__}" +ACCEPT_ENCODING = ", ".join( + [key for key in SUPPORTED_DECODERS.keys() if key != "identity"] +) + + +class ClientState(enum.Enum): + # UNOPENED: + # The client has been instantiated, but has not been used to send a request, + # or been opened by entering the context of a `with` block. + UNOPENED = 1 + # OPENED: + # The client has either sent a request, or is within a `with` block. + OPENED = 2 + # CLOSED: + # The client has either exited the `with` block, or `close()` has + # been called explicitly. + CLOSED = 3 + + +class BoundSyncStream(SyncByteStream): + """ + A byte stream that is bound to a given response instance, and that + ensures the `response.elapsed` is set once the response is closed. + """ + + def __init__( + self, stream: SyncByteStream, response: Response, start: float + ) -> None: + self._stream = stream + self._response = response + self._start = start + + def __iter__(self) -> typing.Iterator[bytes]: + for chunk in self._stream: + yield chunk + + def close(self) -> None: + elapsed = time.perf_counter() - self._start + self._response.elapsed = datetime.timedelta(seconds=elapsed) + self._stream.close() + + +class BoundAsyncStream(AsyncByteStream): + """ + An async byte stream that is bound to a given response instance, and that + ensures the `response.elapsed` is set once the response is closed. + """ + + def __init__( + self, stream: AsyncByteStream, response: Response, start: float + ) -> None: + self._stream = stream + self._response = response + self._start = start + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + async for chunk in self._stream: + yield chunk + + async def aclose(self) -> None: + elapsed = time.perf_counter() - self._start + self._response.elapsed = datetime.timedelta(seconds=elapsed) + await self._stream.aclose() + + +EventHook = typing.Callable[..., typing.Any] + + +class BaseClient: + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + trust_env: bool = True, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + event_hooks = {} if event_hooks is None else event_hooks + + self._base_url = self._enforce_trailing_slash(URL(base_url)) + + self._auth = self._build_auth(auth) + self._params = QueryParams(params) + self.headers = Headers(headers) + self._cookies = Cookies(cookies) + self._timeout = Timeout(timeout) + self.follow_redirects = follow_redirects + self.max_redirects = max_redirects + self._event_hooks = { + "request": list(event_hooks.get("request", [])), + "response": list(event_hooks.get("response", [])), + } + self._trust_env = trust_env + self._default_encoding = default_encoding + self._state = ClientState.UNOPENED + + @property + def is_closed(self) -> bool: + """ + Check if the client being closed + """ + return self._state == ClientState.CLOSED + + @property + def trust_env(self) -> bool: + return self._trust_env + + def _enforce_trailing_slash(self, url: URL) -> URL: + if url.raw_path.endswith(b"/"): + return url + return url.copy_with(raw_path=url.raw_path + b"/") + + def _get_proxy_map( + self, proxy: ProxyTypes | None, allow_env_proxies: bool + ) -> dict[str, Proxy | None]: + if proxy is None: + if allow_env_proxies: + return { + key: None if url is None else Proxy(url=url) + for key, url in get_environment_proxies().items() + } + return {} + else: + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + return {"all://": proxy} + + @property + def timeout(self) -> Timeout: + return self._timeout + + @timeout.setter + def timeout(self, timeout: TimeoutTypes) -> None: + self._timeout = Timeout(timeout) + + @property + def event_hooks(self) -> dict[str, list[EventHook]]: + return self._event_hooks + + @event_hooks.setter + def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None: + self._event_hooks = { + "request": list(event_hooks.get("request", [])), + "response": list(event_hooks.get("response", [])), + } + + @property + def auth(self) -> Auth | None: + """ + Authentication class used when none is passed at the request-level. + + See also [Authentication][0]. + + [0]: /quickstart/#authentication + """ + return self._auth + + @auth.setter + def auth(self, auth: AuthTypes) -> None: + self._auth = self._build_auth(auth) + + @property + def base_url(self) -> URL: + """ + Base URL to use when sending requests with relative URLs. + """ + return self._base_url + + @base_url.setter + def base_url(self, url: URL | str) -> None: + self._base_url = self._enforce_trailing_slash(URL(url)) + + @property + def headers(self) -> Headers: + """ + HTTP headers to include when sending requests. + """ + return self._headers + + @headers.setter + def headers(self, headers: HeaderTypes) -> None: + client_headers = Headers( + { + b"Accept": b"*/*", + b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"), + b"Connection": b"keep-alive", + b"User-Agent": USER_AGENT.encode("ascii"), + } + ) + client_headers.update(headers) + self._headers = client_headers + + @property + def cookies(self) -> Cookies: + """ + Cookie values to include when sending requests. + """ + return self._cookies + + @cookies.setter + def cookies(self, cookies: CookieTypes) -> None: + self._cookies = Cookies(cookies) + + @property + def params(self) -> QueryParams: + """ + Query parameters to include in the URL when sending requests. + """ + return self._params + + @params.setter + def params(self, params: QueryParamTypes) -> None: + self._params = QueryParams(params) + + def build_request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Request: + """ + Build and return a request instance. + + * The `params`, `headers` and `cookies` arguments + are merged with any values set on the client. + * The `url` argument is merged with any `base_url` set on the client. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + url = self._merge_url(url) + headers = self._merge_headers(headers) + cookies = self._merge_cookies(cookies) + params = self._merge_queryparams(params) + extensions = {} if extensions is None else extensions + if "timeout" not in extensions: + timeout = ( + self.timeout + if isinstance(timeout, UseClientDefault) + else Timeout(timeout) + ) + extensions = dict(**extensions, timeout=timeout.as_dict()) + return Request( + method, + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + extensions=extensions, + ) + + def _merge_url(self, url: URL | str) -> URL: + """ + Merge a URL argument together with any 'base_url' on the client, + to create the URL used for the outgoing request. + """ + merge_url = URL(url) + if merge_url.is_relative_url: + # To merge URLs we always append to the base URL. To get this + # behaviour correct we always ensure the base URL ends in a '/' + # separator, and strip any leading '/' from the merge URL. + # + # So, eg... + # + # >>> client = Client(base_url="https://www.example.com/subpath") + # >>> client.base_url + # URL('https://www.example.com/subpath/') + # >>> client.build_request("GET", "/path").url + # URL('https://www.example.com/subpath/path') + merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") + return self.base_url.copy_with(raw_path=merge_raw_path) + return merge_url + + def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None: + """ + Merge a cookies argument together with any cookies on the client, + to create the cookies used for the outgoing request. + """ + if cookies or self.cookies: + merged_cookies = Cookies(self.cookies) + merged_cookies.update(cookies) + return merged_cookies + return cookies + + def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None: + """ + Merge a headers argument together with any headers on the client, + to create the headers used for the outgoing request. + """ + merged_headers = Headers(self.headers) + merged_headers.update(headers) + return merged_headers + + def _merge_queryparams( + self, params: QueryParamTypes | None = None + ) -> QueryParamTypes | None: + """ + Merge a queryparams argument together with any queryparams on the client, + to create the queryparams used for the outgoing request. + """ + if params or self.params: + merged_queryparams = QueryParams(self.params) + return merged_queryparams.merge(params) + return params + + def _build_auth(self, auth: AuthTypes | None) -> Auth | None: + if auth is None: + return None + elif isinstance(auth, tuple): + return BasicAuth(username=auth[0], password=auth[1]) + elif isinstance(auth, Auth): + return auth + elif callable(auth): + return FunctionAuth(func=auth) + else: + raise TypeError(f'Invalid "auth" argument: {auth!r}') + + def _build_request_auth( + self, + request: Request, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + ) -> Auth: + auth = ( + self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth) + ) + + if auth is not None: + return auth + + username, password = request.url.username, request.url.password + if username or password: + return BasicAuth(username=username, password=password) + + return Auth() + + def _build_redirect_request(self, request: Request, response: Response) -> Request: + """ + Given a request and a redirect response, return a new request that + should be used to effect the redirect. + """ + method = self._redirect_method(request, response) + url = self._redirect_url(request, response) + headers = self._redirect_headers(request, url, method) + stream = self._redirect_stream(request, method) + cookies = Cookies(self.cookies) + return Request( + method=method, + url=url, + headers=headers, + cookies=cookies, + stream=stream, + extensions=request.extensions, + ) + + def _redirect_method(self, request: Request, response: Response) -> str: + """ + When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.SEE_OTHER and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # Turn 302s into GETs. + if response.status_code == codes.FOUND and method != "HEAD": + method = "GET" + + # If a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in 'requests' issue 1704. + if response.status_code == codes.MOVED_PERMANENTLY and method == "POST": + method = "GET" + + return method + + def _redirect_url(self, request: Request, response: Response) -> URL: + """ + Return the URL for the redirect to follow. + """ + location = response.headers["Location"] + + try: + url = URL(location) + except InvalidURL as exc: + raise RemoteProtocolError( + f"Invalid URL in location header: {exc}.", request=request + ) from None + + # Handle malformed 'Location' headers that are "absolute" form, have no host. + # See: https://github.com/encode/httpx/issues/771 + if url.scheme and not url.host: + url = url.copy_with(host=request.url.host) + + # Facilitate relative 'Location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + if url.is_relative_url: + url = request.url.join(url) + + # Attach previous fragment if needed (RFC 7231 7.1.2) + if request.url.fragment and not url.fragment: + url = url.copy_with(fragment=request.url.fragment) + + return url + + def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers: + """ + Return the headers that should be used for the redirect request. + """ + headers = Headers(request.headers) + + if not _same_origin(url, request.url): + if not _is_https_redirect(request.url, url): + # Strip Authorization headers when responses are redirected + # away from the origin. (Except for direct HTTP to HTTPS redirects.) + headers.pop("Authorization", None) + + # Update the Host header. + headers["Host"] = url.netloc.decode("ascii") + + if method != request.method and method == "GET": + # If we've switch to a 'GET' request, then strip any headers which + # are only relevant to the request body. + headers.pop("Content-Length", None) + headers.pop("Transfer-Encoding", None) + + # We should use the client cookie store to determine any cookie header, + # rather than whatever was on the original outgoing request. + headers.pop("Cookie", None) + + return headers + + def _redirect_stream( + self, request: Request, method: str + ) -> SyncByteStream | AsyncByteStream | None: + """ + Return the body that should be used for the redirect request. + """ + if method != request.method and method == "GET": + return None + + return request.stream + + def _set_timeout(self, request: Request) -> None: + if "timeout" not in request.extensions: + timeout = ( + self.timeout + if isinstance(self.timeout, UseClientDefault) + else Timeout(self.timeout) + ) + request.extensions = dict(**request.extensions, timeout=timeout.as_dict()) + + +class Client(BaseClient): + """ + An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc. + + It can be shared between threads. + + Usage: + + ```python + >>> client = httpx.Client() + >>> response = client.get('https://example.org') + ``` + + **Parameters:** + + * **auth** - *(optional)* An authentication class to use when sending + requests. + * **params** - *(optional)* Query parameters to include in request URLs, as + a string, dictionary, or sequence of two-tuples. + * **headers** - *(optional)* Dictionary of HTTP headers to include when + sending requests. + * **cookies** - *(optional)* Dictionary of Cookie items to include when + sending requests. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + requests. + * **limits** - *(optional)* The limits configuration to use. + * **max_redirects** - *(optional)* The maximum number of redirect responses + that should be followed. + * **base_url** - *(optional)* A URL to use as the base when building + request URLs. + * **transport** - *(optional)* A transport class to use for sending requests + over the network. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + * **default_encoding** - *(optional)* The default encoding to use for decoding + response text, if no charset information is included in a response Content-Type + header. Set to a callable for automatic character set detection. Default: "utf-8". + """ + + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + proxy: ProxyTypes | None = None, + mounts: None | (typing.Mapping[str, BaseTransport | None]) = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + limits: Limits = DEFAULT_LIMITS, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + transport: BaseTransport | None = None, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + super().__init__( + auth=auth, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + follow_redirects=follow_redirects, + max_redirects=max_redirects, + event_hooks=event_hooks, + base_url=base_url, + trust_env=trust_env, + default_encoding=default_encoding, + ) + + if http2: + try: + import h2 # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using http2=True, but the 'h2' package is not installed. " + "Make sure to install httpx using `pip install httpx[http2]`." + ) from None + + allow_env_proxies = trust_env and transport is None + proxy_map = self._get_proxy_map(proxy, allow_env_proxies) + + self._transport = self._init_transport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + transport=transport, + ) + self._mounts: dict[URLPattern, BaseTransport | None] = { + URLPattern(key): None + if proxy is None + else self._init_proxy_transport( + proxy, + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + for key, proxy in proxy_map.items() + } + if mounts is not None: + self._mounts.update( + {URLPattern(key): transport for key, transport in mounts.items()} + ) + + self._mounts = dict(sorted(self._mounts.items())) + + def _init_transport( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + transport: BaseTransport | None = None, + ) -> BaseTransport: + if transport is not None: + return transport + + return HTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + + def _init_proxy_transport( + self, + proxy: Proxy, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + ) -> BaseTransport: + return HTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + proxy=proxy, + ) + + def _transport_for_url(self, url: URL) -> BaseTransport: + """ + Returns the transport instance that should be used for a given URL. + This will either be the standard connection pool, or a proxy. + """ + for pattern, transport in self._mounts.items(): + if pattern.matches(url): + return self._transport if transport is None else transport + + return self._transport + + def request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Build and send a request. + + Equivalent to: + + ```python + request = client.build_request(...) + response = client.send(request, ...) + ``` + + See `Client.build_request()`, `Client.send()` and + [Merging of configuration][0] for how the various parameters + are merged with client-level configuration. + + [0]: /advanced/clients/#merging-of-configuration + """ + if cookies is not None: + message = ( + "Setting per-request cookies=<...> is being deprecated, because " + "the expected behaviour on cookie persistence is ambiguous. Set " + "cookies directly on the client instance instead." + ) + warnings.warn(message, DeprecationWarning, stacklevel=2) + + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + return self.send(request, auth=auth, follow_redirects=follow_redirects) + + @contextmanager + def stream( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> typing.Iterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + response = self.send( + request=request, + auth=auth, + follow_redirects=follow_redirects, + stream=True, + ) + try: + yield response + finally: + response.close() + + def send( + self, + request: Request, + *, + stream: bool = False, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> Response: + """ + Send a request. + + The request is sent as-is, unmodified. + + Typically you'll want to build one with `Client.build_request()` + so that any client-level configuration is merged into the request, + but passing an explicit `httpx.Request()` is supported as well. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + if self._state == ClientState.CLOSED: + raise RuntimeError("Cannot send a request, as the client has been closed.") + + self._state = ClientState.OPENED + follow_redirects = ( + self.follow_redirects + if isinstance(follow_redirects, UseClientDefault) + else follow_redirects + ) + + self._set_timeout(request) + + auth = self._build_request_auth(request, auth) + + response = self._send_handling_auth( + request, + auth=auth, + follow_redirects=follow_redirects, + history=[], + ) + try: + if not stream: + response.read() + + return response + + except BaseException as exc: + response.close() + raise exc + + def _send_handling_auth( + self, + request: Request, + auth: Auth, + follow_redirects: bool, + history: list[Response], + ) -> Response: + auth_flow = auth.sync_auth_flow(request) + try: + request = next(auth_flow) + + while True: + response = self._send_handling_redirects( + request, + follow_redirects=follow_redirects, + history=history, + ) + try: + try: + next_request = auth_flow.send(response) + except StopIteration: + return response + + response.history = list(history) + response.read() + request = next_request + history.append(response) + + except BaseException as exc: + response.close() + raise exc + finally: + auth_flow.close() + + def _send_handling_redirects( + self, + request: Request, + follow_redirects: bool, + history: list[Response], + ) -> Response: + while True: + if len(history) > self.max_redirects: + raise TooManyRedirects( + "Exceeded maximum allowed redirects.", request=request + ) + + for hook in self._event_hooks["request"]: + hook(request) + + response = self._send_single_request(request) + try: + for hook in self._event_hooks["response"]: + hook(response) + response.history = list(history) + + if not response.has_redirect_location: + return response + + request = self._build_redirect_request(request, response) + history = history + [response] + + if follow_redirects: + response.read() + else: + response.next_request = request + return response + + except BaseException as exc: + response.close() + raise exc + + def _send_single_request(self, request: Request) -> Response: + """ + Sends a single request, without handling any redirections. + """ + transport = self._transport_for_url(request.url) + start = time.perf_counter() + + if not isinstance(request.stream, SyncByteStream): + raise RuntimeError( + "Attempted to send an async request with a sync Client instance." + ) + + with request_context(request=request): + response = transport.handle_request(request) + + assert isinstance(response.stream, SyncByteStream) + + response.request = request + response.stream = BoundSyncStream( + response.stream, response=response, start=start + ) + self.cookies.extract_cookies(response) + response.default_encoding = self._default_encoding + + logger.info( + 'HTTP Request: %s %s "%s %d %s"', + request.method, + request.url, + response.http_version, + response.status_code, + response.reason_phrase, + ) + + return response + + def get( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `GET` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def options( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def head( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `HEAD` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def post( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def put( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def patch( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def delete( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `DELETE` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def close(self) -> None: + """ + Close transport and proxies. + """ + if self._state != ClientState.CLOSED: + self._state = ClientState.CLOSED + + self._transport.close() + for transport in self._mounts.values(): + if transport is not None: + transport.close() + + def __enter__(self: T) -> T: + if self._state != ClientState.UNOPENED: + msg = { + ClientState.OPENED: "Cannot open a client instance more than once.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), + }[self._state] + raise RuntimeError(msg) + + self._state = ClientState.OPENED + + self._transport.__enter__() + for transport in self._mounts.values(): + if transport is not None: + transport.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self._state = ClientState.CLOSED + + self._transport.__exit__(exc_type, exc_value, traceback) + for transport in self._mounts.values(): + if transport is not None: + transport.__exit__(exc_type, exc_value, traceback) + + +class AsyncClient(BaseClient): + """ + An asynchronous HTTP client, with connection pooling, HTTP/2, redirects, + cookie persistence, etc. + + It can be shared between tasks. + + Usage: + + ```python + >>> async with httpx.AsyncClient() as client: + >>> response = await client.get('https://example.org') + ``` + + **Parameters:** + + * **auth** - *(optional)* An authentication class to use when sending + requests. + * **params** - *(optional)* Query parameters to include in request URLs, as + a string, dictionary, or sequence of two-tuples. + * **headers** - *(optional)* Dictionary of HTTP headers to include when + sending requests. + * **cookies** - *(optional)* Dictionary of Cookie items to include when + sending requests. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + requests. + * **limits** - *(optional)* The limits configuration to use. + * **max_redirects** - *(optional)* The maximum number of redirect responses + that should be followed. + * **base_url** - *(optional)* A URL to use as the base when building + request URLs. + * **transport** - *(optional)* A transport class to use for sending requests + over the network. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + * **default_encoding** - *(optional)* The default encoding to use for decoding + response text, if no charset information is included in a response Content-Type + header. Set to a callable for automatic character set detection. Default: "utf-8". + """ + + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + http1: bool = True, + http2: bool = False, + proxy: ProxyTypes | None = None, + mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + limits: Limits = DEFAULT_LIMITS, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + transport: AsyncBaseTransport | None = None, + trust_env: bool = True, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + super().__init__( + auth=auth, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + follow_redirects=follow_redirects, + max_redirects=max_redirects, + event_hooks=event_hooks, + base_url=base_url, + trust_env=trust_env, + default_encoding=default_encoding, + ) + + if http2: + try: + import h2 # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using http2=True, but the 'h2' package is not installed. " + "Make sure to install httpx using `pip install httpx[http2]`." + ) from None + + allow_env_proxies = trust_env and transport is None + proxy_map = self._get_proxy_map(proxy, allow_env_proxies) + + self._transport = self._init_transport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + transport=transport, + ) + + self._mounts: dict[URLPattern, AsyncBaseTransport | None] = { + URLPattern(key): None + if proxy is None + else self._init_proxy_transport( + proxy, + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + for key, proxy in proxy_map.items() + } + if mounts is not None: + self._mounts.update( + {URLPattern(key): transport for key, transport in mounts.items()} + ) + self._mounts = dict(sorted(self._mounts.items())) + + def _init_transport( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + transport: AsyncBaseTransport | None = None, + ) -> AsyncBaseTransport: + if transport is not None: + return transport + + return AsyncHTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + + def _init_proxy_transport( + self, + proxy: Proxy, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + ) -> AsyncBaseTransport: + return AsyncHTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + proxy=proxy, + ) + + def _transport_for_url(self, url: URL) -> AsyncBaseTransport: + """ + Returns the transport instance that should be used for a given URL. + This will either be the standard connection pool, or a proxy. + """ + for pattern, transport in self._mounts.items(): + if pattern.matches(url): + return self._transport if transport is None else transport + + return self._transport + + async def request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Build and send a request. + + Equivalent to: + + ```python + request = client.build_request(...) + response = await client.send(request, ...) + ``` + + See `AsyncClient.build_request()`, `AsyncClient.send()` + and [Merging of configuration][0] for how the various parameters + are merged with client-level configuration. + + [0]: /advanced/clients/#merging-of-configuration + """ + + if cookies is not None: # pragma: no cover + message = ( + "Setting per-request cookies=<...> is being deprecated, because " + "the expected behaviour on cookie persistence is ambiguous. Set " + "cookies directly on the client instance instead." + ) + warnings.warn(message, DeprecationWarning, stacklevel=2) + + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + return await self.send(request, auth=auth, follow_redirects=follow_redirects) + + @asynccontextmanager + async def stream( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> typing.AsyncIterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + response = await self.send( + request=request, + auth=auth, + follow_redirects=follow_redirects, + stream=True, + ) + try: + yield response + finally: + await response.aclose() + + async def send( + self, + request: Request, + *, + stream: bool = False, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> Response: + """ + Send a request. + + The request is sent as-is, unmodified. + + Typically you'll want to build one with `AsyncClient.build_request()` + so that any client-level configuration is merged into the request, + but passing an explicit `httpx.Request()` is supported as well. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + if self._state == ClientState.CLOSED: + raise RuntimeError("Cannot send a request, as the client has been closed.") + + self._state = ClientState.OPENED + follow_redirects = ( + self.follow_redirects + if isinstance(follow_redirects, UseClientDefault) + else follow_redirects + ) + + self._set_timeout(request) + + auth = self._build_request_auth(request, auth) + + response = await self._send_handling_auth( + request, + auth=auth, + follow_redirects=follow_redirects, + history=[], + ) + try: + if not stream: + await response.aread() + + return response + + except BaseException as exc: + await response.aclose() + raise exc + + async def _send_handling_auth( + self, + request: Request, + auth: Auth, + follow_redirects: bool, + history: list[Response], + ) -> Response: + auth_flow = auth.async_auth_flow(request) + try: + request = await auth_flow.__anext__() + + while True: + response = await self._send_handling_redirects( + request, + follow_redirects=follow_redirects, + history=history, + ) + try: + try: + next_request = await auth_flow.asend(response) + except StopAsyncIteration: + return response + + response.history = list(history) + await response.aread() + request = next_request + history.append(response) + + except BaseException as exc: + await response.aclose() + raise exc + finally: + await auth_flow.aclose() + + async def _send_handling_redirects( + self, + request: Request, + follow_redirects: bool, + history: list[Response], + ) -> Response: + while True: + if len(history) > self.max_redirects: + raise TooManyRedirects( + "Exceeded maximum allowed redirects.", request=request + ) + + for hook in self._event_hooks["request"]: + await hook(request) + + response = await self._send_single_request(request) + try: + for hook in self._event_hooks["response"]: + await hook(response) + + response.history = list(history) + + if not response.has_redirect_location: + return response + + request = self._build_redirect_request(request, response) + history = history + [response] + + if follow_redirects: + await response.aread() + else: + response.next_request = request + return response + + except BaseException as exc: + await response.aclose() + raise exc + + async def _send_single_request(self, request: Request) -> Response: + """ + Sends a single request, without handling any redirections. + """ + transport = self._transport_for_url(request.url) + start = time.perf_counter() + + if not isinstance(request.stream, AsyncByteStream): + raise RuntimeError( + "Attempted to send an sync request with an AsyncClient instance." + ) + + with request_context(request=request): + response = await transport.handle_async_request(request) + + assert isinstance(response.stream, AsyncByteStream) + response.request = request + response.stream = BoundAsyncStream( + response.stream, response=response, start=start + ) + self.cookies.extract_cookies(response) + response.default_encoding = self._default_encoding + + logger.info( + 'HTTP Request: %s %s "%s %d %s"', + request.method, + request.url, + response.http_version, + response.status_code, + response.reason_phrase, + ) + + return response + + async def get( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `GET` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def options( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def head( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `HEAD` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def post( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def put( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def patch( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def delete( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `DELETE` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def aclose(self) -> None: + """ + Close transport and proxies. + """ + if self._state != ClientState.CLOSED: + self._state = ClientState.CLOSED + + await self._transport.aclose() + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.aclose() + + async def __aenter__(self: U) -> U: + if self._state != ClientState.UNOPENED: + msg = { + ClientState.OPENED: "Cannot open a client instance more than once.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), + }[self._state] + raise RuntimeError(msg) + + self._state = ClientState.OPENED + + await self._transport.__aenter__() + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self._state = ClientState.CLOSED + + await self._transport.__aexit__(exc_type, exc_value, traceback) + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.__aexit__(exc_type, exc_value, traceback) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py new file mode 100644 index 0000000..467a6c9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_config.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +import os +import typing + +from ._models import Headers +from ._types import CertTypes, HeaderTypes, TimeoutTypes +from ._urls import URL + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + +__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"] + + +class UnsetType: + pass # pragma: no cover + + +UNSET = UnsetType() + + +def create_ssl_context( + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, +) -> ssl.SSLContext: + import ssl + import warnings + + import certifi + + if verify is True: + if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover + ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"]) + elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover + ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"]) + else: + # Default case... + ctx = ssl.create_default_context(cafile=certifi.where()) + elif verify is False: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + elif isinstance(verify, str): # pragma: nocover + message = ( + "`verify=` is deprecated. " + "Use `verify=ssl.create_default_context(cafile=...)` " + "or `verify=ssl.create_default_context(capath=...)` instead." + ) + warnings.warn(message, DeprecationWarning) + if os.path.isdir(verify): + return ssl.create_default_context(capath=verify) + return ssl.create_default_context(cafile=verify) + else: + ctx = verify + + if cert: # pragma: nocover + message = ( + "`cert=...` is deprecated. Use `verify=` instead," + "with `.load_cert_chain()` to configure the certificate chain." + ) + warnings.warn(message, DeprecationWarning) + if isinstance(cert, str): + ctx.load_cert_chain(cert) + else: + ctx.load_cert_chain(*cert) + + return ctx + + +class Timeout: + """ + Timeout configuration. + + **Usage**: + + Timeout(None) # No timeouts. + Timeout(5.0) # 5s timeout on all operations. + Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts. + Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere. + Timeout(5.0, pool=None) # No timeout on acquiring connection from pool. + # 5s timeout elsewhere. + """ + + def __init__( + self, + timeout: TimeoutTypes | UnsetType = UNSET, + *, + connect: None | float | UnsetType = UNSET, + read: None | float | UnsetType = UNSET, + write: None | float | UnsetType = UNSET, + pool: None | float | UnsetType = UNSET, + ) -> None: + if isinstance(timeout, Timeout): + # Passed as a single explicit Timeout. + assert connect is UNSET + assert read is UNSET + assert write is UNSET + assert pool is UNSET + self.connect = timeout.connect # type: typing.Optional[float] + self.read = timeout.read # type: typing.Optional[float] + self.write = timeout.write # type: typing.Optional[float] + self.pool = timeout.pool # type: typing.Optional[float] + elif isinstance(timeout, tuple): + # Passed as a tuple. + self.connect = timeout[0] + self.read = timeout[1] + self.write = None if len(timeout) < 3 else timeout[2] + self.pool = None if len(timeout) < 4 else timeout[3] + elif not ( + isinstance(connect, UnsetType) + or isinstance(read, UnsetType) + or isinstance(write, UnsetType) + or isinstance(pool, UnsetType) + ): + self.connect = connect + self.read = read + self.write = write + self.pool = pool + else: + if isinstance(timeout, UnsetType): + raise ValueError( + "httpx.Timeout must either include a default, or set all " + "four parameters explicitly." + ) + self.connect = timeout if isinstance(connect, UnsetType) else connect + self.read = timeout if isinstance(read, UnsetType) else read + self.write = timeout if isinstance(write, UnsetType) else write + self.pool = timeout if isinstance(pool, UnsetType) else pool + + def as_dict(self) -> dict[str, float | None]: + return { + "connect": self.connect, + "read": self.read, + "write": self.write, + "pool": self.pool, + } + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, self.__class__) + and self.connect == other.connect + and self.read == other.read + and self.write == other.write + and self.pool == other.pool + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + if len({self.connect, self.read, self.write, self.pool}) == 1: + return f"{class_name}(timeout={self.connect})" + return ( + f"{class_name}(connect={self.connect}, " + f"read={self.read}, write={self.write}, pool={self.pool})" + ) + + +class Limits: + """ + Configuration for limits to various client behaviors. + + **Parameters:** + + * **max_connections** - The maximum number of concurrent connections that may be + established. + * **max_keepalive_connections** - Allow the connection pool to maintain + keep-alive connections below this point. Should be less than or equal + to `max_connections`. + * **keepalive_expiry** - Time limit on idle keep-alive connections in seconds. + """ + + def __init__( + self, + *, + max_connections: int | None = None, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = 5.0, + ) -> None: + self.max_connections = max_connections + self.max_keepalive_connections = max_keepalive_connections + self.keepalive_expiry = keepalive_expiry + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, self.__class__) + and self.max_connections == other.max_connections + and self.max_keepalive_connections == other.max_keepalive_connections + and self.keepalive_expiry == other.keepalive_expiry + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return ( + f"{class_name}(max_connections={self.max_connections}, " + f"max_keepalive_connections={self.max_keepalive_connections}, " + f"keepalive_expiry={self.keepalive_expiry})" + ) + + +class Proxy: + def __init__( + self, + url: URL | str, + *, + ssl_context: ssl.SSLContext | None = None, + auth: tuple[str, str] | None = None, + headers: HeaderTypes | None = None, + ) -> None: + url = URL(url) + headers = Headers(headers) + + if url.scheme not in ("http", "https", "socks5", "socks5h"): + raise ValueError(f"Unknown scheme for proxy URL {url!r}") + + if url.username or url.password: + # Remove any auth credentials from the URL. + auth = (url.username, url.password) + url = url.copy_with(username=None, password=None) + + self.url = url + self.auth = auth + self.headers = headers + self.ssl_context = ssl_context + + @property + def raw_auth(self) -> tuple[bytes, bytes] | None: + # The proxy authentication as raw bytes. + return ( + None + if self.auth is None + else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8")) + ) + + def __repr__(self) -> str: + # The authentication is represented with the password component masked. + auth = (self.auth[0], "********") if self.auth else None + + # Build a nice concise representation. + url_str = f"{str(self.url)!r}" + auth_str = f", auth={auth!r}" if auth else "" + headers_str = f", headers={dict(self.headers)!r}" if self.headers else "" + return f"Proxy({url_str}{auth_str}{headers_str})" + + +DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0) +DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20) +DEFAULT_MAX_REDIRECTS = 20 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py new file mode 100644 index 0000000..6f479a0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_content.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import inspect +import warnings +from json import dumps as json_dumps +from typing import ( + Any, + AsyncIterable, + AsyncIterator, + Iterable, + Iterator, + Mapping, +) +from urllib.parse import urlencode + +from ._exceptions import StreamClosed, StreamConsumed +from ._multipart import MultipartStream +from ._types import ( + AsyncByteStream, + RequestContent, + RequestData, + RequestFiles, + ResponseContent, + SyncByteStream, +) +from ._utils import peek_filelike_length, primitive_value_to_str + +__all__ = ["ByteStream"] + + +class ByteStream(AsyncByteStream, SyncByteStream): + def __init__(self, stream: bytes) -> None: + self._stream = stream + + def __iter__(self) -> Iterator[bytes]: + yield self._stream + + async def __aiter__(self) -> AsyncIterator[bytes]: + yield self._stream + + +class IteratorByteStream(SyncByteStream): + CHUNK_SIZE = 65_536 + + def __init__(self, stream: Iterable[bytes]) -> None: + self._stream = stream + self._is_stream_consumed = False + self._is_generator = inspect.isgenerator(stream) + + def __iter__(self) -> Iterator[bytes]: + if self._is_stream_consumed and self._is_generator: + raise StreamConsumed() + + self._is_stream_consumed = True + if hasattr(self._stream, "read"): + # File-like interfaces should use 'read' directly. + chunk = self._stream.read(self.CHUNK_SIZE) + while chunk: + yield chunk + chunk = self._stream.read(self.CHUNK_SIZE) + else: + # Otherwise iterate. + for part in self._stream: + yield part + + +class AsyncIteratorByteStream(AsyncByteStream): + CHUNK_SIZE = 65_536 + + def __init__(self, stream: AsyncIterable[bytes]) -> None: + self._stream = stream + self._is_stream_consumed = False + self._is_generator = inspect.isasyncgen(stream) + + async def __aiter__(self) -> AsyncIterator[bytes]: + if self._is_stream_consumed and self._is_generator: + raise StreamConsumed() + + self._is_stream_consumed = True + if hasattr(self._stream, "aread"): + # File-like interfaces should use 'aread' directly. + chunk = await self._stream.aread(self.CHUNK_SIZE) + while chunk: + yield chunk + chunk = await self._stream.aread(self.CHUNK_SIZE) + else: + # Otherwise iterate. + async for part in self._stream: + yield part + + +class UnattachedStream(AsyncByteStream, SyncByteStream): + """ + If a request or response is serialized using pickle, then it is no longer + attached to a stream for I/O purposes. Any stream operations should result + in `httpx.StreamClosed`. + """ + + def __iter__(self) -> Iterator[bytes]: + raise StreamClosed() + + async def __aiter__(self) -> AsyncIterator[bytes]: + raise StreamClosed() + yield b"" # pragma: no cover + + +def encode_content( + content: str | bytes | Iterable[bytes] | AsyncIterable[bytes], +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + if isinstance(content, (bytes, str)): + body = content.encode("utf-8") if isinstance(content, str) else content + content_length = len(body) + headers = {"Content-Length": str(content_length)} if body else {} + return headers, ByteStream(body) + + elif isinstance(content, Iterable) and not isinstance(content, dict): + # `not isinstance(content, dict)` is a bit oddly specific, but it + # catches a case that's easy for users to make in error, and would + # otherwise pass through here, like any other bytes-iterable, + # because `dict` happens to be iterable. See issue #2491. + content_length_or_none = peek_filelike_length(content) + + if content_length_or_none is None: + headers = {"Transfer-Encoding": "chunked"} + else: + headers = {"Content-Length": str(content_length_or_none)} + return headers, IteratorByteStream(content) # type: ignore + + elif isinstance(content, AsyncIterable): + headers = {"Transfer-Encoding": "chunked"} + return headers, AsyncIteratorByteStream(content) + + raise TypeError(f"Unexpected type for 'content', {type(content)!r}") + + +def encode_urlencoded_data( + data: RequestData, +) -> tuple[dict[str, str], ByteStream]: + plain_data = [] + for key, value in data.items(): + if isinstance(value, (list, tuple)): + plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) + else: + plain_data.append((key, primitive_value_to_str(value))) + body = urlencode(plain_data, doseq=True).encode("utf-8") + content_length = str(len(body)) + content_type = "application/x-www-form-urlencoded" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_multipart_data( + data: RequestData, files: RequestFiles, boundary: bytes | None +) -> tuple[dict[str, str], MultipartStream]: + multipart = MultipartStream(data=data, files=files, boundary=boundary) + headers = multipart.get_headers() + return headers, multipart + + +def encode_text(text: str) -> tuple[dict[str, str], ByteStream]: + body = text.encode("utf-8") + content_length = str(len(body)) + content_type = "text/plain; charset=utf-8" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_html(html: str) -> tuple[dict[str, str], ByteStream]: + body = html.encode("utf-8") + content_length = str(len(body)) + content_type = "text/html; charset=utf-8" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]: + body = json_dumps( + json, ensure_ascii=False, separators=(",", ":"), allow_nan=False + ).encode("utf-8") + content_length = str(len(body)) + content_type = "application/json" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_request( + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: Any | None = None, + boundary: bytes | None = None, +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + """ + Handles encoding the given `content`, `data`, `files`, and `json`, + returning a two-tuple of (, ). + """ + if data is not None and not isinstance(data, Mapping): + # We prefer to separate `content=` + # for raw request content, and `data=
` for url encoded or + # multipart form content. + # + # However for compat with requests, we *do* still support + # `data=` usages. We deal with that case here, treating it + # as if `content=<...>` had been supplied instead. + message = "Use 'content=<...>' to upload raw bytes/text content." + warnings.warn(message, DeprecationWarning, stacklevel=2) + return encode_content(data) + + if content is not None: + return encode_content(content) + elif files: + return encode_multipart_data(data or {}, files, boundary) + elif data: + return encode_urlencoded_data(data) + elif json is not None: + return encode_json(json) + + return {}, ByteStream(b"") + + +def encode_response( + content: ResponseContent | None = None, + text: str | None = None, + html: str | None = None, + json: Any | None = None, +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + """ + Handles encoding the given `content`, returning a two-tuple of + (, ). + """ + if content is not None: + return encode_content(content) + elif text is not None: + return encode_text(text) + elif html is not None: + return encode_html(html) + elif json is not None: + return encode_json(json) + + return {}, ByteStream(b"") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py new file mode 100644 index 0000000..899dfad --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py @@ -0,0 +1,393 @@ +""" +Handlers for Content-Encoding. + +See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding +""" + +from __future__ import annotations + +import codecs +import io +import typing +import zlib + +from ._exceptions import DecodingError + +# Brotli support is optional +try: + # The C bindings in `brotli` are recommended for CPython. + import brotli +except ImportError: # pragma: no cover + try: + # The CFFI bindings in `brotlicffi` are recommended for PyPy + # and other environments. + import brotlicffi as brotli + except ImportError: + brotli = None + + +# Zstandard support is optional +try: + import zstandard +except ImportError: # pragma: no cover + zstandard = None # type: ignore + + +class ContentDecoder: + def decode(self, data: bytes) -> bytes: + raise NotImplementedError() # pragma: no cover + + def flush(self) -> bytes: + raise NotImplementedError() # pragma: no cover + + +class IdentityDecoder(ContentDecoder): + """ + Handle unencoded data. + """ + + def decode(self, data: bytes) -> bytes: + return data + + def flush(self) -> bytes: + return b"" + + +class DeflateDecoder(ContentDecoder): + """ + Handle 'deflate' decoding. + + See: https://stackoverflow.com/questions/1838699 + """ + + def __init__(self) -> None: + self.first_attempt = True + self.decompressor = zlib.decompressobj() + + def decode(self, data: bytes) -> bytes: + was_first_attempt = self.first_attempt + self.first_attempt = False + try: + return self.decompressor.decompress(data) + except zlib.error as exc: + if was_first_attempt: + self.decompressor = zlib.decompressobj(-zlib.MAX_WBITS) + return self.decode(data) + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + try: + return self.decompressor.flush() + except zlib.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class GZipDecoder(ContentDecoder): + """ + Handle 'gzip' decoding. + + See: https://stackoverflow.com/questions/1838699 + """ + + def __init__(self) -> None: + self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 16) + + def decode(self, data: bytes) -> bytes: + try: + return self.decompressor.decompress(data) + except zlib.error as exc: + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + try: + return self.decompressor.flush() + except zlib.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class BrotliDecoder(ContentDecoder): + """ + Handle 'brotli' decoding. + + Requires `pip install brotlipy`. See: https://brotlipy.readthedocs.io/ + or `pip install brotli`. See https://github.com/google/brotli + Supports both 'brotlipy' and 'Brotli' packages since they share an import + name. The top branches are for 'brotlipy' and bottom branches for 'Brotli' + """ + + def __init__(self) -> None: + if brotli is None: # pragma: no cover + raise ImportError( + "Using 'BrotliDecoder', but neither of the 'brotlicffi' or 'brotli' " + "packages have been installed. " + "Make sure to install httpx using `pip install httpx[brotli]`." + ) from None + + self.decompressor = brotli.Decompressor() + self.seen_data = False + self._decompress: typing.Callable[[bytes], bytes] + if hasattr(self.decompressor, "decompress"): + # The 'brotlicffi' package. + self._decompress = self.decompressor.decompress # pragma: no cover + else: + # The 'brotli' package. + self._decompress = self.decompressor.process # pragma: no cover + + def decode(self, data: bytes) -> bytes: + if not data: + return b"" + self.seen_data = True + try: + return self._decompress(data) + except brotli.error as exc: + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + if not self.seen_data: + return b"" + try: + if hasattr(self.decompressor, "finish"): + # Only available in the 'brotlicffi' package. + + # As the decompressor decompresses eagerly, this + # will never actually emit any data. However, it will potentially throw + # errors if a truncated or damaged data stream has been used. + self.decompressor.finish() # pragma: no cover + return b"" + except brotli.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class ZStandardDecoder(ContentDecoder): + """ + Handle 'zstd' RFC 8878 decoding. + + Requires `pip install zstandard`. + Can be installed as a dependency of httpx using `pip install httpx[zstd]`. + """ + + # inspired by the ZstdDecoder implementation in urllib3 + def __init__(self) -> None: + if zstandard is None: # pragma: no cover + raise ImportError( + "Using 'ZStandardDecoder', ..." + "Make sure to install httpx using `pip install httpx[zstd]`." + ) from None + + self.decompressor = zstandard.ZstdDecompressor().decompressobj() + self.seen_data = False + + def decode(self, data: bytes) -> bytes: + assert zstandard is not None + self.seen_data = True + output = io.BytesIO() + try: + output.write(self.decompressor.decompress(data)) + while self.decompressor.eof and self.decompressor.unused_data: + unused_data = self.decompressor.unused_data + self.decompressor = zstandard.ZstdDecompressor().decompressobj() + output.write(self.decompressor.decompress(unused_data)) + except zstandard.ZstdError as exc: + raise DecodingError(str(exc)) from exc + return output.getvalue() + + def flush(self) -> bytes: + if not self.seen_data: + return b"" + ret = self.decompressor.flush() # note: this is a no-op + if not self.decompressor.eof: + raise DecodingError("Zstandard data is incomplete") # pragma: no cover + return bytes(ret) + + +class MultiDecoder(ContentDecoder): + """ + Handle the case where multiple encodings have been applied. + """ + + def __init__(self, children: typing.Sequence[ContentDecoder]) -> None: + """ + 'children' should be a sequence of decoders in the order in which + each was applied. + """ + # Note that we reverse the order for decoding. + self.children = list(reversed(children)) + + def decode(self, data: bytes) -> bytes: + for child in self.children: + data = child.decode(data) + return data + + def flush(self) -> bytes: + data = b"" + for child in self.children: + data = child.decode(data) + child.flush() + return data + + +class ByteChunker: + """ + Handles returning byte content in fixed-size chunks. + """ + + def __init__(self, chunk_size: int | None = None) -> None: + self._buffer = io.BytesIO() + self._chunk_size = chunk_size + + def decode(self, content: bytes) -> list[bytes]: + if self._chunk_size is None: + return [content] if content else [] + + self._buffer.write(content) + if self._buffer.tell() >= self._chunk_size: + value = self._buffer.getvalue() + chunks = [ + value[i : i + self._chunk_size] + for i in range(0, len(value), self._chunk_size) + ] + if len(chunks[-1]) == self._chunk_size: + self._buffer.seek(0) + self._buffer.truncate() + return chunks + else: + self._buffer.seek(0) + self._buffer.write(chunks[-1]) + self._buffer.truncate() + return chunks[:-1] + else: + return [] + + def flush(self) -> list[bytes]: + value = self._buffer.getvalue() + self._buffer.seek(0) + self._buffer.truncate() + return [value] if value else [] + + +class TextChunker: + """ + Handles returning text content in fixed-size chunks. + """ + + def __init__(self, chunk_size: int | None = None) -> None: + self._buffer = io.StringIO() + self._chunk_size = chunk_size + + def decode(self, content: str) -> list[str]: + if self._chunk_size is None: + return [content] if content else [] + + self._buffer.write(content) + if self._buffer.tell() >= self._chunk_size: + value = self._buffer.getvalue() + chunks = [ + value[i : i + self._chunk_size] + for i in range(0, len(value), self._chunk_size) + ] + if len(chunks[-1]) == self._chunk_size: + self._buffer.seek(0) + self._buffer.truncate() + return chunks + else: + self._buffer.seek(0) + self._buffer.write(chunks[-1]) + self._buffer.truncate() + return chunks[:-1] + else: + return [] + + def flush(self) -> list[str]: + value = self._buffer.getvalue() + self._buffer.seek(0) + self._buffer.truncate() + return [value] if value else [] + + +class TextDecoder: + """ + Handles incrementally decoding bytes into text + """ + + def __init__(self, encoding: str = "utf-8") -> None: + self.decoder = codecs.getincrementaldecoder(encoding)(errors="replace") + + def decode(self, data: bytes) -> str: + return self.decoder.decode(data) + + def flush(self) -> str: + return self.decoder.decode(b"", True) + + +class LineDecoder: + """ + Handles incrementally reading lines from text. + + Has the same behaviour as the stdllib splitlines, + but handling the input iteratively. + """ + + def __init__(self) -> None: + self.buffer: list[str] = [] + self.trailing_cr: bool = False + + def decode(self, text: str) -> list[str]: + # See https://docs.python.org/3/library/stdtypes.html#str.splitlines + NEWLINE_CHARS = "\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029" + + # We always push a trailing `\r` into the next decode iteration. + if self.trailing_cr: + text = "\r" + text + self.trailing_cr = False + if text.endswith("\r"): + self.trailing_cr = True + text = text[:-1] + + if not text: + # NOTE: the edge case input of empty text doesn't occur in practice, + # because other httpx internals filter out this value + return [] # pragma: no cover + + trailing_newline = text[-1] in NEWLINE_CHARS + lines = text.splitlines() + + if len(lines) == 1 and not trailing_newline: + # No new lines, buffer the input and continue. + self.buffer.append(lines[0]) + return [] + + if self.buffer: + # Include any existing buffer in the first portion of the + # splitlines result. + lines = ["".join(self.buffer) + lines[0]] + lines[1:] + self.buffer = [] + + if not trailing_newline: + # If the last segment of splitlines is not newline terminated, + # then drop it from our output and start a new buffer. + self.buffer = [lines.pop()] + + return lines + + def flush(self) -> list[str]: + if not self.buffer and not self.trailing_cr: + return [] + + lines = ["".join(self.buffer)] + self.buffer = [] + self.trailing_cr = False + return lines + + +SUPPORTED_DECODERS = { + "identity": IdentityDecoder, + "gzip": GZipDecoder, + "deflate": DeflateDecoder, + "br": BrotliDecoder, + "zstd": ZStandardDecoder, +} + + +if brotli is None: + SUPPORTED_DECODERS.pop("br") # pragma: no cover +if zstandard is None: + SUPPORTED_DECODERS.pop("zstd") # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py new file mode 100644 index 0000000..77f45a6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py @@ -0,0 +1,379 @@ +""" +Our exception hierarchy: + +* HTTPError + x RequestError + + TransportError + - TimeoutException + · ConnectTimeout + · ReadTimeout + · WriteTimeout + · PoolTimeout + - NetworkError + · ConnectError + · ReadError + · WriteError + · CloseError + - ProtocolError + · LocalProtocolError + · RemoteProtocolError + - ProxyError + - UnsupportedProtocol + + DecodingError + + TooManyRedirects + x HTTPStatusError +* InvalidURL +* CookieConflict +* StreamError + x StreamConsumed + x StreamClosed + x ResponseNotRead + x RequestNotRead +""" + +from __future__ import annotations + +import contextlib +import typing + +if typing.TYPE_CHECKING: + from ._models import Request, Response # pragma: no cover + +__all__ = [ + "CloseError", + "ConnectError", + "ConnectTimeout", + "CookieConflict", + "DecodingError", + "HTTPError", + "HTTPStatusError", + "InvalidURL", + "LocalProtocolError", + "NetworkError", + "PoolTimeout", + "ProtocolError", + "ProxyError", + "ReadError", + "ReadTimeout", + "RemoteProtocolError", + "RequestError", + "RequestNotRead", + "ResponseNotRead", + "StreamClosed", + "StreamConsumed", + "StreamError", + "TimeoutException", + "TooManyRedirects", + "TransportError", + "UnsupportedProtocol", + "WriteError", + "WriteTimeout", +] + + +class HTTPError(Exception): + """ + Base class for `RequestError` and `HTTPStatusError`. + + Useful for `try...except` blocks when issuing a request, + and then calling `.raise_for_status()`. + + For example: + + ``` + try: + response = httpx.get("https://www.example.com") + response.raise_for_status() + except httpx.HTTPError as exc: + print(f"HTTP Exception for {exc.request.url} - {exc}") + ``` + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + self._request: Request | None = None + + @property + def request(self) -> Request: + if self._request is None: + raise RuntimeError("The .request property has not been set.") + return self._request + + @request.setter + def request(self, request: Request) -> None: + self._request = request + + +class RequestError(HTTPError): + """ + Base class for all exceptions that may occur when issuing a `.request()`. + """ + + def __init__(self, message: str, *, request: Request | None = None) -> None: + super().__init__(message) + # At the point an exception is raised we won't typically have a request + # instance to associate it with. + # + # The 'request_context' context manager is used within the Client and + # Response methods in order to ensure that any raised exceptions + # have a `.request` property set on them. + self._request = request + + +class TransportError(RequestError): + """ + Base class for all exceptions that occur at the level of the Transport API. + """ + + +# Timeout exceptions... + + +class TimeoutException(TransportError): + """ + The base class for timeout errors. + + An operation has timed out. + """ + + +class ConnectTimeout(TimeoutException): + """ + Timed out while connecting to the host. + """ + + +class ReadTimeout(TimeoutException): + """ + Timed out while receiving data from the host. + """ + + +class WriteTimeout(TimeoutException): + """ + Timed out while sending data to the host. + """ + + +class PoolTimeout(TimeoutException): + """ + Timed out waiting to acquire a connection from the pool. + """ + + +# Core networking exceptions... + + +class NetworkError(TransportError): + """ + The base class for network-related errors. + + An error occurred while interacting with the network. + """ + + +class ReadError(NetworkError): + """ + Failed to receive data from the network. + """ + + +class WriteError(NetworkError): + """ + Failed to send data through the network. + """ + + +class ConnectError(NetworkError): + """ + Failed to establish a connection. + """ + + +class CloseError(NetworkError): + """ + Failed to close a connection. + """ + + +# Other transport exceptions... + + +class ProxyError(TransportError): + """ + An error occurred while establishing a proxy connection. + """ + + +class UnsupportedProtocol(TransportError): + """ + Attempted to make a request to an unsupported protocol. + + For example issuing a request to `ftp://www.example.com`. + """ + + +class ProtocolError(TransportError): + """ + The protocol was violated. + """ + + +class LocalProtocolError(ProtocolError): + """ + A protocol was violated by the client. + + For example if the user instantiated a `Request` instance explicitly, + failed to include the mandatory `Host:` header, and then issued it directly + using `client.send()`. + """ + + +class RemoteProtocolError(ProtocolError): + """ + The protocol was violated by the server. + + For example, returning malformed HTTP. + """ + + +# Other request exceptions... + + +class DecodingError(RequestError): + """ + Decoding of the response failed, due to a malformed encoding. + """ + + +class TooManyRedirects(RequestError): + """ + Too many redirects. + """ + + +# Client errors + + +class HTTPStatusError(HTTPError): + """ + The response had an error HTTP status of 4xx or 5xx. + + May be raised when calling `response.raise_for_status()` + """ + + def __init__(self, message: str, *, request: Request, response: Response) -> None: + super().__init__(message) + self.request = request + self.response = response + + +class InvalidURL(Exception): + """ + URL is improperly formed or cannot be parsed. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +class CookieConflict(Exception): + """ + Attempted to lookup a cookie by name, but multiple cookies existed. + + Can occur when calling `response.cookies.get(...)`. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +# Stream exceptions... + +# These may occur as the result of a programming error, by accessing +# the request/response stream in an invalid manner. + + +class StreamError(RuntimeError): + """ + The base class for stream exceptions. + + The developer made an error in accessing the request stream in + an invalid way. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +class StreamConsumed(StreamError): + """ + Attempted to read or stream content, but the content has already + been streamed. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. For requests, this could be due to passing " + "a generator as request content, and then receiving a redirect " + "response or a secondary request as part of an authentication flow." + "For responses, this could be due to attempting to stream the response " + "content more than once." + ) + super().__init__(message) + + +class StreamClosed(StreamError): + """ + Attempted to read or stream response content, but the request has been + closed. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream content, but the stream has " "been closed." + ) + super().__init__(message) + + +class ResponseNotRead(StreamError): + """ + Attempted to access streaming response content, without having called `read()`. + """ + + def __init__(self) -> None: + message = ( + "Attempted to access streaming response content," + " without having called `read()`." + ) + super().__init__(message) + + +class RequestNotRead(StreamError): + """ + Attempted to access streaming request content, without having called `read()`. + """ + + def __init__(self) -> None: + message = ( + "Attempted to access streaming request content," + " without having called `read()`." + ) + super().__init__(message) + + +@contextlib.contextmanager +def request_context( + request: Request | None = None, +) -> typing.Iterator[None]: + """ + A context manager that can be used to attach the given request context + to any `RequestError` exceptions that are raised within the block. + """ + try: + yield + except RequestError as exc: + if request is not None: + exc.request = request + raise exc diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py new file mode 100644 index 0000000..cffa4bb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_main.py @@ -0,0 +1,506 @@ +from __future__ import annotations + +import functools +import json +import sys +import typing + +import click +import pygments.lexers +import pygments.util +import rich.console +import rich.markup +import rich.progress +import rich.syntax +import rich.table + +from ._client import Client +from ._exceptions import RequestError +from ._models import Response +from ._status_codes import codes + +if typing.TYPE_CHECKING: + import httpcore # pragma: no cover + + +def print_help() -> None: + console = rich.console.Console() + + console.print("[bold]HTTPX :butterfly:", justify="center") + console.print() + console.print("A next generation HTTP client.", justify="center") + console.print() + console.print( + "Usage: [bold]httpx[/bold] [cyan] [OPTIONS][/cyan] ", justify="left" + ) + console.print() + + table = rich.table.Table.grid(padding=1, pad_edge=True) + table.add_column("Parameter", no_wrap=True, justify="left", style="bold") + table.add_column("Description") + table.add_row( + "-m, --method [cyan]METHOD", + "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\n" + "[Default: GET, or POST if a request body is included]", + ) + table.add_row( + "-p, --params [cyan] ...", + "Query parameters to include in the request URL.", + ) + table.add_row( + "-c, --content [cyan]TEXT", "Byte content to include in the request body." + ) + table.add_row( + "-d, --data [cyan] ...", "Form data to include in the request body." + ) + table.add_row( + "-f, --files [cyan] ...", + "Form files to include in the request body.", + ) + table.add_row("-j, --json [cyan]TEXT", "JSON data to include in the request body.") + table.add_row( + "-h, --headers [cyan] ...", + "Include additional HTTP headers in the request.", + ) + table.add_row( + "--cookies [cyan] ...", "Cookies to include in the request." + ) + table.add_row( + "--auth [cyan]", + "Username and password to include in the request. Specify '-' for the password" + " to use a password prompt. Note that using --verbose/-v will expose" + " the Authorization header, including the password encoding" + " in a trivially reversible format.", + ) + + table.add_row( + "--proxy [cyan]URL", + "Send the request via a proxy. Should be the URL giving the proxy address.", + ) + + table.add_row( + "--timeout [cyan]FLOAT", + "Timeout value to use for network operations, such as establishing the" + " connection, reading some data, etc... [Default: 5.0]", + ) + + table.add_row("--follow-redirects", "Automatically follow redirects.") + table.add_row("--no-verify", "Disable SSL verification.") + table.add_row( + "--http2", "Send the request using HTTP/2, if the remote server supports it." + ) + + table.add_row( + "--download [cyan]FILE", + "Save the response content as a file, rather than displaying it.", + ) + + table.add_row("-v, --verbose", "Verbose output. Show request as well as response.") + table.add_row("--help", "Show this message and exit.") + console.print(table) + + +def get_lexer_for_response(response: Response) -> str: + content_type = response.headers.get("Content-Type") + if content_type is not None: + mime_type, _, _ = content_type.partition(";") + try: + return typing.cast( + str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name + ) + except pygments.util.ClassNotFound: # pragma: no cover + pass + return "" # pragma: no cover + + +def format_request_headers(request: httpcore.Request, http2: bool = False) -> str: + version = "HTTP/2" if http2 else "HTTP/1.1" + headers = [ + (name.lower() if http2 else name, value) for name, value in request.headers + ] + method = request.method.decode("ascii") + target = request.url.target.decode("ascii") + lines = [f"{method} {target} {version}"] + [ + f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers + ] + return "\n".join(lines) + + +def format_response_headers( + http_version: bytes, + status: int, + reason_phrase: bytes | None, + headers: list[tuple[bytes, bytes]], +) -> str: + version = http_version.decode("ascii") + reason = ( + codes.get_reason_phrase(status) + if reason_phrase is None + else reason_phrase.decode("ascii") + ) + lines = [f"{version} {status} {reason}"] + [ + f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers + ] + return "\n".join(lines) + + +def print_request_headers(request: httpcore.Request, http2: bool = False) -> None: + console = rich.console.Console() + http_text = format_request_headers(request, http2=http2) + syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + + +def print_response_headers( + http_version: bytes, + status: int, + reason_phrase: bytes | None, + headers: list[tuple[bytes, bytes]], +) -> None: + console = rich.console.Console() + http_text = format_response_headers(http_version, status, reason_phrase, headers) + syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + + +def print_response(response: Response) -> None: + console = rich.console.Console() + lexer_name = get_lexer_for_response(response) + if lexer_name: + if lexer_name.lower() == "json": + try: + data = response.json() + text = json.dumps(data, indent=4) + except ValueError: # pragma: no cover + text = response.text + else: + text = response.text + + syntax = rich.syntax.Syntax(text, lexer_name, theme="ansi_dark", word_wrap=True) + console.print(syntax) + else: + console.print(f"<{len(response.content)} bytes of binary data>") + + +_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...] +_PCTRTTT = typing.Tuple[_PCTRTT, ...] +_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]] + + +def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover + lines = [] + for key, value in cert.items(): + if isinstance(value, (list, tuple)): + lines.append(f"* {key}:") + for item in value: + if key in ("subject", "issuer"): + for sub_item in item: + lines.append(f"* {sub_item[0]}: {sub_item[1]!r}") + elif isinstance(item, tuple) and len(item) == 2: + lines.append(f"* {item[0]}: {item[1]!r}") + else: + lines.append(f"* {item!r}") + else: + lines.append(f"* {key}: {value!r}") + return "\n".join(lines) + + +def trace( + name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False +) -> None: + console = rich.console.Console() + if name == "connection.connect_tcp.started" and verbose: + host = info["host"] + console.print(f"* Connecting to {host!r}") + elif name == "connection.connect_tcp.complete" and verbose: + stream = info["return_value"] + server_addr = stream.get_extra_info("server_addr") + console.print(f"* Connected to {server_addr[0]!r} on port {server_addr[1]}") + elif name == "connection.start_tls.complete" and verbose: # pragma: no cover + stream = info["return_value"] + ssl_object = stream.get_extra_info("ssl_object") + version = ssl_object.version() + cipher = ssl_object.cipher() + server_cert = ssl_object.getpeercert() + alpn = ssl_object.selected_alpn_protocol() + console.print(f"* SSL established using {version!r} / {cipher[0]!r}") + console.print(f"* Selected ALPN protocol: {alpn!r}") + if server_cert: + console.print("* Server certificate:") + console.print(format_certificate(server_cert)) + elif name == "http11.send_request_headers.started" and verbose: + request = info["request"] + print_request_headers(request, http2=False) + elif name == "http2.send_request_headers.started" and verbose: # pragma: no cover + request = info["request"] + print_request_headers(request, http2=True) + elif name == "http11.receive_response_headers.complete": + http_version, status, reason_phrase, headers = info["return_value"] + print_response_headers(http_version, status, reason_phrase, headers) + elif name == "http2.receive_response_headers.complete": # pragma: no cover + status, headers = info["return_value"] + http_version = b"HTTP/2" + reason_phrase = None + print_response_headers(http_version, status, reason_phrase, headers) + + +def download_response(response: Response, download: typing.BinaryIO) -> None: + console = rich.console.Console() + console.print() + content_length = response.headers.get("Content-Length") + with rich.progress.Progress( + "[progress.description]{task.description}", + "[progress.percentage]{task.percentage:>3.0f}%", + rich.progress.BarColumn(bar_width=None), + rich.progress.DownloadColumn(), + rich.progress.TransferSpeedColumn(), + ) as progress: + description = f"Downloading [bold]{rich.markup.escape(download.name)}" + download_task = progress.add_task( + description, + total=int(content_length or 0), + start=content_length is not None, + ) + for chunk in response.iter_bytes(): + download.write(chunk) + progress.update(download_task, completed=response.num_bytes_downloaded) + + +def validate_json( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> typing.Any: + if value is None: + return None + + try: + return json.loads(value) + except json.JSONDecodeError: # pragma: no cover + raise click.BadParameter("Not valid JSON") + + +def validate_auth( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> typing.Any: + if value == (None, None): + return None + + username, password = value + if password == "-": # pragma: no cover + password = click.prompt("Password", hide_input=True) + return (username, password) + + +def handle_help( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> None: + if not value or ctx.resilient_parsing: + return + + print_help() + ctx.exit() + + +@click.command(add_help_option=False) +@click.argument("url", type=str) +@click.option( + "--method", + "-m", + "method", + type=str, + help=( + "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. " + "[Default: GET, or POST if a request body is included]" + ), +) +@click.option( + "--params", + "-p", + "params", + type=(str, str), + multiple=True, + help="Query parameters to include in the request URL.", +) +@click.option( + "--content", + "-c", + "content", + type=str, + help="Byte content to include in the request body.", +) +@click.option( + "--data", + "-d", + "data", + type=(str, str), + multiple=True, + help="Form data to include in the request body.", +) +@click.option( + "--files", + "-f", + "files", + type=(str, click.File(mode="rb")), + multiple=True, + help="Form files to include in the request body.", +) +@click.option( + "--json", + "-j", + "json", + type=str, + callback=validate_json, + help="JSON data to include in the request body.", +) +@click.option( + "--headers", + "-h", + "headers", + type=(str, str), + multiple=True, + help="Include additional HTTP headers in the request.", +) +@click.option( + "--cookies", + "cookies", + type=(str, str), + multiple=True, + help="Cookies to include in the request.", +) +@click.option( + "--auth", + "auth", + type=(str, str), + default=(None, None), + callback=validate_auth, + help=( + "Username and password to include in the request. " + "Specify '-' for the password to use a password prompt. " + "Note that using --verbose/-v will expose the Authorization header, " + "including the password encoding in a trivially reversible format." + ), +) +@click.option( + "--proxy", + "proxy", + type=str, + default=None, + help="Send the request via a proxy. Should be the URL giving the proxy address.", +) +@click.option( + "--timeout", + "timeout", + type=float, + default=5.0, + help=( + "Timeout value to use for network operations, such as establishing the " + "connection, reading some data, etc... [Default: 5.0]" + ), +) +@click.option( + "--follow-redirects", + "follow_redirects", + is_flag=True, + default=False, + help="Automatically follow redirects.", +) +@click.option( + "--no-verify", + "verify", + is_flag=True, + default=True, + help="Disable SSL verification.", +) +@click.option( + "--http2", + "http2", + type=bool, + is_flag=True, + default=False, + help="Send the request using HTTP/2, if the remote server supports it.", +) +@click.option( + "--download", + type=click.File("wb"), + help="Save the response content as a file, rather than displaying it.", +) +@click.option( + "--verbose", + "-v", + type=bool, + is_flag=True, + default=False, + help="Verbose. Show request as well as response.", +) +@click.option( + "--help", + is_flag=True, + is_eager=True, + expose_value=False, + callback=handle_help, + help="Show this message and exit.", +) +def main( + url: str, + method: str, + params: list[tuple[str, str]], + content: str, + data: list[tuple[str, str]], + files: list[tuple[str, click.File]], + json: str, + headers: list[tuple[str, str]], + cookies: list[tuple[str, str]], + auth: tuple[str, str] | None, + proxy: str, + timeout: float, + follow_redirects: bool, + verify: bool, + http2: bool, + download: typing.BinaryIO | None, + verbose: bool, +) -> None: + """ + An HTTP command line client. + Sends a request and displays the response. + """ + if not method: + method = "POST" if content or data or files or json else "GET" + + try: + with Client(proxy=proxy, timeout=timeout, http2=http2, verify=verify) as client: + with client.stream( + method, + url, + params=list(params), + content=content, + data=dict(data), + files=files, # type: ignore + json=json, + headers=headers, + cookies=dict(cookies), + auth=auth, + follow_redirects=follow_redirects, + extensions={"trace": functools.partial(trace, verbose=verbose)}, + ) as response: + if download is not None: + download_response(response, download) + else: + response.read() + if response.content: + print_response(response) + + except RequestError as exc: + console = rich.console.Console() + console.print(f"[red]{type(exc).__name__}[/red]: {exc}") + sys.exit(1) + + sys.exit(0 if response.is_success else 1) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py new file mode 100644 index 0000000..67d74bf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_models.py @@ -0,0 +1,1277 @@ +from __future__ import annotations + +import codecs +import datetime +import email.message +import json as jsonlib +import re +import typing +import urllib.request +from collections.abc import Mapping +from http.cookiejar import Cookie, CookieJar + +from ._content import ByteStream, UnattachedStream, encode_request, encode_response +from ._decoders import ( + SUPPORTED_DECODERS, + ByteChunker, + ContentDecoder, + IdentityDecoder, + LineDecoder, + MultiDecoder, + TextChunker, + TextDecoder, +) +from ._exceptions import ( + CookieConflict, + HTTPStatusError, + RequestNotRead, + ResponseNotRead, + StreamClosed, + StreamConsumed, + request_context, +) +from ._multipart import get_multipart_boundary_from_content_type +from ._status_codes import codes +from ._types import ( + AsyncByteStream, + CookieTypes, + HeaderTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestExtensions, + RequestFiles, + ResponseContent, + ResponseExtensions, + SyncByteStream, +) +from ._urls import URL +from ._utils import to_bytes_or_str, to_str + +__all__ = ["Cookies", "Headers", "Request", "Response"] + +SENSITIVE_HEADERS = {"authorization", "proxy-authorization"} + + +def _is_known_encoding(encoding: str) -> bool: + """ + Return `True` if `encoding` is a known codec. + """ + try: + codecs.lookup(encoding) + except LookupError: + return False + return True + + +def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes: + """ + Coerce str/bytes into a strictly byte-wise HTTP header key. + """ + return key if isinstance(key, bytes) else key.encode(encoding or "ascii") + + +def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes: + """ + Coerce str/bytes into a strictly byte-wise HTTP header value. + """ + if isinstance(value, bytes): + return value + if not isinstance(value, str): + raise TypeError(f"Header value must be str or bytes, not {type(value)}") + return value.encode(encoding or "ascii") + + +def _parse_content_type_charset(content_type: str) -> str | None: + # We used to use `cgi.parse_header()` here, but `cgi` became a dead battery. + # See: https://peps.python.org/pep-0594/#cgi + msg = email.message.Message() + msg["content-type"] = content_type + return msg.get_content_charset(failobj=None) + + +def _parse_header_links(value: str) -> list[dict[str, str]]: + """ + Returns a list of parsed link headers, for more info see: + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link + The generic syntax of those is: + Link: < uri-reference >; param1=value1; param2="value2" + So for instance: + Link; '; type="image/jpeg",;' + would return + [ + {"url": "http:/.../front.jpeg", "type": "image/jpeg"}, + {"url": "http://.../back.jpeg"}, + ] + :param value: HTTP Link entity-header field + :return: list of parsed link headers + """ + links: list[dict[str, str]] = [] + replace_chars = " '\"" + value = value.strip(replace_chars) + if not value: + return links + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + link = {"url": url.strip("<> '\"")} + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + link[key.strip(replace_chars)] = value.strip(replace_chars) + links.append(link) + return links + + +def _obfuscate_sensitive_headers( + items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]], +) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]: + for k, v in items: + if to_str(k.lower()) in SENSITIVE_HEADERS: + v = to_bytes_or_str("[secure]", match_type_of=v) + yield k, v + + +class Headers(typing.MutableMapping[str, str]): + """ + HTTP headers, as a case-insensitive multi-dict. + """ + + def __init__( + self, + headers: HeaderTypes | None = None, + encoding: str | None = None, + ) -> None: + self._list = [] # type: typing.List[typing.Tuple[bytes, bytes, bytes]] + + if isinstance(headers, Headers): + self._list = list(headers._list) + elif isinstance(headers, Mapping): + for k, v in headers.items(): + bytes_key = _normalize_header_key(k, encoding) + bytes_value = _normalize_header_value(v, encoding) + self._list.append((bytes_key, bytes_key.lower(), bytes_value)) + elif headers is not None: + for k, v in headers: + bytes_key = _normalize_header_key(k, encoding) + bytes_value = _normalize_header_value(v, encoding) + self._list.append((bytes_key, bytes_key.lower(), bytes_value)) + + self._encoding = encoding + + @property + def encoding(self) -> str: + """ + Header encoding is mandated as ascii, but we allow fallbacks to utf-8 + or iso-8859-1. + """ + if self._encoding is None: + for encoding in ["ascii", "utf-8"]: + for key, value in self.raw: + try: + key.decode(encoding) + value.decode(encoding) + except UnicodeDecodeError: + break + else: + # The else block runs if 'break' did not occur, meaning + # all values fitted the encoding. + self._encoding = encoding + break + else: + # The ISO-8859-1 encoding covers all 256 code points in a byte, + # so will never raise decode errors. + self._encoding = "iso-8859-1" + return self._encoding + + @encoding.setter + def encoding(self, value: str) -> None: + self._encoding = value + + @property + def raw(self) -> list[tuple[bytes, bytes]]: + """ + Returns a list of the raw header items, as byte pairs. + """ + return [(raw_key, value) for raw_key, _, value in self._list] + + def keys(self) -> typing.KeysView[str]: + return {key.decode(self.encoding): None for _, key, value in self._list}.keys() + + def values(self) -> typing.ValuesView[str]: + values_dict: dict[str, str] = {} + for _, key, value in self._list: + str_key = key.decode(self.encoding) + str_value = value.decode(self.encoding) + if str_key in values_dict: + values_dict[str_key] += f", {str_value}" + else: + values_dict[str_key] = str_value + return values_dict.values() + + def items(self) -> typing.ItemsView[str, str]: + """ + Return `(key, value)` items of headers. Concatenate headers + into a single comma separated value when a key occurs multiple times. + """ + values_dict: dict[str, str] = {} + for _, key, value in self._list: + str_key = key.decode(self.encoding) + str_value = value.decode(self.encoding) + if str_key in values_dict: + values_dict[str_key] += f", {str_value}" + else: + values_dict[str_key] = str_value + return values_dict.items() + + def multi_items(self) -> list[tuple[str, str]]: + """ + Return a list of `(key, value)` pairs of headers. Allow multiple + occurrences of the same key without concatenating into a single + comma separated value. + """ + return [ + (key.decode(self.encoding), value.decode(self.encoding)) + for _, key, value in self._list + ] + + def get(self, key: str, default: typing.Any = None) -> typing.Any: + """ + Return a header value. If multiple occurrences of the header occur + then concatenate them together with commas. + """ + try: + return self[key] + except KeyError: + return default + + def get_list(self, key: str, split_commas: bool = False) -> list[str]: + """ + Return a list of all header values for a given key. + If `split_commas=True` is passed, then any comma separated header + values are split into multiple return strings. + """ + get_header_key = key.lower().encode(self.encoding) + + values = [ + item_value.decode(self.encoding) + for _, item_key, item_value in self._list + if item_key.lower() == get_header_key + ] + + if not split_commas: + return values + + split_values = [] + for value in values: + split_values.extend([item.strip() for item in value.split(",")]) + return split_values + + def update(self, headers: HeaderTypes | None = None) -> None: # type: ignore + headers = Headers(headers) + for key in headers.keys(): + if key in self: + self.pop(key) + self._list.extend(headers._list) + + def copy(self) -> Headers: + return Headers(self, encoding=self.encoding) + + def __getitem__(self, key: str) -> str: + """ + Return a single header value. + + If there are multiple headers with the same key, then we concatenate + them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2 + """ + normalized_key = key.lower().encode(self.encoding) + + items = [ + header_value.decode(self.encoding) + for _, header_key, header_value in self._list + if header_key == normalized_key + ] + + if items: + return ", ".join(items) + + raise KeyError(key) + + def __setitem__(self, key: str, value: str) -> None: + """ + Set the header `key` to `value`, removing any duplicate entries. + Retains insertion order. + """ + set_key = key.encode(self._encoding or "utf-8") + set_value = value.encode(self._encoding or "utf-8") + lookup_key = set_key.lower() + + found_indexes = [ + idx + for idx, (_, item_key, _) in enumerate(self._list) + if item_key == lookup_key + ] + + for idx in reversed(found_indexes[1:]): + del self._list[idx] + + if found_indexes: + idx = found_indexes[0] + self._list[idx] = (set_key, lookup_key, set_value) + else: + self._list.append((set_key, lookup_key, set_value)) + + def __delitem__(self, key: str) -> None: + """ + Remove the header `key`. + """ + del_key = key.lower().encode(self.encoding) + + pop_indexes = [ + idx + for idx, (_, item_key, _) in enumerate(self._list) + if item_key.lower() == del_key + ] + + if not pop_indexes: + raise KeyError(key) + + for idx in reversed(pop_indexes): + del self._list[idx] + + def __contains__(self, key: typing.Any) -> bool: + header_key = key.lower().encode(self.encoding) + return header_key in [key for _, key, _ in self._list] + + def __iter__(self) -> typing.Iterator[typing.Any]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._list) + + def __eq__(self, other: typing.Any) -> bool: + try: + other_headers = Headers(other) + except ValueError: + return False + + self_list = [(key, value) for _, key, value in self._list] + other_list = [(key, value) for _, key, value in other_headers._list] + return sorted(self_list) == sorted(other_list) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + + encoding_str = "" + if self.encoding != "ascii": + encoding_str = f", encoding={self.encoding!r}" + + as_list = list(_obfuscate_sensitive_headers(self.multi_items())) + as_dict = dict(as_list) + + no_duplicate_keys = len(as_dict) == len(as_list) + if no_duplicate_keys: + return f"{class_name}({as_dict!r}{encoding_str})" + return f"{class_name}({as_list!r}{encoding_str})" + + +class Request: + def __init__( + self, + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + stream: SyncByteStream | AsyncByteStream | None = None, + extensions: RequestExtensions | None = None, + ) -> None: + self.method = method.upper() + self.url = URL(url) if params is None else URL(url, params=params) + self.headers = Headers(headers) + self.extensions = {} if extensions is None else dict(extensions) + + if cookies: + Cookies(cookies).set_cookie_header(self) + + if stream is None: + content_type: str | None = self.headers.get("content-type") + headers, stream = encode_request( + content=content, + data=data, + files=files, + json=json, + boundary=get_multipart_boundary_from_content_type( + content_type=content_type.encode(self.headers.encoding) + if content_type + else None + ), + ) + self._prepare(headers) + self.stream = stream + # Load the request body, except for streaming content. + if isinstance(stream, ByteStream): + self.read() + else: + # There's an important distinction between `Request(content=...)`, + # and `Request(stream=...)`. + # + # Using `content=...` implies automatically populated `Host` and content + # headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`. + # + # Using `stream=...` will not automatically include *any* + # auto-populated headers. + # + # As an end-user you don't really need `stream=...`. It's only + # useful when: + # + # * Preserving the request stream when copying requests, eg for redirects. + # * Creating request instances on the *server-side* of the transport API. + self.stream = stream + + def _prepare(self, default_headers: dict[str, str]) -> None: + for key, value in default_headers.items(): + # Ignore Transfer-Encoding if the Content-Length has been set explicitly. + if key.lower() == "transfer-encoding" and "Content-Length" in self.headers: + continue + self.headers.setdefault(key, value) + + auto_headers: list[tuple[bytes, bytes]] = [] + + has_host = "Host" in self.headers + has_content_length = ( + "Content-Length" in self.headers or "Transfer-Encoding" in self.headers + ) + + if not has_host and self.url.host: + auto_headers.append((b"Host", self.url.netloc)) + if not has_content_length and self.method in ("POST", "PUT", "PATCH"): + auto_headers.append((b"Content-Length", b"0")) + + self.headers = Headers(auto_headers + self.headers.raw) + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + raise RequestNotRead() + return self._content + + def read(self) -> bytes: + """ + Read and return the request content. + """ + if not hasattr(self, "_content"): + assert isinstance(self.stream, typing.Iterable) + self._content = b"".join(self.stream) + if not isinstance(self.stream, ByteStream): + # If a streaming request has been read entirely into memory, then + # we can replace the stream with a raw bytes implementation, + # to ensure that any non-replayable streams can still be used. + self.stream = ByteStream(self._content) + return self._content + + async def aread(self) -> bytes: + """ + Read and return the request content. + """ + if not hasattr(self, "_content"): + assert isinstance(self.stream, typing.AsyncIterable) + self._content = b"".join([part async for part in self.stream]) + if not isinstance(self.stream, ByteStream): + # If a streaming request has been read entirely into memory, then + # we can replace the stream with a raw bytes implementation, + # to ensure that any non-replayable streams can still be used. + self.stream = ByteStream(self._content) + return self._content + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + url = str(self.url) + return f"<{class_name}({self.method!r}, {url!r})>" + + def __getstate__(self) -> dict[str, typing.Any]: + return { + name: value + for name, value in self.__dict__.items() + if name not in ["extensions", "stream"] + } + + def __setstate__(self, state: dict[str, typing.Any]) -> None: + for name, value in state.items(): + setattr(self, name, value) + self.extensions = {} + self.stream = UnattachedStream() + + +class Response: + def __init__( + self, + status_code: int, + *, + headers: HeaderTypes | None = None, + content: ResponseContent | None = None, + text: str | None = None, + html: str | None = None, + json: typing.Any = None, + stream: SyncByteStream | AsyncByteStream | None = None, + request: Request | None = None, + extensions: ResponseExtensions | None = None, + history: list[Response] | None = None, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + self.status_code = status_code + self.headers = Headers(headers) + + self._request: Request | None = request + + # When follow_redirects=False and a redirect is received, + # the client will set `response.next_request`. + self.next_request: Request | None = None + + self.extensions = {} if extensions is None else dict(extensions) + self.history = [] if history is None else list(history) + + self.is_closed = False + self.is_stream_consumed = False + + self.default_encoding = default_encoding + + if stream is None: + headers, stream = encode_response(content, text, html, json) + self._prepare(headers) + self.stream = stream + if isinstance(stream, ByteStream): + # Load the response body, except for streaming content. + self.read() + else: + # There's an important distinction between `Response(content=...)`, + # and `Response(stream=...)`. + # + # Using `content=...` implies automatically populated content headers, + # of either `Content-Length: ...` or `Transfer-Encoding: chunked`. + # + # Using `stream=...` will not automatically include any content headers. + # + # As an end-user you don't really need `stream=...`. It's only + # useful when creating response instances having received a stream + # from the transport API. + self.stream = stream + + self._num_bytes_downloaded = 0 + + def _prepare(self, default_headers: dict[str, str]) -> None: + for key, value in default_headers.items(): + # Ignore Transfer-Encoding if the Content-Length has been set explicitly. + if key.lower() == "transfer-encoding" and "content-length" in self.headers: + continue + self.headers.setdefault(key, value) + + @property + def elapsed(self) -> datetime.timedelta: + """ + Returns the time taken for the complete request/response + cycle to complete. + """ + if not hasattr(self, "_elapsed"): + raise RuntimeError( + "'.elapsed' may only be accessed after the response " + "has been read or closed." + ) + return self._elapsed + + @elapsed.setter + def elapsed(self, elapsed: datetime.timedelta) -> None: + self._elapsed = elapsed + + @property + def request(self) -> Request: + """ + Returns the request instance associated to the current response. + """ + if self._request is None: + raise RuntimeError( + "The request instance has not been set on this response." + ) + return self._request + + @request.setter + def request(self, value: Request) -> None: + self._request = value + + @property + def http_version(self) -> str: + try: + http_version: bytes = self.extensions["http_version"] + except KeyError: + return "HTTP/1.1" + else: + return http_version.decode("ascii", errors="ignore") + + @property + def reason_phrase(self) -> str: + try: + reason_phrase: bytes = self.extensions["reason_phrase"] + except KeyError: + return codes.get_reason_phrase(self.status_code) + else: + return reason_phrase.decode("ascii", errors="ignore") + + @property + def url(self) -> URL: + """ + Returns the URL for which the request was made. + """ + return self.request.url + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + raise ResponseNotRead() + return self._content + + @property + def text(self) -> str: + if not hasattr(self, "_text"): + content = self.content + if not content: + self._text = "" + else: + decoder = TextDecoder(encoding=self.encoding or "utf-8") + self._text = "".join([decoder.decode(self.content), decoder.flush()]) + return self._text + + @property + def encoding(self) -> str | None: + """ + Return an encoding to use for decoding the byte content into text. + The priority for determining this is given by... + + * `.encoding = <>` has been set explicitly. + * The encoding as specified by the charset parameter in the Content-Type header. + * The encoding as determined by `default_encoding`, which may either be + a string like "utf-8" indicating the encoding to use, or may be a callable + which enables charset autodetection. + """ + if not hasattr(self, "_encoding"): + encoding = self.charset_encoding + if encoding is None or not _is_known_encoding(encoding): + if isinstance(self.default_encoding, str): + encoding = self.default_encoding + elif hasattr(self, "_content"): + encoding = self.default_encoding(self._content) + self._encoding = encoding or "utf-8" + return self._encoding + + @encoding.setter + def encoding(self, value: str) -> None: + """ + Set the encoding to use for decoding the byte content into text. + + If the `text` attribute has been accessed, attempting to set the + encoding will throw a ValueError. + """ + if hasattr(self, "_text"): + raise ValueError( + "Setting encoding after `text` has been accessed is not allowed." + ) + self._encoding = value + + @property + def charset_encoding(self) -> str | None: + """ + Return the encoding, as specified by the Content-Type header. + """ + content_type = self.headers.get("Content-Type") + if content_type is None: + return None + + return _parse_content_type_charset(content_type) + + def _get_content_decoder(self) -> ContentDecoder: + """ + Returns a decoder instance which can be used to decode the raw byte + content, depending on the Content-Encoding used in the response. + """ + if not hasattr(self, "_decoder"): + decoders: list[ContentDecoder] = [] + values = self.headers.get_list("content-encoding", split_commas=True) + for value in values: + value = value.strip().lower() + try: + decoder_cls = SUPPORTED_DECODERS[value] + decoders.append(decoder_cls()) + except KeyError: + continue + + if len(decoders) == 1: + self._decoder = decoders[0] + elif len(decoders) > 1: + self._decoder = MultiDecoder(children=decoders) + else: + self._decoder = IdentityDecoder() + + return self._decoder + + @property + def is_informational(self) -> bool: + """ + A property which is `True` for 1xx status codes, `False` otherwise. + """ + return codes.is_informational(self.status_code) + + @property + def is_success(self) -> bool: + """ + A property which is `True` for 2xx status codes, `False` otherwise. + """ + return codes.is_success(self.status_code) + + @property + def is_redirect(self) -> bool: + """ + A property which is `True` for 3xx status codes, `False` otherwise. + + Note that not all responses with a 3xx status code indicate a URL redirect. + + Use `response.has_redirect_location` to determine responses with a properly + formed URL redirection. + """ + return codes.is_redirect(self.status_code) + + @property + def is_client_error(self) -> bool: + """ + A property which is `True` for 4xx status codes, `False` otherwise. + """ + return codes.is_client_error(self.status_code) + + @property + def is_server_error(self) -> bool: + """ + A property which is `True` for 5xx status codes, `False` otherwise. + """ + return codes.is_server_error(self.status_code) + + @property + def is_error(self) -> bool: + """ + A property which is `True` for 4xx and 5xx status codes, `False` otherwise. + """ + return codes.is_error(self.status_code) + + @property + def has_redirect_location(self) -> bool: + """ + Returns True for 3xx responses with a properly formed URL redirection, + `False` otherwise. + """ + return ( + self.status_code + in ( + # 301 (Cacheable redirect. Method may change to GET.) + codes.MOVED_PERMANENTLY, + # 302 (Uncacheable redirect. Method may change to GET.) + codes.FOUND, + # 303 (Client should make a GET or HEAD request.) + codes.SEE_OTHER, + # 307 (Equiv. 302, but retain method) + codes.TEMPORARY_REDIRECT, + # 308 (Equiv. 301, but retain method) + codes.PERMANENT_REDIRECT, + ) + and "Location" in self.headers + ) + + def raise_for_status(self) -> Response: + """ + Raise the `HTTPStatusError` if one occurred. + """ + request = self._request + if request is None: + raise RuntimeError( + "Cannot call `raise_for_status` as the request " + "instance has not been set on this response." + ) + + if self.is_success: + return self + + if self.has_redirect_location: + message = ( + "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" + "Redirect location: '{0.headers[location]}'\n" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" + ) + else: + message = ( + "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" + ) + + status_class = self.status_code // 100 + error_types = { + 1: "Informational response", + 3: "Redirect response", + 4: "Client error", + 5: "Server error", + } + error_type = error_types.get(status_class, "Invalid status code") + message = message.format(self, error_type=error_type) + raise HTTPStatusError(message, request=request, response=self) + + def json(self, **kwargs: typing.Any) -> typing.Any: + return jsonlib.loads(self.content, **kwargs) + + @property + def cookies(self) -> Cookies: + if not hasattr(self, "_cookies"): + self._cookies = Cookies() + self._cookies.extract_cookies(self) + return self._cookies + + @property + def links(self) -> dict[str | None, dict[str, str]]: + """ + Returns the parsed header links of the response, if any + """ + header = self.headers.get("link") + if header is None: + return {} + + return { + (link.get("rel") or link.get("url")): link + for link in _parse_header_links(header) + } + + @property + def num_bytes_downloaded(self) -> int: + return self._num_bytes_downloaded + + def __repr__(self) -> str: + return f"" + + def __getstate__(self) -> dict[str, typing.Any]: + return { + name: value + for name, value in self.__dict__.items() + if name not in ["extensions", "stream", "is_closed", "_decoder"] + } + + def __setstate__(self, state: dict[str, typing.Any]) -> None: + for name, value in state.items(): + setattr(self, name, value) + self.is_closed = True + self.extensions = {} + self.stream = UnattachedStream() + + def read(self) -> bytes: + """ + Read and return the response content. + """ + if not hasattr(self, "_content"): + self._content = b"".join(self.iter_bytes()) + return self._content + + def iter_bytes(self, chunk_size: int | None = None) -> typing.Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + This allows us to handle gzip, deflate, brotli, and zstd encoded responses. + """ + if hasattr(self, "_content"): + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), max(chunk_size, 1)): + yield self._content[i : i + chunk_size] + else: + decoder = self._get_content_decoder() + chunker = ByteChunker(chunk_size=chunk_size) + with request_context(request=self._request): + for raw_bytes in self.iter_raw(): + decoded = decoder.decode(raw_bytes) + for chunk in chunker.decode(decoded): + yield chunk + decoded = decoder.flush() + for chunk in chunker.decode(decoded): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> typing.Iterator[str]: + """ + A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + decoder = TextDecoder(encoding=self.encoding or "utf-8") + chunker = TextChunker(chunk_size=chunk_size) + with request_context(request=self._request): + for byte_content in self.iter_bytes(): + text_content = decoder.decode(byte_content) + for chunk in chunker.decode(text_content): + yield chunk + text_content = decoder.flush() + for chunk in chunker.decode(text_content): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + def iter_lines(self) -> typing.Iterator[str]: + decoder = LineDecoder() + with request_context(request=self._request): + for text in self.iter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line + + def iter_raw(self, chunk_size: int | None = None) -> typing.Iterator[bytes]: + """ + A byte-iterator over the raw response content. + """ + if self.is_stream_consumed: + raise StreamConsumed() + if self.is_closed: + raise StreamClosed() + if not isinstance(self.stream, SyncByteStream): + raise RuntimeError("Attempted to call a sync iterator on an async stream.") + + self.is_stream_consumed = True + self._num_bytes_downloaded = 0 + chunker = ByteChunker(chunk_size=chunk_size) + + with request_context(request=self._request): + for raw_stream_bytes in self.stream: + self._num_bytes_downloaded += len(raw_stream_bytes) + for chunk in chunker.decode(raw_stream_bytes): + yield chunk + + for chunk in chunker.flush(): + yield chunk + + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + Automatically called if the response body is read to completion. + """ + if not isinstance(self.stream, SyncByteStream): + raise RuntimeError("Attempted to call an sync close on an async stream.") + + if not self.is_closed: + self.is_closed = True + with request_context(request=self._request): + self.stream.close() + + async def aread(self) -> bytes: + """ + Read and return the response content. + """ + if not hasattr(self, "_content"): + self._content = b"".join([part async for part in self.aiter_bytes()]) + return self._content + + async def aiter_bytes( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + This allows us to handle gzip, deflate, brotli, and zstd encoded responses. + """ + if hasattr(self, "_content"): + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), max(chunk_size, 1)): + yield self._content[i : i + chunk_size] + else: + decoder = self._get_content_decoder() + chunker = ByteChunker(chunk_size=chunk_size) + with request_context(request=self._request): + async for raw_bytes in self.aiter_raw(): + decoded = decoder.decode(raw_bytes) + for chunk in chunker.decode(decoded): + yield chunk + decoded = decoder.flush() + for chunk in chunker.decode(decoded): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + async def aiter_text( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[str]: + """ + A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + decoder = TextDecoder(encoding=self.encoding or "utf-8") + chunker = TextChunker(chunk_size=chunk_size) + with request_context(request=self._request): + async for byte_content in self.aiter_bytes(): + text_content = decoder.decode(byte_content) + for chunk in chunker.decode(text_content): + yield chunk + text_content = decoder.flush() + for chunk in chunker.decode(text_content): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + async def aiter_lines(self) -> typing.AsyncIterator[str]: + decoder = LineDecoder() + with request_context(request=self._request): + async for text in self.aiter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line + + async def aiter_raw( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[bytes]: + """ + A byte-iterator over the raw response content. + """ + if self.is_stream_consumed: + raise StreamConsumed() + if self.is_closed: + raise StreamClosed() + if not isinstance(self.stream, AsyncByteStream): + raise RuntimeError("Attempted to call an async iterator on an sync stream.") + + self.is_stream_consumed = True + self._num_bytes_downloaded = 0 + chunker = ByteChunker(chunk_size=chunk_size) + + with request_context(request=self._request): + async for raw_stream_bytes in self.stream: + self._num_bytes_downloaded += len(raw_stream_bytes) + for chunk in chunker.decode(raw_stream_bytes): + yield chunk + + for chunk in chunker.flush(): + yield chunk + + await self.aclose() + + async def aclose(self) -> None: + """ + Close the response and release the connection. + Automatically called if the response body is read to completion. + """ + if not isinstance(self.stream, AsyncByteStream): + raise RuntimeError("Attempted to call an async close on an sync stream.") + + if not self.is_closed: + self.is_closed = True + with request_context(request=self._request): + await self.stream.aclose() + + +class Cookies(typing.MutableMapping[str, str]): + """ + HTTP Cookies, as a mutable mapping. + """ + + def __init__(self, cookies: CookieTypes | None = None) -> None: + if cookies is None or isinstance(cookies, dict): + self.jar = CookieJar() + if isinstance(cookies, dict): + for key, value in cookies.items(): + self.set(key, value) + elif isinstance(cookies, list): + self.jar = CookieJar() + for key, value in cookies: + self.set(key, value) + elif isinstance(cookies, Cookies): + self.jar = CookieJar() + for cookie in cookies.jar: + self.jar.set_cookie(cookie) + else: + self.jar = cookies + + def extract_cookies(self, response: Response) -> None: + """ + Loads any cookies based on the response `Set-Cookie` headers. + """ + urllib_response = self._CookieCompatResponse(response) + urllib_request = self._CookieCompatRequest(response.request) + + self.jar.extract_cookies(urllib_response, urllib_request) # type: ignore + + def set_cookie_header(self, request: Request) -> None: + """ + Sets an appropriate 'Cookie:' HTTP header on the `Request`. + """ + urllib_request = self._CookieCompatRequest(request) + self.jar.add_cookie_header(urllib_request) + + def set(self, name: str, value: str, domain: str = "", path: str = "/") -> None: + """ + Set a cookie value by name. May optionally include domain and path. + """ + kwargs = { + "version": 0, + "name": name, + "value": value, + "port": None, + "port_specified": False, + "domain": domain, + "domain_specified": bool(domain), + "domain_initial_dot": domain.startswith("."), + "path": path, + "path_specified": bool(path), + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + cookie = Cookie(**kwargs) # type: ignore + self.jar.set_cookie(cookie) + + def get( # type: ignore + self, + name: str, + default: str | None = None, + domain: str | None = None, + path: str | None = None, + ) -> str | None: + """ + Get a cookie by name. May optionally include domain and path + in order to specify exactly which cookie to retrieve. + """ + value = None + for cookie in self.jar: + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if value is not None: + message = f"Multiple cookies exist with name={name}" + raise CookieConflict(message) + value = cookie.value + + if value is None: + return default + return value + + def delete( + self, + name: str, + domain: str | None = None, + path: str | None = None, + ) -> None: + """ + Delete a cookie by name. May optionally include domain and path + in order to specify exactly which cookie to delete. + """ + if domain is not None and path is not None: + return self.jar.clear(domain, path, name) + + remove = [ + cookie + for cookie in self.jar + if cookie.name == name + and (domain is None or cookie.domain == domain) + and (path is None or cookie.path == path) + ] + + for cookie in remove: + self.jar.clear(cookie.domain, cookie.path, cookie.name) + + def clear(self, domain: str | None = None, path: str | None = None) -> None: + """ + Delete all cookies. Optionally include a domain and path in + order to only delete a subset of all the cookies. + """ + args = [] + if domain is not None: + args.append(domain) + if path is not None: + assert domain is not None + args.append(path) + self.jar.clear(*args) + + def update(self, cookies: CookieTypes | None = None) -> None: # type: ignore + cookies = Cookies(cookies) + for cookie in cookies.jar: + self.jar.set_cookie(cookie) + + def __setitem__(self, name: str, value: str) -> None: + return self.set(name, value) + + def __getitem__(self, name: str) -> str: + value = self.get(name) + if value is None: + raise KeyError(name) + return value + + def __delitem__(self, name: str) -> None: + return self.delete(name) + + def __len__(self) -> int: + return len(self.jar) + + def __iter__(self) -> typing.Iterator[str]: + return (cookie.name for cookie in self.jar) + + def __bool__(self) -> bool: + for _ in self.jar: + return True + return False + + def __repr__(self) -> str: + cookies_repr = ", ".join( + [ + f"" + for cookie in self.jar + ] + ) + + return f"" + + class _CookieCompatRequest(urllib.request.Request): + """ + Wraps a `Request` instance up in a compatibility interface suitable + for use with `CookieJar` operations. + """ + + def __init__(self, request: Request) -> None: + super().__init__( + url=str(request.url), + headers=dict(request.headers), + method=request.method, + ) + self.request = request + + def add_unredirected_header(self, key: str, value: str) -> None: + super().add_unredirected_header(key, value) + self.request.headers[key] = value + + class _CookieCompatResponse: + """ + Wraps a `Request` instance up in a compatibility interface suitable + for use with `CookieJar` operations. + """ + + def __init__(self, response: Response) -> None: + self.response = response + + def info(self) -> email.message.Message: + info = email.message.Message() + for key, value in self.response.headers.multi_items(): + # Note that setting `info[key]` here is an "append" operation, + # not a "replace" operation. + # https://docs.python.org/3/library/email.compat32-message.html#email.message.Message.__setitem__ + info[key] = value + return info diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py new file mode 100644 index 0000000..b4761af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py @@ -0,0 +1,300 @@ +from __future__ import annotations + +import io +import mimetypes +import os +import re +import typing +from pathlib import Path + +from ._types import ( + AsyncByteStream, + FileContent, + FileTypes, + RequestData, + RequestFiles, + SyncByteStream, +) +from ._utils import ( + peek_filelike_length, + primitive_value_to_str, + to_bytes, +) + +_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"} +_HTML5_FORM_ENCODING_REPLACEMENTS.update( + {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B} +) +_HTML5_FORM_ENCODING_RE = re.compile( + r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()]) +) + + +def _format_form_param(name: str, value: str) -> bytes: + """ + Encode a name/value pair within a multipart form. + """ + + def replacer(match: typing.Match[str]) -> str: + return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)] + + value = _HTML5_FORM_ENCODING_RE.sub(replacer, value) + return f'{name}="{value}"'.encode() + + +def _guess_content_type(filename: str | None) -> str | None: + """ + Guesses the mimetype based on a filename. Defaults to `application/octet-stream`. + + Returns `None` if `filename` is `None` or empty. + """ + if filename: + return mimetypes.guess_type(filename)[0] or "application/octet-stream" + return None + + +def get_multipart_boundary_from_content_type( + content_type: bytes | None, +) -> bytes | None: + if not content_type or not content_type.startswith(b"multipart/form-data"): + return None + # parse boundary according to + # https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 + if b";" in content_type: + for section in content_type.split(b";"): + if section.strip().lower().startswith(b"boundary="): + return section.strip()[len(b"boundary=") :].strip(b'"') + return None + + +class DataField: + """ + A single form field item, within a multipart form field. + """ + + def __init__(self, name: str, value: str | bytes | int | float | None) -> None: + if not isinstance(name, str): + raise TypeError( + f"Invalid type for name. Expected str, got {type(name)}: {name!r}" + ) + if value is not None and not isinstance(value, (str, bytes, int, float)): + raise TypeError( + "Invalid type for value. Expected primitive type," + f" got {type(value)}: {value!r}" + ) + self.name = name + self.value: str | bytes = ( + value if isinstance(value, bytes) else primitive_value_to_str(value) + ) + + def render_headers(self) -> bytes: + if not hasattr(self, "_headers"): + name = _format_form_param("name", self.name) + self._headers = b"".join( + [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"] + ) + + return self._headers + + def render_data(self) -> bytes: + if not hasattr(self, "_data"): + self._data = to_bytes(self.value) + + return self._data + + def get_length(self) -> int: + headers = self.render_headers() + data = self.render_data() + return len(headers) + len(data) + + def render(self) -> typing.Iterator[bytes]: + yield self.render_headers() + yield self.render_data() + + +class FileField: + """ + A single file field item, within a multipart form field. + """ + + CHUNK_SIZE = 64 * 1024 + + def __init__(self, name: str, value: FileTypes) -> None: + self.name = name + + fileobj: FileContent + + headers: dict[str, str] = {} + content_type: str | None = None + + # This large tuple based API largely mirror's requests' API + # It would be good to think of better APIs for this that we could + # include in httpx 2.0 since variable length tuples(especially of 4 elements) + # are quite unwieldly + if isinstance(value, tuple): + if len(value) == 2: + # neither the 3rd parameter (content_type) nor the 4th (headers) + # was included + filename, fileobj = value + elif len(value) == 3: + filename, fileobj, content_type = value + else: + # all 4 parameters included + filename, fileobj, content_type, headers = value # type: ignore + else: + filename = Path(str(getattr(value, "name", "upload"))).name + fileobj = value + + if content_type is None: + content_type = _guess_content_type(filename) + + has_content_type_header = any("content-type" in key.lower() for key in headers) + if content_type is not None and not has_content_type_header: + # note that unlike requests, we ignore the content_type provided in the 3rd + # tuple element if it is also included in the headers requests does + # the opposite (it overwrites the headerwith the 3rd tuple element) + headers["Content-Type"] = content_type + + if isinstance(fileobj, io.StringIO): + raise TypeError( + "Multipart file uploads require 'io.BytesIO', not 'io.StringIO'." + ) + if isinstance(fileobj, io.TextIOBase): + raise TypeError( + "Multipart file uploads must be opened in binary mode, not text mode." + ) + + self.filename = filename + self.file = fileobj + self.headers = headers + + def get_length(self) -> int | None: + headers = self.render_headers() + + if isinstance(self.file, (str, bytes)): + return len(headers) + len(to_bytes(self.file)) + + file_length = peek_filelike_length(self.file) + + # If we can't determine the filesize without reading it into memory, + # then return `None` here, to indicate an unknown file length. + if file_length is None: + return None + + return len(headers) + file_length + + def render_headers(self) -> bytes: + if not hasattr(self, "_headers"): + parts = [ + b"Content-Disposition: form-data; ", + _format_form_param("name", self.name), + ] + if self.filename: + filename = _format_form_param("filename", self.filename) + parts.extend([b"; ", filename]) + for header_name, header_value in self.headers.items(): + key, val = f"\r\n{header_name}: ".encode(), header_value.encode() + parts.extend([key, val]) + parts.append(b"\r\n\r\n") + self._headers = b"".join(parts) + + return self._headers + + def render_data(self) -> typing.Iterator[bytes]: + if isinstance(self.file, (str, bytes)): + yield to_bytes(self.file) + return + + if hasattr(self.file, "seek"): + try: + self.file.seek(0) + except io.UnsupportedOperation: + pass + + chunk = self.file.read(self.CHUNK_SIZE) + while chunk: + yield to_bytes(chunk) + chunk = self.file.read(self.CHUNK_SIZE) + + def render(self) -> typing.Iterator[bytes]: + yield self.render_headers() + yield from self.render_data() + + +class MultipartStream(SyncByteStream, AsyncByteStream): + """ + Request content as streaming multipart encoded form data. + """ + + def __init__( + self, + data: RequestData, + files: RequestFiles, + boundary: bytes | None = None, + ) -> None: + if boundary is None: + boundary = os.urandom(16).hex().encode("ascii") + + self.boundary = boundary + self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( + "ascii" + ) + self.fields = list(self._iter_fields(data, files)) + + def _iter_fields( + self, data: RequestData, files: RequestFiles + ) -> typing.Iterator[FileField | DataField]: + for name, value in data.items(): + if isinstance(value, (tuple, list)): + for item in value: + yield DataField(name=name, value=item) + else: + yield DataField(name=name, value=value) + + file_items = files.items() if isinstance(files, typing.Mapping) else files + for name, value in file_items: + yield FileField(name=name, value=value) + + def iter_chunks(self) -> typing.Iterator[bytes]: + for field in self.fields: + yield b"--%s\r\n" % self.boundary + yield from field.render() + yield b"\r\n" + yield b"--%s--\r\n" % self.boundary + + def get_content_length(self) -> int | None: + """ + Return the length of the multipart encoded content, or `None` if + any of the files have a length that cannot be determined upfront. + """ + boundary_length = len(self.boundary) + length = 0 + + for field in self.fields: + field_length = field.get_length() + if field_length is None: + return None + + length += 2 + boundary_length + 2 # b"--{boundary}\r\n" + length += field_length + length += 2 # b"\r\n" + + length += 2 + boundary_length + 4 # b"--{boundary}--\r\n" + return length + + # Content stream interface. + + def get_headers(self) -> dict[str, str]: + content_length = self.get_content_length() + content_type = self.content_type + if content_length is None: + return {"Transfer-Encoding": "chunked", "Content-Type": content_type} + return {"Content-Length": str(content_length), "Content-Type": content_type} + + def __iter__(self) -> typing.Iterator[bytes]: + for chunk in self.iter_chunks(): + yield chunk + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + for chunk in self.iter_chunks(): + yield chunk diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py new file mode 100644 index 0000000..133a623 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +from enum import IntEnum + +__all__ = ["codes"] + + +class codes(IntEnum): + """HTTP status codes and reason phrases + + Status codes from the following RFCs are all observed: + + * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 6585: Additional HTTP Status Codes + * RFC 3229: Delta encoding in HTTP + * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 + * RFC 5842: Binding Extensions to WebDAV + * RFC 7238: Permanent Redirect + * RFC 2295: Transparent Content Negotiation in HTTP + * RFC 2774: An HTTP Extension Framework + * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2) + * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) + * RFC 7725: An HTTP Status Code to Report Legal Obstacles + * RFC 8297: An HTTP Status Code for Indicating Hints + * RFC 8470: Using Early Data in HTTP + """ + + def __new__(cls, value: int, phrase: str = "") -> codes: + obj = int.__new__(cls, value) + obj._value_ = value + + obj.phrase = phrase # type: ignore[attr-defined] + return obj + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def get_reason_phrase(cls, value: int) -> str: + try: + return codes(value).phrase # type: ignore + except ValueError: + return "" + + @classmethod + def is_informational(cls, value: int) -> bool: + """ + Returns `True` for 1xx status codes, `False` otherwise. + """ + return 100 <= value <= 199 + + @classmethod + def is_success(cls, value: int) -> bool: + """ + Returns `True` for 2xx status codes, `False` otherwise. + """ + return 200 <= value <= 299 + + @classmethod + def is_redirect(cls, value: int) -> bool: + """ + Returns `True` for 3xx status codes, `False` otherwise. + """ + return 300 <= value <= 399 + + @classmethod + def is_client_error(cls, value: int) -> bool: + """ + Returns `True` for 4xx status codes, `False` otherwise. + """ + return 400 <= value <= 499 + + @classmethod + def is_server_error(cls, value: int) -> bool: + """ + Returns `True` for 5xx status codes, `False` otherwise. + """ + return 500 <= value <= 599 + + @classmethod + def is_error(cls, value: int) -> bool: + """ + Returns `True` for 4xx or 5xx status codes, `False` otherwise. + """ + return 400 <= value <= 599 + + # informational + CONTINUE = 100, "Continue" + SWITCHING_PROTOCOLS = 101, "Switching Protocols" + PROCESSING = 102, "Processing" + EARLY_HINTS = 103, "Early Hints" + + # success + OK = 200, "OK" + CREATED = 201, "Created" + ACCEPTED = 202, "Accepted" + NON_AUTHORITATIVE_INFORMATION = 203, "Non-Authoritative Information" + NO_CONTENT = 204, "No Content" + RESET_CONTENT = 205, "Reset Content" + PARTIAL_CONTENT = 206, "Partial Content" + MULTI_STATUS = 207, "Multi-Status" + ALREADY_REPORTED = 208, "Already Reported" + IM_USED = 226, "IM Used" + + # redirection + MULTIPLE_CHOICES = 300, "Multiple Choices" + MOVED_PERMANENTLY = 301, "Moved Permanently" + FOUND = 302, "Found" + SEE_OTHER = 303, "See Other" + NOT_MODIFIED = 304, "Not Modified" + USE_PROXY = 305, "Use Proxy" + TEMPORARY_REDIRECT = 307, "Temporary Redirect" + PERMANENT_REDIRECT = 308, "Permanent Redirect" + + # client error + BAD_REQUEST = 400, "Bad Request" + UNAUTHORIZED = 401, "Unauthorized" + PAYMENT_REQUIRED = 402, "Payment Required" + FORBIDDEN = 403, "Forbidden" + NOT_FOUND = 404, "Not Found" + METHOD_NOT_ALLOWED = 405, "Method Not Allowed" + NOT_ACCEPTABLE = 406, "Not Acceptable" + PROXY_AUTHENTICATION_REQUIRED = 407, "Proxy Authentication Required" + REQUEST_TIMEOUT = 408, "Request Timeout" + CONFLICT = 409, "Conflict" + GONE = 410, "Gone" + LENGTH_REQUIRED = 411, "Length Required" + PRECONDITION_FAILED = 412, "Precondition Failed" + REQUEST_ENTITY_TOO_LARGE = 413, "Request Entity Too Large" + REQUEST_URI_TOO_LONG = 414, "Request-URI Too Long" + UNSUPPORTED_MEDIA_TYPE = 415, "Unsupported Media Type" + REQUESTED_RANGE_NOT_SATISFIABLE = 416, "Requested Range Not Satisfiable" + EXPECTATION_FAILED = 417, "Expectation Failed" + IM_A_TEAPOT = 418, "I'm a teapot" + MISDIRECTED_REQUEST = 421, "Misdirected Request" + UNPROCESSABLE_ENTITY = 422, "Unprocessable Entity" + LOCKED = 423, "Locked" + FAILED_DEPENDENCY = 424, "Failed Dependency" + TOO_EARLY = 425, "Too Early" + UPGRADE_REQUIRED = 426, "Upgrade Required" + PRECONDITION_REQUIRED = 428, "Precondition Required" + TOO_MANY_REQUESTS = 429, "Too Many Requests" + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, "Request Header Fields Too Large" + UNAVAILABLE_FOR_LEGAL_REASONS = 451, "Unavailable For Legal Reasons" + + # server errors + INTERNAL_SERVER_ERROR = 500, "Internal Server Error" + NOT_IMPLEMENTED = 501, "Not Implemented" + BAD_GATEWAY = 502, "Bad Gateway" + SERVICE_UNAVAILABLE = 503, "Service Unavailable" + GATEWAY_TIMEOUT = 504, "Gateway Timeout" + HTTP_VERSION_NOT_SUPPORTED = 505, "HTTP Version Not Supported" + VARIANT_ALSO_NEGOTIATES = 506, "Variant Also Negotiates" + INSUFFICIENT_STORAGE = 507, "Insufficient Storage" + LOOP_DETECTED = 508, "Loop Detected" + NOT_EXTENDED = 510, "Not Extended" + NETWORK_AUTHENTICATION_REQUIRED = 511, "Network Authentication Required" + + +# Include lower-case styles for `requests` compatibility. +for code in codes: + setattr(codes, code._name_.lower(), int(code)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py new file mode 100644 index 0000000..7a32105 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py @@ -0,0 +1,15 @@ +from .asgi import * +from .base import * +from .default import * +from .mock import * +from .wsgi import * + +__all__ = [ + "ASGITransport", + "AsyncBaseTransport", + "BaseTransport", + "AsyncHTTPTransport", + "HTTPTransport", + "MockTransport", + "WSGITransport", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py new file mode 100644 index 0000000..2bc4efa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import typing + +from .._models import Request, Response +from .._types import AsyncByteStream +from .base import AsyncBaseTransport + +if typing.TYPE_CHECKING: # pragma: no cover + import asyncio + + import trio + + Event = typing.Union[asyncio.Event, trio.Event] + + +_Message = typing.MutableMapping[str, typing.Any] +_Receive = typing.Callable[[], typing.Awaitable[_Message]] +_Send = typing.Callable[ + [typing.MutableMapping[str, typing.Any]], typing.Awaitable[None] +] +_ASGIApp = typing.Callable[ + [typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None] +] + +__all__ = ["ASGITransport"] + + +def is_running_trio() -> bool: + try: + # sniffio is a dependency of trio. + + # See https://github.com/python-trio/trio/issues/2802 + import sniffio + + if sniffio.current_async_library() == "trio": + return True + except ImportError: # pragma: nocover + pass + + return False + + +def create_event() -> Event: + if is_running_trio(): + import trio + + return trio.Event() + + import asyncio + + return asyncio.Event() + + +class ASGIResponseStream(AsyncByteStream): + def __init__(self, body: list[bytes]) -> None: + self._body = body + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + yield b"".join(self._body) + + +class ASGITransport(AsyncBaseTransport): + """ + A custom AsyncTransport that handles sending requests directly to an ASGI app. + + ```python + transport = httpx.ASGITransport( + app=app, + root_path="/submount", + client=("1.2.3.4", 123) + ) + client = httpx.AsyncClient(transport=transport) + ``` + + Arguments: + + * `app` - The ASGI application. + * `raise_app_exceptions` - Boolean indicating if exceptions in the application + should be raised. Default to `True`. Can be set to `False` for use cases + such as testing the content of a client 500 response. + * `root_path` - The root path on which the ASGI application should be mounted. + * `client` - A two-tuple indicating the client IP and port of incoming requests. + ``` + """ + + def __init__( + self, + app: _ASGIApp, + raise_app_exceptions: bool = True, + root_path: str = "", + client: tuple[str, int] = ("127.0.0.1", 123), + ) -> None: + self.app = app + self.raise_app_exceptions = raise_app_exceptions + self.root_path = root_path + self.client = client + + async def handle_async_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, AsyncByteStream) + + # ASGI scope. + scope = { + "type": "http", + "asgi": {"version": "3.0"}, + "http_version": "1.1", + "method": request.method, + "headers": [(k.lower(), v) for (k, v) in request.headers.raw], + "scheme": request.url.scheme, + "path": request.url.path, + "raw_path": request.url.raw_path.split(b"?")[0], + "query_string": request.url.query, + "server": (request.url.host, request.url.port), + "client": self.client, + "root_path": self.root_path, + } + + # Request. + request_body_chunks = request.stream.__aiter__() + request_complete = False + + # Response. + status_code = None + response_headers = None + body_parts = [] + response_started = False + response_complete = create_event() + + # ASGI callables. + + async def receive() -> dict[str, typing.Any]: + nonlocal request_complete + + if request_complete: + await response_complete.wait() + return {"type": "http.disconnect"} + + try: + body = await request_body_chunks.__anext__() + except StopAsyncIteration: + request_complete = True + return {"type": "http.request", "body": b"", "more_body": False} + return {"type": "http.request", "body": body, "more_body": True} + + async def send(message: typing.MutableMapping[str, typing.Any]) -> None: + nonlocal status_code, response_headers, response_started + + if message["type"] == "http.response.start": + assert not response_started + + status_code = message["status"] + response_headers = message.get("headers", []) + response_started = True + + elif message["type"] == "http.response.body": + assert not response_complete.is_set() + body = message.get("body", b"") + more_body = message.get("more_body", False) + + if body and request.method != "HEAD": + body_parts.append(body) + + if not more_body: + response_complete.set() + + try: + await self.app(scope, receive, send) + except Exception: # noqa: PIE-786 + if self.raise_app_exceptions: + raise + + response_complete.set() + if status_code is None: + status_code = 500 + if response_headers is None: + response_headers = {} + + assert response_complete.is_set() + assert status_code is not None + assert response_headers is not None + + stream = ASGIResponseStream(body_parts) + + return Response(status_code, headers=response_headers, stream=stream) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py new file mode 100644 index 0000000..66fd99d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import typing +from types import TracebackType + +from .._models import Request, Response + +T = typing.TypeVar("T", bound="BaseTransport") +A = typing.TypeVar("A", bound="AsyncBaseTransport") + +__all__ = ["AsyncBaseTransport", "BaseTransport"] + + +class BaseTransport: + def __enter__(self: T) -> T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self.close() + + def handle_request(self, request: Request) -> Response: + """ + Send a single HTTP request and return a response. + + Developers shouldn't typically ever need to call into this API directly, + since the Client class provides all the higher level user-facing API + niceties. + + In order to properly release any network resources, the response + stream should *either* be consumed immediately, with a call to + `response.stream.read()`, or else the `handle_request` call should + be followed with a try/finally block to ensuring the stream is + always closed. + + Example usage: + + with httpx.HTTPTransport() as transport: + req = httpx.Request( + method=b"GET", + url=(b"https", b"www.example.com", 443, b"/"), + headers=[(b"Host", b"www.example.com")], + ) + resp = transport.handle_request(req) + body = resp.stream.read() + print(resp.status_code, resp.headers, body) + + + Takes a `Request` instance as the only argument. + + Returns a `Response` instance. + """ + raise NotImplementedError( + "The 'handle_request' method must be implemented." + ) # pragma: no cover + + def close(self) -> None: + pass + + +class AsyncBaseTransport: + async def __aenter__(self: A) -> A: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + await self.aclose() + + async def handle_async_request( + self, + request: Request, + ) -> Response: + raise NotImplementedError( + "The 'handle_async_request' method must be implemented." + ) # pragma: no cover + + async def aclose(self) -> None: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py new file mode 100644 index 0000000..d5aa05f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py @@ -0,0 +1,406 @@ +""" +Custom transports, with nicely configured defaults. + +The following additional keyword arguments are currently supported by httpcore... + +* uds: str +* local_address: str +* retries: int + +Example usages... + +# Disable HTTP/2 on a single specific domain. +mounts = { + "all://": httpx.HTTPTransport(http2=True), + "all://*example.org": httpx.HTTPTransport() +} + +# Using advanced httpcore configuration, with connection retries. +transport = httpx.HTTPTransport(retries=1) +client = httpx.Client(transport=transport) + +# Using advanced httpcore configuration, with unix domain sockets. +transport = httpx.HTTPTransport(uds="socket.uds") +client = httpx.Client(transport=transport) +""" + +from __future__ import annotations + +import contextlib +import typing +from types import TracebackType + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + + import httpx # pragma: no cover + +from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context +from .._exceptions import ( + ConnectError, + ConnectTimeout, + LocalProtocolError, + NetworkError, + PoolTimeout, + ProtocolError, + ProxyError, + ReadError, + ReadTimeout, + RemoteProtocolError, + TimeoutException, + UnsupportedProtocol, + WriteError, + WriteTimeout, +) +from .._models import Request, Response +from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream +from .._urls import URL +from .base import AsyncBaseTransport, BaseTransport + +T = typing.TypeVar("T", bound="HTTPTransport") +A = typing.TypeVar("A", bound="AsyncHTTPTransport") + +SOCKET_OPTION = typing.Union[ + typing.Tuple[int, int, int], + typing.Tuple[int, int, typing.Union[bytes, bytearray]], + typing.Tuple[int, int, None, int], +] + +__all__ = ["AsyncHTTPTransport", "HTTPTransport"] + +HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {} + + +def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]: + import httpcore + + return { + httpcore.TimeoutException: TimeoutException, + httpcore.ConnectTimeout: ConnectTimeout, + httpcore.ReadTimeout: ReadTimeout, + httpcore.WriteTimeout: WriteTimeout, + httpcore.PoolTimeout: PoolTimeout, + httpcore.NetworkError: NetworkError, + httpcore.ConnectError: ConnectError, + httpcore.ReadError: ReadError, + httpcore.WriteError: WriteError, + httpcore.ProxyError: ProxyError, + httpcore.UnsupportedProtocol: UnsupportedProtocol, + httpcore.ProtocolError: ProtocolError, + httpcore.LocalProtocolError: LocalProtocolError, + httpcore.RemoteProtocolError: RemoteProtocolError, + } + + +@contextlib.contextmanager +def map_httpcore_exceptions() -> typing.Iterator[None]: + global HTTPCORE_EXC_MAP + if len(HTTPCORE_EXC_MAP) == 0: + HTTPCORE_EXC_MAP = _load_httpcore_exceptions() + try: + yield + except Exception as exc: + mapped_exc = None + + for from_exc, to_exc in HTTPCORE_EXC_MAP.items(): + if not isinstance(exc, from_exc): + continue + # We want to map to the most specific exception we can find. + # Eg if `exc` is an `httpcore.ReadTimeout`, we want to map to + # `httpx.ReadTimeout`, not just `httpx.TimeoutException`. + if mapped_exc is None or issubclass(to_exc, mapped_exc): + mapped_exc = to_exc + + if mapped_exc is None: # pragma: no cover + raise + + message = str(exc) + raise mapped_exc(message) from exc + + +class ResponseStream(SyncByteStream): + def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None: + self._httpcore_stream = httpcore_stream + + def __iter__(self) -> typing.Iterator[bytes]: + with map_httpcore_exceptions(): + for part in self._httpcore_stream: + yield part + + def close(self) -> None: + if hasattr(self._httpcore_stream, "close"): + self._httpcore_stream.close() + + +class HTTPTransport(BaseTransport): + def __init__( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + proxy: ProxyTypes | None = None, + uds: str | None = None, + local_address: str | None = None, + retries: int = 0, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + import httpcore + + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) + + if proxy is None: + self._pool = httpcore.ConnectionPool( + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + uds=uds, + local_address=local_address, + retries=retries, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("http", "https"): + self._pool = httpcore.HTTPProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + proxy_headers=proxy.headers.raw, + ssl_context=ssl_context, + proxy_ssl_context=proxy.ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("socks5", "socks5h"): + try: + import socksio # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using SOCKS proxy, but the 'socksio' package is not installed. " + "Make sure to install httpx using `pip install httpx[socks]`." + ) from None + + self._pool = httpcore.SOCKSProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + ) + else: # pragma: no cover + raise ValueError( + "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," + f" but got {proxy.url.scheme!r}." + ) + + def __enter__(self: T) -> T: # Use generics for subclass support. + self._pool.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + with map_httpcore_exceptions(): + self._pool.__exit__(exc_type, exc_value, traceback) + + def handle_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, SyncByteStream) + import httpcore + + req = httpcore.Request( + method=request.method, + url=httpcore.URL( + scheme=request.url.raw_scheme, + host=request.url.raw_host, + port=request.url.port, + target=request.url.raw_path, + ), + headers=request.headers.raw, + content=request.stream, + extensions=request.extensions, + ) + with map_httpcore_exceptions(): + resp = self._pool.handle_request(req) + + assert isinstance(resp.stream, typing.Iterable) + + return Response( + status_code=resp.status, + headers=resp.headers, + stream=ResponseStream(resp.stream), + extensions=resp.extensions, + ) + + def close(self) -> None: + self._pool.close() + + +class AsyncResponseStream(AsyncByteStream): + def __init__(self, httpcore_stream: typing.AsyncIterable[bytes]) -> None: + self._httpcore_stream = httpcore_stream + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + with map_httpcore_exceptions(): + async for part in self._httpcore_stream: + yield part + + async def aclose(self) -> None: + if hasattr(self._httpcore_stream, "aclose"): + await self._httpcore_stream.aclose() + + +class AsyncHTTPTransport(AsyncBaseTransport): + def __init__( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + proxy: ProxyTypes | None = None, + uds: str | None = None, + local_address: str | None = None, + retries: int = 0, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + import httpcore + + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) + + if proxy is None: + self._pool = httpcore.AsyncConnectionPool( + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + uds=uds, + local_address=local_address, + retries=retries, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("http", "https"): + self._pool = httpcore.AsyncHTTPProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + proxy_headers=proxy.headers.raw, + proxy_ssl_context=proxy.ssl_context, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("socks5", "socks5h"): + try: + import socksio # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using SOCKS proxy, but the 'socksio' package is not installed. " + "Make sure to install httpx using `pip install httpx[socks]`." + ) from None + + self._pool = httpcore.AsyncSOCKSProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + ) + else: # pragma: no cover + raise ValueError( + "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," + " but got {proxy.url.scheme!r}." + ) + + async def __aenter__(self: A) -> A: # Use generics for subclass support. + await self._pool.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + with map_httpcore_exceptions(): + await self._pool.__aexit__(exc_type, exc_value, traceback) + + async def handle_async_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, AsyncByteStream) + import httpcore + + req = httpcore.Request( + method=request.method, + url=httpcore.URL( + scheme=request.url.raw_scheme, + host=request.url.raw_host, + port=request.url.port, + target=request.url.raw_path, + ), + headers=request.headers.raw, + content=request.stream, + extensions=request.extensions, + ) + with map_httpcore_exceptions(): + resp = await self._pool.handle_async_request(req) + + assert isinstance(resp.stream, typing.AsyncIterable) + + return Response( + status_code=resp.status, + headers=resp.headers, + stream=AsyncResponseStream(resp.stream), + extensions=resp.extensions, + ) + + async def aclose(self) -> None: + await self._pool.aclose() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py new file mode 100644 index 0000000..8c418f5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .._models import Request, Response +from .base import AsyncBaseTransport, BaseTransport + +SyncHandler = typing.Callable[[Request], Response] +AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]] + + +__all__ = ["MockTransport"] + + +class MockTransport(AsyncBaseTransport, BaseTransport): + def __init__(self, handler: SyncHandler | AsyncHandler) -> None: + self.handler = handler + + def handle_request( + self, + request: Request, + ) -> Response: + request.read() + response = self.handler(request) + if not isinstance(response, Response): # pragma: no cover + raise TypeError("Cannot use an async handler in a sync Client") + return response + + async def handle_async_request( + self, + request: Request, + ) -> Response: + await request.aread() + response = self.handler(request) + + # Allow handler to *optionally* be an `async` function. + # If it is, then the `response` variable need to be awaited to actually + # return the result. + + if not isinstance(response, Response): + response = await response + + return response diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py new file mode 100644 index 0000000..8592ffe --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import io +import itertools +import sys +import typing + +from .._models import Request, Response +from .._types import SyncByteStream +from .base import BaseTransport + +if typing.TYPE_CHECKING: + from _typeshed import OptExcInfo # pragma: no cover + from _typeshed.wsgi import WSGIApplication # pragma: no cover + +_T = typing.TypeVar("_T") + + +__all__ = ["WSGITransport"] + + +def _skip_leading_empty_chunks(body: typing.Iterable[_T]) -> typing.Iterable[_T]: + body = iter(body) + for chunk in body: + if chunk: + return itertools.chain([chunk], body) + return [] + + +class WSGIByteStream(SyncByteStream): + def __init__(self, result: typing.Iterable[bytes]) -> None: + self._close = getattr(result, "close", None) + self._result = _skip_leading_empty_chunks(result) + + def __iter__(self) -> typing.Iterator[bytes]: + for part in self._result: + yield part + + def close(self) -> None: + if self._close is not None: + self._close() + + +class WSGITransport(BaseTransport): + """ + A custom transport that handles sending requests directly to an WSGI app. + The simplest way to use this functionality is to use the `app` argument. + + ``` + client = httpx.Client(app=app) + ``` + + Alternatively, you can setup the transport instance explicitly. + This allows you to include any additional configuration arguments specific + to the WSGITransport class: + + ``` + transport = httpx.WSGITransport( + app=app, + script_name="/submount", + remote_addr="1.2.3.4" + ) + client = httpx.Client(transport=transport) + ``` + + Arguments: + + * `app` - The WSGI application. + * `raise_app_exceptions` - Boolean indicating if exceptions in the application + should be raised. Default to `True`. Can be set to `False` for use cases + such as testing the content of a client 500 response. + * `script_name` - The root path on which the WSGI application should be mounted. + * `remote_addr` - A string indicating the client IP of incoming requests. + ``` + """ + + def __init__( + self, + app: WSGIApplication, + raise_app_exceptions: bool = True, + script_name: str = "", + remote_addr: str = "127.0.0.1", + wsgi_errors: typing.TextIO | None = None, + ) -> None: + self.app = app + self.raise_app_exceptions = raise_app_exceptions + self.script_name = script_name + self.remote_addr = remote_addr + self.wsgi_errors = wsgi_errors + + def handle_request(self, request: Request) -> Response: + request.read() + wsgi_input = io.BytesIO(request.content) + + port = request.url.port or {"http": 80, "https": 443}[request.url.scheme] + environ = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": request.url.scheme, + "wsgi.input": wsgi_input, + "wsgi.errors": self.wsgi_errors or sys.stderr, + "wsgi.multithread": True, + "wsgi.multiprocess": False, + "wsgi.run_once": False, + "REQUEST_METHOD": request.method, + "SCRIPT_NAME": self.script_name, + "PATH_INFO": request.url.path, + "QUERY_STRING": request.url.query.decode("ascii"), + "SERVER_NAME": request.url.host, + "SERVER_PORT": str(port), + "SERVER_PROTOCOL": "HTTP/1.1", + "REMOTE_ADDR": self.remote_addr, + } + for header_key, header_value in request.headers.raw: + key = header_key.decode("ascii").upper().replace("-", "_") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = "HTTP_" + key + environ[key] = header_value.decode("ascii") + + seen_status = None + seen_response_headers = None + seen_exc_info = None + + def start_response( + status: str, + response_headers: list[tuple[str, str]], + exc_info: OptExcInfo | None = None, + ) -> typing.Callable[[bytes], typing.Any]: + nonlocal seen_status, seen_response_headers, seen_exc_info + seen_status = status + seen_response_headers = response_headers + seen_exc_info = exc_info + return lambda _: None + + result = self.app(environ, start_response) + + stream = WSGIByteStream(result) + + assert seen_status is not None + assert seen_response_headers is not None + if seen_exc_info and seen_exc_info[0] and self.raise_app_exceptions: + raise seen_exc_info[1] + + status_code = int(seen_status.split()[0]) + headers = [ + (key.encode("ascii"), value.encode("ascii")) + for key, value in seen_response_headers + ] + + return Response(status_code, headers=headers, stream=stream) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py new file mode 100644 index 0000000..704dfdf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_types.py @@ -0,0 +1,114 @@ +""" +Type definitions for type checking purposes. +""" + +from http.cookiejar import CookieJar +from typing import ( + IO, + TYPE_CHECKING, + Any, + AsyncIterable, + AsyncIterator, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Union, +) + +if TYPE_CHECKING: # pragma: no cover + from ._auth import Auth # noqa: F401 + from ._config import Proxy, Timeout # noqa: F401 + from ._models import Cookies, Headers, Request # noqa: F401 + from ._urls import URL, QueryParams # noqa: F401 + + +PrimitiveData = Optional[Union[str, int, float, bool]] + +URLTypes = Union["URL", str] + +QueryParamTypes = Union[ + "QueryParams", + Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]], + List[Tuple[str, PrimitiveData]], + Tuple[Tuple[str, PrimitiveData], ...], + str, + bytes, +] + +HeaderTypes = Union[ + "Headers", + Mapping[str, str], + Mapping[bytes, bytes], + Sequence[Tuple[str, str]], + Sequence[Tuple[bytes, bytes]], +] + +CookieTypes = Union["Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]] + +TimeoutTypes = Union[ + Optional[float], + Tuple[Optional[float], Optional[float], Optional[float], Optional[float]], + "Timeout", +] +ProxyTypes = Union["URL", str, "Proxy"] +CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]] + +AuthTypes = Union[ + Tuple[Union[str, bytes], Union[str, bytes]], + Callable[["Request"], "Request"], + "Auth", +] + +RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] +ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] +ResponseExtensions = Mapping[str, Any] + +RequestData = Mapping[str, Any] + +FileContent = Union[IO[bytes], bytes, str] +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] + +RequestExtensions = Mapping[str, Any] + +__all__ = ["AsyncByteStream", "SyncByteStream"] + + +class SyncByteStream: + def __iter__(self) -> Iterator[bytes]: + raise NotImplementedError( + "The '__iter__' method must be implemented." + ) # pragma: no cover + yield b"" # pragma: no cover + + def close(self) -> None: + """ + Subclasses can override this method to release any network resources + after a request/response cycle is complete. + """ + + +class AsyncByteStream: + async def __aiter__(self) -> AsyncIterator[bytes]: + raise NotImplementedError( + "The '__aiter__' method must be implemented." + ) # pragma: no cover + yield b"" # pragma: no cover + + async def aclose(self) -> None: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py new file mode 100644 index 0000000..bf190fd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py @@ -0,0 +1,527 @@ +""" +An implementation of `urlparse` that provides URL validation and normalization +as described by RFC3986. + +We rely on this implementation rather than the one in Python's stdlib, because: + +* It provides more complete URL validation. +* It properly differentiates between an empty querystring and an absent querystring, + to distinguish URLs with a trailing '?'. +* It handles scheme, hostname, port, and path normalization. +* It supports IDNA hostnames, normalizing them to their encoded form. +* The API supports passing individual components, as well as the complete URL string. + +Previously we relied on the excellent `rfc3986` package to handle URL parsing and +validation, but this module provides a simpler alternative, with less indirection +required. +""" + +from __future__ import annotations + +import ipaddress +import re +import typing + +import idna + +from ._exceptions import InvalidURL + +MAX_URL_LENGTH = 65536 + +# https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3 +UNRESERVED_CHARACTERS = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" +) +SUB_DELIMS = "!$&'()*+,;=" + +PERCENT_ENCODED_REGEX = re.compile("%[A-Fa-f0-9]{2}") + +# https://url.spec.whatwg.org/#percent-encoded-bytes + +# The fragment percent-encode set is the C0 control percent-encode set +# and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`). +FRAG_SAFE = "".join( + [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)] +) + +# The query percent-encode set is the C0 control percent-encode set +# and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>). +QUERY_SAFE = "".join( + [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)] +) + +# The path percent-encode set is the query percent-encode set +# and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}). +PATH_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D) + ] +) + +# The userinfo percent-encode set is the path percent-encode set +# and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), +# U+005B ([) to U+005E (^), inclusive, and U+007C (|). +USERNAME_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) +PASSWORD_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) +# Note... The terminology 'userinfo' percent-encode set in the WHATWG document +# is used for the username and password quoting. For the joint userinfo component +# we remove U+003A (:) from the safe set. +USERINFO_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) + + +# {scheme}: (optional) +# //{authority} (optional) +# {path} +# ?{query} (optional) +# #{fragment} (optional) +URL_REGEX = re.compile( + ( + r"(?:(?P{scheme}):)?" + r"(?://(?P{authority}))?" + r"(?P{path})" + r"(?:\?(?P{query}))?" + r"(?:#(?P{fragment}))?" + ).format( + scheme="([a-zA-Z][a-zA-Z0-9+.-]*)?", + authority="[^/?#]*", + path="[^?#]*", + query="[^#]*", + fragment=".*", + ) +) + +# {userinfo}@ (optional) +# {host} +# :{port} (optional) +AUTHORITY_REGEX = re.compile( + ( + r"(?:(?P{userinfo})@)?" r"(?P{host})" r":?(?P{port})?" + ).format( + userinfo=".*", # Any character sequence. + host="(\\[.*\\]|[^:@]*)", # Either any character sequence excluding ':' or '@', + # or an IPv6 address enclosed within square brackets. + port=".*", # Any character sequence. + ) +) + + +# If we call urlparse with an individual component, then we need to regex +# validate that component individually. +# Note that we're duplicating the same strings as above. Shock! Horror!! +COMPONENT_REGEX = { + "scheme": re.compile("([a-zA-Z][a-zA-Z0-9+.-]*)?"), + "authority": re.compile("[^/?#]*"), + "path": re.compile("[^?#]*"), + "query": re.compile("[^#]*"), + "fragment": re.compile(".*"), + "userinfo": re.compile("[^@]*"), + "host": re.compile("(\\[.*\\]|[^:]*)"), + "port": re.compile(".*"), +} + + +# We use these simple regexs as a first pass before handing off to +# the stdlib 'ipaddress' module for IP address validation. +IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$") +IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$") + + +class ParseResult(typing.NamedTuple): + scheme: str + userinfo: str + host: str + port: int | None + path: str + query: str | None + fragment: str | None + + @property + def authority(self) -> str: + return "".join( + [ + f"{self.userinfo}@" if self.userinfo else "", + f"[{self.host}]" if ":" in self.host else self.host, + f":{self.port}" if self.port is not None else "", + ] + ) + + @property + def netloc(self) -> str: + return "".join( + [ + f"[{self.host}]" if ":" in self.host else self.host, + f":{self.port}" if self.port is not None else "", + ] + ) + + def copy_with(self, **kwargs: str | None) -> ParseResult: + if not kwargs: + return self + + defaults = { + "scheme": self.scheme, + "authority": self.authority, + "path": self.path, + "query": self.query, + "fragment": self.fragment, + } + defaults.update(kwargs) + return urlparse("", **defaults) + + def __str__(self) -> str: + authority = self.authority + return "".join( + [ + f"{self.scheme}:" if self.scheme else "", + f"//{authority}" if authority else "", + self.path, + f"?{self.query}" if self.query is not None else "", + f"#{self.fragment}" if self.fragment is not None else "", + ] + ) + + +def urlparse(url: str = "", **kwargs: str | None) -> ParseResult: + # Initial basic checks on allowable URLs. + # --------------------------------------- + + # Hard limit the maximum allowable URL length. + if len(url) > MAX_URL_LENGTH: + raise InvalidURL("URL too long") + + # If a URL includes any ASCII control characters including \t, \r, \n, + # then treat it as invalid. + if any(char.isascii() and not char.isprintable() for char in url): + char = next(char for char in url if char.isascii() and not char.isprintable()) + idx = url.find(char) + error = ( + f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}." + ) + raise InvalidURL(error) + + # Some keyword arguments require special handling. + # ------------------------------------------------ + + # Coerce "port" to a string, if it is provided as an integer. + if "port" in kwargs: + port = kwargs["port"] + kwargs["port"] = str(port) if isinstance(port, int) else port + + # Replace "netloc" with "host and "port". + if "netloc" in kwargs: + netloc = kwargs.pop("netloc") or "" + kwargs["host"], _, kwargs["port"] = netloc.partition(":") + + # Replace "username" and/or "password" with "userinfo". + if "username" in kwargs or "password" in kwargs: + username = quote(kwargs.pop("username", "") or "", safe=USERNAME_SAFE) + password = quote(kwargs.pop("password", "") or "", safe=PASSWORD_SAFE) + kwargs["userinfo"] = f"{username}:{password}" if password else username + + # Replace "raw_path" with "path" and "query". + if "raw_path" in kwargs: + raw_path = kwargs.pop("raw_path") or "" + kwargs["path"], seperator, kwargs["query"] = raw_path.partition("?") + if not seperator: + kwargs["query"] = None + + # Ensure that IPv6 "host" addresses are always escaped with "[...]". + if "host" in kwargs: + host = kwargs.get("host") or "" + if ":" in host and not (host.startswith("[") and host.endswith("]")): + kwargs["host"] = f"[{host}]" + + # If any keyword arguments are provided, ensure they are valid. + # ------------------------------------------------------------- + + for key, value in kwargs.items(): + if value is not None: + if len(value) > MAX_URL_LENGTH: + raise InvalidURL(f"URL component '{key}' too long") + + # If a component includes any ASCII control characters including \t, \r, \n, + # then treat it as invalid. + if any(char.isascii() and not char.isprintable() for char in value): + char = next( + char for char in value if char.isascii() and not char.isprintable() + ) + idx = value.find(char) + error = ( + f"Invalid non-printable ASCII character in URL {key} component, " + f"{char!r} at position {idx}." + ) + raise InvalidURL(error) + + # Ensure that keyword arguments match as a valid regex. + if not COMPONENT_REGEX[key].fullmatch(value): + raise InvalidURL(f"Invalid URL component '{key}'") + + # The URL_REGEX will always match, but may have empty components. + url_match = URL_REGEX.match(url) + assert url_match is not None + url_dict = url_match.groupdict() + + # * 'scheme', 'authority', and 'path' may be empty strings. + # * 'query' may be 'None', indicating no trailing "?" portion. + # Any string including the empty string, indicates a trailing "?". + # * 'fragment' may be 'None', indicating no trailing "#" portion. + # Any string including the empty string, indicates a trailing "#". + scheme = kwargs.get("scheme", url_dict["scheme"]) or "" + authority = kwargs.get("authority", url_dict["authority"]) or "" + path = kwargs.get("path", url_dict["path"]) or "" + query = kwargs.get("query", url_dict["query"]) + frag = kwargs.get("fragment", url_dict["fragment"]) + + # The AUTHORITY_REGEX will always match, but may have empty components. + authority_match = AUTHORITY_REGEX.match(authority) + assert authority_match is not None + authority_dict = authority_match.groupdict() + + # * 'userinfo' and 'host' may be empty strings. + # * 'port' may be 'None'. + userinfo = kwargs.get("userinfo", authority_dict["userinfo"]) or "" + host = kwargs.get("host", authority_dict["host"]) or "" + port = kwargs.get("port", authority_dict["port"]) + + # Normalize and validate each component. + # We end up with a parsed representation of the URL, + # with components that are plain ASCII bytestrings. + parsed_scheme: str = scheme.lower() + parsed_userinfo: str = quote(userinfo, safe=USERINFO_SAFE) + parsed_host: str = encode_host(host) + parsed_port: int | None = normalize_port(port, scheme) + + has_scheme = parsed_scheme != "" + has_authority = ( + parsed_userinfo != "" or parsed_host != "" or parsed_port is not None + ) + validate_path(path, has_scheme=has_scheme, has_authority=has_authority) + if has_scheme or has_authority: + path = normalize_path(path) + + parsed_path: str = quote(path, safe=PATH_SAFE) + parsed_query: str | None = None if query is None else quote(query, safe=QUERY_SAFE) + parsed_frag: str | None = None if frag is None else quote(frag, safe=FRAG_SAFE) + + # The parsed ASCII bytestrings are our canonical form. + # All properties of the URL are derived from these. + return ParseResult( + parsed_scheme, + parsed_userinfo, + parsed_host, + parsed_port, + parsed_path, + parsed_query, + parsed_frag, + ) + + +def encode_host(host: str) -> str: + if not host: + return "" + + elif IPv4_STYLE_HOSTNAME.match(host): + # Validate IPv4 hostnames like #.#.#.# + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + try: + ipaddress.IPv4Address(host) + except ipaddress.AddressValueError: + raise InvalidURL(f"Invalid IPv4 address: {host!r}") + return host + + elif IPv6_STYLE_HOSTNAME.match(host): + # Validate IPv6 hostnames like [...] + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # "A host identified by an Internet Protocol literal address, version 6 + # [RFC3513] or later, is distinguished by enclosing the IP literal + # within square brackets ("[" and "]"). This is the only place where + # square bracket characters are allowed in the URI syntax." + try: + ipaddress.IPv6Address(host[1:-1]) + except ipaddress.AddressValueError: + raise InvalidURL(f"Invalid IPv6 address: {host!r}") + return host[1:-1] + + elif host.isascii(): + # Regular ASCII hostnames + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # reg-name = *( unreserved / pct-encoded / sub-delims ) + WHATWG_SAFE = '"`{}%|\\' + return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE) + + # IDNA hostnames + try: + return idna.encode(host.lower()).decode("ascii") + except idna.IDNAError: + raise InvalidURL(f"Invalid IDNA hostname: {host!r}") + + +def normalize_port(port: str | int | None, scheme: str) -> int | None: + # From https://tools.ietf.org/html/rfc3986#section-3.2.3 + # + # "A scheme may define a default port. For example, the "http" scheme + # defines a default port of "80", corresponding to its reserved TCP + # port number. The type of port designated by the port number (e.g., + # TCP, UDP, SCTP) is defined by the URI scheme. URI producers and + # normalizers should omit the port component and its ":" delimiter if + # port is empty or if its value would be the same as that of the + # scheme's default." + if port is None or port == "": + return None + + try: + port_as_int = int(port) + except ValueError: + raise InvalidURL(f"Invalid port: {port!r}") + + # See https://url.spec.whatwg.org/#url-miscellaneous + default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get( + scheme + ) + if port_as_int == default_port: + return None + return port_as_int + + +def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None: + """ + Path validation rules that depend on if the URL contains + a scheme or authority component. + + See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3 + """ + if has_authority: + # If a URI contains an authority component, then the path component + # must either be empty or begin with a slash ("/") character." + if path and not path.startswith("/"): + raise InvalidURL("For absolute URLs, path must be empty or begin with '/'") + + if not has_scheme and not has_authority: + # If a URI does not contain an authority component, then the path cannot begin + # with two slash characters ("//"). + if path.startswith("//"): + raise InvalidURL("Relative URLs cannot have a path starting with '//'") + + # In addition, a URI reference (Section 4.1) may be a relative-path reference, + # in which case the first path segment cannot contain a colon (":") character. + if path.startswith(":"): + raise InvalidURL("Relative URLs cannot have a path starting with ':'") + + +def normalize_path(path: str) -> str: + """ + Drop "." and ".." segments from a URL path. + + For example: + + normalize_path("/path/./to/somewhere/..") == "/path/to" + """ + # Fast return when no '.' characters in the path. + if "." not in path: + return path + + components = path.split("/") + + # Fast return when no '.' or '..' components in the path. + if "." not in components and ".." not in components: + return path + + # https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 + output: list[str] = [] + for component in components: + if component == ".": + pass + elif component == "..": + if output and output != [""]: + output.pop() + else: + output.append(component) + return "/".join(output) + + +def PERCENT(string: str) -> str: + return "".join([f"%{byte:02X}" for byte in string.encode("utf-8")]) + + +def percent_encoded(string: str, safe: str) -> str: + """ + Use percent-encoding to quote a string. + """ + NON_ESCAPED_CHARS = UNRESERVED_CHARACTERS + safe + + # Fast path for strings that don't need escaping. + if not string.rstrip(NON_ESCAPED_CHARS): + return string + + return "".join( + [char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string] + ) + + +def quote(string: str, safe: str) -> str: + """ + Use percent-encoding to quote a string, omitting existing '%xx' escape sequences. + + See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1 + + * `string`: The string to be percent-escaped. + * `safe`: A string containing characters that may be treated as safe, and do not + need to be escaped. Unreserved characters are always treated as safe. + See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3 + """ + parts = [] + current_position = 0 + for match in re.finditer(PERCENT_ENCODED_REGEX, string): + start_position, end_position = match.start(), match.end() + matched_text = match.group(0) + # Add any text up to the '%xx' escape sequence. + if start_position != current_position: + leading_text = string[current_position:start_position] + parts.append(percent_encoded(leading_text, safe=safe)) + + # Add the '%xx' escape sequence. + parts.append(matched_text) + current_position = end_position + + # Add any text after the final '%xx' escape sequence. + if current_position != len(string): + trailing_text = string[current_position:] + parts.append(percent_encoded(trailing_text, safe=safe)) + + return "".join(parts) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py new file mode 100644 index 0000000..147a8fa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_urls.py @@ -0,0 +1,641 @@ +from __future__ import annotations + +import typing +from urllib.parse import parse_qs, unquote, urlencode + +import idna + +from ._types import QueryParamTypes +from ._urlparse import urlparse +from ._utils import primitive_value_to_str + +__all__ = ["URL", "QueryParams"] + + +class URL: + """ + url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink") + + assert url.scheme == "https" + assert url.username == "jo@email.com" + assert url.password == "a secret" + assert url.userinfo == b"jo%40email.com:a%20secret" + assert url.host == "müller.de" + assert url.raw_host == b"xn--mller-kva.de" + assert url.port == 1234 + assert url.netloc == b"xn--mller-kva.de:1234" + assert url.path == "/pa th" + assert url.query == b"?search=ab" + assert url.raw_path == b"/pa%20th?search=ab" + assert url.fragment == "anchorlink" + + The components of a URL are broken down like this: + + https://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink + [scheme] [ username ] [password] [ host ][port][ path ] [ query ] [fragment] + [ userinfo ] [ netloc ][ raw_path ] + + Note that: + + * `url.scheme` is normalized to always be lowercased. + + * `url.host` is normalized to always be lowercased. Internationalized domain + names are represented in unicode, without IDNA encoding applied. For instance: + + url = httpx.URL("http://中国.icom.museum") + assert url.host == "中国.icom.museum" + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.host == "中国.icom.museum" + + * `url.raw_host` is normalized to always be lowercased, and is IDNA encoded. + + url = httpx.URL("http://中国.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + * `url.port` is either None or an integer. URLs that include the default port for + "http", "https", "ws", "wss", and "ftp" schemes have their port + normalized to `None`. + + assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80") + assert httpx.URL("http://example.com").port is None + assert httpx.URL("http://example.com:80").port is None + + * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work + with `url.username` and `url.password` instead, which handle the URL escaping. + + * `url.raw_path` is raw bytes of both the path and query, without URL escaping. + This portion is used as the target when constructing HTTP requests. Usually you'll + want to work with `url.path` instead. + + * `url.query` is raw bytes, without URL escaping. A URL query string portion can + only be properly URL escaped when decoding the parameter names and values + themselves. + """ + + def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: + if kwargs: + allowed = { + "scheme": str, + "username": str, + "password": str, + "userinfo": bytes, + "host": str, + "port": int, + "netloc": bytes, + "path": str, + "query": bytes, + "raw_path": bytes, + "fragment": str, + "params": object, + } + + # Perform type checking for all supported keyword arguments. + for key, value in kwargs.items(): + if key not in allowed: + message = f"{key!r} is an invalid keyword argument for URL()" + raise TypeError(message) + if value is not None and not isinstance(value, allowed[key]): + expected = allowed[key].__name__ + seen = type(value).__name__ + message = f"Argument {key!r} must be {expected} but got {seen}" + raise TypeError(message) + if isinstance(value, bytes): + kwargs[key] = value.decode("ascii") + + if "params" in kwargs: + # Replace any "params" keyword with the raw "query" instead. + # + # Ensure that empty params use `kwargs["query"] = None` rather + # than `kwargs["query"] = ""`, so that generated URLs do not + # include an empty trailing "?". + params = kwargs.pop("params") + kwargs["query"] = None if not params else str(QueryParams(params)) + + if isinstance(url, str): + self._uri_reference = urlparse(url, **kwargs) + elif isinstance(url, URL): + self._uri_reference = url._uri_reference.copy_with(**kwargs) + else: + raise TypeError( + "Invalid type for url. Expected str or httpx.URL," + f" got {type(url)}: {url!r}" + ) + + @property + def scheme(self) -> str: + """ + The URL scheme, such as "http", "https". + Always normalised to lowercase. + """ + return self._uri_reference.scheme + + @property + def raw_scheme(self) -> bytes: + """ + The raw bytes representation of the URL scheme, such as b"http", b"https". + Always normalised to lowercase. + """ + return self._uri_reference.scheme.encode("ascii") + + @property + def userinfo(self) -> bytes: + """ + The URL userinfo as a raw bytestring. + For example: b"jo%40email.com:a%20secret". + """ + return self._uri_reference.userinfo.encode("ascii") + + @property + def username(self) -> str: + """ + The URL username as a string, with URL decoding applied. + For example: "jo@email.com" + """ + userinfo = self._uri_reference.userinfo + return unquote(userinfo.partition(":")[0]) + + @property + def password(self) -> str: + """ + The URL password as a string, with URL decoding applied. + For example: "a secret" + """ + userinfo = self._uri_reference.userinfo + return unquote(userinfo.partition(":")[2]) + + @property + def host(self) -> str: + """ + The URL host as a string. + Always normalized to lowercase, with IDNA hosts decoded into unicode. + + Examples: + + url = httpx.URL("http://www.EXAMPLE.org") + assert url.host == "www.example.org" + + url = httpx.URL("http://中国.icom.museum") + assert url.host == "中国.icom.museum" + + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.host == "中国.icom.museum" + + url = httpx.URL("https://[::ffff:192.168.0.1]") + assert url.host == "::ffff:192.168.0.1" + """ + host: str = self._uri_reference.host + + if host.startswith("xn--"): + host = idna.decode(host) + + return host + + @property + def raw_host(self) -> bytes: + """ + The raw bytes representation of the URL host. + Always normalized to lowercase, and IDNA encoded. + + Examples: + + url = httpx.URL("http://www.EXAMPLE.org") + assert url.raw_host == b"www.example.org" + + url = httpx.URL("http://中国.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + url = httpx.URL("https://[::ffff:192.168.0.1]") + assert url.raw_host == b"::ffff:192.168.0.1" + """ + return self._uri_reference.host.encode("ascii") + + @property + def port(self) -> int | None: + """ + The URL port as an integer. + + Note that the URL class performs port normalization as per the WHATWG spec. + Default ports for "http", "https", "ws", "wss", and "ftp" schemes are always + treated as `None`. + + For example: + + assert httpx.URL("http://www.example.com") == httpx.URL("http://www.example.com:80") + assert httpx.URL("http://www.example.com:80").port is None + """ + return self._uri_reference.port + + @property + def netloc(self) -> bytes: + """ + Either `` or `:` as bytes. + Always normalized to lowercase, and IDNA encoded. + + This property may be used for generating the value of a request + "Host" header. + """ + return self._uri_reference.netloc.encode("ascii") + + @property + def path(self) -> str: + """ + The URL path as a string. Excluding the query string, and URL decoded. + + For example: + + url = httpx.URL("https://example.com/pa%20th") + assert url.path == "/pa th" + """ + path = self._uri_reference.path or "/" + return unquote(path) + + @property + def query(self) -> bytes: + """ + The URL query string, as raw bytes, excluding the leading b"?". + + This is necessarily a bytewise interface, because we cannot + perform URL decoding of this representation until we've parsed + the keys and values into a QueryParams instance. + + For example: + + url = httpx.URL("https://example.com/?filter=some%20search%20terms") + assert url.query == b"filter=some%20search%20terms" + """ + query = self._uri_reference.query or "" + return query.encode("ascii") + + @property + def params(self) -> QueryParams: + """ + The URL query parameters, neatly parsed and packaged into an immutable + multidict representation. + """ + return QueryParams(self._uri_reference.query) + + @property + def raw_path(self) -> bytes: + """ + The complete URL path and query string as raw bytes. + Used as the target when constructing HTTP requests. + + For example: + + GET /users?search=some%20text HTTP/1.1 + Host: www.example.org + Connection: close + """ + path = self._uri_reference.path or "/" + if self._uri_reference.query is not None: + path += "?" + self._uri_reference.query + return path.encode("ascii") + + @property + def fragment(self) -> str: + """ + The URL fragments, as used in HTML anchors. + As a string, without the leading '#'. + """ + return unquote(self._uri_reference.fragment or "") + + @property + def is_absolute_url(self) -> bool: + """ + Return `True` for absolute URLs such as 'http://example.com/path', + and `False` for relative URLs such as '/path'. + """ + # We don't use `.is_absolute` from `rfc3986` because it treats + # URLs with a fragment portion as not absolute. + # What we actually care about is if the URL provides + # a scheme and hostname to which connections should be made. + return bool(self._uri_reference.scheme and self._uri_reference.host) + + @property + def is_relative_url(self) -> bool: + """ + Return `False` for absolute URLs such as 'http://example.com/path', + and `True` for relative URLs such as '/path'. + """ + return not self.is_absolute_url + + def copy_with(self, **kwargs: typing.Any) -> URL: + """ + Copy this URL, returning a new URL with some components altered. + Accepts the same set of parameters as the components that are made + available via properties on the `URL` class. + + For example: + + url = httpx.URL("https://www.example.com").copy_with( + username="jo@gmail.com", password="a secret" + ) + assert url == "https://jo%40email.com:a%20secret@www.example.com" + """ + return URL(self, **kwargs) + + def copy_set_param(self, key: str, value: typing.Any = None) -> URL: + return self.copy_with(params=self.params.set(key, value)) + + def copy_add_param(self, key: str, value: typing.Any = None) -> URL: + return self.copy_with(params=self.params.add(key, value)) + + def copy_remove_param(self, key: str) -> URL: + return self.copy_with(params=self.params.remove(key)) + + def copy_merge_params(self, params: QueryParamTypes) -> URL: + return self.copy_with(params=self.params.merge(params)) + + def join(self, url: URL | str) -> URL: + """ + Return an absolute URL, using this URL as the base. + + Eg. + + url = httpx.URL("https://www.example.com/test") + url = url.join("/new/path") + assert url == "https://www.example.com/new/path" + """ + from urllib.parse import urljoin + + return URL(urljoin(str(self), str(URL(url)))) + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(self, other: typing.Any) -> bool: + return isinstance(other, (URL, str)) and str(self) == str(URL(other)) + + def __str__(self) -> str: + return str(self._uri_reference) + + def __repr__(self) -> str: + scheme, userinfo, host, port, path, query, fragment = self._uri_reference + + if ":" in userinfo: + # Mask any password component. + userinfo = f'{userinfo.split(":")[0]}:[secure]' + + authority = "".join( + [ + f"{userinfo}@" if userinfo else "", + f"[{host}]" if ":" in host else host, + f":{port}" if port is not None else "", + ] + ) + url = "".join( + [ + f"{self.scheme}:" if scheme else "", + f"//{authority}" if authority else "", + path, + f"?{query}" if query is not None else "", + f"#{fragment}" if fragment is not None else "", + ] + ) + + return f"{self.__class__.__name__}({url!r})" + + @property + def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: nocover + import collections + import warnings + + warnings.warn("URL.raw is deprecated.") + RawURL = collections.namedtuple( + "RawURL", ["raw_scheme", "raw_host", "port", "raw_path"] + ) + return RawURL( + raw_scheme=self.raw_scheme, + raw_host=self.raw_host, + port=self.port, + raw_path=self.raw_path, + ) + + +class QueryParams(typing.Mapping[str, str]): + """ + URL query parameters, as a multi-dict. + """ + + def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None: + assert len(args) < 2, "Too many arguments." + assert not (args and kwargs), "Cannot mix named and unnamed arguments." + + value = args[0] if args else kwargs + + if value is None or isinstance(value, (str, bytes)): + value = value.decode("ascii") if isinstance(value, bytes) else value + self._dict = parse_qs(value, keep_blank_values=True) + elif isinstance(value, QueryParams): + self._dict = {k: list(v) for k, v in value._dict.items()} + else: + dict_value: dict[typing.Any, list[typing.Any]] = {} + if isinstance(value, (list, tuple)): + # Convert list inputs like: + # [("a", "123"), ("a", "456"), ("b", "789")] + # To a dict representation, like: + # {"a": ["123", "456"], "b": ["789"]} + for item in value: + dict_value.setdefault(item[0], []).append(item[1]) + else: + # Convert dict inputs like: + # {"a": "123", "b": ["456", "789"]} + # To dict inputs where values are always lists, like: + # {"a": ["123"], "b": ["456", "789"]} + dict_value = { + k: list(v) if isinstance(v, (list, tuple)) else [v] + for k, v in value.items() + } + + # Ensure that keys and values are neatly coerced to strings. + # We coerce values `True` and `False` to JSON-like "true" and "false" + # representations, and coerce `None` values to the empty string. + self._dict = { + str(k): [primitive_value_to_str(item) for item in v] + for k, v in dict_value.items() + } + + def keys(self) -> typing.KeysView[str]: + """ + Return all the keys in the query params. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.keys()) == ["a", "b"] + """ + return self._dict.keys() + + def values(self) -> typing.ValuesView[str]: + """ + Return all the values in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.values()) == ["123", "789"] + """ + return {k: v[0] for k, v in self._dict.items()}.values() + + def items(self) -> typing.ItemsView[str, str]: + """ + Return all items in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.items()) == [("a", "123"), ("b", "789")] + """ + return {k: v[0] for k, v in self._dict.items()}.items() + + def multi_items(self) -> list[tuple[str, str]]: + """ + Return all items in the query params. Allow duplicate keys to occur. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")] + """ + multi_items: list[tuple[str, str]] = [] + for k, v in self._dict.items(): + multi_items.extend([(k, i) for i in v]) + return multi_items + + def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any: + """ + Get a value from the query param for a given key. If the key occurs + more than once, then only the first value is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert q.get("a") == "123" + """ + if key in self._dict: + return self._dict[str(key)][0] + return default + + def get_list(self, key: str) -> list[str]: + """ + Get all values from the query param for a given key. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert q.get_list("a") == ["123", "456"] + """ + return list(self._dict.get(str(key), [])) + + def set(self, key: str, value: typing.Any = None) -> QueryParams: + """ + Return a new QueryParams instance, setting the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.set("a", "456") + assert q == httpx.QueryParams("a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[str(key)] = [primitive_value_to_str(value)] + return q + + def add(self, key: str, value: typing.Any = None) -> QueryParams: + """ + Return a new QueryParams instance, setting or appending the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.add("a", "456") + assert q == httpx.QueryParams("a=123&a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[str(key)] = q.get_list(key) + [primitive_value_to_str(value)] + return q + + def remove(self, key: str) -> QueryParams: + """ + Return a new QueryParams instance, removing the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.remove("a") + assert q == httpx.QueryParams("") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict.pop(str(key), None) + return q + + def merge(self, params: QueryParamTypes | None = None) -> QueryParams: + """ + Return a new QueryParams instance, updated with. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.merge({"b": "456"}) + assert q == httpx.QueryParams("a=123&b=456") + + q = httpx.QueryParams("a=123") + q = q.merge({"a": "456", "b": "789"}) + assert q == httpx.QueryParams("a=456&b=789") + """ + q = QueryParams(params) + q._dict = {**self._dict, **q._dict} + return q + + def __getitem__(self, key: typing.Any) -> str: + return self._dict[key][0] + + def __contains__(self, key: typing.Any) -> bool: + return key in self._dict + + def __iter__(self) -> typing.Iterator[typing.Any]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._dict) + + def __bool__(self) -> bool: + return bool(self._dict) + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(self, other: typing.Any) -> bool: + if not isinstance(other, self.__class__): + return False + return sorted(self.multi_items()) == sorted(other.multi_items()) + + def __str__(self) -> str: + return urlencode(self.multi_items()) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + query_string = str(self) + return f"{class_name}({query_string!r})" + + def update(self, params: QueryParamTypes | None = None) -> None: + raise RuntimeError( + "QueryParams are immutable since 0.18.0. " + "Use `q = q.merge(...)` to create an updated copy." + ) + + def __setitem__(self, key: str, value: str) -> None: + raise RuntimeError( + "QueryParams are immutable since 0.18.0. " + "Use `q = q.set(key, value)` to create an updated copy." + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py new file mode 100644 index 0000000..7fe827d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/_utils.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +import ipaddress +import os +import re +import typing +from urllib.request import getproxies + +from ._types import PrimitiveData + +if typing.TYPE_CHECKING: # pragma: no cover + from ._urls import URL + + +def primitive_value_to_str(value: PrimitiveData) -> str: + """ + Coerce a primitive data type into a string value. + + Note that we prefer JSON-style 'true'/'false' for boolean values here. + """ + if value is True: + return "true" + elif value is False: + return "false" + elif value is None: + return "" + return str(value) + + +def get_environment_proxies() -> dict[str, str | None]: + """Gets proxy information from the environment""" + + # urllib.request.getproxies() falls back on System + # Registry and Config for proxies on Windows and macOS. + # We don't want to propagate non-HTTP proxies into + # our configuration such as 'TRAVIS_APT_PROXY'. + proxy_info = getproxies() + mounts: dict[str, str | None] = {} + + for scheme in ("http", "https", "all"): + if proxy_info.get(scheme): + hostname = proxy_info[scheme] + mounts[f"{scheme}://"] = ( + hostname if "://" in hostname else f"http://{hostname}" + ) + + no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")] + for hostname in no_proxy_hosts: + # See https://curl.haxx.se/libcurl/c/CURLOPT_NOPROXY.html for details + # on how names in `NO_PROXY` are handled. + if hostname == "*": + # If NO_PROXY=* is used or if "*" occurs as any one of the comma + # separated hostnames, then we should just bypass any information + # from HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and always ignore + # proxies. + return {} + elif hostname: + # NO_PROXY=.google.com is marked as "all://*.google.com, + # which disables "www.google.com" but not "google.com" + # NO_PROXY=google.com is marked as "all://*google.com, + # which disables "www.google.com" and "google.com". + # (But not "wwwgoogle.com") + # NO_PROXY can include domains, IPv6, IPv4 addresses and "localhost" + # NO_PROXY=example.com,::1,localhost,192.168.0.0/16 + if "://" in hostname: + mounts[hostname] = None + elif is_ipv4_hostname(hostname): + mounts[f"all://{hostname}"] = None + elif is_ipv6_hostname(hostname): + mounts[f"all://[{hostname}]"] = None + elif hostname.lower() == "localhost": + mounts[f"all://{hostname}"] = None + else: + mounts[f"all://*{hostname}"] = None + + return mounts + + +def to_bytes(value: str | bytes, encoding: str = "utf-8") -> bytes: + return value.encode(encoding) if isinstance(value, str) else value + + +def to_str(value: str | bytes, encoding: str = "utf-8") -> str: + return value if isinstance(value, str) else value.decode(encoding) + + +def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr: + return value if isinstance(match_type_of, str) else value.encode() + + +def unquote(value: str) -> str: + return value[1:-1] if value[0] == value[-1] == '"' else value + + +def peek_filelike_length(stream: typing.Any) -> int | None: + """ + Given a file-like stream object, return its length in number of bytes + without reading it into memory. + """ + try: + # Is it an actual file? + fd = stream.fileno() + # Yup, seems to be an actual file. + length = os.fstat(fd).st_size + except (AttributeError, OSError): + # No... Maybe it's something that supports random access, like `io.BytesIO`? + try: + # Assuming so, go to end of stream to figure out its length, + # then put it back in place. + offset = stream.tell() + length = stream.seek(0, os.SEEK_END) + stream.seek(offset) + except (AttributeError, OSError): + # Not even that? Sorry, we're doomed... + return None + + return length + + +class URLPattern: + """ + A utility class currently used for making lookups against proxy keys... + + # Wildcard matching... + >>> pattern = URLPattern("all://") + >>> pattern.matches(httpx.URL("http://example.com")) + True + + # Witch scheme matching... + >>> pattern = URLPattern("https://") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + False + + # With domain matching... + >>> pattern = URLPattern("https://example.com") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + False + >>> pattern.matches(httpx.URL("https://other.com")) + False + + # Wildcard scheme, with domain matching... + >>> pattern = URLPattern("all://example.com") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + True + >>> pattern.matches(httpx.URL("https://other.com")) + False + + # With port matching... + >>> pattern = URLPattern("https://example.com:1234") + >>> pattern.matches(httpx.URL("https://example.com:1234")) + True + >>> pattern.matches(httpx.URL("https://example.com")) + False + """ + + def __init__(self, pattern: str) -> None: + from ._urls import URL + + if pattern and ":" not in pattern: + raise ValueError( + f"Proxy keys should use proper URL forms rather " + f"than plain scheme strings. " + f'Instead of "{pattern}", use "{pattern}://"' + ) + + url = URL(pattern) + self.pattern = pattern + self.scheme = "" if url.scheme == "all" else url.scheme + self.host = "" if url.host == "*" else url.host + self.port = url.port + if not url.host or url.host == "*": + self.host_regex: typing.Pattern[str] | None = None + elif url.host.startswith("*."): + # *.example.com should match "www.example.com", but not "example.com" + domain = re.escape(url.host[2:]) + self.host_regex = re.compile(f"^.+\\.{domain}$") + elif url.host.startswith("*"): + # *example.com should match "www.example.com" and "example.com" + domain = re.escape(url.host[1:]) + self.host_regex = re.compile(f"^(.+\\.)?{domain}$") + else: + # example.com should match "example.com" but not "www.example.com" + domain = re.escape(url.host) + self.host_regex = re.compile(f"^{domain}$") + + def matches(self, other: URL) -> bool: + if self.scheme and self.scheme != other.scheme: + return False + if ( + self.host + and self.host_regex is not None + and not self.host_regex.match(other.host) + ): + return False + if self.port is not None and self.port != other.port: + return False + return True + + @property + def priority(self) -> tuple[int, int, int]: + """ + The priority allows URLPattern instances to be sortable, so that + we can match from most specific to least specific. + """ + # URLs with a port should take priority over URLs without a port. + port_priority = 0 if self.port is not None else 1 + # Longer hostnames should match first. + host_priority = -len(self.host) + # Longer schemes should match first. + scheme_priority = -len(self.scheme) + return (port_priority, host_priority, scheme_priority) + + def __hash__(self) -> int: + return hash(self.pattern) + + def __lt__(self, other: URLPattern) -> bool: + return self.priority < other.priority + + def __eq__(self, other: typing.Any) -> bool: + return isinstance(other, URLPattern) and self.pattern == other.pattern + + +def is_ipv4_hostname(hostname: str) -> bool: + try: + ipaddress.IPv4Address(hostname.split("/")[0]) + except Exception: + return False + return True + + +def is_ipv6_hostname(hostname: str) -> bool: + try: + ipaddress.IPv6Address(hostname.split("/")[0]) + except Exception: + return False + return True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA new file mode 100644 index 0000000..41f3bf1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA @@ -0,0 +1,351 @@ +Metadata-Version: 2.4 +Name: httpx-sse +Version: 0.4.3 +Summary: Consume Server-Sent Event (SSE) messages with HTTPX. +Author-email: Florimond Manca +License: MIT +Project-URL: Homepage, https://github.com/florimondmanca/httpx-sse +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE +Dynamic: license-file + +# httpx-sse + +[![Build Status](https://dev.azure.com/florimondmanca/public/_apis/build/status/florimondmanca.httpx-sse?branchName=master)](https://dev.azure.com/florimondmanca/public/_build?definitionId=19) +[![Coverage](https://codecov.io/gh/florimondmanca/httpx-sse/branch/master/graph/badge.svg)](https://codecov.io/gh/florimondmanca/httpx-sse) +[![Package version](https://badge.fury.io/py/httpx-sse.svg)](https://pypi.org/project/httpx-sse) + +Consume [Server-Sent Event (SSE)](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) messages with [HTTPX](https://www.python-httpx.org). + +**Table of contents** + +- [Installation](#installation) +- [Quickstart](#quickstart) +- [How-To](#how-to) +- [API Reference](#api-reference) + +## Installation + +**NOTE**: This is beta software. Please be sure to pin your dependencies. + +```bash +pip install httpx-sse=="0.4.*" +``` + +## Quickstart + +`httpx-sse` provides the [`connect_sse`](#connect_sse) and [`aconnect_sse`](#aconnect_sse) helpers for connecting to an SSE endpoint. The resulting [`EventSource`](#eventsource) object exposes the [`.iter_sse()`](#iter_sse) and [`.aiter_sse()`](#aiter_sse) methods to iterate over the server-sent events. + +Example usage: + +```python +import httpx +from httpx_sse import connect_sse + +with httpx.Client() as client: + with connect_sse(client, "GET", "http://localhost:8000/sse") as event_source: + for sse in event_source.iter_sse(): + print(sse.event, sse.data, sse.id, sse.retry) +``` + +You can try this against this example Starlette server ([credit](https://sysid.github.io/sse/)): + +```python +# Requirements: pip install uvicorn starlette sse-starlette +import asyncio +import uvicorn +from starlette.applications import Starlette +from starlette.routing import Route +from sse_starlette.sse import EventSourceResponse + +async def numbers(minimum, maximum): + for i in range(minimum, maximum + 1): + await asyncio.sleep(0.9) + yield {"data": i} + +async def sse(request): + generator = numbers(1, 5) + return EventSourceResponse(generator) + +routes = [ + Route("/sse", endpoint=sse) +] + +app = Starlette(routes=routes) + +if __name__ == "__main__": + uvicorn.run(app) +``` + +## How-To + +### Calling into Python web apps + +You can [call into Python web apps](https://www.python-httpx.org/async/#calling-into-python-web-apps) with HTTPX and `httpx-sse` to test SSE endpoints directly. + +Here's an example of calling into a Starlette ASGI app... + +```python +import asyncio + +import httpx +from httpx_sse import aconnect_sse +from sse_starlette.sse import EventSourceResponse +from starlette.applications import Starlette +from starlette.routing import Route + +async def auth_events(request): + async def events(): + yield { + "event": "login", + "data": '{"user_id": "4135"}', + } + + return EventSourceResponse(events()) + +app = Starlette(routes=[Route("/sse/auth/", endpoint=auth_events)]) + +async def main(): + async with httpx.AsyncClient(transport=httpx.ASGITransport(app)) as client: + async with aconnect_sse( + client, "GET", "http://localhost:8000/sse/auth/" + ) as event_source: + events = [sse async for sse in event_source.aiter_sse()] + (sse,) = events + assert sse.event == "login" + assert sse.json() == {"user_id": "4135"} + +asyncio.run(main()) +``` + +### Handling reconnections + +_(Advanced)_ + +`SSETransport` and `AsyncSSETransport` don't have reconnection built-in. This is because how to perform retries is generally dependent on your use case. As a result, if the connection breaks while attempting to read from the server, you will get an `httpx.ReadError` from `iter_sse()` (or `aiter_sse()`). + +However, `httpx-sse` does allow implementing reconnection by using the `Last-Event-ID` and reconnection time (in milliseconds), exposed as `sse.id` and `sse.retry` respectively. + +Here's how you might achieve this using [`stamina`](https://github.com/hynek/stamina)... + +```python +import time +from typing import Iterator + +import httpx +from httpx_sse import connect_sse, ServerSentEvent +from stamina import retry + +def iter_sse_retrying(client, method, url): + last_event_id = "" + reconnection_delay = 0.0 + + # `stamina` will apply jitter and exponential backoff on top of + # the `retry` reconnection delay sent by the server. + @retry(on=httpx.ReadError) + def _iter_sse(): + nonlocal last_event_id, reconnection_delay + + time.sleep(reconnection_delay) + + headers = {"Accept": "text/event-stream"} + + if last_event_id: + headers["Last-Event-ID"] = last_event_id + + with connect_sse(client, method, url, headers=headers) as event_source: + for sse in event_source.iter_sse(): + last_event_id = sse.id + + if sse.retry is not None: + reconnection_delay = sse.retry / 1000 + + yield sse + + return _iter_sse() +``` + +Usage: + +```python +with httpx.Client() as client: + for sse in iter_sse_retrying(client, "GET", "http://localhost:8000/sse"): + print(sse.event, sse.data) +``` + +## API Reference + +### `connect_sse` + +```python +def connect_sse( + client: httpx.Client, + method: str, + url: Union[str, httpx.URL], + **kwargs, +) -> ContextManager[EventSource] +``` + +Connect to an SSE endpoint and return an [`EventSource`](#eventsource) context manager. + +This sets `Cache-Control: no-store` on the request, as per the SSE spec, as well as `Accept: text/event-stream`. + +If the response `Content-Type` is not `text/event-stream`, this will raise an [`SSEError`](#sseerror). + +### `aconnect_sse` + +```python +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: Union[str, httpx.URL], + **kwargs, +) -> AsyncContextManager[EventSource] +``` + +An async equivalent to [`connect_sse`](#connect_sse). + +### `EventSource` + +```python +def __init__(response: httpx.Response) +``` + +Helper for working with an SSE response. + +#### `response` + +The underlying [`httpx.Response`](https://www.python-httpx.org/api/#response). + +You may use this to perform more operations and checks on the response, such as checking for HTTP status errors: + +```python +with connect_sse(...) as event_source: + event_source.response.raise_for_status() + + for sse in event_source.iter_sse(): + ... +``` + +#### `iter_sse` + +```python +def iter_sse() -> Iterator[ServerSentEvent] +``` + +Decode the response content and yield corresponding [`ServerSentEvent`](#serversentevent). + +Example usage: + +```python +for sse in event_source.iter_sse(): + ... +``` + +#### `aiter_sse` + +```python +async def iter_sse() -> AsyncIterator[ServerSentEvent] +``` + +An async equivalent to `iter_sse`. + +### `ServerSentEvent` + +Represents a server-sent event. + +* `event: str` - Defaults to `"message"`. +* `data: str` - Defaults to `""`. +* `id: str` - Defaults to `""`. +* `retry: str | None` - Defaults to `None`. + +Methods: + +* `json() -> Any` - Returns `sse.data` decoded as JSON. + +### `SSEError` + +An error that occurred while making a request to an SSE endpoint. + +Parents: + +* `httpx.TransportError` + +## License + +MIT + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## 0.4.3 - 2025-10-10 + +### Fixed + +* Fix performance issue introduced by the improved line parsing from release 0.4.2. (Pull #40) + +## 0.4.2 - 2025-10-07 + +### Fixed + +* Fix incorrect newline parsing that was not compliant with SSE spec. (Pull #37) + +## 0.4.1 - 2025-06-24 + +### Fixed + +* Always close the response async generator in `aiter_sse()`. (Pull #30) + +## 0.4.0 - 2023-12-22 + +### Removed + +* Dropped Python 3.7 support, as it has reached EOL. (Pull #21) + +### Added + +* Add official support for Python 3.12. (Pull #21) + +### Fixed + +* Allow `Content-Type` that contain but are not strictly `text/event-stream`. (Pull #22 by @dbuades) +* Improve error message when `Content-Type` is missing. (Pull #20 by @jamesbraza) + +## 0.3.1 - 2023-06-01 + +### Added + +* Add `__repr__()` for `ServerSentEvent` model, which may help with debugging and other tasks. (Pull #16) + +## 0.3.0 - 2023-04-27 + +### Changed + +* Raising an `SSEError` if the response content type is not `text/event-stream` is now performed as part of `iter_sse()` / `aiter_sse()`, instead of `connect_sse()` / `aconnect_sse()`. This allows inspecting the response before iterating on server-sent events, such as checking for error responses. (Pull #12) + +## 0.2.0 - 2023-03-27 + +### Changed + +* `connect_sse()` and `aconnect_sse()` now require a `method` argument: `connect_sse(client, "GET", "https://example.org")`. This provides support for SSE requests with HTTP verbs other than `GET`. (Pull #7) + +## 0.1.0 - 2023-02-05 + +_Initial release_ + +### Added + +* Add `connect_sse`, `aconnect_sse()`, `ServerSentEvent` and `SSEError`. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD new file mode 100644 index 0000000..dc042e8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD @@ -0,0 +1,11 @@ +httpx_sse/__init__.py,sha256=G-F8jpJLo4eJwKvKt6ejTQYPhBHgmksZTNuaQySRAjc,282 +httpx_sse/_api.py,sha256=D7pfkajmdHlms75OV8HpdOrOEr5NbWQlDwMfSFcJ8FU,2919 +httpx_sse/_decoders.py,sha256=na1XyVBiCLbRE8tg0o713U5Gz-m9fBxYPfSMmu_xns8,4005 +httpx_sse/_exceptions.py,sha256=sFZBUT1xIM1DvYQ6ckpzrhBG9d8VffFSZFMIEhv1N58,62 +httpx_sse/_models.py,sha256=4WwGeBEGMR2nbROTZ7PyVlu7chmWg7tbkJyXyd5DTx0,1221 +httpx_sse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpx_sse-0.4.3.dist-info/licenses/LICENSE,sha256=vuxn5O6Dp68m8kK-2Zh6qGPW0t-3dhBqojYMQYfzGQE,1072 +httpx_sse-0.4.3.dist-info/METADATA,sha256=1MbYBO5axG6bO2WVwnq4riunz7X2kseLFv9GPkvRqB4,9671 +httpx_sse-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 +httpx_sse-0.4.3.dist-info/top_level.txt,sha256=hbK3kwBH9d-rEZQvJ6yBawdDylIKfJ8fh0CM4EVNs04,10 +httpx_sse-0.4.3.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL new file mode 100644 index 0000000..e7fa31b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE new file mode 100644 index 0000000..36600eb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Florimond Manca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt new file mode 100644 index 0000000..a5e926f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt @@ -0,0 +1 @@ +httpx_sse diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py new file mode 100644 index 0000000..b83cf09 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py @@ -0,0 +1,14 @@ +from ._api import EventSource, aconnect_sse, connect_sse +from ._exceptions import SSEError +from ._models import ServerSentEvent + +__version__ = "0.4.3" + +__all__ = [ + "__version__", + "EventSource", + "connect_sse", + "aconnect_sse", + "ServerSentEvent", + "SSEError", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py new file mode 100644 index 0000000..017ed5e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py @@ -0,0 +1,93 @@ +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager, contextmanager +from typing import Any, AsyncIterator, Iterator, cast + +import httpx + +from ._decoders import SSEDecoder, SSELineDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + + +class EventSource: + def __init__(self, response: httpx.Response) -> None: + self._response = response + + def _check_content_type(self) -> None: + content_type = self._response.headers.get("content-type", "").partition(";")[0] + if "text/event-stream" not in content_type: + raise SSEError( + "Expected response header Content-Type to contain 'text/event-stream', " + f"got {content_type!r}" + ) + + @property + def response(self) -> httpx.Response: + return self._response + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + for line in _iter_sse_lines(self._response): + line = line.rstrip("\n") + sse = decoder.decode(line) + if sse is not None: + yield sse + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + lines = cast(AsyncGenerator[str, None], _aiter_sse_lines(self._response)) + try: + async for line in lines: + line = line.rstrip("\n") + sse = decoder.decode(line) + if sse is not None: + yield sse + finally: + await lines.aclose() + + +@contextmanager +def connect_sse( + client: httpx.Client, method: str, url: str, **kwargs: Any +) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +async def _aiter_sse_lines(response: httpx.Response) -> AsyncIterator[str]: + decoder = SSELineDecoder() + async for text in response.aiter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line + + +def _iter_sse_lines(response: httpx.Response) -> Iterator[str]: + decoder = SSELineDecoder() + for text in response.iter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py new file mode 100644 index 0000000..287e384 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py @@ -0,0 +1,139 @@ +from typing import List, Optional + +from ._models import ServerSentEvent + + +def _splitlines_sse(text: str) -> List[str]: + """Split text on \r\n, \r, or \n only.""" + if not text: + return [] + + if "\r" not in text: + lines = text.split("\n") + else: + normalized = text.replace("\r\n", "\n").replace("\r", "\n") + lines = normalized.split("\n") + + if text[-1] in "\r\n": + lines.pop() + + return lines + + +class SSELineDecoder: + """ + Handles incrementally reading lines from text. + + Mostly a copy of httpx._decoders.LineDecoder, but as per SSE spec, only \r\n, \r, + and \n are treated as newlines, which differs from the behavior of splitlines() + used by httpx._decoders.LineDecoder. + """ + + def __init__(self) -> None: + self.buffer: list[str] = [] + self.trailing_cr: bool = False + + def decode(self, text: str) -> list[str]: + # We always push a trailing `\r` into the next decode iteration. + if self.trailing_cr: + text = "\r" + text + self.trailing_cr = False + if text.endswith("\r"): + self.trailing_cr = True + text = text[:-1] + + if not text: + # NOTE: the edge case input of empty text doesn't occur in practice, + # because other httpx internals filter out this value + return [] # pragma: no cover + + trailing_newline = text[-1] in "\n\r" + lines = _splitlines_sse(text) + + if len(lines) == 1 and not trailing_newline: + # No new lines, buffer the input and continue. + self.buffer.append(lines[0]) + return [] + + if self.buffer: + # Include any existing buffer in the first portion of the + # splitlines result. + lines = ["".join(self.buffer) + lines[0]] + lines[1:] + self.buffer = [] + + if not trailing_newline: + # If the last segment of splitlines is not newline terminated, + # then drop it from our output and start a new buffer. + self.buffer = [lines.pop()] + + return lines + + def flush(self) -> list[str]: + if not self.buffer and not self.trailing_cr: + return [] + + lines = ["".join(self.buffer)] + self.buffer = [] + self.trailing_cr = False + return lines + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if ( + not self._event + and not self._data + and not self._last_event_id + and self._retry is None + ): + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py new file mode 100644 index 0000000..cd2c4d2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py @@ -0,0 +1,5 @@ +import httpx + + +class SSEError(httpx.TransportError): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py new file mode 100644 index 0000000..d4dc119 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py @@ -0,0 +1,54 @@ +import json +from typing import Any, Optional + + +class ServerSentEvent: + def __init__( + self, + event: Optional[str] = None, + data: Optional[str] = None, + id: Optional[str] = None, + retry: Optional[int] = None, + ) -> None: + if not event: + event = "message" + + if data is None: + data = "" + + if id is None: + id = "" + + self._event = event + self._data = data + self._id = id + self._retry = retry + + @property + def event(self) -> str: + return self._event + + @property + def data(self) -> str: + return self._data + + @property + def id(self) -> str: + return self._id + + @property + def retry(self) -> Optional[int]: + return self._retry + + def json(self) -> Any: + return json.loads(self.data) + + def __repr__(self) -> str: + pieces = [f"event={self.event!r}"] + if self.data != "": + pieces.append(f"data={self.data!r}") + if self.id != "": + pieces.append(f"id={self.id!r}") + if self.retry is not None: + pieces.append(f"retry={self.retry!r}") + return f"ServerSentEvent({', '.join(pieces)})" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA new file mode 100644 index 0000000..edbc446 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA @@ -0,0 +1,176 @@ +Metadata-Version: 2.5 +Name: idna +Version: 3.20 +Summary: Internationalized Domain Names in Applications (IDNA) +Author-email: Kim Davies +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +License-File: LICENSE.md +Requires-Dist: ruff >= 0.16.0 ; extra == "all" +Requires-Dist: mypy >= 1.11.2 ; extra == "all" +Requires-Dist: ty >= 0.0.37 ; extra == "all" +Requires-Dist: pytest >= 8.3.2 ; extra == "all" +Requires-Dist: hypothesis >= 6.141.1 ; extra == "all" +Requires-Dist: coverage >= 7.10.0 ; extra == "all" +Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.md +Project-URL: Issue tracker, https://github.com/kjd/idna/issues +Project-URL: Source, https://github.com/kjd/idna +Provides-Extra: all +Import-Name: idna + +# Internationalized Domain Names in Applications (IDNA) + +Support for [Internationalized Domain Names in Applications +(IDNA)](https://tools.ietf.org/html/rfc5891) and [Unicode IDNA +Compatibility Processing](https://unicode.org/reports/tr46/). It +supersedes the standard library's `encodings.idna`, which only +implements the 2003 specification, offering broader script coverage and +limiting domains with known security vulnerabilities. + +## Usage + +Package may be installed from [PyPI](https://pypi.org/project/idna/) via +the typical methods (e.g. `python3 -m pip install idna`) + +For typical usage, the `encode` and `decode` functions will take a +domain name argument and perform a conversion to ASCII-compatible encoding +(known as A-labels), or to Unicode strings (known as U-labels) +respectively. + +```pycon +>>> import idna +>>> idna.encode('ドメイン.テスト') +b'xn--eckwd4c7c.xn--zckzah' +>>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) +ドメイン.テスト +``` + +Conversions can be applied at a per-label basis using the `ulabel` or +`alabel` functions for specialized use cases. + + +### Compatibility Mapping (UTS #46) + +This library provides support for [Unicode IDNA Compatibility +Processing](https://unicode.org/reports/tr46/) which normalizes input from +different potential ways a user may input a domain prior to performing the IDNA +conversion operations. This functionality, known as a +[mapping](https://tools.ietf.org/html/rfc5895), is considered by the +specification to be a local user-interface issue distinct from IDNA +conversion functionality. + +For example, "Königsgäßchen" is not a permissible label as capital letters +are not allowed. UTS #46 will convert this into lower case prior to applying +the IDNA conversion. + +```pycon +>>> import idna +>>> idna.encode('Königsgäßchen') +... +idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed +>>> idna.encode('Königsgäßchen', uts46=True) +b'xn--knigsgchen-b4a3dun' +>>> idna.decode('xn--knigsgchen-b4a3dun') +'königsgäßchen' +``` + +When performing a decode operation for display purposes, `decode()` +accepts a `display=True` argument that leaves any `xn--` label that +fails to decode unchanged. This is useful for user interface display +where a domain is in use, the A-label form can be presented when it +is not a valid IDN. + + +## Exceptions + +All errors raised during conversion derive from the `idna.IDNAError` +base class. The more specific exceptions are: + +* `idna.IDNABidiError` — raised when a label contains an illegal + combination of left-to-right and right-to-left characters. +* `idna.InvalidCodepoint` — raised when a label contains a codepoint + that is INVALID for IDNA. +* `idna.InvalidCodepointContext` — raised when a CONTEXTO or CONTEXTJ + codepoint appears in a position whose contextual requirements are + not satisfied. + +Exceptions carry machine-readable attributes so that applications do +not need to parse the message: `code` is a short, stable identifier +for the rule that failed (such as `disallowed_codepoint` or +`label_too_long`); and, when the failure can be attributed to a +particular character, `text` (the label or domain being validated), +`codepoint` (the offending codepoint as an integer) and `position` +are set. + + +## Command-line tool + +The package supports command-line usage to convert domain names +between their Unicode and ASCII-compatible forms. It can be run either +as a module (`python3 -m idna`) or, once installed (such as with `uv +tool` or `pipx`), via the `idna` script: + +```bash +$ uv tool install idna +$ idna xn--e1afmkfd.xn--p1ai +пример.рф +$ idna пример.рф +xn--e1afmkfd.xn--p1ai +``` + +Mode can be specified with `-e`/`--encode` or `-d`/`--decode`, otherwise +it will be chosen automatically based on the first input. Multiple +domains can be supplied either as arguments or through standard input. +UTS #46 mapping is applied by default, which lets the tool accept +inputs that aren't strictly valid IDNA 2008 by normalising them first, +pass `--strict` to disable UTS #46. + +Conversion failures are reported on stderr together with the +offending input; processing continues with the remaining domains and +the tool exits with a non-zero status if any conversion failed. + + +## Additional Notes + +* **Python version support**. This library supports Python 3.9 and higher. + As this library serves as a low-level toolkit for a variety of + applications, we strive to support all versions of Python that are + not beyond end-of-life. Free-threaded Python is also supported, + as the library holds no mutable global state the functions can be + called concurrently from multiple threads. + +* **Unicode version**. The IDNA and UTS #46 lookup tables are generated + from a specific Unicode release. Some Unicode data depends on the + running Python's `unicodedata` module, so on an older Python a + character new to Unicode may be rejected as unknown even if this + library knows about it. + +* **Emoji**. It is an occasional request to support emoji domains in + this library. Encoding of symbols like emoji is expressly prohibited by + the IDNA technical standard, and emoji domains are broadly phased + out across the domain industry due to associated security risks. + +* **Regenerating lookup tables**. The IDNA and UTS #46 functionality + relies upon pre-calculated lookup tables, generated using the + `idna-data` script in [`tools/`](tools/README.md). + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD new file mode 100644 index 0000000..1e93cc6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD @@ -0,0 +1,16 @@ +idna/__init__.py,sha256=hRTD7VMTajWW699RL6SHu919pama3K7YLgNj0sMG068,944 +idna/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83 +idna/cli.py,sha256=LgGXKiXmev37pdgkATwB6AiJORZTnA2zP3LzfEK4iWo,4245 +idna/codec.py,sha256=ff9CR9debiZcvqEmHxDRIrqSmaNxQpz6AY4JHsC3YC0,8002 +idna/compat.py,sha256=T_eZXOUt0b5Tga56cPybQu9CCYK0FTK1mdiPb96na8A,1376 +idna/core.py,sha256=PdTD_-5P8iMgf6xl84EqHmiVrorR56btYBFXaPNVFSA,32580 +idna/idnadata.py,sha256=I6mowdFd6cY00L8hPXeYEPSFCD_hsPfgkEStBKacx-s,45763 +idna/intranges.py,sha256=g49scLSkqJtAhLmOODa7hVHriSjmb60tiTsEoocJdBI,1851 +idna/package_data.py,sha256=rnWVNKPFgqAKyFbPykZKOe6CUdOTII2K8GtgW8IOAqk,21 +idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna/uts46data.py,sha256=dw6Em_oVbHGCiolEClf-mupwc18QOuRhbuT2guE9bAA,237174 +idna-3.20.dist-info/entry_points.txt,sha256=7H3nGOHap3jnLE5e7q7Ywr9Vq8axB7WIj5-C_4N2vhw,38 +idna-3.20.dist-info/licenses/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 +idna-3.20.dist-info/WHEEL,sha256=lqN8jXt_QloFOGcx9kStvWbryhfEXoI2F2z-YURm350,81 +idna-3.20.dist-info/METADATA,sha256=29jBTB5MoeDJgkptvHy794iE8461LZEUjNMCX2ceS4U,7207 +idna-3.20.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL new file mode 100644 index 0000000..1ab9a08 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 4.1.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt new file mode 100644 index 0000000..59ca7ac --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +idna=idna.cli:main + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..f706835 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2026, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py new file mode 100644 index 0000000..d6c890a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__init__.py @@ -0,0 +1,47 @@ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .idnadata import __version__ as unicode_version +from .intranges import intranges_contain +from .package_data import __version__ + +__all__ = [ + "__version__", + "unicode_version", + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py new file mode 100644 index 0000000..dbdd066 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/__main__.py @@ -0,0 +1,6 @@ +import sys + +from .cli import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py new file mode 100644 index 0000000..894d8a0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/cli.py @@ -0,0 +1,132 @@ +"""Command-line interface for the :mod:`idna` package. + +Invoked via ``python -m idna``. See :func:`main` for the entry point. +""" + +from __future__ import annotations + +import argparse +import sys +from itertools import chain +from typing import IO, TYPE_CHECKING + +from . import IDNAError, decode, encode, unicode_version +from .core import _alabel_prefix, _unicode_dots_re +from .package_data import __version__ + +if TYPE_CHECKING: + from collections.abc import Iterable + + +def _looks_like_alabel(s: str) -> bool: + """Return True if any label in ``s`` carries the ``xn--`` ACE prefix.""" + prefix = _alabel_prefix.decode("ascii") + return any(label.lower().startswith(prefix) for label in _unicode_dots_re.split(s)) + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="python -m idna", + description=( + "Convert a domain name between its Unicode (U-label) and " + "ASCII-compatible (A-label) forms. With no mode flag, the " + "direction is chosen from the first input — if it contains " + "an xn-- label the stream is decoded, otherwise it is " + "encoded — and the same mode is applied to every remaining " + "input. UTS #46 mapping is applied by default; pass " + "--strict to disable it. When no domains are given on the " + "command line and stdin is piped, one domain per line is " + "read from stdin." + ), + ) + mode = parser.add_mutually_exclusive_group() + mode.add_argument( + "-e", + "--encode", + dest="mode", + action="store_const", + const="encode", + help="Encode the input to its ASCII A-label form.", + ) + mode.add_argument( + "-d", + "--decode", + dest="mode", + action="store_const", + const="decode", + help="Decode the input from its ASCII A-label form.", + ) + parser.add_argument( + "--strict", + action="store_true", + help="Disable the default UTS #46 mapping and apply IDNA 2008 rules verbatim.", + ) + parser.add_argument( + "--version", + action="version", + version=f"idna {__version__} (Unicode {unicode_version})", + ) + parser.add_argument( + "domain", + nargs="*", + help="One or more domain names to convert. Omit to read from stdin.", + ) + return parser + + +def _iter_stdin(stream: IO[str]) -> Iterable[str]: + """Yield non-empty stripped lines from ``stream``, ignoring blanks.""" + for line in stream: + stripped = line.strip() + if stripped: + yield stripped + + +def _convert_one(domain: str, mode: str, uts46: bool) -> bool: + """Convert ``domain`` and write the result; return ``False`` on failure.""" + try: + if mode == "decode": + print(decode(domain, uts46=uts46)) + else: + print(encode(domain, uts46=uts46).decode("ascii")) + except IDNAError as err: + print(f"idna: {mode} failed for {domain!r}: {err}", file=sys.stderr) + return False + return True + + +def main(argv: list[str] | None = None) -> int: + """Entry point for ``python -m idna``. + + When more than one domain is supplied (via positional arguments or + piped stdin) and no mode flag is given, the first input determines + the direction and that mode is applied uniformly to the rest. + + :param argv: Argument list excluding the program name. Defaults to + :data:`sys.argv` when ``None``. + :returns: ``0`` on success, ``1`` if any conversion fails. + """ + parser = _build_parser() + args = parser.parse_args(argv) + uts46 = not args.strict + + if args.domain: + domains: Iterable[str] = args.domain + elif not sys.stdin.isatty(): + domains = _iter_stdin(sys.stdin) + else: + parser.error("a domain argument is required when stdin is a terminal") + + iterator = iter(domains) + first = next(iterator, None) + if first is None: + return 0 + + mode = args.mode or ("decode" if _looks_like_alabel(first) else "encode") + + results = [_convert_one(domain, mode, uts46) for domain in chain([first], iterator)] + return 0 if all(results) else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py new file mode 100644 index 0000000..8efc1ee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/codec.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import codecs +from typing import Any + +from .core import IDNAError, _max_domain_length, _unicode_dots_re, alabel, decode, encode, ulabel + + +class Codec(codecs.Codec): + """Stateless IDNA 2008 codec. + + Implements the :class:`codecs.Codec` protocol so that the whole-domain + encoder (:func:`idna.encode`) and decoder (:func:`idna.decode`) are + accessible through the standard codec machinery as ``"idna2008"``. + + Only the ``"strict"`` error handler is supported; any other handler + raises :exc:`~idna.IDNAError`. + """ + + def encode(self, data: str, errors: str = "strict") -> tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"', code="unsupported_errors") + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = "strict") -> tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"', code="unsupported_errors") + + if not data: + return "", 0 + + return decode(data), len(data) + + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + """Incremental IDNA 2008 encoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + encoded one whole label at a time. Any of the four Unicode label + separators (``U+002E``, ``U+3002``, ``U+FF0E``, ``U+FF61``) ends a + label; the result always uses ``U+002E`` as the separator. + + The 253-octet domain length limit (254 with a trailing dot) that + :func:`idna.encode` enforces is applied to the accumulated output, so + that streaming a name and encoding it in one shot either both succeed + with the same result or both raise :exc:`~idna.IDNAError`. + + Only the ``"strict"`` error handler is supported. + """ + + def __init__(self, errors: str = "strict") -> None: + super().__init__(errors) + self._emitted = 0 # octets returned so far + self._trailing_dot = False # whether the output so far ends with "." + + def reset(self) -> None: + super().reset() + self._emitted = 0 + self._trailing_dot = False + + def getstate(self) -> Any: + if not self.buffer and not self._emitted: + return 0 + return (self.buffer, self._emitted, self._trailing_dot) + + def setstate(self, state: Any) -> None: + if state: + self.buffer, self._emitted, self._trailing_dot = state + else: + self.reset() + + def _buffer_encode(self, data: str, errors: str, final: bool) -> tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"', code="unsupported_errors") + + result_bytes = b"" + size = 0 + if data: + labels = _unicode_dots_re.split(data) + trailing_dot = b"" + if labels: + if not labels[-1]: + trailing_dot = b"." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b"." + + result = [] + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + result_bytes = b".".join(result) + trailing_dot + size += len(trailing_dot) + + # Mirror encode(): the whole name may not exceed 253 octets, or 254 + # when it ends with a dot. Until the input is final a trailing dot + # may still arrive, so only the 254-octet ceiling applies before then. + self._emitted += len(result_bytes) + if result_bytes: + self._trailing_dot = result_bytes.endswith(b".") + may_end_with_dot = self._trailing_dot or not final + if self._emitted > _max_domain_length + may_end_with_dot: + raise IDNAError("Domain too long", code="domain_too_long") + return result_bytes, size + + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + """Incremental IDNA 2008 decoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + decoded one whole label at a time. + + The 254-octet input length limit that :func:`idna.decode` enforces is + applied to the accumulated input, so that streaming a name and decoding + it in one shot either both succeed with the same result or both raise + :exc:`~idna.IDNAError`. + + Only the ``"strict"`` error handler is supported. + """ + + def __init__(self, errors: str = "strict") -> None: + super().__init__(errors) + self._consumed = 0 # input octets consumed so far + + def reset(self) -> None: + super().reset() + self._consumed = 0 + + def getstate(self) -> tuple[bytes, int]: + return (self.buffer, self._consumed) + + def setstate(self, state: tuple[bytes, int]) -> None: + self.buffer, self._consumed = state + + def _buffer_decode(self, data: Any, errors: str, final: bool) -> tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"', code="unsupported_errors") + + if not data: + return ("", 0) + + if not isinstance(data, str): + try: + data = str(data, "ascii") + except UnicodeDecodeError as err: + raise IDNAError("Invalid ASCII in A-label", code="invalid_ascii") from err + + # Mirror decode(), which rejects input longer than 254 characters + # (253 plus a possible trailing dot) before looking at any label. + # ``data`` is the unconsumed buffer plus the new input, so this is + # the total seen so far. + if self._consumed + len(data) > _max_domain_length + 1: + raise IDNAError("Domain too long", code="domain_too_long") + + labels = _unicode_dots_re.split(data) + trailing_dot = "" + if labels: + if not labels[-1]: + trailing_dot = "." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = "." + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = ".".join(result) + trailing_dot + size += len(trailing_dot) + self._consumed += size + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> codecs.CodecInfo | None: + """Codec search function registered with :mod:`codecs`. + + Returns a :class:`codecs.CodecInfo` for the ``"idna2008"`` codec name + so that ``str.encode("idna2008")`` and ``bytes.decode("idna2008")`` + invoke the IDNA 2008 codec defined in this module. + + :param name: The codec name being looked up. + :returns: A :class:`codecs.CodecInfo` instance if ``name`` is + ``"idna2008"``, otherwise ``None``. + """ + if name != "idna2008": + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, + decode=Codec().decode, # type: ignore + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + + +codecs.register(search_function) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py new file mode 100644 index 0000000..5bedee9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/compat.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import Any + +from .core import decode, encode + + +def ToASCII(label: str) -> bytes: + """Compatibility shim for :rfc:`3490` ``ToASCII``. + + Delegates to :func:`idna.encode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.encode` directly. + + :param label: The label or domain to encode. + :returns: The encoded form as ASCII :class:`bytes`. + """ + return encode(label) + + +def ToUnicode(label: bytes | bytearray) -> str: + """Compatibility shim for :rfc:`3490` ``ToUnicode``. + + Delegates to :func:`idna.decode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.decode` directly. + + :param label: The label or domain to decode. + :returns: The decoded Unicode form. + """ + return decode(label) + + +def nameprep(s: Any) -> None: + """Stub for :rfc:`3491` Nameprep, which is not used by IDNA 2008. + + IDNA 2008 (:rfc:`5891`) replaces Nameprep with the per-codepoint + validity classes from :rfc:`5892`; this function exists only to + return a clear error if legacy code attempts to call it. + + :raises NotImplementedError: Always. + """ + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py new file mode 100644 index 0000000..d4a6f0e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/core.py @@ -0,0 +1,863 @@ +from __future__ import annotations + +import bisect +import re +import unicodedata +import warnings +from typing import Literal + +from . import idnadata +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b"xn--" +_max_input_length = 1024 +_max_domain_length = 253 # RFC 1035 octets, excluding any trailing dot +_STATUS_VALID, _STATUS_MAPPED, _STATUS_DEVIATION, _STATUS_IGNORED = b"VMDI" +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") +_std3_disallowed_re = re.compile("[\x00-\x2c\x2f\x3a-\x40A-Z\x5b-\x60\x7b-\x7f]") +_bidi_rtl_first = frozenset({"R", "AL"}) +_bidi_rtl_categories = frozenset({"R", "AL", "AN"}) +_bidi_rtl_allowed = frozenset({"R", "AL", "AN", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) +_bidi_rtl_valid_ending = frozenset({"R", "AL", "EN", "AN"}) +_bidi_rtl_numeric = frozenset({"AN", "EN"}) +_bidi_ltr_allowed = frozenset({"L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) +_bidi_ltr_valid_ending = frozenset({"L", "EN"}) +_bidi_joiner_l_or_d = frozenset({"L", "D"}) +_bidi_joiner_r_or_d = frozenset({"R", "D"}) + + +def _joining_type(cp: int) -> str | None: + for jt, ranges in idnadata.joining_types.items(): + if intranges_contain(cp, ranges): + return jt + return None + + +# Machine-readable identifiers for the rule an :class:`IDNAError` reports. +# These strings are stable and documented; exception message wording is not. +_ErrorCode = Literal[ + "input_too_long", + "label_too_long", + "domain_too_long", + "empty_label", + "empty_domain", + "not_nfc", + "hyphen_3_4", + "hyphen_start_end", + "leading_combiner", + "disallowed_codepoint", + "contextj", + "contexto", + "unknown_codepoint", + "bidi_rule_1", + "bidi_rule_2", + "bidi_rule_3", + "bidi_rule_4", + "bidi_rule_5", + "bidi_rule_6", + "bidi_unknown_direction", + "invalid_alabel", + "non_canonical_alabel", + "invalid_ascii", + "invalid_utf8", + "uts46_disallowed", + "uts46_std3", + "unsupported_errors", +] + + +class IDNAError(UnicodeError): + """Base exception for all IDNA-encoding related problems. + + ``str(err)`` is a human-readable description of the failure. The + exception also carries machine-readable attributes so callers do not + need to parse the message: + + * ``code`` -- a short, stable identifier for the rule that failed, such + as ``"disallowed_codepoint"`` or ``"bidi_rule_2"``; the full list is + documented in the README. Message wording, by contrast, may change + between releases. + * ``text`` -- the label (or, for UTS #46 processing, the domain) that + was being validated; + * ``codepoint`` -- the offending codepoint, as an ``int``; + * ``position`` -- the 1-based index of the offending character within + ``text``, matching the position quoted in the message. + + Each is ``None`` when it does not apply. + """ + + code: str | None + text: str | None + codepoint: int | None + position: int | None + + def __init__( + self, + *args: object, + code: _ErrorCode | None = None, + text: str | None = None, + codepoint: int | None = None, + position: int | None = None, + ) -> None: + super().__init__(*args) + self.code = code + self.text = text + self.codepoint = codepoint + self.position = position + + +class IDNABidiError(IDNAError): + """Exception when bidirectional requirements are not satisfied""" + + +class InvalidCodepoint(IDNAError): + """Exception when a disallowed or unallocated codepoint is used""" + + +class InvalidCodepointContext(IDNAError): + """Exception when the codepoint is not valid in the context it is used""" + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0 and not unicodedata.name(chr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + + +def _punycode(s: str) -> bytes: + return s.encode("punycode") + + +def _unot(s: int) -> str: + return f"U+{s:04X}" + + +def valid_label_length(label: bytes | str) -> bool: + """Check that a label does not exceed the maximum permitted length. + + Per :rfc:`1035` (and :rfc:`5891` §4.2.4) a DNS label must not exceed + 63 octets. The argument may be either a :class:`str` (a U-label, where + length is measured in characters) or :class:`bytes` (an A-label, where + length is measured in octets). + + :param label: The label to check. + :returns: ``True`` if the label is within the length limit, otherwise + ``False``. + """ + return len(label) <= 63 + + +def valid_string_length(domain: bytes | str, trailing_dot: bool) -> bool: + """Check that a full domain name does not exceed the maximum length. + + Per :rfc:`1035`, a domain name is limited to 253 octets when no trailing + dot is present, or 254 octets when one is included. + + :param domain: The full (possibly multi-label) domain name. + :param trailing_dot: ``True`` if ``domain`` includes a trailing ``.``. + :returns: ``True`` if the domain is within the length limit, otherwise + ``False``. + """ + return len(domain) <= _max_domain_length + trailing_dot + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + """Validate the Bidi Rule from :rfc:`5893` for a single label. + + The Bidi Rule constrains how bidirectional characters (Hebrew, Arabic, + etc.) may appear within a label. By default the check is only applied + when the label contains at least one right-to-left character (Unicode + bidirectional categories ``R``, ``AL``, or ``AN``); set ``check_ltr`` + to ``True`` to apply it to LTR-only labels as well. + + :param label: The label to validate, as a Unicode string. + :param check_ltr: If ``True``, apply the rules even when the label + contains no RTL characters. + :returns: ``True`` if the label satisfies the Bidi Rule. + :raises IDNABidiError: If any of Bidi Rule conditions 1-6 are violated, + or if the directional category of a codepoint cannot be determined. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == "": + # String likely comes from a newer version of Unicode + raise IDNABidiError( + f"Unknown directionality in label {label!r} at position {idx}", + code="bidi_unknown_direction", + text=label, + codepoint=ord(cp), + position=idx, + ) + if direction in _bidi_rtl_categories: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in _bidi_rtl_first: + rtl = True + elif direction == "L": + rtl = False + else: + raise IDNABidiError( + f"First codepoint in label {label!r} must be directionality L, R or AL", + code="bidi_rule_1", + text=label, + codepoint=ord(label[0]), + position=1, + ) + + valid_ending = False + ending_idx = 1 + number_type: str | None = None + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if direction not in _bidi_rtl_allowed: + raise IDNABidiError( + f"Invalid direction for codepoint at position {idx} in a right-to-left label", + code="bidi_rule_2", + text=label, + codepoint=ord(cp), + position=idx, + ) + # Bidi rule 3 + if direction in _bidi_rtl_valid_ending: + valid_ending = True + ending_idx = idx + elif direction != "NSM": + valid_ending = False + ending_idx = idx + # Bidi rule 4 + if direction in _bidi_rtl_numeric: + if not number_type: + number_type = direction + elif number_type != direction: + raise IDNABidiError( + "Can not mix numeral types in a right-to-left label", + code="bidi_rule_4", + text=label, + codepoint=ord(cp), + position=idx, + ) + else: + # Bidi rule 5 + if direction not in _bidi_ltr_allowed: + raise IDNABidiError( + f"Invalid direction for codepoint at position {idx} in a left-to-right label", + code="bidi_rule_5", + text=label, + codepoint=ord(cp), + position=idx, + ) + # Bidi rule 6 + if direction in _bidi_ltr_valid_ending: + valid_ending = True + ending_idx = idx + elif direction != "NSM": + valid_ending = False + ending_idx = idx + + if not valid_ending: + # Rules 3 and 6 concern the last character that is not a + # non-spacing mark, which is what ``ending_idx`` tracks. + raise IDNABidiError( + "Label ends with illegal codepoint directionality", + code="bidi_rule_3" if rtl else "bidi_rule_6", + text=label, + codepoint=ord(label[ending_idx - 1]), + position=ending_idx, + ) + + return True + + +def check_initial_combiner(label: str) -> bool: + """Reject labels that begin with a combining mark. + + Per :rfc:`5891` §4.2.3.2 a label must not start with a character of + Unicode general category ``M`` (Mark). + + :param label: The label to check. + :returns: ``True`` if the first character is not a combining mark. + :raises IDNAError: If the label begins with a combining character. + """ + if label and unicodedata.category(label[0])[0] == "M": + raise IDNAError( + "Label begins with an illegal combining character", + code="leading_combiner", + text=label, + codepoint=ord(label[0]), + position=1, + ) + return True + + +def check_hyphen_ok(label: str) -> bool: + """Validate the hyphen restrictions for a label. + + Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen + (``U+002D``), and must not have hyphens in both the third and fourth + positions (the prefix reserved for A-labels). + + :param label: The label to check. + :returns: ``True`` if the hyphen restrictions are satisfied. + :raises IDNAError: If any of the hyphen restrictions are violated. + """ + if label[2:4] == "--": + raise IDNAError("Label has disallowed hyphens in 3rd and 4th position", code="hyphen_3_4") + if label.startswith("-") or label.endswith("-"): + raise IDNAError("Label must not start or end with a hyphen", code="hyphen_start_end") + return True + + +def check_nfc(label: str) -> None: + """Require that a label is in Unicode Normalization Form C. + + :param label: The label to check. + :raises IDNAError: If ``label`` differs from its NFC normalisation. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + if unicodedata.normalize("NFC", label) != label: + raise IDNAError("Label must be in Normalization Form C", code="not_nfc") + + +def valid_contextj(label: str, pos: int) -> bool: + """Validate the CONTEXTJ rules from :rfc:`5892` Appendix A. + + These rules govern the contextual use of the joiner codepoints + ``U+200C`` (ZERO WIDTH NON-JOINER, Appendix A.1) and ``U+200D`` + (ZERO WIDTH JOINER, Appendix A.2) within a label. + + :param label: The label containing the codepoint. + :param pos: Index of the joiner codepoint within ``label``. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTJ + rule, ``False`` otherwise (including when the codepoint at + ``pos`` is not a recognised joiner). + :raises ValueError: If an adjacent codepoint has no Unicode name when + determining its combining class. + :raises IDNAError: If ``label`` exceeds the defensive input length limit. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + cp_value = ord(label[pos]) + + if cp_value == 0x200C: + if pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos - 1, -1, -1): + joining_type = _joining_type(ord(label[i])) + if joining_type == "T": + continue + if joining_type in _bidi_joiner_l_or_d: + ok = True + break + break + + if not ok: + return False + + ok = False + for i in range(pos + 1, len(label)): + joining_type = _joining_type(ord(label[i])) + if joining_type == "T": + continue + if joining_type in _bidi_joiner_r_or_d: + ok = True + break + break + return ok + + if cp_value == 0x200D: + return pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class + + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + """Validate the CONTEXTO rules from :rfc:`5892` Appendix A. + + Covers the contextual rules for codepoints such as MIDDLE DOT + (``U+00B7``), Greek lower numeral sign, Hebrew punctuation, Katakana + middle dot, and the Arabic-Indic / Extended Arabic-Indic digit ranges. + + :param label: The label containing the codepoint. + :param pos: Index of the codepoint within ``label``. + :param exception: Reserved for forward compatibility; currently unused. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTO + rule, ``False`` otherwise (including when the codepoint is not a + recognised CONTEXTO codepoint). + :raises IDNAError: If ``label`` exceeds the defensive input length limit. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + cp_value = ord(label[pos]) + + if cp_value == 0x00B7: + return 0 < pos < len(label) - 1 and ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C + + if cp_value == 0x0375: + if pos < len(label) - 1 and len(label) > 1: + return _is_script(label[pos + 1], "Greek") + return False + + if cp_value in {0x05F3, 0x05F4}: + if pos > 0: + return _is_script(label[pos - 1], "Hebrew") + return False + + if cp_value == 0x30FB: + for cp in label: + if cp == "\u30fb": + continue + if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): + return True + return False + + if 0x660 <= cp_value <= 0x669: + return not any(0x6F0 <= ord(cp) <= 0x06F9 for cp in label) + + if 0x6F0 <= cp_value <= 0x6F9: + return not any(0x660 <= ord(cp) <= 0x0669 for cp in label) + + return False + + +def check_label(label: str | bytes | bytearray) -> None: + """Run the full set of IDNA 2008 validity checks on a single label. + + Applies, in order: NFC normalisation (:func:`check_nfc`), hyphen + restrictions (:func:`check_hyphen_ok`), the no-leading-combiner rule + (:func:`check_initial_combiner`), per-codepoint validity (PVALID, + CONTEXTJ, CONTEXTO classes from :rfc:`5892`), and the Bidi Rule + (:func:`check_bidi`). + + :param label: The label to validate. ``bytes`` or ``bytearray`` input + is decoded as UTF-8 first. + :raises IDNAError: If the label is empty or fails a structural rule. + :raises InvalidCodepoint: If the label contains a DISALLOWED or + UNASSIGNED codepoint. + :raises InvalidCodepointContext: If a CONTEXTJ or CONTEXTO codepoint + is not valid in its context. + :raises IDNABidiError: If the Bidi Rule is violated. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + if isinstance(label, (bytes, bytearray)): + try: + label = label.decode("utf-8") + except UnicodeDecodeError as err: + raise IDNAError("Invalid UTF-8 in label", code="invalid_utf8") from err + if len(label) == 0: + raise IDNAError("Empty Label", code="empty_label") + + # Check against the domain length rather than the label length to + # support some UTS #46 use cases, while still bounding the work done + # by the label contextual rules below. + if not valid_string_length(label, trailing_dot=True): + raise IDNAError("Label too long", code="label_too_long") + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for pos, cp in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): + continue + if intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): + try: + contextj_ok = valid_contextj(label, pos) + except ValueError as err: + raise IDNAError( + f"Unknown codepoint adjacent to joiner {_unot(cp_value)} at position {pos + 1} in {label!r}", + code="unknown_codepoint", + text=label, + codepoint=cp_value, + position=pos + 1, + ) from err + if not contextj_ok: + raise InvalidCodepointContext( + f"Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}", + code="contextj", + text=label, + codepoint=cp_value, + position=pos + 1, + ) + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): + if not valid_contexto(label, pos): + raise InvalidCodepointContext( + f"Codepoint {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}", + code="contexto", + text=label, + codepoint=cp_value, + position=pos + 1, + ) + else: + raise InvalidCodepoint( + f"Codepoint {_unot(cp_value)} at position {pos + 1} of {label!r} not allowed", + code="disallowed_codepoint", + text=label, + codepoint=cp_value, + position=pos + 1, + ) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + """Convert a single U-label into its A-label form. + + The result is the ASCII-Compatible Encoding (ACE) form per :rfc:`5891` + §4: the label is validated, Punycode-encoded, and prefixed with + ``xn--``. Pure ASCII labels that are already valid IDNA labels are + returned unchanged (as :class:`bytes`). + + :param label: The label to convert, as a Unicode string. + :returns: The A-label as ASCII-encoded :class:`bytes`. + :raises IDNAError: If the label is invalid or the resulting A-label + exceeds 63 octets. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + pass + else: + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError("Label too long", code="label_too_long") + return label_bytes + + check_label(label) + label_bytes = _alabel_prefix + _punycode(label) + + if not valid_label_length(label_bytes): + raise IDNAError("Label too long", code="label_too_long") + + return label_bytes + + +def ulabel(label: str | bytes | bytearray) -> str: + """Convert a single A-label into its U-label form. + + Performs the inverse of :func:`alabel`: an ``xn--``-prefixed label is + Punycode-decoded and validated, and is rejected unless it is the + canonical A-label for the decoded U-label (:rfc:`5891` §5.3). Labels + that are already Unicode (or plain ASCII without the ACE prefix) are + validated and returned as a Unicode string. + + :param label: The label to convert. ``bytes`` or ``bytearray`` input + is treated as ASCII. + :returns: The U-label as a Unicode string. + :raises IDNAError: If the label is malformed or fails validation. + """ + if len(label) > _max_input_length: + raise IDNAError("Label too long", code="input_too_long") + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = bytes(label) + if not label_bytes.isascii(): + raise IDNAError("Invalid ASCII in A-label", code="invalid_ascii") + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix) :] + if not label_bytes: + raise IDNAError("Malformed A-label, no Punycode eligible content found", code="invalid_alabel") + if label_bytes.endswith(b"-"): + raise IDNAError("A-label must not end with a hyphen", code="invalid_alabel") + else: + check_label(label_bytes) + return label_bytes.decode("ascii") + + try: + label = label_bytes.decode("punycode") + except UnicodeError as err: + raise IDNAError("Invalid A-label", code="invalid_alabel") from err + # RFC 5891 §5.3: the label is rejected unless re-encoding the decoded + # form reproduces the (lowercased) input. This catches "fake A-labels" + # (RFC 5890 §2.3.2.1) such as ``xn---bbk``, a non-canonical Punycode + # spelling of ``xn--bbk`` that would otherwise decode to the same + # U-label and so display identically to a different wire-format name. + if _punycode(label) != label_bytes: + raise IDNAError("A-label is not the canonical Punycode encoding of its U-label", code="non_canonical_alabel") + check_label(label) + return label + + +def _check_std3(text: str, domain: str, offset: int) -> None: + """Raise if ``text``, a slice of ``domain`` starting at ``offset`` that + UTS #46 mapping left unchanged, contains an ASCII character disallowed + under ``UseSTD3ASCIIRules``.""" + match = _std3_disallowed_re.search(text) + if match: + codepoint = ord(match.group()) + position = offset + match.start() + 1 + raise InvalidCodepoint( + f"Codepoint {_unot(codepoint)} not allowed at position {position} in {domain!r}", + code="uts46_std3", + text=domain, + codepoint=codepoint, + position=position, + ) + + +def _warn_transitional() -> None: + warnings.warn( + "Transitional processing is deprecated in UTS #46 and has no effect. " + "The transitional argument will be removed in a future version.", + DeprecationWarning, + stacklevel=3, + ) + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Apply the UTS #46 character mapping to a domain string. + + Implements the mapping table from `UTS #46 §4 + `_: each character is kept, + replaced, or rejected based on its status (``V``, ``M``, ``D``, + ``I``, ``X``). The result is returned in Normalisation Form C. + + :param domain: The full domain name to remap. + :param std3_rules: If ``True``, apply UTS #46's ``UseSTD3ASCIIRules``: + after mapping, any ASCII character other than a lowercase letter, + digit, hyphen or the label separator ``.`` is rejected, whether it + appeared in the input or was produced by a mapping (e.g. U+FF01 + FULLWIDTH EXCLAMATION MARK maps to ``!``). If ``False``, such + characters are passed through. + :param transitional: Deprecated and ignored. UTS #46 deprecated + transitional processing in Unicode 15.1 and deviation (status + ``D``) codepoints are now always kept, so this has no effect + beyond emitting a :class:`DeprecationWarning`. It will be removed + in a future version. + :returns: The remapped domain, in Normalisation Form C. + :raises InvalidCodepoint: If the domain contains a disallowed + codepoint under the chosen rules. + :raises IDNAError: If ``domain`` exceeds the defensive input length limit. + """ + if transitional: + _warn_transitional() + if len(domain) > _max_input_length: + raise IDNAError("Domain too long", code="input_too_long") + if domain.isascii(): + # The only ASCII mapping in UTS #46 is upper- to lowercase, and + # ASCII is invariant under NFC, so lowercasing is the whole job. + result = domain.lower() + if std3_rules: + _check_std3(result, domain, 0) + return result + + from .uts46data import uts46_replacements, uts46_starts, uts46_statuses + + # ``start`` marks the run of unchanged input not yet copied; a run is + # only sliced out when a character must be replaced or dropped, so the + # common no-change case makes no copy. STD3 is checked per output piece + # to report a violation at its input position. + output: list[str] = [] + start = 0 + for pos, char in enumerate(domain): + code_point = ord(char) + i = code_point if code_point < 256 else bisect.bisect_right(uts46_starts, code_point) - 1 + status = uts46_statuses[i] + # UTS #46 §4: V valid, D deviation (kept), M mapped, I ignored, + # anything else disallowed. + if status == _STATUS_VALID: + continue + if status == _STATUS_MAPPED: + replacement = uts46_replacements[i] + elif status == _STATUS_DEVIATION: + continue + elif status == _STATUS_IGNORED: + replacement = None + else: + raise InvalidCodepoint( + f"Codepoint {_unot(code_point)} not allowed at position {pos + 1} in {domain!r}", + code="uts46_disallowed", + text=domain, + codepoint=code_point, + position=pos + 1, + ) + if start < pos: + run = domain[start:pos] + if std3_rules: + _check_std3(run, domain, start) + output.append(run) + if replacement: + if std3_rules and _std3_disallowed_re.search(replacement): + raise InvalidCodepoint( + f"Codepoint {_unot(code_point)} not allowed at position {pos + 1} in {domain!r}", + code="uts46_std3", + text=domain, + codepoint=code_point, + position=pos + 1, + ) + output.append(replacement) + start = pos + 1 + + if start == 0: + if std3_rules: + _check_std3(domain, domain, 0) + return unicodedata.normalize("NFC", domain) + tail = domain[start:] + if std3_rules: + _check_std3(tail, domain, start) + output.append(tail) + return unicodedata.normalize("NFC", "".join(output)) + + +def encode( + s: str | bytes | bytearray, + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + transitional: bool = False, +) -> bytes: + """Encode a Unicode domain name into its ASCII (A-label) form. + + Splits the input on label separators (only ``U+002E`` if ``strict`` is + set; otherwise also IDEOGRAPHIC FULL STOP ``U+3002``, FULLWIDTH FULL + STOP ``U+FF0E``, and HALFWIDTH IDEOGRAPHIC FULL STOP ``U+FF61``), + encodes each label with :func:`alabel`, and rejoins them with ``.``. + Optionally pre-processes the input through :func:`uts46_remap`. + + :param s: The domain name to encode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before encoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :param transitional: Deprecated and ignored (see :func:`uts46_remap`): + emits a :class:`DeprecationWarning` and will be removed in a + future version. + :returns: The encoded domain as ASCII :class:`bytes`. + :raises IDNAError: If the domain is empty, contains an invalid label, + or exceeds the maximum domain length. + """ + if transitional: + _warn_transitional() + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError) as err: + raise IDNAError( + "should pass a unicode string to the function rather than a byte string.", code="invalid_ascii" + ) from err + if len(s) > _max_input_length: + raise IDNAError("Domain too long", code="input_too_long") + if uts46: + s = uts46_remap(s, std3_rules) + + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long", code="domain_too_long") + + trailing_dot = False + result = [] + labels = s.split(".") if strict else _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain", code="empty_domain") + if labels[-1] == "": + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label", code="empty_label") + if trailing_dot: + result.append(b"") + s = b".".join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError("Domain too long", code="domain_too_long") + return s + + +def decode( + s: str | bytes | bytearray, + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + display: bool = False, +) -> str: + """Decode an A-label-encoded domain name back to Unicode. + + Splits the input on label separators (see :func:`encode` for the + rules), decodes each label with :func:`ulabel`, and rejoins them + with ``.``. Optionally pre-processes the input through + :func:`uts46_remap`. + + :param s: The domain name to decode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before decoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :param display: If ``True``, any ``xn--`` label that fails IDNA + validation is passed through unchanged (lowercased) rather than + aborting the whole call. Intended for "decode for display" + consumers (e.g. URL libraries, HTTP clients) that want to show + the user the label as it appears on the wire when it cannot be + rendered as Unicode. Matches the per-label recovery prescribed + by UTS #46 §4 and the WHATWG URL "domain to Unicode" algorithm. + :returns: The decoded domain as a Unicode string. + :raises IDNAError: If the input is not valid ASCII, contains an + invalid label, or is empty. + """ + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError) as err: + raise IDNAError("Invalid ASCII in A-label", code="invalid_ascii") from err + if len(s) > _max_input_length: + raise IDNAError("Domain too long", code="input_too_long") + if uts46: + s = uts46_remap(s, std3_rules, False) + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long", code="domain_too_long") + trailing_dot = False + result = [] + labels = s.split(".") if strict else _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain", code="empty_domain") + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + try: + u = ulabel(label) + except IDNAError: + if display and label[:4].lower() == "xn--": + u = label.lower() + else: + raise + if u: + result.append(u) + else: + raise IDNAError("Empty label", code="empty_label") + if trailing_dot: + result.append("") + return ".".join(result) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py new file mode 100644 index 0000000..471c047 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/idnadata.py @@ -0,0 +1,1933 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "18.0.0" + +scripts = { + "Greek": ( + 0x37000000374, + 0x37500000378, + 0x37A0000037E, + 0x37F00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038B, + 0x38C0000038D, + 0x38E000003A2, + 0x3A3000003E2, + 0x3F000000400, + 0x1D2600001D2B, + 0x1D5D00001D62, + 0x1D6600001D6B, + 0x1DBF00001DC0, + 0x1F0000001F16, + 0x1F1800001F1E, + 0x1F2000001F46, + 0x1F4800001F4E, + 0x1F5000001F58, + 0x1F5900001F5A, + 0x1F5B00001F5C, + 0x1F5D00001F5E, + 0x1F5F00001F7E, + 0x1F8000001FB5, + 0x1FB600001FC5, + 0x1FC600001FD4, + 0x1FD600001FDC, + 0x1FDD00001FF0, + 0x1FF200001FF5, + 0x1FF600001FFF, + 0x212600002127, + 0xAB650000AB66, + 0x101400001018F, + 0x101A0000101A1, + 0x1D2000001D246, + 0x1DFF30001DFF5, + ), + "Han": ( + 0x2E8000002E9A, + 0x2E9B00002EF4, + 0x2F0000002FD6, + 0x300500003006, + 0x300700003008, + 0x30210000302A, + 0x30380000303C, + 0x340000004DC0, + 0x4E000000A000, + 0xF9000000FA6E, + 0xFA700000FADA, + 0x16FE200016FE4, + 0x16FF000016FF7, + 0x200000002A6E0, + 0x2A7000002B81F, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x2F8000002FA1E, + 0x300000003134B, + 0x313500003347A, + ), + "Hebrew": ( + 0x591000005CA, + 0x5D0000005EB, + 0x5EF000005F5, + 0xFB1D0000FB37, + 0xFB380000FB3D, + 0xFB3E0000FB3F, + 0xFB400000FB42, + 0xFB430000FB45, + 0xFB460000FB50, + ), + "Hiragana": ( + 0x304100003097, + 0x309D000030A0, + 0x1B0010001B120, + 0x1B1230001B124, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1F2000001F201, + ), + "Katakana": ( + 0x30A1000030FB, + 0x30FD00003100, + 0x31F000003200, + 0x32D0000032FF, + 0x330000003358, + 0xFF660000FF70, + 0xFF710000FF9E, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B001, + 0x1B1200001B123, + 0x1B1240001B129, + 0x1B1550001B156, + 0x1B1640001B169, + ), +} + + +joining_types = { + "C": ( + 0x64000000641, + 0x7FA000007FB, + 0x88300000886, + 0x180A0000180B, + 0x200D0000200E, + ), + "D": ( + 0x62000000621, + 0x62600000627, + 0x62800000629, + 0x62A0000062F, + 0x63300000640, + 0x64100000648, + 0x6490000064B, + 0x66E00000670, + 0x67800000688, + 0x69A000006C0, + 0x6C1000006C3, + 0x6CC000006CD, + 0x6CE000006CF, + 0x6D0000006D2, + 0x6FA000006FD, + 0x6FF00000700, + 0x71200000715, + 0x71A0000071E, + 0x71F00000728, + 0x7290000072A, + 0x72B0000072C, + 0x72D0000072F, + 0x74E00000759, + 0x75C0000076B, + 0x76D00000771, + 0x77200000773, + 0x77500000778, + 0x77A00000780, + 0x7CA000007EB, + 0x84100000846, + 0x84800000849, + 0x84A00000854, + 0x85500000856, + 0x86000000861, + 0x86200000866, + 0x86800000869, + 0x88600000887, + 0x8890000088E, + 0x88F00000890, + 0x8A0000008AA, + 0x8AF000008B1, + 0x8B3000008B9, + 0x8BA000008C9, + 0x180700001808, + 0x182000001879, + 0x1887000018A9, + 0x18AA000018AB, + 0xA8400000A872, + 0x10AC000010AC5, + 0x10AD300010AD7, + 0x10AD800010ADD, + 0x10ADE00010AE1, + 0x10AEB00010AEF, + 0x10B8000010B81, + 0x10B8200010B83, + 0x10B8600010B89, + 0x10B8A00010B8C, + 0x10B8D00010B8E, + 0x10B9000010B91, + 0x10BAD00010BAF, + 0x10D0100010D22, + 0x10D2300010D24, + 0x10EC300010EC5, + 0x10EC600010EC8, + 0x10F3000010F33, + 0x10F3400010F45, + 0x10F5100010F54, + 0x10F7000010F74, + 0x10F7600010F82, + 0x10FB000010FB1, + 0x10FB200010FB4, + 0x10FB800010FB9, + 0x10FBB00010FBD, + 0x10FBE00010FC0, + 0x10FC100010FC2, + 0x10FC400010FC5, + 0x10FCA00010FCB, + 0x1E9000001E944, + ), + "L": ( + 0xA8720000A873, + 0x10ACD00010ACE, + 0x10AD700010AD8, + 0x10D0000010D01, + 0x10ED900010EEF, + 0x10FCB00010FCC, + ), + "R": ( + 0x62200000626, + 0x62700000628, + 0x6290000062A, + 0x62F00000633, + 0x64800000649, + 0x67100000674, + 0x67500000678, + 0x6880000069A, + 0x6C0000006C1, + 0x6C3000006CC, + 0x6CD000006CE, + 0x6CF000006D0, + 0x6D2000006D4, + 0x6D5000006D6, + 0x6EE000006F0, + 0x71000000711, + 0x7150000071A, + 0x71E0000071F, + 0x72800000729, + 0x72A0000072B, + 0x72C0000072D, + 0x72F00000730, + 0x74D0000074E, + 0x7590000075C, + 0x76B0000076D, + 0x77100000772, + 0x77300000775, + 0x7780000077A, + 0x84000000841, + 0x84600000848, + 0x8490000084A, + 0x85400000855, + 0x85600000859, + 0x86700000868, + 0x8690000086B, + 0x87000000883, + 0x88E0000088F, + 0x8AA000008AD, + 0x8AE000008AF, + 0x8B1000008B3, + 0x8B9000008BA, + 0x10AC500010AC6, + 0x10AC700010AC8, + 0x10AC900010ACB, + 0x10ACE00010AD3, + 0x10ADD00010ADE, + 0x10AE100010AE2, + 0x10AE400010AE5, + 0x10AEF00010AF0, + 0x10B8100010B82, + 0x10B8300010B86, + 0x10B8900010B8A, + 0x10B8C00010B8D, + 0x10B8E00010B90, + 0x10B9100010B92, + 0x10BA900010BAD, + 0x10D2200010D23, + 0x10EC200010EC3, + 0x10F3300010F34, + 0x10F5400010F55, + 0x10F7400010F76, + 0x10FB400010FB7, + 0x10FB900010FBB, + 0x10FBD00010FBE, + 0x10FC200010FC4, + 0x10FC900010FCA, + ), + "T": ( + 0xAD000000AE, + 0x30000000370, + 0x4830000048A, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005CA, + 0x6100000061B, + 0x61C0000061D, + 0x64B00000660, + 0x67000000671, + 0x6D6000006DD, + 0x6DF000006E5, + 0x6E7000006E9, + 0x6EA000006EE, + 0x70F00000710, + 0x71100000712, + 0x7300000074B, + 0x7A6000007B1, + 0x7EB000007F4, + 0x7FD000007FE, + 0x8160000081A, + 0x81B00000824, + 0x82500000828, + 0x8290000082E, + 0x8590000085C, + 0x897000008A0, + 0x8CA000008E2, + 0x8E300000903, + 0x93A0000093B, + 0x93C0000093D, + 0x94100000949, + 0x94D0000094E, + 0x95100000958, + 0x96200000964, + 0x98100000982, + 0x9BC000009BD, + 0x9C1000009C5, + 0x9CD000009CE, + 0x9E2000009E4, + 0x9FE000009FF, + 0xA0100000A03, + 0xA3C00000A3D, + 0xA4100000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA7000000A72, + 0xA7500000A76, + 0xA8100000A83, + 0xABC00000ABD, + 0xAC100000AC6, + 0xAC700000AC9, + 0xACD00000ACE, + 0xAE200000AE4, + 0xAFA00000B00, + 0xB0100000B02, + 0xB3C00000B3D, + 0xB3F00000B40, + 0xB4100000B45, + 0xB4D00000B4E, + 0xB5300000B57, + 0xB6200000B64, + 0xB8200000B83, + 0xBC000000BC1, + 0xBCD00000BCE, + 0xC0000000C01, + 0xC0400000C05, + 0xC3C00000C3D, + 0xC3E00000C41, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC6200000C64, + 0xC8100000C82, + 0xCBC00000CBD, + 0xCBF00000CC0, + 0xCC600000CC7, + 0xCCC00000CCE, + 0xCE200000CE4, + 0xD0000000D02, + 0xD3B00000D3D, + 0xD4100000D45, + 0xD4D00000D4E, + 0xD6200000D64, + 0xD8100000D82, + 0xDCA00000DCB, + 0xDD200000DD5, + 0xDD600000DD7, + 0xE3100000E32, + 0xE3400000E3B, + 0xE4700000E4F, + 0xEB100000EB2, + 0xEB400000EBD, + 0xEC800000ECF, + 0xF1800000F1A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF7100000F7F, + 0xF8000000F85, + 0xF8600000F88, + 0xF8D00000F98, + 0xF9900000FBD, + 0xFC600000FC7, + 0x102D00001031, + 0x103200001038, + 0x10390000103B, + 0x103D0000103F, + 0x10580000105A, + 0x105E00001061, + 0x107100001075, + 0x108200001083, + 0x108500001087, + 0x108D0000108E, + 0x109D0000109E, + 0x135D00001360, + 0x171200001715, + 0x173200001734, + 0x175200001754, + 0x177200001774, + 0x17B4000017B6, + 0x17B7000017BE, + 0x17C6000017C7, + 0x17C9000017D4, + 0x17DD000017DE, + 0x180B0000180E, + 0x180F00001810, + 0x188500001887, + 0x18A9000018AA, + 0x192000001923, + 0x192700001929, + 0x193200001933, + 0x19390000193C, + 0x1A1700001A19, + 0x1A1B00001A1C, + 0x1A5600001A57, + 0x1A5800001A5F, + 0x1A6000001A61, + 0x1A6200001A63, + 0x1A6500001A6D, + 0x1A7300001A7D, + 0x1A7F00001A80, + 0x1AB000001AF1, + 0x1B0000001B04, + 0x1B3400001B35, + 0x1B3600001B3B, + 0x1B3C00001B3D, + 0x1B4200001B43, + 0x1B6B00001B74, + 0x1B8000001B82, + 0x1BA200001BA6, + 0x1BA800001BAA, + 0x1BAB00001BAE, + 0x1BE600001BE7, + 0x1BE800001BEA, + 0x1BED00001BEE, + 0x1BEF00001BF2, + 0x1C2C00001C34, + 0x1C3600001C38, + 0x1CD000001CD3, + 0x1CD400001CE1, + 0x1CE200001CE9, + 0x1CED00001CEE, + 0x1CF400001CF5, + 0x1CF800001CFA, + 0x1DC000001E00, + 0x200B0000200C, + 0x200E00002010, + 0x202A0000202F, + 0x206000002065, + 0x206A00002070, + 0x20D0000020F1, + 0x2CEF00002CF2, + 0x2D7F00002D80, + 0x2DE000002E00, + 0x302A0000302E, + 0x30990000309B, + 0xA66F0000A673, + 0xA6740000A67E, + 0xA69E0000A6A0, + 0xA6F00000A6F2, + 0xA8020000A803, + 0xA8060000A807, + 0xA80B0000A80C, + 0xA8250000A827, + 0xA82C0000A82D, + 0xA8C40000A8C6, + 0xA8E00000A8F2, + 0xA8FF0000A900, + 0xA9260000A92E, + 0xA9470000A952, + 0xA9800000A983, + 0xA9B30000A9B4, + 0xA9B60000A9BA, + 0xA9BC0000A9BE, + 0xA9E50000A9E6, + 0xAA290000AA2F, + 0xAA310000AA33, + 0xAA350000AA37, + 0xAA430000AA44, + 0xAA4C0000AA4D, + 0xAA7C0000AA7D, + 0xAAB00000AAB1, + 0xAAB20000AAB5, + 0xAAB70000AAB9, + 0xAABE0000AAC0, + 0xAAC10000AAC2, + 0xAAEC0000AAEE, + 0xAAF60000AAF7, + 0xABE50000ABE6, + 0xABE80000ABE9, + 0xABED0000ABEE, + 0xFB1E0000FB1F, + 0xFE000000FE10, + 0xFE200000FE30, + 0xFEFF0000FF00, + 0xFFF90000FFFC, + 0x101FD000101FE, + 0x102E0000102E1, + 0x103760001037B, + 0x10A0100010A04, + 0x10A0500010A07, + 0x10A0C00010A10, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10AE500010AE7, + 0x10D2400010D28, + 0x10D6900010D6E, + 0x10EAB00010EAD, + 0x10ECB00010ED0, + 0x10EF000010F00, + 0x10F4600010F51, + 0x10F8200010F86, + 0x1100100011002, + 0x1103800011047, + 0x1107000011071, + 0x1107300011075, + 0x1107F00011082, + 0x110B3000110B7, + 0x110B9000110BB, + 0x110C2000110C3, + 0x1110000011103, + 0x111270001112C, + 0x1112D00011135, + 0x1117300011174, + 0x1118000011182, + 0x111B6000111BF, + 0x111C9000111CD, + 0x111CF000111D0, + 0x1122F00011232, + 0x1123400011235, + 0x1123600011238, + 0x1123E0001123F, + 0x1124100011242, + 0x112DF000112E0, + 0x112E3000112EB, + 0x1130000011302, + 0x1133B0001133D, + 0x1134000011341, + 0x113660001136D, + 0x1137000011375, + 0x113BB000113C1, + 0x113CE000113CF, + 0x113D0000113D1, + 0x113D2000113D3, + 0x113E1000113E3, + 0x1143800011440, + 0x1144200011445, + 0x1144600011447, + 0x1145E0001145F, + 0x114B3000114B9, + 0x114BA000114BB, + 0x114BF000114C1, + 0x114C2000114C4, + 0x115B2000115B6, + 0x115BC000115BE, + 0x115BF000115C1, + 0x115DC000115DE, + 0x116330001163B, + 0x1163D0001163E, + 0x1163F00011641, + 0x116AB000116AC, + 0x116AD000116AE, + 0x116B0000116B6, + 0x116B7000116B8, + 0x1171D0001171E, + 0x1171F00011720, + 0x1172200011726, + 0x117270001172C, + 0x1182F00011838, + 0x118390001183B, + 0x1193B0001193D, + 0x1193E0001193F, + 0x1194300011944, + 0x119D4000119D8, + 0x119DA000119DC, + 0x119E0000119E1, + 0x11A0100011A0B, + 0x11A3300011A39, + 0x11A3B00011A3F, + 0x11A4700011A48, + 0x11A5100011A57, + 0x11A5900011A5C, + 0x11A8A00011A97, + 0x11A9800011A9A, + 0x11B6000011B61, + 0x11B6200011B65, + 0x11B6600011B67, + 0x11C3000011C37, + 0x11C3800011C3E, + 0x11C3F00011C40, + 0x11C9200011CA8, + 0x11CAA00011CB1, + 0x11CB200011CB4, + 0x11CB500011CB7, + 0x11D3100011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D46, + 0x11D4700011D48, + 0x11D9000011D92, + 0x11D9500011D96, + 0x11D9700011D98, + 0x11DF000011DF1, + 0x11EF300011EF5, + 0x11F0000011F02, + 0x11F3600011F3B, + 0x11F4000011F41, + 0x11F4200011F43, + 0x11F5A00011F5B, + 0x1343000013441, + 0x1344700013456, + 0x1611E0001612A, + 0x1612D00016130, + 0x16AF000016AF5, + 0x16B3000016B37, + 0x16F4F00016F50, + 0x16F8F00016F93, + 0x16FE400016FE5, + 0x1BC9D0001BC9F, + 0x1BCA00001BCA4, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1D1270001D129, + 0x1D1670001D16A, + 0x1D1730001D183, + 0x1D1850001D18C, + 0x1D1AA0001D1AE, + 0x1D2420001D245, + 0x1D25B0001D25D, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1300001E137, + 0x1E2AE0001E2AF, + 0x1E2EC0001E2F0, + 0x1E4EC0001E4F0, + 0x1E5EE0001E5F0, + 0x1E6E30001E6E4, + 0x1E6E60001E6E7, + 0x1E6EE0001E6F0, + 0x1E6F50001E6F6, + 0x1E8D00001E8D7, + 0x1E9440001E94C, + 0xE0001000E0002, + 0xE0020000E0080, + 0xE0100000E01F0, + ), +} + + +codepoint_classes = { + "PVALID": ( + 0x2D0000002E, + 0x300000003A, + 0x610000007B, + 0xDF000000F7, + 0xF800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010A, + 0x10B0000010C, + 0x10D0000010E, + 0x10F00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011A, + 0x11B0000011C, + 0x11D0000011E, + 0x11F00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012A, + 0x12B0000012C, + 0x12D0000012E, + 0x12F00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13A0000013B, + 0x13C0000013D, + 0x13E0000013F, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14B0000014C, + 0x14D0000014E, + 0x14F00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015A, + 0x15B0000015C, + 0x15D0000015E, + 0x15F00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016A, + 0x16B0000016C, + 0x16D0000016E, + 0x16F00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17A0000017B, + 0x17C0000017D, + 0x17E0000017F, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18C0000018E, + 0x19200000193, + 0x19500000196, + 0x1990000019C, + 0x19E0000019F, + 0x1A1000001A2, + 0x1A3000001A4, + 0x1A5000001A6, + 0x1A8000001A9, + 0x1AA000001AC, + 0x1AD000001AE, + 0x1B0000001B1, + 0x1B4000001B5, + 0x1B6000001B7, + 0x1B9000001BC, + 0x1BD000001C4, + 0x1CE000001CF, + 0x1D0000001D1, + 0x1D2000001D3, + 0x1D4000001D5, + 0x1D6000001D7, + 0x1D8000001D9, + 0x1DA000001DB, + 0x1DC000001DE, + 0x1DF000001E0, + 0x1E1000001E2, + 0x1E3000001E4, + 0x1E5000001E6, + 0x1E7000001E8, + 0x1E9000001EA, + 0x1EB000001EC, + 0x1ED000001EE, + 0x1EF000001F1, + 0x1F5000001F6, + 0x1F9000001FA, + 0x1FB000001FC, + 0x1FD000001FE, + 0x1FF00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020A, + 0x20B0000020C, + 0x20D0000020E, + 0x20F00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021A, + 0x21B0000021C, + 0x21D0000021E, + 0x21F00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022A, + 0x22B0000022C, + 0x22D0000022E, + 0x22F00000230, + 0x23100000232, + 0x2330000023A, + 0x23C0000023D, + 0x23F00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024A, + 0x24B0000024C, + 0x24D0000024E, + 0x24F000002B0, + 0x2B9000002C2, + 0x2C6000002D2, + 0x2EC000002ED, + 0x2EE000002EF, + 0x30000000340, + 0x34200000343, + 0x3460000034F, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37B0000037E, + 0x39000000391, + 0x3AC000003CF, + 0x3D7000003D8, + 0x3D9000003DA, + 0x3DB000003DC, + 0x3DD000003DE, + 0x3DF000003E0, + 0x3E1000003E2, + 0x3E3000003E4, + 0x3E5000003E6, + 0x3E7000003E8, + 0x3E9000003EA, + 0x3EB000003EC, + 0x3ED000003EE, + 0x3EF000003F0, + 0x3F3000003F4, + 0x3F8000003F9, + 0x3FB000003FD, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046A, + 0x46B0000046C, + 0x46D0000046E, + 0x46F00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047A, + 0x47B0000047C, + 0x47D0000047E, + 0x47F00000480, + 0x48100000482, + 0x48300000488, + 0x48B0000048C, + 0x48D0000048E, + 0x48F00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049A, + 0x49B0000049C, + 0x49D0000049E, + 0x49F000004A0, + 0x4A1000004A2, + 0x4A3000004A4, + 0x4A5000004A6, + 0x4A7000004A8, + 0x4A9000004AA, + 0x4AB000004AC, + 0x4AD000004AE, + 0x4AF000004B0, + 0x4B1000004B2, + 0x4B3000004B4, + 0x4B5000004B6, + 0x4B7000004B8, + 0x4B9000004BA, + 0x4BB000004BC, + 0x4BD000004BE, + 0x4BF000004C0, + 0x4C2000004C3, + 0x4C4000004C5, + 0x4C6000004C7, + 0x4C8000004C9, + 0x4CA000004CB, + 0x4CC000004CD, + 0x4CE000004D0, + 0x4D1000004D2, + 0x4D3000004D4, + 0x4D5000004D6, + 0x4D7000004D8, + 0x4D9000004DA, + 0x4DB000004DC, + 0x4DD000004DE, + 0x4DF000004E0, + 0x4E1000004E2, + 0x4E3000004E4, + 0x4E5000004E6, + 0x4E7000004E8, + 0x4E9000004EA, + 0x4EB000004EC, + 0x4ED000004EE, + 0x4EF000004F0, + 0x4F1000004F2, + 0x4F3000004F4, + 0x4F5000004F6, + 0x4F7000004F8, + 0x4F9000004FA, + 0x4FB000004FC, + 0x4FD000004FE, + 0x4FF00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050A, + 0x50B0000050C, + 0x50D0000050E, + 0x50F00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051A, + 0x51B0000051C, + 0x51D0000051E, + 0x51F00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052A, + 0x52B0000052C, + 0x52D0000052E, + 0x52F00000530, + 0x5590000055A, + 0x56000000587, + 0x58800000589, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005CA, + 0x5D0000005EB, + 0x5EF000005F3, + 0x6100000061B, + 0x62000000640, + 0x64100000660, + 0x66E00000675, + 0x679000006D4, + 0x6D5000006DD, + 0x6DF000006E9, + 0x6EA000006F0, + 0x6FA00000700, + 0x7100000074B, + 0x74D000007B2, + 0x7C0000007F6, + 0x7FD000007FE, + 0x8000000082E, + 0x8400000085C, + 0x8600000086B, + 0x87000000888, + 0x88900000890, + 0x897000008E2, + 0x8E300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098D, + 0x98F00000991, + 0x993000009A9, + 0x9AA000009B1, + 0x9B2000009B3, + 0x9B6000009BA, + 0x9BC000009C5, + 0x9C7000009C9, + 0x9CB000009CF, + 0x9D7000009D8, + 0x9E0000009E4, + 0x9E6000009F2, + 0x9FC000009FD, + 0x9FE000009FF, + 0xA0100000A04, + 0xA0500000A0B, + 0xA0F00000A11, + 0xA1300000A29, + 0xA2A00000A31, + 0xA3200000A33, + 0xA3500000A36, + 0xA3800000A3A, + 0xA3C00000A3D, + 0xA3E00000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA5C00000A5D, + 0xA6600000A76, + 0xA8100000A84, + 0xA8500000A8E, + 0xA8F00000A92, + 0xA9300000AA9, + 0xAAA00000AB1, + 0xAB200000AB4, + 0xAB500000ABA, + 0xABC00000AC6, + 0xAC700000ACA, + 0xACB00000ACE, + 0xAD000000AD1, + 0xAE000000AE4, + 0xAE600000AF0, + 0xAF900000B00, + 0xB0100000B04, + 0xB0500000B0D, + 0xB0F00000B11, + 0xB1300000B29, + 0xB2A00000B31, + 0xB3200000B34, + 0xB3500000B3A, + 0xB3C00000B45, + 0xB4700000B49, + 0xB4B00000B4E, + 0xB5300000B58, + 0xB5F00000B64, + 0xB6600000B70, + 0xB7100000B72, + 0xB8200000B84, + 0xB8500000B8B, + 0xB8E00000B91, + 0xB9200000B96, + 0xB9900000B9B, + 0xB9C00000B9D, + 0xB9E00000BA0, + 0xBA300000BA5, + 0xBA800000BAB, + 0xBAE00000BBA, + 0xBBE00000BC3, + 0xBC600000BC9, + 0xBCA00000BCE, + 0xBD000000BD1, + 0xBD700000BD8, + 0xBE600000BF0, + 0xC0000000C0D, + 0xC0E00000C11, + 0xC1200000C29, + 0xC2A00000C3A, + 0xC3C00000C45, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC5800000C5B, + 0xC5C00000C5E, + 0xC6000000C64, + 0xC6600000C70, + 0xC8000000C84, + 0xC8500000C8D, + 0xC8E00000C91, + 0xC9200000CA9, + 0xCAA00000CB4, + 0xCB500000CBA, + 0xCBC00000CC5, + 0xCC600000CC9, + 0xCCA00000CCE, + 0xCD500000CD7, + 0xCDC00000CDF, + 0xCE000000CE4, + 0xCE600000CF0, + 0xCF100000CF4, + 0xD0000000D0D, + 0xD0E00000D11, + 0xD1200000D45, + 0xD4600000D49, + 0xD4A00000D4F, + 0xD5400000D58, + 0xD5F00000D64, + 0xD6600000D70, + 0xD7A00000D80, + 0xD8100000D84, + 0xD8500000D97, + 0xD9A00000DB2, + 0xDB300000DBC, + 0xDBD00000DBE, + 0xDC000000DC7, + 0xDCA00000DCB, + 0xDCF00000DD5, + 0xDD600000DD7, + 0xDD800000DE0, + 0xDE600000DF0, + 0xDF200000DF4, + 0xE0100000E33, + 0xE3400000E3B, + 0xE4000000E4F, + 0xE5000000E5A, + 0xE8100000E83, + 0xE8400000E85, + 0xE8600000E8B, + 0xE8C00000EA4, + 0xEA500000EA6, + 0xEA700000EB3, + 0xEB400000EBE, + 0xEC000000EC5, + 0xEC600000EC7, + 0xEC800000ECF, + 0xED000000EDA, + 0xEDE00000EE0, + 0xF0000000F01, + 0xF0B00000F0C, + 0xF1800000F1A, + 0xF2000000F2A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF3E00000F43, + 0xF4400000F48, + 0xF4900000F4D, + 0xF4E00000F52, + 0xF5300000F57, + 0xF5800000F5C, + 0xF5D00000F69, + 0xF6A00000F6D, + 0xF7100000F73, + 0xF7400000F75, + 0xF7A00000F81, + 0xF8200000F85, + 0xF8600000F93, + 0xF9400000F98, + 0xF9900000F9D, + 0xF9E00000FA2, + 0xFA300000FA7, + 0xFA800000FAC, + 0xFAD00000FB9, + 0xFBA00000FBD, + 0xFC600000FC7, + 0x10000000104A, + 0x10500000109E, + 0x10D0000010FB, + 0x10FD00001100, + 0x120000001249, + 0x124A0000124E, + 0x125000001257, + 0x125800001259, + 0x125A0000125E, + 0x126000001289, + 0x128A0000128E, + 0x1290000012B1, + 0x12B2000012B6, + 0x12B8000012BF, + 0x12C0000012C1, + 0x12C2000012C6, + 0x12C8000012D7, + 0x12D800001311, + 0x131200001316, + 0x13180000135B, + 0x135D00001360, + 0x138000001390, + 0x13A0000013F6, + 0x14010000166D, + 0x166F00001680, + 0x16810000169B, + 0x16A0000016EB, + 0x16F1000016F9, + 0x170000001716, + 0x171F00001735, + 0x174000001754, + 0x17600000176D, + 0x176E00001771, + 0x177200001774, + 0x1780000017B4, + 0x17B6000017D4, + 0x17D7000017D8, + 0x17DC000017DE, + 0x17E0000017EA, + 0x18100000181A, + 0x182000001879, + 0x1880000018AB, + 0x18B0000018F6, + 0x19000000191F, + 0x19200000192C, + 0x19300000193C, + 0x19460000196E, + 0x197000001975, + 0x1980000019AC, + 0x19B0000019CA, + 0x19D0000019DA, + 0x1A0000001A1C, + 0x1A2000001A5F, + 0x1A6000001A7D, + 0x1A7F00001A8A, + 0x1A9000001A9A, + 0x1AA700001AA8, + 0x1AB000001ABE, + 0x1ABF00001AF1, + 0x1B0000001B4D, + 0x1B5000001B5A, + 0x1B6B00001B74, + 0x1B8000001BF4, + 0x1C0000001C38, + 0x1C4000001C4A, + 0x1C4D00001C7E, + 0x1C8A00001C8B, + 0x1CD000001CD3, + 0x1CD400001CFB, + 0x1D0000001D2C, + 0x1D2F00001D30, + 0x1D3B00001D3C, + 0x1D4E00001D4F, + 0x1D6B00001D78, + 0x1D7900001D9B, + 0x1DC000001E00, + 0x1E0100001E02, + 0x1E0300001E04, + 0x1E0500001E06, + 0x1E0700001E08, + 0x1E0900001E0A, + 0x1E0B00001E0C, + 0x1E0D00001E0E, + 0x1E0F00001E10, + 0x1E1100001E12, + 0x1E1300001E14, + 0x1E1500001E16, + 0x1E1700001E18, + 0x1E1900001E1A, + 0x1E1B00001E1C, + 0x1E1D00001E1E, + 0x1E1F00001E20, + 0x1E2100001E22, + 0x1E2300001E24, + 0x1E2500001E26, + 0x1E2700001E28, + 0x1E2900001E2A, + 0x1E2B00001E2C, + 0x1E2D00001E2E, + 0x1E2F00001E30, + 0x1E3100001E32, + 0x1E3300001E34, + 0x1E3500001E36, + 0x1E3700001E38, + 0x1E3900001E3A, + 0x1E3B00001E3C, + 0x1E3D00001E3E, + 0x1E3F00001E40, + 0x1E4100001E42, + 0x1E4300001E44, + 0x1E4500001E46, + 0x1E4700001E48, + 0x1E4900001E4A, + 0x1E4B00001E4C, + 0x1E4D00001E4E, + 0x1E4F00001E50, + 0x1E5100001E52, + 0x1E5300001E54, + 0x1E5500001E56, + 0x1E5700001E58, + 0x1E5900001E5A, + 0x1E5B00001E5C, + 0x1E5D00001E5E, + 0x1E5F00001E60, + 0x1E6100001E62, + 0x1E6300001E64, + 0x1E6500001E66, + 0x1E6700001E68, + 0x1E6900001E6A, + 0x1E6B00001E6C, + 0x1E6D00001E6E, + 0x1E6F00001E70, + 0x1E7100001E72, + 0x1E7300001E74, + 0x1E7500001E76, + 0x1E7700001E78, + 0x1E7900001E7A, + 0x1E7B00001E7C, + 0x1E7D00001E7E, + 0x1E7F00001E80, + 0x1E8100001E82, + 0x1E8300001E84, + 0x1E8500001E86, + 0x1E8700001E88, + 0x1E8900001E8A, + 0x1E8B00001E8C, + 0x1E8D00001E8E, + 0x1E8F00001E90, + 0x1E9100001E92, + 0x1E9300001E94, + 0x1E9500001E9A, + 0x1E9C00001E9E, + 0x1E9F00001EA0, + 0x1EA100001EA2, + 0x1EA300001EA4, + 0x1EA500001EA6, + 0x1EA700001EA8, + 0x1EA900001EAA, + 0x1EAB00001EAC, + 0x1EAD00001EAE, + 0x1EAF00001EB0, + 0x1EB100001EB2, + 0x1EB300001EB4, + 0x1EB500001EB6, + 0x1EB700001EB8, + 0x1EB900001EBA, + 0x1EBB00001EBC, + 0x1EBD00001EBE, + 0x1EBF00001EC0, + 0x1EC100001EC2, + 0x1EC300001EC4, + 0x1EC500001EC6, + 0x1EC700001EC8, + 0x1EC900001ECA, + 0x1ECB00001ECC, + 0x1ECD00001ECE, + 0x1ECF00001ED0, + 0x1ED100001ED2, + 0x1ED300001ED4, + 0x1ED500001ED6, + 0x1ED700001ED8, + 0x1ED900001EDA, + 0x1EDB00001EDC, + 0x1EDD00001EDE, + 0x1EDF00001EE0, + 0x1EE100001EE2, + 0x1EE300001EE4, + 0x1EE500001EE6, + 0x1EE700001EE8, + 0x1EE900001EEA, + 0x1EEB00001EEC, + 0x1EED00001EEE, + 0x1EEF00001EF0, + 0x1EF100001EF2, + 0x1EF300001EF4, + 0x1EF500001EF6, + 0x1EF700001EF8, + 0x1EF900001EFA, + 0x1EFB00001EFC, + 0x1EFD00001EFE, + 0x1EFF00001F08, + 0x1F1000001F16, + 0x1F2000001F28, + 0x1F3000001F38, + 0x1F4000001F46, + 0x1F5000001F58, + 0x1F6000001F68, + 0x1F7000001F71, + 0x1F7200001F73, + 0x1F7400001F75, + 0x1F7600001F77, + 0x1F7800001F79, + 0x1F7A00001F7B, + 0x1F7C00001F7D, + 0x1FB000001FB2, + 0x1FB600001FB7, + 0x1FC600001FC7, + 0x1FD000001FD3, + 0x1FD600001FD8, + 0x1FE000001FE3, + 0x1FE400001FE8, + 0x1FF600001FF7, + 0x208F00002090, + 0x214E0000214F, + 0x218400002185, + 0x2C3000002C60, + 0x2C6100002C62, + 0x2C6500002C67, + 0x2C6800002C69, + 0x2C6A00002C6B, + 0x2C6C00002C6D, + 0x2C7100002C72, + 0x2C7300002C75, + 0x2C7600002C7C, + 0x2C8100002C82, + 0x2C8300002C84, + 0x2C8500002C86, + 0x2C8700002C88, + 0x2C8900002C8A, + 0x2C8B00002C8C, + 0x2C8D00002C8E, + 0x2C8F00002C90, + 0x2C9100002C92, + 0x2C9300002C94, + 0x2C9500002C96, + 0x2C9700002C98, + 0x2C9900002C9A, + 0x2C9B00002C9C, + 0x2C9D00002C9E, + 0x2C9F00002CA0, + 0x2CA100002CA2, + 0x2CA300002CA4, + 0x2CA500002CA6, + 0x2CA700002CA8, + 0x2CA900002CAA, + 0x2CAB00002CAC, + 0x2CAD00002CAE, + 0x2CAF00002CB0, + 0x2CB100002CB2, + 0x2CB300002CB4, + 0x2CB500002CB6, + 0x2CB700002CB8, + 0x2CB900002CBA, + 0x2CBB00002CBC, + 0x2CBD00002CBE, + 0x2CBF00002CC0, + 0x2CC100002CC2, + 0x2CC300002CC4, + 0x2CC500002CC6, + 0x2CC700002CC8, + 0x2CC900002CCA, + 0x2CCB00002CCC, + 0x2CCD00002CCE, + 0x2CCF00002CD0, + 0x2CD100002CD2, + 0x2CD300002CD4, + 0x2CD500002CD6, + 0x2CD700002CD8, + 0x2CD900002CDA, + 0x2CDB00002CDC, + 0x2CDD00002CDE, + 0x2CDF00002CE0, + 0x2CE100002CE2, + 0x2CE300002CE5, + 0x2CEC00002CED, + 0x2CEE00002CF2, + 0x2CF300002CF4, + 0x2D0000002D26, + 0x2D2700002D28, + 0x2D2D00002D2E, + 0x2D3000002D68, + 0x2D7F00002D97, + 0x2DA000002DA7, + 0x2DA800002DAF, + 0x2DB000002DB7, + 0x2DB800002DBF, + 0x2DC000002DC7, + 0x2DC800002DCF, + 0x2DD000002DD7, + 0x2DD800002DDF, + 0x2DE000002E00, + 0x2E2F00002E30, + 0x300500003008, + 0x302A0000302E, + 0x303C0000303D, + 0x304100003097, + 0x30990000309B, + 0x309D0000309F, + 0x30A1000030FB, + 0x30FC000030FF, + 0x310500003130, + 0x31A0000031C0, + 0x31F000003200, + 0x340000004DC0, + 0x4E000000A48D, + 0xA4D00000A4FE, + 0xA5000000A60D, + 0xA6100000A62C, + 0xA6410000A642, + 0xA6430000A644, + 0xA6450000A646, + 0xA6470000A648, + 0xA6490000A64A, + 0xA64B0000A64C, + 0xA64D0000A64E, + 0xA64F0000A650, + 0xA6510000A652, + 0xA6530000A654, + 0xA6550000A656, + 0xA6570000A658, + 0xA6590000A65A, + 0xA65B0000A65C, + 0xA65D0000A65E, + 0xA65F0000A660, + 0xA6610000A662, + 0xA6630000A664, + 0xA6650000A666, + 0xA6670000A668, + 0xA6690000A66A, + 0xA66B0000A66C, + 0xA66D0000A670, + 0xA6740000A67E, + 0xA67F0000A680, + 0xA6810000A682, + 0xA6830000A684, + 0xA6850000A686, + 0xA6870000A688, + 0xA6890000A68A, + 0xA68B0000A68C, + 0xA68D0000A68E, + 0xA68F0000A690, + 0xA6910000A692, + 0xA6930000A694, + 0xA6950000A696, + 0xA6970000A698, + 0xA6990000A69A, + 0xA69B0000A69C, + 0xA69E0000A6E6, + 0xA6F00000A6F2, + 0xA7170000A720, + 0xA7230000A724, + 0xA7250000A726, + 0xA7270000A728, + 0xA7290000A72A, + 0xA72B0000A72C, + 0xA72D0000A72E, + 0xA72F0000A732, + 0xA7330000A734, + 0xA7350000A736, + 0xA7370000A738, + 0xA7390000A73A, + 0xA73B0000A73C, + 0xA73D0000A73E, + 0xA73F0000A740, + 0xA7410000A742, + 0xA7430000A744, + 0xA7450000A746, + 0xA7470000A748, + 0xA7490000A74A, + 0xA74B0000A74C, + 0xA74D0000A74E, + 0xA74F0000A750, + 0xA7510000A752, + 0xA7530000A754, + 0xA7550000A756, + 0xA7570000A758, + 0xA7590000A75A, + 0xA75B0000A75C, + 0xA75D0000A75E, + 0xA75F0000A760, + 0xA7610000A762, + 0xA7630000A764, + 0xA7650000A766, + 0xA7670000A768, + 0xA7690000A76A, + 0xA76B0000A76C, + 0xA76D0000A76E, + 0xA76F0000A770, + 0xA7710000A779, + 0xA77A0000A77B, + 0xA77C0000A77D, + 0xA77F0000A780, + 0xA7810000A782, + 0xA7830000A784, + 0xA7850000A786, + 0xA7870000A789, + 0xA78C0000A78D, + 0xA78E0000A790, + 0xA7910000A792, + 0xA7930000A796, + 0xA7970000A798, + 0xA7990000A79A, + 0xA79B0000A79C, + 0xA79D0000A79E, + 0xA79F0000A7A0, + 0xA7A10000A7A2, + 0xA7A30000A7A4, + 0xA7A50000A7A6, + 0xA7A70000A7A8, + 0xA7A90000A7AA, + 0xA7AF0000A7B0, + 0xA7B50000A7B6, + 0xA7B70000A7B8, + 0xA7B90000A7BA, + 0xA7BB0000A7BC, + 0xA7BD0000A7BE, + 0xA7BF0000A7C0, + 0xA7C10000A7C2, + 0xA7C30000A7C4, + 0xA7C80000A7C9, + 0xA7CA0000A7CB, + 0xA7CD0000A7CE, + 0xA7CF0000A7D0, + 0xA7D10000A7D2, + 0xA7D30000A7D4, + 0xA7D50000A7D6, + 0xA7D70000A7D8, + 0xA7D90000A7DA, + 0xA7DB0000A7DC, + 0xA7F60000A7F8, + 0xA7FA0000A828, + 0xA82C0000A82D, + 0xA8400000A874, + 0xA8800000A8C6, + 0xA8D00000A8DA, + 0xA8E00000A8F8, + 0xA8FB0000A8FC, + 0xA8FD0000A92E, + 0xA9300000A954, + 0xA9800000A9C1, + 0xA9CF0000A9DA, + 0xA9E00000A9FF, + 0xAA000000AA37, + 0xAA400000AA4E, + 0xAA500000AA5A, + 0xAA600000AA77, + 0xAA7A0000AAC3, + 0xAADB0000AADE, + 0xAAE00000AAF0, + 0xAAF20000AAF7, + 0xAB010000AB07, + 0xAB090000AB0F, + 0xAB110000AB17, + 0xAB200000AB27, + 0xAB280000AB2F, + 0xAB300000AB5B, + 0xAB600000AB69, + 0xABC00000ABEB, + 0xABEC0000ABEE, + 0xABF00000ABFA, + 0xAC000000D7A4, + 0xFA0E0000FA10, + 0xFA110000FA12, + 0xFA130000FA15, + 0xFA1F0000FA20, + 0xFA210000FA22, + 0xFA230000FA25, + 0xFA270000FA2A, + 0xFB1E0000FB1F, + 0xFE200000FE30, + 0xFE730000FE74, + 0x100000001000C, + 0x1000D00010027, + 0x100280001003B, + 0x1003C0001003E, + 0x1003F0001004E, + 0x100500001005E, + 0x10080000100FB, + 0x101FD000101FE, + 0x102800001029D, + 0x102A0000102D1, + 0x102E0000102E1, + 0x1030000010320, + 0x1032D00010341, + 0x103420001034A, + 0x103500001037B, + 0x103800001039E, + 0x103A0000103C4, + 0x103C8000103D0, + 0x104280001049E, + 0x104A0000104AA, + 0x104D8000104FC, + 0x1050000010528, + 0x1053000010564, + 0x10597000105A2, + 0x105A3000105B2, + 0x105B3000105BA, + 0x105BB000105BD, + 0x105C0000105F4, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080A00010836, + 0x1083700010839, + 0x1083C0001083D, + 0x1083F00010856, + 0x1086000010877, + 0x108800001089F, + 0x108E0000108F3, + 0x108F4000108F6, + 0x1090000010916, + 0x109200001093A, + 0x109400001095A, + 0x10980000109B8, + 0x109BE000109C0, + 0x10A0000010A04, + 0x10A0500010A07, + 0x10A0C00010A14, + 0x10A1500010A18, + 0x10A1900010A36, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10A6000010A7D, + 0x10A8000010A9D, + 0x10AC000010AC8, + 0x10AC900010AE7, + 0x10B0000010B36, + 0x10B4000010B56, + 0x10B6000010B73, + 0x10B8000010B92, + 0x10C0000010C49, + 0x10CC000010CF3, + 0x10D0000010D28, + 0x10D3000010D3A, + 0x10D4000010D50, + 0x10D6900010D6E, + 0x10D6F00010D86, + 0x10E8000010EAA, + 0x10EAB00010EAD, + 0x10EB000010EB2, + 0x10EC200010EC8, + 0x10ECB00010ED0, + 0x10ED900010EEF, + 0x10EF000010F1D, + 0x10F2700010F28, + 0x10F3000010F51, + 0x10F7000010F86, + 0x10FB000010FC5, + 0x10FE000010FF7, + 0x1100000011047, + 0x1106600011076, + 0x1107F000110BB, + 0x110C2000110C3, + 0x110D0000110E9, + 0x110F0000110FA, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111C5, + 0x111C9000111CD, + 0x111CE000111DB, + 0x111DC000111DD, + 0x1120000011212, + 0x1121300011238, + 0x1123E00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128A0001128E, + 0x1128F0001129E, + 0x1129F000112A9, + 0x112B0000112EB, + 0x112F0000112FA, + 0x1130000011304, + 0x113050001130D, + 0x1130F00011311, + 0x1131300011329, + 0x1132A00011331, + 0x1133200011334, + 0x113350001133A, + 0x1133B00011345, + 0x1134700011349, + 0x1134B0001134E, + 0x1135000011351, + 0x1135700011358, + 0x1135D00011364, + 0x113660001136D, + 0x1137000011375, + 0x113800001138A, + 0x1138B0001138C, + 0x1138E0001138F, + 0x11390000113B6, + 0x113B7000113C1, + 0x113C2000113C3, + 0x113C5000113C6, + 0x113C7000113CB, + 0x113CC000113D4, + 0x113E1000113E3, + 0x114000001144B, + 0x114500001145A, + 0x1145E00011462, + 0x11480000114C6, + 0x114C7000114C8, + 0x114D0000114DA, + 0x11580000115B6, + 0x115B8000115C1, + 0x115D8000115DE, + 0x1160000011641, + 0x1164400011645, + 0x116500001165A, + 0x11680000116B9, + 0x116C0000116CA, + 0x116D0000116E4, + 0x117000001171B, + 0x1171D0001172C, + 0x117300001173A, + 0x1174000011747, + 0x118000001183B, + 0x118C0000118EA, + 0x118FF00011907, + 0x119090001190A, + 0x1190C00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193B00011944, + 0x119500001195A, + 0x119A0000119A8, + 0x119AA000119D8, + 0x119DA000119E2, + 0x119E3000119E5, + 0x11A0000011A3F, + 0x11A4700011A48, + 0x11A5000011A9A, + 0x11A9D00011A9E, + 0x11AB000011AF9, + 0x11B0A00011B0B, + 0x11B6000011B68, + 0x11BC000011BE1, + 0x11BF000011BFA, + 0x11C0000011C09, + 0x11C0A00011C37, + 0x11C3800011C41, + 0x11C5000011C5A, + 0x11C7200011C90, + 0x11C9200011CA8, + 0x11CA900011CB7, + 0x11D0000011D07, + 0x11D0800011D0A, + 0x11D0B00011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D48, + 0x11D5000011D5A, + 0x11D6000011D66, + 0x11D6700011D69, + 0x11D6A00011D8F, + 0x11D9000011D92, + 0x11D9300011D99, + 0x11DA000011DAA, + 0x11DB000011DDC, + 0x11DE000011DEA, + 0x11DF000011DF2, + 0x11EE000011EF7, + 0x11F0000011F11, + 0x11F1200011F3B, + 0x11F3E00011F43, + 0x11F5000011F5B, + 0x11FB000011FB1, + 0x120000001239A, + 0x1248000012544, + 0x12F9000012FF1, + 0x1300000013430, + 0x1344000013456, + 0x13460000143FB, + 0x1440000014647, + 0x161000001613A, + 0x1680000016A39, + 0x16A4000016A5F, + 0x16A6000016A6A, + 0x16A7000016ABF, + 0x16AC000016ACA, + 0x16AD000016AEE, + 0x16AF000016AF5, + 0x16B0000016B37, + 0x16B4000016B44, + 0x16B5000016B5A, + 0x16B6300016B78, + 0x16B7D00016B90, + 0x16D4000016D6D, + 0x16D7000016D7A, + 0x16E6000016E80, + 0x16EBB00016ED4, + 0x16F0000016F4B, + 0x16F4F00016F88, + 0x16F8F00016FA0, + 0x16FE000016FE2, + 0x16FE300016FE5, + 0x16FF000016FF4, + 0x1700000018CDB, + 0x18CFF00018D21, + 0x18D8000018DF3, + 0x18E0000019192, + 0x191A0000191D3, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B123, + 0x1B1270001B129, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1B1550001B156, + 0x1B1640001B169, + 0x1B1700001B2FC, + 0x1BC000001BC6B, + 0x1BC700001BC7D, + 0x1BC800001BC89, + 0x1BC900001BC9A, + 0x1BC9D0001BC9F, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1D2500001D253, + 0x1D25B0001D25D, + 0x1D25F0001D260, + 0x1D2800001D282, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1DF000001DF40, + 0x1DF410001DF48, + 0x1DF490001DF4A, + 0x1DF4B0001DF4D, + 0x1DF4E0001DF51, + 0x1DF520001DF68, + 0x1DF690001DF6A, + 0x1DF6B0001DF6C, + 0x1DF6D0001DF6E, + 0x1DF6F0001DF72, + 0x1DF730001DF74, + 0x1DF750001DF76, + 0x1DF770001DF78, + 0x1DF790001DF7A, + 0x1DF7B0001DF7C, + 0x1DF7D0001DF7E, + 0x1DF7F0001DF82, + 0x1DF900001DF95, + 0x1DF960001DF97, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1000001E12D, + 0x1E1300001E13E, + 0x1E1400001E14A, + 0x1E14E0001E14F, + 0x1E2900001E2AF, + 0x1E2C00001E2FA, + 0x1E4D00001E4FA, + 0x1E5D00001E5FB, + 0x1E6C00001E6DF, + 0x1E6E00001E6F6, + 0x1E6FE0001E700, + 0x1E7E00001E7E7, + 0x1E7E80001E7EC, + 0x1E7ED0001E7EF, + 0x1E7F00001E7FF, + 0x1E8000001E8C5, + 0x1E8D00001E8D7, + 0x1E9220001E94C, + 0x1E9500001E95A, + 0x200000002A6E0, + 0x2A7000002B81F, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x300000003134B, + 0x313500003347A, + 0x3D0000003FC40, + ), + "CONTEXTJ": (0x200C0000200E,), + "CONTEXTO": ( + 0xB7000000B8, + 0x37500000376, + 0x5F3000005F5, + 0x6600000066A, + 0x6F0000006FA, + 0x30FB000030FC, + ), +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py new file mode 100644 index 0000000..19d7781 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/intranges.py @@ -0,0 +1,55 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + + +def intranges_from_list(list_: list[int]) -> tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i + 1 < len(sorted_list) and sorted_list[i] == sorted_list[i + 1] - 1: + continue + current_range = sorted_list[last_write + 1 : i + 1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + + +def _decode_range(r: int) -> tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos - 1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py new file mode 100644 index 0000000..2f9ff7e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/package_data.py @@ -0,0 +1 @@ +__version__ = "3.20" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py new file mode 100644 index 0000000..dbfb60f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/idna/uts46data.py @@ -0,0 +1,17117 @@ +# This file is automatically generated by tools/idna-data + +from __future__ import annotations + +from array import array + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "18.0.0" + +uts46_starts: array[int] = array( + "I", + ( + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + 0x8, + 0x9, + 0xA, + 0xB, + 0xC, + 0xD, + 0xE, + 0xF, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2A, + 0x2B, + 0x2C, + 0x2D, + 0x2E, + 0x2F, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + 0x40, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4A, + 0x4B, + 0x4C, + 0x4D, + 0x4E, + 0x4F, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5A, + 0x5B, + 0x5C, + 0x5D, + 0x5E, + 0x5F, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6A, + 0x6B, + 0x6C, + 0x6D, + 0x6E, + 0x6F, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7A, + 0x7B, + 0x7C, + 0x7D, + 0x7E, + 0x7F, + 0x80, + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x86, + 0x87, + 0x88, + 0x89, + 0x8A, + 0x8B, + 0x8C, + 0x8D, + 0x8E, + 0x8F, + 0x90, + 0x91, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, + 0x9A, + 0x9B, + 0x9C, + 0x9D, + 0x9E, + 0x9F, + 0xA0, + 0xA1, + 0xA2, + 0xA3, + 0xA4, + 0xA5, + 0xA6, + 0xA7, + 0xA8, + 0xA9, + 0xAA, + 0xAB, + 0xAC, + 0xAD, + 0xAE, + 0xAF, + 0xB0, + 0xB1, + 0xB2, + 0xB3, + 0xB4, + 0xB5, + 0xB6, + 0xB7, + 0xB8, + 0xB9, + 0xBA, + 0xBB, + 0xBC, + 0xBD, + 0xBE, + 0xBF, + 0xC0, + 0xC1, + 0xC2, + 0xC3, + 0xC4, + 0xC5, + 0xC6, + 0xC7, + 0xC8, + 0xC9, + 0xCA, + 0xCB, + 0xCC, + 0xCD, + 0xCE, + 0xCF, + 0xD0, + 0xD1, + 0xD2, + 0xD3, + 0xD4, + 0xD5, + 0xD6, + 0xD7, + 0xD8, + 0xD9, + 0xDA, + 0xDB, + 0xDC, + 0xDD, + 0xDE, + 0xDF, + 0xE0, + 0xE1, + 0xE2, + 0xE3, + 0xE4, + 0xE5, + 0xE6, + 0xE7, + 0xE8, + 0xE9, + 0xEA, + 0xEB, + 0xEC, + 0xED, + 0xEE, + 0xEF, + 0xF0, + 0xF1, + 0xF2, + 0xF3, + 0xF4, + 0xF5, + 0xF6, + 0xF7, + 0xF8, + 0xF9, + 0xFA, + 0xFB, + 0xFC, + 0xFD, + 0xFE, + 0xFF, + 0x100, + 0x101, + 0x102, + 0x103, + 0x104, + 0x105, + 0x106, + 0x107, + 0x108, + 0x109, + 0x10A, + 0x10B, + 0x10C, + 0x10D, + 0x10E, + 0x10F, + 0x110, + 0x111, + 0x112, + 0x113, + 0x114, + 0x115, + 0x116, + 0x117, + 0x118, + 0x119, + 0x11A, + 0x11B, + 0x11C, + 0x11D, + 0x11E, + 0x11F, + 0x120, + 0x121, + 0x122, + 0x123, + 0x124, + 0x125, + 0x126, + 0x127, + 0x128, + 0x129, + 0x12A, + 0x12B, + 0x12C, + 0x12D, + 0x12E, + 0x12F, + 0x130, + 0x131, + 0x132, + 0x134, + 0x135, + 0x136, + 0x137, + 0x139, + 0x13A, + 0x13B, + 0x13C, + 0x13D, + 0x13E, + 0x13F, + 0x141, + 0x142, + 0x143, + 0x144, + 0x145, + 0x146, + 0x147, + 0x148, + 0x149, + 0x14A, + 0x14B, + 0x14C, + 0x14D, + 0x14E, + 0x14F, + 0x150, + 0x151, + 0x152, + 0x153, + 0x154, + 0x155, + 0x156, + 0x157, + 0x158, + 0x159, + 0x15A, + 0x15B, + 0x15C, + 0x15D, + 0x15E, + 0x15F, + 0x160, + 0x161, + 0x162, + 0x163, + 0x164, + 0x165, + 0x166, + 0x167, + 0x168, + 0x169, + 0x16A, + 0x16B, + 0x16C, + 0x16D, + 0x16E, + 0x16F, + 0x170, + 0x171, + 0x172, + 0x173, + 0x174, + 0x175, + 0x176, + 0x177, + 0x178, + 0x179, + 0x17A, + 0x17B, + 0x17C, + 0x17D, + 0x17E, + 0x17F, + 0x180, + 0x181, + 0x182, + 0x183, + 0x184, + 0x185, + 0x186, + 0x187, + 0x188, + 0x189, + 0x18A, + 0x18B, + 0x18C, + 0x18E, + 0x18F, + 0x190, + 0x191, + 0x192, + 0x193, + 0x194, + 0x195, + 0x196, + 0x197, + 0x198, + 0x199, + 0x19C, + 0x19D, + 0x19E, + 0x19F, + 0x1A0, + 0x1A1, + 0x1A2, + 0x1A3, + 0x1A4, + 0x1A5, + 0x1A6, + 0x1A7, + 0x1A8, + 0x1A9, + 0x1AA, + 0x1AC, + 0x1AD, + 0x1AE, + 0x1AF, + 0x1B0, + 0x1B1, + 0x1B2, + 0x1B3, + 0x1B4, + 0x1B5, + 0x1B6, + 0x1B7, + 0x1B8, + 0x1B9, + 0x1BC, + 0x1BD, + 0x1C4, + 0x1C7, + 0x1CA, + 0x1CD, + 0x1CE, + 0x1CF, + 0x1D0, + 0x1D1, + 0x1D2, + 0x1D3, + 0x1D4, + 0x1D5, + 0x1D6, + 0x1D7, + 0x1D8, + 0x1D9, + 0x1DA, + 0x1DB, + 0x1DC, + 0x1DE, + 0x1DF, + 0x1E0, + 0x1E1, + 0x1E2, + 0x1E3, + 0x1E4, + 0x1E5, + 0x1E6, + 0x1E7, + 0x1E8, + 0x1E9, + 0x1EA, + 0x1EB, + 0x1EC, + 0x1ED, + 0x1EE, + 0x1EF, + 0x1F1, + 0x1F4, + 0x1F5, + 0x1F6, + 0x1F7, + 0x1F8, + 0x1F9, + 0x1FA, + 0x1FB, + 0x1FC, + 0x1FD, + 0x1FE, + 0x1FF, + 0x200, + 0x201, + 0x202, + 0x203, + 0x204, + 0x205, + 0x206, + 0x207, + 0x208, + 0x209, + 0x20A, + 0x20B, + 0x20C, + 0x20D, + 0x20E, + 0x20F, + 0x210, + 0x211, + 0x212, + 0x213, + 0x214, + 0x215, + 0x216, + 0x217, + 0x218, + 0x219, + 0x21A, + 0x21B, + 0x21C, + 0x21D, + 0x21E, + 0x21F, + 0x220, + 0x221, + 0x222, + 0x223, + 0x224, + 0x225, + 0x226, + 0x227, + 0x228, + 0x229, + 0x22A, + 0x22B, + 0x22C, + 0x22D, + 0x22E, + 0x22F, + 0x230, + 0x231, + 0x232, + 0x233, + 0x23A, + 0x23B, + 0x23C, + 0x23D, + 0x23E, + 0x23F, + 0x241, + 0x242, + 0x243, + 0x244, + 0x245, + 0x246, + 0x247, + 0x248, + 0x249, + 0x24A, + 0x24B, + 0x24C, + 0x24D, + 0x24E, + 0x24F, + 0x2B0, + 0x2B1, + 0x2B2, + 0x2B3, + 0x2B4, + 0x2B5, + 0x2B6, + 0x2B7, + 0x2B8, + 0x2B9, + 0x2D8, + 0x2D9, + 0x2DA, + 0x2DB, + 0x2DC, + 0x2DD, + 0x2DE, + 0x2E0, + 0x2E1, + 0x2E2, + 0x2E3, + 0x2E4, + 0x2E5, + 0x340, + 0x341, + 0x342, + 0x343, + 0x344, + 0x345, + 0x346, + 0x34F, + 0x350, + 0x370, + 0x371, + 0x372, + 0x373, + 0x374, + 0x375, + 0x376, + 0x377, + 0x378, + 0x37A, + 0x37B, + 0x37E, + 0x37F, + 0x380, + 0x384, + 0x385, + 0x386, + 0x387, + 0x388, + 0x389, + 0x38A, + 0x38B, + 0x38C, + 0x38D, + 0x38E, + 0x38F, + 0x390, + 0x391, + 0x392, + 0x393, + 0x394, + 0x395, + 0x396, + 0x397, + 0x398, + 0x399, + 0x39A, + 0x39B, + 0x39C, + 0x39D, + 0x39E, + 0x39F, + 0x3A0, + 0x3A1, + 0x3A2, + 0x3A3, + 0x3A4, + 0x3A5, + 0x3A6, + 0x3A7, + 0x3A8, + 0x3A9, + 0x3AA, + 0x3AB, + 0x3AC, + 0x3C2, + 0x3C3, + 0x3CF, + 0x3D0, + 0x3D1, + 0x3D2, + 0x3D3, + 0x3D4, + 0x3D5, + 0x3D6, + 0x3D7, + 0x3D8, + 0x3D9, + 0x3DA, + 0x3DB, + 0x3DC, + 0x3DD, + 0x3DE, + 0x3DF, + 0x3E0, + 0x3E1, + 0x3E2, + 0x3E3, + 0x3E4, + 0x3E5, + 0x3E6, + 0x3E7, + 0x3E8, + 0x3E9, + 0x3EA, + 0x3EB, + 0x3EC, + 0x3ED, + 0x3EE, + 0x3EF, + 0x3F0, + 0x3F1, + 0x3F2, + 0x3F3, + 0x3F4, + 0x3F5, + 0x3F6, + 0x3F7, + 0x3F8, + 0x3F9, + 0x3FA, + 0x3FB, + 0x3FD, + 0x3FE, + 0x3FF, + 0x400, + 0x401, + 0x402, + 0x403, + 0x404, + 0x405, + 0x406, + 0x407, + 0x408, + 0x409, + 0x40A, + 0x40B, + 0x40C, + 0x40D, + 0x40E, + 0x40F, + 0x410, + 0x411, + 0x412, + 0x413, + 0x414, + 0x415, + 0x416, + 0x417, + 0x418, + 0x419, + 0x41A, + 0x41B, + 0x41C, + 0x41D, + 0x41E, + 0x41F, + 0x420, + 0x421, + 0x422, + 0x423, + 0x424, + 0x425, + 0x426, + 0x427, + 0x428, + 0x429, + 0x42A, + 0x42B, + 0x42C, + 0x42D, + 0x42E, + 0x42F, + 0x430, + 0x460, + 0x461, + 0x462, + 0x463, + 0x464, + 0x465, + 0x466, + 0x467, + 0x468, + 0x469, + 0x46A, + 0x46B, + 0x46C, + 0x46D, + 0x46E, + 0x46F, + 0x470, + 0x471, + 0x472, + 0x473, + 0x474, + 0x475, + 0x476, + 0x477, + 0x478, + 0x479, + 0x47A, + 0x47B, + 0x47C, + 0x47D, + 0x47E, + 0x47F, + 0x480, + 0x481, + 0x48A, + 0x48B, + 0x48C, + 0x48D, + 0x48E, + 0x48F, + 0x490, + 0x491, + 0x492, + 0x493, + 0x494, + 0x495, + 0x496, + 0x497, + 0x498, + 0x499, + 0x49A, + 0x49B, + 0x49C, + 0x49D, + 0x49E, + 0x49F, + 0x4A0, + 0x4A1, + 0x4A2, + 0x4A3, + 0x4A4, + 0x4A5, + 0x4A6, + 0x4A7, + 0x4A8, + 0x4A9, + 0x4AA, + 0x4AB, + 0x4AC, + 0x4AD, + 0x4AE, + 0x4AF, + 0x4B0, + 0x4B1, + 0x4B2, + 0x4B3, + 0x4B4, + 0x4B5, + 0x4B6, + 0x4B7, + 0x4B8, + 0x4B9, + 0x4BA, + 0x4BB, + 0x4BC, + 0x4BD, + 0x4BE, + 0x4BF, + 0x4C0, + 0x4C1, + 0x4C2, + 0x4C3, + 0x4C4, + 0x4C5, + 0x4C6, + 0x4C7, + 0x4C8, + 0x4C9, + 0x4CA, + 0x4CB, + 0x4CC, + 0x4CD, + 0x4CE, + 0x4D0, + 0x4D1, + 0x4D2, + 0x4D3, + 0x4D4, + 0x4D5, + 0x4D6, + 0x4D7, + 0x4D8, + 0x4D9, + 0x4DA, + 0x4DB, + 0x4DC, + 0x4DD, + 0x4DE, + 0x4DF, + 0x4E0, + 0x4E1, + 0x4E2, + 0x4E3, + 0x4E4, + 0x4E5, + 0x4E6, + 0x4E7, + 0x4E8, + 0x4E9, + 0x4EA, + 0x4EB, + 0x4EC, + 0x4ED, + 0x4EE, + 0x4EF, + 0x4F0, + 0x4F1, + 0x4F2, + 0x4F3, + 0x4F4, + 0x4F5, + 0x4F6, + 0x4F7, + 0x4F8, + 0x4F9, + 0x4FA, + 0x4FB, + 0x4FC, + 0x4FD, + 0x4FE, + 0x4FF, + 0x500, + 0x501, + 0x502, + 0x503, + 0x504, + 0x505, + 0x506, + 0x507, + 0x508, + 0x509, + 0x50A, + 0x50B, + 0x50C, + 0x50D, + 0x50E, + 0x50F, + 0x510, + 0x511, + 0x512, + 0x513, + 0x514, + 0x515, + 0x516, + 0x517, + 0x518, + 0x519, + 0x51A, + 0x51B, + 0x51C, + 0x51D, + 0x51E, + 0x51F, + 0x520, + 0x521, + 0x522, + 0x523, + 0x524, + 0x525, + 0x526, + 0x527, + 0x528, + 0x529, + 0x52A, + 0x52B, + 0x52C, + 0x52D, + 0x52E, + 0x52F, + 0x530, + 0x531, + 0x532, + 0x533, + 0x534, + 0x535, + 0x536, + 0x537, + 0x538, + 0x539, + 0x53A, + 0x53B, + 0x53C, + 0x53D, + 0x53E, + 0x53F, + 0x540, + 0x541, + 0x542, + 0x543, + 0x544, + 0x545, + 0x546, + 0x547, + 0x548, + 0x549, + 0x54A, + 0x54B, + 0x54C, + 0x54D, + 0x54E, + 0x54F, + 0x550, + 0x551, + 0x552, + 0x553, + 0x554, + 0x555, + 0x556, + 0x557, + 0x558, + 0x559, + 0x587, + 0x588, + 0x58B, + 0x58C, + 0x58D, + 0x590, + 0x591, + 0x5CA, + 0x5D0, + 0x5EB, + 0x5EF, + 0x5F5, + 0x606, + 0x61C, + 0x61D, + 0x675, + 0x676, + 0x677, + 0x678, + 0x679, + 0x6DD, + 0x6DE, + 0x70E, + 0x710, + 0x74B, + 0x74D, + 0x7B2, + 0x7C0, + 0x7FB, + 0x7FD, + 0x82E, + 0x830, + 0x83F, + 0x840, + 0x85C, + 0x85E, + 0x85F, + 0x860, + 0x86B, + 0x870, + 0x890, + 0x897, + 0x8E2, + 0x8E3, + 0x958, + 0x959, + 0x95A, + 0x95B, + 0x95C, + 0x95D, + 0x95E, + 0x95F, + 0x960, + 0x984, + 0x985, + 0x98D, + 0x98F, + 0x991, + 0x993, + 0x9A9, + 0x9AA, + 0x9B1, + 0x9B2, + 0x9B3, + 0x9B6, + 0x9BA, + 0x9BC, + 0x9C5, + 0x9C7, + 0x9C9, + 0x9CB, + 0x9CF, + 0x9D7, + 0x9D8, + 0x9DC, + 0x9DD, + 0x9DE, + 0x9DF, + 0x9E0, + 0x9E4, + 0x9E6, + 0x9FF, + 0xA01, + 0xA04, + 0xA05, + 0xA0B, + 0xA0F, + 0xA11, + 0xA13, + 0xA29, + 0xA2A, + 0xA31, + 0xA32, + 0xA33, + 0xA34, + 0xA35, + 0xA36, + 0xA37, + 0xA38, + 0xA3A, + 0xA3C, + 0xA3D, + 0xA3E, + 0xA43, + 0xA47, + 0xA49, + 0xA4B, + 0xA4E, + 0xA51, + 0xA52, + 0xA59, + 0xA5A, + 0xA5B, + 0xA5C, + 0xA5D, + 0xA5E, + 0xA5F, + 0xA66, + 0xA77, + 0xA81, + 0xA84, + 0xA85, + 0xA8E, + 0xA8F, + 0xA92, + 0xA93, + 0xAA9, + 0xAAA, + 0xAB1, + 0xAB2, + 0xAB4, + 0xAB5, + 0xABA, + 0xABC, + 0xAC6, + 0xAC7, + 0xACA, + 0xACB, + 0xACE, + 0xAD0, + 0xAD1, + 0xAE0, + 0xAE4, + 0xAE6, + 0xAF2, + 0xAF9, + 0xB00, + 0xB01, + 0xB04, + 0xB05, + 0xB0D, + 0xB0F, + 0xB11, + 0xB13, + 0xB29, + 0xB2A, + 0xB31, + 0xB32, + 0xB34, + 0xB35, + 0xB3A, + 0xB3C, + 0xB45, + 0xB47, + 0xB49, + 0xB4B, + 0xB4E, + 0xB53, + 0xB58, + 0xB5C, + 0xB5D, + 0xB5E, + 0xB5F, + 0xB64, + 0xB66, + 0xB78, + 0xB82, + 0xB84, + 0xB85, + 0xB8B, + 0xB8E, + 0xB91, + 0xB92, + 0xB96, + 0xB99, + 0xB9B, + 0xB9C, + 0xB9D, + 0xB9E, + 0xBA0, + 0xBA3, + 0xBA5, + 0xBA8, + 0xBAB, + 0xBAE, + 0xBBA, + 0xBBE, + 0xBC3, + 0xBC6, + 0xBC9, + 0xBCA, + 0xBCE, + 0xBD0, + 0xBD1, + 0xBD7, + 0xBD8, + 0xBE6, + 0xBFB, + 0xC00, + 0xC0D, + 0xC0E, + 0xC11, + 0xC12, + 0xC29, + 0xC2A, + 0xC3A, + 0xC3C, + 0xC45, + 0xC46, + 0xC49, + 0xC4A, + 0xC4E, + 0xC55, + 0xC57, + 0xC58, + 0xC5B, + 0xC5C, + 0xC5E, + 0xC60, + 0xC64, + 0xC66, + 0xC70, + 0xC77, + 0xC8D, + 0xC8E, + 0xC91, + 0xC92, + 0xCA9, + 0xCAA, + 0xCB4, + 0xCB5, + 0xCBA, + 0xCBC, + 0xCC5, + 0xCC6, + 0xCC9, + 0xCCA, + 0xCCE, + 0xCD5, + 0xCD7, + 0xCDC, + 0xCDF, + 0xCE0, + 0xCE4, + 0xCE6, + 0xCF0, + 0xCF1, + 0xCF4, + 0xD00, + 0xD0D, + 0xD0E, + 0xD11, + 0xD12, + 0xD45, + 0xD46, + 0xD49, + 0xD4A, + 0xD50, + 0xD54, + 0xD64, + 0xD66, + 0xD80, + 0xD81, + 0xD84, + 0xD85, + 0xD97, + 0xD9A, + 0xDB2, + 0xDB3, + 0xDBC, + 0xDBD, + 0xDBE, + 0xDC0, + 0xDC7, + 0xDCA, + 0xDCB, + 0xDCF, + 0xDD5, + 0xDD6, + 0xDD7, + 0xDD8, + 0xDE0, + 0xDE6, + 0xDF0, + 0xDF2, + 0xDF5, + 0xE01, + 0xE33, + 0xE34, + 0xE3B, + 0xE3F, + 0xE5C, + 0xE81, + 0xE83, + 0xE84, + 0xE85, + 0xE86, + 0xE8B, + 0xE8C, + 0xEA4, + 0xEA5, + 0xEA6, + 0xEA7, + 0xEB3, + 0xEB4, + 0xEBE, + 0xEC0, + 0xEC5, + 0xEC6, + 0xEC7, + 0xEC8, + 0xECF, + 0xED0, + 0xEDA, + 0xEDC, + 0xEDD, + 0xEDE, + 0xEE0, + 0xF00, + 0xF0C, + 0xF0D, + 0xF43, + 0xF44, + 0xF48, + 0xF49, + 0xF4D, + 0xF4E, + 0xF52, + 0xF53, + 0xF57, + 0xF58, + 0xF5C, + 0xF5D, + 0xF69, + 0xF6A, + 0xF6D, + 0xF71, + 0xF73, + 0xF74, + 0xF75, + 0xF76, + 0xF77, + 0xF78, + 0xF79, + 0xF7A, + 0xF81, + 0xF82, + 0xF93, + 0xF94, + 0xF98, + 0xF99, + 0xF9D, + 0xF9E, + 0xFA2, + 0xFA3, + 0xFA7, + 0xFA8, + 0xFAC, + 0xFAD, + 0xFB9, + 0xFBA, + 0xFBD, + 0xFBE, + 0xFCD, + 0xFCE, + 0xFDB, + 0x1000, + 0x10A0, + 0x10A1, + 0x10A2, + 0x10A3, + 0x10A4, + 0x10A5, + 0x10A6, + 0x10A7, + 0x10A8, + 0x10A9, + 0x10AA, + 0x10AB, + 0x10AC, + 0x10AD, + 0x10AE, + 0x10AF, + 0x10B0, + 0x10B1, + 0x10B2, + 0x10B3, + 0x10B4, + 0x10B5, + 0x10B6, + 0x10B7, + 0x10B8, + 0x10B9, + 0x10BA, + 0x10BB, + 0x10BC, + 0x10BD, + 0x10BE, + 0x10BF, + 0x10C0, + 0x10C1, + 0x10C2, + 0x10C3, + 0x10C4, + 0x10C5, + 0x10C6, + 0x10C7, + 0x10C8, + 0x10CD, + 0x10CE, + 0x10D0, + 0x10FC, + 0x10FD, + 0x115F, + 0x1161, + 0x1249, + 0x124A, + 0x124E, + 0x1250, + 0x1257, + 0x1258, + 0x1259, + 0x125A, + 0x125E, + 0x1260, + 0x1289, + 0x128A, + 0x128E, + 0x1290, + 0x12B1, + 0x12B2, + 0x12B6, + 0x12B8, + 0x12BF, + 0x12C0, + 0x12C1, + 0x12C2, + 0x12C6, + 0x12C8, + 0x12D7, + 0x12D8, + 0x1311, + 0x1312, + 0x1316, + 0x1318, + 0x135B, + 0x135D, + 0x137D, + 0x1380, + 0x139A, + 0x13A0, + 0x13F6, + 0x13F8, + 0x13F9, + 0x13FA, + 0x13FB, + 0x13FC, + 0x13FD, + 0x13FE, + 0x1400, + 0x1680, + 0x1681, + 0x169D, + 0x16A0, + 0x16F9, + 0x1700, + 0x1716, + 0x171F, + 0x1737, + 0x1740, + 0x1754, + 0x1760, + 0x176D, + 0x176E, + 0x1771, + 0x1772, + 0x1774, + 0x1780, + 0x17B4, + 0x17B6, + 0x17DE, + 0x17E0, + 0x17EA, + 0x17F0, + 0x17FA, + 0x1800, + 0x180B, + 0x1810, + 0x181A, + 0x1820, + 0x1879, + 0x1880, + 0x18AB, + 0x18B0, + 0x18F6, + 0x1900, + 0x191F, + 0x1920, + 0x192C, + 0x1930, + 0x193C, + 0x1940, + 0x1941, + 0x1944, + 0x196E, + 0x1970, + 0x1975, + 0x1980, + 0x19AC, + 0x19B0, + 0x19CA, + 0x19D0, + 0x19DB, + 0x19DE, + 0x1A1C, + 0x1A1E, + 0x1A5F, + 0x1A60, + 0x1A7D, + 0x1A7F, + 0x1A8A, + 0x1A90, + 0x1A9A, + 0x1AA0, + 0x1AAE, + 0x1AB0, + 0x1AF1, + 0x1B00, + 0x1B4D, + 0x1B4E, + 0x1BF4, + 0x1BFC, + 0x1C38, + 0x1C3B, + 0x1C4A, + 0x1C4D, + 0x1C80, + 0x1C81, + 0x1C82, + 0x1C83, + 0x1C84, + 0x1C86, + 0x1C87, + 0x1C88, + 0x1C89, + 0x1C8A, + 0x1C8B, + 0x1C90, + 0x1C91, + 0x1C92, + 0x1C93, + 0x1C94, + 0x1C95, + 0x1C96, + 0x1C97, + 0x1C98, + 0x1C99, + 0x1C9A, + 0x1C9B, + 0x1C9C, + 0x1C9D, + 0x1C9E, + 0x1C9F, + 0x1CA0, + 0x1CA1, + 0x1CA2, + 0x1CA3, + 0x1CA4, + 0x1CA5, + 0x1CA6, + 0x1CA7, + 0x1CA8, + 0x1CA9, + 0x1CAA, + 0x1CAB, + 0x1CAC, + 0x1CAD, + 0x1CAE, + 0x1CAF, + 0x1CB0, + 0x1CB1, + 0x1CB2, + 0x1CB3, + 0x1CB4, + 0x1CB5, + 0x1CB6, + 0x1CB7, + 0x1CB8, + 0x1CB9, + 0x1CBA, + 0x1CBB, + 0x1CBD, + 0x1CBE, + 0x1CBF, + 0x1CC0, + 0x1CC8, + 0x1CD0, + 0x1CFB, + 0x1D00, + 0x1D2C, + 0x1D2D, + 0x1D2E, + 0x1D2F, + 0x1D30, + 0x1D31, + 0x1D32, + 0x1D33, + 0x1D34, + 0x1D35, + 0x1D36, + 0x1D37, + 0x1D38, + 0x1D39, + 0x1D3A, + 0x1D3B, + 0x1D3C, + 0x1D3D, + 0x1D3E, + 0x1D3F, + 0x1D40, + 0x1D41, + 0x1D42, + 0x1D43, + 0x1D44, + 0x1D45, + 0x1D46, + 0x1D47, + 0x1D48, + 0x1D49, + 0x1D4A, + 0x1D4B, + 0x1D4C, + 0x1D4D, + 0x1D4E, + 0x1D4F, + 0x1D50, + 0x1D51, + 0x1D52, + 0x1D53, + 0x1D54, + 0x1D55, + 0x1D56, + 0x1D57, + 0x1D58, + 0x1D59, + 0x1D5A, + 0x1D5B, + 0x1D5C, + 0x1D5D, + 0x1D5E, + 0x1D5F, + 0x1D60, + 0x1D61, + 0x1D62, + 0x1D63, + 0x1D64, + 0x1D65, + 0x1D66, + 0x1D67, + 0x1D68, + 0x1D69, + 0x1D6A, + 0x1D6B, + 0x1D78, + 0x1D79, + 0x1D9B, + 0x1D9C, + 0x1D9D, + 0x1D9E, + 0x1D9F, + 0x1DA0, + 0x1DA1, + 0x1DA2, + 0x1DA3, + 0x1DA4, + 0x1DA5, + 0x1DA6, + 0x1DA7, + 0x1DA8, + 0x1DA9, + 0x1DAA, + 0x1DAB, + 0x1DAC, + 0x1DAD, + 0x1DAE, + 0x1DAF, + 0x1DB0, + 0x1DB1, + 0x1DB2, + 0x1DB3, + 0x1DB4, + 0x1DB5, + 0x1DB6, + 0x1DB7, + 0x1DB8, + 0x1DB9, + 0x1DBA, + 0x1DBB, + 0x1DBC, + 0x1DBD, + 0x1DBE, + 0x1DBF, + 0x1DC0, + 0x1E00, + 0x1E01, + 0x1E02, + 0x1E03, + 0x1E04, + 0x1E05, + 0x1E06, + 0x1E07, + 0x1E08, + 0x1E09, + 0x1E0A, + 0x1E0B, + 0x1E0C, + 0x1E0D, + 0x1E0E, + 0x1E0F, + 0x1E10, + 0x1E11, + 0x1E12, + 0x1E13, + 0x1E14, + 0x1E15, + 0x1E16, + 0x1E17, + 0x1E18, + 0x1E19, + 0x1E1A, + 0x1E1B, + 0x1E1C, + 0x1E1D, + 0x1E1E, + 0x1E1F, + 0x1E20, + 0x1E21, + 0x1E22, + 0x1E23, + 0x1E24, + 0x1E25, + 0x1E26, + 0x1E27, + 0x1E28, + 0x1E29, + 0x1E2A, + 0x1E2B, + 0x1E2C, + 0x1E2D, + 0x1E2E, + 0x1E2F, + 0x1E30, + 0x1E31, + 0x1E32, + 0x1E33, + 0x1E34, + 0x1E35, + 0x1E36, + 0x1E37, + 0x1E38, + 0x1E39, + 0x1E3A, + 0x1E3B, + 0x1E3C, + 0x1E3D, + 0x1E3E, + 0x1E3F, + 0x1E40, + 0x1E41, + 0x1E42, + 0x1E43, + 0x1E44, + 0x1E45, + 0x1E46, + 0x1E47, + 0x1E48, + 0x1E49, + 0x1E4A, + 0x1E4B, + 0x1E4C, + 0x1E4D, + 0x1E4E, + 0x1E4F, + 0x1E50, + 0x1E51, + 0x1E52, + 0x1E53, + 0x1E54, + 0x1E55, + 0x1E56, + 0x1E57, + 0x1E58, + 0x1E59, + 0x1E5A, + 0x1E5B, + 0x1E5C, + 0x1E5D, + 0x1E5E, + 0x1E5F, + 0x1E60, + 0x1E61, + 0x1E62, + 0x1E63, + 0x1E64, + 0x1E65, + 0x1E66, + 0x1E67, + 0x1E68, + 0x1E69, + 0x1E6A, + 0x1E6B, + 0x1E6C, + 0x1E6D, + 0x1E6E, + 0x1E6F, + 0x1E70, + 0x1E71, + 0x1E72, + 0x1E73, + 0x1E74, + 0x1E75, + 0x1E76, + 0x1E77, + 0x1E78, + 0x1E79, + 0x1E7A, + 0x1E7B, + 0x1E7C, + 0x1E7D, + 0x1E7E, + 0x1E7F, + 0x1E80, + 0x1E81, + 0x1E82, + 0x1E83, + 0x1E84, + 0x1E85, + 0x1E86, + 0x1E87, + 0x1E88, + 0x1E89, + 0x1E8A, + 0x1E8B, + 0x1E8C, + 0x1E8D, + 0x1E8E, + 0x1E8F, + 0x1E90, + 0x1E91, + 0x1E92, + 0x1E93, + 0x1E94, + 0x1E95, + 0x1E9A, + 0x1E9B, + 0x1E9C, + 0x1E9E, + 0x1E9F, + 0x1EA0, + 0x1EA1, + 0x1EA2, + 0x1EA3, + 0x1EA4, + 0x1EA5, + 0x1EA6, + 0x1EA7, + 0x1EA8, + 0x1EA9, + 0x1EAA, + 0x1EAB, + 0x1EAC, + 0x1EAD, + 0x1EAE, + 0x1EAF, + 0x1EB0, + 0x1EB1, + 0x1EB2, + 0x1EB3, + 0x1EB4, + 0x1EB5, + 0x1EB6, + 0x1EB7, + 0x1EB8, + 0x1EB9, + 0x1EBA, + 0x1EBB, + 0x1EBC, + 0x1EBD, + 0x1EBE, + 0x1EBF, + 0x1EC0, + 0x1EC1, + 0x1EC2, + 0x1EC3, + 0x1EC4, + 0x1EC5, + 0x1EC6, + 0x1EC7, + 0x1EC8, + 0x1EC9, + 0x1ECA, + 0x1ECB, + 0x1ECC, + 0x1ECD, + 0x1ECE, + 0x1ECF, + 0x1ED0, + 0x1ED1, + 0x1ED2, + 0x1ED3, + 0x1ED4, + 0x1ED5, + 0x1ED6, + 0x1ED7, + 0x1ED8, + 0x1ED9, + 0x1EDA, + 0x1EDB, + 0x1EDC, + 0x1EDD, + 0x1EDE, + 0x1EDF, + 0x1EE0, + 0x1EE1, + 0x1EE2, + 0x1EE3, + 0x1EE4, + 0x1EE5, + 0x1EE6, + 0x1EE7, + 0x1EE8, + 0x1EE9, + 0x1EEA, + 0x1EEB, + 0x1EEC, + 0x1EED, + 0x1EEE, + 0x1EEF, + 0x1EF0, + 0x1EF1, + 0x1EF2, + 0x1EF3, + 0x1EF4, + 0x1EF5, + 0x1EF6, + 0x1EF7, + 0x1EF8, + 0x1EF9, + 0x1EFA, + 0x1EFB, + 0x1EFC, + 0x1EFD, + 0x1EFE, + 0x1EFF, + 0x1F08, + 0x1F09, + 0x1F0A, + 0x1F0B, + 0x1F0C, + 0x1F0D, + 0x1F0E, + 0x1F0F, + 0x1F10, + 0x1F16, + 0x1F18, + 0x1F19, + 0x1F1A, + 0x1F1B, + 0x1F1C, + 0x1F1D, + 0x1F1E, + 0x1F20, + 0x1F28, + 0x1F29, + 0x1F2A, + 0x1F2B, + 0x1F2C, + 0x1F2D, + 0x1F2E, + 0x1F2F, + 0x1F30, + 0x1F38, + 0x1F39, + 0x1F3A, + 0x1F3B, + 0x1F3C, + 0x1F3D, + 0x1F3E, + 0x1F3F, + 0x1F40, + 0x1F46, + 0x1F48, + 0x1F49, + 0x1F4A, + 0x1F4B, + 0x1F4C, + 0x1F4D, + 0x1F4E, + 0x1F50, + 0x1F58, + 0x1F59, + 0x1F5A, + 0x1F5B, + 0x1F5C, + 0x1F5D, + 0x1F5E, + 0x1F5F, + 0x1F60, + 0x1F68, + 0x1F69, + 0x1F6A, + 0x1F6B, + 0x1F6C, + 0x1F6D, + 0x1F6E, + 0x1F6F, + 0x1F70, + 0x1F71, + 0x1F72, + 0x1F73, + 0x1F74, + 0x1F75, + 0x1F76, + 0x1F77, + 0x1F78, + 0x1F79, + 0x1F7A, + 0x1F7B, + 0x1F7C, + 0x1F7D, + 0x1F7E, + 0x1F80, + 0x1F81, + 0x1F82, + 0x1F83, + 0x1F84, + 0x1F85, + 0x1F86, + 0x1F87, + 0x1F88, + 0x1F89, + 0x1F8A, + 0x1F8B, + 0x1F8C, + 0x1F8D, + 0x1F8E, + 0x1F8F, + 0x1F90, + 0x1F91, + 0x1F92, + 0x1F93, + 0x1F94, + 0x1F95, + 0x1F96, + 0x1F97, + 0x1F98, + 0x1F99, + 0x1F9A, + 0x1F9B, + 0x1F9C, + 0x1F9D, + 0x1F9E, + 0x1F9F, + 0x1FA0, + 0x1FA1, + 0x1FA2, + 0x1FA3, + 0x1FA4, + 0x1FA5, + 0x1FA6, + 0x1FA7, + 0x1FA8, + 0x1FA9, + 0x1FAA, + 0x1FAB, + 0x1FAC, + 0x1FAD, + 0x1FAE, + 0x1FAF, + 0x1FB0, + 0x1FB2, + 0x1FB3, + 0x1FB4, + 0x1FB5, + 0x1FB6, + 0x1FB7, + 0x1FB8, + 0x1FB9, + 0x1FBA, + 0x1FBB, + 0x1FBC, + 0x1FBD, + 0x1FBE, + 0x1FBF, + 0x1FC0, + 0x1FC1, + 0x1FC2, + 0x1FC3, + 0x1FC4, + 0x1FC5, + 0x1FC6, + 0x1FC7, + 0x1FC8, + 0x1FC9, + 0x1FCA, + 0x1FCB, + 0x1FCC, + 0x1FCD, + 0x1FCE, + 0x1FCF, + 0x1FD0, + 0x1FD3, + 0x1FD4, + 0x1FD6, + 0x1FD8, + 0x1FD9, + 0x1FDA, + 0x1FDB, + 0x1FDC, + 0x1FDD, + 0x1FDE, + 0x1FDF, + 0x1FE0, + 0x1FE3, + 0x1FE4, + 0x1FE8, + 0x1FE9, + 0x1FEA, + 0x1FEB, + 0x1FEC, + 0x1FED, + 0x1FEE, + 0x1FEF, + 0x1FF0, + 0x1FF2, + 0x1FF3, + 0x1FF4, + 0x1FF5, + 0x1FF6, + 0x1FF7, + 0x1FF8, + 0x1FF9, + 0x1FFA, + 0x1FFB, + 0x1FFC, + 0x1FFD, + 0x1FFE, + 0x1FFF, + 0x2000, + 0x200B, + 0x200C, + 0x200E, + 0x2010, + 0x2011, + 0x2012, + 0x2017, + 0x2018, + 0x2024, + 0x2027, + 0x2028, + 0x202F, + 0x2030, + 0x2033, + 0x2034, + 0x2035, + 0x2036, + 0x2037, + 0x2038, + 0x203C, + 0x203D, + 0x203E, + 0x203F, + 0x2047, + 0x2048, + 0x2049, + 0x204A, + 0x2057, + 0x2058, + 0x205F, + 0x2060, + 0x2065, + 0x206A, + 0x2070, + 0x2071, + 0x2072, + 0x2074, + 0x2075, + 0x2076, + 0x2077, + 0x2078, + 0x2079, + 0x207A, + 0x207B, + 0x207C, + 0x207D, + 0x207E, + 0x207F, + 0x2080, + 0x2081, + 0x2082, + 0x2083, + 0x2084, + 0x2085, + 0x2086, + 0x2087, + 0x2088, + 0x2089, + 0x208A, + 0x208B, + 0x208C, + 0x208D, + 0x208E, + 0x208F, + 0x2090, + 0x2091, + 0x2092, + 0x2093, + 0x2094, + 0x2095, + 0x2096, + 0x2097, + 0x2098, + 0x2099, + 0x209A, + 0x209B, + 0x209C, + 0x209D, + 0x209E, + 0x209F, + 0x20A0, + 0x20A8, + 0x20A9, + 0x20C5, + 0x20D0, + 0x20F1, + 0x2100, + 0x2101, + 0x2102, + 0x2103, + 0x2104, + 0x2105, + 0x2106, + 0x2107, + 0x2108, + 0x2109, + 0x210A, + 0x210B, + 0x210F, + 0x2110, + 0x2112, + 0x2114, + 0x2115, + 0x2116, + 0x2117, + 0x2119, + 0x211A, + 0x211B, + 0x211E, + 0x2120, + 0x2121, + 0x2122, + 0x2123, + 0x2124, + 0x2125, + 0x2126, + 0x2127, + 0x2128, + 0x2129, + 0x212A, + 0x212B, + 0x212C, + 0x212D, + 0x212E, + 0x212F, + 0x2131, + 0x2132, + 0x2133, + 0x2134, + 0x2135, + 0x2136, + 0x2137, + 0x2138, + 0x2139, + 0x213A, + 0x213B, + 0x213C, + 0x213D, + 0x213F, + 0x2140, + 0x2141, + 0x2145, + 0x2147, + 0x2148, + 0x2149, + 0x214A, + 0x2150, + 0x2151, + 0x2152, + 0x2153, + 0x2154, + 0x2155, + 0x2156, + 0x2157, + 0x2158, + 0x2159, + 0x215A, + 0x215B, + 0x215C, + 0x215D, + 0x215E, + 0x215F, + 0x2160, + 0x2161, + 0x2162, + 0x2163, + 0x2164, + 0x2165, + 0x2166, + 0x2167, + 0x2168, + 0x2169, + 0x216A, + 0x216B, + 0x216C, + 0x216D, + 0x216E, + 0x216F, + 0x2170, + 0x2171, + 0x2172, + 0x2173, + 0x2174, + 0x2175, + 0x2176, + 0x2177, + 0x2178, + 0x2179, + 0x217A, + 0x217B, + 0x217C, + 0x217D, + 0x217E, + 0x217F, + 0x2180, + 0x2183, + 0x2184, + 0x2189, + 0x218A, + 0x218C, + 0x2190, + 0x222C, + 0x222D, + 0x222E, + 0x222F, + 0x2230, + 0x2231, + 0x2329, + 0x232A, + 0x232B, + 0x242A, + 0x2440, + 0x244B, + 0x2460, + 0x2461, + 0x2462, + 0x2463, + 0x2464, + 0x2465, + 0x2466, + 0x2467, + 0x2468, + 0x2469, + 0x246A, + 0x246B, + 0x246C, + 0x246D, + 0x246E, + 0x246F, + 0x2470, + 0x2471, + 0x2472, + 0x2473, + 0x2474, + 0x2475, + 0x2476, + 0x2477, + 0x2478, + 0x2479, + 0x247A, + 0x247B, + 0x247C, + 0x247D, + 0x247E, + 0x247F, + 0x2480, + 0x2481, + 0x2482, + 0x2483, + 0x2484, + 0x2485, + 0x2486, + 0x2487, + 0x2488, + 0x249C, + 0x249D, + 0x249E, + 0x249F, + 0x24A0, + 0x24A1, + 0x24A2, + 0x24A3, + 0x24A4, + 0x24A5, + 0x24A6, + 0x24A7, + 0x24A8, + 0x24A9, + 0x24AA, + 0x24AB, + 0x24AC, + 0x24AD, + 0x24AE, + 0x24AF, + 0x24B0, + 0x24B1, + 0x24B2, + 0x24B3, + 0x24B4, + 0x24B5, + 0x24B6, + 0x24B7, + 0x24B8, + 0x24B9, + 0x24BA, + 0x24BB, + 0x24BC, + 0x24BD, + 0x24BE, + 0x24BF, + 0x24C0, + 0x24C1, + 0x24C2, + 0x24C3, + 0x24C4, + 0x24C5, + 0x24C6, + 0x24C7, + 0x24C8, + 0x24C9, + 0x24CA, + 0x24CB, + 0x24CC, + 0x24CD, + 0x24CE, + 0x24CF, + 0x24D0, + 0x24D1, + 0x24D2, + 0x24D3, + 0x24D4, + 0x24D5, + 0x24D6, + 0x24D7, + 0x24D8, + 0x24D9, + 0x24DA, + 0x24DB, + 0x24DC, + 0x24DD, + 0x24DE, + 0x24DF, + 0x24E0, + 0x24E1, + 0x24E2, + 0x24E3, + 0x24E4, + 0x24E5, + 0x24E6, + 0x24E7, + 0x24E8, + 0x24E9, + 0x24EA, + 0x24EB, + 0x2A0C, + 0x2A0D, + 0x2A74, + 0x2A75, + 0x2A76, + 0x2A77, + 0x2ADC, + 0x2ADD, + 0x2B74, + 0x2B76, + 0x2C00, + 0x2C01, + 0x2C02, + 0x2C03, + 0x2C04, + 0x2C05, + 0x2C06, + 0x2C07, + 0x2C08, + 0x2C09, + 0x2C0A, + 0x2C0B, + 0x2C0C, + 0x2C0D, + 0x2C0E, + 0x2C0F, + 0x2C10, + 0x2C11, + 0x2C12, + 0x2C13, + 0x2C14, + 0x2C15, + 0x2C16, + 0x2C17, + 0x2C18, + 0x2C19, + 0x2C1A, + 0x2C1B, + 0x2C1C, + 0x2C1D, + 0x2C1E, + 0x2C1F, + 0x2C20, + 0x2C21, + 0x2C22, + 0x2C23, + 0x2C24, + 0x2C25, + 0x2C26, + 0x2C27, + 0x2C28, + 0x2C29, + 0x2C2A, + 0x2C2B, + 0x2C2C, + 0x2C2D, + 0x2C2E, + 0x2C2F, + 0x2C30, + 0x2C60, + 0x2C61, + 0x2C62, + 0x2C63, + 0x2C64, + 0x2C65, + 0x2C67, + 0x2C68, + 0x2C69, + 0x2C6A, + 0x2C6B, + 0x2C6C, + 0x2C6D, + 0x2C6E, + 0x2C6F, + 0x2C70, + 0x2C71, + 0x2C72, + 0x2C73, + 0x2C75, + 0x2C76, + 0x2C7C, + 0x2C7D, + 0x2C7E, + 0x2C7F, + 0x2C80, + 0x2C81, + 0x2C82, + 0x2C83, + 0x2C84, + 0x2C85, + 0x2C86, + 0x2C87, + 0x2C88, + 0x2C89, + 0x2C8A, + 0x2C8B, + 0x2C8C, + 0x2C8D, + 0x2C8E, + 0x2C8F, + 0x2C90, + 0x2C91, + 0x2C92, + 0x2C93, + 0x2C94, + 0x2C95, + 0x2C96, + 0x2C97, + 0x2C98, + 0x2C99, + 0x2C9A, + 0x2C9B, + 0x2C9C, + 0x2C9D, + 0x2C9E, + 0x2C9F, + 0x2CA0, + 0x2CA1, + 0x2CA2, + 0x2CA3, + 0x2CA4, + 0x2CA5, + 0x2CA6, + 0x2CA7, + 0x2CA8, + 0x2CA9, + 0x2CAA, + 0x2CAB, + 0x2CAC, + 0x2CAD, + 0x2CAE, + 0x2CAF, + 0x2CB0, + 0x2CB1, + 0x2CB2, + 0x2CB3, + 0x2CB4, + 0x2CB5, + 0x2CB6, + 0x2CB7, + 0x2CB8, + 0x2CB9, + 0x2CBA, + 0x2CBB, + 0x2CBC, + 0x2CBD, + 0x2CBE, + 0x2CBF, + 0x2CC0, + 0x2CC1, + 0x2CC2, + 0x2CC3, + 0x2CC4, + 0x2CC5, + 0x2CC6, + 0x2CC7, + 0x2CC8, + 0x2CC9, + 0x2CCA, + 0x2CCB, + 0x2CCC, + 0x2CCD, + 0x2CCE, + 0x2CCF, + 0x2CD0, + 0x2CD1, + 0x2CD2, + 0x2CD3, + 0x2CD4, + 0x2CD5, + 0x2CD6, + 0x2CD7, + 0x2CD8, + 0x2CD9, + 0x2CDA, + 0x2CDB, + 0x2CDC, + 0x2CDD, + 0x2CDE, + 0x2CDF, + 0x2CE0, + 0x2CE1, + 0x2CE2, + 0x2CE3, + 0x2CEB, + 0x2CEC, + 0x2CED, + 0x2CEE, + 0x2CF2, + 0x2CF3, + 0x2CF4, + 0x2CF9, + 0x2D26, + 0x2D27, + 0x2D28, + 0x2D2D, + 0x2D2E, + 0x2D30, + 0x2D68, + 0x2D6F, + 0x2D70, + 0x2D71, + 0x2D7F, + 0x2D97, + 0x2DA0, + 0x2DA7, + 0x2DA8, + 0x2DAF, + 0x2DB0, + 0x2DB7, + 0x2DB8, + 0x2DBF, + 0x2DC0, + 0x2DC7, + 0x2DC8, + 0x2DCF, + 0x2DD0, + 0x2DD7, + 0x2DD8, + 0x2DDF, + 0x2DE0, + 0x2E5E, + 0x2E60, + 0x2E64, + 0x2E80, + 0x2E9A, + 0x2E9B, + 0x2E9F, + 0x2EA0, + 0x2EF3, + 0x2EF4, + 0x2F00, + 0x2F01, + 0x2F02, + 0x2F03, + 0x2F04, + 0x2F05, + 0x2F06, + 0x2F07, + 0x2F08, + 0x2F09, + 0x2F0A, + 0x2F0B, + 0x2F0C, + 0x2F0D, + 0x2F0E, + 0x2F0F, + 0x2F10, + 0x2F11, + 0x2F12, + 0x2F13, + 0x2F14, + 0x2F15, + 0x2F16, + 0x2F17, + 0x2F18, + 0x2F19, + 0x2F1A, + 0x2F1B, + 0x2F1C, + 0x2F1D, + 0x2F1E, + 0x2F1F, + 0x2F20, + 0x2F21, + 0x2F22, + 0x2F23, + 0x2F24, + 0x2F25, + 0x2F26, + 0x2F27, + 0x2F28, + 0x2F29, + 0x2F2A, + 0x2F2B, + 0x2F2C, + 0x2F2D, + 0x2F2E, + 0x2F2F, + 0x2F30, + 0x2F31, + 0x2F32, + 0x2F33, + 0x2F34, + 0x2F35, + 0x2F36, + 0x2F37, + 0x2F38, + 0x2F39, + 0x2F3A, + 0x2F3B, + 0x2F3C, + 0x2F3D, + 0x2F3E, + 0x2F3F, + 0x2F40, + 0x2F41, + 0x2F42, + 0x2F43, + 0x2F44, + 0x2F45, + 0x2F46, + 0x2F47, + 0x2F48, + 0x2F49, + 0x2F4A, + 0x2F4B, + 0x2F4C, + 0x2F4D, + 0x2F4E, + 0x2F4F, + 0x2F50, + 0x2F51, + 0x2F52, + 0x2F53, + 0x2F54, + 0x2F55, + 0x2F56, + 0x2F57, + 0x2F58, + 0x2F59, + 0x2F5A, + 0x2F5B, + 0x2F5C, + 0x2F5D, + 0x2F5E, + 0x2F5F, + 0x2F60, + 0x2F61, + 0x2F62, + 0x2F63, + 0x2F64, + 0x2F65, + 0x2F66, + 0x2F67, + 0x2F68, + 0x2F69, + 0x2F6A, + 0x2F6B, + 0x2F6C, + 0x2F6D, + 0x2F6E, + 0x2F6F, + 0x2F70, + 0x2F71, + 0x2F72, + 0x2F73, + 0x2F74, + 0x2F75, + 0x2F76, + 0x2F77, + 0x2F78, + 0x2F79, + 0x2F7A, + 0x2F7B, + 0x2F7C, + 0x2F7D, + 0x2F7E, + 0x2F7F, + 0x2F80, + 0x2F81, + 0x2F82, + 0x2F83, + 0x2F84, + 0x2F85, + 0x2F86, + 0x2F87, + 0x2F88, + 0x2F89, + 0x2F8A, + 0x2F8B, + 0x2F8C, + 0x2F8D, + 0x2F8E, + 0x2F8F, + 0x2F90, + 0x2F91, + 0x2F92, + 0x2F93, + 0x2F94, + 0x2F95, + 0x2F96, + 0x2F97, + 0x2F98, + 0x2F99, + 0x2F9A, + 0x2F9B, + 0x2F9C, + 0x2F9D, + 0x2F9E, + 0x2F9F, + 0x2FA0, + 0x2FA1, + 0x2FA2, + 0x2FA3, + 0x2FA4, + 0x2FA5, + 0x2FA6, + 0x2FA7, + 0x2FA8, + 0x2FA9, + 0x2FAA, + 0x2FAB, + 0x2FAC, + 0x2FAD, + 0x2FAE, + 0x2FAF, + 0x2FB0, + 0x2FB1, + 0x2FB2, + 0x2FB3, + 0x2FB4, + 0x2FB5, + 0x2FB6, + 0x2FB7, + 0x2FB8, + 0x2FB9, + 0x2FBA, + 0x2FBB, + 0x2FBC, + 0x2FBD, + 0x2FBE, + 0x2FBF, + 0x2FC0, + 0x2FC1, + 0x2FC2, + 0x2FC3, + 0x2FC4, + 0x2FC5, + 0x2FC6, + 0x2FC7, + 0x2FC8, + 0x2FC9, + 0x2FCA, + 0x2FCB, + 0x2FCC, + 0x2FCD, + 0x2FCE, + 0x2FCF, + 0x2FD0, + 0x2FD1, + 0x2FD2, + 0x2FD3, + 0x2FD4, + 0x2FD5, + 0x2FD6, + 0x3000, + 0x3001, + 0x3002, + 0x3003, + 0x3036, + 0x3037, + 0x3038, + 0x3039, + 0x303A, + 0x303B, + 0x3040, + 0x3041, + 0x3097, + 0x3099, + 0x309B, + 0x309C, + 0x309D, + 0x309F, + 0x30A0, + 0x30FF, + 0x3100, + 0x3105, + 0x3130, + 0x3131, + 0x3132, + 0x3133, + 0x3134, + 0x3135, + 0x3136, + 0x3137, + 0x3138, + 0x3139, + 0x313A, + 0x313B, + 0x313C, + 0x313D, + 0x313E, + 0x313F, + 0x3140, + 0x3141, + 0x3142, + 0x3143, + 0x3144, + 0x3145, + 0x3146, + 0x3147, + 0x3148, + 0x3149, + 0x314A, + 0x314B, + 0x314C, + 0x314D, + 0x314E, + 0x314F, + 0x3150, + 0x3151, + 0x3152, + 0x3153, + 0x3154, + 0x3155, + 0x3156, + 0x3157, + 0x3158, + 0x3159, + 0x315A, + 0x315B, + 0x315C, + 0x315D, + 0x315E, + 0x315F, + 0x3160, + 0x3161, + 0x3162, + 0x3163, + 0x3164, + 0x3165, + 0x3166, + 0x3167, + 0x3168, + 0x3169, + 0x316A, + 0x316B, + 0x316C, + 0x316D, + 0x316E, + 0x316F, + 0x3170, + 0x3171, + 0x3172, + 0x3173, + 0x3174, + 0x3175, + 0x3176, + 0x3177, + 0x3178, + 0x3179, + 0x317A, + 0x317B, + 0x317C, + 0x317D, + 0x317E, + 0x317F, + 0x3180, + 0x3181, + 0x3182, + 0x3183, + 0x3184, + 0x3185, + 0x3186, + 0x3187, + 0x3188, + 0x3189, + 0x318A, + 0x318B, + 0x318C, + 0x318D, + 0x318E, + 0x318F, + 0x3190, + 0x3192, + 0x3193, + 0x3194, + 0x3195, + 0x3196, + 0x3197, + 0x3198, + 0x3199, + 0x319A, + 0x319B, + 0x319C, + 0x319D, + 0x319E, + 0x319F, + 0x31A0, + 0x31E6, + 0x31F0, + 0x3200, + 0x3201, + 0x3202, + 0x3203, + 0x3204, + 0x3205, + 0x3206, + 0x3207, + 0x3208, + 0x3209, + 0x320A, + 0x320B, + 0x320C, + 0x320D, + 0x320E, + 0x320F, + 0x3210, + 0x3211, + 0x3212, + 0x3213, + 0x3214, + 0x3215, + 0x3216, + 0x3217, + 0x3218, + 0x3219, + 0x321A, + 0x321B, + 0x321C, + 0x321D, + 0x321E, + 0x321F, + 0x3220, + 0x3221, + 0x3222, + 0x3223, + 0x3224, + 0x3225, + 0x3226, + 0x3227, + 0x3228, + 0x3229, + 0x322A, + 0x322B, + 0x322C, + 0x322D, + 0x322E, + 0x322F, + 0x3230, + 0x3231, + 0x3232, + 0x3233, + 0x3234, + 0x3235, + 0x3236, + 0x3237, + 0x3238, + 0x3239, + 0x323A, + 0x323B, + 0x323C, + 0x323D, + 0x323E, + 0x323F, + 0x3240, + 0x3241, + 0x3242, + 0x3243, + 0x3244, + 0x3245, + 0x3246, + 0x3247, + 0x3248, + 0x3250, + 0x3251, + 0x3252, + 0x3253, + 0x3254, + 0x3255, + 0x3256, + 0x3257, + 0x3258, + 0x3259, + 0x325A, + 0x325B, + 0x325C, + 0x325D, + 0x325E, + 0x325F, + 0x3260, + 0x3261, + 0x3262, + 0x3263, + 0x3264, + 0x3265, + 0x3266, + 0x3267, + 0x3268, + 0x3269, + 0x326A, + 0x326B, + 0x326C, + 0x326D, + 0x326E, + 0x326F, + 0x3270, + 0x3271, + 0x3272, + 0x3273, + 0x3274, + 0x3275, + 0x3276, + 0x3277, + 0x3278, + 0x3279, + 0x327A, + 0x327B, + 0x327C, + 0x327D, + 0x327E, + 0x327F, + 0x3280, + 0x3281, + 0x3282, + 0x3283, + 0x3284, + 0x3285, + 0x3286, + 0x3287, + 0x3288, + 0x3289, + 0x328A, + 0x328B, + 0x328C, + 0x328D, + 0x328E, + 0x328F, + 0x3290, + 0x3291, + 0x3292, + 0x3293, + 0x3294, + 0x3295, + 0x3296, + 0x3297, + 0x3298, + 0x3299, + 0x329A, + 0x329B, + 0x329C, + 0x329D, + 0x329E, + 0x329F, + 0x32A0, + 0x32A1, + 0x32A2, + 0x32A3, + 0x32A4, + 0x32A5, + 0x32A6, + 0x32A7, + 0x32A8, + 0x32A9, + 0x32AA, + 0x32AB, + 0x32AC, + 0x32AD, + 0x32AE, + 0x32AF, + 0x32B0, + 0x32B1, + 0x32B2, + 0x32B3, + 0x32B4, + 0x32B5, + 0x32B6, + 0x32B7, + 0x32B8, + 0x32B9, + 0x32BA, + 0x32BB, + 0x32BC, + 0x32BD, + 0x32BE, + 0x32BF, + 0x32C0, + 0x32C1, + 0x32C2, + 0x32C3, + 0x32C4, + 0x32C5, + 0x32C6, + 0x32C7, + 0x32C8, + 0x32C9, + 0x32CA, + 0x32CB, + 0x32CC, + 0x32CD, + 0x32CE, + 0x32CF, + 0x32D0, + 0x32D1, + 0x32D2, + 0x32D3, + 0x32D4, + 0x32D5, + 0x32D6, + 0x32D7, + 0x32D8, + 0x32D9, + 0x32DA, + 0x32DB, + 0x32DC, + 0x32DD, + 0x32DE, + 0x32DF, + 0x32E0, + 0x32E1, + 0x32E2, + 0x32E3, + 0x32E4, + 0x32E5, + 0x32E6, + 0x32E7, + 0x32E8, + 0x32E9, + 0x32EA, + 0x32EB, + 0x32EC, + 0x32ED, + 0x32EE, + 0x32EF, + 0x32F0, + 0x32F1, + 0x32F2, + 0x32F3, + 0x32F4, + 0x32F5, + 0x32F6, + 0x32F7, + 0x32F8, + 0x32F9, + 0x32FA, + 0x32FB, + 0x32FC, + 0x32FD, + 0x32FE, + 0x32FF, + 0x3300, + 0x3301, + 0x3302, + 0x3303, + 0x3304, + 0x3305, + 0x3306, + 0x3307, + 0x3308, + 0x3309, + 0x330A, + 0x330B, + 0x330C, + 0x330D, + 0x330E, + 0x330F, + 0x3310, + 0x3311, + 0x3312, + 0x3313, + 0x3314, + 0x3315, + 0x3316, + 0x3317, + 0x3318, + 0x3319, + 0x331A, + 0x331B, + 0x331C, + 0x331D, + 0x331E, + 0x331F, + 0x3320, + 0x3321, + 0x3322, + 0x3323, + 0x3324, + 0x3325, + 0x3326, + 0x3327, + 0x3328, + 0x3329, + 0x332A, + 0x332B, + 0x332C, + 0x332D, + 0x332E, + 0x332F, + 0x3330, + 0x3331, + 0x3332, + 0x3333, + 0x3334, + 0x3335, + 0x3336, + 0x3337, + 0x3338, + 0x3339, + 0x333A, + 0x333B, + 0x333C, + 0x333D, + 0x333E, + 0x333F, + 0x3340, + 0x3341, + 0x3342, + 0x3343, + 0x3344, + 0x3345, + 0x3346, + 0x3347, + 0x3348, + 0x3349, + 0x334A, + 0x334B, + 0x334C, + 0x334D, + 0x334E, + 0x334F, + 0x3350, + 0x3351, + 0x3352, + 0x3353, + 0x3354, + 0x3355, + 0x3356, + 0x3357, + 0x3358, + 0x3359, + 0x335A, + 0x335B, + 0x335C, + 0x335D, + 0x335E, + 0x335F, + 0x3360, + 0x3361, + 0x3362, + 0x3363, + 0x3364, + 0x3365, + 0x3366, + 0x3367, + 0x3368, + 0x3369, + 0x336A, + 0x336B, + 0x336C, + 0x336D, + 0x336E, + 0x336F, + 0x3370, + 0x3371, + 0x3372, + 0x3373, + 0x3374, + 0x3375, + 0x3376, + 0x3377, + 0x3378, + 0x3379, + 0x337A, + 0x337B, + 0x337C, + 0x337D, + 0x337E, + 0x337F, + 0x3380, + 0x3381, + 0x3382, + 0x3383, + 0x3384, + 0x3385, + 0x3386, + 0x3387, + 0x3388, + 0x3389, + 0x338A, + 0x338B, + 0x338C, + 0x338D, + 0x338E, + 0x338F, + 0x3390, + 0x3391, + 0x3392, + 0x3393, + 0x3394, + 0x3395, + 0x3396, + 0x3397, + 0x3398, + 0x3399, + 0x339A, + 0x339B, + 0x339C, + 0x339D, + 0x339E, + 0x339F, + 0x33A0, + 0x33A1, + 0x33A2, + 0x33A3, + 0x33A4, + 0x33A5, + 0x33A6, + 0x33A7, + 0x33A8, + 0x33A9, + 0x33AA, + 0x33AB, + 0x33AC, + 0x33AD, + 0x33AE, + 0x33AF, + 0x33B0, + 0x33B1, + 0x33B2, + 0x33B3, + 0x33B4, + 0x33B5, + 0x33B6, + 0x33B7, + 0x33B8, + 0x33B9, + 0x33BA, + 0x33BB, + 0x33BC, + 0x33BD, + 0x33BE, + 0x33BF, + 0x33C0, + 0x33C1, + 0x33C2, + 0x33C3, + 0x33C4, + 0x33C5, + 0x33C6, + 0x33C7, + 0x33C8, + 0x33C9, + 0x33CA, + 0x33CB, + 0x33CC, + 0x33CD, + 0x33CE, + 0x33CF, + 0x33D0, + 0x33D1, + 0x33D2, + 0x33D3, + 0x33D4, + 0x33D5, + 0x33D6, + 0x33D7, + 0x33D8, + 0x33D9, + 0x33DA, + 0x33DB, + 0x33DC, + 0x33DD, + 0x33DE, + 0x33DF, + 0x33E0, + 0x33E1, + 0x33E2, + 0x33E3, + 0x33E4, + 0x33E5, + 0x33E6, + 0x33E7, + 0x33E8, + 0x33E9, + 0x33EA, + 0x33EB, + 0x33EC, + 0x33ED, + 0x33EE, + 0x33EF, + 0x33F0, + 0x33F1, + 0x33F2, + 0x33F3, + 0x33F4, + 0x33F5, + 0x33F6, + 0x33F7, + 0x33F8, + 0x33F9, + 0x33FA, + 0x33FB, + 0x33FC, + 0x33FD, + 0x33FE, + 0x33FF, + 0x3400, + 0xA48D, + 0xA490, + 0xA4C7, + 0xA4D0, + 0xA62C, + 0xA640, + 0xA641, + 0xA642, + 0xA643, + 0xA644, + 0xA645, + 0xA646, + 0xA647, + 0xA648, + 0xA649, + 0xA64A, + 0xA64B, + 0xA64C, + 0xA64D, + 0xA64E, + 0xA64F, + 0xA650, + 0xA651, + 0xA652, + 0xA653, + 0xA654, + 0xA655, + 0xA656, + 0xA657, + 0xA658, + 0xA659, + 0xA65A, + 0xA65B, + 0xA65C, + 0xA65D, + 0xA65E, + 0xA65F, + 0xA660, + 0xA661, + 0xA662, + 0xA663, + 0xA664, + 0xA665, + 0xA666, + 0xA667, + 0xA668, + 0xA669, + 0xA66A, + 0xA66B, + 0xA66C, + 0xA66D, + 0xA680, + 0xA681, + 0xA682, + 0xA683, + 0xA684, + 0xA685, + 0xA686, + 0xA687, + 0xA688, + 0xA689, + 0xA68A, + 0xA68B, + 0xA68C, + 0xA68D, + 0xA68E, + 0xA68F, + 0xA690, + 0xA691, + 0xA692, + 0xA693, + 0xA694, + 0xA695, + 0xA696, + 0xA697, + 0xA698, + 0xA699, + 0xA69A, + 0xA69B, + 0xA69C, + 0xA69D, + 0xA69E, + 0xA6F8, + 0xA700, + 0xA722, + 0xA723, + 0xA724, + 0xA725, + 0xA726, + 0xA727, + 0xA728, + 0xA729, + 0xA72A, + 0xA72B, + 0xA72C, + 0xA72D, + 0xA72E, + 0xA72F, + 0xA732, + 0xA733, + 0xA734, + 0xA735, + 0xA736, + 0xA737, + 0xA738, + 0xA739, + 0xA73A, + 0xA73B, + 0xA73C, + 0xA73D, + 0xA73E, + 0xA73F, + 0xA740, + 0xA741, + 0xA742, + 0xA743, + 0xA744, + 0xA745, + 0xA746, + 0xA747, + 0xA748, + 0xA749, + 0xA74A, + 0xA74B, + 0xA74C, + 0xA74D, + 0xA74E, + 0xA74F, + 0xA750, + 0xA751, + 0xA752, + 0xA753, + 0xA754, + 0xA755, + 0xA756, + 0xA757, + 0xA758, + 0xA759, + 0xA75A, + 0xA75B, + 0xA75C, + 0xA75D, + 0xA75E, + 0xA75F, + 0xA760, + 0xA761, + 0xA762, + 0xA763, + 0xA764, + 0xA765, + 0xA766, + 0xA767, + 0xA768, + 0xA769, + 0xA76A, + 0xA76B, + 0xA76C, + 0xA76D, + 0xA76E, + 0xA76F, + 0xA770, + 0xA771, + 0xA779, + 0xA77A, + 0xA77B, + 0xA77C, + 0xA77D, + 0xA77E, + 0xA77F, + 0xA780, + 0xA781, + 0xA782, + 0xA783, + 0xA784, + 0xA785, + 0xA786, + 0xA787, + 0xA78B, + 0xA78C, + 0xA78D, + 0xA78E, + 0xA790, + 0xA791, + 0xA792, + 0xA793, + 0xA796, + 0xA797, + 0xA798, + 0xA799, + 0xA79A, + 0xA79B, + 0xA79C, + 0xA79D, + 0xA79E, + 0xA79F, + 0xA7A0, + 0xA7A1, + 0xA7A2, + 0xA7A3, + 0xA7A4, + 0xA7A5, + 0xA7A6, + 0xA7A7, + 0xA7A8, + 0xA7A9, + 0xA7AA, + 0xA7AB, + 0xA7AC, + 0xA7AD, + 0xA7AE, + 0xA7AF, + 0xA7B0, + 0xA7B1, + 0xA7B2, + 0xA7B3, + 0xA7B4, + 0xA7B5, + 0xA7B6, + 0xA7B7, + 0xA7B8, + 0xA7B9, + 0xA7BA, + 0xA7BB, + 0xA7BC, + 0xA7BD, + 0xA7BE, + 0xA7BF, + 0xA7C0, + 0xA7C1, + 0xA7C2, + 0xA7C3, + 0xA7C4, + 0xA7C5, + 0xA7C6, + 0xA7C7, + 0xA7C8, + 0xA7C9, + 0xA7CA, + 0xA7CB, + 0xA7CC, + 0xA7CD, + 0xA7CE, + 0xA7CF, + 0xA7D0, + 0xA7D1, + 0xA7D2, + 0xA7D3, + 0xA7D4, + 0xA7D5, + 0xA7D6, + 0xA7D7, + 0xA7D8, + 0xA7D9, + 0xA7DA, + 0xA7DB, + 0xA7DC, + 0xA7DD, + 0xA7DE, + 0xA7E2, + 0xA7E3, + 0xA7F1, + 0xA7F2, + 0xA7F3, + 0xA7F4, + 0xA7F5, + 0xA7F6, + 0xA7F8, + 0xA7F9, + 0xA7FA, + 0xA82D, + 0xA830, + 0xA83A, + 0xA840, + 0xA878, + 0xA880, + 0xA8C6, + 0xA8CE, + 0xA8DA, + 0xA8E0, + 0xA954, + 0xA95F, + 0xA97D, + 0xA980, + 0xA9CE, + 0xA9CF, + 0xA9DA, + 0xA9DE, + 0xA9FF, + 0xAA00, + 0xAA37, + 0xAA40, + 0xAA4E, + 0xAA50, + 0xAA5A, + 0xAA5C, + 0xAAC3, + 0xAADB, + 0xAAF7, + 0xAB01, + 0xAB07, + 0xAB09, + 0xAB0F, + 0xAB11, + 0xAB17, + 0xAB20, + 0xAB27, + 0xAB28, + 0xAB2F, + 0xAB30, + 0xAB5C, + 0xAB5D, + 0xAB5E, + 0xAB5F, + 0xAB60, + 0xAB69, + 0xAB6A, + 0xAB6C, + 0xAB6D, + 0xAB6E, + 0xAB70, + 0xAB71, + 0xAB72, + 0xAB73, + 0xAB74, + 0xAB75, + 0xAB76, + 0xAB77, + 0xAB78, + 0xAB79, + 0xAB7A, + 0xAB7B, + 0xAB7C, + 0xAB7D, + 0xAB7E, + 0xAB7F, + 0xAB80, + 0xAB81, + 0xAB82, + 0xAB83, + 0xAB84, + 0xAB85, + 0xAB86, + 0xAB87, + 0xAB88, + 0xAB89, + 0xAB8A, + 0xAB8B, + 0xAB8C, + 0xAB8D, + 0xAB8E, + 0xAB8F, + 0xAB90, + 0xAB91, + 0xAB92, + 0xAB93, + 0xAB94, + 0xAB95, + 0xAB96, + 0xAB97, + 0xAB98, + 0xAB99, + 0xAB9A, + 0xAB9B, + 0xAB9C, + 0xAB9D, + 0xAB9E, + 0xAB9F, + 0xABA0, + 0xABA1, + 0xABA2, + 0xABA3, + 0xABA4, + 0xABA5, + 0xABA6, + 0xABA7, + 0xABA8, + 0xABA9, + 0xABAA, + 0xABAB, + 0xABAC, + 0xABAD, + 0xABAE, + 0xABAF, + 0xABB0, + 0xABB1, + 0xABB2, + 0xABB3, + 0xABB4, + 0xABB5, + 0xABB6, + 0xABB7, + 0xABB8, + 0xABB9, + 0xABBA, + 0xABBB, + 0xABBC, + 0xABBD, + 0xABBE, + 0xABBF, + 0xABC0, + 0xABEE, + 0xABF0, + 0xABFA, + 0xAC00, + 0xD7A4, + 0xD7B0, + 0xD7C7, + 0xD7CB, + 0xD7FC, + 0xF900, + 0xF901, + 0xF902, + 0xF903, + 0xF904, + 0xF905, + 0xF906, + 0xF907, + 0xF909, + 0xF90A, + 0xF90B, + 0xF90C, + 0xF90D, + 0xF90E, + 0xF90F, + 0xF910, + 0xF911, + 0xF912, + 0xF913, + 0xF914, + 0xF915, + 0xF916, + 0xF917, + 0xF918, + 0xF919, + 0xF91A, + 0xF91B, + 0xF91C, + 0xF91D, + 0xF91E, + 0xF91F, + 0xF920, + 0xF921, + 0xF922, + 0xF923, + 0xF924, + 0xF925, + 0xF926, + 0xF927, + 0xF928, + 0xF929, + 0xF92A, + 0xF92B, + 0xF92C, + 0xF92D, + 0xF92E, + 0xF92F, + 0xF930, + 0xF931, + 0xF932, + 0xF933, + 0xF934, + 0xF935, + 0xF936, + 0xF937, + 0xF938, + 0xF939, + 0xF93A, + 0xF93B, + 0xF93C, + 0xF93D, + 0xF93E, + 0xF93F, + 0xF940, + 0xF941, + 0xF942, + 0xF943, + 0xF944, + 0xF945, + 0xF946, + 0xF947, + 0xF948, + 0xF949, + 0xF94A, + 0xF94B, + 0xF94C, + 0xF94D, + 0xF94E, + 0xF94F, + 0xF950, + 0xF951, + 0xF952, + 0xF953, + 0xF954, + 0xF955, + 0xF956, + 0xF957, + 0xF958, + 0xF959, + 0xF95A, + 0xF95B, + 0xF95C, + 0xF95D, + 0xF95E, + 0xF95F, + 0xF960, + 0xF961, + 0xF962, + 0xF963, + 0xF964, + 0xF965, + 0xF966, + 0xF967, + 0xF968, + 0xF969, + 0xF96A, + 0xF96B, + 0xF96C, + 0xF96D, + 0xF96E, + 0xF96F, + 0xF970, + 0xF971, + 0xF972, + 0xF973, + 0xF974, + 0xF975, + 0xF976, + 0xF977, + 0xF978, + 0xF979, + 0xF97A, + 0xF97B, + 0xF97C, + 0xF97D, + 0xF97E, + 0xF97F, + 0xF980, + 0xF981, + 0xF982, + 0xF983, + 0xF984, + 0xF985, + 0xF986, + 0xF987, + 0xF988, + 0xF989, + 0xF98A, + 0xF98B, + 0xF98C, + 0xF98D, + 0xF98E, + 0xF98F, + 0xF990, + 0xF991, + 0xF992, + 0xF993, + 0xF994, + 0xF995, + 0xF996, + 0xF997, + 0xF998, + 0xF999, + 0xF99A, + 0xF99B, + 0xF99C, + 0xF99D, + 0xF99E, + 0xF99F, + 0xF9A0, + 0xF9A1, + 0xF9A2, + 0xF9A3, + 0xF9A4, + 0xF9A5, + 0xF9A6, + 0xF9A7, + 0xF9A8, + 0xF9A9, + 0xF9AA, + 0xF9AB, + 0xF9AC, + 0xF9AD, + 0xF9AE, + 0xF9AF, + 0xF9B0, + 0xF9B1, + 0xF9B2, + 0xF9B3, + 0xF9B4, + 0xF9B5, + 0xF9B6, + 0xF9B7, + 0xF9B8, + 0xF9B9, + 0xF9BA, + 0xF9BB, + 0xF9BC, + 0xF9BD, + 0xF9BE, + 0xF9BF, + 0xF9C0, + 0xF9C1, + 0xF9C2, + 0xF9C3, + 0xF9C4, + 0xF9C5, + 0xF9C6, + 0xF9C7, + 0xF9C8, + 0xF9C9, + 0xF9CA, + 0xF9CB, + 0xF9CC, + 0xF9CD, + 0xF9CE, + 0xF9CF, + 0xF9D0, + 0xF9D1, + 0xF9D2, + 0xF9D3, + 0xF9D4, + 0xF9D5, + 0xF9D6, + 0xF9D7, + 0xF9D8, + 0xF9D9, + 0xF9DA, + 0xF9DB, + 0xF9DC, + 0xF9DD, + 0xF9DE, + 0xF9DF, + 0xF9E0, + 0xF9E1, + 0xF9E2, + 0xF9E3, + 0xF9E4, + 0xF9E5, + 0xF9E6, + 0xF9E7, + 0xF9E8, + 0xF9E9, + 0xF9EA, + 0xF9EB, + 0xF9EC, + 0xF9ED, + 0xF9EE, + 0xF9EF, + 0xF9F0, + 0xF9F1, + 0xF9F2, + 0xF9F3, + 0xF9F4, + 0xF9F5, + 0xF9F6, + 0xF9F7, + 0xF9F8, + 0xF9F9, + 0xF9FA, + 0xF9FB, + 0xF9FC, + 0xF9FD, + 0xF9FE, + 0xF9FF, + 0xFA00, + 0xFA01, + 0xFA02, + 0xFA03, + 0xFA04, + 0xFA05, + 0xFA06, + 0xFA07, + 0xFA08, + 0xFA09, + 0xFA0A, + 0xFA0B, + 0xFA0C, + 0xFA0D, + 0xFA0E, + 0xFA10, + 0xFA11, + 0xFA12, + 0xFA13, + 0xFA15, + 0xFA16, + 0xFA17, + 0xFA18, + 0xFA19, + 0xFA1A, + 0xFA1B, + 0xFA1C, + 0xFA1D, + 0xFA1E, + 0xFA1F, + 0xFA20, + 0xFA21, + 0xFA22, + 0xFA23, + 0xFA25, + 0xFA26, + 0xFA27, + 0xFA2A, + 0xFA2B, + 0xFA2C, + 0xFA2D, + 0xFA2E, + 0xFA2F, + 0xFA30, + 0xFA31, + 0xFA32, + 0xFA33, + 0xFA34, + 0xFA35, + 0xFA36, + 0xFA37, + 0xFA38, + 0xFA39, + 0xFA3A, + 0xFA3B, + 0xFA3C, + 0xFA3D, + 0xFA3E, + 0xFA3F, + 0xFA40, + 0xFA41, + 0xFA42, + 0xFA43, + 0xFA44, + 0xFA45, + 0xFA46, + 0xFA47, + 0xFA48, + 0xFA49, + 0xFA4A, + 0xFA4B, + 0xFA4C, + 0xFA4D, + 0xFA4E, + 0xFA4F, + 0xFA50, + 0xFA51, + 0xFA52, + 0xFA53, + 0xFA54, + 0xFA55, + 0xFA56, + 0xFA57, + 0xFA58, + 0xFA59, + 0xFA5A, + 0xFA5B, + 0xFA5C, + 0xFA5D, + 0xFA5F, + 0xFA60, + 0xFA61, + 0xFA62, + 0xFA63, + 0xFA64, + 0xFA65, + 0xFA66, + 0xFA67, + 0xFA68, + 0xFA69, + 0xFA6A, + 0xFA6B, + 0xFA6C, + 0xFA6D, + 0xFA6E, + 0xFA70, + 0xFA71, + 0xFA72, + 0xFA73, + 0xFA74, + 0xFA75, + 0xFA76, + 0xFA77, + 0xFA78, + 0xFA79, + 0xFA7A, + 0xFA7B, + 0xFA7C, + 0xFA7D, + 0xFA7E, + 0xFA7F, + 0xFA80, + 0xFA81, + 0xFA82, + 0xFA83, + 0xFA84, + 0xFA85, + 0xFA86, + 0xFA87, + 0xFA88, + 0xFA89, + 0xFA8A, + 0xFA8B, + 0xFA8C, + 0xFA8D, + 0xFA8E, + 0xFA8F, + 0xFA90, + 0xFA91, + 0xFA92, + 0xFA93, + 0xFA94, + 0xFA95, + 0xFA96, + 0xFA97, + 0xFA98, + 0xFA99, + 0xFA9A, + 0xFA9B, + 0xFA9C, + 0xFA9D, + 0xFA9E, + 0xFA9F, + 0xFAA0, + 0xFAA1, + 0xFAA2, + 0xFAA3, + 0xFAA4, + 0xFAA5, + 0xFAA6, + 0xFAA7, + 0xFAA8, + 0xFAA9, + 0xFAAA, + 0xFAAB, + 0xFAAC, + 0xFAAD, + 0xFAAE, + 0xFAAF, + 0xFAB0, + 0xFAB1, + 0xFAB2, + 0xFAB3, + 0xFAB4, + 0xFAB5, + 0xFAB6, + 0xFAB7, + 0xFAB8, + 0xFAB9, + 0xFABA, + 0xFABB, + 0xFABC, + 0xFABD, + 0xFABE, + 0xFABF, + 0xFAC0, + 0xFAC1, + 0xFAC2, + 0xFAC3, + 0xFAC4, + 0xFAC5, + 0xFAC6, + 0xFAC7, + 0xFAC8, + 0xFAC9, + 0xFACA, + 0xFACB, + 0xFACC, + 0xFACD, + 0xFACE, + 0xFACF, + 0xFAD0, + 0xFAD1, + 0xFAD2, + 0xFAD3, + 0xFAD4, + 0xFAD5, + 0xFAD6, + 0xFAD7, + 0xFAD8, + 0xFAD9, + 0xFADA, + 0xFB00, + 0xFB01, + 0xFB02, + 0xFB03, + 0xFB04, + 0xFB05, + 0xFB07, + 0xFB13, + 0xFB14, + 0xFB15, + 0xFB16, + 0xFB17, + 0xFB18, + 0xFB1D, + 0xFB1E, + 0xFB1F, + 0xFB20, + 0xFB21, + 0xFB22, + 0xFB23, + 0xFB24, + 0xFB25, + 0xFB26, + 0xFB27, + 0xFB28, + 0xFB29, + 0xFB2A, + 0xFB2B, + 0xFB2C, + 0xFB2D, + 0xFB2E, + 0xFB2F, + 0xFB30, + 0xFB31, + 0xFB32, + 0xFB33, + 0xFB34, + 0xFB35, + 0xFB36, + 0xFB37, + 0xFB38, + 0xFB39, + 0xFB3A, + 0xFB3B, + 0xFB3C, + 0xFB3D, + 0xFB3E, + 0xFB3F, + 0xFB40, + 0xFB41, + 0xFB42, + 0xFB43, + 0xFB44, + 0xFB45, + 0xFB46, + 0xFB47, + 0xFB48, + 0xFB49, + 0xFB4A, + 0xFB4B, + 0xFB4C, + 0xFB4D, + 0xFB4E, + 0xFB4F, + 0xFB50, + 0xFB52, + 0xFB56, + 0xFB5A, + 0xFB5E, + 0xFB62, + 0xFB66, + 0xFB6A, + 0xFB6E, + 0xFB72, + 0xFB76, + 0xFB7A, + 0xFB7E, + 0xFB82, + 0xFB84, + 0xFB86, + 0xFB88, + 0xFB8A, + 0xFB8C, + 0xFB8E, + 0xFB92, + 0xFB96, + 0xFB9A, + 0xFB9E, + 0xFBA0, + 0xFBA4, + 0xFBA6, + 0xFBAA, + 0xFBAE, + 0xFBB0, + 0xFBB2, + 0xFBD3, + 0xFBD7, + 0xFBD9, + 0xFBDB, + 0xFBDD, + 0xFBDE, + 0xFBE0, + 0xFBE2, + 0xFBE4, + 0xFBE8, + 0xFBEA, + 0xFBEC, + 0xFBEE, + 0xFBF0, + 0xFBF2, + 0xFBF4, + 0xFBF6, + 0xFBF9, + 0xFBFC, + 0xFC00, + 0xFC01, + 0xFC02, + 0xFC03, + 0xFC04, + 0xFC05, + 0xFC06, + 0xFC07, + 0xFC08, + 0xFC09, + 0xFC0A, + 0xFC0B, + 0xFC0C, + 0xFC0D, + 0xFC0E, + 0xFC0F, + 0xFC10, + 0xFC11, + 0xFC12, + 0xFC13, + 0xFC14, + 0xFC15, + 0xFC16, + 0xFC17, + 0xFC18, + 0xFC19, + 0xFC1A, + 0xFC1B, + 0xFC1C, + 0xFC1D, + 0xFC1E, + 0xFC1F, + 0xFC20, + 0xFC21, + 0xFC22, + 0xFC23, + 0xFC24, + 0xFC25, + 0xFC26, + 0xFC27, + 0xFC28, + 0xFC29, + 0xFC2A, + 0xFC2B, + 0xFC2C, + 0xFC2D, + 0xFC2E, + 0xFC2F, + 0xFC30, + 0xFC31, + 0xFC32, + 0xFC33, + 0xFC34, + 0xFC35, + 0xFC36, + 0xFC37, + 0xFC38, + 0xFC39, + 0xFC3A, + 0xFC3B, + 0xFC3C, + 0xFC3D, + 0xFC3E, + 0xFC3F, + 0xFC40, + 0xFC41, + 0xFC42, + 0xFC43, + 0xFC44, + 0xFC45, + 0xFC46, + 0xFC47, + 0xFC48, + 0xFC49, + 0xFC4A, + 0xFC4B, + 0xFC4C, + 0xFC4D, + 0xFC4E, + 0xFC4F, + 0xFC50, + 0xFC51, + 0xFC52, + 0xFC53, + 0xFC54, + 0xFC55, + 0xFC56, + 0xFC57, + 0xFC58, + 0xFC59, + 0xFC5A, + 0xFC5B, + 0xFC5C, + 0xFC5D, + 0xFC5E, + 0xFC5F, + 0xFC60, + 0xFC61, + 0xFC62, + 0xFC63, + 0xFC64, + 0xFC65, + 0xFC66, + 0xFC67, + 0xFC68, + 0xFC69, + 0xFC6A, + 0xFC6B, + 0xFC6C, + 0xFC6D, + 0xFC6E, + 0xFC6F, + 0xFC70, + 0xFC71, + 0xFC72, + 0xFC73, + 0xFC74, + 0xFC75, + 0xFC76, + 0xFC77, + 0xFC78, + 0xFC79, + 0xFC7A, + 0xFC7B, + 0xFC7C, + 0xFC7D, + 0xFC7E, + 0xFC7F, + 0xFC80, + 0xFC81, + 0xFC82, + 0xFC83, + 0xFC84, + 0xFC85, + 0xFC86, + 0xFC87, + 0xFC88, + 0xFC89, + 0xFC8A, + 0xFC8B, + 0xFC8C, + 0xFC8D, + 0xFC8E, + 0xFC8F, + 0xFC90, + 0xFC91, + 0xFC92, + 0xFC93, + 0xFC94, + 0xFC95, + 0xFC96, + 0xFC97, + 0xFC98, + 0xFC99, + 0xFC9A, + 0xFC9B, + 0xFC9C, + 0xFC9D, + 0xFC9E, + 0xFC9F, + 0xFCA0, + 0xFCA1, + 0xFCA2, + 0xFCA3, + 0xFCA4, + 0xFCA5, + 0xFCA6, + 0xFCA7, + 0xFCA8, + 0xFCA9, + 0xFCAA, + 0xFCAB, + 0xFCAC, + 0xFCAD, + 0xFCAE, + 0xFCAF, + 0xFCB0, + 0xFCB1, + 0xFCB2, + 0xFCB3, + 0xFCB4, + 0xFCB5, + 0xFCB6, + 0xFCB7, + 0xFCB8, + 0xFCB9, + 0xFCBA, + 0xFCBB, + 0xFCBC, + 0xFCBD, + 0xFCBE, + 0xFCBF, + 0xFCC0, + 0xFCC1, + 0xFCC2, + 0xFCC3, + 0xFCC4, + 0xFCC5, + 0xFCC6, + 0xFCC7, + 0xFCC8, + 0xFCC9, + 0xFCCA, + 0xFCCB, + 0xFCCC, + 0xFCCD, + 0xFCCE, + 0xFCCF, + 0xFCD0, + 0xFCD1, + 0xFCD2, + 0xFCD3, + 0xFCD4, + 0xFCD5, + 0xFCD6, + 0xFCD7, + 0xFCD8, + 0xFCD9, + 0xFCDA, + 0xFCDB, + 0xFCDC, + 0xFCDD, + 0xFCDE, + 0xFCDF, + 0xFCE0, + 0xFCE1, + 0xFCE2, + 0xFCE3, + 0xFCE4, + 0xFCE5, + 0xFCE6, + 0xFCE7, + 0xFCE8, + 0xFCE9, + 0xFCEA, + 0xFCEB, + 0xFCEC, + 0xFCED, + 0xFCEE, + 0xFCEF, + 0xFCF0, + 0xFCF1, + 0xFCF2, + 0xFCF3, + 0xFCF4, + 0xFCF5, + 0xFCF6, + 0xFCF7, + 0xFCF8, + 0xFCF9, + 0xFCFA, + 0xFCFB, + 0xFCFC, + 0xFCFD, + 0xFCFE, + 0xFCFF, + 0xFD00, + 0xFD01, + 0xFD02, + 0xFD03, + 0xFD04, + 0xFD05, + 0xFD06, + 0xFD07, + 0xFD08, + 0xFD09, + 0xFD0A, + 0xFD0B, + 0xFD0C, + 0xFD0D, + 0xFD0E, + 0xFD0F, + 0xFD10, + 0xFD11, + 0xFD12, + 0xFD13, + 0xFD14, + 0xFD15, + 0xFD16, + 0xFD17, + 0xFD18, + 0xFD19, + 0xFD1A, + 0xFD1B, + 0xFD1C, + 0xFD1D, + 0xFD1E, + 0xFD1F, + 0xFD20, + 0xFD21, + 0xFD22, + 0xFD23, + 0xFD24, + 0xFD25, + 0xFD26, + 0xFD27, + 0xFD28, + 0xFD29, + 0xFD2A, + 0xFD2B, + 0xFD2C, + 0xFD2D, + 0xFD2E, + 0xFD2F, + 0xFD30, + 0xFD31, + 0xFD32, + 0xFD33, + 0xFD34, + 0xFD35, + 0xFD36, + 0xFD37, + 0xFD38, + 0xFD39, + 0xFD3A, + 0xFD3B, + 0xFD3C, + 0xFD3E, + 0xFD50, + 0xFD51, + 0xFD53, + 0xFD54, + 0xFD55, + 0xFD56, + 0xFD57, + 0xFD58, + 0xFD5A, + 0xFD5B, + 0xFD5C, + 0xFD5D, + 0xFD5E, + 0xFD5F, + 0xFD61, + 0xFD62, + 0xFD64, + 0xFD66, + 0xFD67, + 0xFD69, + 0xFD6A, + 0xFD6C, + 0xFD6E, + 0xFD6F, + 0xFD71, + 0xFD73, + 0xFD74, + 0xFD75, + 0xFD76, + 0xFD78, + 0xFD79, + 0xFD7A, + 0xFD7B, + 0xFD7C, + 0xFD7E, + 0xFD7F, + 0xFD80, + 0xFD81, + 0xFD82, + 0xFD83, + 0xFD85, + 0xFD87, + 0xFD89, + 0xFD8A, + 0xFD8B, + 0xFD8C, + 0xFD8D, + 0xFD8E, + 0xFD8F, + 0xFD90, + 0xFD92, + 0xFD93, + 0xFD94, + 0xFD95, + 0xFD96, + 0xFD97, + 0xFD99, + 0xFD9A, + 0xFD9B, + 0xFD9C, + 0xFD9E, + 0xFD9F, + 0xFDA0, + 0xFDA1, + 0xFDA2, + 0xFDA3, + 0xFDA4, + 0xFDA5, + 0xFDA6, + 0xFDA7, + 0xFDA8, + 0xFDA9, + 0xFDAA, + 0xFDAB, + 0xFDAC, + 0xFDAD, + 0xFDAE, + 0xFDAF, + 0xFDB0, + 0xFDB1, + 0xFDB2, + 0xFDB3, + 0xFDB4, + 0xFDB5, + 0xFDB6, + 0xFDB7, + 0xFDB8, + 0xFDB9, + 0xFDBA, + 0xFDBB, + 0xFDBC, + 0xFDBD, + 0xFDBE, + 0xFDBF, + 0xFDC0, + 0xFDC1, + 0xFDC2, + 0xFDC3, + 0xFDC4, + 0xFDC5, + 0xFDC6, + 0xFDC7, + 0xFDC8, + 0xFDD0, + 0xFDF0, + 0xFDF1, + 0xFDF2, + 0xFDF3, + 0xFDF4, + 0xFDF5, + 0xFDF6, + 0xFDF7, + 0xFDF8, + 0xFDF9, + 0xFDFA, + 0xFDFB, + 0xFDFC, + 0xFDFD, + 0xFE00, + 0xFE10, + 0xFE11, + 0xFE12, + 0xFE13, + 0xFE14, + 0xFE15, + 0xFE16, + 0xFE17, + 0xFE18, + 0xFE19, + 0xFE20, + 0xFE30, + 0xFE31, + 0xFE32, + 0xFE33, + 0xFE35, + 0xFE36, + 0xFE37, + 0xFE38, + 0xFE39, + 0xFE3A, + 0xFE3B, + 0xFE3C, + 0xFE3D, + 0xFE3E, + 0xFE3F, + 0xFE40, + 0xFE41, + 0xFE42, + 0xFE43, + 0xFE44, + 0xFE45, + 0xFE47, + 0xFE48, + 0xFE49, + 0xFE4D, + 0xFE50, + 0xFE51, + 0xFE52, + 0xFE54, + 0xFE55, + 0xFE56, + 0xFE57, + 0xFE58, + 0xFE59, + 0xFE5A, + 0xFE5B, + 0xFE5C, + 0xFE5D, + 0xFE5E, + 0xFE5F, + 0xFE60, + 0xFE61, + 0xFE62, + 0xFE63, + 0xFE64, + 0xFE65, + 0xFE66, + 0xFE67, + 0xFE68, + 0xFE69, + 0xFE6A, + 0xFE6B, + 0xFE6C, + 0xFE70, + 0xFE71, + 0xFE72, + 0xFE73, + 0xFE74, + 0xFE75, + 0xFE76, + 0xFE77, + 0xFE78, + 0xFE79, + 0xFE7A, + 0xFE7B, + 0xFE7C, + 0xFE7D, + 0xFE7E, + 0xFE7F, + 0xFE80, + 0xFE81, + 0xFE83, + 0xFE85, + 0xFE87, + 0xFE89, + 0xFE8D, + 0xFE8F, + 0xFE93, + 0xFE95, + 0xFE99, + 0xFE9D, + 0xFEA1, + 0xFEA5, + 0xFEA9, + 0xFEAB, + 0xFEAD, + 0xFEAF, + 0xFEB1, + 0xFEB5, + 0xFEB9, + 0xFEBD, + 0xFEC1, + 0xFEC5, + 0xFEC9, + 0xFECD, + 0xFED1, + 0xFED5, + 0xFED9, + 0xFEDD, + 0xFEE1, + 0xFEE5, + 0xFEE9, + 0xFEED, + 0xFEEF, + 0xFEF1, + 0xFEF5, + 0xFEF7, + 0xFEF9, + 0xFEFB, + 0xFEFD, + 0xFEFF, + 0xFF00, + 0xFF01, + 0xFF02, + 0xFF03, + 0xFF04, + 0xFF05, + 0xFF06, + 0xFF07, + 0xFF08, + 0xFF09, + 0xFF0A, + 0xFF0B, + 0xFF0C, + 0xFF0D, + 0xFF0E, + 0xFF0F, + 0xFF10, + 0xFF11, + 0xFF12, + 0xFF13, + 0xFF14, + 0xFF15, + 0xFF16, + 0xFF17, + 0xFF18, + 0xFF19, + 0xFF1A, + 0xFF1B, + 0xFF1C, + 0xFF1D, + 0xFF1E, + 0xFF1F, + 0xFF20, + 0xFF21, + 0xFF22, + 0xFF23, + 0xFF24, + 0xFF25, + 0xFF26, + 0xFF27, + 0xFF28, + 0xFF29, + 0xFF2A, + 0xFF2B, + 0xFF2C, + 0xFF2D, + 0xFF2E, + 0xFF2F, + 0xFF30, + 0xFF31, + 0xFF32, + 0xFF33, + 0xFF34, + 0xFF35, + 0xFF36, + 0xFF37, + 0xFF38, + 0xFF39, + 0xFF3A, + 0xFF3B, + 0xFF3C, + 0xFF3D, + 0xFF3E, + 0xFF3F, + 0xFF40, + 0xFF41, + 0xFF42, + 0xFF43, + 0xFF44, + 0xFF45, + 0xFF46, + 0xFF47, + 0xFF48, + 0xFF49, + 0xFF4A, + 0xFF4B, + 0xFF4C, + 0xFF4D, + 0xFF4E, + 0xFF4F, + 0xFF50, + 0xFF51, + 0xFF52, + 0xFF53, + 0xFF54, + 0xFF55, + 0xFF56, + 0xFF57, + 0xFF58, + 0xFF59, + 0xFF5A, + 0xFF5B, + 0xFF5C, + 0xFF5D, + 0xFF5E, + 0xFF5F, + 0xFF60, + 0xFF61, + 0xFF62, + 0xFF63, + 0xFF64, + 0xFF65, + 0xFF66, + 0xFF67, + 0xFF68, + 0xFF69, + 0xFF6A, + 0xFF6B, + 0xFF6C, + 0xFF6D, + 0xFF6E, + 0xFF6F, + 0xFF70, + 0xFF71, + 0xFF72, + 0xFF73, + 0xFF74, + 0xFF75, + 0xFF76, + 0xFF77, + 0xFF78, + 0xFF79, + 0xFF7A, + 0xFF7B, + 0xFF7C, + 0xFF7D, + 0xFF7E, + 0xFF7F, + 0xFF80, + 0xFF81, + 0xFF82, + 0xFF83, + 0xFF84, + 0xFF85, + 0xFF86, + 0xFF87, + 0xFF88, + 0xFF89, + 0xFF8A, + 0xFF8B, + 0xFF8C, + 0xFF8D, + 0xFF8E, + 0xFF8F, + 0xFF90, + 0xFF91, + 0xFF92, + 0xFF93, + 0xFF94, + 0xFF95, + 0xFF96, + 0xFF97, + 0xFF98, + 0xFF99, + 0xFF9A, + 0xFF9B, + 0xFF9C, + 0xFF9D, + 0xFF9E, + 0xFF9F, + 0xFFA0, + 0xFFA1, + 0xFFA2, + 0xFFA3, + 0xFFA4, + 0xFFA5, + 0xFFA6, + 0xFFA7, + 0xFFA8, + 0xFFA9, + 0xFFAA, + 0xFFAB, + 0xFFAC, + 0xFFAD, + 0xFFAE, + 0xFFAF, + 0xFFB0, + 0xFFB1, + 0xFFB2, + 0xFFB3, + 0xFFB4, + 0xFFB5, + 0xFFB6, + 0xFFB7, + 0xFFB8, + 0xFFB9, + 0xFFBA, + 0xFFBB, + 0xFFBC, + 0xFFBD, + 0xFFBE, + 0xFFBF, + 0xFFC2, + 0xFFC3, + 0xFFC4, + 0xFFC5, + 0xFFC6, + 0xFFC7, + 0xFFC8, + 0xFFCA, + 0xFFCB, + 0xFFCC, + 0xFFCD, + 0xFFCE, + 0xFFCF, + 0xFFD0, + 0xFFD2, + 0xFFD3, + 0xFFD4, + 0xFFD5, + 0xFFD6, + 0xFFD7, + 0xFFD8, + 0xFFDA, + 0xFFDB, + 0xFFDC, + 0xFFDD, + 0xFFE0, + 0xFFE1, + 0xFFE2, + 0xFFE3, + 0xFFE4, + 0xFFE5, + 0xFFE6, + 0xFFE7, + 0xFFE8, + 0xFFE9, + 0xFFEA, + 0xFFEB, + 0xFFEC, + 0xFFED, + 0xFFEE, + 0xFFEF, + 0x10000, + 0x1000C, + 0x1000D, + 0x10027, + 0x10028, + 0x1003B, + 0x1003C, + 0x1003E, + 0x1003F, + 0x1004E, + 0x10050, + 0x1005E, + 0x10080, + 0x100FB, + 0x10100, + 0x10103, + 0x10107, + 0x10134, + 0x10137, + 0x1018F, + 0x10190, + 0x1019D, + 0x101A0, + 0x101A1, + 0x101D0, + 0x101FE, + 0x10280, + 0x1029D, + 0x102A0, + 0x102D1, + 0x102E0, + 0x102FC, + 0x10300, + 0x10324, + 0x1032D, + 0x1034B, + 0x10350, + 0x1037B, + 0x10380, + 0x1039E, + 0x1039F, + 0x103C4, + 0x103C8, + 0x103D6, + 0x10400, + 0x10401, + 0x10402, + 0x10403, + 0x10404, + 0x10405, + 0x10406, + 0x10407, + 0x10408, + 0x10409, + 0x1040A, + 0x1040B, + 0x1040C, + 0x1040D, + 0x1040E, + 0x1040F, + 0x10410, + 0x10411, + 0x10412, + 0x10413, + 0x10414, + 0x10415, + 0x10416, + 0x10417, + 0x10418, + 0x10419, + 0x1041A, + 0x1041B, + 0x1041C, + 0x1041D, + 0x1041E, + 0x1041F, + 0x10420, + 0x10421, + 0x10422, + 0x10423, + 0x10424, + 0x10425, + 0x10426, + 0x10427, + 0x10428, + 0x1049E, + 0x104A0, + 0x104AA, + 0x104B0, + 0x104B1, + 0x104B2, + 0x104B3, + 0x104B4, + 0x104B5, + 0x104B6, + 0x104B7, + 0x104B8, + 0x104B9, + 0x104BA, + 0x104BB, + 0x104BC, + 0x104BD, + 0x104BE, + 0x104BF, + 0x104C0, + 0x104C1, + 0x104C2, + 0x104C3, + 0x104C4, + 0x104C5, + 0x104C6, + 0x104C7, + 0x104C8, + 0x104C9, + 0x104CA, + 0x104CB, + 0x104CC, + 0x104CD, + 0x104CE, + 0x104CF, + 0x104D0, + 0x104D1, + 0x104D2, + 0x104D3, + 0x104D4, + 0x104D8, + 0x104FC, + 0x10500, + 0x10528, + 0x10530, + 0x10564, + 0x1056F, + 0x10570, + 0x10571, + 0x10572, + 0x10573, + 0x10574, + 0x10575, + 0x10576, + 0x10577, + 0x10578, + 0x10579, + 0x1057A, + 0x1057B, + 0x1057C, + 0x1057D, + 0x1057E, + 0x1057F, + 0x10580, + 0x10581, + 0x10582, + 0x10583, + 0x10584, + 0x10585, + 0x10586, + 0x10587, + 0x10588, + 0x10589, + 0x1058A, + 0x1058B, + 0x1058C, + 0x1058D, + 0x1058E, + 0x1058F, + 0x10590, + 0x10591, + 0x10592, + 0x10593, + 0x10594, + 0x10595, + 0x10596, + 0x10597, + 0x105A2, + 0x105A3, + 0x105B2, + 0x105B3, + 0x105BA, + 0x105BB, + 0x105BD, + 0x105C0, + 0x105F4, + 0x10600, + 0x10737, + 0x10740, + 0x10756, + 0x10760, + 0x10768, + 0x10780, + 0x10781, + 0x10782, + 0x10783, + 0x10784, + 0x10785, + 0x10786, + 0x10787, + 0x10788, + 0x10789, + 0x1078A, + 0x1078B, + 0x1078C, + 0x1078D, + 0x1078E, + 0x1078F, + 0x10790, + 0x10791, + 0x10792, + 0x10793, + 0x10794, + 0x10795, + 0x10796, + 0x10797, + 0x10798, + 0x10799, + 0x1079A, + 0x1079B, + 0x1079C, + 0x1079D, + 0x1079E, + 0x1079F, + 0x107A0, + 0x107A1, + 0x107A2, + 0x107A3, + 0x107A4, + 0x107A5, + 0x107A6, + 0x107A7, + 0x107A8, + 0x107A9, + 0x107AA, + 0x107AB, + 0x107AC, + 0x107AD, + 0x107AE, + 0x107AF, + 0x107B0, + 0x107B1, + 0x107B2, + 0x107B3, + 0x107B4, + 0x107B5, + 0x107B6, + 0x107B7, + 0x107B8, + 0x107B9, + 0x107BA, + 0x107BB, + 0x107BC, + 0x107BD, + 0x107BE, + 0x107BF, + 0x107C0, + 0x10800, + 0x10806, + 0x10808, + 0x10809, + 0x1080A, + 0x10836, + 0x10837, + 0x10839, + 0x1083C, + 0x1083D, + 0x1083F, + 0x10856, + 0x10857, + 0x1089F, + 0x108A7, + 0x108B0, + 0x108E0, + 0x108F3, + 0x108F4, + 0x108F6, + 0x108FB, + 0x1091C, + 0x1091F, + 0x1093A, + 0x1093F, + 0x1095A, + 0x10980, + 0x109B8, + 0x109BC, + 0x109D0, + 0x109D2, + 0x10A04, + 0x10A05, + 0x10A07, + 0x10A0C, + 0x10A14, + 0x10A15, + 0x10A18, + 0x10A19, + 0x10A36, + 0x10A38, + 0x10A3B, + 0x10A3F, + 0x10A49, + 0x10A50, + 0x10A59, + 0x10A60, + 0x10AA0, + 0x10AC0, + 0x10AE7, + 0x10AEB, + 0x10AF7, + 0x10B00, + 0x10B36, + 0x10B39, + 0x10B56, + 0x10B58, + 0x10B73, + 0x10B78, + 0x10B92, + 0x10B99, + 0x10B9D, + 0x10BA9, + 0x10BB0, + 0x10C00, + 0x10C49, + 0x10C80, + 0x10C81, + 0x10C82, + 0x10C83, + 0x10C84, + 0x10C85, + 0x10C86, + 0x10C87, + 0x10C88, + 0x10C89, + 0x10C8A, + 0x10C8B, + 0x10C8C, + 0x10C8D, + 0x10C8E, + 0x10C8F, + 0x10C90, + 0x10C91, + 0x10C92, + 0x10C93, + 0x10C94, + 0x10C95, + 0x10C96, + 0x10C97, + 0x10C98, + 0x10C99, + 0x10C9A, + 0x10C9B, + 0x10C9C, + 0x10C9D, + 0x10C9E, + 0x10C9F, + 0x10CA0, + 0x10CA1, + 0x10CA2, + 0x10CA3, + 0x10CA4, + 0x10CA5, + 0x10CA6, + 0x10CA7, + 0x10CA8, + 0x10CA9, + 0x10CAA, + 0x10CAB, + 0x10CAC, + 0x10CAD, + 0x10CAE, + 0x10CAF, + 0x10CB0, + 0x10CB1, + 0x10CB2, + 0x10CB3, + 0x10CC0, + 0x10CF3, + 0x10CFA, + 0x10D28, + 0x10D30, + 0x10D3A, + 0x10D40, + 0x10D50, + 0x10D51, + 0x10D52, + 0x10D53, + 0x10D54, + 0x10D55, + 0x10D56, + 0x10D57, + 0x10D58, + 0x10D59, + 0x10D5A, + 0x10D5B, + 0x10D5C, + 0x10D5D, + 0x10D5E, + 0x10D5F, + 0x10D60, + 0x10D61, + 0x10D62, + 0x10D63, + 0x10D64, + 0x10D65, + 0x10D66, + 0x10D69, + 0x10D86, + 0x10D8E, + 0x10D90, + 0x10E60, + 0x10E7F, + 0x10E80, + 0x10EAA, + 0x10EAB, + 0x10EAE, + 0x10EB0, + 0x10EB2, + 0x10EC2, + 0x10EC8, + 0x10EC9, + 0x10EEF, + 0x10EF0, + 0x10F28, + 0x10F30, + 0x10F5A, + 0x10F70, + 0x10F8A, + 0x10FB0, + 0x10FCC, + 0x10FE0, + 0x10FF7, + 0x11000, + 0x1104E, + 0x11052, + 0x11076, + 0x1107F, + 0x110BD, + 0x110BE, + 0x110C3, + 0x110D0, + 0x110E9, + 0x110F0, + 0x110FA, + 0x11100, + 0x11135, + 0x11136, + 0x11148, + 0x11150, + 0x11177, + 0x11180, + 0x111E0, + 0x111E1, + 0x111F5, + 0x11200, + 0x11212, + 0x11213, + 0x11242, + 0x11280, + 0x11287, + 0x11288, + 0x11289, + 0x1128A, + 0x1128E, + 0x1128F, + 0x1129E, + 0x1129F, + 0x112AA, + 0x112B0, + 0x112EB, + 0x112F0, + 0x112FA, + 0x11300, + 0x11304, + 0x11305, + 0x1130D, + 0x1130F, + 0x11311, + 0x11313, + 0x11329, + 0x1132A, + 0x11331, + 0x11332, + 0x11334, + 0x11335, + 0x1133A, + 0x1133B, + 0x11345, + 0x11347, + 0x11349, + 0x1134B, + 0x1134E, + 0x11350, + 0x11351, + 0x11357, + 0x11358, + 0x1135D, + 0x11364, + 0x11366, + 0x1136D, + 0x11370, + 0x11375, + 0x11380, + 0x1138A, + 0x1138B, + 0x1138C, + 0x1138E, + 0x1138F, + 0x11390, + 0x113B6, + 0x113B7, + 0x113C1, + 0x113C2, + 0x113C3, + 0x113C5, + 0x113C6, + 0x113C7, + 0x113CB, + 0x113CC, + 0x113D6, + 0x113D7, + 0x113D9, + 0x113E1, + 0x113E3, + 0x11400, + 0x1145C, + 0x1145D, + 0x11462, + 0x11480, + 0x114C8, + 0x114D0, + 0x114DA, + 0x11580, + 0x115B6, + 0x115B8, + 0x115DE, + 0x11600, + 0x11645, + 0x11650, + 0x1165A, + 0x11660, + 0x1166D, + 0x11680, + 0x116BA, + 0x116C0, + 0x116CA, + 0x116D0, + 0x116E4, + 0x11700, + 0x1171B, + 0x1171D, + 0x1172C, + 0x11730, + 0x11747, + 0x11800, + 0x1183C, + 0x118A0, + 0x118A1, + 0x118A2, + 0x118A3, + 0x118A4, + 0x118A5, + 0x118A6, + 0x118A7, + 0x118A8, + 0x118A9, + 0x118AA, + 0x118AB, + 0x118AC, + 0x118AD, + 0x118AE, + 0x118AF, + 0x118B0, + 0x118B1, + 0x118B2, + 0x118B3, + 0x118B4, + 0x118B5, + 0x118B6, + 0x118B7, + 0x118B8, + 0x118B9, + 0x118BA, + 0x118BB, + 0x118BC, + 0x118BD, + 0x118BE, + 0x118BF, + 0x118C0, + 0x118F3, + 0x118FF, + 0x11907, + 0x11909, + 0x1190A, + 0x1190C, + 0x11914, + 0x11915, + 0x11917, + 0x11918, + 0x11936, + 0x11937, + 0x11939, + 0x1193B, + 0x11947, + 0x11950, + 0x1195A, + 0x119A0, + 0x119A8, + 0x119AA, + 0x119D8, + 0x119DA, + 0x119E5, + 0x11A00, + 0x11A48, + 0x11A50, + 0x11AA3, + 0x11AB0, + 0x11AF9, + 0x11B00, + 0x11B0B, + 0x11B60, + 0x11B68, + 0x11BC0, + 0x11BE2, + 0x11BF0, + 0x11BFA, + 0x11C00, + 0x11C09, + 0x11C0A, + 0x11C37, + 0x11C38, + 0x11C46, + 0x11C50, + 0x11C6D, + 0x11C70, + 0x11C90, + 0x11C92, + 0x11CA8, + 0x11CA9, + 0x11CB7, + 0x11D00, + 0x11D07, + 0x11D08, + 0x11D0A, + 0x11D0B, + 0x11D37, + 0x11D3A, + 0x11D3B, + 0x11D3C, + 0x11D3E, + 0x11D3F, + 0x11D48, + 0x11D50, + 0x11D5A, + 0x11D60, + 0x11D66, + 0x11D67, + 0x11D69, + 0x11D6A, + 0x11D8F, + 0x11D90, + 0x11D92, + 0x11D93, + 0x11D99, + 0x11DA0, + 0x11DAA, + 0x11DB0, + 0x11DDC, + 0x11DE0, + 0x11DEA, + 0x11DF0, + 0x11DF2, + 0x11EE0, + 0x11EF9, + 0x11F00, + 0x11F11, + 0x11F12, + 0x11F3B, + 0x11F3E, + 0x11F5B, + 0x11FB0, + 0x11FB1, + 0x11FC0, + 0x11FF2, + 0x11FFF, + 0x1239A, + 0x12400, + 0x12544, + 0x12550, + 0x12687, + 0x12F90, + 0x12FF3, + 0x13000, + 0x13430, + 0x13440, + 0x13456, + 0x13460, + 0x143FB, + 0x14400, + 0x14647, + 0x16100, + 0x1613A, + 0x16800, + 0x16A39, + 0x16A40, + 0x16A5F, + 0x16A60, + 0x16A6A, + 0x16A6E, + 0x16ABF, + 0x16AC0, + 0x16ACA, + 0x16AD0, + 0x16AEE, + 0x16AF0, + 0x16AF6, + 0x16B00, + 0x16B46, + 0x16B50, + 0x16B5A, + 0x16B5B, + 0x16B62, + 0x16B63, + 0x16B78, + 0x16B7D, + 0x16B90, + 0x16D40, + 0x16D7A, + 0x16E40, + 0x16E41, + 0x16E42, + 0x16E43, + 0x16E44, + 0x16E45, + 0x16E46, + 0x16E47, + 0x16E48, + 0x16E49, + 0x16E4A, + 0x16E4B, + 0x16E4C, + 0x16E4D, + 0x16E4E, + 0x16E4F, + 0x16E50, + 0x16E51, + 0x16E52, + 0x16E53, + 0x16E54, + 0x16E55, + 0x16E56, + 0x16E57, + 0x16E58, + 0x16E59, + 0x16E5A, + 0x16E5B, + 0x16E5C, + 0x16E5D, + 0x16E5E, + 0x16E5F, + 0x16E60, + 0x16E9B, + 0x16EA0, + 0x16EA1, + 0x16EA2, + 0x16EA3, + 0x16EA4, + 0x16EA5, + 0x16EA6, + 0x16EA7, + 0x16EA8, + 0x16EA9, + 0x16EAA, + 0x16EAB, + 0x16EAC, + 0x16EAD, + 0x16EAE, + 0x16EAF, + 0x16EB0, + 0x16EB1, + 0x16EB2, + 0x16EB3, + 0x16EB4, + 0x16EB5, + 0x16EB6, + 0x16EB7, + 0x16EB8, + 0x16EB9, + 0x16EBB, + 0x16ED4, + 0x16F00, + 0x16F4B, + 0x16F4F, + 0x16F88, + 0x16F8F, + 0x16FA0, + 0x16FE0, + 0x16FE5, + 0x16FF0, + 0x16FF7, + 0x17000, + 0x18CDB, + 0x18CFF, + 0x18D21, + 0x18D80, + 0x18DF3, + 0x18E00, + 0x19192, + 0x191A0, + 0x191D3, + 0x1AFF0, + 0x1AFF4, + 0x1AFF5, + 0x1AFFC, + 0x1AFFD, + 0x1AFFF, + 0x1B000, + 0x1B123, + 0x1B124, + 0x1B125, + 0x1B126, + 0x1B127, + 0x1B129, + 0x1B132, + 0x1B133, + 0x1B150, + 0x1B153, + 0x1B155, + 0x1B156, + 0x1B164, + 0x1B169, + 0x1B170, + 0x1B2FC, + 0x1BC00, + 0x1BC6B, + 0x1BC70, + 0x1BC7D, + 0x1BC80, + 0x1BC89, + 0x1BC90, + 0x1BC9A, + 0x1BC9C, + 0x1BCA0, + 0x1BCA4, + 0x1CC00, + 0x1CCD6, + 0x1CCD7, + 0x1CCD8, + 0x1CCD9, + 0x1CCDA, + 0x1CCDB, + 0x1CCDC, + 0x1CCDD, + 0x1CCDE, + 0x1CCDF, + 0x1CCE0, + 0x1CCE1, + 0x1CCE2, + 0x1CCE3, + 0x1CCE4, + 0x1CCE5, + 0x1CCE6, + 0x1CCE7, + 0x1CCE8, + 0x1CCE9, + 0x1CCEA, + 0x1CCEB, + 0x1CCEC, + 0x1CCED, + 0x1CCEE, + 0x1CCEF, + 0x1CCF0, + 0x1CCF1, + 0x1CCF2, + 0x1CCF3, + 0x1CCF4, + 0x1CCF5, + 0x1CCF6, + 0x1CCF7, + 0x1CCF8, + 0x1CCF9, + 0x1CCFA, + 0x1CCFD, + 0x1CD00, + 0x1CEB4, + 0x1CEBA, + 0x1CED1, + 0x1CED2, + 0x1CED5, + 0x1CEDD, + 0x1CEFE, + 0x1CF00, + 0x1CF2E, + 0x1CF30, + 0x1CF47, + 0x1CF50, + 0x1CFC4, + 0x1D000, + 0x1D0F6, + 0x1D100, + 0x1D15E, + 0x1D15F, + 0x1D160, + 0x1D161, + 0x1D162, + 0x1D163, + 0x1D164, + 0x1D165, + 0x1D173, + 0x1D17B, + 0x1D1BB, + 0x1D1BC, + 0x1D1BD, + 0x1D1BE, + 0x1D1BF, + 0x1D1C0, + 0x1D1C1, + 0x1D246, + 0x1D250, + 0x1D282, + 0x1D2C0, + 0x1D2D4, + 0x1D2E0, + 0x1D2F4, + 0x1D300, + 0x1D357, + 0x1D360, + 0x1D379, + 0x1D400, + 0x1D401, + 0x1D402, + 0x1D403, + 0x1D404, + 0x1D405, + 0x1D406, + 0x1D407, + 0x1D408, + 0x1D409, + 0x1D40A, + 0x1D40B, + 0x1D40C, + 0x1D40D, + 0x1D40E, + 0x1D40F, + 0x1D410, + 0x1D411, + 0x1D412, + 0x1D413, + 0x1D414, + 0x1D415, + 0x1D416, + 0x1D417, + 0x1D418, + 0x1D419, + 0x1D41A, + 0x1D41B, + 0x1D41C, + 0x1D41D, + 0x1D41E, + 0x1D41F, + 0x1D420, + 0x1D421, + 0x1D422, + 0x1D423, + 0x1D424, + 0x1D425, + 0x1D426, + 0x1D427, + 0x1D428, + 0x1D429, + 0x1D42A, + 0x1D42B, + 0x1D42C, + 0x1D42D, + 0x1D42E, + 0x1D42F, + 0x1D430, + 0x1D431, + 0x1D432, + 0x1D433, + 0x1D434, + 0x1D435, + 0x1D436, + 0x1D437, + 0x1D438, + 0x1D439, + 0x1D43A, + 0x1D43B, + 0x1D43C, + 0x1D43D, + 0x1D43E, + 0x1D43F, + 0x1D440, + 0x1D441, + 0x1D442, + 0x1D443, + 0x1D444, + 0x1D445, + 0x1D446, + 0x1D447, + 0x1D448, + 0x1D449, + 0x1D44A, + 0x1D44B, + 0x1D44C, + 0x1D44D, + 0x1D44E, + 0x1D44F, + 0x1D450, + 0x1D451, + 0x1D452, + 0x1D453, + 0x1D454, + 0x1D455, + 0x1D456, + 0x1D457, + 0x1D458, + 0x1D459, + 0x1D45A, + 0x1D45B, + 0x1D45C, + 0x1D45D, + 0x1D45E, + 0x1D45F, + 0x1D460, + 0x1D461, + 0x1D462, + 0x1D463, + 0x1D464, + 0x1D465, + 0x1D466, + 0x1D467, + 0x1D468, + 0x1D469, + 0x1D46A, + 0x1D46B, + 0x1D46C, + 0x1D46D, + 0x1D46E, + 0x1D46F, + 0x1D470, + 0x1D471, + 0x1D472, + 0x1D473, + 0x1D474, + 0x1D475, + 0x1D476, + 0x1D477, + 0x1D478, + 0x1D479, + 0x1D47A, + 0x1D47B, + 0x1D47C, + 0x1D47D, + 0x1D47E, + 0x1D47F, + 0x1D480, + 0x1D481, + 0x1D482, + 0x1D483, + 0x1D484, + 0x1D485, + 0x1D486, + 0x1D487, + 0x1D488, + 0x1D489, + 0x1D48A, + 0x1D48B, + 0x1D48C, + 0x1D48D, + 0x1D48E, + 0x1D48F, + 0x1D490, + 0x1D491, + 0x1D492, + 0x1D493, + 0x1D494, + 0x1D495, + 0x1D496, + 0x1D497, + 0x1D498, + 0x1D499, + 0x1D49A, + 0x1D49B, + 0x1D49C, + 0x1D49D, + 0x1D49E, + 0x1D49F, + 0x1D4A0, + 0x1D4A2, + 0x1D4A3, + 0x1D4A5, + 0x1D4A6, + 0x1D4A7, + 0x1D4A9, + 0x1D4AA, + 0x1D4AB, + 0x1D4AC, + 0x1D4AD, + 0x1D4AE, + 0x1D4AF, + 0x1D4B0, + 0x1D4B1, + 0x1D4B2, + 0x1D4B3, + 0x1D4B4, + 0x1D4B5, + 0x1D4B6, + 0x1D4B7, + 0x1D4B8, + 0x1D4B9, + 0x1D4BA, + 0x1D4BB, + 0x1D4BC, + 0x1D4BD, + 0x1D4BE, + 0x1D4BF, + 0x1D4C0, + 0x1D4C1, + 0x1D4C2, + 0x1D4C3, + 0x1D4C4, + 0x1D4C5, + 0x1D4C6, + 0x1D4C7, + 0x1D4C8, + 0x1D4C9, + 0x1D4CA, + 0x1D4CB, + 0x1D4CC, + 0x1D4CD, + 0x1D4CE, + 0x1D4CF, + 0x1D4D0, + 0x1D4D1, + 0x1D4D2, + 0x1D4D3, + 0x1D4D4, + 0x1D4D5, + 0x1D4D6, + 0x1D4D7, + 0x1D4D8, + 0x1D4D9, + 0x1D4DA, + 0x1D4DB, + 0x1D4DC, + 0x1D4DD, + 0x1D4DE, + 0x1D4DF, + 0x1D4E0, + 0x1D4E1, + 0x1D4E2, + 0x1D4E3, + 0x1D4E4, + 0x1D4E5, + 0x1D4E6, + 0x1D4E7, + 0x1D4E8, + 0x1D4E9, + 0x1D4EA, + 0x1D4EB, + 0x1D4EC, + 0x1D4ED, + 0x1D4EE, + 0x1D4EF, + 0x1D4F0, + 0x1D4F1, + 0x1D4F2, + 0x1D4F3, + 0x1D4F4, + 0x1D4F5, + 0x1D4F6, + 0x1D4F7, + 0x1D4F8, + 0x1D4F9, + 0x1D4FA, + 0x1D4FB, + 0x1D4FC, + 0x1D4FD, + 0x1D4FE, + 0x1D4FF, + 0x1D500, + 0x1D501, + 0x1D502, + 0x1D503, + 0x1D504, + 0x1D505, + 0x1D506, + 0x1D507, + 0x1D508, + 0x1D509, + 0x1D50A, + 0x1D50B, + 0x1D50D, + 0x1D50E, + 0x1D50F, + 0x1D510, + 0x1D511, + 0x1D512, + 0x1D513, + 0x1D514, + 0x1D515, + 0x1D516, + 0x1D517, + 0x1D518, + 0x1D519, + 0x1D51A, + 0x1D51B, + 0x1D51C, + 0x1D51D, + 0x1D51E, + 0x1D51F, + 0x1D520, + 0x1D521, + 0x1D522, + 0x1D523, + 0x1D524, + 0x1D525, + 0x1D526, + 0x1D527, + 0x1D528, + 0x1D529, + 0x1D52A, + 0x1D52B, + 0x1D52C, + 0x1D52D, + 0x1D52E, + 0x1D52F, + 0x1D530, + 0x1D531, + 0x1D532, + 0x1D533, + 0x1D534, + 0x1D535, + 0x1D536, + 0x1D537, + 0x1D538, + 0x1D539, + 0x1D53A, + 0x1D53B, + 0x1D53C, + 0x1D53D, + 0x1D53E, + 0x1D53F, + 0x1D540, + 0x1D541, + 0x1D542, + 0x1D543, + 0x1D544, + 0x1D545, + 0x1D546, + 0x1D547, + 0x1D54A, + 0x1D54B, + 0x1D54C, + 0x1D54D, + 0x1D54E, + 0x1D54F, + 0x1D550, + 0x1D551, + 0x1D552, + 0x1D553, + 0x1D554, + 0x1D555, + 0x1D556, + 0x1D557, + 0x1D558, + 0x1D559, + 0x1D55A, + 0x1D55B, + 0x1D55C, + 0x1D55D, + 0x1D55E, + 0x1D55F, + 0x1D560, + 0x1D561, + 0x1D562, + 0x1D563, + 0x1D564, + 0x1D565, + 0x1D566, + 0x1D567, + 0x1D568, + 0x1D569, + 0x1D56A, + 0x1D56B, + 0x1D56C, + 0x1D56D, + 0x1D56E, + 0x1D56F, + 0x1D570, + 0x1D571, + 0x1D572, + 0x1D573, + 0x1D574, + 0x1D575, + 0x1D576, + 0x1D577, + 0x1D578, + 0x1D579, + 0x1D57A, + 0x1D57B, + 0x1D57C, + 0x1D57D, + 0x1D57E, + 0x1D57F, + 0x1D580, + 0x1D581, + 0x1D582, + 0x1D583, + 0x1D584, + 0x1D585, + 0x1D586, + 0x1D587, + 0x1D588, + 0x1D589, + 0x1D58A, + 0x1D58B, + 0x1D58C, + 0x1D58D, + 0x1D58E, + 0x1D58F, + 0x1D590, + 0x1D591, + 0x1D592, + 0x1D593, + 0x1D594, + 0x1D595, + 0x1D596, + 0x1D597, + 0x1D598, + 0x1D599, + 0x1D59A, + 0x1D59B, + 0x1D59C, + 0x1D59D, + 0x1D59E, + 0x1D59F, + 0x1D5A0, + 0x1D5A1, + 0x1D5A2, + 0x1D5A3, + 0x1D5A4, + 0x1D5A5, + 0x1D5A6, + 0x1D5A7, + 0x1D5A8, + 0x1D5A9, + 0x1D5AA, + 0x1D5AB, + 0x1D5AC, + 0x1D5AD, + 0x1D5AE, + 0x1D5AF, + 0x1D5B0, + 0x1D5B1, + 0x1D5B2, + 0x1D5B3, + 0x1D5B4, + 0x1D5B5, + 0x1D5B6, + 0x1D5B7, + 0x1D5B8, + 0x1D5B9, + 0x1D5BA, + 0x1D5BB, + 0x1D5BC, + 0x1D5BD, + 0x1D5BE, + 0x1D5BF, + 0x1D5C0, + 0x1D5C1, + 0x1D5C2, + 0x1D5C3, + 0x1D5C4, + 0x1D5C5, + 0x1D5C6, + 0x1D5C7, + 0x1D5C8, + 0x1D5C9, + 0x1D5CA, + 0x1D5CB, + 0x1D5CC, + 0x1D5CD, + 0x1D5CE, + 0x1D5CF, + 0x1D5D0, + 0x1D5D1, + 0x1D5D2, + 0x1D5D3, + 0x1D5D4, + 0x1D5D5, + 0x1D5D6, + 0x1D5D7, + 0x1D5D8, + 0x1D5D9, + 0x1D5DA, + 0x1D5DB, + 0x1D5DC, + 0x1D5DD, + 0x1D5DE, + 0x1D5DF, + 0x1D5E0, + 0x1D5E1, + 0x1D5E2, + 0x1D5E3, + 0x1D5E4, + 0x1D5E5, + 0x1D5E6, + 0x1D5E7, + 0x1D5E8, + 0x1D5E9, + 0x1D5EA, + 0x1D5EB, + 0x1D5EC, + 0x1D5ED, + 0x1D5EE, + 0x1D5EF, + 0x1D5F0, + 0x1D5F1, + 0x1D5F2, + 0x1D5F3, + 0x1D5F4, + 0x1D5F5, + 0x1D5F6, + 0x1D5F7, + 0x1D5F8, + 0x1D5F9, + 0x1D5FA, + 0x1D5FB, + 0x1D5FC, + 0x1D5FD, + 0x1D5FE, + 0x1D5FF, + 0x1D600, + 0x1D601, + 0x1D602, + 0x1D603, + 0x1D604, + 0x1D605, + 0x1D606, + 0x1D607, + 0x1D608, + 0x1D609, + 0x1D60A, + 0x1D60B, + 0x1D60C, + 0x1D60D, + 0x1D60E, + 0x1D60F, + 0x1D610, + 0x1D611, + 0x1D612, + 0x1D613, + 0x1D614, + 0x1D615, + 0x1D616, + 0x1D617, + 0x1D618, + 0x1D619, + 0x1D61A, + 0x1D61B, + 0x1D61C, + 0x1D61D, + 0x1D61E, + 0x1D61F, + 0x1D620, + 0x1D621, + 0x1D622, + 0x1D623, + 0x1D624, + 0x1D625, + 0x1D626, + 0x1D627, + 0x1D628, + 0x1D629, + 0x1D62A, + 0x1D62B, + 0x1D62C, + 0x1D62D, + 0x1D62E, + 0x1D62F, + 0x1D630, + 0x1D631, + 0x1D632, + 0x1D633, + 0x1D634, + 0x1D635, + 0x1D636, + 0x1D637, + 0x1D638, + 0x1D639, + 0x1D63A, + 0x1D63B, + 0x1D63C, + 0x1D63D, + 0x1D63E, + 0x1D63F, + 0x1D640, + 0x1D641, + 0x1D642, + 0x1D643, + 0x1D644, + 0x1D645, + 0x1D646, + 0x1D647, + 0x1D648, + 0x1D649, + 0x1D64A, + 0x1D64B, + 0x1D64C, + 0x1D64D, + 0x1D64E, + 0x1D64F, + 0x1D650, + 0x1D651, + 0x1D652, + 0x1D653, + 0x1D654, + 0x1D655, + 0x1D656, + 0x1D657, + 0x1D658, + 0x1D659, + 0x1D65A, + 0x1D65B, + 0x1D65C, + 0x1D65D, + 0x1D65E, + 0x1D65F, + 0x1D660, + 0x1D661, + 0x1D662, + 0x1D663, + 0x1D664, + 0x1D665, + 0x1D666, + 0x1D667, + 0x1D668, + 0x1D669, + 0x1D66A, + 0x1D66B, + 0x1D66C, + 0x1D66D, + 0x1D66E, + 0x1D66F, + 0x1D670, + 0x1D671, + 0x1D672, + 0x1D673, + 0x1D674, + 0x1D675, + 0x1D676, + 0x1D677, + 0x1D678, + 0x1D679, + 0x1D67A, + 0x1D67B, + 0x1D67C, + 0x1D67D, + 0x1D67E, + 0x1D67F, + 0x1D680, + 0x1D681, + 0x1D682, + 0x1D683, + 0x1D684, + 0x1D685, + 0x1D686, + 0x1D687, + 0x1D688, + 0x1D689, + 0x1D68A, + 0x1D68B, + 0x1D68C, + 0x1D68D, + 0x1D68E, + 0x1D68F, + 0x1D690, + 0x1D691, + 0x1D692, + 0x1D693, + 0x1D694, + 0x1D695, + 0x1D696, + 0x1D697, + 0x1D698, + 0x1D699, + 0x1D69A, + 0x1D69B, + 0x1D69C, + 0x1D69D, + 0x1D69E, + 0x1D69F, + 0x1D6A0, + 0x1D6A1, + 0x1D6A2, + 0x1D6A3, + 0x1D6A4, + 0x1D6A5, + 0x1D6A6, + 0x1D6A7, + 0x1D6A8, + 0x1D6A9, + 0x1D6AA, + 0x1D6AB, + 0x1D6AC, + 0x1D6AD, + 0x1D6AE, + 0x1D6AF, + 0x1D6B0, + 0x1D6B1, + 0x1D6B2, + 0x1D6B3, + 0x1D6B4, + 0x1D6B5, + 0x1D6B6, + 0x1D6B7, + 0x1D6B8, + 0x1D6B9, + 0x1D6BA, + 0x1D6BB, + 0x1D6BC, + 0x1D6BD, + 0x1D6BE, + 0x1D6BF, + 0x1D6C0, + 0x1D6C1, + 0x1D6C2, + 0x1D6C3, + 0x1D6C4, + 0x1D6C5, + 0x1D6C6, + 0x1D6C7, + 0x1D6C8, + 0x1D6C9, + 0x1D6CA, + 0x1D6CB, + 0x1D6CC, + 0x1D6CD, + 0x1D6CE, + 0x1D6CF, + 0x1D6D0, + 0x1D6D1, + 0x1D6D2, + 0x1D6D3, + 0x1D6D5, + 0x1D6D6, + 0x1D6D7, + 0x1D6D8, + 0x1D6D9, + 0x1D6DA, + 0x1D6DB, + 0x1D6DC, + 0x1D6DD, + 0x1D6DE, + 0x1D6DF, + 0x1D6E0, + 0x1D6E1, + 0x1D6E2, + 0x1D6E3, + 0x1D6E4, + 0x1D6E5, + 0x1D6E6, + 0x1D6E7, + 0x1D6E8, + 0x1D6E9, + 0x1D6EA, + 0x1D6EB, + 0x1D6EC, + 0x1D6ED, + 0x1D6EE, + 0x1D6EF, + 0x1D6F0, + 0x1D6F1, + 0x1D6F2, + 0x1D6F3, + 0x1D6F4, + 0x1D6F5, + 0x1D6F6, + 0x1D6F7, + 0x1D6F8, + 0x1D6F9, + 0x1D6FA, + 0x1D6FB, + 0x1D6FC, + 0x1D6FD, + 0x1D6FE, + 0x1D6FF, + 0x1D700, + 0x1D701, + 0x1D702, + 0x1D703, + 0x1D704, + 0x1D705, + 0x1D706, + 0x1D707, + 0x1D708, + 0x1D709, + 0x1D70A, + 0x1D70B, + 0x1D70C, + 0x1D70D, + 0x1D70F, + 0x1D710, + 0x1D711, + 0x1D712, + 0x1D713, + 0x1D714, + 0x1D715, + 0x1D716, + 0x1D717, + 0x1D718, + 0x1D719, + 0x1D71A, + 0x1D71B, + 0x1D71C, + 0x1D71D, + 0x1D71E, + 0x1D71F, + 0x1D720, + 0x1D721, + 0x1D722, + 0x1D723, + 0x1D724, + 0x1D725, + 0x1D726, + 0x1D727, + 0x1D728, + 0x1D729, + 0x1D72A, + 0x1D72B, + 0x1D72C, + 0x1D72D, + 0x1D72E, + 0x1D72F, + 0x1D730, + 0x1D731, + 0x1D732, + 0x1D733, + 0x1D734, + 0x1D735, + 0x1D736, + 0x1D737, + 0x1D738, + 0x1D739, + 0x1D73A, + 0x1D73B, + 0x1D73C, + 0x1D73D, + 0x1D73E, + 0x1D73F, + 0x1D740, + 0x1D741, + 0x1D742, + 0x1D743, + 0x1D744, + 0x1D745, + 0x1D746, + 0x1D747, + 0x1D749, + 0x1D74A, + 0x1D74B, + 0x1D74C, + 0x1D74D, + 0x1D74E, + 0x1D74F, + 0x1D750, + 0x1D751, + 0x1D752, + 0x1D753, + 0x1D754, + 0x1D755, + 0x1D756, + 0x1D757, + 0x1D758, + 0x1D759, + 0x1D75A, + 0x1D75B, + 0x1D75C, + 0x1D75D, + 0x1D75E, + 0x1D75F, + 0x1D760, + 0x1D761, + 0x1D762, + 0x1D763, + 0x1D764, + 0x1D765, + 0x1D766, + 0x1D767, + 0x1D768, + 0x1D769, + 0x1D76A, + 0x1D76B, + 0x1D76C, + 0x1D76D, + 0x1D76E, + 0x1D76F, + 0x1D770, + 0x1D771, + 0x1D772, + 0x1D773, + 0x1D774, + 0x1D775, + 0x1D776, + 0x1D777, + 0x1D778, + 0x1D779, + 0x1D77A, + 0x1D77B, + 0x1D77C, + 0x1D77D, + 0x1D77E, + 0x1D77F, + 0x1D780, + 0x1D781, + 0x1D783, + 0x1D784, + 0x1D785, + 0x1D786, + 0x1D787, + 0x1D788, + 0x1D789, + 0x1D78A, + 0x1D78B, + 0x1D78C, + 0x1D78D, + 0x1D78E, + 0x1D78F, + 0x1D790, + 0x1D791, + 0x1D792, + 0x1D793, + 0x1D794, + 0x1D795, + 0x1D796, + 0x1D797, + 0x1D798, + 0x1D799, + 0x1D79A, + 0x1D79B, + 0x1D79C, + 0x1D79D, + 0x1D79E, + 0x1D79F, + 0x1D7A0, + 0x1D7A1, + 0x1D7A2, + 0x1D7A3, + 0x1D7A4, + 0x1D7A5, + 0x1D7A6, + 0x1D7A7, + 0x1D7A8, + 0x1D7A9, + 0x1D7AA, + 0x1D7AB, + 0x1D7AC, + 0x1D7AD, + 0x1D7AE, + 0x1D7AF, + 0x1D7B0, + 0x1D7B1, + 0x1D7B2, + 0x1D7B3, + 0x1D7B4, + 0x1D7B5, + 0x1D7B6, + 0x1D7B7, + 0x1D7B8, + 0x1D7B9, + 0x1D7BA, + 0x1D7BB, + 0x1D7BD, + 0x1D7BE, + 0x1D7BF, + 0x1D7C0, + 0x1D7C1, + 0x1D7C2, + 0x1D7C3, + 0x1D7C4, + 0x1D7C5, + 0x1D7C6, + 0x1D7C7, + 0x1D7C8, + 0x1D7C9, + 0x1D7CA, + 0x1D7CC, + 0x1D7CE, + 0x1D7CF, + 0x1D7D0, + 0x1D7D1, + 0x1D7D2, + 0x1D7D3, + 0x1D7D4, + 0x1D7D5, + 0x1D7D6, + 0x1D7D7, + 0x1D7D8, + 0x1D7D9, + 0x1D7DA, + 0x1D7DB, + 0x1D7DC, + 0x1D7DD, + 0x1D7DE, + 0x1D7DF, + 0x1D7E0, + 0x1D7E1, + 0x1D7E2, + 0x1D7E3, + 0x1D7E4, + 0x1D7E5, + 0x1D7E6, + 0x1D7E7, + 0x1D7E8, + 0x1D7E9, + 0x1D7EA, + 0x1D7EB, + 0x1D7EC, + 0x1D7ED, + 0x1D7EE, + 0x1D7EF, + 0x1D7F0, + 0x1D7F1, + 0x1D7F2, + 0x1D7F3, + 0x1D7F4, + 0x1D7F5, + 0x1D7F6, + 0x1D7F7, + 0x1D7F8, + 0x1D7F9, + 0x1D7FA, + 0x1D7FB, + 0x1D7FC, + 0x1D7FD, + 0x1D7FE, + 0x1D7FF, + 0x1D800, + 0x1DA8C, + 0x1DA9B, + 0x1DAA0, + 0x1DAA1, + 0x1DAB0, + 0x1DB00, + 0x1DB1D, + 0x1DF00, + 0x1DF40, + 0x1DF41, + 0x1DF48, + 0x1DF49, + 0x1DF4A, + 0x1DF4B, + 0x1DF4D, + 0x1DF4E, + 0x1DF51, + 0x1DF52, + 0x1DF68, + 0x1DF69, + 0x1DF6A, + 0x1DF6B, + 0x1DF6C, + 0x1DF6D, + 0x1DF6E, + 0x1DF6F, + 0x1DF72, + 0x1DF73, + 0x1DF74, + 0x1DF75, + 0x1DF76, + 0x1DF77, + 0x1DF78, + 0x1DF79, + 0x1DF7A, + 0x1DF7B, + 0x1DF7C, + 0x1DF7D, + 0x1DF7E, + 0x1DF7F, + 0x1DF82, + 0x1DF90, + 0x1DF95, + 0x1DF96, + 0x1DF97, + 0x1DFCD, + 0x1DFCE, + 0x1DFCF, + 0x1DFD0, + 0x1DFD1, + 0x1DFD2, + 0x1DFD3, + 0x1DFD4, + 0x1DFD5, + 0x1DFD6, + 0x1DFD7, + 0x1DFD8, + 0x1DFD9, + 0x1DFDA, + 0x1DFDB, + 0x1DFDC, + 0x1DFDD, + 0x1DFDE, + 0x1DFDF, + 0x1DFE0, + 0x1DFE1, + 0x1DFE2, + 0x1DFE3, + 0x1DFE4, + 0x1DFE5, + 0x1DFE6, + 0x1DFE7, + 0x1DFE8, + 0x1DFE9, + 0x1DFEA, + 0x1DFEB, + 0x1DFEC, + 0x1DFED, + 0x1DFEE, + 0x1DFEF, + 0x1DFF0, + 0x1DFF1, + 0x1DFF2, + 0x1DFF3, + 0x1DFF4, + 0x1DFF5, + 0x1DFF6, + 0x1DFF7, + 0x1DFF8, + 0x1DFF9, + 0x1DFFA, + 0x1DFFB, + 0x1DFFC, + 0x1DFFD, + 0x1DFFE, + 0x1DFFF, + 0x1E000, + 0x1E007, + 0x1E008, + 0x1E019, + 0x1E01B, + 0x1E022, + 0x1E023, + 0x1E025, + 0x1E026, + 0x1E02B, + 0x1E030, + 0x1E031, + 0x1E032, + 0x1E033, + 0x1E034, + 0x1E035, + 0x1E036, + 0x1E037, + 0x1E038, + 0x1E039, + 0x1E03A, + 0x1E03B, + 0x1E03C, + 0x1E03D, + 0x1E03E, + 0x1E03F, + 0x1E040, + 0x1E041, + 0x1E042, + 0x1E043, + 0x1E044, + 0x1E045, + 0x1E046, + 0x1E047, + 0x1E048, + 0x1E049, + 0x1E04A, + 0x1E04B, + 0x1E04C, + 0x1E04D, + 0x1E04E, + 0x1E04F, + 0x1E050, + 0x1E051, + 0x1E052, + 0x1E053, + 0x1E054, + 0x1E055, + 0x1E056, + 0x1E057, + 0x1E058, + 0x1E059, + 0x1E05A, + 0x1E05B, + 0x1E05C, + 0x1E05D, + 0x1E05E, + 0x1E05F, + 0x1E060, + 0x1E061, + 0x1E062, + 0x1E063, + 0x1E064, + 0x1E065, + 0x1E066, + 0x1E067, + 0x1E068, + 0x1E069, + 0x1E06A, + 0x1E06B, + 0x1E06C, + 0x1E06D, + 0x1E06E, + 0x1E08F, + 0x1E090, + 0x1E100, + 0x1E12D, + 0x1E130, + 0x1E13E, + 0x1E140, + 0x1E14A, + 0x1E14E, + 0x1E150, + 0x1E290, + 0x1E2AF, + 0x1E2C0, + 0x1E2FA, + 0x1E2FF, + 0x1E300, + 0x1E4D0, + 0x1E4FA, + 0x1E5D0, + 0x1E5FB, + 0x1E5FF, + 0x1E600, + 0x1E6C0, + 0x1E6DF, + 0x1E6E0, + 0x1E6F6, + 0x1E6FE, + 0x1E700, + 0x1E7E0, + 0x1E7E7, + 0x1E7E8, + 0x1E7EC, + 0x1E7ED, + 0x1E7EF, + 0x1E7F0, + 0x1E7FF, + 0x1E800, + 0x1E8C5, + 0x1E8C7, + 0x1E8D7, + 0x1E900, + 0x1E901, + 0x1E902, + 0x1E903, + 0x1E904, + 0x1E905, + 0x1E906, + 0x1E907, + 0x1E908, + 0x1E909, + 0x1E90A, + 0x1E90B, + 0x1E90C, + 0x1E90D, + 0x1E90E, + 0x1E90F, + 0x1E910, + 0x1E911, + 0x1E912, + 0x1E913, + 0x1E914, + 0x1E915, + 0x1E916, + 0x1E917, + 0x1E918, + 0x1E919, + 0x1E91A, + 0x1E91B, + 0x1E91C, + 0x1E91D, + 0x1E91E, + 0x1E91F, + 0x1E920, + 0x1E921, + 0x1E922, + 0x1E94C, + 0x1E950, + 0x1E95A, + 0x1E95E, + 0x1E960, + 0x1EC71, + 0x1ECB5, + 0x1ED01, + 0x1ED3E, + 0x1EE00, + 0x1EE01, + 0x1EE02, + 0x1EE03, + 0x1EE04, + 0x1EE05, + 0x1EE06, + 0x1EE07, + 0x1EE08, + 0x1EE09, + 0x1EE0A, + 0x1EE0B, + 0x1EE0C, + 0x1EE0D, + 0x1EE0E, + 0x1EE0F, + 0x1EE10, + 0x1EE11, + 0x1EE12, + 0x1EE13, + 0x1EE14, + 0x1EE15, + 0x1EE16, + 0x1EE17, + 0x1EE18, + 0x1EE19, + 0x1EE1A, + 0x1EE1B, + 0x1EE1C, + 0x1EE1D, + 0x1EE1E, + 0x1EE1F, + 0x1EE20, + 0x1EE21, + 0x1EE22, + 0x1EE23, + 0x1EE24, + 0x1EE25, + 0x1EE27, + 0x1EE28, + 0x1EE29, + 0x1EE2A, + 0x1EE2B, + 0x1EE2C, + 0x1EE2D, + 0x1EE2E, + 0x1EE2F, + 0x1EE30, + 0x1EE31, + 0x1EE32, + 0x1EE33, + 0x1EE34, + 0x1EE35, + 0x1EE36, + 0x1EE37, + 0x1EE38, + 0x1EE39, + 0x1EE3A, + 0x1EE3B, + 0x1EE3C, + 0x1EE42, + 0x1EE43, + 0x1EE47, + 0x1EE48, + 0x1EE49, + 0x1EE4A, + 0x1EE4B, + 0x1EE4C, + 0x1EE4D, + 0x1EE4E, + 0x1EE4F, + 0x1EE50, + 0x1EE51, + 0x1EE52, + 0x1EE53, + 0x1EE54, + 0x1EE55, + 0x1EE57, + 0x1EE58, + 0x1EE59, + 0x1EE5A, + 0x1EE5B, + 0x1EE5C, + 0x1EE5D, + 0x1EE5E, + 0x1EE5F, + 0x1EE60, + 0x1EE61, + 0x1EE62, + 0x1EE63, + 0x1EE64, + 0x1EE65, + 0x1EE67, + 0x1EE68, + 0x1EE69, + 0x1EE6A, + 0x1EE6B, + 0x1EE6C, + 0x1EE6D, + 0x1EE6E, + 0x1EE6F, + 0x1EE70, + 0x1EE71, + 0x1EE72, + 0x1EE73, + 0x1EE74, + 0x1EE75, + 0x1EE76, + 0x1EE77, + 0x1EE78, + 0x1EE79, + 0x1EE7A, + 0x1EE7B, + 0x1EE7C, + 0x1EE7D, + 0x1EE7E, + 0x1EE7F, + 0x1EE80, + 0x1EE81, + 0x1EE82, + 0x1EE83, + 0x1EE84, + 0x1EE85, + 0x1EE86, + 0x1EE87, + 0x1EE88, + 0x1EE89, + 0x1EE8A, + 0x1EE8B, + 0x1EE8C, + 0x1EE8D, + 0x1EE8E, + 0x1EE8F, + 0x1EE90, + 0x1EE91, + 0x1EE92, + 0x1EE93, + 0x1EE94, + 0x1EE95, + 0x1EE96, + 0x1EE97, + 0x1EE98, + 0x1EE99, + 0x1EE9A, + 0x1EE9B, + 0x1EE9C, + 0x1EEA1, + 0x1EEA2, + 0x1EEA3, + 0x1EEA4, + 0x1EEA5, + 0x1EEA6, + 0x1EEA7, + 0x1EEA8, + 0x1EEA9, + 0x1EEAA, + 0x1EEAB, + 0x1EEAC, + 0x1EEAD, + 0x1EEAE, + 0x1EEAF, + 0x1EEB0, + 0x1EEB1, + 0x1EEB2, + 0x1EEB3, + 0x1EEB4, + 0x1EEB5, + 0x1EEB6, + 0x1EEB7, + 0x1EEB8, + 0x1EEB9, + 0x1EEBA, + 0x1EEBB, + 0x1EEBC, + 0x1EEF0, + 0x1EEF2, + 0x1F000, + 0x1F02C, + 0x1F030, + 0x1F094, + 0x1F0A0, + 0x1F0AF, + 0x1F0B1, + 0x1F0C0, + 0x1F0C1, + 0x1F0D0, + 0x1F0D1, + 0x1F0F6, + 0x1F101, + 0x1F102, + 0x1F103, + 0x1F104, + 0x1F105, + 0x1F106, + 0x1F107, + 0x1F108, + 0x1F109, + 0x1F10A, + 0x1F10B, + 0x1F110, + 0x1F111, + 0x1F112, + 0x1F113, + 0x1F114, + 0x1F115, + 0x1F116, + 0x1F117, + 0x1F118, + 0x1F119, + 0x1F11A, + 0x1F11B, + 0x1F11C, + 0x1F11D, + 0x1F11E, + 0x1F11F, + 0x1F120, + 0x1F121, + 0x1F122, + 0x1F123, + 0x1F124, + 0x1F125, + 0x1F126, + 0x1F127, + 0x1F128, + 0x1F129, + 0x1F12A, + 0x1F12B, + 0x1F12C, + 0x1F12D, + 0x1F12E, + 0x1F12F, + 0x1F130, + 0x1F131, + 0x1F132, + 0x1F133, + 0x1F134, + 0x1F135, + 0x1F136, + 0x1F137, + 0x1F138, + 0x1F139, + 0x1F13A, + 0x1F13B, + 0x1F13C, + 0x1F13D, + 0x1F13E, + 0x1F13F, + 0x1F140, + 0x1F141, + 0x1F142, + 0x1F143, + 0x1F144, + 0x1F145, + 0x1F146, + 0x1F147, + 0x1F148, + 0x1F149, + 0x1F14A, + 0x1F14B, + 0x1F14C, + 0x1F14D, + 0x1F14E, + 0x1F14F, + 0x1F150, + 0x1F16A, + 0x1F16B, + 0x1F16C, + 0x1F16D, + 0x1F190, + 0x1F191, + 0x1F1AF, + 0x1F1E6, + 0x1F200, + 0x1F201, + 0x1F202, + 0x1F203, + 0x1F210, + 0x1F211, + 0x1F212, + 0x1F213, + 0x1F214, + 0x1F215, + 0x1F216, + 0x1F217, + 0x1F218, + 0x1F219, + 0x1F21A, + 0x1F21B, + 0x1F21C, + 0x1F21D, + 0x1F21E, + 0x1F21F, + 0x1F220, + 0x1F221, + 0x1F222, + 0x1F223, + 0x1F224, + 0x1F225, + 0x1F226, + 0x1F227, + 0x1F228, + 0x1F229, + 0x1F22A, + 0x1F22B, + 0x1F22C, + 0x1F22D, + 0x1F22E, + 0x1F22F, + 0x1F230, + 0x1F231, + 0x1F232, + 0x1F233, + 0x1F234, + 0x1F235, + 0x1F236, + 0x1F237, + 0x1F238, + 0x1F239, + 0x1F23A, + 0x1F23B, + 0x1F23C, + 0x1F240, + 0x1F241, + 0x1F242, + 0x1F243, + 0x1F244, + 0x1F245, + 0x1F246, + 0x1F247, + 0x1F248, + 0x1F249, + 0x1F250, + 0x1F251, + 0x1F252, + 0x1F260, + 0x1F266, + 0x1F300, + 0x1F6DA, + 0x1F6DC, + 0x1F6ED, + 0x1F6F0, + 0x1F6FD, + 0x1F700, + 0x1F7DC, + 0x1F7E0, + 0x1F7EC, + 0x1F7F0, + 0x1F80C, + 0x1F810, + 0x1F848, + 0x1F850, + 0x1F85A, + 0x1F860, + 0x1F888, + 0x1F890, + 0x1F8AE, + 0x1F8B0, + 0x1F8BC, + 0x1F8C0, + 0x1F8C2, + 0x1F8D0, + 0x1F8D9, + 0x1F900, + 0x1FA58, + 0x1FA60, + 0x1FA6E, + 0x1FA70, + 0x1FA7D, + 0x1FA80, + 0x1FAC7, + 0x1FAC8, + 0x1FAC9, + 0x1FACC, + 0x1FADE, + 0x1FADF, + 0x1FAEC, + 0x1FAEF, + 0x1FAFB, + 0x1FB00, + 0x1FB93, + 0x1FB94, + 0x1FBF0, + 0x1FBF1, + 0x1FBF2, + 0x1FBF3, + 0x1FBF4, + 0x1FBF5, + 0x1FBF6, + 0x1FBF7, + 0x1FBF8, + 0x1FBF9, + 0x1FBFA, + 0x1FBFB, + 0x20000, + 0x2A6E0, + 0x2A700, + 0x2B81F, + 0x2B820, + 0x2CEAE, + 0x2CEB0, + 0x2EBE1, + 0x2EBF0, + 0x2EE5E, + 0x2F800, + 0x2F801, + 0x2F802, + 0x2F803, + 0x2F804, + 0x2F805, + 0x2F806, + 0x2F807, + 0x2F808, + 0x2F809, + 0x2F80A, + 0x2F80B, + 0x2F80C, + 0x2F80D, + 0x2F80E, + 0x2F80F, + 0x2F810, + 0x2F811, + 0x2F812, + 0x2F813, + 0x2F814, + 0x2F815, + 0x2F816, + 0x2F817, + 0x2F818, + 0x2F819, + 0x2F81A, + 0x2F81B, + 0x2F81C, + 0x2F81D, + 0x2F81E, + 0x2F81F, + 0x2F820, + 0x2F821, + 0x2F822, + 0x2F823, + 0x2F824, + 0x2F825, + 0x2F826, + 0x2F827, + 0x2F828, + 0x2F829, + 0x2F82A, + 0x2F82B, + 0x2F82C, + 0x2F82D, + 0x2F82E, + 0x2F82F, + 0x2F830, + 0x2F831, + 0x2F834, + 0x2F835, + 0x2F836, + 0x2F837, + 0x2F838, + 0x2F839, + 0x2F83A, + 0x2F83B, + 0x2F83C, + 0x2F83D, + 0x2F83E, + 0x2F83F, + 0x2F840, + 0x2F841, + 0x2F842, + 0x2F843, + 0x2F844, + 0x2F845, + 0x2F847, + 0x2F848, + 0x2F849, + 0x2F84A, + 0x2F84B, + 0x2F84C, + 0x2F84D, + 0x2F84E, + 0x2F84F, + 0x2F850, + 0x2F851, + 0x2F852, + 0x2F853, + 0x2F854, + 0x2F855, + 0x2F856, + 0x2F857, + 0x2F858, + 0x2F859, + 0x2F85A, + 0x2F85B, + 0x2F85C, + 0x2F85D, + 0x2F85E, + 0x2F85F, + 0x2F860, + 0x2F861, + 0x2F862, + 0x2F863, + 0x2F864, + 0x2F865, + 0x2F866, + 0x2F867, + 0x2F868, + 0x2F869, + 0x2F86A, + 0x2F86C, + 0x2F86D, + 0x2F86E, + 0x2F86F, + 0x2F870, + 0x2F871, + 0x2F872, + 0x2F873, + 0x2F874, + 0x2F875, + 0x2F876, + 0x2F877, + 0x2F878, + 0x2F879, + 0x2F87A, + 0x2F87B, + 0x2F87C, + 0x2F87D, + 0x2F87E, + 0x2F87F, + 0x2F880, + 0x2F881, + 0x2F882, + 0x2F883, + 0x2F884, + 0x2F885, + 0x2F886, + 0x2F887, + 0x2F888, + 0x2F889, + 0x2F88A, + 0x2F88B, + 0x2F88C, + 0x2F88D, + 0x2F88E, + 0x2F88F, + 0x2F890, + 0x2F891, + 0x2F893, + 0x2F894, + 0x2F896, + 0x2F897, + 0x2F898, + 0x2F899, + 0x2F89A, + 0x2F89B, + 0x2F89C, + 0x2F89D, + 0x2F89E, + 0x2F89F, + 0x2F8A0, + 0x2F8A1, + 0x2F8A2, + 0x2F8A3, + 0x2F8A4, + 0x2F8A5, + 0x2F8A6, + 0x2F8A7, + 0x2F8A8, + 0x2F8A9, + 0x2F8AA, + 0x2F8AB, + 0x2F8AC, + 0x2F8AD, + 0x2F8AE, + 0x2F8AF, + 0x2F8B0, + 0x2F8B1, + 0x2F8B2, + 0x2F8B3, + 0x2F8B4, + 0x2F8B5, + 0x2F8B6, + 0x2F8B7, + 0x2F8B8, + 0x2F8B9, + 0x2F8BA, + 0x2F8BB, + 0x2F8BC, + 0x2F8BD, + 0x2F8BE, + 0x2F8BF, + 0x2F8C0, + 0x2F8C1, + 0x2F8C2, + 0x2F8C3, + 0x2F8C4, + 0x2F8C5, + 0x2F8C6, + 0x2F8C7, + 0x2F8C8, + 0x2F8C9, + 0x2F8CA, + 0x2F8CB, + 0x2F8CC, + 0x2F8CD, + 0x2F8CE, + 0x2F8CF, + 0x2F8D0, + 0x2F8D1, + 0x2F8D2, + 0x2F8D3, + 0x2F8D4, + 0x2F8D5, + 0x2F8D6, + 0x2F8D7, + 0x2F8D8, + 0x2F8D9, + 0x2F8DA, + 0x2F8DB, + 0x2F8DC, + 0x2F8DD, + 0x2F8DE, + 0x2F8DF, + 0x2F8E0, + 0x2F8E1, + 0x2F8E2, + 0x2F8E3, + 0x2F8E4, + 0x2F8E5, + 0x2F8E6, + 0x2F8E7, + 0x2F8E8, + 0x2F8E9, + 0x2F8EA, + 0x2F8EB, + 0x2F8EC, + 0x2F8ED, + 0x2F8EE, + 0x2F8EF, + 0x2F8F0, + 0x2F8F1, + 0x2F8F2, + 0x2F8F3, + 0x2F8F4, + 0x2F8F5, + 0x2F8F6, + 0x2F8F7, + 0x2F8F8, + 0x2F8F9, + 0x2F8FA, + 0x2F8FB, + 0x2F8FC, + 0x2F8FD, + 0x2F8FE, + 0x2F8FF, + 0x2F900, + 0x2F901, + 0x2F902, + 0x2F903, + 0x2F904, + 0x2F905, + 0x2F906, + 0x2F907, + 0x2F908, + 0x2F909, + 0x2F90A, + 0x2F90B, + 0x2F90C, + 0x2F90D, + 0x2F90E, + 0x2F90F, + 0x2F910, + 0x2F911, + 0x2F912, + 0x2F913, + 0x2F914, + 0x2F915, + 0x2F916, + 0x2F917, + 0x2F918, + 0x2F919, + 0x2F91A, + 0x2F91B, + 0x2F91C, + 0x2F91D, + 0x2F91E, + 0x2F91F, + 0x2F920, + 0x2F921, + 0x2F922, + 0x2F923, + 0x2F924, + 0x2F925, + 0x2F926, + 0x2F927, + 0x2F928, + 0x2F929, + 0x2F92A, + 0x2F92B, + 0x2F92C, + 0x2F92E, + 0x2F92F, + 0x2F930, + 0x2F931, + 0x2F932, + 0x2F933, + 0x2F934, + 0x2F935, + 0x2F936, + 0x2F937, + 0x2F938, + 0x2F939, + 0x2F93A, + 0x2F93B, + 0x2F93C, + 0x2F93D, + 0x2F93E, + 0x2F93F, + 0x2F940, + 0x2F941, + 0x2F942, + 0x2F943, + 0x2F944, + 0x2F945, + 0x2F946, + 0x2F948, + 0x2F949, + 0x2F94A, + 0x2F94B, + 0x2F94C, + 0x2F94D, + 0x2F94E, + 0x2F94F, + 0x2F950, + 0x2F951, + 0x2F952, + 0x2F953, + 0x2F954, + 0x2F955, + 0x2F956, + 0x2F957, + 0x2F958, + 0x2F959, + 0x2F95A, + 0x2F95B, + 0x2F95C, + 0x2F95D, + 0x2F95F, + 0x2F960, + 0x2F961, + 0x2F962, + 0x2F963, + 0x2F964, + 0x2F965, + 0x2F966, + 0x2F967, + 0x2F968, + 0x2F969, + 0x2F96A, + 0x2F96B, + 0x2F96C, + 0x2F96D, + 0x2F96E, + 0x2F96F, + 0x2F970, + 0x2F971, + 0x2F972, + 0x2F973, + 0x2F974, + 0x2F975, + 0x2F976, + 0x2F977, + 0x2F978, + 0x2F979, + 0x2F97A, + 0x2F97B, + 0x2F97C, + 0x2F97D, + 0x2F97E, + 0x2F97F, + 0x2F980, + 0x2F981, + 0x2F982, + 0x2F983, + 0x2F984, + 0x2F985, + 0x2F986, + 0x2F987, + 0x2F988, + 0x2F989, + 0x2F98A, + 0x2F98B, + 0x2F98C, + 0x2F98D, + 0x2F98E, + 0x2F98F, + 0x2F990, + 0x2F991, + 0x2F992, + 0x2F993, + 0x2F994, + 0x2F995, + 0x2F996, + 0x2F997, + 0x2F998, + 0x2F999, + 0x2F99A, + 0x2F99B, + 0x2F99C, + 0x2F99D, + 0x2F99E, + 0x2F99F, + 0x2F9A0, + 0x2F9A1, + 0x2F9A2, + 0x2F9A3, + 0x2F9A4, + 0x2F9A5, + 0x2F9A6, + 0x2F9A7, + 0x2F9A8, + 0x2F9A9, + 0x2F9AA, + 0x2F9AB, + 0x2F9AC, + 0x2F9AD, + 0x2F9AE, + 0x2F9AF, + 0x2F9B0, + 0x2F9B1, + 0x2F9B2, + 0x2F9B3, + 0x2F9B4, + 0x2F9B5, + 0x2F9B6, + 0x2F9B7, + 0x2F9B8, + 0x2F9B9, + 0x2F9BA, + 0x2F9BB, + 0x2F9BC, + 0x2F9BD, + 0x2F9BE, + 0x2F9BF, + 0x2F9C0, + 0x2F9C1, + 0x2F9C2, + 0x2F9C3, + 0x2F9C4, + 0x2F9C5, + 0x2F9C6, + 0x2F9C7, + 0x2F9C8, + 0x2F9C9, + 0x2F9CA, + 0x2F9CB, + 0x2F9CC, + 0x2F9CD, + 0x2F9CE, + 0x2F9CF, + 0x2F9D0, + 0x2F9D1, + 0x2F9D2, + 0x2F9D3, + 0x2F9D4, + 0x2F9D5, + 0x2F9D6, + 0x2F9D7, + 0x2F9D8, + 0x2F9D9, + 0x2F9DA, + 0x2F9DB, + 0x2F9DC, + 0x2F9DD, + 0x2F9DE, + 0x2F9DF, + 0x2F9E0, + 0x2F9E1, + 0x2F9E2, + 0x2F9E3, + 0x2F9E4, + 0x2F9E5, + 0x2F9E6, + 0x2F9E7, + 0x2F9E8, + 0x2F9E9, + 0x2F9EA, + 0x2F9EB, + 0x2F9EC, + 0x2F9ED, + 0x2F9EE, + 0x2F9EF, + 0x2F9F0, + 0x2F9F1, + 0x2F9F2, + 0x2F9F3, + 0x2F9F4, + 0x2F9F5, + 0x2F9F6, + 0x2F9F7, + 0x2F9F8, + 0x2F9F9, + 0x2F9FA, + 0x2F9FB, + 0x2F9FC, + 0x2F9FD, + 0x2F9FE, + 0x2FA00, + 0x2FA01, + 0x2FA02, + 0x2FA03, + 0x2FA04, + 0x2FA05, + 0x2FA06, + 0x2FA07, + 0x2FA08, + 0x2FA09, + 0x2FA0A, + 0x2FA0B, + 0x2FA0C, + 0x2FA0D, + 0x2FA0E, + 0x2FA0F, + 0x2FA10, + 0x2FA11, + 0x2FA12, + 0x2FA13, + 0x2FA14, + 0x2FA15, + 0x2FA16, + 0x2FA17, + 0x2FA18, + 0x2FA19, + 0x2FA1A, + 0x2FA1B, + 0x2FA1C, + 0x2FA1D, + 0x2FA1E, + 0x30000, + 0x3134B, + 0x31350, + 0x3347A, + 0x3D000, + 0x3FC40, + 0xE0100, + 0xE01F0, + ), +) + +uts46_statuses: bytes = ( + b"VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + b"VMMMMMMMMMMMMMMMMMMMMMMMMMMVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXMVVVVVVVMVMVVIVMVVMMMMVVMMMVMMMV" + b"MMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMDVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVMVMVMVMMV" + b"MVMVMVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVMVMVMM" + b"VMVMMVMMMVMMMMVMMVMMMVMMVMMVMVMVMMVMVMVMMVMMMVMVMMVMVMMMMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMMVMMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" + b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMMVMVMMMMVMVMVMVMVMMMMMMMMMVMMMMMM" + b"VMMMMMVMMVMMMVIVMVMVMVMVXMVMMXMMMMMMMXMXMMVMMMMMMMMMMMMMMMMMXMMM" + b"MMMMMMVDVMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMVMVMMMVMMVMVMMVMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" + b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVXMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMXMVMVMMVXVXVXVXVXVMMMMVXVXVXVXVXVXVXVXVX" + b"VXVXVXVMMMMMMMMVXVXVXVXVXVXVXVXVXVXVXMMXMVXVXVXVXVXVXVXVMXVMXVXV" + b"XVXVXVXVXMMMVXMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" + b"XVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" + b"XVXVMVXVXVXVXVXVXVXVMVXVXVXVXVXMMVXVMVMVXVMVMVMVMVMVXVMVMMMMMVMV" + b"MVXVMVMVMVMVMVXVXVXVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMXMXV" + b"MVIVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMMMMMMXVXVXVXVXVXVXVXVX" + b"VXVIVXVXVXVIVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVMMM" + b"MMMMMMVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMVXVXVMMMV" + b"MMMMMMMMMMMVMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVM" + b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" + b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMMMMMMMVXMMMMMMXVMMMMMMMMVMMMMMM" + b"MMVXMMMMMMXVXMXMXMXMVMMMMMMMMVMVMVMVMVMVMVMXMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMXVMMMMMMMMMMMMMMXVMMMMMMMMMVMXVM" + b"MMMXMMMVMVMMMMMMMMXMMMXVMMMMMMMMXMIDXVMVMVXVXMVMMVMMVMVMVMMMVMVM" + b"IXIMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMVMVXVXMMMMVMMM" + b"VMMMMMMVMMVMMMVMMMVMVMVMVMMMMVMMMMMMMMMMVMMMMMVMMMMVMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVXVMMVMMVMMVXVXMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMMMVMVXVMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMMMVMVMVMVMMMMVMVMV" + b"MMMMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVXVXVXVXVXMVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVMVMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMVMVMVMMMVXVXVMMVM" + b"VMXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMIMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXVMMMMMMMMMMMMMMVXVMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMXMMMMMMMMM" + b"MMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXMVMVMVMVMVM" + b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" + b"MVXVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMM" + b"MMVMMMMMVMVMVMVMVMVMVMVMMMMVMVMMVMVMVMVMVMVMVMVMMXMXMMMMMVMMVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVMMMMVMVMMXMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMV" + b"XVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMVMVMVMMMMMMMMMMVMVMVMMVMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMXMMMMMXMVMMMMMMMMMMMMMMMMMMMMM" + b"MMMXMMMMMXMXMMXMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXMMMMMMMMMMMM" + b"MVIMMXMMMMMMXVXMMMMMMMMMMMMMMMMMMMVMMMMMMXMMMMMMMMMMMMMMMMMMMXMM" + b"MMXMMMVMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXIXMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMIMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMM" + b"MMMXMMMMMMXMMMMMMXMMMXMMMMMMMXMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVMMMMMMMMMMMXMMMMMMMMMM" + b"MMMMMXMMMMMMMXMMXVXVXVXVXVXVXVXVXVMMMMMXMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMVXMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVXVX" + b"VXVXVXVXVXVXVXVXVMMMMVXVXVXVXVXVXVXVXVXVXVIXVMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMVXVXVXVXVXVXVXVXVXVMMMMMMMVIVMMMMMMVXVXVXVXVXVX" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMXMXMMXMMMMXMMMMMMMMMMMMXMXMMMMMM" + b"MXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMXMMMMXMMMMMMMMXMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMXMMMM" + b"MXMXMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"XMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVMVMV" + b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVXVMVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" + b"XVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVXMMMMXMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMXMMXMXMXMMMMMMMMMMXMMMMXMXMXMXMXMXMXMMMXMMXMXMX" + b"MXMXMXMXMMXMXMMMMXMMMMMMMXMMMMXMMMMXMXMMMMMMMMMMXMMMMMMMMMMMMMMM" + b"MMXMMMXMMMMMXMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXMMMMMMMMMMVMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMVMVX" + b"VMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMXMMXV" + b"XVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVMMMMMMMMMMVXVXVXVX" + b"VXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" + b"MMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXIX" +) + +uts46_replacements: tuple[str | None, ...] = ( + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + " ", + None, + None, + None, + None, + None, + None, + None, + " ̈", + None, + "a", + None, + None, + None, + None, + " ̄", + None, + None, + "2", + "3", + " ́", + "μ", + None, + None, + " ̧", + "1", + "o", + None, + "1⁄4", + "1⁄2", + "3⁄4", + None, + "à", + "á", + "â", + "ã", + "ä", + "å", + "æ", + "ç", + "è", + "é", + "ê", + "ë", + "ì", + "í", + "î", + "ï", + "ð", + "ñ", + "ò", + "ó", + "ô", + "õ", + "ö", + None, + "ø", + "ù", + "ú", + "û", + "ü", + "ý", + "þ", + "ss", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ā", + None, + "ă", + None, + "ą", + None, + "ć", + None, + "ĉ", + None, + "ċ", + None, + "č", + None, + "ď", + None, + "đ", + None, + "ē", + None, + "ĕ", + None, + "ė", + None, + "ę", + None, + "ě", + None, + "ĝ", + None, + "ğ", + None, + "ġ", + None, + "ģ", + None, + "ĥ", + None, + "ħ", + None, + "ĩ", + None, + "ī", + None, + "ĭ", + None, + "į", + None, + "i̇", + None, + "ij", + "ĵ", + None, + "ķ", + None, + "ĺ", + None, + "ļ", + None, + "ľ", + None, + "l·", + "ł", + None, + "ń", + None, + "ņ", + None, + "ň", + None, + "ʼn", + "ŋ", + None, + "ō", + None, + "ŏ", + None, + "ő", + None, + "œ", + None, + "ŕ", + None, + "ŗ", + None, + "ř", + None, + "ś", + None, + "ŝ", + None, + "ş", + None, + "š", + None, + "ţ", + None, + "ť", + None, + "ŧ", + None, + "ũ", + None, + "ū", + None, + "ŭ", + None, + "ů", + None, + "ű", + None, + "ų", + None, + "ŵ", + None, + "ŷ", + None, + "ÿ", + "ź", + None, + "ż", + None, + "ž", + None, + "s", + None, + "ɓ", + "ƃ", + None, + "ƅ", + None, + "ɔ", + "ƈ", + None, + "ɖ", + "ɗ", + "ƌ", + None, + "ǝ", + "ə", + "ɛ", + "ƒ", + None, + "ɠ", + "ɣ", + None, + "ɩ", + "ɨ", + "ƙ", + None, + "ɯ", + "ɲ", + None, + "ɵ", + "ơ", + None, + "ƣ", + None, + "ƥ", + None, + "ʀ", + "ƨ", + None, + "ʃ", + None, + "ƭ", + None, + "ʈ", + "ư", + None, + "ʊ", + "ʋ", + "ƴ", + None, + "ƶ", + None, + "ʒ", + "ƹ", + None, + "ƽ", + None, + "dž", + "lj", + "nj", + "ǎ", + None, + "ǐ", + None, + "ǒ", + None, + "ǔ", + None, + "ǖ", + None, + "ǘ", + None, + "ǚ", + None, + "ǜ", + None, + "ǟ", + None, + "ǡ", + None, + "ǣ", + None, + "ǥ", + None, + "ǧ", + None, + "ǩ", + None, + "ǫ", + None, + "ǭ", + None, + "ǯ", + None, + "dz", + "ǵ", + None, + "ƕ", + "ƿ", + "ǹ", + None, + "ǻ", + None, + "ǽ", + None, + "ǿ", + None, + "ȁ", + None, + "ȃ", + None, + "ȅ", + None, + "ȇ", + None, + "ȉ", + None, + "ȋ", + None, + "ȍ", + None, + "ȏ", + None, + "ȑ", + None, + "ȓ", + None, + "ȕ", + None, + "ȗ", + None, + "ș", + None, + "ț", + None, + "ȝ", + None, + "ȟ", + None, + "ƞ", + None, + "ȣ", + None, + "ȥ", + None, + "ȧ", + None, + "ȩ", + None, + "ȫ", + None, + "ȭ", + None, + "ȯ", + None, + "ȱ", + None, + "ȳ", + None, + "ⱥ", + "ȼ", + None, + "ƚ", + "ⱦ", + None, + "ɂ", + None, + "ƀ", + "ʉ", + "ʌ", + "ɇ", + None, + "ɉ", + None, + "ɋ", + None, + "ɍ", + None, + "ɏ", + None, + "h", + "ɦ", + "j", + "r", + "ɹ", + "ɻ", + "ʁ", + "w", + "y", + None, + " ̆", + " ̇", + " ̊", + " ̨", + " ̃", + " ̋", + None, + "ɣ", + "l", + "s", + "x", + "ʕ", + None, + "̀", + "́", + None, + "̓", + "̈́", + "ι", + None, + None, + None, + "ͱ", + None, + "ͳ", + None, + "ʹ", + None, + "ͷ", + None, + None, + " ι", + None, + ";", + "ϳ", + None, + " ́", + " ̈́", + "ά", + "·", + "έ", + "ή", + "ί", + None, + "ό", + None, + "ύ", + "ώ", + None, + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + None, + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "ϊ", + "ϋ", + None, + "σ", + None, + "ϗ", + "β", + "θ", + "υ", + "ύ", + "ϋ", + "φ", + "π", + None, + "ϙ", + None, + "ϛ", + None, + "ϝ", + None, + "ϟ", + None, + "ϡ", + None, + "ϣ", + None, + "ϥ", + None, + "ϧ", + None, + "ϩ", + None, + "ϫ", + None, + "ϭ", + None, + "ϯ", + None, + "κ", + "ρ", + "σ", + None, + "θ", + "ε", + None, + "ϸ", + None, + "σ", + "ϻ", + None, + "ͻ", + "ͼ", + "ͽ", + "ѐ", + "ё", + "ђ", + "ѓ", + "є", + "ѕ", + "і", + "ї", + "ј", + "љ", + "њ", + "ћ", + "ќ", + "ѝ", + "ў", + "џ", + "а", + "б", + "в", + "г", + "д", + "е", + "ж", + "з", + "и", + "й", + "к", + "л", + "м", + "н", + "о", + "п", + "р", + "с", + "т", + "у", + "ф", + "х", + "ц", + "ч", + "ш", + "щ", + "ъ", + "ы", + "ь", + "э", + "ю", + "я", + None, + "ѡ", + None, + "ѣ", + None, + "ѥ", + None, + "ѧ", + None, + "ѩ", + None, + "ѫ", + None, + "ѭ", + None, + "ѯ", + None, + "ѱ", + None, + "ѳ", + None, + "ѵ", + None, + "ѷ", + None, + "ѹ", + None, + "ѻ", + None, + "ѽ", + None, + "ѿ", + None, + "ҁ", + None, + "ҋ", + None, + "ҍ", + None, + "ҏ", + None, + "ґ", + None, + "ғ", + None, + "ҕ", + None, + "җ", + None, + "ҙ", + None, + "қ", + None, + "ҝ", + None, + "ҟ", + None, + "ҡ", + None, + "ң", + None, + "ҥ", + None, + "ҧ", + None, + "ҩ", + None, + "ҫ", + None, + "ҭ", + None, + "ү", + None, + "ұ", + None, + "ҳ", + None, + "ҵ", + None, + "ҷ", + None, + "ҹ", + None, + "һ", + None, + "ҽ", + None, + "ҿ", + None, + "ӏ", + "ӂ", + None, + "ӄ", + None, + "ӆ", + None, + "ӈ", + None, + "ӊ", + None, + "ӌ", + None, + "ӎ", + None, + "ӑ", + None, + "ӓ", + None, + "ӕ", + None, + "ӗ", + None, + "ә", + None, + "ӛ", + None, + "ӝ", + None, + "ӟ", + None, + "ӡ", + None, + "ӣ", + None, + "ӥ", + None, + "ӧ", + None, + "ө", + None, + "ӫ", + None, + "ӭ", + None, + "ӯ", + None, + "ӱ", + None, + "ӳ", + None, + "ӵ", + None, + "ӷ", + None, + "ӹ", + None, + "ӻ", + None, + "ӽ", + None, + "ӿ", + None, + "ԁ", + None, + "ԃ", + None, + "ԅ", + None, + "ԇ", + None, + "ԉ", + None, + "ԋ", + None, + "ԍ", + None, + "ԏ", + None, + "ԑ", + None, + "ԓ", + None, + "ԕ", + None, + "ԗ", + None, + "ԙ", + None, + "ԛ", + None, + "ԝ", + None, + "ԟ", + None, + "ԡ", + None, + "ԣ", + None, + "ԥ", + None, + "ԧ", + None, + "ԩ", + None, + "ԫ", + None, + "ԭ", + None, + "ԯ", + None, + None, + "ա", + "բ", + "գ", + "դ", + "ե", + "զ", + "է", + "ը", + "թ", + "ժ", + "ի", + "լ", + "խ", + "ծ", + "կ", + "հ", + "ձ", + "ղ", + "ճ", + "մ", + "յ", + "ն", + "շ", + "ո", + "չ", + "պ", + "ջ", + "ռ", + "ս", + "վ", + "տ", + "ր", + "ց", + "ւ", + "փ", + "ք", + "օ", + "ֆ", + None, + "է", + None, + "եւ", + None, + "ի", + "յ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "اٴ", + "وٴ", + "ۇٴ", + "يٴ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "क़", + "ख़", + "ग़", + "ज़", + "ड़", + "ढ़", + "फ़", + "य़", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ড়", + "ঢ়", + None, + "য়", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ਲ਼", + None, + None, + "ਸ਼", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ਖ਼", + "ਗ਼", + "ਜ਼", + None, + None, + "ਫ਼", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ଡ଼", + "ଢ଼", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ํา", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ໍາ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ຫນ", + "ຫມ", + None, + None, + None, + "་", + None, + "གྷ", + None, + None, + None, + "ཌྷ", + None, + "དྷ", + None, + "བྷ", + None, + "ཛྷ", + None, + "ཀྵ", + None, + None, + None, + "ཱི", + None, + "ཱུ", + "ྲྀ", + "ྲཱྀ", + "ླྀ", + "ླཱྀ", + None, + "ཱྀ", + None, + "ྒྷ", + None, + None, + None, + "ྜྷ", + None, + "ྡྷ", + None, + "ྦྷ", + None, + "ྫྷ", + None, + "ྐྵ", + None, + None, + None, + None, + None, + None, + None, + "ⴀ", + "ⴁ", + "ⴂ", + "ⴃ", + "ⴄ", + "ⴅ", + "ⴆ", + "ⴇ", + "ⴈ", + "ⴉ", + "ⴊ", + "ⴋ", + "ⴌ", + "ⴍ", + "ⴎ", + "ⴏ", + "ⴐ", + "ⴑ", + "ⴒ", + "ⴓ", + "ⴔ", + "ⴕ", + "ⴖ", + "ⴗ", + "ⴘ", + "ⴙ", + "ⴚ", + "ⴛ", + "ⴜ", + "ⴝ", + "ⴞ", + "ⴟ", + "ⴠ", + "ⴡ", + "ⴢ", + "ⴣ", + "ⴤ", + "ⴥ", + None, + "ⴧ", + None, + "ⴭ", + None, + None, + "ნ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "Ᏸ", + "Ᏹ", + "Ᏺ", + "Ᏻ", + "Ᏼ", + "Ᏽ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "в", + "д", + "о", + "с", + "т", + "ъ", + "ѣ", + "ꙋ", + "ᲊ", + None, + None, + "ა", + "ბ", + "გ", + "დ", + "ე", + "ვ", + "ზ", + "თ", + "ი", + "კ", + "ლ", + "მ", + "ნ", + "ო", + "პ", + "ჟ", + "რ", + "ს", + "ტ", + "უ", + "ფ", + "ქ", + "ღ", + "ყ", + "შ", + "ჩ", + "ც", + "ძ", + "წ", + "ჭ", + "ხ", + "ჯ", + "ჰ", + "ჱ", + "ჲ", + "ჳ", + "ჴ", + "ჵ", + "ჶ", + "ჷ", + "ჸ", + "ჹ", + "ჺ", + None, + "ჽ", + "ჾ", + "ჿ", + None, + None, + None, + None, + None, + "a", + "æ", + "b", + None, + "d", + "e", + "ǝ", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + None, + "o", + "ȣ", + "p", + "r", + "t", + "u", + "w", + "a", + "ɐ", + "ɑ", + "ᴂ", + "b", + "d", + "e", + "ə", + "ɛ", + "ɜ", + "g", + None, + "k", + "m", + "ŋ", + "o", + "ɔ", + "ᴖ", + "ᴗ", + "p", + "t", + "u", + "ᴝ", + "ɯ", + "v", + "ᴥ", + "β", + "γ", + "δ", + "φ", + "χ", + "i", + "r", + "u", + "v", + "β", + "γ", + "ρ", + "φ", + "χ", + None, + "н", + None, + "ɒ", + "c", + "ɕ", + "ð", + "ɜ", + "f", + "ɟ", + "ɡ", + "ɥ", + "ɨ", + "ɩ", + "ɪ", + "ᵻ", + "ʝ", + "ɭ", + "ᶅ", + "ʟ", + "ɱ", + "ɰ", + "ɲ", + "ɳ", + "ɴ", + "ɵ", + "ɸ", + "ʂ", + "ʃ", + "ƫ", + "ʉ", + "ʊ", + "ᴜ", + "ʋ", + "ʌ", + "z", + "ʐ", + "ʑ", + "ʒ", + "θ", + None, + "ḁ", + None, + "ḃ", + None, + "ḅ", + None, + "ḇ", + None, + "ḉ", + None, + "ḋ", + None, + "ḍ", + None, + "ḏ", + None, + "ḑ", + None, + "ḓ", + None, + "ḕ", + None, + "ḗ", + None, + "ḙ", + None, + "ḛ", + None, + "ḝ", + None, + "ḟ", + None, + "ḡ", + None, + "ḣ", + None, + "ḥ", + None, + "ḧ", + None, + "ḩ", + None, + "ḫ", + None, + "ḭ", + None, + "ḯ", + None, + "ḱ", + None, + "ḳ", + None, + "ḵ", + None, + "ḷ", + None, + "ḹ", + None, + "ḻ", + None, + "ḽ", + None, + "ḿ", + None, + "ṁ", + None, + "ṃ", + None, + "ṅ", + None, + "ṇ", + None, + "ṉ", + None, + "ṋ", + None, + "ṍ", + None, + "ṏ", + None, + "ṑ", + None, + "ṓ", + None, + "ṕ", + None, + "ṗ", + None, + "ṙ", + None, + "ṛ", + None, + "ṝ", + None, + "ṟ", + None, + "ṡ", + None, + "ṣ", + None, + "ṥ", + None, + "ṧ", + None, + "ṩ", + None, + "ṫ", + None, + "ṭ", + None, + "ṯ", + None, + "ṱ", + None, + "ṳ", + None, + "ṵ", + None, + "ṷ", + None, + "ṹ", + None, + "ṻ", + None, + "ṽ", + None, + "ṿ", + None, + "ẁ", + None, + "ẃ", + None, + "ẅ", + None, + "ẇ", + None, + "ẉ", + None, + "ẋ", + None, + "ẍ", + None, + "ẏ", + None, + "ẑ", + None, + "ẓ", + None, + "ẕ", + None, + "aʾ", + "ṡ", + None, + "ß", + None, + "ạ", + None, + "ả", + None, + "ấ", + None, + "ầ", + None, + "ẩ", + None, + "ẫ", + None, + "ậ", + None, + "ắ", + None, + "ằ", + None, + "ẳ", + None, + "ẵ", + None, + "ặ", + None, + "ẹ", + None, + "ẻ", + None, + "ẽ", + None, + "ế", + None, + "ề", + None, + "ể", + None, + "ễ", + None, + "ệ", + None, + "ỉ", + None, + "ị", + None, + "ọ", + None, + "ỏ", + None, + "ố", + None, + "ồ", + None, + "ổ", + None, + "ỗ", + None, + "ộ", + None, + "ớ", + None, + "ờ", + None, + "ở", + None, + "ỡ", + None, + "ợ", + None, + "ụ", + None, + "ủ", + None, + "ứ", + None, + "ừ", + None, + "ử", + None, + "ữ", + None, + "ự", + None, + "ỳ", + None, + "ỵ", + None, + "ỷ", + None, + "ỹ", + None, + "ỻ", + None, + "ỽ", + None, + "ỿ", + None, + "ἀ", + "ἁ", + "ἂ", + "ἃ", + "ἄ", + "ἅ", + "ἆ", + "ἇ", + None, + None, + "ἐ", + "ἑ", + "ἒ", + "ἓ", + "ἔ", + "ἕ", + None, + None, + "ἠ", + "ἡ", + "ἢ", + "ἣ", + "ἤ", + "ἥ", + "ἦ", + "ἧ", + None, + "ἰ", + "ἱ", + "ἲ", + "ἳ", + "ἴ", + "ἵ", + "ἶ", + "ἷ", + None, + None, + "ὀ", + "ὁ", + "ὂ", + "ὃ", + "ὄ", + "ὅ", + None, + None, + None, + "ὑ", + None, + "ὓ", + None, + "ὕ", + None, + "ὗ", + None, + "ὠ", + "ὡ", + "ὢ", + "ὣ", + "ὤ", + "ὥ", + "ὦ", + "ὧ", + None, + "ά", + None, + "έ", + None, + "ή", + None, + "ί", + None, + "ό", + None, + "ύ", + None, + "ώ", + None, + "ἀι", + "ἁι", + "ἂι", + "ἃι", + "ἄι", + "ἅι", + "ἆι", + "ἇι", + "ἀι", + "ἁι", + "ἂι", + "ἃι", + "ἄι", + "ἅι", + "ἆι", + "ἇι", + "ἠι", + "ἡι", + "ἢι", + "ἣι", + "ἤι", + "ἥι", + "ἦι", + "ἧι", + "ἠι", + "ἡι", + "ἢι", + "ἣι", + "ἤι", + "ἥι", + "ἦι", + "ἧι", + "ὠι", + "ὡι", + "ὢι", + "ὣι", + "ὤι", + "ὥι", + "ὦι", + "ὧι", + "ὠι", + "ὡι", + "ὢι", + "ὣι", + "ὤι", + "ὥι", + "ὦι", + "ὧι", + None, + "ὰι", + "αι", + "άι", + None, + None, + "ᾶι", + "ᾰ", + "ᾱ", + "ὰ", + "ά", + "αι", + " ̓", + "ι", + " ̓", + " ͂", + " ̈͂", + "ὴι", + "ηι", + "ήι", + None, + None, + "ῆι", + "ὲ", + "έ", + "ὴ", + "ή", + "ηι", + " ̓̀", + " ̓́", + " ̓͂", + None, + "ΐ", + None, + None, + "ῐ", + "ῑ", + "ὶ", + "ί", + None, + " ̔̀", + " ̔́", + " ̔͂", + None, + "ΰ", + None, + "ῠ", + "ῡ", + "ὺ", + "ύ", + "ῥ", + " ̈̀", + " ̈́", + "`", + None, + "ὼι", + "ωι", + "ώι", + None, + None, + "ῶι", + "ὸ", + "ό", + "ὼ", + "ώ", + "ωι", + " ́", + " ̔", + None, + " ", + None, + "", + None, + None, + "‐", + None, + " ̳", + None, + None, + None, + None, + " ", + None, + "′′", + "′′′", + None, + "‵‵", + "‵‵‵", + None, + "!!", + None, + " ̅", + None, + "??", + "?!", + "!?", + None, + "′′′′", + None, + " ", + None, + None, + None, + "0", + "i", + None, + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "−", + "=", + "(", + ")", + "n", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "−", + "=", + "(", + ")", + None, + "a", + "e", + "o", + "x", + "ə", + "h", + "k", + "l", + "m", + "n", + "p", + "s", + "t", + "w", + "y", + "z", + None, + "rs", + None, + None, + None, + None, + "a/c", + "a/s", + "c", + "°c", + None, + "c/o", + "c/u", + "ɛ", + None, + "°f", + "g", + "h", + "ħ", + "i", + "l", + None, + "n", + "no", + None, + "p", + "q", + "r", + None, + "sm", + "tel", + "tm", + None, + "z", + None, + "ω", + None, + "z", + None, + "k", + "å", + "b", + "c", + None, + "e", + "f", + "ⅎ", + "m", + "o", + "א", + "ב", + "ג", + "ד", + "i", + None, + "fax", + "π", + "γ", + "π", + "∑", + None, + "d", + "e", + "i", + "j", + None, + "1⁄7", + "1⁄9", + "1⁄10", + "1⁄3", + "2⁄3", + "1⁄5", + "2⁄5", + "3⁄5", + "4⁄5", + "1⁄6", + "5⁄6", + "1⁄8", + "3⁄8", + "5⁄8", + "7⁄8", + "1⁄", + "i", + "ii", + "iii", + "iv", + "v", + "vi", + "vii", + "viii", + "ix", + "x", + "xi", + "xii", + "l", + "c", + "d", + "m", + "i", + "ii", + "iii", + "iv", + "v", + "vi", + "vii", + "viii", + "ix", + "x", + "xi", + "xii", + "l", + "c", + "d", + "m", + None, + "ↄ", + None, + "0⁄3", + None, + None, + None, + "∫∫", + "∫∫∫", + None, + "∮∮", + "∮∮∮", + None, + "〈", + "〉", + None, + None, + None, + None, + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "(1)", + "(2)", + "(3)", + "(4)", + "(5)", + "(6)", + "(7)", + "(8)", + "(9)", + "(10)", + "(11)", + "(12)", + "(13)", + "(14)", + "(15)", + "(16)", + "(17)", + "(18)", + "(19)", + "(20)", + None, + "(a)", + "(b)", + "(c)", + "(d)", + "(e)", + "(f)", + "(g)", + "(h)", + "(i)", + "(j)", + "(k)", + "(l)", + "(m)", + "(n)", + "(o)", + "(p)", + "(q)", + "(r)", + "(s)", + "(t)", + "(u)", + "(v)", + "(w)", + "(x)", + "(y)", + "(z)", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + None, + "∫∫∫∫", + None, + "::=", + "==", + "===", + None, + "⫝̸", + None, + None, + None, + "ⰰ", + "ⰱ", + "ⰲ", + "ⰳ", + "ⰴ", + "ⰵ", + "ⰶ", + "ⰷ", + "ⰸ", + "ⰹ", + "ⰺ", + "ⰻ", + "ⰼ", + "ⰽ", + "ⰾ", + "ⰿ", + "ⱀ", + "ⱁ", + "ⱂ", + "ⱃ", + "ⱄ", + "ⱅ", + "ⱆ", + "ⱇ", + "ⱈ", + "ⱉ", + "ⱊ", + "ⱋ", + "ⱌ", + "ⱍ", + "ⱎ", + "ⱏ", + "ⱐ", + "ⱑ", + "ⱒ", + "ⱓ", + "ⱔ", + "ⱕ", + "ⱖ", + "ⱗ", + "ⱘ", + "ⱙ", + "ⱚ", + "ⱛ", + "ⱜ", + "ⱝ", + "ⱞ", + "ⱟ", + None, + "ⱡ", + None, + "ɫ", + "ᵽ", + "ɽ", + None, + "ⱨ", + None, + "ⱪ", + None, + "ⱬ", + None, + "ɑ", + "ɱ", + "ɐ", + "ɒ", + None, + "ⱳ", + None, + "ⱶ", + None, + "j", + "v", + "ȿ", + "ɀ", + "ⲁ", + None, + "ⲃ", + None, + "ⲅ", + None, + "ⲇ", + None, + "ⲉ", + None, + "ⲋ", + None, + "ⲍ", + None, + "ⲏ", + None, + "ⲑ", + None, + "ⲓ", + None, + "ⲕ", + None, + "ⲗ", + None, + "ⲙ", + None, + "ⲛ", + None, + "ⲝ", + None, + "ⲟ", + None, + "ⲡ", + None, + "ⲣ", + None, + "ⲥ", + None, + "ⲧ", + None, + "ⲩ", + None, + "ⲫ", + None, + "ⲭ", + None, + "ⲯ", + None, + "ⲱ", + None, + "ⲳ", + None, + "ⲵ", + None, + "ⲷ", + None, + "ⲹ", + None, + "ⲻ", + None, + "ⲽ", + None, + "ⲿ", + None, + "ⳁ", + None, + "ⳃ", + None, + "ⳅ", + None, + "ⳇ", + None, + "ⳉ", + None, + "ⳋ", + None, + "ⳍ", + None, + "ⳏ", + None, + "ⳑ", + None, + "ⳓ", + None, + "ⳕ", + None, + "ⳗ", + None, + "ⳙ", + None, + "ⳛ", + None, + "ⳝ", + None, + "ⳟ", + None, + "ⳡ", + None, + "ⳣ", + None, + "ⳬ", + None, + "ⳮ", + None, + "ⳳ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ⵡ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "母", + None, + "龟", + None, + "一", + "丨", + "丶", + "丿", + "乙", + "亅", + "二", + "亠", + "人", + "儿", + "入", + "八", + "冂", + "冖", + "冫", + "几", + "凵", + "刀", + "力", + "勹", + "匕", + "匚", + "匸", + "十", + "卜", + "卩", + "厂", + "厶", + "又", + "口", + "囗", + "土", + "士", + "夂", + "夊", + "夕", + "大", + "女", + "子", + "宀", + "寸", + "小", + "尢", + "尸", + "屮", + "山", + "巛", + "工", + "己", + "巾", + "干", + "幺", + "广", + "廴", + "廾", + "弋", + "弓", + "彐", + "彡", + "彳", + "心", + "戈", + "戶", + "手", + "支", + "攴", + "文", + "斗", + "斤", + "方", + "无", + "日", + "曰", + "月", + "木", + "欠", + "止", + "歹", + "殳", + "毋", + "比", + "毛", + "氏", + "气", + "水", + "火", + "爪", + "父", + "爻", + "爿", + "片", + "牙", + "牛", + "犬", + "玄", + "玉", + "瓜", + "瓦", + "甘", + "生", + "用", + "田", + "疋", + "疒", + "癶", + "白", + "皮", + "皿", + "目", + "矛", + "矢", + "石", + "示", + "禸", + "禾", + "穴", + "立", + "竹", + "米", + "糸", + "缶", + "网", + "羊", + "羽", + "老", + "而", + "耒", + "耳", + "聿", + "肉", + "臣", + "自", + "至", + "臼", + "舌", + "舛", + "舟", + "艮", + "色", + "艸", + "虍", + "虫", + "血", + "行", + "衣", + "襾", + "見", + "角", + "言", + "谷", + "豆", + "豕", + "豸", + "貝", + "赤", + "走", + "足", + "身", + "車", + "辛", + "辰", + "辵", + "邑", + "酉", + "釆", + "里", + "金", + "長", + "門", + "阜", + "隶", + "隹", + "雨", + "靑", + "非", + "面", + "革", + "韋", + "韭", + "音", + "頁", + "風", + "飛", + "食", + "首", + "香", + "馬", + "骨", + "高", + "髟", + "鬥", + "鬯", + "鬲", + "鬼", + "魚", + "鳥", + "鹵", + "鹿", + "麥", + "麻", + "黃", + "黍", + "黑", + "黹", + "黽", + "鼎", + "鼓", + "鼠", + "鼻", + "齊", + "齒", + "龍", + "龜", + "龠", + None, + " ", + None, + ".", + None, + "〒", + None, + "十", + "卄", + "卅", + None, + None, + None, + None, + None, + " ゙", + " ゚", + None, + "より", + None, + "コト", + None, + None, + None, + "ᄀ", + "ᄁ", + "ᆪ", + "ᄂ", + "ᆬ", + "ᆭ", + "ᄃ", + "ᄄ", + "ᄅ", + "ᆰ", + "ᆱ", + "ᆲ", + "ᆳ", + "ᆴ", + "ᆵ", + "ᄚ", + "ᄆ", + "ᄇ", + "ᄈ", + "ᄡ", + "ᄉ", + "ᄊ", + "ᄋ", + "ᄌ", + "ᄍ", + "ᄎ", + "ᄏ", + "ᄐ", + "ᄑ", + "ᄒ", + "ᅡ", + "ᅢ", + "ᅣ", + "ᅤ", + "ᅥ", + "ᅦ", + "ᅧ", + "ᅨ", + "ᅩ", + "ᅪ", + "ᅫ", + "ᅬ", + "ᅭ", + "ᅮ", + "ᅯ", + "ᅰ", + "ᅱ", + "ᅲ", + "ᅳ", + "ᅴ", + "ᅵ", + None, + "ᄔ", + "ᄕ", + "ᇇ", + "ᇈ", + "ᇌ", + "ᇎ", + "ᇓ", + "ᇗ", + "ᇙ", + "ᄜ", + "ᇝ", + "ᇟ", + "ᄝ", + "ᄞ", + "ᄠ", + "ᄢ", + "ᄣ", + "ᄧ", + "ᄩ", + "ᄫ", + "ᄬ", + "ᄭ", + "ᄮ", + "ᄯ", + "ᄲ", + "ᄶ", + "ᅀ", + "ᅇ", + "ᅌ", + "ᇱ", + "ᇲ", + "ᅗ", + "ᅘ", + "ᅙ", + "ᆄ", + "ᆅ", + "ᆈ", + "ᆑ", + "ᆒ", + "ᆔ", + "ᆞ", + "ᆡ", + None, + None, + "一", + "二", + "三", + "四", + "上", + "中", + "下", + "甲", + "乙", + "丙", + "丁", + "天", + "地", + "人", + None, + None, + None, + "(ᄀ)", + "(ᄂ)", + "(ᄃ)", + "(ᄅ)", + "(ᄆ)", + "(ᄇ)", + "(ᄉ)", + "(ᄋ)", + "(ᄌ)", + "(ᄎ)", + "(ᄏ)", + "(ᄐ)", + "(ᄑ)", + "(ᄒ)", + "(가)", + "(나)", + "(다)", + "(라)", + "(마)", + "(바)", + "(사)", + "(아)", + "(자)", + "(차)", + "(카)", + "(타)", + "(파)", + "(하)", + "(주)", + "(오전)", + "(오후)", + None, + "(一)", + "(二)", + "(三)", + "(四)", + "(五)", + "(六)", + "(七)", + "(八)", + "(九)", + "(十)", + "(月)", + "(火)", + "(水)", + "(木)", + "(金)", + "(土)", + "(日)", + "(株)", + "(有)", + "(社)", + "(名)", + "(特)", + "(財)", + "(祝)", + "(労)", + "(代)", + "(呼)", + "(学)", + "(監)", + "(企)", + "(資)", + "(協)", + "(祭)", + "(休)", + "(自)", + "(至)", + "問", + "幼", + "文", + "箏", + None, + "pte", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + "32", + "33", + "34", + "35", + "ᄀ", + "ᄂ", + "ᄃ", + "ᄅ", + "ᄆ", + "ᄇ", + "ᄉ", + "ᄋ", + "ᄌ", + "ᄎ", + "ᄏ", + "ᄐ", + "ᄑ", + "ᄒ", + "가", + "나", + "다", + "라", + "마", + "바", + "사", + "아", + "자", + "차", + "카", + "타", + "파", + "하", + "참고", + "주의", + "우", + None, + "一", + "二", + "三", + "四", + "五", + "六", + "七", + "八", + "九", + "十", + "月", + "火", + "水", + "木", + "金", + "土", + "日", + "株", + "有", + "社", + "名", + "特", + "財", + "祝", + "労", + "秘", + "男", + "女", + "適", + "優", + "印", + "注", + "項", + "休", + "写", + "正", + "上", + "中", + "下", + "左", + "右", + "医", + "宗", + "学", + "監", + "企", + "資", + "協", + "夜", + "36", + "37", + "38", + "39", + "40", + "41", + "42", + "43", + "44", + "45", + "46", + "47", + "48", + "49", + "50", + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月", + "hg", + "erg", + "ev", + "ltd", + "ア", + "イ", + "ウ", + "エ", + "オ", + "カ", + "キ", + "ク", + "ケ", + "コ", + "サ", + "シ", + "ス", + "セ", + "ソ", + "タ", + "チ", + "ツ", + "テ", + "ト", + "ナ", + "ニ", + "ヌ", + "ネ", + "ノ", + "ハ", + "ヒ", + "フ", + "ヘ", + "ホ", + "マ", + "ミ", + "ム", + "メ", + "モ", + "ヤ", + "ユ", + "ヨ", + "ラ", + "リ", + "ル", + "レ", + "ロ", + "ワ", + "ヰ", + "ヱ", + "ヲ", + "令和", + "アパート", + "アルファ", + "アンペア", + "アール", + "イニング", + "インチ", + "ウォン", + "エスクード", + "エーカー", + "オンス", + "オーム", + "カイリ", + "カラット", + "カロリー", + "ガロン", + "ガンマ", + "ギガ", + "ギニー", + "キュリー", + "ギルダー", + "キロ", + "キログラム", + "キロメートル", + "キロワット", + "グラム", + "グラムトン", + "クルゼイロ", + "クローネ", + "ケース", + "コルナ", + "コーポ", + "サイクル", + "サンチーム", + "シリング", + "センチ", + "セント", + "ダース", + "デシ", + "ドル", + "トン", + "ナノ", + "ノット", + "ハイツ", + "パーセント", + "パーツ", + "バーレル", + "ピアストル", + "ピクル", + "ピコ", + "ビル", + "ファラッド", + "フィート", + "ブッシェル", + "フラン", + "ヘクタール", + "ペソ", + "ペニヒ", + "ヘルツ", + "ペンス", + "ページ", + "ベータ", + "ポイント", + "ボルト", + "ホン", + "ポンド", + "ホール", + "ホーン", + "マイクロ", + "マイル", + "マッハ", + "マルク", + "マンション", + "ミクロン", + "ミリ", + "ミリバール", + "メガ", + "メガトン", + "メートル", + "ヤード", + "ヤール", + "ユアン", + "リットル", + "リラ", + "ルピー", + "ルーブル", + "レム", + "レントゲン", + "ワット", + "0点", + "1点", + "2点", + "3点", + "4点", + "5点", + "6点", + "7点", + "8点", + "9点", + "10点", + "11点", + "12点", + "13点", + "14点", + "15点", + "16点", + "17点", + "18点", + "19点", + "20点", + "21点", + "22点", + "23点", + "24点", + "hpa", + "da", + "au", + "bar", + "ov", + "pc", + "dm", + "dm2", + "dm3", + "iu", + "平成", + "昭和", + "大正", + "明治", + "株式会社", + "pa", + "na", + "μa", + "ma", + "ka", + "kb", + "mb", + "gb", + "cal", + "kcal", + "pf", + "nf", + "μf", + "μg", + "mg", + "kg", + "hz", + "khz", + "mhz", + "ghz", + "thz", + "μl", + "ml", + "dl", + "kl", + "fm", + "nm", + "μm", + "mm", + "cm", + "km", + "mm2", + "cm2", + "m2", + "km2", + "mm3", + "cm3", + "m3", + "km3", + "m∕s", + "m∕s2", + "pa", + "kpa", + "mpa", + "gpa", + "rad", + "rad∕s", + "rad∕s2", + "ps", + "ns", + "μs", + "ms", + "pv", + "nv", + "μv", + "mv", + "kv", + "mv", + "pw", + "nw", + "μw", + "mw", + "kw", + "mw", + "kω", + "mω", + None, + "bq", + "cc", + "cd", + "c∕kg", + None, + "db", + "gy", + "ha", + "hp", + "in", + "kk", + "km", + "kt", + "lm", + "ln", + "log", + "lx", + "mb", + "mil", + "mol", + "ph", + None, + "ppm", + "pr", + "sr", + "sv", + "wb", + "v∕m", + "a∕m", + "1日", + "2日", + "3日", + "4日", + "5日", + "6日", + "7日", + "8日", + "9日", + "10日", + "11日", + "12日", + "13日", + "14日", + "15日", + "16日", + "17日", + "18日", + "19日", + "20日", + "21日", + "22日", + "23日", + "24日", + "25日", + "26日", + "27日", + "28日", + "29日", + "30日", + "31日", + "gal", + None, + None, + None, + None, + None, + None, + "ꙁ", + None, + "ꙃ", + None, + "ꙅ", + None, + "ꙇ", + None, + "ꙉ", + None, + "ꙋ", + None, + "ꙍ", + None, + "ꙏ", + None, + "ꙑ", + None, + "ꙓ", + None, + "ꙕ", + None, + "ꙗ", + None, + "ꙙ", + None, + "ꙛ", + None, + "ꙝ", + None, + "ꙟ", + None, + "ꙡ", + None, + "ꙣ", + None, + "ꙥ", + None, + "ꙧ", + None, + "ꙩ", + None, + "ꙫ", + None, + "ꙭ", + None, + "ꚁ", + None, + "ꚃ", + None, + "ꚅ", + None, + "ꚇ", + None, + "ꚉ", + None, + "ꚋ", + None, + "ꚍ", + None, + "ꚏ", + None, + "ꚑ", + None, + "ꚓ", + None, + "ꚕ", + None, + "ꚗ", + None, + "ꚙ", + None, + "ꚛ", + None, + "ъ", + "ь", + None, + None, + None, + "ꜣ", + None, + "ꜥ", + None, + "ꜧ", + None, + "ꜩ", + None, + "ꜫ", + None, + "ꜭ", + None, + "ꜯ", + None, + "ꜳ", + None, + "ꜵ", + None, + "ꜷ", + None, + "ꜹ", + None, + "ꜻ", + None, + "ꜽ", + None, + "ꜿ", + None, + "ꝁ", + None, + "ꝃ", + None, + "ꝅ", + None, + "ꝇ", + None, + "ꝉ", + None, + "ꝋ", + None, + "ꝍ", + None, + "ꝏ", + None, + "ꝑ", + None, + "ꝓ", + None, + "ꝕ", + None, + "ꝗ", + None, + "ꝙ", + None, + "ꝛ", + None, + "ꝝ", + None, + "ꝟ", + None, + "ꝡ", + None, + "ꝣ", + None, + "ꝥ", + None, + "ꝧ", + None, + "ꝩ", + None, + "ꝫ", + None, + "ꝭ", + None, + "ꝯ", + None, + "ꝯ", + None, + "ꝺ", + None, + "ꝼ", + None, + "ᵹ", + "ꝿ", + None, + "ꞁ", + None, + "ꞃ", + None, + "ꞅ", + None, + "ꞇ", + None, + "ꞌ", + None, + "ɥ", + None, + "ꞑ", + None, + "ꞓ", + None, + "ꞗ", + None, + "ꞙ", + None, + "ꞛ", + None, + "ꞝ", + None, + "ꞟ", + None, + "ꞡ", + None, + "ꞣ", + None, + "ꞥ", + None, + "ꞧ", + None, + "ꞩ", + None, + "ɦ", + "ɜ", + "ɡ", + "ɬ", + "ɪ", + None, + "ʞ", + "ʇ", + "ʝ", + "ꭓ", + "ꞵ", + None, + "ꞷ", + None, + "ꞹ", + None, + "ꞻ", + None, + "ꞽ", + None, + "ꞿ", + None, + "ꟁ", + None, + "ꟃ", + None, + "ꞔ", + "ʂ", + "ᶎ", + "ꟈ", + None, + "ꟊ", + None, + "ɤ", + "ꟍ", + None, + "꟏", + None, + "ꟑ", + None, + "ꟓ", + None, + "ꟕ", + None, + "ꟗ", + None, + "ꟙ", + None, + "ꟛ", + None, + "ƛ", + "ɷ", + None, + "ɼ", + None, + "s", + "c", + "f", + "q", + "ꟶ", + None, + "ħ", + "œ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ꜧ", + "ꬷ", + "ɫ", + "ꭒ", + None, + "ʍ", + None, + "ꭋ", + "ꭌ", + None, + "Ꭰ", + "Ꭱ", + "Ꭲ", + "Ꭳ", + "Ꭴ", + "Ꭵ", + "Ꭶ", + "Ꭷ", + "Ꭸ", + "Ꭹ", + "Ꭺ", + "Ꭻ", + "Ꭼ", + "Ꭽ", + "Ꭾ", + "Ꭿ", + "Ꮀ", + "Ꮁ", + "Ꮂ", + "Ꮃ", + "Ꮄ", + "Ꮅ", + "Ꮆ", + "Ꮇ", + "Ꮈ", + "Ꮉ", + "Ꮊ", + "Ꮋ", + "Ꮌ", + "Ꮍ", + "Ꮎ", + "Ꮏ", + "Ꮐ", + "Ꮑ", + "Ꮒ", + "Ꮓ", + "Ꮔ", + "Ꮕ", + "Ꮖ", + "Ꮗ", + "Ꮘ", + "Ꮙ", + "Ꮚ", + "Ꮛ", + "Ꮜ", + "Ꮝ", + "Ꮞ", + "Ꮟ", + "Ꮠ", + "Ꮡ", + "Ꮢ", + "Ꮣ", + "Ꮤ", + "Ꮥ", + "Ꮦ", + "Ꮧ", + "Ꮨ", + "Ꮩ", + "Ꮪ", + "Ꮫ", + "Ꮬ", + "Ꮭ", + "Ꮮ", + "Ꮯ", + "Ꮰ", + "Ꮱ", + "Ꮲ", + "Ꮳ", + "Ꮴ", + "Ꮵ", + "Ꮶ", + "Ꮷ", + "Ꮸ", + "Ꮹ", + "Ꮺ", + "Ꮻ", + "Ꮼ", + "Ꮽ", + "Ꮾ", + "Ꮿ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "豈", + "更", + "車", + "賈", + "滑", + "串", + "句", + "龜", + "契", + "金", + "喇", + "奈", + "懶", + "癩", + "羅", + "蘿", + "螺", + "裸", + "邏", + "樂", + "洛", + "烙", + "珞", + "落", + "酪", + "駱", + "亂", + "卵", + "欄", + "爛", + "蘭", + "鸞", + "嵐", + "濫", + "藍", + "襤", + "拉", + "臘", + "蠟", + "廊", + "朗", + "浪", + "狼", + "郎", + "來", + "冷", + "勞", + "擄", + "櫓", + "爐", + "盧", + "老", + "蘆", + "虜", + "路", + "露", + "魯", + "鷺", + "碌", + "祿", + "綠", + "菉", + "錄", + "鹿", + "論", + "壟", + "弄", + "籠", + "聾", + "牢", + "磊", + "賂", + "雷", + "壘", + "屢", + "樓", + "淚", + "漏", + "累", + "縷", + "陋", + "勒", + "肋", + "凜", + "凌", + "稜", + "綾", + "菱", + "陵", + "讀", + "拏", + "樂", + "諾", + "丹", + "寧", + "怒", + "率", + "異", + "北", + "磻", + "便", + "復", + "不", + "泌", + "數", + "索", + "參", + "塞", + "省", + "葉", + "說", + "殺", + "辰", + "沈", + "拾", + "若", + "掠", + "略", + "亮", + "兩", + "凉", + "梁", + "糧", + "良", + "諒", + "量", + "勵", + "呂", + "女", + "廬", + "旅", + "濾", + "礪", + "閭", + "驪", + "麗", + "黎", + "力", + "曆", + "歷", + "轢", + "年", + "憐", + "戀", + "撚", + "漣", + "煉", + "璉", + "秊", + "練", + "聯", + "輦", + "蓮", + "連", + "鍊", + "列", + "劣", + "咽", + "烈", + "裂", + "說", + "廉", + "念", + "捻", + "殮", + "簾", + "獵", + "令", + "囹", + "寧", + "嶺", + "怜", + "玲", + "瑩", + "羚", + "聆", + "鈴", + "零", + "靈", + "領", + "例", + "禮", + "醴", + "隸", + "惡", + "了", + "僚", + "寮", + "尿", + "料", + "樂", + "燎", + "療", + "蓼", + "遼", + "龍", + "暈", + "阮", + "劉", + "杻", + "柳", + "流", + "溜", + "琉", + "留", + "硫", + "紐", + "類", + "六", + "戮", + "陸", + "倫", + "崙", + "淪", + "輪", + "律", + "慄", + "栗", + "率", + "隆", + "利", + "吏", + "履", + "易", + "李", + "梨", + "泥", + "理", + "痢", + "罹", + "裏", + "裡", + "里", + "離", + "匿", + "溺", + "吝", + "燐", + "璘", + "藺", + "隣", + "鱗", + "麟", + "林", + "淋", + "臨", + "立", + "笠", + "粒", + "狀", + "炙", + "識", + "什", + "茶", + "刺", + "切", + "度", + "拓", + "糖", + "宅", + "洞", + "暴", + "輻", + "行", + "降", + "見", + "廓", + "兀", + "嗀", + None, + "塚", + None, + "晴", + None, + "凞", + "猪", + "益", + "礼", + "神", + "祥", + "福", + "靖", + "精", + "羽", + None, + "蘒", + None, + "諸", + None, + "逸", + "都", + None, + "飯", + "飼", + "館", + "鶴", + "郞", + "隷", + "侮", + "僧", + "免", + "勉", + "勤", + "卑", + "喝", + "嘆", + "器", + "塀", + "墨", + "層", + "屮", + "悔", + "慨", + "憎", + "懲", + "敏", + "既", + "暑", + "梅", + "海", + "渚", + "漢", + "煮", + "爫", + "琢", + "碑", + "社", + "祉", + "祈", + "祐", + "祖", + "祝", + "禍", + "禎", + "穀", + "突", + "節", + "練", + "縉", + "繁", + "署", + "者", + "臭", + "艹", + "著", + "褐", + "視", + "謁", + "謹", + "賓", + "贈", + "辶", + "逸", + "難", + "響", + "頻", + "恵", + "𤋮", + "舘", + None, + "並", + "况", + "全", + "侀", + "充", + "冀", + "勇", + "勺", + "喝", + "啕", + "喙", + "嗢", + "塚", + "墳", + "奄", + "奔", + "婢", + "嬨", + "廒", + "廙", + "彩", + "徭", + "惘", + "慎", + "愈", + "憎", + "慠", + "懲", + "戴", + "揄", + "搜", + "摒", + "敖", + "晴", + "朗", + "望", + "杖", + "歹", + "殺", + "流", + "滛", + "滋", + "漢", + "瀞", + "煮", + "瞧", + "爵", + "犯", + "猪", + "瑱", + "甆", + "画", + "瘝", + "瘟", + "益", + "盛", + "直", + "睊", + "着", + "磌", + "窱", + "節", + "类", + "絛", + "練", + "缾", + "者", + "荒", + "華", + "蝹", + "襁", + "覆", + "視", + "調", + "諸", + "請", + "謁", + "諾", + "諭", + "謹", + "變", + "贈", + "輸", + "遲", + "醙", + "鉶", + "陼", + "難", + "靖", + "韛", + "響", + "頋", + "頻", + "鬒", + "龜", + "𢡊", + "𢡄", + "𣏕", + "㮝", + "䀘", + "䀹", + "𥉉", + "𥳐", + "𧻓", + "齃", + "龎", + None, + "ff", + "fi", + "fl", + "ffi", + "ffl", + "st", + None, + "մն", + "մե", + "մի", + "վն", + "մխ", + None, + "יִ", + None, + "ײַ", + "ע", + "א", + "ד", + "ה", + "כ", + "ל", + "ם", + "ר", + "ת", + "+", + "שׁ", + "שׂ", + "שּׁ", + "שּׂ", + "אַ", + "אָ", + "אּ", + "בּ", + "גּ", + "דּ", + "הּ", + "וּ", + "זּ", + None, + "טּ", + "יּ", + "ךּ", + "כּ", + "לּ", + None, + "מּ", + None, + "נּ", + "סּ", + None, + "ףּ", + "פּ", + None, + "צּ", + "קּ", + "רּ", + "שּ", + "תּ", + "וֹ", + "בֿ", + "כֿ", + "פֿ", + "אל", + "ٱ", + "ٻ", + "پ", + "ڀ", + "ٺ", + "ٿ", + "ٹ", + "ڤ", + "ڦ", + "ڄ", + "ڃ", + "چ", + "ڇ", + "ڍ", + "ڌ", + "ڎ", + "ڈ", + "ژ", + "ڑ", + "ک", + "گ", + "ڳ", + "ڱ", + "ں", + "ڻ", + "ۀ", + "ہ", + "ھ", + "ے", + "ۓ", + None, + "ڭ", + "ۇ", + "ۆ", + "ۈ", + "ۇٴ", + "ۋ", + "ۅ", + "ۉ", + "ې", + "ى", + "ئا", + "ئە", + "ئو", + "ئۇ", + "ئۆ", + "ئۈ", + "ئې", + "ئى", + "ی", + "ئج", + "ئح", + "ئم", + "ئى", + "ئي", + "بج", + "بح", + "بخ", + "بم", + "بى", + "بي", + "تج", + "تح", + "تخ", + "تم", + "تى", + "تي", + "ثج", + "ثم", + "ثى", + "ثي", + "جح", + "جم", + "حج", + "حم", + "خج", + "خح", + "خم", + "سج", + "سح", + "سخ", + "سم", + "صح", + "صم", + "ضج", + "ضح", + "ضخ", + "ضم", + "طح", + "طم", + "ظم", + "عج", + "عم", + "غج", + "غم", + "فج", + "فح", + "فخ", + "فم", + "فى", + "في", + "قح", + "قم", + "قى", + "قي", + "كا", + "كج", + "كح", + "كخ", + "كل", + "كم", + "كى", + "كي", + "لج", + "لح", + "لخ", + "لم", + "لى", + "لي", + "مج", + "مح", + "مخ", + "مم", + "مى", + "مي", + "نج", + "نح", + "نخ", + "نم", + "نى", + "ني", + "هج", + "هم", + "هى", + "هي", + "يج", + "يح", + "يخ", + "يم", + "يى", + "يي", + "ذٰ", + "رٰ", + "ىٰ", + " ٌّ", + " ٍّ", + " َّ", + " ُّ", + " ِّ", + " ّٰ", + "ئر", + "ئز", + "ئم", + "ئن", + "ئى", + "ئي", + "بر", + "بز", + "بم", + "بن", + "بى", + "بي", + "تر", + "تز", + "تم", + "تن", + "تى", + "تي", + "ثر", + "ثز", + "ثم", + "ثن", + "ثى", + "ثي", + "فى", + "في", + "قى", + "قي", + "كا", + "كل", + "كم", + "كى", + "كي", + "لم", + "لى", + "لي", + "ما", + "مم", + "نر", + "نز", + "نم", + "نن", + "نى", + "ني", + "ىٰ", + "ير", + "يز", + "يم", + "ين", + "يى", + "يي", + "ئج", + "ئح", + "ئخ", + "ئم", + "ئه", + "بج", + "بح", + "بخ", + "بم", + "به", + "تج", + "تح", + "تخ", + "تم", + "ته", + "ثم", + "جح", + "جم", + "حج", + "حم", + "خج", + "خم", + "سج", + "سح", + "سخ", + "سم", + "صح", + "صخ", + "صم", + "ضج", + "ضح", + "ضخ", + "ضم", + "طح", + "ظم", + "عج", + "عم", + "غج", + "غم", + "فج", + "فح", + "فخ", + "فم", + "قح", + "قم", + "كج", + "كح", + "كخ", + "كل", + "كم", + "لج", + "لح", + "لخ", + "لم", + "له", + "مج", + "مح", + "مخ", + "مم", + "نج", + "نح", + "نخ", + "نم", + "نه", + "هج", + "هم", + "هٰ", + "يج", + "يح", + "يخ", + "يم", + "يه", + "ئم", + "ئه", + "بم", + "به", + "تم", + "ته", + "ثم", + "ثه", + "سم", + "سه", + "شم", + "شه", + "كل", + "كم", + "لم", + "نم", + "نه", + "يم", + "يه", + "ـَّ", + "ـُّ", + "ـِّ", + "طى", + "طي", + "عى", + "عي", + "غى", + "غي", + "سى", + "سي", + "شى", + "شي", + "حى", + "حي", + "جى", + "جي", + "خى", + "خي", + "صى", + "صي", + "ضى", + "ضي", + "شج", + "شح", + "شخ", + "شم", + "شر", + "سر", + "صر", + "ضر", + "طى", + "طي", + "عى", + "عي", + "غى", + "غي", + "سى", + "سي", + "شى", + "شي", + "حى", + "حي", + "جى", + "جي", + "خى", + "خي", + "صى", + "صي", + "ضى", + "ضي", + "شج", + "شح", + "شخ", + "شم", + "شر", + "سر", + "صر", + "ضر", + "شج", + "شح", + "شخ", + "شم", + "سه", + "شه", + "طم", + "سج", + "سح", + "سخ", + "شج", + "شح", + "شخ", + "طم", + "ظم", + "اً", + None, + "تجم", + "تحج", + "تحم", + "تخم", + "تمج", + "تمح", + "تمخ", + "جمح", + "حمي", + "حمى", + "سحج", + "سجح", + "سجى", + "سمح", + "سمج", + "سمم", + "صحح", + "صمم", + "شحم", + "شجي", + "شمخ", + "شمم", + "ضحى", + "ضخم", + "طمح", + "طمم", + "طمي", + "عجم", + "عمم", + "عمى", + "غمم", + "غمي", + "غمى", + "فخم", + "قمح", + "قمم", + "لحم", + "لحي", + "لحى", + "لجج", + "لخم", + "لمح", + "محج", + "محم", + "محي", + "مجح", + "مجم", + "مخج", + "مخم", + None, + "مجخ", + "همج", + "همم", + "نحم", + "نحى", + "نجم", + "نجى", + "نمي", + "نمى", + "يمم", + "بخي", + "تجي", + "تجى", + "تخي", + "تخى", + "تمي", + "تمى", + "جمي", + "جحى", + "جمى", + "سخى", + "صحي", + "شحي", + "ضحي", + "لجي", + "لمي", + "يحي", + "يجي", + "يمي", + "ممي", + "قمي", + "نحي", + "قمح", + "لحم", + "عمي", + "كمي", + "نجح", + "مخي", + "لجم", + "كمم", + "لجم", + "نجح", + "جحي", + "حجي", + "مجي", + "فمي", + "بحي", + "كمم", + "عجم", + "صمم", + "سخي", + "نجي", + None, + None, + "صلے", + "قلے", + "الله", + "اكبر", + "محمد", + "صلعم", + "رسول", + "عليه", + "وسلم", + "صلى", + "صلى الله عليه وسلم", + "جل جلاله", + "ریال", + None, + None, + ",", + "、", + None, + ":", + ";", + "!", + "?", + "〖", + "〗", + None, + None, + None, + "—", + "–", + "_", + "(", + ")", + "{", + "}", + "〔", + "〕", + "【", + "】", + "《", + "》", + "〈", + "〉", + "「", + "」", + "『", + "』", + None, + "[", + "]", + " ̅", + "_", + ",", + "、", + None, + ";", + ":", + "?", + "!", + "—", + "(", + ")", + "{", + "}", + "〔", + "〕", + "#", + "&", + "*", + "+", + "-", + "<", + ">", + "=", + None, + "\\", + "$", + "%", + "@", + None, + " ً", + "ـً", + " ٌ", + None, + " ٍ", + None, + " َ", + "ـَ", + " ُ", + "ـُ", + " ِ", + "ـِ", + " ّ", + "ـّ", + " ْ", + "ـْ", + "ء", + "آ", + "أ", + "ؤ", + "إ", + "ئ", + "ا", + "ب", + "ة", + "ت", + "ث", + "ج", + "ح", + "خ", + "د", + "ذ", + "ر", + "ز", + "س", + "ش", + "ص", + "ض", + "ط", + "ظ", + "ع", + "غ", + "ف", + "ق", + "ك", + "ل", + "م", + "ن", + "ه", + "و", + "ى", + "ي", + "لآ", + "لأ", + "لإ", + "لا", + None, + None, + None, + "!", + '"', + "#", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ":", + ";", + "<", + "=", + ">", + "?", + "@", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "[", + "\\", + "]", + "^", + "_", + "`", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "{", + "|", + "}", + "~", + "⦅", + "⦆", + ".", + "「", + "」", + "、", + "・", + "ヲ", + "ァ", + "ィ", + "ゥ", + "ェ", + "ォ", + "ャ", + "ュ", + "ョ", + "ッ", + "ー", + "ア", + "イ", + "ウ", + "エ", + "オ", + "カ", + "キ", + "ク", + "ケ", + "コ", + "サ", + "シ", + "ス", + "セ", + "ソ", + "タ", + "チ", + "ツ", + "テ", + "ト", + "ナ", + "ニ", + "ヌ", + "ネ", + "ノ", + "ハ", + "ヒ", + "フ", + "ヘ", + "ホ", + "マ", + "ミ", + "ム", + "メ", + "モ", + "ヤ", + "ユ", + "ヨ", + "ラ", + "リ", + "ル", + "レ", + "ロ", + "ワ", + "ン", + "゙", + "゚", + None, + "ᄀ", + "ᄁ", + "ᆪ", + "ᄂ", + "ᆬ", + "ᆭ", + "ᄃ", + "ᄄ", + "ᄅ", + "ᆰ", + "ᆱ", + "ᆲ", + "ᆳ", + "ᆴ", + "ᆵ", + "ᄚ", + "ᄆ", + "ᄇ", + "ᄈ", + "ᄡ", + "ᄉ", + "ᄊ", + "ᄋ", + "ᄌ", + "ᄍ", + "ᄎ", + "ᄏ", + "ᄐ", + "ᄑ", + "ᄒ", + None, + "ᅡ", + "ᅢ", + "ᅣ", + "ᅤ", + "ᅥ", + "ᅦ", + None, + "ᅧ", + "ᅨ", + "ᅩ", + "ᅪ", + "ᅫ", + "ᅬ", + None, + "ᅭ", + "ᅮ", + "ᅯ", + "ᅰ", + "ᅱ", + "ᅲ", + None, + "ᅳ", + "ᅴ", + "ᅵ", + None, + "¢", + "£", + "¬", + " ̄", + "¦", + "¥", + "₩", + None, + "│", + "←", + "↑", + "→", + "↓", + "■", + "○", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𐐨", + "𐐩", + "𐐪", + "𐐫", + "𐐬", + "𐐭", + "𐐮", + "𐐯", + "𐐰", + "𐐱", + "𐐲", + "𐐳", + "𐐴", + "𐐵", + "𐐶", + "𐐷", + "𐐸", + "𐐹", + "𐐺", + "𐐻", + "𐐼", + "𐐽", + "𐐾", + "𐐿", + "𐑀", + "𐑁", + "𐑂", + "𐑃", + "𐑄", + "𐑅", + "𐑆", + "𐑇", + "𐑈", + "𐑉", + "𐑊", + "𐑋", + "𐑌", + "𐑍", + "𐑎", + "𐑏", + None, + None, + None, + None, + "𐓘", + "𐓙", + "𐓚", + "𐓛", + "𐓜", + "𐓝", + "𐓞", + "𐓟", + "𐓠", + "𐓡", + "𐓢", + "𐓣", + "𐓤", + "𐓥", + "𐓦", + "𐓧", + "𐓨", + "𐓩", + "𐓪", + "𐓫", + "𐓬", + "𐓭", + "𐓮", + "𐓯", + "𐓰", + "𐓱", + "𐓲", + "𐓳", + "𐓴", + "𐓵", + "𐓶", + "𐓷", + "𐓸", + "𐓹", + "𐓺", + "𐓻", + None, + None, + None, + None, + None, + None, + None, + None, + "𐖗", + "𐖘", + "𐖙", + "𐖚", + "𐖛", + "𐖜", + "𐖝", + "𐖞", + "𐖟", + "𐖠", + "𐖡", + None, + "𐖣", + "𐖤", + "𐖥", + "𐖦", + "𐖧", + "𐖨", + "𐖩", + "𐖪", + "𐖫", + "𐖬", + "𐖭", + "𐖮", + "𐖯", + "𐖰", + "𐖱", + None, + "𐖳", + "𐖴", + "𐖵", + "𐖶", + "𐖷", + "𐖸", + "𐖹", + None, + "𐖻", + "𐖼", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ː", + "ˑ", + "æ", + "ʙ", + "ɓ", + None, + "ʣ", + "ꭦ", + "ʥ", + "ʤ", + "ɖ", + "ɗ", + "ᶑ", + "ɘ", + "ɞ", + "ʩ", + "ɤ", + "ɢ", + "ɠ", + "ʛ", + "ħ", + "ʜ", + "ɧ", + "ʄ", + "ʪ", + "ʫ", + "ɬ", + "𝼄", + "ꞎ", + "ɮ", + "𝼅", + "ʎ", + "𝼆", + "ø", + "ɶ", + "ɷ", + "q", + "ɺ", + "𝼈", + "ɽ", + "ɾ", + "ʀ", + "ʨ", + "ʦ", + "ꭧ", + "ʧ", + "ʈ", + "ⱱ", + None, + "ʏ", + "ʡ", + "ʢ", + "ʘ", + "ǀ", + "ǁ", + "ǂ", + "𝼊", + "𝼞", + "ʇ", + "ʖ", + "ʗ", + "ʞ", + "𝼋", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𐳀", + "𐳁", + "𐳂", + "𐳃", + "𐳄", + "𐳅", + "𐳆", + "𐳇", + "𐳈", + "𐳉", + "𐳊", + "𐳋", + "𐳌", + "𐳍", + "𐳎", + "𐳏", + "𐳐", + "𐳑", + "𐳒", + "𐳓", + "𐳔", + "𐳕", + "𐳖", + "𐳗", + "𐳘", + "𐳙", + "𐳚", + "𐳛", + "𐳜", + "𐳝", + "𐳞", + "𐳟", + "𐳠", + "𐳡", + "𐳢", + "𐳣", + "𐳤", + "𐳥", + "𐳦", + "𐳧", + "𐳨", + "𐳩", + "𐳪", + "𐳫", + "𐳬", + "𐳭", + "𐳮", + "𐳯", + "𐳰", + "𐳱", + "𐳲", + None, + None, + None, + None, + None, + None, + None, + None, + "𐵰", + "𐵱", + "𐵲", + "𐵳", + "𐵴", + "𐵵", + "𐵶", + "𐵷", + "𐵸", + "𐵹", + "𐵺", + "𐵻", + "𐵼", + "𐵽", + "𐵾", + "𐵿", + "𐶀", + "𐶁", + "𐶂", + "𐶃", + "𐶄", + "𐶅", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𑣀", + "𑣁", + "𑣂", + "𑣃", + "𑣄", + "𑣅", + "𑣆", + "𑣇", + "𑣈", + "𑣉", + "𑣊", + "𑣋", + "𑣌", + "𑣍", + "𑣎", + "𑣏", + "𑣐", + "𑣑", + "𑣒", + "𑣓", + "𑣔", + "𑣕", + "𑣖", + "𑣗", + "𑣘", + "𑣙", + "𑣚", + "𑣛", + "𑣜", + "𑣝", + "𑣞", + "𑣟", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𖹠", + "𖹡", + "𖹢", + "𖹣", + "𖹤", + "𖹥", + "𖹦", + "𖹧", + "𖹨", + "𖹩", + "𖹪", + "𖹫", + "𖹬", + "𖹭", + "𖹮", + "𖹯", + "𖹰", + "𖹱", + "𖹲", + "𖹳", + "𖹴", + "𖹵", + "𖹶", + "𖹷", + "𖹸", + "𖹹", + "𖹺", + "𖹻", + "𖹼", + "𖹽", + "𖹾", + "𖹿", + None, + None, + "𖺻", + "𖺼", + "𖺽", + "𖺾", + "𖺿", + "𖻀", + "𖻁", + "𖻂", + "𖻃", + "𖻄", + "𖻅", + "𖻆", + "𖻇", + "𖻈", + "𖻉", + "𖻊", + "𖻋", + "𖻌", + "𖻍", + "𖻎", + "𖻏", + "𖻐", + "𖻑", + "𖻒", + "𖻓", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "こと", + "トキ", + "トテ", + "ヨリ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𝅗𝅥", + "𝅘𝅥", + "𝅘𝅥𝅮", + "𝅘𝅥𝅯", + "𝅘𝅥𝅰", + "𝅘𝅥𝅱", + "𝅘𝅥𝅲", + None, + None, + None, + "𝆹𝅥", + "𝆺𝅥", + "𝆹𝅥𝅮", + "𝆺𝅥𝅮", + "𝆹𝅥𝅯", + "𝆺𝅥𝅯", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + None, + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + None, + "c", + "d", + None, + "g", + None, + "j", + "k", + None, + "n", + "o", + "p", + "q", + None, + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + None, + "f", + None, + "h", + "i", + "j", + "k", + "l", + "m", + "n", + None, + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + None, + "d", + "e", + "f", + "g", + None, + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + None, + "s", + "t", + "u", + "v", + "w", + "x", + "y", + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + None, + "d", + "e", + "f", + "g", + None, + "i", + "j", + "k", + "l", + "m", + None, + "o", + None, + "s", + "t", + "u", + "v", + "w", + "x", + "y", + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "ı", + "ȷ", + "ss", + None, + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "θ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∇", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∂", + "ε", + "θ", + "κ", + "φ", + "ρ", + "π", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "θ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∇", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∂", + "ε", + "θ", + "κ", + "φ", + "ρ", + "π", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "θ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∇", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∂", + "ε", + "θ", + "κ", + "φ", + "ρ", + "π", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "θ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∇", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∂", + "ε", + "θ", + "κ", + "φ", + "ρ", + "π", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "θ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∇", + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", + "∂", + "ε", + "θ", + "κ", + "φ", + "ρ", + "π", + "ϝ", + None, + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𝽁", + None, + "𝽉", + None, + "𝽋", + None, + "𝽎", + None, + "𝽒", + None, + "𝽩", + None, + "𝽫", + None, + "𝽭", + None, + "𝽯", + None, + "𝽳", + None, + "𝽵", + None, + "𝽷", + None, + "𝽹", + None, + "𝽻", + None, + "𝽽", + None, + "𝽿", + None, + None, + None, + "ss", + None, + None, + "𝽗", + "𝽘", + "𝽙", + "ɣ", + "ᴘ", + "ƀ", + "đ", + "ǥ", + "ł", + "ƚ", + "𝽐", + "𝼾", + "ɉ", + "ᵿ", + "𝼿", + "ƞ", + "ɼ", + "ȸ", + "ȹ", + "𝼫", + "𝼙", + "ʆ", + "ᶘ", + "𝼬", + "𝼜", + "ʓ", + "ᶚ", + "ᴅ", + "ᴀ", + "ᴇ", + "ɿ", + "ʅ", + "ʮ", + "ʯ", + "ȡ", + "ȴ", + "ȵ", + "ȶ", + "ψ", + "ω", + "ᶁ", + "ꞕ", + "ᶇ", + "ᶊ", + "ᶎ", + "ƈ", + "ƙ", + "ƥ", + "ʠ", + "ƭ", + "𝼉", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "а", + "б", + "в", + "г", + "д", + "е", + "ж", + "з", + "и", + "к", + "л", + "м", + "о", + "п", + "р", + "с", + "т", + "у", + "ф", + "х", + "ц", + "ч", + "ш", + "ы", + "э", + "ю", + "ꚉ", + "ә", + "і", + "ј", + "ө", + "ү", + "ӏ", + "а", + "б", + "в", + "г", + "д", + "е", + "ж", + "з", + "и", + "к", + "л", + "о", + "п", + "с", + "у", + "ф", + "х", + "ц", + "ч", + "ш", + "ъ", + "ы", + "ґ", + "і", + "ѕ", + "џ", + "ҫ", + "ꙑ", + "ұ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "𞤢", + "𞤣", + "𞤤", + "𞤥", + "𞤦", + "𞤧", + "𞤨", + "𞤩", + "𞤪", + "𞤫", + "𞤬", + "𞤭", + "𞤮", + "𞤯", + "𞤰", + "𞤱", + "𞤲", + "𞤳", + "𞤴", + "𞤵", + "𞤶", + "𞤷", + "𞤸", + "𞤹", + "𞤺", + "𞤻", + "𞤼", + "𞤽", + "𞤾", + "𞤿", + "𞥀", + "𞥁", + "𞥂", + "𞥃", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "ا", + "ب", + "ج", + "د", + None, + "و", + "ز", + "ح", + "ط", + "ي", + "ك", + "ل", + "م", + "ن", + "س", + "ع", + "ف", + "ص", + "ق", + "ر", + "ش", + "ت", + "ث", + "خ", + "ذ", + "ض", + "ظ", + "غ", + "ٮ", + "ں", + "ڡ", + "ٯ", + None, + "ب", + "ج", + None, + "ه", + None, + "ح", + None, + "ي", + "ك", + "ل", + "م", + "ن", + "س", + "ع", + "ف", + "ص", + "ق", + None, + "ش", + "ت", + "ث", + "خ", + None, + "ض", + None, + "غ", + None, + "ج", + None, + "ح", + None, + "ي", + None, + "ل", + None, + "ن", + "س", + "ع", + None, + "ص", + "ق", + None, + "ش", + None, + "خ", + None, + "ض", + None, + "غ", + None, + "ں", + None, + "ٯ", + None, + "ب", + "ج", + None, + "ه", + None, + "ح", + "ط", + "ي", + "ك", + None, + "م", + "ن", + "س", + "ع", + "ف", + "ص", + "ق", + None, + "ش", + "ت", + "ث", + "خ", + None, + "ض", + "ظ", + "غ", + "ٮ", + None, + "ڡ", + None, + "ا", + "ب", + "ج", + "د", + "ه", + "و", + "ز", + "ح", + "ط", + "ي", + None, + "ل", + "م", + "ن", + "س", + "ع", + "ف", + "ص", + "ق", + "ر", + "ش", + "ت", + "ث", + "خ", + "ذ", + "ض", + "ظ", + "غ", + None, + "ب", + "ج", + "د", + None, + "و", + "ز", + "ح", + "ط", + "ي", + None, + "ل", + "م", + "ن", + "س", + "ع", + "ف", + "ص", + "ق", + "ر", + "ش", + "ت", + "ث", + "خ", + "ذ", + "ض", + "ظ", + "غ", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "0,", + "1,", + "2,", + "3,", + "4,", + "5,", + "6,", + "7,", + "8,", + "9,", + None, + "(a)", + "(b)", + "(c)", + "(d)", + "(e)", + "(f)", + "(g)", + "(h)", + "(i)", + "(j)", + "(k)", + "(l)", + "(m)", + "(n)", + "(o)", + "(p)", + "(q)", + "(r)", + "(s)", + "(t)", + "(u)", + "(v)", + "(w)", + "(x)", + "(y)", + "(z)", + "〔s〕", + "c", + "r", + "cd", + "wz", + None, + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "hv", + "mv", + "sd", + "ss", + "ppv", + "wc", + None, + "mc", + "md", + "mr", + None, + "dj", + None, + None, + None, + "ほか", + "ココ", + "サ", + None, + "手", + "字", + "双", + "デ", + "二", + "多", + "解", + "天", + "交", + "映", + "無", + "料", + "前", + "後", + "再", + "新", + "初", + "終", + "生", + "販", + "声", + "吹", + "演", + "投", + "捕", + "一", + "三", + "遊", + "左", + "中", + "右", + "指", + "走", + "打", + "禁", + "空", + "合", + "満", + "有", + "月", + "申", + "割", + "営", + "配", + None, + "〔本〕", + "〔三〕", + "〔二〕", + "〔安〕", + "〔点〕", + "〔打〕", + "〔盗〕", + "〔勝〕", + "〔敗〕", + None, + "得", + "可", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + "丽", + "丸", + "乁", + "𠄢", + "你", + "侮", + "侻", + "倂", + "偺", + "備", + "僧", + "像", + "㒞", + "𠘺", + "免", + "兔", + "兤", + "具", + "𠔜", + "㒹", + "內", + "再", + "𠕋", + "冗", + "冤", + "仌", + "冬", + "况", + "𩇟", + "凵", + "刃", + "㓟", + "刻", + "剆", + "割", + "剷", + "㔕", + "勇", + "勉", + "勤", + "勺", + "包", + "匆", + "北", + "卉", + "卑", + "博", + "即", + "卽", + "卿", + "𠨬", + "灰", + "及", + "叟", + "𠭣", + "叫", + "叱", + "吆", + "咞", + "吸", + "呈", + "周", + "咢", + "哶", + "唐", + "啓", + "啣", + "善", + "喙", + "喫", + "喳", + "嗂", + "圖", + "嘆", + "圗", + "噑", + "噴", + "切", + "壮", + "城", + "埴", + "堍", + "型", + "堲", + "報", + "墬", + "𡓤", + "売", + "壷", + "夆", + "多", + "夢", + "奢", + "𡚨", + "𡛪", + "姬", + "娛", + "娧", + "姘", + "婦", + "㛮", + "㛼", + "嬈", + "嬾", + "𡧈", + "寃", + "寘", + "寧", + "寳", + "𡬘", + "寿", + "将", + "当", + "尢", + "㞁", + "屠", + "屮", + "峀", + "岍", + "𡷤", + "嵃", + "𡷦", + "嵮", + "嵫", + "嵼", + "巡", + "巢", + "㠯", + "巽", + "帨", + "帽", + "幩", + "㡢", + "𢆃", + "㡼", + "庰", + "庳", + "庶", + "廊", + "𪎒", + "廾", + "𢌱", + "舁", + "弢", + "㣇", + "𣊸", + "𦇚", + "形", + "彫", + "㣣", + "徚", + "忍", + "志", + "忹", + "悁", + "㤺", + "㤜", + "悔", + "𢛔", + "惇", + "慈", + "慌", + "慎", + "慌", + "慺", + "憎", + "憲", + "憤", + "憯", + "懞", + "懲", + "懶", + "成", + "戛", + "扝", + "抱", + "拔", + "捐", + "𢬌", + "挽", + "拼", + "捨", + "掃", + "揤", + "𢯱", + "搢", + "揅", + "掩", + "㨮", + "摩", + "摾", + "撝", + "摷", + "㩬", + "敏", + "敬", + "𣀊", + "旣", + "書", + "晉", + "㬙", + "暑", + "㬈", + "㫤", + "冒", + "冕", + "最", + "暜", + "肭", + "䏙", + "朗", + "望", + "朡", + "杞", + "杓", + "𣏃", + "㭉", + "柺", + "枅", + "桒", + "梅", + "𣑭", + "梎", + "栟", + "椔", + "㮝", + "楂", + "榣", + "槪", + "檨", + "𣚣", + "櫛", + "㰘", + "次", + "𣢧", + "歔", + "㱎", + "歲", + "殟", + "殺", + "殻", + "𣪍", + "𡴋", + "𣫺", + "汎", + "𣲼", + "沿", + "泍", + "汧", + "洖", + "派", + "海", + "流", + "浩", + "浸", + "涅", + "𣴞", + "洴", + "港", + "湮", + "㴳", + "滋", + "滇", + "𣻑", + "淹", + "潮", + "𣽞", + "𣾎", + "濆", + "瀹", + "瀞", + "瀛", + "㶖", + "灊", + "災", + "灷", + "炭", + "𠔥", + "煅", + "𤉣", + "熜", + "𤎫", + "爨", + "爵", + "牐", + "𤘈", + "犀", + "犕", + "𤜵", + "𤠔", + "獺", + "王", + "㺬", + "玥", + "㺸", + "瑇", + "瑜", + "瑱", + "璅", + "瓊", + "㼛", + "甤", + "𤰶", + "甾", + "𤲒", + "異", + "𢆟", + "瘐", + "𤾡", + "𤾸", + "𥁄", + "㿼", + "䀈", + "直", + "𥃳", + "𥃲", + "𥄙", + "𥄳", + "眞", + "真", + "睊", + "䀹", + "瞋", + "䁆", + "䂖", + "𥐝", + "硎", + "碌", + "磌", + "䃣", + "𥘦", + "祖", + "𥚚", + "𥛅", + "福", + "秫", + "䄯", + "穀", + "穊", + "穏", + "𥥼", + "𥪧", + "竮", + "䈂", + "𥮫", + "篆", + "築", + "䈧", + "𥲀", + "糒", + "䊠", + "糨", + "糣", + "紀", + "𥾆", + "絣", + "䌁", + "緇", + "縂", + "繅", + "䌴", + "𦈨", + "𦉇", + "䍙", + "𦋙", + "罺", + "𦌾", + "羕", + "翺", + "者", + "𦓚", + "𦔣", + "聠", + "𦖨", + "聰", + "𣍟", + "䏕", + "育", + "脃", + "䐋", + "脾", + "媵", + "𦞧", + "𦞵", + "𣎓", + "𣎜", + "舁", + "舄", + "辞", + "䑫", + "芑", + "芋", + "芝", + "劳", + "花", + "芳", + "芽", + "苦", + "𦬼", + "若", + "茝", + "荣", + "莭", + "茣", + "莽", + "菧", + "著", + "荓", + "菊", + "菌", + "菜", + "𦰶", + "𦵫", + "𦳕", + "䔫", + "蓱", + "蓳", + "蔖", + "𧏊", + "蕤", + "𦼬", + "䕝", + "䕡", + "𦾱", + "𧃒", + "䕫", + "虐", + "虜", + "虧", + "虩", + "蚩", + "蚈", + "蜎", + "蛢", + "蝹", + "蜨", + "蝫", + "螆", + "䗗", + "蟡", + "蠁", + "䗹", + "衠", + "衣", + "𧙧", + "裗", + "裞", + "䘵", + "裺", + "㒻", + "𧢮", + "𧥦", + "䚾", + "䛇", + "誠", + "諭", + "變", + "豕", + "𧲨", + "貫", + "賁", + "贛", + "起", + "𧼯", + "𠠄", + "跋", + "趼", + "跰", + "𠣞", + "軔", + "輸", + "𨗒", + "𨗭", + "邔", + "郱", + "鄑", + "𨜮", + "鄛", + "鈸", + "鋗", + "鋘", + "鉼", + "鏹", + "鐕", + "𨯺", + "開", + "䦕", + "閷", + "𨵷", + "䧦", + "雃", + "嶲", + "霣", + "𩅅", + "𩈚", + "䩮", + "䩶", + "韠", + "𩐊", + "䪲", + "𩒖", + "頋", + "頩", + "𩖶", + "飢", + "䬳", + "餩", + "馧", + "駂", + "駾", + "䯎", + "𩬰", + "鬒", + "鱀", + "鳽", + "䳎", + "䳭", + "鵧", + "𪃎", + "䳸", + "𪄅", + "𪈎", + "𪊑", + "麻", + "䵖", + "黹", + "黾", + "鼅", + "鼏", + "鼖", + "鼻", + "𪘀", + None, + None, + None, + None, + None, + None, + None, + None, + None, +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA new file mode 100644 index 0000000..ba828d2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA @@ -0,0 +1,170 @@ +Metadata-Version: 2.4 +Name: jsonschema +Version: 4.26.0 +Summary: An implementation of JSON Schema validation for Python +Project-URL: Homepage, https://github.com/python-jsonschema/jsonschema +Project-URL: Documentation, https://python-jsonschema.readthedocs.io/ +Project-URL: Issues, https://github.com/python-jsonschema/jsonschema/issues/ +Project-URL: Funding, https://github.com/sponsors/Julian +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-jsonschema&utm_medium=referral&utm_campaign=pypi-link +Project-URL: Changelog, https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst +Project-URL: Source, https://github.com/python-jsonschema/jsonschema +Author-email: Julian Berman +License-Expression: MIT +License-File: COPYING +Keywords: data validation,json,json schema,jsonschema,validation +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: File Formats :: JSON +Classifier: Topic :: File Formats :: JSON :: JSON Schema +Requires-Python: >=3.10 +Requires-Dist: attrs>=22.2.0 +Requires-Dist: jsonschema-specifications>=2023.03.6 +Requires-Dist: referencing>=0.28.4 +Requires-Dist: rpds-py>=0.25.0 +Provides-Extra: format +Requires-Dist: fqdn; extra == 'format' +Requires-Dist: idna; extra == 'format' +Requires-Dist: isoduration; extra == 'format' +Requires-Dist: jsonpointer>1.13; extra == 'format' +Requires-Dist: rfc3339-validator; extra == 'format' +Requires-Dist: rfc3987; extra == 'format' +Requires-Dist: uri-template; extra == 'format' +Requires-Dist: webcolors>=1.11; extra == 'format' +Provides-Extra: format-nongpl +Requires-Dist: fqdn; extra == 'format-nongpl' +Requires-Dist: idna; extra == 'format-nongpl' +Requires-Dist: isoduration; extra == 'format-nongpl' +Requires-Dist: jsonpointer>1.13; extra == 'format-nongpl' +Requires-Dist: rfc3339-validator; extra == 'format-nongpl' +Requires-Dist: rfc3986-validator>0.1.0; extra == 'format-nongpl' +Requires-Dist: rfc3987-syntax>=1.1.0; extra == 'format-nongpl' +Requires-Dist: uri-template; extra == 'format-nongpl' +Requires-Dist: webcolors>=24.6.0; extra == 'format-nongpl' +Description-Content-Type: text/x-rst + +========== +jsonschema +========== + +|PyPI| |Pythons| |CI| |ReadTheDocs| |Precommit| |Zenodo| + +.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg + :alt: PyPI version + :target: https://pypi.org/project/jsonschema/ + +.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg + :alt: Supported Python versions + :target: https://pypi.org/project/jsonschema/ + +.. |CI| image:: https://github.com/python-jsonschema/jsonschema/workflows/CI/badge.svg + :alt: Build status + :target: https://github.com/python-jsonschema/jsonschema/actions?query=workflow%3ACI + +.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat + :alt: ReadTheDocs status + :target: https://python-jsonschema.readthedocs.io/en/stable/ + +.. |Precommit| image:: https://results.pre-commit.ci/badge/github/python-jsonschema/jsonschema/main.svg + :alt: pre-commit.ci status + :target: https://results.pre-commit.ci/latest/github/python-jsonschema/jsonschema/main + +.. |Zenodo| image:: https://zenodo.org/badge/3072629.svg + :alt: Zenodo DOI + :target: https://zenodo.org/badge/latestdoi/3072629 + + +``jsonschema`` is an implementation of the `JSON Schema `_ specification for Python. + +.. code:: python + + >>> from jsonschema import validate + + >>> # A sample schema, like what we'd get from json.load() + >>> schema = { + ... "type" : "object", + ... "properties" : { + ... "price" : {"type" : "number"}, + ... "name" : {"type" : "string"}, + ... }, + ... } + + >>> # If no exception is raised by validate(), the instance is valid. + >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema) + + >>> validate( + ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema, + ... ) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValidationError: 'Invalid' is not of type 'number' + +It can also be used from the command line by installing `check-jsonschema `_. + +Features +-------- + +* Full support for `Draft 2020-12 `_, `Draft 2019-09 `_, `Draft 7 `_, `Draft 6 `_, `Draft 4 `_ and `Draft 3 `_ + +* `Lazy validation `_ that can iteratively report *all* validation errors. + +* `Programmatic querying `_ of which properties or items failed validation. + + +Installation +------------ + +``jsonschema`` is available on `PyPI `_. You can install using `pip `_: + +.. code:: bash + + $ pip install jsonschema + + +Extras +====== + +Two extras are available when installing the package, both currently related to ``format`` validation: + + * ``format`` + * ``format-nongpl`` + +They can be used when installing in order to include additional dependencies, e.g.: + +.. code:: bash + + $ pip install jsonschema'[format]' + +Be aware that the mere presence of these dependencies – or even the specification of ``format`` checks in a schema – do *not* activate format checks (as per the specification). +Please read the `format validation documentation `_ for further details. + +About +----- + +I'm Julian Berman. + +``jsonschema`` is on `GitHub `_. + +Get in touch, via GitHub or otherwise, if you've got something to contribute, it'd be most welcome! + +If you feel overwhelmingly grateful, you can also `sponsor me `_. + +And for companies who appreciate ``jsonschema`` and its continued support and growth, ``jsonschema`` is also now supportable via `TideLift `_. + + +Release Information +------------------- + +v4.26.0 +======= + +* Decrease import time by delaying importing of ``urllib.request`` (#1416). diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD new file mode 100644 index 0000000..aa5b1a8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD @@ -0,0 +1,43 @@ +jsonschema/__init__.py,sha256=p-Rw4TS_0OPHZIJyImDWsdWgmd6CPWHMXLq7BuQxTGc,3941 +jsonschema/__main__.py,sha256=iLsZf2upUB3ilBKTlMnyK-HHt2Cnnfkwwxi_c6gLvSA,115 +jsonschema/_format.py,sha256=lrs1U7iSB2ZX9Z2EalsROYeQm1d9kefw2CLo08G8P4s,15568 +jsonschema/_keywords.py,sha256=r8_DrqAfn6QLwQnmXEggveiSU-UaIL2p2nuPINelfFc,14949 +jsonschema/_legacy_keywords.py,sha256=2tWuwRPWbYS7EAl8wBIC_rabGuv1J4dfYLqNEPpShhA,15191 +jsonschema/_types.py,sha256=f_nkTDbrja3E3fsINTC73y_gMXTU210I_H7qK0NN30c,5456 +jsonschema/_typing.py,sha256=pXLnGy3jNtIUTq037kUfq0we17085eHtJwDJ2t--mQE,617 +jsonschema/_utils.py,sha256=Xv6_wKKslBJlwyj9-j2c8JDFw-4z4aWFnVe2pX8h7U4,10659 +jsonschema/cli.py,sha256=av90OtpSxuiko3FAyEHtxpI-NSuX3WMtoQpIx09obJY,8445 +jsonschema/exceptions.py,sha256=b42hUDOfPFcprI4ZlNpjDeLKv8k9vOhgWct2jyDlxzk,15256 +jsonschema/protocols.py,sha256=lgyryVHqFijSFRAz95ch3VN2Fz-oTFyhj9Obcy5P_CI,7198 +jsonschema/validators.py,sha256=l_FxuZW0sMoFUwqTZqElL7F1P5nzy3sQaLbZqrEJb6U,47159 +jsonschema/benchmarks/__init__.py,sha256=A0sQrxDBVHSyQ-8ru3L11hMXf3q9gVuB9x_YgHb4R9M,70 +jsonschema/benchmarks/const_vs_enum.py,sha256=DVFi3WDqBalZFOibnjpX1uTSr3Rxa2cPgFcowd7Ukrs,830 +jsonschema/benchmarks/contains.py,sha256=gexQoUrCOwECofbt19BeosQZ7WFL6PDdkX49DWwBlOg,786 +jsonschema/benchmarks/import_benchmark.py,sha256=2DRIOVS6p-bqLn_822B_X5FkIRvsJ2V1CYnV2druzDs,779 +jsonschema/benchmarks/issue232.py,sha256=3LLYLIlBGQnVuyyo2iAv-xky5P6PRFHANx4-zIIQOoE,521 +jsonschema/benchmarks/json_schema_test_suite.py,sha256=PvfabpUYcF4_7csYDTcTauED8rnFEGYbdY5RqTXD08s,320 +jsonschema/benchmarks/nested_schemas.py,sha256=mo07dx-CIgmSOI62CNs4g5xu1FzHklLBpkQoDxWYcKs,1892 +jsonschema/benchmarks/subcomponents.py,sha256=fEyiMzsWeK2pd7DEGCuuY-vzGunwhHczRBWEnBRLKIo,1113 +jsonschema/benchmarks/unused_registry.py,sha256=hwRwONc9cefPtYzkoX_TYRO3GyUojriv0-YQaK3vnj0,940 +jsonschema/benchmarks/useless_applicator_schemas.py,sha256=EVm5-EtOEFoLP_Vt2j4SrCwlx05NhPqNuZQ6LIMP1Dc,3342 +jsonschema/benchmarks/useless_keywords.py,sha256=bj_zKr1oVctFlqyZaObCsYTgFjiiNgPzC0hr1Y868mE,867 +jsonschema/benchmarks/validator_creation.py,sha256=UkUQlLAnussnr_KdCIdad6xx2pXxQLmYtsXoiirKeWQ,285 +jsonschema/benchmarks/issue232/issue.json,sha256=eaPOZjMRu5u8RpKrsA9uk7ucPZS5tkKG4D_hkOTQ3Hk,117105 +jsonschema/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jsonschema/tests/_suite.py,sha256=2k0X91N7dOHhQc5mrYv40OKf1weioj6RMBqWgLT6-PI,8374 +jsonschema/tests/fuzz_validate.py,sha256=fUA7yTJIihaCwJplkUehZeyB84HcXEcqtY5oPJXIO7I,1114 +jsonschema/tests/test_cli.py,sha256=A89r5LOHy-peLPZA5YDkOaMTWqzQO_w2Tu8WFz_vphM,28544 +jsonschema/tests/test_deprecations.py,sha256=yG6mkRJHpTHbWoxpLC5y5H7fk8erGOs8f_9V4tCBEh8,15754 +jsonschema/tests/test_exceptions.py,sha256=rsEqdjknUz3dEyJb6_DiModPHXsJTEWzDrxUscg8G5o,24301 +jsonschema/tests/test_format.py,sha256=eVm5SMaWF2lOPO28bPAwNvkiQvHCQKy-MnuAgEchfEc,3188 +jsonschema/tests/test_jsonschema_test_suite.py,sha256=tAfxknM65OR9LyDPHu1pkEaombLgjRLnJ6FPiWPdxjg,8461 +jsonschema/tests/test_types.py,sha256=cF51KTDmdsx06MrIc4fXKt0X9fIsVgw5uhT8CamVa8U,6977 +jsonschema/tests/test_utils.py,sha256=sao74o1PyYMxBfqweokQN48CFSS6yhJk5FkCfMJ5PsI,4163 +jsonschema/tests/test_validators.py,sha256=uzl-agyLoZPTufouIHSxFPfPYrdIdjNwjHcD_5NyU4E,87949 +jsonschema/tests/typing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py,sha256=I5XUl5ZYUSZo3APkF10l8Mnfk09oXKqvXWttGGd3sDk,1238 +jsonschema-4.26.0.dist-info/METADATA,sha256=BbGJhmypipbaOTS_a4NofO0_-FMlJzt34WtwX22LJtI,7592 +jsonschema-4.26.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87 +jsonschema-4.26.0.dist-info/entry_points.txt,sha256=vO7rX4Fs_xIVJy2pnAtKgTSxfpnozAVQ0DjCmpMxnWE,51 +jsonschema-4.26.0.dist-info/licenses/COPYING,sha256=T5KgFaE8TRoEC-8BiqE0MLTxvHO0Gxa7hGw0Z2bedDk,1057 +jsonschema-4.26.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL new file mode 100644 index 0000000..ae8ec1b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.28.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt new file mode 100644 index 0000000..eecef9d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +jsonschema = jsonschema.cli:main diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING new file mode 100644 index 0000000..af9cfbd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2013 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py new file mode 100644 index 0000000..d8dec8c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py @@ -0,0 +1,120 @@ +""" +An implementation of JSON Schema for Python. + +The main functionality is provided by the validator classes for each of the +supported JSON Schema versions. + +Most commonly, `jsonschema.validators.validate` is the quickest way to simply +validate a given instance under a schema, and will create a validator +for you. +""" +import warnings + +from jsonschema._format import FormatChecker +from jsonschema._types import TypeChecker +from jsonschema.exceptions import SchemaError, ValidationError +from jsonschema.validators import ( + Draft3Validator, + Draft4Validator, + Draft6Validator, + Draft7Validator, + Draft201909Validator, + Draft202012Validator, + validate, +) + + +def __getattr__(name): + if name == "__version__": + warnings.warn( + "Accessing jsonschema.__version__ is deprecated and will be " + "removed in a future release. Use importlib.metadata directly " + "to query for jsonschema's version.", + DeprecationWarning, + stacklevel=2, + ) + + from importlib import metadata + return metadata.version("jsonschema") + elif name == "RefResolver": + from jsonschema.validators import _RefResolver + warnings.warn( + _RefResolver._DEPRECATION_MESSAGE, + DeprecationWarning, + stacklevel=2, + ) + return _RefResolver + elif name == "ErrorTree": + warnings.warn( + "Importing ErrorTree directly from the jsonschema package " + "is deprecated and will become an ImportError. Import it from " + "jsonschema.exceptions instead.", + DeprecationWarning, + stacklevel=2, + ) + from jsonschema.exceptions import ErrorTree + return ErrorTree + elif name == "FormatError": + warnings.warn( + "Importing FormatError directly from the jsonschema package " + "is deprecated and will become an ImportError. Import it from " + "jsonschema.exceptions instead.", + DeprecationWarning, + stacklevel=2, + ) + from jsonschema.exceptions import FormatError + return FormatError + elif name == "Validator": + warnings.warn( + "Importing Validator directly from the jsonschema package " + "is deprecated and will become an ImportError. Import it from " + "jsonschema.protocols instead.", + DeprecationWarning, + stacklevel=2, + ) + from jsonschema.protocols import Validator + return Validator + elif name == "RefResolutionError": + from jsonschema.exceptions import _RefResolutionError + warnings.warn( + _RefResolutionError._DEPRECATION_MESSAGE, + DeprecationWarning, + stacklevel=2, + ) + return _RefResolutionError + + format_checkers = { + "draft3_format_checker": Draft3Validator, + "draft4_format_checker": Draft4Validator, + "draft6_format_checker": Draft6Validator, + "draft7_format_checker": Draft7Validator, + "draft201909_format_checker": Draft201909Validator, + "draft202012_format_checker": Draft202012Validator, + } + ValidatorForFormat = format_checkers.get(name) + if ValidatorForFormat is not None: + warnings.warn( + f"Accessing jsonschema.{name} is deprecated and will be " + "removed in a future release. Instead, use the FORMAT_CHECKER " + "attribute on the corresponding Validator.", + DeprecationWarning, + stacklevel=2, + ) + return ValidatorForFormat.FORMAT_CHECKER + + raise AttributeError(f"module {__name__} has no attribute {name}") + + +__all__ = [ + "Draft3Validator", + "Draft4Validator", + "Draft6Validator", + "Draft7Validator", + "Draft201909Validator", + "Draft202012Validator", + "FormatChecker", + "SchemaError", + "TypeChecker", + "ValidationError", + "validate", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py new file mode 100644 index 0000000..fb260ae --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py @@ -0,0 +1,6 @@ +""" +The jsonschema CLI is now deprecated in favor of check-jsonschema. +""" +from jsonschema.cli import main + +main() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py new file mode 100644 index 0000000..5efda86 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py @@ -0,0 +1,546 @@ +from __future__ import annotations + +from contextlib import suppress +from datetime import date, datetime +from uuid import UUID +import ipaddress +import re +import typing +import warnings + +from jsonschema.exceptions import FormatError + +_FormatCheckCallable = typing.Callable[[object], bool] +#: A format checker callable. +_F = typing.TypeVar("_F", bound=_FormatCheckCallable) +_RaisesType = type[Exception] | tuple[type[Exception], ...] + +_RE_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$", re.ASCII) + + +class FormatChecker: + """ + A ``format`` property checker. + + JSON Schema does not mandate that the ``format`` property actually do any + validation. If validation is desired however, instances of this class can + be hooked into validators to enable format validation. + + `FormatChecker` objects always return ``True`` when asked about + formats that they do not know how to validate. + + To add a check for a custom format use the `FormatChecker.checks` + decorator. + + Arguments: + + formats: + + The known formats to validate. This argument can be used to + limit which formats will be used during validation. + + """ + + checkers: dict[ + str, + tuple[_FormatCheckCallable, _RaisesType], + ] = {} # noqa: RUF012 + + def __init__(self, formats: typing.Iterable[str] | None = None): + if formats is None: + formats = self.checkers.keys() + self.checkers = {k: self.checkers[k] for k in formats} + + def __repr__(self): + return f"" + + def checks( + self, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: + """ + Register a decorated function as validating a new format. + + Arguments: + + format: + + The format that the decorated function will check. + + raises: + + The exception(s) raised by the decorated function when an + invalid instance is found. + + The exception object will be accessible as the + `jsonschema.exceptions.ValidationError.cause` attribute of the + resulting validation error. + + """ + + def _checks(func: _F) -> _F: + self.checkers[format] = (func, raises) + return func + + return _checks + + @classmethod + def cls_checks( + cls, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: + warnings.warn( + ( + "FormatChecker.cls_checks is deprecated. Call " + "FormatChecker.checks on a specific FormatChecker instance " + "instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return cls._cls_checks(format=format, raises=raises) + + @classmethod + def _cls_checks( + cls, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: + def _checks(func: _F) -> _F: + cls.checkers[format] = (func, raises) + return func + + return _checks + + def check(self, instance: object, format: str) -> None: + """ + Check whether the instance conforms to the given format. + + Arguments: + + instance (*any primitive type*, i.e. str, number, bool): + + The instance to check + + format: + + The format that instance should conform to + + Raises: + + FormatError: + + if the instance does not conform to ``format`` + + """ + if format not in self.checkers: + return + + func, raises = self.checkers[format] + result, cause = None, None + try: + result = func(instance) + except raises as e: + cause = e + if not result: + raise FormatError(f"{instance!r} is not a {format!r}", cause=cause) + + def conforms(self, instance: object, format: str) -> bool: + """ + Check whether the instance conforms to the given format. + + Arguments: + + instance (*any primitive type*, i.e. str, number, bool): + + The instance to check + + format: + + The format that instance should conform to + + Returns: + + bool: whether it conformed + + """ + try: + self.check(instance, format) + except FormatError: + return False + else: + return True + + +draft3_format_checker = FormatChecker() +draft4_format_checker = FormatChecker() +draft6_format_checker = FormatChecker() +draft7_format_checker = FormatChecker() +draft201909_format_checker = FormatChecker() +draft202012_format_checker = FormatChecker() + +_draft_checkers: dict[str, FormatChecker] = dict( + draft3=draft3_format_checker, + draft4=draft4_format_checker, + draft6=draft6_format_checker, + draft7=draft7_format_checker, + draft201909=draft201909_format_checker, + draft202012=draft202012_format_checker, +) + + +def _checks_drafts( + name=None, + draft3=None, + draft4=None, + draft6=None, + draft7=None, + draft201909=None, + draft202012=None, + raises=(), +) -> typing.Callable[[_F], _F]: + draft3 = draft3 or name + draft4 = draft4 or name + draft6 = draft6 or name + draft7 = draft7 or name + draft201909 = draft201909 or name + draft202012 = draft202012 or name + + def wrap(func: _F) -> _F: + if draft3: + func = _draft_checkers["draft3"].checks(draft3, raises)(func) + if draft4: + func = _draft_checkers["draft4"].checks(draft4, raises)(func) + if draft6: + func = _draft_checkers["draft6"].checks(draft6, raises)(func) + if draft7: + func = _draft_checkers["draft7"].checks(draft7, raises)(func) + if draft201909: + func = _draft_checkers["draft201909"].checks(draft201909, raises)( + func, + ) + if draft202012: + func = _draft_checkers["draft202012"].checks(draft202012, raises)( + func, + ) + + # Oy. This is bad global state, but relied upon for now, until + # deprecation. See #519 and test_format_checkers_come_with_defaults + FormatChecker._cls_checks( + draft202012 or draft201909 or draft7 or draft6 or draft4 or draft3, + raises, + )(func) + return func + + return wrap + + +@_checks_drafts(name="idn-email") +@_checks_drafts(name="email") +def is_email(instance: object) -> bool: + if not isinstance(instance, str): + return True + return "@" in instance + + +@_checks_drafts( + draft3="ip-address", + draft4="ipv4", + draft6="ipv4", + draft7="ipv4", + draft201909="ipv4", + draft202012="ipv4", + raises=ipaddress.AddressValueError, +) +def is_ipv4(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(ipaddress.IPv4Address(instance)) + + +@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError) +def is_ipv6(instance: object) -> bool: + if not isinstance(instance, str): + return True + address = ipaddress.IPv6Address(instance) + return not getattr(address, "scope_id", "") + + +with suppress(ImportError): + from fqdn import FQDN + + @_checks_drafts( + draft3="host-name", + draft4="hostname", + draft6="hostname", + draft7="hostname", + draft201909="hostname", + draft202012="hostname", + # fqdn.FQDN("") raises a ValueError due to a bug + # however, it's not clear when or if that will be fixed, so catch it + # here for now + raises=ValueError, + ) + def is_host_name(instance: object) -> bool: + if not isinstance(instance, str): + return True + return FQDN(instance, min_labels=1).is_valid + + +with suppress(ImportError): + # The built-in `idna` codec only implements RFC 3890, so we go elsewhere. + import idna + + @_checks_drafts( + draft7="idn-hostname", + draft201909="idn-hostname", + draft202012="idn-hostname", + raises=(idna.IDNAError, UnicodeError), + ) + def is_idn_host_name(instance: object) -> bool: + if not isinstance(instance, str): + return True + idna.encode(instance) + return True + + +try: + import rfc3987 +except ImportError: + with suppress(ImportError): + from rfc3986_validator import validate_rfc3986 + + @_checks_drafts(name="uri") + def is_uri(instance: object) -> bool: + if not isinstance(instance, str): + return True + return validate_rfc3986(instance, rule="URI") + + @_checks_drafts( + draft6="uri-reference", + draft7="uri-reference", + draft201909="uri-reference", + draft202012="uri-reference", + raises=ValueError, + ) + def is_uri_reference(instance: object) -> bool: + if not isinstance(instance, str): + return True + return validate_rfc3986(instance, rule="URI_reference") + + with suppress(ImportError): + from rfc3987_syntax import is_valid_syntax as _rfc3987_is_valid_syntax + + @_checks_drafts( + draft7="iri", + draft201909="iri", + draft202012="iri", + raises=ValueError, + ) + def is_iri(instance: object) -> bool: + if not isinstance(instance, str): + return True + return _rfc3987_is_valid_syntax("iri", instance) + + @_checks_drafts( + draft7="iri-reference", + draft201909="iri-reference", + draft202012="iri-reference", + raises=ValueError, + ) + def is_iri_reference(instance: object) -> bool: + if not isinstance(instance, str): + return True + return _rfc3987_is_valid_syntax("iri_reference", instance) + +else: + + @_checks_drafts( + draft7="iri", + draft201909="iri", + draft202012="iri", + raises=ValueError, + ) + def is_iri(instance: object) -> bool: + if not isinstance(instance, str): + return True + return rfc3987.parse(instance, rule="IRI") + + @_checks_drafts( + draft7="iri-reference", + draft201909="iri-reference", + draft202012="iri-reference", + raises=ValueError, + ) + def is_iri_reference(instance: object) -> bool: + if not isinstance(instance, str): + return True + return rfc3987.parse(instance, rule="IRI_reference") + + @_checks_drafts(name="uri", raises=ValueError) + def is_uri(instance: object) -> bool: + if not isinstance(instance, str): + return True + return rfc3987.parse(instance, rule="URI") + + @_checks_drafts( + draft6="uri-reference", + draft7="uri-reference", + draft201909="uri-reference", + draft202012="uri-reference", + raises=ValueError, + ) + def is_uri_reference(instance: object) -> bool: + if not isinstance(instance, str): + return True + return rfc3987.parse(instance, rule="URI_reference") + + +with suppress(ImportError): + from rfc3339_validator import validate_rfc3339 + + @_checks_drafts(name="date-time") + def is_datetime(instance: object) -> bool: + if not isinstance(instance, str): + return True + return validate_rfc3339(instance.upper()) + + @_checks_drafts( + draft7="time", + draft201909="time", + draft202012="time", + ) + def is_time(instance: object) -> bool: + if not isinstance(instance, str): + return True + return is_datetime("1970-01-01T" + instance) + + +@_checks_drafts(name="regex", raises=re.error) +def is_regex(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(re.compile(instance)) + + +@_checks_drafts( + draft3="date", + draft7="date", + draft201909="date", + draft202012="date", + raises=ValueError, +) +def is_date(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(_RE_DATE.fullmatch(instance) and date.fromisoformat(instance)) + + +@_checks_drafts(draft3="time", raises=ValueError) +def is_draft3_time(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(datetime.strptime(instance, "%H:%M:%S")) # noqa: DTZ007 + + +with suppress(ImportError): + import webcolors + + @_checks_drafts(draft3="color", raises=(ValueError, TypeError)) + def is_css21_color(instance: object) -> bool: + if isinstance(instance, str): + try: + webcolors.name_to_hex(instance) + except ValueError: + webcolors.normalize_hex(instance.lower()) + return True + + +with suppress(ImportError): + import jsonpointer + + @_checks_drafts( + draft6="json-pointer", + draft7="json-pointer", + draft201909="json-pointer", + draft202012="json-pointer", + raises=jsonpointer.JsonPointerException, + ) + def is_json_pointer(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(jsonpointer.JsonPointer(instance)) + + # TODO: I don't want to maintain this, so it + # needs to go either into jsonpointer (pending + # https://github.com/stefankoegl/python-json-pointer/issues/34) or + # into a new external library. + @_checks_drafts( + draft7="relative-json-pointer", + draft201909="relative-json-pointer", + draft202012="relative-json-pointer", + raises=jsonpointer.JsonPointerException, + ) + def is_relative_json_pointer(instance: object) -> bool: + # Definition taken from: + # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3 + if not isinstance(instance, str): + return True + if not instance: + return False + + non_negative_integer, rest = [], "" + for i, character in enumerate(instance): + if character.isdigit(): + # digits with a leading "0" are not allowed + if i > 0 and int(instance[i - 1]) == 0: + return False + + non_negative_integer.append(character) + continue + + if not non_negative_integer: + return False + + rest = instance[i:] + break + return (rest == "#") or bool(jsonpointer.JsonPointer(rest)) + + +with suppress(ImportError): + import uri_template + + @_checks_drafts( + draft6="uri-template", + draft7="uri-template", + draft201909="uri-template", + draft202012="uri-template", + ) + def is_uri_template(instance: object) -> bool: + if not isinstance(instance, str): + return True + return uri_template.validate(instance) + + +with suppress(ImportError): + import isoduration + + @_checks_drafts( + draft201909="duration", + draft202012="duration", + raises=isoduration.DurationParsingException, + ) + def is_duration(instance: object) -> bool: + if not isinstance(instance, str): + return True + isoduration.parse_duration(instance) + # FIXME: See bolsote/isoduration#25 and bolsote/isoduration#21 + return instance.endswith(tuple("DMYWHMS")) + + +@_checks_drafts( + draft201909="uuid", + draft202012="uuid", + raises=ValueError, +) +def is_uuid(instance: object) -> bool: + if not isinstance(instance, str): + return True + UUID(instance) + return all(instance[position] == "-" for position in (8, 13, 18, 23)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py new file mode 100644 index 0000000..f30f954 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py @@ -0,0 +1,449 @@ +from fractions import Fraction +import re + +from jsonschema._utils import ( + ensure_list, + equal, + extras_msg, + find_additional_properties, + find_evaluated_item_indexes_by_schema, + find_evaluated_property_keys_by_schema, + uniq, +) +from jsonschema.exceptions import FormatError, ValidationError + + +def patternProperties(validator, patternProperties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for pattern, subschema in patternProperties.items(): + for k, v in instance.items(): + if re.search(pattern, k): + yield from validator.descend( + v, subschema, path=k, schema_path=pattern, + ) + + +def propertyNames(validator, propertyNames, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property in instance: + yield from validator.descend(instance=property, schema=propertyNames) + + +def additionalProperties(validator, aP, instance, schema): + if not validator.is_type(instance, "object"): + return + + extras = set(find_additional_properties(instance, schema)) + + if validator.is_type(aP, "object"): + for extra in extras: + yield from validator.descend(instance[extra], aP, path=extra) + elif not aP and extras: + if "patternProperties" in schema: + verb = "does" if len(extras) == 1 else "do" + joined = ", ".join(repr(each) for each in sorted(extras)) + patterns = ", ".join( + repr(each) for each in sorted(schema["patternProperties"]) + ) + error = f"{joined} {verb} not match any of the regexes: {patterns}" + yield ValidationError(error) + else: + error = "Additional properties are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(sorted(extras, key=str))) + + +def items(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + prefix = len(schema.get("prefixItems", [])) + total = len(instance) + extra = total - prefix + if extra <= 0: + return + + if items is False: + rest = instance[prefix:] if extra != 1 else instance[prefix] + item = "items" if prefix != 1 else "item" + yield ValidationError( + f"Expected at most {prefix} {item} but found {extra} " + f"extra: {rest!r}", + ) + else: + for index in range(prefix, total): + yield from validator.descend( + instance=instance[index], + schema=items, + path=index, + ) + + +def const(validator, const, instance, schema): + if not equal(instance, const): + yield ValidationError(f"{const!r} was expected") + + +def contains(validator, contains, instance, schema): + if not validator.is_type(instance, "array"): + return + + matches = 0 + min_contains = schema.get("minContains", 1) + max_contains = schema.get("maxContains", len(instance)) + + contains_validator = validator.evolve(schema=contains) + + for each in instance: + if contains_validator.is_valid(each): + matches += 1 + if matches > max_contains: + yield ValidationError( + "Too many items match the given schema " + f"(expected at most {max_contains})", + validator="maxContains", + validator_value=max_contains, + ) + return + + if matches < min_contains: + if not matches: + yield ValidationError( + f"{instance!r} does not contain items " + "matching the given schema", + ) + else: + yield ValidationError( + "Too few items match the given schema (expected at least " + f"{min_contains} but only {matches} matched)", + validator="minContains", + validator_value=min_contains, + ) + + +def exclusiveMinimum(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance <= minimum: + yield ValidationError( + f"{instance!r} is less than or equal to " + f"the minimum of {minimum!r}", + ) + + +def exclusiveMaximum(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance >= maximum: + yield ValidationError( + f"{instance!r} is greater than or equal " + f"to the maximum of {maximum!r}", + ) + + +def minimum(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance < minimum: + message = f"{instance!r} is less than the minimum of {minimum!r}" + yield ValidationError(message) + + +def maximum(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance > maximum: + message = f"{instance!r} is greater than the maximum of {maximum!r}" + yield ValidationError(message) + + +def multipleOf(validator, dB, instance, schema): + if not validator.is_type(instance, "number"): + return + + if isinstance(dB, float): + quotient = instance / dB + try: + failed = int(quotient) != quotient + except OverflowError: + # When `instance` is large and `dB` is less than one, + # quotient can overflow to infinity; and then casting to int + # raises an error. + # + # In this case we fall back to Fraction logic, which is + # exact and cannot overflow. The performance is also + # acceptable: we try the fast all-float option first, and + # we know that fraction(dB) can have at most a few hundred + # digits in each part. The worst-case slowdown is therefore + # for already-slow enormous integers or Decimals. + failed = (Fraction(instance) / Fraction(dB)).denominator != 1 + else: + failed = instance % dB + + if failed: + yield ValidationError(f"{instance!r} is not a multiple of {dB}") + + +def minItems(validator, mI, instance, schema): + if validator.is_type(instance, "array") and len(instance) < mI: + message = "should be non-empty" if mI == 1 else "is too short" + yield ValidationError(f"{instance!r} {message}") + + +def maxItems(validator, mI, instance, schema): + if validator.is_type(instance, "array") and len(instance) > mI: + message = "is expected to be empty" if mI == 0 else "is too long" + yield ValidationError(f"{instance!r} {message}") + + +def uniqueItems(validator, uI, instance, schema): + if ( + uI + and validator.is_type(instance, "array") + and not uniq(instance) + ): + yield ValidationError(f"{instance!r} has non-unique elements") + + +def pattern(validator, patrn, instance, schema): + if ( + validator.is_type(instance, "string") + and not re.search(patrn, instance) + ): + yield ValidationError(f"{instance!r} does not match {patrn!r}") + + +def format(validator, format, instance, schema): + if validator.format_checker is not None: + try: + validator.format_checker.check(instance, format) + except FormatError as error: + yield ValidationError(error.message, cause=error.cause) + + +def minLength(validator, mL, instance, schema): + if validator.is_type(instance, "string") and len(instance) < mL: + message = "should be non-empty" if mL == 1 else "is too short" + yield ValidationError(f"{instance!r} {message}") + + +def maxLength(validator, mL, instance, schema): + if validator.is_type(instance, "string") and len(instance) > mL: + message = "is expected to be empty" if mL == 0 else "is too long" + yield ValidationError(f"{instance!r} {message}") + + +def dependentRequired(validator, dependentRequired, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependentRequired.items(): + if property not in instance: + continue + + for each in dependency: + if each not in instance: + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + + +def dependentSchemas(validator, dependentSchemas, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependentSchemas.items(): + if property not in instance: + continue + yield from validator.descend( + instance, dependency, schema_path=property, + ) + + +def enum(validator, enums, instance, schema): + if all(not equal(each, instance) for each in enums): + yield ValidationError(f"{instance!r} is not one of {enums!r}") + + +def ref(validator, ref, instance, schema): + yield from validator._validate_reference(ref=ref, instance=instance) + + +def dynamicRef(validator, dynamicRef, instance, schema): + yield from validator._validate_reference(ref=dynamicRef, instance=instance) + + +def type(validator, types, instance, schema): + types = ensure_list(types) + + if not any(validator.is_type(instance, type) for type in types): + reprs = ", ".join(repr(type) for type in types) + yield ValidationError(f"{instance!r} is not of type {reprs}") + + +def properties(validator, properties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, subschema in properties.items(): + if property in instance: + yield from validator.descend( + instance[property], + subschema, + path=property, + schema_path=property, + ) + + +def required(validator, required, instance, schema): + if not validator.is_type(instance, "object"): + return + for property in required: + if property not in instance: + yield ValidationError(f"{property!r} is a required property") + + +def minProperties(validator, mP, instance, schema): + if validator.is_type(instance, "object") and len(instance) < mP: + message = ( + "should be non-empty" if mP == 1 + else "does not have enough properties" + ) + yield ValidationError(f"{instance!r} {message}") + + +def maxProperties(validator, mP, instance, schema): + if not validator.is_type(instance, "object"): + return + if validator.is_type(instance, "object") and len(instance) > mP: + message = ( + "is expected to be empty" if mP == 0 + else "has too many properties" + ) + yield ValidationError(f"{instance!r} {message}") + + +def allOf(validator, allOf, instance, schema): + for index, subschema in enumerate(allOf): + yield from validator.descend(instance, subschema, schema_path=index) + + +def anyOf(validator, anyOf, instance, schema): + all_errors = [] + for index, subschema in enumerate(anyOf): + errs = list(validator.descend(instance, subschema, schema_path=index)) + if not errs: + break + all_errors.extend(errs) + else: + yield ValidationError( + f"{instance!r} is not valid under any of the given schemas", + context=all_errors, + ) + + +def oneOf(validator, oneOf, instance, schema): + subschemas = enumerate(oneOf) + all_errors = [] + for index, subschema in subschemas: + errs = list(validator.descend(instance, subschema, schema_path=index)) + if not errs: + first_valid = subschema + break + all_errors.extend(errs) + else: + yield ValidationError( + f"{instance!r} is not valid under any of the given schemas", + context=all_errors, + ) + + more_valid = [ + each for _, each in subschemas + if validator.evolve(schema=each).is_valid(instance) + ] + if more_valid: + more_valid.append(first_valid) + reprs = ", ".join(repr(schema) for schema in more_valid) + yield ValidationError(f"{instance!r} is valid under each of {reprs}") + + +def not_(validator, not_schema, instance, schema): + if validator.evolve(schema=not_schema).is_valid(instance): + message = f"{instance!r} should not be valid under {not_schema!r}" + yield ValidationError(message) + + +def if_(validator, if_schema, instance, schema): + if validator.evolve(schema=if_schema).is_valid(instance): + if "then" in schema: + then = schema["then"] + yield from validator.descend(instance, then, schema_path="then") + elif "else" in schema: + else_ = schema["else"] + yield from validator.descend(instance, else_, schema_path="else") + + +def unevaluatedItems(validator, unevaluatedItems, instance, schema): + if not validator.is_type(instance, "array"): + return + evaluated_item_indexes = find_evaluated_item_indexes_by_schema( + validator, instance, schema, + ) + unevaluated_items = [ + item for index, item in enumerate(instance) + if index not in evaluated_item_indexes + ] + if unevaluated_items: + error = "Unevaluated items are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(unevaluated_items)) + + +def unevaluatedProperties(validator, unevaluatedProperties, instance, schema): + if not validator.is_type(instance, "object"): + return + evaluated_keys = find_evaluated_property_keys_by_schema( + validator, instance, schema, + ) + unevaluated_keys = [] + for property in instance: + if property not in evaluated_keys: + for _ in validator.descend( + instance[property], + unevaluatedProperties, + path=property, + schema_path=property, + ): + # FIXME: Include context for each unevaluated property + # indicating why it's invalid under the subschema. + unevaluated_keys.append(property) # noqa: PERF401 + + if unevaluated_keys: + if unevaluatedProperties is False: + error = "Unevaluated properties are not allowed (%s %s unexpected)" + extras = sorted(unevaluated_keys, key=str) + yield ValidationError(error % extras_msg(extras)) + else: + error = ( + "Unevaluated properties are not valid under " + "the given schema (%s %s unevaluated and invalid)" + ) + yield ValidationError(error % extras_msg(unevaluated_keys)) + + +def prefixItems(validator, prefixItems, instance, schema): + if not validator.is_type(instance, "array"): + return + + for (index, item), subschema in zip(enumerate(instance), prefixItems): + yield from validator.descend( + instance=item, + schema=subschema, + schema_path=index, + path=index, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py new file mode 100644 index 0000000..c691589 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py @@ -0,0 +1,449 @@ +import re + +from referencing.jsonschema import lookup_recursive_ref + +from jsonschema import _utils +from jsonschema.exceptions import ValidationError + + +def ignore_ref_siblings(schema): + """ + Ignore siblings of ``$ref`` if it is present. + + Otherwise, return all keywords. + + Suitable for use with `create`'s ``applicable_validators`` argument. + """ + ref = schema.get("$ref") + if ref is not None: + return [("$ref", ref)] + else: + return schema.items() + + +def dependencies_draft3(validator, dependencies, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependencies.items(): + if property not in instance: + continue + + if validator.is_type(dependency, "object"): + yield from validator.descend( + instance, dependency, schema_path=property, + ) + elif validator.is_type(dependency, "string"): + if dependency not in instance: + message = f"{dependency!r} is a dependency of {property!r}" + yield ValidationError(message) + else: + for each in dependency: + if each not in instance: + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + + +def dependencies_draft4_draft6_draft7( + validator, + dependencies, + instance, + schema, +): + """ + Support for the ``dependencies`` keyword from pre-draft 2019-09. + + In later drafts, the keyword was split into separate + ``dependentRequired`` and ``dependentSchemas`` validators. + """ + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependencies.items(): + if property not in instance: + continue + + if validator.is_type(dependency, "array"): + for each in dependency: + if each not in instance: + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + else: + yield from validator.descend( + instance, dependency, schema_path=property, + ) + + +def disallow_draft3(validator, disallow, instance, schema): + for disallowed in _utils.ensure_list(disallow): + if validator.evolve(schema={"type": [disallowed]}).is_valid(instance): + message = f"{disallowed!r} is disallowed for {instance!r}" + yield ValidationError(message) + + +def extends_draft3(validator, extends, instance, schema): + if validator.is_type(extends, "object"): + yield from validator.descend(instance, extends) + return + for index, subschema in enumerate(extends): + yield from validator.descend(instance, subschema, schema_path=index) + + +def items_draft3_draft4(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + if validator.is_type(items, "object"): + for index, item in enumerate(instance): + yield from validator.descend(item, items, path=index) + else: + for (index, item), subschema in zip(enumerate(instance), items): + yield from validator.descend( + item, subschema, path=index, schema_path=index, + ) + + +def additionalItems(validator, aI, instance, schema): + if ( + not validator.is_type(instance, "array") + or validator.is_type(schema.get("items", {}), "object") + ): + return + + len_items = len(schema.get("items", [])) + if validator.is_type(aI, "object"): + for index, item in enumerate(instance[len_items:], start=len_items): + yield from validator.descend(item, aI, path=index) + elif not aI and len(instance) > len(schema.get("items", [])): + error = "Additional items are not allowed (%s %s unexpected)" + yield ValidationError( + error % _utils.extras_msg(instance[len(schema.get("items", [])):]), + ) + + +def items_draft6_draft7_draft201909(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + if validator.is_type(items, "array"): + for (index, item), subschema in zip(enumerate(instance), items): + yield from validator.descend( + item, subschema, path=index, schema_path=index, + ) + else: + for index, item in enumerate(instance): + yield from validator.descend(item, items, path=index) + + +def minimum_draft3_draft4(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if schema.get("exclusiveMinimum", False): + failed = instance <= minimum + cmp = "less than or equal to" + else: + failed = instance < minimum + cmp = "less than" + + if failed: + message = f"{instance!r} is {cmp} the minimum of {minimum!r}" + yield ValidationError(message) + + +def maximum_draft3_draft4(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if schema.get("exclusiveMaximum", False): + failed = instance >= maximum + cmp = "greater than or equal to" + else: + failed = instance > maximum + cmp = "greater than" + + if failed: + message = f"{instance!r} is {cmp} the maximum of {maximum!r}" + yield ValidationError(message) + + +def properties_draft3(validator, properties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, subschema in properties.items(): + if property in instance: + yield from validator.descend( + instance[property], + subschema, + path=property, + schema_path=property, + ) + elif subschema.get("required", False): + error = ValidationError(f"{property!r} is a required property") + error._set( + validator="required", + validator_value=subschema["required"], + instance=instance, + schema=schema, + ) + error.path.appendleft(property) + error.schema_path.extend([property, "required"]) + yield error + + +def type_draft3(validator, types, instance, schema): + types = _utils.ensure_list(types) + + all_errors = [] + for index, type in enumerate(types): + if validator.is_type(type, "object"): + errors = list(validator.descend(instance, type, schema_path=index)) + if not errors: + return + all_errors.extend(errors) + elif validator.is_type(instance, type): + return + + reprs = [] + for type in types: + try: + reprs.append(repr(type["name"])) + except Exception: # noqa: BLE001 + reprs.append(repr(type)) + yield ValidationError( + f"{instance!r} is not of type {', '.join(reprs)}", + context=all_errors, + ) + + +def contains_draft6_draft7(validator, contains, instance, schema): + if not validator.is_type(instance, "array"): + return + + if not any( + validator.evolve(schema=contains).is_valid(element) + for element in instance + ): + yield ValidationError( + f"None of {instance!r} are valid under the given schema", + ) + + +def recursiveRef(validator, recursiveRef, instance, schema): + resolved = lookup_recursive_ref(validator._resolver) + yield from validator.descend( + instance, + resolved.contents, + resolver=resolved.resolver, + ) + + +def find_evaluated_item_indexes_by_schema(validator, instance, schema): + """ + Get all indexes of items that get evaluated under the current schema. + + Covers all keywords related to unevaluatedItems: items, prefixItems, if, + then, else, contains, unevaluatedItems, allOf, oneOf, anyOf + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_indexes = [] + + ref = schema.get("$ref") + if ref is not None: + resolved = validator._resolver.lookup(ref) + evaluated_indexes.extend( + find_evaluated_item_indexes_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + if "$recursiveRef" in schema: + resolved = lookup_recursive_ref(validator._resolver) + evaluated_indexes.extend( + find_evaluated_item_indexes_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + if "items" in schema: + if "additionalItems" in schema: + return list(range(len(instance))) + + if validator.is_type(schema["items"], "object"): + return list(range(len(instance))) + evaluated_indexes += list(range(len(schema["items"]))) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["then"], + ) + elif "else" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["else"], + ) + + for keyword in ["contains", "unevaluatedItems"]: + if keyword in schema: + for k, v in enumerate(instance): + if validator.evolve(schema=schema[keyword]).is_valid(v): + evaluated_indexes.append(k) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = next(validator.descend(instance, subschema), None) + if errs is None: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, subschema, + ) + + return evaluated_indexes + + +def unevaluatedItems_draft2019(validator, unevaluatedItems, instance, schema): + if not validator.is_type(instance, "array"): + return + evaluated_item_indexes = find_evaluated_item_indexes_by_schema( + validator, instance, schema, + ) + unevaluated_items = [ + item for index, item in enumerate(instance) + if index not in evaluated_item_indexes + ] + if unevaluated_items: + error = "Unevaluated items are not allowed (%s %s unexpected)" + yield ValidationError(error % _utils.extras_msg(unevaluated_items)) + + +def find_evaluated_property_keys_by_schema(validator, instance, schema): + if validator.is_type(schema, "boolean"): + return [] + evaluated_keys = [] + + ref = schema.get("$ref") + if ref is not None: + resolved = validator._resolver.lookup(ref) + evaluated_keys.extend( + find_evaluated_property_keys_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + if "$recursiveRef" in schema: + resolved = lookup_recursive_ref(validator._resolver) + evaluated_keys.extend( + find_evaluated_property_keys_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + for keyword in [ + "properties", "additionalProperties", "unevaluatedProperties", + ]: + if keyword in schema: + schema_value = schema[keyword] + if validator.is_type(schema_value, "boolean") and schema_value: + evaluated_keys += instance.keys() + + elif validator.is_type(schema_value, "object"): + for property in schema_value: + if property in instance: + evaluated_keys.append(property) + + if "patternProperties" in schema: + for property in instance: + for pattern in schema["patternProperties"]: + if re.search(pattern, property): + evaluated_keys.append(property) + + if "dependentSchemas" in schema: + for property, subschema in schema["dependentSchemas"].items(): + if property not in instance: + continue + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = next(validator.descend(instance, subschema), None) + if errs is None: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["then"], + ) + elif "else" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["else"], + ) + + return evaluated_keys + + +def unevaluatedProperties_draft2019(validator, uP, instance, schema): + if not validator.is_type(instance, "object"): + return + evaluated_keys = find_evaluated_property_keys_by_schema( + validator, instance, schema, + ) + unevaluated_keys = [] + for property in instance: + if property not in evaluated_keys: + for _ in validator.descend( + instance[property], + uP, + path=property, + schema_path=property, + ): + # FIXME: Include context for each unevaluated property + # indicating why it's invalid under the subschema. + unevaluated_keys.append(property) # noqa: PERF401 + + if unevaluated_keys: + if uP is False: + error = "Unevaluated properties are not allowed (%s %s unexpected)" + extras = sorted(unevaluated_keys, key=str) + yield ValidationError(error % _utils.extras_msg(extras)) + else: + error = ( + "Unevaluated properties are not valid under " + "the given schema (%s %s unevaluated and invalid)" + ) + yield ValidationError(error % _utils.extras_msg(unevaluated_keys)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py new file mode 100644 index 0000000..fcfe3f0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +import numbers + +from attrs import evolve, field, frozen +from rpds import HashTrieMap + +from jsonschema.exceptions import UndefinedTypeCheck + +if TYPE_CHECKING: + from collections.abc import Callable, Mapping + from typing import Any + + +# unfortunately, the type of HashTrieMap is generic, and if used as an attrs +# converter, the generic type is presented to mypy, which then fails to match +# the concrete type of a type checker mapping +# this "do nothing" wrapper presents the correct information to mypy +def _typed_map_converter( + init_val: Mapping[str, Callable[[TypeChecker, Any], bool]], +) -> HashTrieMap[str, Callable[[TypeChecker, Any], bool]]: + return HashTrieMap.convert(init_val) + + +def is_array(checker, instance): + return isinstance(instance, list) + + +def is_bool(checker, instance): + return isinstance(instance, bool) + + +def is_integer(checker, instance): + # bool inherits from int, so ensure bools aren't reported as ints + if isinstance(instance, bool): + return False + return isinstance(instance, int) + + +def is_null(checker, instance): + return instance is None + + +def is_number(checker, instance): + # bool inherits from int, so ensure bools aren't reported as ints + if isinstance(instance, bool): + return False + return isinstance(instance, numbers.Number) + + +def is_object(checker, instance): + return isinstance(instance, dict) + + +def is_string(checker, instance): + return isinstance(instance, str) + + +def is_any(checker, instance): + return True + + +@frozen(repr=False) +class TypeChecker: + """ + A :kw:`type` property checker. + + A `TypeChecker` performs type checking for a `Validator`, converting + between the defined JSON Schema types and some associated Python types or + objects. + + Modifying the behavior just mentioned by redefining which Python objects + are considered to be of which JSON Schema types can be done using + `TypeChecker.redefine` or `TypeChecker.redefine_many`, and types can be + removed via `TypeChecker.remove`. Each of these return a new `TypeChecker`. + + Arguments: + + type_checkers: + + The initial mapping of types to their checking functions. + + """ + + _type_checkers: HashTrieMap[ + str, Callable[[TypeChecker, Any], bool], + ] = field(default=HashTrieMap(), converter=_typed_map_converter) + + def __repr__(self): + types = ", ".join(repr(k) for k in sorted(self._type_checkers)) + return f"<{self.__class__.__name__} types={{{types}}}>" + + def is_type(self, instance, type: str) -> bool: + """ + Check if the instance is of the appropriate type. + + Arguments: + + instance: + + The instance to check + + type: + + The name of the type that is expected. + + Raises: + + `jsonschema.exceptions.UndefinedTypeCheck`: + + if ``type`` is unknown to this object. + + """ + try: + fn = self._type_checkers[type] + except KeyError: + raise UndefinedTypeCheck(type) from None + + return fn(self, instance) + + def redefine(self, type: str, fn) -> TypeChecker: + """ + Produce a new checker with the given type redefined. + + Arguments: + + type: + + The name of the type to check. + + fn (collections.abc.Callable): + + A callable taking exactly two parameters - the type + checker calling the function and the instance to check. + The function should return true if instance is of this + type and false otherwise. + + """ + return self.redefine_many({type: fn}) + + def redefine_many(self, definitions=()) -> TypeChecker: + """ + Produce a new checker with the given types redefined. + + Arguments: + + definitions (dict): + + A dictionary mapping types to their checking functions. + + """ + type_checkers = self._type_checkers.update(definitions) + return evolve(self, type_checkers=type_checkers) + + def remove(self, *types) -> TypeChecker: + """ + Produce a new checker with the given types forgotten. + + Arguments: + + types: + + the names of the types to remove. + + Raises: + + `jsonschema.exceptions.UndefinedTypeCheck`: + + if any given type is unknown to this object + + """ + type_checkers = self._type_checkers + for each in types: + try: + type_checkers = type_checkers.remove(each) + except KeyError: + raise UndefinedTypeCheck(each) from None + return evolve(self, type_checkers=type_checkers) + + +draft3_type_checker = TypeChecker( + { + "any": is_any, + "array": is_array, + "boolean": is_bool, + "integer": is_integer, + "object": is_object, + "null": is_null, + "number": is_number, + "string": is_string, + }, +) +draft4_type_checker = draft3_type_checker.remove("any") +draft6_type_checker = draft4_type_checker.redefine( + "integer", + lambda checker, instance: ( + is_integer(checker, instance) + or (isinstance(instance, float) and instance.is_integer()) + ), +) +draft7_type_checker = draft6_type_checker +draft201909_type_checker = draft7_type_checker +draft202012_type_checker = draft201909_type_checker diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py new file mode 100644 index 0000000..f8dda63 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py @@ -0,0 +1,29 @@ +""" +Some (initially private) typing helpers for jsonschema's types. +""" +from collections.abc import Callable, Iterable +from typing import Any, Protocol + +import referencing.jsonschema + +from jsonschema.protocols import Validator + + +class SchemaKeywordValidator(Protocol): + def __call__( + self, + validator: Validator, + value: Any, + instance: Any, + schema: referencing.jsonschema.Schema, + ) -> None: + ... + + +id_of = Callable[[referencing.jsonschema.Schema], str | None] + + +ApplicableValidators = Callable[ + [referencing.jsonschema.Schema], + Iterable[tuple[str, Any]], +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py new file mode 100644 index 0000000..84a0965 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py @@ -0,0 +1,355 @@ +from collections.abc import Mapping, MutableMapping, Sequence +from urllib.parse import urlsplit +import itertools +import re + + +class URIDict(MutableMapping): + """ + Dictionary which uses normalized URIs as keys. + """ + + def normalize(self, uri): + return urlsplit(uri).geturl() + + def __init__(self, *args, **kwargs): + self.store = dict() + self.store.update(*args, **kwargs) + + def __getitem__(self, uri): + return self.store[self.normalize(uri)] + + def __setitem__(self, uri, value): + self.store[self.normalize(uri)] = value + + def __delitem__(self, uri): + del self.store[self.normalize(uri)] + + def __iter__(self): + return iter(self.store) + + def __len__(self): # pragma: no cover -- untested, but to be removed + return len(self.store) + + def __repr__(self): # pragma: no cover -- untested, but to be removed + return repr(self.store) + + +class Unset: + """ + An as-of-yet unset attribute or unprovided default parameter. + """ + + def __repr__(self): # pragma: no cover + return "" + + +def format_as_index(container, indices): + """ + Construct a single string containing indexing operations for the indices. + + For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"] + + Arguments: + + container (str): + + A word to use for the thing being indexed + + indices (sequence): + + The indices to format. + + """ + if not indices: + return container + return f"{container}[{']['.join(repr(index) for index in indices)}]" + + +def find_additional_properties(instance, schema): + """ + Return the set of additional properties for the given ``instance``. + + Weeds out properties that should have been validated by ``properties`` and + / or ``patternProperties``. + + Assumes ``instance`` is dict-like already. + """ + properties = schema.get("properties", {}) + patterns = "|".join(schema.get("patternProperties", {})) + for property in instance: + if property not in properties: + if patterns and re.search(patterns, property): + continue + yield property + + +def extras_msg(extras): + """ + Create an error message for extra items or properties. + """ + verb = "was" if len(extras) == 1 else "were" + return ", ".join(repr(extra) for extra in extras), verb + + +def ensure_list(thing): + """ + Wrap ``thing`` in a list if it's a single str. + + Otherwise, return it unchanged. + """ + if isinstance(thing, str): + return [thing] + return thing + + +def _mapping_equal(one, two): + """ + Check if two mappings are equal using the semantics of `equal`. + """ + if len(one) != len(two): + return False + return all( + key in two and equal(value, two[key]) + for key, value in one.items() + ) + + +def _sequence_equal(one, two): + """ + Check if two sequences are equal using the semantics of `equal`. + """ + if len(one) != len(two): + return False + return all(equal(i, j) for i, j in zip(one, two)) + + +def equal(one, two): + """ + Check if two things are equal evading some Python type hierarchy semantics. + + Specifically in JSON Schema, evade `bool` inheriting from `int`, + recursing into sequences to do the same. + """ + if one is two: + return True + if isinstance(one, str) or isinstance(two, str): + return one == two + if isinstance(one, Sequence) and isinstance(two, Sequence): + return _sequence_equal(one, two) + if isinstance(one, Mapping) and isinstance(two, Mapping): + return _mapping_equal(one, two) + return unbool(one) == unbool(two) + + +def unbool(element, true=object(), false=object()): + """ + A hack to make True and 1 and False and 0 unique for ``uniq``. + """ + if element is True: + return true + elif element is False: + return false + return element + + +def uniq(container): + """ + Check if all of a container's elements are unique. + + Tries to rely on the container being recursively sortable, or otherwise + falls back on (slow) brute force. + """ + try: + sort = sorted(unbool(i) for i in container) + sliced = itertools.islice(sort, 1, None) + + for i, j in zip(sort, sliced): + if equal(i, j): + return False + + except (NotImplementedError, TypeError): + seen = [] + for e in container: + e = unbool(e) + + for i in seen: + if equal(i, e): + return False + + seen.append(e) + return True + + +def find_evaluated_item_indexes_by_schema(validator, instance, schema): + """ + Get all indexes of items that get evaluated under the current schema. + + Covers all keywords related to unevaluatedItems: items, prefixItems, if, + then, else, contains, unevaluatedItems, allOf, oneOf, anyOf + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_indexes = [] + + if "items" in schema: + return list(range(len(instance))) + + ref = schema.get("$ref") + if ref is not None: + resolved = validator._resolver.lookup(ref) + evaluated_indexes.extend( + find_evaluated_item_indexes_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + dynamicRef = schema.get("$dynamicRef") + if dynamicRef is not None: + resolved = validator._resolver.lookup(dynamicRef) + evaluated_indexes.extend( + find_evaluated_item_indexes_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + if "prefixItems" in schema: + evaluated_indexes += list(range(len(schema["prefixItems"]))) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["then"], + ) + elif "else" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["else"], + ) + + for keyword in ["contains", "unevaluatedItems"]: + if keyword in schema: + for k, v in enumerate(instance): + if validator.evolve(schema=schema[keyword]).is_valid(v): + evaluated_indexes.append(k) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = next(validator.descend(instance, subschema), None) + if errs is None: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, subschema, + ) + + return evaluated_indexes + + +def find_evaluated_property_keys_by_schema(validator, instance, schema): + """ + Get all keys of items that get evaluated under the current schema. + + Covers all keywords related to unevaluatedProperties: properties, + additionalProperties, unevaluatedProperties, patternProperties, + dependentSchemas, allOf, oneOf, anyOf, if, then, else + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_keys = [] + + ref = schema.get("$ref") + if ref is not None: + resolved = validator._resolver.lookup(ref) + evaluated_keys.extend( + find_evaluated_property_keys_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + dynamicRef = schema.get("$dynamicRef") + if dynamicRef is not None: + resolved = validator._resolver.lookup(dynamicRef) + evaluated_keys.extend( + find_evaluated_property_keys_by_schema( + validator.evolve( + schema=resolved.contents, + _resolver=resolved.resolver, + ), + instance, + resolved.contents, + ), + ) + + properties = schema.get("properties") + if validator.is_type(properties, "object"): + evaluated_keys += properties.keys() & instance.keys() + + for keyword in ["additionalProperties", "unevaluatedProperties"]: + if (subschema := schema.get(keyword)) is None: + continue + evaluated_keys += ( + key + for key, value in instance.items() + if is_valid(validator.descend(value, subschema)) + ) + + if "patternProperties" in schema: + for property in instance: + for pattern in schema["patternProperties"]: + if re.search(pattern, property): + evaluated_keys.append(property) + + if "dependentSchemas" in schema: + for property, subschema in schema["dependentSchemas"].items(): + if property not in instance: + continue + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + for keyword in ["allOf", "oneOf", "anyOf"]: + for subschema in schema.get(keyword, []): + if not is_valid(validator.descend(instance, subschema)): + continue + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["then"], + ) + elif "else" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["else"], + ) + + return evaluated_keys + + +def is_valid(errs_it): + """Whether there are no errors in the given iterator.""" + return next(errs_it, None) is None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py new file mode 100644 index 0000000..e3dcc68 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py @@ -0,0 +1,5 @@ +""" +Benchmarks for validation. + +This package is *not* public API. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py new file mode 100644 index 0000000..c6fecd1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py @@ -0,0 +1,30 @@ +""" +A benchmark for comparing equivalent validation of `const` and `enum`. +""" + +from pyperf import Runner + +from jsonschema import Draft202012Validator + +value = [37] * 100 +const_schema = {"const": list(value)} +enum_schema = {"enum": [list(value)]} + +valid = list(value) +invalid = [*valid, 73] + +const = Draft202012Validator(const_schema) +enum = Draft202012Validator(enum_schema) + +assert const.is_valid(valid) +assert enum.is_valid(valid) +assert not const.is_valid(invalid) +assert not enum.is_valid(invalid) + + +if __name__ == "__main__": + runner = Runner() + runner.bench_func("const valid", lambda: const.is_valid(valid)) + runner.bench_func("const invalid", lambda: const.is_valid(invalid)) + runner.bench_func("enum valid", lambda: enum.is_valid(valid)) + runner.bench_func("enum invalid", lambda: enum.is_valid(invalid)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py new file mode 100644 index 0000000..739cd04 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py @@ -0,0 +1,28 @@ +""" +A benchmark for validation of the `contains` keyword. +""" + +from pyperf import Runner + +from jsonschema import Draft202012Validator + +schema = { + "type": "array", + "contains": {"const": 37}, +} +validator = Draft202012Validator(schema) + +size = 1000 +beginning = [37] + [0] * (size - 1) +middle = [0] * (size // 2) + [37] + [0] * (size // 2) +end = [0] * (size - 1) + [37] +invalid = [0] * size + + +if __name__ == "__main__": + runner = Runner() + runner.bench_func("baseline", lambda: validator.is_valid([])) + runner.bench_func("beginning", lambda: validator.is_valid(beginning)) + runner.bench_func("middle", lambda: validator.is_valid(middle)) + runner.bench_func("end", lambda: validator.is_valid(end)) + runner.bench_func("invalid", lambda: validator.is_valid(invalid)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py new file mode 100644 index 0000000..59fa921 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py @@ -0,0 +1,31 @@ +""" +A benchmark which measures the import time of jsonschema +""" + +import subprocess +import sys + + +def import_time(loops): + total_us = 0 + for _ in range(loops): + p = subprocess.run( # noqa: S603 (arguments are static) + [sys.executable, "-X", "importtime", "-c", "import jsonschema"], + stderr=subprocess.PIPE, + stdout=subprocess.DEVNULL, + check=True, + ) + + line = p.stderr.splitlines()[-1] + field = line.split(b"|")[-2].strip() + us = int(field) # microseconds + total_us += us + + # pyperf expects seconds + return total_us / 1_000_000.0 + +if __name__ == "__main__": + from pyperf import Runner + runner = Runner() + + runner.bench_time_func("Import time (cumulative)", import_time) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py new file mode 100644 index 0000000..efd0715 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py @@ -0,0 +1,25 @@ +""" +A performance benchmark using the example from issue #232. + +See https://github.com/python-jsonschema/jsonschema/pull/232. +""" +from pathlib import Path + +from pyperf import Runner +from referencing import Registry + +from jsonschema.tests._suite import Version +import jsonschema + +issue232 = Version( + path=Path(__file__).parent / "issue232", + remotes=Registry(), + name="issue232", +) + + +if __name__ == "__main__": + issue232.benchmark( + runner=Runner(), + Validator=jsonschema.Draft4Validator, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json new file mode 100644 index 0000000..804c340 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json @@ -0,0 +1,2653 @@ +[ + { + "description": "Petstore", + "schema": { + "title": "A JSON Schema for Swagger 2.0 API.", + "id": "http://swagger.io/v2/schema.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "swagger", + "info", + "paths" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "swagger": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "The Swagger version of this document." + }, + "info": { + "$ref": "#/definitions/info" + }, + "host": { + "type": "string", + "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", + "description": "The host (name or ip) of the API. Example: 'swagger.io'" + }, + "basePath": { + "type": "string", + "pattern": "^/", + "description": "The base path to the API. Example: '/api'." + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "consumes": { + "description": "A list of MIME types accepted by the API.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "paths": { + "$ref": "#/definitions/paths" + }, + "definitions": { + "$ref": "#/definitions/definitions" + }, + "parameters": { + "$ref": "#/definitions/parameterDefinitions" + }, + "responses": { + "$ref": "#/definitions/responseDefinitions" + }, + "security": { + "$ref": "#/definitions/security" + }, + "securityDefinitions": { + "$ref": "#/definitions/securityDefinitions" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/tag" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "definitions": { + "info": { + "type": "object", + "description": "General information about the API.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." + }, + "termsOfService": { + "type": "string", + "description": "The terms of service for the API." + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + } + } + }, + "contact": { + "type": "object", + "description": "Contact information for the owners of the API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "license": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "paths": { + "type": "object", + "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + }, + "^/": { + "$ref": "#/definitions/pathItem" + } + }, + "additionalProperties": false + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "description": "One or more JSON objects describing the schemas being consumed and produced by the API." + }, + "parameterDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/parameter" + }, + "description": "One or more JSON representations for parameters" + }, + "responseDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/response" + }, + "description": "One or more JSON representations for parameters" + }, + "externalDocs": { + "type": "object", + "additionalProperties": false, + "description": "information about external documentation", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "examples": { + "type": "object", + "additionalProperties": true + }, + "mimeType": { + "type": "string", + "description": "The MIME type of the HTTP message." + }, + "operation": { + "type": "object", + "required": [ + "responses" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "operationId": { + "type": "string", + "description": "A unique identifier of the operation." + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "consumes": { + "description": "A list of MIME types the API can consume.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "parameters": { + "$ref": "#/definitions/parametersList" + }, + "responses": { + "$ref": "#/definitions/responses" + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "$ref": "#/definitions/security" + } + } + }, + "pathItem": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/operation" + }, + "put": { + "$ref": "#/definitions/operation" + }, + "post": { + "$ref": "#/definitions/operation" + }, + "delete": { + "$ref": "#/definitions/operation" + }, + "options": { + "$ref": "#/definitions/operation" + }, + "head": { + "$ref": "#/definitions/operation" + }, + "patch": { + "$ref": "#/definitions/operation" + }, + "parameters": { + "$ref": "#/definitions/parametersList" + } + } + }, + "responses": { + "type": "object", + "description": "Response objects names can either be any valid HTTP status code or 'default'.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^([0-9]{3})$|^(default)$": { + "$ref": "#/definitions/responseValue" + }, + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "not": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + } + }, + "responseValue": { + "oneOf": [ + { + "$ref": "#/definitions/response" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/fileSchema" + } + ] + }, + "headers": { + "$ref": "#/definitions/headers" + }, + "examples": { + "$ref": "#/definitions/examples" + } + }, + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/header" + } + }, + "header": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "vendorExtension": { + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "bodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "body" + ] + }, + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "schema": { + "$ref": "#/definitions/schema" + } + }, + "additionalProperties": false + }, + "headerParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "header" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "queryParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "query" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "formDataParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "formData" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array", + "file" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "pathParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ], + "description": "Determines whether or not this parameter is required or optional." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "path" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "nonBodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "type" + ], + "oneOf": [ + { + "$ref": "#/definitions/headerParameterSubSchema" + }, + { + "$ref": "#/definitions/formDataParameterSubSchema" + }, + { + "$ref": "#/definitions/queryParameterSubSchema" + }, + { + "$ref": "#/definitions/pathParameterSubSchema" + } + ] + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/bodyParameter" + }, + { + "$ref": "#/definitions/nonBodyParameter" + } + ] + }, + "schema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "maxProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "type": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/type" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "default": {} + }, + "discriminator": { + "type": "string" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/xml" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "fileSchema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "type" + ], + "properties": { + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "primitivesItems": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRequirement" + }, + "uniqueItems": true + }, + "securityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "tag": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "securityDefinitions": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuthenticationSecurity" + }, + { + "$ref": "#/definitions/apiKeySecurity" + }, + { + "$ref": "#/definitions/oauth2ImplicitSecurity" + }, + { + "$ref": "#/definitions/oauth2PasswordSecurity" + }, + { + "$ref": "#/definitions/oauth2ApplicationSecurity" + }, + { + "$ref": "#/definitions/oauth2AccessCodeSecurity" + } + ] + } + }, + "basicAuthenticationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "apiKeySecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ImplicitSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "implicit" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2PasswordSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "password" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ApplicationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "application" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2AccessCodeSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "accessCode" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2Scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mediaTypeList": { + "type": "array", + "items": { + "$ref": "#/definitions/mimeType" + }, + "uniqueItems": true + }, + "parametersList": { + "type": "array", + "description": "The parameters needed to send a valid API call.", + "additionalItems": false, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/parameter" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "uniqueItems": true + }, + "schemesList": { + "type": "array", + "description": "The transfer protocol of the API.", + "items": { + "type": "string", + "enum": [ + "http", + "https", + "ws", + "wss" + ] + }, + "uniqueItems": true + }, + "collectionFormat": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes" + ], + "default": "csv" + }, + "collectionFormatWithMulti": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes", + "multi" + ], + "default": "csv" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "jsonReference": { + "type": "object", + "required": [ + "$ref" + ], + "additionalProperties": false, + "properties": { + "$ref": { + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "Example petsore", + "data": { + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponse" + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10.0, + "minimum": 1.0, + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1.0, + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + }, + "headers": { + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + }, + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } + }, + "valid": true + } + ] + } +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py new file mode 100644 index 0000000..905fb6a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py @@ -0,0 +1,12 @@ +""" +A performance benchmark using the official test suite. + +This benchmarks jsonschema using every valid example in the +JSON-Schema-Test-Suite. It will take some time to complete. +""" +from pyperf import Runner + +from jsonschema.tests._suite import Suite + +if __name__ == "__main__": + Suite().benchmark(runner=Runner()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py new file mode 100644 index 0000000..b025c47 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py @@ -0,0 +1,56 @@ +""" +Validating highly nested schemas shouldn't cause exponential time blowups. + +See https://github.com/python-jsonschema/jsonschema/issues/1097. +""" +from itertools import cycle + +from jsonschema.validators import validator_for + +metaschemaish = { + "$id": "https://example.com/draft/2020-12/schema/strict", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": True, + "https://json-schema.org/draft/2020-12/vocab/applicator": True, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": True, + "https://json-schema.org/draft/2020-12/vocab/validation": True, + "https://json-schema.org/draft/2020-12/vocab/meta-data": True, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": True, + "https://json-schema.org/draft/2020-12/vocab/content": True, + }, + "$dynamicAnchor": "meta", + + "$ref": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": False, +} + + +def nested_schema(levels): + """ + Produce a schema which validates deeply nested objects and arrays. + """ + + names = cycle(["foo", "bar", "baz", "quux", "spam", "eggs"]) + schema = {"type": "object", "properties": {"ham": {"type": "string"}}} + for _, name in zip(range(levels - 1), names): + schema = {"type": "object", "properties": {name: schema}} + return schema + + +validator = validator_for(metaschemaish)(metaschemaish) + +if __name__ == "__main__": + from pyperf import Runner + runner = Runner() + + not_nested = nested_schema(levels=1) + runner.bench_func("not nested", lambda: validator.is_valid(not_nested)) + + for levels in range(1, 11, 3): + schema = nested_schema(levels=levels) + runner.bench_func( + f"nested * {levels}", + lambda schema=schema: validator.is_valid(schema), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py new file mode 100644 index 0000000..6d78c7b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py @@ -0,0 +1,42 @@ +""" +A benchmark which tries to compare the possible slow subparts of validation. +""" +from referencing import Registry +from referencing.jsonschema import DRAFT202012 +from rpds import HashTrieMap, HashTrieSet + +from jsonschema import Draft202012Validator + +schema = { + "type": "array", + "minLength": 1, + "maxLength": 1, + "items": {"type": "integer"}, +} + +hmap = HashTrieMap() +hset = HashTrieSet() + +registry = Registry() + +v = Draft202012Validator(schema) + + +def registry_data_structures(): + return hmap.insert("foo", "bar"), hset.insert("foo") + + +def registry_add(): + resource = DRAFT202012.create_resource(schema) + return registry.with_resource(uri="urn:example", resource=resource) + + +if __name__ == "__main__": + from pyperf import Runner + runner = Runner() + + runner.bench_func("HashMap/HashSet insertion", registry_data_structures) + runner.bench_func("Registry insertion", registry_add) + runner.bench_func("Success", lambda: v.is_valid([1])) + runner.bench_func("Failure", lambda: v.is_valid(["foo"])) + runner.bench_func("Metaschema validation", lambda: v.check_schema(schema)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py new file mode 100644 index 0000000..7b272c2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py @@ -0,0 +1,35 @@ +""" +An unused schema registry should not cause slower validation. + +"Unused" here means one where no reference resolution is occurring anyhow. + +See https://github.com/python-jsonschema/jsonschema/issues/1088. +""" +from pyperf import Runner +from referencing import Registry +from referencing.jsonschema import DRAFT201909 + +from jsonschema import Draft201909Validator + +registry = Registry().with_resource( + "urn:example:foo", + DRAFT201909.create_resource({}), +) + +schema = {"$ref": "https://json-schema.org/draft/2019-09/schema"} +instance = {"maxLength": 4} + +no_registry = Draft201909Validator(schema) +with_useless_registry = Draft201909Validator(schema, registry=registry) + +if __name__ == "__main__": + runner = Runner() + + runner.bench_func( + "no registry", + lambda: no_registry.is_valid(instance), + ) + runner.bench_func( + "useless registry", + lambda: with_useless_registry.is_valid(instance), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py new file mode 100644 index 0000000..f3229c0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py @@ -0,0 +1,106 @@ + +""" +A benchmark for validation of applicators containing lots of useless schemas. + +Signals a small possible optimization to remove all such schemas ahead of time. +""" + +from pyperf import Runner + +from jsonschema import Draft202012Validator as Validator + +NUM_USELESS = 100000 + +subschema = {"const": 37} + +valid = 37 +invalid = 12 + +baseline = Validator(subschema) + + +# These should be indistinguishable from just `subschema` +by_name = { + "single subschema": { + "anyOf": Validator({"anyOf": [subschema]}), + "allOf": Validator({"allOf": [subschema]}), + "oneOf": Validator({"oneOf": [subschema]}), + }, + "redundant subschemas": { + "anyOf": Validator({"anyOf": [subschema] * NUM_USELESS}), + "allOf": Validator({"allOf": [subschema] * NUM_USELESS}), + }, + "useless successful subschemas (beginning)": { + "anyOf": Validator({"anyOf": [subschema, *[True] * NUM_USELESS]}), + "allOf": Validator({"allOf": [subschema, *[True] * NUM_USELESS]}), + }, + "useless successful subschemas (middle)": { + "anyOf": Validator( + { + "anyOf": [ + *[True] * (NUM_USELESS // 2), + subschema, + *[True] * (NUM_USELESS // 2), + ], + }, + ), + "allOf": Validator( + { + "allOf": [ + *[True] * (NUM_USELESS // 2), + subschema, + *[True] * (NUM_USELESS // 2), + ], + }, + ), + }, + "useless successful subschemas (end)": { + "anyOf": Validator({"anyOf": [*[True] * NUM_USELESS, subschema]}), + "allOf": Validator({"allOf": [*[True] * NUM_USELESS, subschema]}), + }, + "useless failing subschemas (beginning)": { + "anyOf": Validator({"anyOf": [subschema, *[False] * NUM_USELESS]}), + "oneOf": Validator({"oneOf": [subschema, *[False] * NUM_USELESS]}), + }, + "useless failing subschemas (middle)": { + "anyOf": Validator( + { + "anyOf": [ + *[False] * (NUM_USELESS // 2), + subschema, + *[False] * (NUM_USELESS // 2), + ], + }, + ), + "oneOf": Validator( + { + "oneOf": [ + *[False] * (NUM_USELESS // 2), + subschema, + *[False] * (NUM_USELESS // 2), + ], + }, + ), + }, + "useless failing subschemas (end)": { + "anyOf": Validator({"anyOf": [*[False] * NUM_USELESS, subschema]}), + "oneOf": Validator({"oneOf": [*[False] * NUM_USELESS, subschema]}), + }, +} + +if __name__ == "__main__": + runner = Runner() + + runner.bench_func("baseline valid", lambda: baseline.is_valid(valid)) + runner.bench_func("baseline invalid", lambda: baseline.is_valid(invalid)) + + for group, applicators in by_name.items(): + for applicator, validator in applicators.items(): + runner.bench_func( + f"{group}: {applicator} valid", + lambda validator=validator: validator.is_valid(valid), + ) + runner.bench_func( + f"{group}: {applicator} invalid", + lambda validator=validator: validator.is_valid(invalid), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py new file mode 100644 index 0000000..50f4359 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py @@ -0,0 +1,32 @@ +""" +A benchmark for validation of schemas containing lots of useless keywords. + +Checks we filter them out once, ahead of time. +""" + +from pyperf import Runner + +from jsonschema import Draft202012Validator + +NUM_USELESS = 100000 +schema = dict( + [ + ("not", {"const": 42}), + *((str(i), i) for i in range(NUM_USELESS)), + ("type", "integer"), + *((str(i), i) for i in range(NUM_USELESS, NUM_USELESS)), + ("minimum", 37), + ], +) +validator = Draft202012Validator(schema) + +valid = 3737 +invalid = 12 + + +if __name__ == "__main__": + runner = Runner() + runner.bench_func("beginning of schema", lambda: validator.is_valid(42)) + runner.bench_func("middle of schema", lambda: validator.is_valid("foo")) + runner.bench_func("end of schema", lambda: validator.is_valid(12)) + runner.bench_func("valid", lambda: validator.is_valid(3737)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py new file mode 100644 index 0000000..4baeb3a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py @@ -0,0 +1,14 @@ +from pyperf import Runner + +from jsonschema import Draft202012Validator + +schema = { + "type": "array", + "minLength": 1, + "maxLength": 1, + "items": {"type": "integer"}, +} + + +if __name__ == "__main__": + Runner().bench_func("validator creation", Draft202012Validator, schema) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py new file mode 100644 index 0000000..f3ca4d6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py @@ -0,0 +1,292 @@ +""" +The ``jsonschema`` command line. +""" + +from importlib import metadata +from json import JSONDecodeError +from pkgutil import resolve_name +from textwrap import dedent +import argparse +import json +import sys +import traceback +import warnings + +from attrs import define, field + +from jsonschema.exceptions import SchemaError +from jsonschema.validators import _RefResolver, validator_for + +warnings.warn( + ( + "The jsonschema CLI is deprecated and will be removed in a future " + "version. Please use check-jsonschema instead, which can be installed " + "from https://pypi.org/project/check-jsonschema/" + ), + DeprecationWarning, + stacklevel=2, +) + + +class _CannotLoadFile(Exception): + pass + + +@define +class _Outputter: + + _formatter = field() + _stdout = field() + _stderr = field() + + @classmethod + def from_arguments(cls, arguments, stdout, stderr): + if arguments["output"] == "plain": + formatter = _PlainFormatter(arguments["error_format"]) + elif arguments["output"] == "pretty": + formatter = _PrettyFormatter() + return cls(formatter=formatter, stdout=stdout, stderr=stderr) + + def load(self, path): + try: + file = open(path) # noqa: SIM115, PTH123 + except FileNotFoundError as error: + self.filenotfound_error(path=path, exc_info=sys.exc_info()) + raise _CannotLoadFile() from error + + with file: + try: + return json.load(file) + except JSONDecodeError as error: + self.parsing_error(path=path, exc_info=sys.exc_info()) + raise _CannotLoadFile() from error + + def filenotfound_error(self, **kwargs): + self._stderr.write(self._formatter.filenotfound_error(**kwargs)) + + def parsing_error(self, **kwargs): + self._stderr.write(self._formatter.parsing_error(**kwargs)) + + def validation_error(self, **kwargs): + self._stderr.write(self._formatter.validation_error(**kwargs)) + + def validation_success(self, **kwargs): + self._stdout.write(self._formatter.validation_success(**kwargs)) + + +@define +class _PrettyFormatter: + + _ERROR_MSG = dedent( + """\ + ===[{type}]===({path})=== + + {body} + ----------------------------- + """, + ) + _SUCCESS_MSG = "===[SUCCESS]===({path})===\n" + + def filenotfound_error(self, path, exc_info): + return self._ERROR_MSG.format( + path=path, + type="FileNotFoundError", + body=f"{path!r} does not exist.", + ) + + def parsing_error(self, path, exc_info): + exc_type, exc_value, exc_traceback = exc_info + exc_lines = "".join( + traceback.format_exception(exc_type, exc_value, exc_traceback), + ) + return self._ERROR_MSG.format( + path=path, + type=exc_type.__name__, + body=exc_lines, + ) + + def validation_error(self, instance_path, error): + return self._ERROR_MSG.format( + path=instance_path, + type=error.__class__.__name__, + body=error, + ) + + def validation_success(self, instance_path): + return self._SUCCESS_MSG.format(path=instance_path) + + +@define +class _PlainFormatter: + + _error_format = field() + + def filenotfound_error(self, path, exc_info): + return f"{path!r} does not exist.\n" + + def parsing_error(self, path, exc_info): + return "Failed to parse {}: {}\n".format( + "" if path == "" else repr(path), + exc_info[1], + ) + + def validation_error(self, instance_path, error): + return self._error_format.format(file_name=instance_path, error=error) + + def validation_success(self, instance_path): + return "" + + +def _resolve_name_with_default(name): + if "." not in name: + name = "jsonschema." + name + return resolve_name(name) + + +parser = argparse.ArgumentParser( + description="JSON Schema Validation CLI", +) +parser.add_argument( + "-i", "--instance", + action="append", + dest="instances", + help=""" + a path to a JSON instance (i.e. filename.json) to validate (may + be specified multiple times). If no instances are provided via this + option, one will be expected on standard input. + """, +) +parser.add_argument( + "-F", "--error-format", + help=""" + the format to use for each validation error message, specified + in a form suitable for str.format. This string will be passed + one formatted object named 'error' for each ValidationError. + Only provide this option when using --output=plain, which is the + default. If this argument is unprovided and --output=plain is + used, a simple default representation will be used. + """, +) +parser.add_argument( + "-o", "--output", + choices=["plain", "pretty"], + default="plain", + help=""" + an output format to use. 'plain' (default) will produce minimal + text with one line for each error, while 'pretty' will produce + more detailed human-readable output on multiple lines. + """, +) +parser.add_argument( + "-V", "--validator", + type=_resolve_name_with_default, + help=""" + the fully qualified object name of a validator to use, or, for + validators that are registered with jsonschema, simply the name + of the class. + """, +) +parser.add_argument( + "--base-uri", + help=""" + a base URI to assign to the provided schema, even if it does not + declare one (via e.g. $id). This option can be used if you wish to + resolve relative references to a particular URI (or local path) + """, +) +parser.add_argument( + "--version", + action="version", + version=metadata.version("jsonschema"), +) +parser.add_argument( + "schema", + help="the path to a JSON Schema to validate with (i.e. schema.json)", +) + + +def parse_args(args): # noqa: D103 + arguments = vars(parser.parse_args(args=args or ["--help"])) + if arguments["output"] != "plain" and arguments["error_format"]: + raise parser.error( + "--error-format can only be used with --output plain", + ) + if arguments["output"] == "plain" and arguments["error_format"] is None: + arguments["error_format"] = "{error.instance}: {error.message}\n" + return arguments + + +def _validate_instance(instance_path, instance, validator, outputter): + invalid = False + for error in validator.iter_errors(instance): + invalid = True + outputter.validation_error(instance_path=instance_path, error=error) + + if not invalid: + outputter.validation_success(instance_path=instance_path) + return invalid + + +def main(args=sys.argv[1:]): # noqa: D103 + sys.exit(run(arguments=parse_args(args=args))) + + +def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): # noqa: D103 + outputter = _Outputter.from_arguments( + arguments=arguments, + stdout=stdout, + stderr=stderr, + ) + + try: + schema = outputter.load(arguments["schema"]) + except _CannotLoadFile: + return 1 + + Validator = arguments["validator"] + if Validator is None: + Validator = validator_for(schema) + + try: + Validator.check_schema(schema) + except SchemaError as error: + outputter.validation_error( + instance_path=arguments["schema"], + error=error, + ) + return 1 + + if arguments["instances"]: + load, instances = outputter.load, arguments["instances"] + else: + def load(_): + try: + return json.load(stdin) + except JSONDecodeError as error: + outputter.parsing_error( + path="", exc_info=sys.exc_info(), + ) + raise _CannotLoadFile() from error + instances = [""] + + resolver = _RefResolver( + base_uri=arguments["base_uri"], + referrer=schema, + ) if arguments["base_uri"] is not None else None + + validator = Validator(schema, resolver=resolver) + exit_code = 0 + for each in instances: + try: + instance = load(each) + except _CannotLoadFile: + exit_code = 1 + else: + exit_code |= _validate_instance( + instance_path=each, + instance=instance, + validator=validator, + outputter=outputter, + ) + + return exit_code diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py new file mode 100644 index 0000000..d955e35 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py @@ -0,0 +1,490 @@ +""" +Validation errors, and some surrounding helpers. +""" +from __future__ import annotations + +from collections import defaultdict, deque +from pprint import pformat +from textwrap import dedent, indent +from typing import TYPE_CHECKING, Any, ClassVar +import heapq +import re +import warnings + +from attrs import define +from referencing.exceptions import Unresolvable as _Unresolvable + +from jsonschema import _utils + +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping, MutableMapping, Sequence + + from jsonschema import _types + +WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"]) +STRONG_MATCHES: frozenset[str] = frozenset() + +_JSON_PATH_COMPATIBLE_PROPERTY_PATTERN = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$") + +_unset = _utils.Unset() + + +def _pretty(thing: Any, prefix: str): + """ + Format something for an error message as prettily as we currently can. + """ + return indent(pformat(thing, width=72, sort_dicts=False), prefix).lstrip() + + +def __getattr__(name): + if name == "RefResolutionError": + warnings.warn( + _RefResolutionError._DEPRECATION_MESSAGE, + DeprecationWarning, + stacklevel=2, + ) + return _RefResolutionError + raise AttributeError(f"module {__name__} has no attribute {name}") + + +class _Error(Exception): + + _word_for_schema_in_error_message: ClassVar[str] + _word_for_instance_in_error_message: ClassVar[str] + + def __init__( + self, + message: str, + validator: str = _unset, # type: ignore[assignment] + path: Iterable[str | int] = (), + cause: Exception | None = None, + context=(), + validator_value: Any = _unset, + instance: Any = _unset, + schema: Mapping[str, Any] | bool = _unset, # type: ignore[assignment] + schema_path: Iterable[str | int] = (), + parent: _Error | None = None, + type_checker: _types.TypeChecker = _unset, # type: ignore[assignment] + ) -> None: + super().__init__( + message, + validator, + path, + cause, + context, + validator_value, + instance, + schema, + schema_path, + parent, + ) + self.message = message + self.path = self.relative_path = deque(path) + self.schema_path = self.relative_schema_path = deque(schema_path) + self.context = list(context) + self.cause = self.__cause__ = cause + self.validator = validator + self.validator_value = validator_value + self.instance = instance + self.schema = schema + self.parent = parent + self._type_checker = type_checker + + for error in context: + error.parent = self + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self.message!r}>" + + def __str__(self) -> str: + essential_for_verbose = ( + self.validator, self.validator_value, self.instance, self.schema, + ) + if any(m is _unset for m in essential_for_verbose): + return self.message + + schema_path = _utils.format_as_index( + container=self._word_for_schema_in_error_message, + indices=list(self.relative_schema_path)[:-1], + ) + instance_path = _utils.format_as_index( + container=self._word_for_instance_in_error_message, + indices=self.relative_path, + ) + prefix = 16 * " " + + return dedent( + f"""\ + {self.message} + + Failed validating {self.validator!r} in {schema_path}: + {_pretty(self.schema, prefix=prefix)} + + On {instance_path}: + {_pretty(self.instance, prefix=prefix)} + """.rstrip(), + ) + + @classmethod + def create_from(cls, other: _Error): + return cls(**other._contents()) + + @property + def absolute_path(self) -> Sequence[str | int]: + parent = self.parent + if parent is None: + return self.relative_path + + path = deque(self.relative_path) + path.extendleft(reversed(parent.absolute_path)) + return path + + @property + def absolute_schema_path(self) -> Sequence[str | int]: + parent = self.parent + if parent is None: + return self.relative_schema_path + + path = deque(self.relative_schema_path) + path.extendleft(reversed(parent.absolute_schema_path)) + return path + + @property + def json_path(self) -> str: + path = "$" + for elem in self.absolute_path: + if isinstance(elem, int): + path += "[" + str(elem) + "]" + elif _JSON_PATH_COMPATIBLE_PROPERTY_PATTERN.match(elem): + path += "." + elem + else: + escaped_elem = elem.replace("\\", "\\\\").replace("'", r"\'") + path += "['" + escaped_elem + "']" + return path + + def _set( + self, + type_checker: _types.TypeChecker | None = None, + **kwargs: Any, + ) -> None: + if type_checker is not None and self._type_checker is _unset: + self._type_checker = type_checker + + for k, v in kwargs.items(): + if getattr(self, k) is _unset: + setattr(self, k, v) + + def _contents(self): + attrs = ( + "message", "cause", "context", "validator", "validator_value", + "path", "schema_path", "instance", "schema", "parent", + ) + return {attr: getattr(self, attr) for attr in attrs} + + def _matches_type(self) -> bool: + try: + # We ignore this as we want to simply crash if this happens + expected = self.schema["type"] # type: ignore[index] + except (KeyError, TypeError): + return False + + if isinstance(expected, str): + return self._type_checker.is_type(self.instance, expected) + + return any( + self._type_checker.is_type(self.instance, expected_type) + for expected_type in expected + ) + + +class ValidationError(_Error): + """ + An instance was invalid under a provided schema. + """ + + _word_for_schema_in_error_message = "schema" + _word_for_instance_in_error_message = "instance" + + +class SchemaError(_Error): + """ + A schema was invalid under its corresponding metaschema. + """ + + _word_for_schema_in_error_message = "metaschema" + _word_for_instance_in_error_message = "schema" + + +@define(slots=False) +class _RefResolutionError(Exception): # noqa: PLW1641 + """ + A ref could not be resolved. + """ + + _DEPRECATION_MESSAGE = ( + "jsonschema.exceptions.RefResolutionError is deprecated as of version " + "4.18.0. If you wish to catch potential reference resolution errors, " + "directly catch referencing.exceptions.Unresolvable." + ) + + _cause: Exception + + def __eq__(self, other): + if self.__class__ is not other.__class__: + return NotImplemented # pragma: no cover -- uncovered but deprecated # noqa: E501 + return self._cause == other._cause + + def __str__(self) -> str: + return str(self._cause) + + +class _WrappedReferencingError(_RefResolutionError, _Unresolvable): # pragma: no cover -- partially uncovered but to be removed # noqa: E501 + def __init__(self, cause: _Unresolvable): + object.__setattr__(self, "_wrapped", cause) + + def __eq__(self, other): + if other.__class__ is self.__class__: + return self._wrapped == other._wrapped + elif other.__class__ is self._wrapped.__class__: + return self._wrapped == other + return NotImplemented + + def __getattr__(self, attr): + return getattr(self._wrapped, attr) + + def __hash__(self): + return hash(self._wrapped) + + def __repr__(self): + return f"" + + def __str__(self): + return f"{self._wrapped.__class__.__name__}: {self._wrapped}" + + +class UndefinedTypeCheck(Exception): + """ + A type checker was asked to check a type it did not have registered. + """ + + def __init__(self, type: str) -> None: + self.type = type + + def __str__(self) -> str: + return f"Type {self.type!r} is unknown to this type checker" + + +class UnknownType(Exception): + """ + A validator was asked to validate an instance against an unknown type. + """ + + def __init__(self, type, instance, schema): + self.type = type + self.instance = instance + self.schema = schema + + def __str__(self): + prefix = 16 * " " + + return dedent( + f"""\ + Unknown type {self.type!r} for validator with schema: + {_pretty(self.schema, prefix=prefix)} + + While checking instance: + {_pretty(self.instance, prefix=prefix)} + """.rstrip(), + ) + + +class FormatError(Exception): + """ + Validating a format failed. + """ + + def __init__(self, message, cause=None): + super().__init__(message, cause) + self.message = message + self.cause = self.__cause__ = cause + + def __str__(self): + return self.message + + +class ErrorTree: + """ + ErrorTrees make it easier to check which validations failed. + """ + + _instance = _unset + + def __init__(self, errors: Iterable[ValidationError] = ()): + self.errors: MutableMapping[str, ValidationError] = {} + self._contents: Mapping[str, ErrorTree] = defaultdict(self.__class__) + + for error in errors: + container = self + for element in error.path: + container = container[element] + container.errors[error.validator] = error + + container._instance = error.instance + + def __contains__(self, index: str | int): + """ + Check whether ``instance[index]`` has any errors. + """ + return index in self._contents + + def __getitem__(self, index): + """ + Retrieve the child tree one level down at the given ``index``. + + If the index is not in the instance that this tree corresponds + to and is not known by this tree, whatever error would be raised + by ``instance.__getitem__`` will be propagated (usually this is + some subclass of `LookupError`. + """ + if self._instance is not _unset and index not in self: + self._instance[index] + return self._contents[index] + + def __setitem__(self, index: str | int, value: ErrorTree): + """ + Add an error to the tree at the given ``index``. + + .. deprecated:: v4.20.0 + + Setting items on an `ErrorTree` is deprecated without replacement. + To populate a tree, provide all of its sub-errors when you + construct the tree. + """ + warnings.warn( + "ErrorTree.__setitem__ is deprecated without replacement.", + DeprecationWarning, + stacklevel=2, + ) + self._contents[index] = value # type: ignore[index] + + def __iter__(self): + """ + Iterate (non-recursively) over the indices in the instance with errors. + """ + return iter(self._contents) + + def __len__(self): + """ + Return the `total_errors`. + """ + return self.total_errors + + def __repr__(self): + total = len(self) + errors = "error" if total == 1 else "errors" + return f"<{self.__class__.__name__} ({total} total {errors})>" + + @property + def total_errors(self): + """ + The total number of errors in the entire tree, including children. + """ + child_errors = sum(len(tree) for _, tree in self._contents.items()) + return len(self.errors) + child_errors + + +def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES): + """ + Create a key function that can be used to sort errors by relevance. + + Arguments: + weak (set): + a collection of validation keywords to consider to be + "weak". If there are two errors at the same level of the + instance and one is in the set of weak validation keywords, + the other error will take priority. By default, :kw:`anyOf` + and :kw:`oneOf` are considered weak keywords and will be + superseded by other same-level validation errors. + + strong (set): + a collection of validation keywords to consider to be + "strong" + + """ + + def relevance(error): + validator = error.validator + return ( # prefer errors which are ... + -len(error.path), # 'deeper' and thereby more specific + error.path, # earlier (for sibling errors) + validator not in weak, # for a non-low-priority keyword + validator in strong, # for a high priority keyword + not error._matches_type(), # at least match the instance's type + ) # otherwise we'll treat them the same + + return relevance + + +relevance = by_relevance() +""" +A key function (e.g. to use with `sorted`) which sorts errors by relevance. + +Example: + +.. code:: python + + sorted(validator.iter_errors(12), key=jsonschema.exceptions.relevance) +""" + + +def best_match(errors, key=relevance): + """ + Try to find an error that appears to be the best match among given errors. + + In general, errors that are higher up in the instance (i.e. for which + `ValidationError.path` is shorter) are considered better matches, + since they indicate "more" is wrong with the instance. + + If the resulting match is either :kw:`oneOf` or :kw:`anyOf`, the + *opposite* assumption is made -- i.e. the deepest error is picked, + since these keywords only need to match once, and any other errors + may not be relevant. + + Arguments: + errors (collections.abc.Iterable): + + the errors to select from. Do not provide a mixture of + errors from different validation attempts (i.e. from + different instances or schemas), since it won't produce + sensical output. + + key (collections.abc.Callable): + + the key to use when sorting errors. See `relevance` and + transitively `by_relevance` for more details (the default is + to sort with the defaults of that function). Changing the + default is only useful if you want to change the function + that rates errors but still want the error context descent + done by this function. + + Returns: + the best matching error, or ``None`` if the iterable was empty + + .. note:: + + This function is a heuristic. Its return value may change for a given + set of inputs from version to version if better heuristics are added. + + """ + best = max(errors, key=key, default=None) + if best is None: + return + + while best.context: + # Calculate the minimum via nsmallest, because we don't recurse if + # all nested errors have the same relevance (i.e. if min == max == all) + smallest = heapq.nsmallest(2, best.context, key=key) + if len(smallest) == 2 and key(smallest[0]) == key(smallest[1]): # noqa: PLR2004 + return best + best = smallest[0] + return best diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py new file mode 100644 index 0000000..b6288dc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py @@ -0,0 +1,230 @@ +""" +typing.Protocol classes for jsonschema interfaces. +""" + +# for reference material on Protocols, see +# https://www.python.org/dev/peps/pep-0544/ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, runtime_checkable + +# in order for Sphinx to resolve references accurately from type annotations, +# it needs to see names like `jsonschema.TypeChecker` +# therefore, only import at type-checking time (to avoid circular references), +# but use `jsonschema` for any types which will otherwise not be resolvable +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping + + import referencing.jsonschema + + from jsonschema import _typing + from jsonschema.exceptions import ValidationError + import jsonschema + import jsonschema.validators + +# For code authors working on the validator protocol, these are the three +# use-cases which should be kept in mind: +# +# 1. As a protocol class, it can be used in type annotations to describe the +# available methods and attributes of a validator +# 2. It is the source of autodoc for the validator documentation +# 3. It is runtime_checkable, meaning that it can be used in isinstance() +# checks. +# +# Since protocols are not base classes, isinstance() checking is limited in +# its capabilities. See docs on runtime_checkable for detail + + +@runtime_checkable +class Validator(Protocol): + """ + The protocol to which all validator classes adhere. + + Arguments: + + schema: + + The schema that the validator object will validate with. + It is assumed to be valid, and providing + an invalid schema can lead to undefined behavior. See + `Validator.check_schema` to validate a schema first. + + registry: + + a schema registry that will be used for looking up JSON references + + resolver: + + a resolver that will be used to resolve :kw:`$ref` + properties (JSON references). If unprovided, one will be created. + + .. deprecated:: v4.18.0 + + `RefResolver <_RefResolver>` has been deprecated in favor of + `referencing`, and with it, this argument. + + format_checker: + + if provided, a checker which will be used to assert about + :kw:`format` properties present in the schema. If unprovided, + *no* format validation is done, and the presence of format + within schemas is strictly informational. Certain formats + require additional packages to be installed in order to assert + against instances. Ensure you've installed `jsonschema` with + its `extra (optional) dependencies ` when + invoking ``pip``. + + .. deprecated:: v4.12.0 + + Subclassing validator classes now explicitly warns this is not part of + their public API. + + """ + + #: An object representing the validator's meta schema (the schema that + #: describes valid schemas in the given version). + META_SCHEMA: ClassVar[Mapping] + + #: A mapping of validation keywords (`str`\s) to functions that + #: validate the keyword with that name. For more information see + #: `creating-validators`. + VALIDATORS: ClassVar[Mapping] + + #: A `jsonschema.TypeChecker` that will be used when validating + #: :kw:`type` keywords in JSON schemas. + TYPE_CHECKER: ClassVar[jsonschema.TypeChecker] + + #: A `jsonschema.FormatChecker` that will be used when validating + #: :kw:`format` keywords in JSON schemas. + FORMAT_CHECKER: ClassVar[jsonschema.FormatChecker] + + #: A function which given a schema returns its ID. + ID_OF: _typing.id_of + + #: The schema that will be used to validate instances + schema: Mapping | bool + + def __init__( + self, + schema: Mapping | bool, + resolver: Any = None, # deprecated + format_checker: jsonschema.FormatChecker | None = None, + *, + registry: referencing.jsonschema.SchemaRegistry = ..., + ) -> None: ... + + @classmethod + def check_schema(cls, schema: Mapping | bool) -> None: + """ + Validate the given schema against the validator's `META_SCHEMA`. + + Raises: + + `jsonschema.exceptions.SchemaError`: + + if the schema is invalid + + """ + + def is_type(self, instance: Any, type: str) -> bool: + """ + Check if the instance is of the given (JSON Schema) type. + + Arguments: + + instance: + + the value to check + + type: + + the name of a known (JSON Schema) type + + Returns: + + whether the instance is of the given type + + Raises: + + `jsonschema.exceptions.UnknownType`: + + if ``type`` is not a known type + + """ + + def is_valid(self, instance: Any) -> bool: + """ + Check if the instance is valid under the current `schema`. + + Returns: + + whether the instance is valid or not + + >>> schema = {"maxItems" : 2} + >>> Draft202012Validator(schema).is_valid([2, 3, 4]) + False + + """ + + def iter_errors(self, instance: Any) -> Iterable[ValidationError]: + r""" + Lazily yield each of the validation errors in the given instance. + + >>> schema = { + ... "type" : "array", + ... "items" : {"enum" : [1, 2, 3]}, + ... "maxItems" : 2, + ... } + >>> v = Draft202012Validator(schema) + >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str): + ... print(error.message) + 4 is not one of [1, 2, 3] + [2, 3, 4] is too long + + .. deprecated:: v4.0.0 + + Calling this function with a second schema argument is deprecated. + Use `Validator.evolve` instead. + """ + + def validate(self, instance: Any) -> None: + """ + Check if the instance is valid under the current `schema`. + + Raises: + + `jsonschema.exceptions.ValidationError`: + + if the instance is invalid + + >>> schema = {"maxItems" : 2} + >>> Draft202012Validator(schema).validate([2, 3, 4]) + Traceback (most recent call last): + ... + ValidationError: [2, 3, 4] is too long + + """ + + def evolve(self, **kwargs) -> Validator: + """ + Create a new validator like this one, but with given changes. + + Preserves all other attributes, so can be used to e.g. create a + validator with a different schema but with the same :kw:`$ref` + resolution behavior. + + >>> validator = Draft202012Validator({}) + >>> validator.evolve(schema={"type": "number"}) + Draft202012Validator(schema={'type': 'number'}, format_checker=None) + + The returned object satisfies the validator protocol, but may not + be of the same concrete class! In particular this occurs + when a :kw:`$ref` occurs to a schema with a different + :kw:`$schema` than this one (i.e. for a different draft). + + >>> validator.evolve( + ... schema={"$schema": Draft7Validator.META_SCHEMA["$id"]} + ... ) + Draft7Validator(schema=..., format_checker=None) + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py new file mode 100644 index 0000000..d61d382 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py @@ -0,0 +1,285 @@ +""" +Python representations of the JSON Schema Test Suite tests. +""" +from __future__ import annotations + +from contextlib import suppress +from functools import partial +from pathlib import Path +from typing import TYPE_CHECKING, Any +import json +import os +import re +import sys +import unittest + +from attrs import field, frozen +from referencing import Registry +import referencing.jsonschema + +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping, Sequence + + from referencing.jsonschema import Schema + import pyperf + +from jsonschema.validators import _VALIDATORS +import jsonschema + +MAGIC_REMOTE_URL = "http://localhost:1234" + +_DELIMITERS = re.compile(r"[\W\- ]+") + + +def _find_suite(): + root = os.environ.get("JSON_SCHEMA_TEST_SUITE") + if root is not None: + return Path(root) + + root = Path(jsonschema.__file__).parent.parent / "json" + if not root.is_dir(): # pragma: no cover + raise ValueError( + ( + "Can't find the JSON-Schema-Test-Suite directory. " + "Set the 'JSON_SCHEMA_TEST_SUITE' environment " + "variable or run the tests from alongside a checkout " + "of the suite." + ), + ) + return root + + +@frozen +class Suite: + + _root: Path = field(factory=_find_suite) + + + def benchmark(self, runner: pyperf.Runner): # pragma: no cover + for name, Validator in _VALIDATORS.items(): + self.version(name=name).benchmark( + runner=runner, + Validator=Validator, + ) + + def version(self, name) -> Version: + Validator = _VALIDATORS[name] + uri: str = Validator.ID_OF(Validator.META_SCHEMA) # type: ignore[assignment] + specification = referencing.jsonschema.specification_with(uri) + + registry = Registry().with_contents( + remotes_in(root=self._root / "remotes", name=name, uri=uri), + default_specification=specification, + ) + return Version( + name=name, + path=self._root / "tests" / name, + remotes=registry, + ) + + +@frozen +class Version: + + _path: Path + _remotes: referencing.jsonschema.SchemaRegistry + + name: str + + def benchmark(self, **kwargs): # pragma: no cover + for case in self.cases(): + case.benchmark(**kwargs) + + def cases(self) -> Iterable[_Case]: + return self._cases_in(paths=self._path.glob("*.json")) + + def format_cases(self) -> Iterable[_Case]: + return self._cases_in(paths=self._path.glob("optional/format/*.json")) + + def optional_cases_of(self, name: str) -> Iterable[_Case]: + return self._cases_in(paths=[self._path / "optional" / f"{name}.json"]) + + def to_unittest_testcase(self, *groups, **kwargs): + name = kwargs.pop("name", "Test" + self.name.title().replace("-", "")) + methods = { + method.__name__: method + for method in ( + test.to_unittest_method(**kwargs) + for group in groups + for case in group + for test in case.tests + ) + } + cls = type(name, (unittest.TestCase,), methods) + + # We're doing crazy things, so if they go wrong, like a function + # behaving differently on some other interpreter, just make them + # not happen. + with suppress(Exception): + cls.__module__ = _someone_save_us_the_module_of_the_caller() + + return cls + + def _cases_in(self, paths: Iterable[Path]) -> Iterable[_Case]: + for path in paths: + for case in json.loads(path.read_text(encoding="utf-8")): + yield _Case.from_dict( + case, + version=self, + subject=path.stem, + remotes=self._remotes, + ) + + +@frozen +class _Case: + + version: Version + + subject: str + description: str + schema: Mapping[str, Any] | bool + tests: list[_Test] + comment: str | None = None + specification: Sequence[dict[str, str]] = () + + @classmethod + def from_dict(cls, data, remotes, **kwargs): + data.update(kwargs) + tests = [ + _Test( + version=data["version"], + subject=data["subject"], + case_description=data["description"], + schema=data["schema"], + remotes=remotes, + **test, + ) for test in data.pop("tests") + ] + return cls(tests=tests, **data) + + def benchmark(self, runner: pyperf.Runner, **kwargs): # pragma: no cover + for test in self.tests: + runner.bench_func( + test.fully_qualified_name, + partial(test.validate_ignoring_errors, **kwargs), + ) + + +def remotes_in( + root: Path, + name: str, + uri: str, +) -> Iterable[tuple[str, Schema]]: + # This messy logic is because the test suite is terrible at indicating + # what remotes are needed for what drafts, and mixes in schemas which + # have no $schema and which are invalid under earlier versions, in with + # other schemas which are needed for tests. + + for each in root.rglob("*.json"): + schema = json.loads(each.read_text()) + + relative = str(each.relative_to(root)).replace("\\", "/") + + if ( + ( # invalid boolean schema + name in {"draft3", "draft4"} + and each.stem == "tree" + ) or + ( # draft/*.json + "$schema" not in schema + and relative.startswith("draft") + and not relative.startswith(name) + ) + ): + continue + yield f"{MAGIC_REMOTE_URL}/{relative}", schema + + +@frozen(repr=False) +class _Test: + + version: Version + + subject: str + case_description: str + description: str + + data: Any + schema: Mapping[str, Any] | bool + + valid: bool + + _remotes: referencing.jsonschema.SchemaRegistry + + comment: str | None = None + + def __repr__(self): # pragma: no cover + return f"" + + @property + def fully_qualified_name(self): # pragma: no cover + return " > ".join( # noqa: FLY002 + [ + self.version.name, + self.subject, + self.case_description, + self.description, + ], + ) + + def to_unittest_method(self, skip=lambda test: None, **kwargs): + if self.valid: + def fn(this): + self.validate(**kwargs) + else: + def fn(this): + with this.assertRaises(jsonschema.ValidationError): + self.validate(**kwargs) + + fn.__name__ = "_".join( + [ + "test", + _DELIMITERS.sub("_", self.subject), + _DELIMITERS.sub("_", self.case_description), + _DELIMITERS.sub("_", self.description), + ], + ) + reason = skip(self) + if reason is None or os.environ.get("JSON_SCHEMA_DEBUG", "0") != "0": + return fn + elif os.environ.get("JSON_SCHEMA_EXPECTED_FAILURES", "0") != "0": # pragma: no cover # noqa: E501 + return unittest.expectedFailure(fn) + else: + return unittest.skip(reason)(fn) + + def validate(self, Validator, **kwargs): + Validator.check_schema(self.schema) + validator = Validator( + schema=self.schema, + registry=self._remotes, + **kwargs, + ) + if os.environ.get("JSON_SCHEMA_DEBUG", "0") != "0": # pragma: no cover + breakpoint() # noqa: T100 + validator.validate(instance=self.data) + + def validate_ignoring_errors(self, Validator): # pragma: no cover + with suppress(jsonschema.ValidationError): + self.validate(Validator=Validator) + + +def _someone_save_us_the_module_of_the_caller(): + """ + The FQON of the module 2nd stack frames up from here. + + This is intended to allow us to dynamically return test case classes that + are indistinguishable from being defined in the module that wants them. + + Otherwise, trial will mis-print the FQON, and copy pasting it won't re-run + the class that really is running. + + Save us all, this is all so so so so so terrible. + """ + + return sys._getframe(2).f_globals["__name__"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py new file mode 100644 index 0000000..c12e88b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py @@ -0,0 +1,50 @@ +""" +Fuzzing setup for OSS-Fuzz. + +See https://github.com/google/oss-fuzz/tree/master/projects/jsonschema for the +other half of the setup here. +""" +import sys + +from hypothesis import given, strategies + +import jsonschema + +PRIM = strategies.one_of( + strategies.booleans(), + strategies.integers(), + strategies.floats(allow_nan=False, allow_infinity=False), + strategies.text(), +) +DICT = strategies.recursive( + base=strategies.one_of( + strategies.booleans(), + strategies.dictionaries(strategies.text(), PRIM), + ), + extend=lambda inner: strategies.dictionaries(strategies.text(), inner), +) + + +@given(obj1=DICT, obj2=DICT) +def test_schemas(obj1, obj2): + try: + jsonschema.validate(instance=obj1, schema=obj2) + except jsonschema.exceptions.ValidationError: + pass + except jsonschema.exceptions.SchemaError: + pass + + +def main(): + atheris.instrument_all() + atheris.Setup( + sys.argv, + test_schemas.hypothesis.fuzz_one_input, + enable_python_coverage=True, + ) + atheris.Fuzz() + + +if __name__ == "__main__": + import atheris + main() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py new file mode 100644 index 0000000..bed9f3e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py @@ -0,0 +1,904 @@ +from contextlib import redirect_stderr, redirect_stdout +from importlib import metadata +from io import StringIO +from json import JSONDecodeError +from pathlib import Path +from textwrap import dedent +from unittest import TestCase +import json +import os +import subprocess +import sys +import tempfile +import warnings + +from jsonschema import Draft4Validator, Draft202012Validator +from jsonschema.exceptions import ( + SchemaError, + ValidationError, + _RefResolutionError, +) +from jsonschema.validators import _LATEST_VERSION, validate + +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + from jsonschema import cli + + +def fake_validator(*errors): + errors = list(reversed(errors)) + + class FakeValidator: + def __init__(self, *args, **kwargs): + pass + + def iter_errors(self, instance): + if errors: + return errors.pop() + return [] # pragma: no cover + + @classmethod + def check_schema(self, schema): + pass + + return FakeValidator + + +def fake_open(all_contents): + def open(path): + contents = all_contents.get(path) + if contents is None: + raise FileNotFoundError(path) + return StringIO(contents) + return open + + +def _message_for(non_json): + try: + json.loads(non_json) + except JSONDecodeError as error: + return str(error) + else: # pragma: no cover + raise RuntimeError("Tried and failed to capture a JSON dump error.") + + +class TestCLI(TestCase): + def run_cli( + self, argv, files=None, stdin=StringIO(), exit_code=0, **override, + ): + arguments = cli.parse_args(argv) + arguments.update(override) + + self.assertFalse(hasattr(cli, "open")) + cli.open = fake_open(files or {}) + try: + stdout, stderr = StringIO(), StringIO() + actual_exit_code = cli.run( + arguments, + stdin=stdin, + stdout=stdout, + stderr=stderr, + ) + finally: + del cli.open + + self.assertEqual( + actual_exit_code, exit_code, msg=dedent( + f""" + Expected an exit code of {exit_code} != {actual_exit_code}. + + stdout: {stdout.getvalue()} + + stderr: {stderr.getvalue()} + """, + ), + ) + return stdout.getvalue(), stderr.getvalue() + + def assertOutputs(self, stdout="", stderr="", **kwargs): + self.assertEqual( + self.run_cli(**kwargs), + (dedent(stdout), dedent(stderr)), + ) + + def test_invalid_instance(self): + error = ValidationError("I am an error!", instance=12) + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_instance=json.dumps(error.instance), + ), + validator=fake_validator([error]), + + argv=["-i", "some_instance", "some_schema"], + + exit_code=1, + stderr="12: I am an error!\n", + ) + + def test_invalid_instance_pretty_output(self): + error = ValidationError("I am an error!", instance=12) + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_instance=json.dumps(error.instance), + ), + validator=fake_validator([error]), + + argv=["-i", "some_instance", "--output", "pretty", "some_schema"], + + exit_code=1, + stderr="""\ + ===[ValidationError]===(some_instance)=== + + I am an error! + ----------------------------- + """, + ) + + def test_invalid_instance_explicit_plain_output(self): + error = ValidationError("I am an error!", instance=12) + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_instance=json.dumps(error.instance), + ), + validator=fake_validator([error]), + + argv=["--output", "plain", "-i", "some_instance", "some_schema"], + + exit_code=1, + stderr="12: I am an error!\n", + ) + + def test_invalid_instance_multiple_errors(self): + instance = 12 + first = ValidationError("First error", instance=instance) + second = ValidationError("Second error", instance=instance) + + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_instance=json.dumps(instance), + ), + validator=fake_validator([first, second]), + + argv=["-i", "some_instance", "some_schema"], + + exit_code=1, + stderr="""\ + 12: First error + 12: Second error + """, + ) + + def test_invalid_instance_multiple_errors_pretty_output(self): + instance = 12 + first = ValidationError("First error", instance=instance) + second = ValidationError("Second error", instance=instance) + + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_instance=json.dumps(instance), + ), + validator=fake_validator([first, second]), + + argv=["-i", "some_instance", "--output", "pretty", "some_schema"], + + exit_code=1, + stderr="""\ + ===[ValidationError]===(some_instance)=== + + First error + ----------------------------- + ===[ValidationError]===(some_instance)=== + + Second error + ----------------------------- + """, + ) + + def test_multiple_invalid_instances(self): + first_instance = 12 + first_errors = [ + ValidationError("An error", instance=first_instance), + ValidationError("Another error", instance=first_instance), + ] + second_instance = "foo" + second_errors = [ValidationError("BOOM", instance=second_instance)] + + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_first_instance=json.dumps(first_instance), + some_second_instance=json.dumps(second_instance), + ), + validator=fake_validator(first_errors, second_errors), + + argv=[ + "-i", "some_first_instance", + "-i", "some_second_instance", + "some_schema", + ], + + exit_code=1, + stderr="""\ + 12: An error + 12: Another error + foo: BOOM + """, + ) + + def test_multiple_invalid_instances_pretty_output(self): + first_instance = 12 + first_errors = [ + ValidationError("An error", instance=first_instance), + ValidationError("Another error", instance=first_instance), + ] + second_instance = "foo" + second_errors = [ValidationError("BOOM", instance=second_instance)] + + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_first_instance=json.dumps(first_instance), + some_second_instance=json.dumps(second_instance), + ), + validator=fake_validator(first_errors, second_errors), + + argv=[ + "--output", "pretty", + "-i", "some_first_instance", + "-i", "some_second_instance", + "some_schema", + ], + + exit_code=1, + stderr="""\ + ===[ValidationError]===(some_first_instance)=== + + An error + ----------------------------- + ===[ValidationError]===(some_first_instance)=== + + Another error + ----------------------------- + ===[ValidationError]===(some_second_instance)=== + + BOOM + ----------------------------- + """, + ) + + def test_custom_error_format(self): + first_instance = 12 + first_errors = [ + ValidationError("An error", instance=first_instance), + ValidationError("Another error", instance=first_instance), + ] + second_instance = "foo" + second_errors = [ValidationError("BOOM", instance=second_instance)] + + self.assertOutputs( + files=dict( + some_schema='{"does not": "matter since it is stubbed"}', + some_first_instance=json.dumps(first_instance), + some_second_instance=json.dumps(second_instance), + ), + validator=fake_validator(first_errors, second_errors), + + argv=[ + "--error-format", ":{error.message}._-_.{error.instance}:", + "-i", "some_first_instance", + "-i", "some_second_instance", + "some_schema", + ], + + exit_code=1, + stderr=":An error._-_.12::Another error._-_.12::BOOM._-_.foo:", + ) + + def test_invalid_schema(self): + self.assertOutputs( + files=dict(some_schema='{"type": 12}'), + argv=["some_schema"], + + exit_code=1, + stderr="""\ + 12: 12 is not valid under any of the given schemas + """, + ) + + def test_invalid_schema_pretty_output(self): + schema = {"type": 12} + + with self.assertRaises(SchemaError) as e: + validate(schema=schema, instance="") + error = str(e.exception) + + self.assertOutputs( + files=dict(some_schema=json.dumps(schema)), + argv=["--output", "pretty", "some_schema"], + + exit_code=1, + stderr=( + "===[SchemaError]===(some_schema)===\n\n" + + str(error) + + "\n-----------------------------\n" + ), + ) + + def test_invalid_schema_multiple_errors(self): + self.assertOutputs( + files=dict(some_schema='{"type": 12, "items": 57}'), + argv=["some_schema"], + + exit_code=1, + stderr="""\ + 57: 57 is not of type 'object', 'boolean' + """, + ) + + def test_invalid_schema_multiple_errors_pretty_output(self): + schema = {"type": 12, "items": 57} + + with self.assertRaises(SchemaError) as e: + validate(schema=schema, instance="") + error = str(e.exception) + + self.assertOutputs( + files=dict(some_schema=json.dumps(schema)), + argv=["--output", "pretty", "some_schema"], + + exit_code=1, + stderr=( + "===[SchemaError]===(some_schema)===\n\n" + + str(error) + + "\n-----------------------------\n" + ), + ) + + def test_invalid_schema_with_invalid_instance(self): + """ + "Validating" an instance that's invalid under an invalid schema + just shows the schema error. + """ + self.assertOutputs( + files=dict( + some_schema='{"type": 12, "minimum": 30}', + some_instance="13", + ), + argv=["-i", "some_instance", "some_schema"], + + exit_code=1, + stderr="""\ + 12: 12 is not valid under any of the given schemas + """, + ) + + def test_invalid_schema_with_invalid_instance_pretty_output(self): + instance, schema = 13, {"type": 12, "minimum": 30} + + with self.assertRaises(SchemaError) as e: + validate(schema=schema, instance=instance) + error = str(e.exception) + + self.assertOutputs( + files=dict( + some_schema=json.dumps(schema), + some_instance=json.dumps(instance), + ), + argv=["--output", "pretty", "-i", "some_instance", "some_schema"], + + exit_code=1, + stderr=( + "===[SchemaError]===(some_schema)===\n\n" + + str(error) + + "\n-----------------------------\n" + ), + ) + + def test_invalid_instance_continues_with_the_rest(self): + self.assertOutputs( + files=dict( + some_schema='{"minimum": 30}', + first_instance="not valid JSON!", + second_instance="12", + ), + argv=[ + "-i", "first_instance", + "-i", "second_instance", + "some_schema", + ], + + exit_code=1, + stderr="""\ + Failed to parse 'first_instance': {} + 12: 12 is less than the minimum of 30 + """.format(_message_for("not valid JSON!")), + ) + + def test_custom_error_format_applies_to_schema_errors(self): + instance, schema = 13, {"type": 12, "minimum": 30} + + with self.assertRaises(SchemaError): + validate(schema=schema, instance=instance) + + self.assertOutputs( + files=dict(some_schema=json.dumps(schema)), + + argv=[ + "--error-format", ":{error.message}._-_.{error.instance}:", + "some_schema", + ], + + exit_code=1, + stderr=":12 is not valid under any of the given schemas._-_.12:", + ) + + def test_instance_is_invalid_JSON(self): + instance = "not valid JSON!" + + self.assertOutputs( + files=dict(some_schema="{}", some_instance=instance), + argv=["-i", "some_instance", "some_schema"], + + exit_code=1, + stderr=f"""\ + Failed to parse 'some_instance': {_message_for(instance)} + """, + ) + + def test_instance_is_invalid_JSON_pretty_output(self): + stdout, stderr = self.run_cli( + files=dict( + some_schema="{}", + some_instance="not valid JSON!", + ), + + argv=["--output", "pretty", "-i", "some_instance", "some_schema"], + + exit_code=1, + ) + self.assertFalse(stdout) + self.assertIn( + "(some_instance)===\n\nTraceback (most recent call last):\n", + stderr, + ) + self.assertNotIn("some_schema", stderr) + + def test_instance_is_invalid_JSON_on_stdin(self): + instance = "not valid JSON!" + + self.assertOutputs( + files=dict(some_schema="{}"), + stdin=StringIO(instance), + + argv=["some_schema"], + + exit_code=1, + stderr=f"""\ + Failed to parse : {_message_for(instance)} + """, + ) + + def test_instance_is_invalid_JSON_on_stdin_pretty_output(self): + stdout, stderr = self.run_cli( + files=dict(some_schema="{}"), + stdin=StringIO("not valid JSON!"), + + argv=["--output", "pretty", "some_schema"], + + exit_code=1, + ) + self.assertFalse(stdout) + self.assertIn( + "()===\n\nTraceback (most recent call last):\n", + stderr, + ) + self.assertNotIn("some_schema", stderr) + + def test_schema_is_invalid_JSON(self): + schema = "not valid JSON!" + + self.assertOutputs( + files=dict(some_schema=schema), + + argv=["some_schema"], + + exit_code=1, + stderr=f"""\ + Failed to parse 'some_schema': {_message_for(schema)} + """, + ) + + def test_schema_is_invalid_JSON_pretty_output(self): + stdout, stderr = self.run_cli( + files=dict(some_schema="not valid JSON!"), + + argv=["--output", "pretty", "some_schema"], + + exit_code=1, + ) + self.assertFalse(stdout) + self.assertIn( + "(some_schema)===\n\nTraceback (most recent call last):\n", + stderr, + ) + + def test_schema_and_instance_are_both_invalid_JSON(self): + """ + Only the schema error is reported, as we abort immediately. + """ + schema, instance = "not valid JSON!", "also not valid JSON!" + self.assertOutputs( + files=dict(some_schema=schema, some_instance=instance), + + argv=["some_schema"], + + exit_code=1, + stderr=f"""\ + Failed to parse 'some_schema': {_message_for(schema)} + """, + ) + + def test_schema_and_instance_are_both_invalid_JSON_pretty_output(self): + """ + Only the schema error is reported, as we abort immediately. + """ + stdout, stderr = self.run_cli( + files=dict( + some_schema="not valid JSON!", + some_instance="also not valid JSON!", + ), + + argv=["--output", "pretty", "-i", "some_instance", "some_schema"], + + exit_code=1, + ) + self.assertFalse(stdout) + self.assertIn( + "(some_schema)===\n\nTraceback (most recent call last):\n", + stderr, + ) + self.assertNotIn("some_instance", stderr) + + def test_instance_does_not_exist(self): + self.assertOutputs( + files=dict(some_schema="{}"), + argv=["-i", "nonexisting_instance", "some_schema"], + + exit_code=1, + stderr="""\ + 'nonexisting_instance' does not exist. + """, + ) + + def test_instance_does_not_exist_pretty_output(self): + self.assertOutputs( + files=dict(some_schema="{}"), + argv=[ + "--output", "pretty", + "-i", "nonexisting_instance", + "some_schema", + ], + + exit_code=1, + stderr="""\ + ===[FileNotFoundError]===(nonexisting_instance)=== + + 'nonexisting_instance' does not exist. + ----------------------------- + """, + ) + + def test_schema_does_not_exist(self): + self.assertOutputs( + argv=["nonexisting_schema"], + + exit_code=1, + stderr="'nonexisting_schema' does not exist.\n", + ) + + def test_schema_does_not_exist_pretty_output(self): + self.assertOutputs( + argv=["--output", "pretty", "nonexisting_schema"], + + exit_code=1, + stderr="""\ + ===[FileNotFoundError]===(nonexisting_schema)=== + + 'nonexisting_schema' does not exist. + ----------------------------- + """, + ) + + def test_neither_instance_nor_schema_exist(self): + self.assertOutputs( + argv=["-i", "nonexisting_instance", "nonexisting_schema"], + + exit_code=1, + stderr="'nonexisting_schema' does not exist.\n", + ) + + def test_neither_instance_nor_schema_exist_pretty_output(self): + self.assertOutputs( + argv=[ + "--output", "pretty", + "-i", "nonexisting_instance", + "nonexisting_schema", + ], + + exit_code=1, + stderr="""\ + ===[FileNotFoundError]===(nonexisting_schema)=== + + 'nonexisting_schema' does not exist. + ----------------------------- + """, + ) + + def test_successful_validation(self): + self.assertOutputs( + files=dict(some_schema="{}", some_instance="{}"), + argv=["-i", "some_instance", "some_schema"], + stdout="", + stderr="", + ) + + def test_successful_validation_pretty_output(self): + self.assertOutputs( + files=dict(some_schema="{}", some_instance="{}"), + argv=["--output", "pretty", "-i", "some_instance", "some_schema"], + stdout="===[SUCCESS]===(some_instance)===\n", + stderr="", + ) + + def test_successful_validation_of_stdin(self): + self.assertOutputs( + files=dict(some_schema="{}"), + stdin=StringIO("{}"), + argv=["some_schema"], + stdout="", + stderr="", + ) + + def test_successful_validation_of_stdin_pretty_output(self): + self.assertOutputs( + files=dict(some_schema="{}"), + stdin=StringIO("{}"), + argv=["--output", "pretty", "some_schema"], + stdout="===[SUCCESS]===()===\n", + stderr="", + ) + + def test_successful_validation_of_just_the_schema(self): + self.assertOutputs( + files=dict(some_schema="{}", some_instance="{}"), + argv=["-i", "some_instance", "some_schema"], + stdout="", + stderr="", + ) + + def test_successful_validation_of_just_the_schema_pretty_output(self): + self.assertOutputs( + files=dict(some_schema="{}", some_instance="{}"), + argv=["--output", "pretty", "-i", "some_instance", "some_schema"], + stdout="===[SUCCESS]===(some_instance)===\n", + stderr="", + ) + + def test_successful_validation_via_explicit_base_uri(self): + ref_schema_file = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115 + ref_schema_file.close() + self.addCleanup(os.remove, ref_schema_file.name) + + ref_path = Path(ref_schema_file.name) + ref_path.write_text('{"definitions": {"num": {"type": "integer"}}}') + + schema = f'{{"$ref": "{ref_path.name}#/definitions/num"}}' + + self.assertOutputs( + files=dict(some_schema=schema, some_instance="1"), + argv=[ + "-i", "some_instance", + "--base-uri", ref_path.parent.as_uri() + "/", + "some_schema", + ], + stdout="", + stderr="", + ) + + def test_unsuccessful_validation_via_explicit_base_uri(self): + ref_schema_file = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115 + ref_schema_file.close() + self.addCleanup(os.remove, ref_schema_file.name) + + ref_path = Path(ref_schema_file.name) + ref_path.write_text('{"definitions": {"num": {"type": "integer"}}}') + + schema = f'{{"$ref": "{ref_path.name}#/definitions/num"}}' + + self.assertOutputs( + files=dict(some_schema=schema, some_instance='"1"'), + argv=[ + "-i", "some_instance", + "--base-uri", ref_path.parent.as_uri() + "/", + "some_schema", + ], + exit_code=1, + stdout="", + stderr="1: '1' is not of type 'integer'\n", + ) + + def test_nonexistent_file_with_explicit_base_uri(self): + schema = '{"$ref": "someNonexistentFile.json#definitions/num"}' + instance = "1" + + with self.assertRaises(_RefResolutionError) as e: + self.assertOutputs( + files=dict( + some_schema=schema, + some_instance=instance, + ), + argv=[ + "-i", "some_instance", + "--base-uri", Path.cwd().as_uri(), + "some_schema", + ], + ) + error = str(e.exception) + self.assertIn(f"{os.sep}someNonexistentFile.json'", error) + + def test_invalid_explicit_base_uri(self): + schema = '{"$ref": "foo.json#definitions/num"}' + instance = "1" + + with self.assertRaises(_RefResolutionError) as e: + self.assertOutputs( + files=dict( + some_schema=schema, + some_instance=instance, + ), + argv=[ + "-i", "some_instance", + "--base-uri", "not@UR1", + "some_schema", + ], + ) + error = str(e.exception) + self.assertEqual( + error, "unknown url type: 'foo.json'", + ) + + def test_it_validates_using_the_latest_validator_when_unspecified(self): + # There isn't a better way now I can think of to ensure that the + # latest version was used, given that the call to validator_for + # is hidden inside the CLI, so guard that that's the case, and + # this test will have to be updated when versions change until + # we can think of a better way to ensure this behavior. + self.assertIs(Draft202012Validator, _LATEST_VERSION) + + self.assertOutputs( + files=dict(some_schema='{"const": "check"}', some_instance='"a"'), + argv=["-i", "some_instance", "some_schema"], + exit_code=1, + stdout="", + stderr="a: 'check' was expected\n", + ) + + def test_it_validates_using_draft7_when_specified(self): + """ + Specifically, `const` validation applies for Draft 7. + """ + schema = """ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "const": "check" + } + """ + instance = '"foo"' + self.assertOutputs( + files=dict(some_schema=schema, some_instance=instance), + argv=["-i", "some_instance", "some_schema"], + exit_code=1, + stdout="", + stderr="foo: 'check' was expected\n", + ) + + def test_it_validates_using_draft4_when_specified(self): + """ + Specifically, `const` validation *does not* apply for Draft 4. + """ + schema = """ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "const": "check" + } + """ + instance = '"foo"' + self.assertOutputs( + files=dict(some_schema=schema, some_instance=instance), + argv=["-i", "some_instance", "some_schema"], + stdout="", + stderr="", + ) + + +class TestParser(TestCase): + + FakeValidator = fake_validator() + + def test_find_validator_by_fully_qualified_object_name(self): + arguments = cli.parse_args( + [ + "--validator", + "jsonschema.tests.test_cli.TestParser.FakeValidator", + "--instance", "mem://some/instance", + "mem://some/schema", + ], + ) + self.assertIs(arguments["validator"], self.FakeValidator) + + def test_find_validator_in_jsonschema(self): + arguments = cli.parse_args( + [ + "--validator", "Draft4Validator", + "--instance", "mem://some/instance", + "mem://some/schema", + ], + ) + self.assertIs(arguments["validator"], Draft4Validator) + + def cli_output_for(self, *argv): + stdout, stderr = StringIO(), StringIO() + with redirect_stdout(stdout), redirect_stderr(stderr): # noqa: SIM117 + with self.assertRaises(SystemExit): + cli.parse_args(argv) + return stdout.getvalue(), stderr.getvalue() + + def test_unknown_output(self): + stdout, stderr = self.cli_output_for( + "--output", "foo", + "mem://some/schema", + ) + self.assertIn("invalid choice: 'foo'", stderr) + self.assertFalse(stdout) + + def test_useless_error_format(self): + stdout, stderr = self.cli_output_for( + "--output", "pretty", + "--error-format", "foo", + "mem://some/schema", + ) + self.assertIn( + "--error-format can only be used with --output plain", + stderr, + ) + self.assertFalse(stdout) + + +class TestCLIIntegration(TestCase): + def test_license(self): + our_metadata = metadata.metadata("jsonschema") + self.assertEqual(our_metadata.get("License-Expression"), "MIT") + + def test_version(self): + version = subprocess.check_output( + [sys.executable, "-W", "ignore", "-m", "jsonschema", "--version"], + stderr=subprocess.STDOUT, + ) + version = version.decode("utf-8").strip() + self.assertEqual(version, metadata.version("jsonschema")) + + def test_no_arguments_shows_usage_notes(self): + output = subprocess.check_output( + [sys.executable, "-m", "jsonschema"], + stderr=subprocess.STDOUT, + ) + output_for_help = subprocess.check_output( + [sys.executable, "-m", "jsonschema", "--help"], + stderr=subprocess.STDOUT, + ) + self.assertEqual(output, output_for_help) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py new file mode 100644 index 0000000..a54b02f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py @@ -0,0 +1,432 @@ +from contextlib import contextmanager +from io import BytesIO +from unittest import TestCase, mock +import importlib.metadata +import json +import subprocess +import sys +import urllib.request + +import referencing.exceptions + +from jsonschema import FormatChecker, exceptions, protocols, validators + + +class TestDeprecations(TestCase): + def test_version(self): + """ + As of v4.0.0, __version__ is deprecated in favor of importlib.metadata. + """ + + message = "Accessing jsonschema.__version__ is deprecated" + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import __version__ + + self.assertEqual(__version__, importlib.metadata.version("jsonschema")) + self.assertEqual(w.filename, __file__) + + def test_validators_ErrorTree(self): + """ + As of v4.0.0, importing ErrorTree from jsonschema.validators is + deprecated in favor of doing so from jsonschema.exceptions. + """ + + message = "Importing ErrorTree from jsonschema.validators is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema.validators import ErrorTree + + self.assertEqual(ErrorTree, exceptions.ErrorTree) + self.assertEqual(w.filename, __file__) + + def test_import_ErrorTree(self): + """ + As of v4.18.0, importing ErrorTree from the package root is + deprecated in favor of doing so from jsonschema.exceptions. + """ + + message = "Importing ErrorTree directly from the jsonschema package " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import ErrorTree + + self.assertEqual(ErrorTree, exceptions.ErrorTree) + self.assertEqual(w.filename, __file__) + + def test_ErrorTree_setitem(self): + """ + As of v4.20.0, setting items on an ErrorTree is deprecated. + """ + + e = exceptions.ValidationError("some error", path=["foo"]) + tree = exceptions.ErrorTree() + subtree = exceptions.ErrorTree(errors=[e]) + + message = "ErrorTree.__setitem__ is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + tree["foo"] = subtree + + self.assertEqual(tree["foo"], subtree) + self.assertEqual(w.filename, __file__) + + def test_import_FormatError(self): + """ + As of v4.18.0, importing FormatError from the package root is + deprecated in favor of doing so from jsonschema.exceptions. + """ + + message = "Importing FormatError directly from the jsonschema package " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import FormatError + + self.assertEqual(FormatError, exceptions.FormatError) + self.assertEqual(w.filename, __file__) + + def test_import_Validator(self): + """ + As of v4.19.0, importing Validator from the package root is + deprecated in favor of doing so from jsonschema.protocols. + """ + + message = "Importing Validator directly from the jsonschema package " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import Validator + + self.assertEqual(Validator, protocols.Validator) + self.assertEqual(w.filename, __file__) + + def test_validators_validators(self): + """ + As of v4.0.0, accessing jsonschema.validators.validators is + deprecated. + """ + + message = "Accessing jsonschema.validators.validators is deprecated" + with self.assertWarnsRegex(DeprecationWarning, message) as w: + value = validators.validators + + self.assertEqual(value, validators._VALIDATORS) + self.assertEqual(w.filename, __file__) + + def test_validators_meta_schemas(self): + """ + As of v4.0.0, accessing jsonschema.validators.meta_schemas is + deprecated. + """ + + message = "Accessing jsonschema.validators.meta_schemas is deprecated" + with self.assertWarnsRegex(DeprecationWarning, message) as w: + value = validators.meta_schemas + + self.assertEqual(value, validators._META_SCHEMAS) + self.assertEqual(w.filename, __file__) + + def test_RefResolver_in_scope(self): + """ + As of v4.0.0, RefResolver.in_scope is deprecated. + """ + + resolver = validators._RefResolver.from_schema({}) + message = "jsonschema.RefResolver.in_scope is deprecated " + with self.assertWarnsRegex(DeprecationWarning, message) as w: # noqa: SIM117 + with resolver.in_scope("foo"): + pass + + self.assertEqual(w.filename, __file__) + + def test_Validator_is_valid_two_arguments(self): + """ + As of v4.0.0, calling is_valid with two arguments (to provide a + different schema) is deprecated. + """ + + validator = validators.Draft7Validator({}) + message = "Passing a schema to Validator.is_valid is deprecated " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + result = validator.is_valid("foo", {"type": "number"}) + + self.assertFalse(result) + self.assertEqual(w.filename, __file__) + + def test_Validator_iter_errors_two_arguments(self): + """ + As of v4.0.0, calling iter_errors with two arguments (to provide a + different schema) is deprecated. + """ + + validator = validators.Draft7Validator({}) + message = "Passing a schema to Validator.iter_errors is deprecated " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + error, = validator.iter_errors("foo", {"type": "number"}) + + self.assertEqual(error.validator, "type") + self.assertEqual(w.filename, __file__) + + def test_Validator_resolver(self): + """ + As of v4.18.0, accessing Validator.resolver is deprecated. + """ + + validator = validators.Draft7Validator({}) + message = "Accessing Draft7Validator.resolver is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + self.assertIsInstance(validator.resolver, validators._RefResolver) + + self.assertEqual(w.filename, __file__) + + def test_RefResolver(self): + """ + As of v4.18.0, RefResolver is fully deprecated. + """ + + message = "jsonschema.RefResolver is deprecated" + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import RefResolver + self.assertEqual(w.filename, __file__) + + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema.validators import RefResolver # noqa: F401 + self.assertEqual(w.filename, __file__) + + def test_RefResolutionError(self): + """ + As of v4.18.0, RefResolutionError is deprecated in favor of directly + catching errors from the referencing library. + """ + + message = "jsonschema.exceptions.RefResolutionError is deprecated" + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import RefResolutionError + + self.assertEqual(RefResolutionError, exceptions._RefResolutionError) + self.assertEqual(w.filename, __file__) + + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema.exceptions import RefResolutionError + + self.assertEqual(RefResolutionError, exceptions._RefResolutionError) + self.assertEqual(w.filename, __file__) + + def test_catching_Unresolvable_directly(self): + """ + This behavior is the intended behavior (i.e. it's not deprecated), but + given we do "tricksy" things in the iterim to wrap exceptions in a + multiple inheritance subclass, we need to be extra sure it works and + stays working. + """ + validator = validators.Draft202012Validator({"$ref": "urn:nothing"}) + + with self.assertRaises(referencing.exceptions.Unresolvable) as e: + validator.validate(12) + + expected = referencing.exceptions.Unresolvable(ref="urn:nothing") + self.assertEqual( + (e.exception, str(e.exception)), + (expected, "Unresolvable: urn:nothing"), + ) + + def test_catching_Unresolvable_via_RefResolutionError(self): + """ + Until RefResolutionError is removed, it is still possible to catch + exceptions from reference resolution using it, even though they may + have been raised by referencing. + """ + with self.assertWarns(DeprecationWarning): + from jsonschema import RefResolutionError + + validator = validators.Draft202012Validator({"$ref": "urn:nothing"}) + + with self.assertRaises(referencing.exceptions.Unresolvable) as u: + validator.validate(12) + + with self.assertRaises(RefResolutionError) as e: + validator.validate(12) + + self.assertEqual( + (e.exception, str(e.exception)), + (u.exception, "Unresolvable: urn:nothing"), + ) + + def test_WrappedReferencingError_hashability(self): + """ + Ensure the wrapped referencing errors are hashable when possible. + """ + with self.assertWarns(DeprecationWarning): + from jsonschema import RefResolutionError + + validator = validators.Draft202012Validator({"$ref": "urn:nothing"}) + + with self.assertRaises(referencing.exceptions.Unresolvable) as u: + validator.validate(12) + + with self.assertRaises(RefResolutionError) as e: + validator.validate(12) + + self.assertIn(e.exception, {u.exception}) + self.assertIn(u.exception, {e.exception}) + + def test_Validator_subclassing(self): + """ + As of v4.12.0, subclassing a validator class produces an explicit + deprecation warning. + + This was never intended to be public API (and some comments over the + years in issues said so, but obviously that's not a great way to make + sure it's followed). + + A future version will explicitly raise an error. + """ + + message = "Subclassing validator classes is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + class Subclass(validators.Draft202012Validator): + pass + + self.assertEqual(w.filename, __file__) + + with self.assertWarnsRegex(DeprecationWarning, message) as w: + class AnotherSubclass(validators.create(meta_schema={})): + pass + + def test_FormatChecker_cls_checks(self): + """ + As of v4.14.0, FormatChecker.cls_checks is deprecated without + replacement. + """ + + self.addCleanup(FormatChecker.checkers.pop, "boom", None) + + message = "FormatChecker.cls_checks " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + FormatChecker.cls_checks("boom") + + self.assertEqual(w.filename, __file__) + + def test_draftN_format_checker(self): + """ + As of v4.16.0, accessing jsonschema.draftn_format_checker is deprecated + in favor of Validator.FORMAT_CHECKER. + """ + + message = "Accessing jsonschema.draft202012_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft202012_format_checker + + self.assertIs( + draft202012_format_checker, + validators.Draft202012Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + message = "Accessing jsonschema.draft201909_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft201909_format_checker + + self.assertIs( + draft201909_format_checker, + validators.Draft201909Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + message = "Accessing jsonschema.draft7_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft7_format_checker + + self.assertIs( + draft7_format_checker, + validators.Draft7Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + message = "Accessing jsonschema.draft6_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft6_format_checker + + self.assertIs( + draft6_format_checker, + validators.Draft6Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + message = "Accessing jsonschema.draft4_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft4_format_checker + + self.assertIs( + draft4_format_checker, + validators.Draft4Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + message = "Accessing jsonschema.draft3_format_checker is " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + from jsonschema import draft3_format_checker + + self.assertIs( + draft3_format_checker, + validators.Draft3Validator.FORMAT_CHECKER, + ) + self.assertEqual(w.filename, __file__) + + with self.assertRaises(ImportError): + from jsonschema import draft1234_format_checker # noqa: F401 + + def test_import_cli(self): + """ + As of v4.17.0, importing jsonschema.cli is deprecated. + """ + + message = "The jsonschema CLI is deprecated and will be removed " + with self.assertWarnsRegex(DeprecationWarning, message) as w: + import jsonschema.cli + importlib.reload(jsonschema.cli) + + self.assertEqual(w.filename, importlib.__file__) + + def test_cli(self): + """ + As of v4.17.0, the jsonschema CLI is deprecated. + """ + + process = subprocess.run( + [sys.executable, "-m", "jsonschema"], + capture_output=True, + check=True, + ) + self.assertIn(b"The jsonschema CLI is deprecated ", process.stderr) + + def test_automatic_remote_retrieval(self): + """ + Automatic retrieval of remote references is deprecated as of v4.18.0. + """ + ref = "http://bar#/$defs/baz" + schema = {"$defs": {"baz": {"type": "integer"}}} + + if "requests" in sys.modules: # pragma: no cover + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = None + + @contextmanager + def fake_urlopen(request): + self.assertIsInstance(request, urllib.request.Request) + self.assertEqual(request.full_url, "http://bar") + + # Ha ha urllib.request.Request "normalizes" header names and + # Request.get_header does not also normalize them... + (header, value), = request.header_items() + self.assertEqual(header.lower(), "user-agent") + self.assertEqual( + value, "python-jsonschema (deprecated $ref resolution)", + ) + yield BytesIO(json.dumps(schema).encode("utf8")) + + validator = validators.Draft202012Validator({"$ref": ref}) + + message = "Automatically retrieving remote references " + patch = mock.patch.object(urllib.request, "urlopen", new=fake_urlopen) + + with patch, self.assertWarnsRegex(DeprecationWarning, message): + self.assertEqual( + (validator.is_valid({}), validator.is_valid(37)), + (False, True), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py new file mode 100644 index 0000000..358b924 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py @@ -0,0 +1,759 @@ +from unittest import TestCase +import textwrap + +import jsonpath_ng + +from jsonschema import exceptions +from jsonschema.validators import _LATEST_VERSION + + +class TestBestMatch(TestCase): + def best_match_of(self, instance, schema): + errors = list(_LATEST_VERSION(schema).iter_errors(instance)) + msg = f"No errors found for {instance} under {schema!r}!" + self.assertTrue(errors, msg=msg) + + best = exceptions.best_match(iter(errors)) + reversed_best = exceptions.best_match(reversed(errors)) + + self.assertEqual( + best._contents(), + reversed_best._contents(), + f"No consistent best match!\nGot: {best}\n\nThen: {reversed_best}", + ) + return best + + def test_shallower_errors_are_better_matches(self): + schema = { + "properties": { + "foo": { + "minProperties": 2, + "properties": {"bar": {"type": "object"}}, + }, + }, + } + best = self.best_match_of(instance={"foo": {"bar": []}}, schema=schema) + self.assertEqual(best.validator, "minProperties") + + def test_oneOf_and_anyOf_are_weak_matches(self): + """ + A property you *must* match is probably better than one you have to + match a part of. + """ + + schema = { + "minProperties": 2, + "anyOf": [{"type": "string"}, {"type": "number"}], + "oneOf": [{"type": "string"}, {"type": "number"}], + } + best = self.best_match_of(instance={}, schema=schema) + self.assertEqual(best.validator, "minProperties") + + def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self): + """ + If the most relevant error is an anyOf, then we traverse its context + and select the otherwise *least* relevant error, since in this case + that means the most specific, deep, error inside the instance. + + I.e. since only one of the schemas must match, we look for the most + relevant one. + """ + + schema = { + "properties": { + "foo": { + "anyOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": {"bar": 12}}, schema=schema) + self.assertEqual(best.validator_value, "array") + + def test_no_anyOf_traversal_for_equally_relevant_errors(self): + """ + We don't traverse into an anyOf (as above) if all of its context errors + seem to be equally "wrong" against the instance. + """ + + schema = { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + {"type": "object"}, + ], + } + best = self.best_match_of(instance=[], schema=schema) + self.assertEqual(best.validator, "anyOf") + + def test_anyOf_traversal_for_single_equally_relevant_error(self): + """ + We *do* traverse anyOf with a single nested error, even though it is + vacuously equally relevant to itself. + """ + + schema = { + "anyOf": [ + {"type": "string"}, + ], + } + best = self.best_match_of(instance=[], schema=schema) + self.assertEqual(best.validator, "type") + + def test_anyOf_traversal_for_single_sibling_errors(self): + """ + We *do* traverse anyOf with a single subschema that fails multiple + times (e.g. on multiple items). + """ + + schema = { + "anyOf": [ + {"items": {"const": 37}}, + ], + } + best = self.best_match_of(instance=[12, 12], schema=schema) + self.assertEqual(best.validator, "const") + + def test_anyOf_traversal_for_non_type_matching_sibling_errors(self): + """ + We *do* traverse anyOf with multiple subschemas when one does not type + match. + """ + + schema = { + "anyOf": [ + {"type": "object"}, + {"items": {"const": 37}}, + ], + } + best = self.best_match_of(instance=[12, 12], schema=schema) + self.assertEqual(best.validator, "const") + + def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self): + """ + If the most relevant error is an oneOf, then we traverse its context + and select the otherwise *least* relevant error, since in this case + that means the most specific, deep, error inside the instance. + + I.e. since only one of the schemas must match, we look for the most + relevant one. + """ + + schema = { + "properties": { + "foo": { + "oneOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": {"bar": 12}}, schema=schema) + self.assertEqual(best.validator_value, "array") + + def test_no_oneOf_traversal_for_equally_relevant_errors(self): + """ + We don't traverse into an oneOf (as above) if all of its context errors + seem to be equally "wrong" against the instance. + """ + + schema = { + "oneOf": [ + {"type": "string"}, + {"type": "integer"}, + {"type": "object"}, + ], + } + best = self.best_match_of(instance=[], schema=schema) + self.assertEqual(best.validator, "oneOf") + + def test_oneOf_traversal_for_single_equally_relevant_error(self): + """ + We *do* traverse oneOf with a single nested error, even though it is + vacuously equally relevant to itself. + """ + + schema = { + "oneOf": [ + {"type": "string"}, + ], + } + best = self.best_match_of(instance=[], schema=schema) + self.assertEqual(best.validator, "type") + + def test_oneOf_traversal_for_single_sibling_errors(self): + """ + We *do* traverse oneOf with a single subschema that fails multiple + times (e.g. on multiple items). + """ + + schema = { + "oneOf": [ + {"items": {"const": 37}}, + ], + } + best = self.best_match_of(instance=[12, 12], schema=schema) + self.assertEqual(best.validator, "const") + + def test_oneOf_traversal_for_non_type_matching_sibling_errors(self): + """ + We *do* traverse oneOf with multiple subschemas when one does not type + match. + """ + + schema = { + "oneOf": [ + {"type": "object"}, + {"items": {"const": 37}}, + ], + } + best = self.best_match_of(instance=[12, 12], schema=schema) + self.assertEqual(best.validator, "const") + + def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self): + """ + Now, if the error is allOf, we traverse but select the *most* relevant + error from the context, because all schemas here must match anyways. + """ + + schema = { + "properties": { + "foo": { + "allOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": {"bar": 12}}, schema=schema) + self.assertEqual(best.validator_value, "string") + + def test_nested_context_for_oneOf(self): + """ + We traverse into nested contexts (a oneOf containing an error in a + nested oneOf here). + """ + + schema = { + "properties": { + "foo": { + "oneOf": [ + {"type": "string"}, + { + "oneOf": [ + {"type": "string"}, + { + "properties": { + "bar": {"type": "array"}, + }, + }, + ], + }, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": {"bar": 12}}, schema=schema) + self.assertEqual(best.validator_value, "array") + + def test_it_prioritizes_matching_types(self): + schema = { + "properties": { + "foo": { + "anyOf": [ + {"type": "array", "minItems": 2}, + {"type": "string", "minLength": 10}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": "bar"}, schema=schema) + self.assertEqual(best.validator, "minLength") + + reordered = { + "properties": { + "foo": { + "anyOf": [ + {"type": "string", "minLength": 10}, + {"type": "array", "minItems": 2}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": "bar"}, schema=reordered) + self.assertEqual(best.validator, "minLength") + + def test_it_prioritizes_matching_union_types(self): + schema = { + "properties": { + "foo": { + "anyOf": [ + {"type": ["array", "object"], "minItems": 2}, + {"type": ["integer", "string"], "minLength": 10}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": "bar"}, schema=schema) + self.assertEqual(best.validator, "minLength") + + reordered = { + "properties": { + "foo": { + "anyOf": [ + {"type": "string", "minLength": 10}, + {"type": "array", "minItems": 2}, + ], + }, + }, + } + best = self.best_match_of(instance={"foo": "bar"}, schema=reordered) + self.assertEqual(best.validator, "minLength") + + def test_boolean_schemas(self): + schema = {"properties": {"foo": False}} + best = self.best_match_of(instance={"foo": "bar"}, schema=schema) + self.assertIsNone(best.validator) + + def test_one_error(self): + validator = _LATEST_VERSION({"minProperties": 2}) + validator.iter_errors({}) + self.assertEqual( + exceptions.best_match(validator.iter_errors({})).validator, + "minProperties", + ) + + def test_no_errors(self): + validator = _LATEST_VERSION({}) + self.assertIsNone(exceptions.best_match(validator.iter_errors({}))) + + +class TestByRelevance(TestCase): + def test_short_paths_are_better_matches(self): + shallow = exceptions.ValidationError("Oh no!", path=["baz"]) + deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"]) + match = max([shallow, deep], key=exceptions.relevance) + self.assertIs(match, shallow) + + match = max([deep, shallow], key=exceptions.relevance) + self.assertIs(match, shallow) + + def test_global_errors_are_even_better_matches(self): + shallow = exceptions.ValidationError("Oh no!", path=[]) + deep = exceptions.ValidationError("Oh yes!", path=["foo"]) + + errors = sorted([shallow, deep], key=exceptions.relevance) + self.assertEqual( + [list(error.path) for error in errors], + [["foo"], []], + ) + + errors = sorted([deep, shallow], key=exceptions.relevance) + self.assertEqual( + [list(error.path) for error in errors], + [["foo"], []], + ) + + def test_weak_keywords_are_lower_priority(self): + weak = exceptions.ValidationError("Oh no!", path=[], validator="a") + normal = exceptions.ValidationError("Oh yes!", path=[], validator="b") + + best_match = exceptions.by_relevance(weak="a") + + match = max([weak, normal], key=best_match) + self.assertIs(match, normal) + + match = max([normal, weak], key=best_match) + self.assertIs(match, normal) + + def test_strong_keywords_are_higher_priority(self): + weak = exceptions.ValidationError("Oh no!", path=[], validator="a") + normal = exceptions.ValidationError("Oh yes!", path=[], validator="b") + strong = exceptions.ValidationError("Oh fine!", path=[], validator="c") + + best_match = exceptions.by_relevance(weak="a", strong="c") + + match = max([weak, normal, strong], key=best_match) + self.assertIs(match, strong) + + match = max([strong, normal, weak], key=best_match) + self.assertIs(match, strong) + + +class TestErrorTree(TestCase): + def test_it_knows_how_many_total_errors_it_contains(self): + # FIXME: #442 + errors = [ + exceptions.ValidationError("Something", validator=i) + for i in range(8) + ] + tree = exceptions.ErrorTree(errors) + self.assertEqual(tree.total_errors, 8) + + def test_it_contains_an_item_if_the_item_had_an_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertIn("bar", tree) + + def test_it_does_not_contain_an_item_if_the_item_had_no_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertNotIn("foo", tree) + + def test_keywords_that_failed_appear_in_errors_dict(self): + error = exceptions.ValidationError("a message", validator="foo") + tree = exceptions.ErrorTree([error]) + self.assertEqual(tree.errors, {"foo": error}) + + def test_it_creates_a_child_tree_for_each_nested_path(self): + errors = [ + exceptions.ValidationError("a bar message", path=["bar"]), + exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]), + ] + tree = exceptions.ErrorTree(errors) + self.assertIn(0, tree["bar"]) + self.assertNotIn(1, tree["bar"]) + + def test_children_have_their_errors_dicts_built(self): + e1, e2 = ( + exceptions.ValidationError("1", validator="foo", path=["bar", 0]), + exceptions.ValidationError("2", validator="quux", path=["bar", 0]), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2}) + + def test_multiple_errors_with_instance(self): + e1, e2 = ( + exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1"), + exceptions.ValidationError( + "2", + validator="quux", + path=["foobar", 2], + instance="i2"), + ) + exceptions.ErrorTree([e1, e2]) + + def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self): + error = exceptions.ValidationError("123", validator="foo", instance=[]) + tree = exceptions.ErrorTree([error]) + + with self.assertRaises(IndexError): + tree[0] + + def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self): + """ + If a keyword refers to a path that isn't in the instance, the + tree still properly returns a subtree for that path. + """ + + error = exceptions.ValidationError( + "a message", validator="foo", instance={}, path=["foo"], + ) + tree = exceptions.ErrorTree([error]) + self.assertIsInstance(tree["foo"], exceptions.ErrorTree) + + def test_iter(self): + e1, e2 = ( + exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1"), + exceptions.ValidationError( + "2", + validator="quux", + path=["foobar", 2], + instance="i2"), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(set(tree), {"bar", "foobar"}) + + def test_repr_single(self): + error = exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1", + ) + tree = exceptions.ErrorTree([error]) + self.assertEqual(repr(tree), "") + + def test_repr_multiple(self): + e1, e2 = ( + exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1"), + exceptions.ValidationError( + "2", + validator="quux", + path=["foobar", 2], + instance="i2"), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(repr(tree), "") + + def test_repr_empty(self): + tree = exceptions.ErrorTree([]) + self.assertEqual(repr(tree), "") + + +class TestErrorInitReprStr(TestCase): + def make_error(self, **kwargs): + defaults = dict( + message="hello", + validator="type", + validator_value="string", + instance=5, + schema={"type": "string"}, + ) + defaults.update(kwargs) + return exceptions.ValidationError(**defaults) + + def assertShows(self, expected, **kwargs): + expected = textwrap.dedent(expected).rstrip("\n") + + error = self.make_error(**kwargs) + message_line, _, rest = str(error).partition("\n") + self.assertEqual(message_line, error.message) + self.assertEqual(rest, expected) + + def test_it_calls_super_and_sets_args(self): + error = self.make_error() + self.assertGreater(len(error.args), 1) + + def test_repr(self): + self.assertEqual( + repr(exceptions.ValidationError(message="Hello!")), + "", + ) + + def test_unset_error(self): + error = exceptions.ValidationError("message") + self.assertEqual(str(error), "message") + + kwargs = { + "validator": "type", + "validator_value": "string", + "instance": 5, + "schema": {"type": "string"}, + } + # Just the message should show if any of the attributes are unset + for attr in kwargs: + k = dict(kwargs) + del k[attr] + error = exceptions.ValidationError("message", **k) + self.assertEqual(str(error), "message") + + def test_empty_paths(self): + self.assertShows( + """ + Failed validating 'type' in schema: + {'type': 'string'} + + On instance: + 5 + """, + path=[], + schema_path=[], + ) + + def test_one_item_paths(self): + self.assertShows( + """ + Failed validating 'type' in schema: + {'type': 'string'} + + On instance[0]: + 5 + """, + path=[0], + schema_path=["items"], + ) + + def test_multiple_item_paths(self): + self.assertShows( + """ + Failed validating 'type' in schema['items'][0]: + {'type': 'string'} + + On instance[0]['a']: + 5 + """, + path=[0, "a"], + schema_path=["items", 0, 1], + ) + + def test_uses_pprint(self): + self.assertShows( + """ + Failed validating 'maxLength' in schema: + {0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 11: 11, + 12: 12, + 13: 13, + 14: 14, + 15: 15, + 16: 16, + 17: 17, + 18: 18, + 19: 19} + + On instance: + [0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24] + """, + instance=list(range(25)), + schema=dict(zip(range(20), range(20))), + validator="maxLength", + ) + + def test_does_not_reorder_dicts(self): + self.assertShows( + """ + Failed validating 'type' in schema: + {'do': 3, 'not': 7, 'sort': 37, 'me': 73} + + On instance: + {'here': 73, 'too': 37, 'no': 7, 'sorting': 3} + """, + schema={ + "do": 3, + "not": 7, + "sort": 37, + "me": 73, + }, + instance={ + "here": 73, + "too": 37, + "no": 7, + "sorting": 3, + }, + ) + + def test_str_works_with_instances_having_overriden_eq_operator(self): + """ + Check for #164 which rendered exceptions unusable when a + `ValidationError` involved instances with an `__eq__` method + that returned truthy values. + """ + + class DontEQMeBro: + def __eq__(this, other): # pragma: no cover + self.fail("Don't!") + + def __ne__(this, other): # pragma: no cover + self.fail("Don't!") + + instance = DontEQMeBro() + error = exceptions.ValidationError( + "a message", + validator="foo", + instance=instance, + validator_value="some", + schema="schema", + ) + self.assertIn(repr(instance), str(error)) + + +class TestHashable(TestCase): + def test_hashable(self): + {exceptions.ValidationError("")} + {exceptions.SchemaError("")} + + +class TestJsonPathRendering(TestCase): + def validate_json_path_rendering(self, property_name, expected_path): + error = exceptions.ValidationError( + path=[property_name], + message="1", + validator="foo", + instance="i1", + ) + + rendered_json_path = error.json_path + self.assertEqual(rendered_json_path, expected_path) + + re_parsed_name = jsonpath_ng.parse(rendered_json_path).right.fields[0] + self.assertEqual(re_parsed_name, property_name) + + def test_basic(self): + self.validate_json_path_rendering("x", "$.x") + + def test_empty(self): + self.validate_json_path_rendering("", "$['']") + + def test_number(self): + self.validate_json_path_rendering("1", "$['1']") + + def test_period(self): + self.validate_json_path_rendering(".", "$['.']") + + def test_single_quote(self): + self.validate_json_path_rendering("'", r"$['\'']") + + def test_space(self): + self.validate_json_path_rendering(" ", "$[' ']") + + def test_backslash(self): + self.validate_json_path_rendering("\\", r"$['\\']") + + def test_backslash_single_quote(self): + self.validate_json_path_rendering(r"\'", r"$['\\\'']") + + def test_underscore(self): + self.validate_json_path_rendering("_", r"$['_']") + + def test_double_quote(self): + self.validate_json_path_rendering('"', """$['"']""") + + def test_hyphen(self): + self.validate_json_path_rendering("-", "$['-']") + + def test_json_path_injection(self): + self.validate_json_path_rendering("a[0]", "$['a[0]']") + + def test_open_bracket(self): + self.validate_json_path_rendering("[", "$['[']") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py new file mode 100644 index 0000000..d829f98 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py @@ -0,0 +1,91 @@ +""" +Tests for the parts of jsonschema related to the :kw:`format` keyword. +""" + +from unittest import TestCase + +from jsonschema import FormatChecker, ValidationError +from jsonschema.exceptions import FormatError +from jsonschema.validators import Draft4Validator + +BOOM = ValueError("Boom!") +BANG = ZeroDivisionError("Bang!") + + +def boom(thing): + if thing == "bang": + raise BANG + raise BOOM + + +class TestFormatChecker(TestCase): + def test_it_can_validate_no_formats(self): + checker = FormatChecker(formats=()) + self.assertFalse(checker.checkers) + + def test_it_raises_a_key_error_for_unknown_formats(self): + with self.assertRaises(KeyError): + FormatChecker(formats=["o noes"]) + + def test_it_can_register_cls_checkers(self): + original = dict(FormatChecker.checkers) + self.addCleanup(FormatChecker.checkers.pop, "boom") + with self.assertWarns(DeprecationWarning): + FormatChecker.cls_checks("boom")(boom) + self.assertEqual( + FormatChecker.checkers, + dict(original, boom=(boom, ())), + ) + + def test_it_can_register_checkers(self): + checker = FormatChecker() + checker.checks("boom")(boom) + self.assertEqual( + checker.checkers, + dict(FormatChecker.checkers, boom=(boom, ())), + ) + + def test_it_catches_registered_errors(self): + checker = FormatChecker() + checker.checks("boom", raises=type(BOOM))(boom) + + with self.assertRaises(FormatError) as cm: + checker.check(instance=12, format="boom") + + self.assertIs(cm.exception.cause, BOOM) + self.assertIs(cm.exception.__cause__, BOOM) + self.assertEqual(str(cm.exception), "12 is not a 'boom'") + + # Unregistered errors should not be caught + with self.assertRaises(type(BANG)): + checker.check(instance="bang", format="boom") + + def test_format_error_causes_become_validation_error_causes(self): + checker = FormatChecker() + checker.checks("boom", raises=ValueError)(boom) + validator = Draft4Validator({"format": "boom"}, format_checker=checker) + + with self.assertRaises(ValidationError) as cm: + validator.validate("BOOM") + + self.assertIs(cm.exception.cause, BOOM) + self.assertIs(cm.exception.__cause__, BOOM) + + def test_format_checkers_come_with_defaults(self): + # This is bad :/ but relied upon. + # The docs for quite awhile recommended people do things like + # validate(..., format_checker=FormatChecker()) + # We should change that, but we can't without deprecation... + checker = FormatChecker() + with self.assertRaises(FormatError): + checker.check(instance="not-an-ipv4", format="ipv4") + + def test_repr(self): + checker = FormatChecker(formats=()) + checker.checks("foo")(lambda thing: True) # pragma: no cover + checker.checks("bar")(lambda thing: True) # pragma: no cover + checker.checks("baz")(lambda thing: True) # pragma: no cover + self.assertEqual( + repr(checker), + "", + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py new file mode 100644 index 0000000..41c9825 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py @@ -0,0 +1,262 @@ +""" +Test runner for the JSON Schema official test suite + +Tests comprehensive correctness of each draft's validator. + +See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details. +""" + + +from jsonschema.tests._suite import Suite +import jsonschema + +SUITE = Suite() +DRAFT3 = SUITE.version(name="draft3") +DRAFT4 = SUITE.version(name="draft4") +DRAFT6 = SUITE.version(name="draft6") +DRAFT7 = SUITE.version(name="draft7") +DRAFT201909 = SUITE.version(name="draft2019-09") +DRAFT202012 = SUITE.version(name="draft2020-12") + + +def skip(message, **kwargs): + def skipper(test): + if all(value == getattr(test, attr) for attr, value in kwargs.items()): + return message + return skipper + + +def ecmascript_regex(test): + if test.subject == "ecmascript-regex": + return "ECMA regex support will be added in #1142." + + +def missing_format(Validator): + def missing_format(test): # pragma: no cover + schema = test.schema + if ( + schema is True + or schema is False + or "format" not in schema + or schema["format"] in Validator.FORMAT_CHECKER.checkers + or test.valid + ): + return + + return f"Format checker {schema['format']!r} not found." + return missing_format + + +def complex_email_validation(test): + if test.subject != "email": + return + + message = "Complex email validation is (intentionally) unsupported." + return skip( + message=message, + description="an invalid domain", + )(test) or skip( + message=message, + description="an invalid IPv4-address-literal", + )(test) or skip( + message=message, + description="dot after local part is not valid", + )(test) or skip( + message=message, + description="dot before local part is not valid", + )(test) or skip( + message=message, + description="two subsequent dots inside local part are not valid", + )(test) + + +def leap_second(test): + message = "Leap seconds are unsupported." + return skip( + message=message, + subject="time", + description="a valid time string with leap second", + )(test) or skip( + message=message, + subject="time", + description="a valid time string with leap second, Zulu", + )(test) or skip( + message=message, + subject="time", + description="a valid time string with leap second with offset", + )(test) or skip( + message=message, + subject="time", + description="valid leap second, positive time-offset", + )(test) or skip( + message=message, + subject="time", + description="valid leap second, negative time-offset", + )(test) or skip( + message=message, + subject="time", + description="valid leap second, large positive time-offset", + )(test) or skip( + message=message, + subject="time", + description="valid leap second, large negative time-offset", + )(test) or skip( + message=message, + subject="time", + description="valid leap second, zero time-offset", + )(test) or skip( + message=message, + subject="date-time", + description="a valid date-time with a leap second, UTC", + )(test) or skip( + message=message, + subject="date-time", + description="a valid date-time with a leap second, with minus offset", + )(test) + + +TestDraft3 = DRAFT3.to_unittest_testcase( + DRAFT3.cases(), + DRAFT3.format_cases(), + DRAFT3.optional_cases_of(name="bignum"), + DRAFT3.optional_cases_of(name="non-bmp-regex"), + DRAFT3.optional_cases_of(name="zeroTerminatedFloats"), + Validator=jsonschema.Draft3Validator, + format_checker=jsonschema.Draft3Validator.FORMAT_CHECKER, + skip=lambda test: ( + ecmascript_regex(test) + or missing_format(jsonschema.Draft3Validator)(test) + or complex_email_validation(test) + ), +) + + +TestDraft4 = DRAFT4.to_unittest_testcase( + DRAFT4.cases(), + DRAFT4.format_cases(), + DRAFT4.optional_cases_of(name="bignum"), + DRAFT4.optional_cases_of(name="float-overflow"), + DRAFT4.optional_cases_of(name="id"), + DRAFT4.optional_cases_of(name="non-bmp-regex"), + DRAFT4.optional_cases_of(name="zeroTerminatedFloats"), + Validator=jsonschema.Draft4Validator, + format_checker=jsonschema.Draft4Validator.FORMAT_CHECKER, + skip=lambda test: ( + ecmascript_regex(test) + or leap_second(test) + or missing_format(jsonschema.Draft4Validator)(test) + or complex_email_validation(test) + ), +) + + +TestDraft6 = DRAFT6.to_unittest_testcase( + DRAFT6.cases(), + DRAFT6.format_cases(), + DRAFT6.optional_cases_of(name="bignum"), + DRAFT6.optional_cases_of(name="float-overflow"), + DRAFT6.optional_cases_of(name="id"), + DRAFT6.optional_cases_of(name="non-bmp-regex"), + Validator=jsonschema.Draft6Validator, + format_checker=jsonschema.Draft6Validator.FORMAT_CHECKER, + skip=lambda test: ( + ecmascript_regex(test) + or leap_second(test) + or missing_format(jsonschema.Draft6Validator)(test) + or complex_email_validation(test) + ), +) + + +TestDraft7 = DRAFT7.to_unittest_testcase( + DRAFT7.cases(), + DRAFT7.format_cases(), + DRAFT7.optional_cases_of(name="bignum"), + DRAFT7.optional_cases_of(name="cross-draft"), + DRAFT7.optional_cases_of(name="float-overflow"), + DRAFT6.optional_cases_of(name="id"), + DRAFT7.optional_cases_of(name="non-bmp-regex"), + DRAFT7.optional_cases_of(name="unknownKeyword"), + Validator=jsonschema.Draft7Validator, + format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER, + skip=lambda test: ( + ecmascript_regex(test) + or leap_second(test) + or missing_format(jsonschema.Draft7Validator)(test) + or complex_email_validation(test) + ), +) + + +TestDraft201909 = DRAFT201909.to_unittest_testcase( + DRAFT201909.cases(), + DRAFT201909.optional_cases_of(name="anchor"), + DRAFT201909.optional_cases_of(name="bignum"), + DRAFT201909.optional_cases_of(name="cross-draft"), + DRAFT201909.optional_cases_of(name="float-overflow"), + DRAFT201909.optional_cases_of(name="id"), + DRAFT201909.optional_cases_of(name="no-schema"), + DRAFT201909.optional_cases_of(name="non-bmp-regex"), + DRAFT201909.optional_cases_of(name="refOfUnknownKeyword"), + DRAFT201909.optional_cases_of(name="unknownKeyword"), + Validator=jsonschema.Draft201909Validator, + skip=skip( + message="Vocabulary support is still in-progress.", + subject="vocabulary", + description=( + "no validation: invalid number, but it still validates" + ), + ), +) + + +TestDraft201909Format = DRAFT201909.to_unittest_testcase( + DRAFT201909.format_cases(), + name="TestDraft201909Format", + Validator=jsonschema.Draft201909Validator, + format_checker=jsonschema.Draft201909Validator.FORMAT_CHECKER, + skip=lambda test: ( + complex_email_validation(test) + or ecmascript_regex(test) + or leap_second(test) + or missing_format(jsonschema.Draft201909Validator)(test) + or complex_email_validation(test) + ), +) + + +TestDraft202012 = DRAFT202012.to_unittest_testcase( + DRAFT202012.cases(), + DRAFT201909.optional_cases_of(name="anchor"), + DRAFT202012.optional_cases_of(name="bignum"), + DRAFT202012.optional_cases_of(name="cross-draft"), + DRAFT202012.optional_cases_of(name="float-overflow"), + DRAFT202012.optional_cases_of(name="id"), + DRAFT202012.optional_cases_of(name="no-schema"), + DRAFT202012.optional_cases_of(name="non-bmp-regex"), + DRAFT202012.optional_cases_of(name="refOfUnknownKeyword"), + DRAFT202012.optional_cases_of(name="unknownKeyword"), + Validator=jsonschema.Draft202012Validator, + skip=skip( + message="Vocabulary support is still in-progress.", + subject="vocabulary", + description=( + "no validation: invalid number, but it still validates" + ), + ), +) + + +TestDraft202012Format = DRAFT202012.to_unittest_testcase( + DRAFT202012.format_cases(), + name="TestDraft202012Format", + Validator=jsonschema.Draft202012Validator, + format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER, + skip=lambda test: ( + complex_email_validation(test) + or ecmascript_regex(test) + or leap_second(test) + or missing_format(jsonschema.Draft202012Validator)(test) + or complex_email_validation(test) + ), +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py new file mode 100644 index 0000000..bd97b18 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py @@ -0,0 +1,221 @@ +""" +Tests for the `TypeChecker`-based type interface. + +The actual correctness of the type checking is handled in +`test_jsonschema_test_suite`; these tests check that TypeChecker +functions correctly at a more granular level. +""" +from collections import namedtuple +from unittest import TestCase + +from jsonschema import ValidationError, _keywords +from jsonschema._types import TypeChecker +from jsonschema.exceptions import UndefinedTypeCheck, UnknownType +from jsonschema.validators import Draft202012Validator, extend + + +def equals_2(checker, instance): + return instance == 2 + + +def is_namedtuple(instance): + return isinstance(instance, tuple) and getattr(instance, "_fields", None) + + +def is_object_or_named_tuple(checker, instance): + if Draft202012Validator.TYPE_CHECKER.is_type(instance, "object"): + return True + return is_namedtuple(instance) + + +class TestTypeChecker(TestCase): + def test_is_type(self): + checker = TypeChecker({"two": equals_2}) + self.assertEqual( + ( + checker.is_type(instance=2, type="two"), + checker.is_type(instance="bar", type="two"), + ), + (True, False), + ) + + def test_is_unknown_type(self): + with self.assertRaises(UndefinedTypeCheck) as e: + TypeChecker().is_type(4, "foobar") + self.assertIn( + "'foobar' is unknown to this type checker", + str(e.exception), + ) + self.assertTrue( + e.exception.__suppress_context__, + msg="Expected the internal KeyError to be hidden.", + ) + + def test_checks_can_be_added_at_init(self): + checker = TypeChecker({"two": equals_2}) + self.assertEqual(checker, TypeChecker().redefine("two", equals_2)) + + def test_redefine_existing_type(self): + self.assertEqual( + TypeChecker().redefine("two", object()).redefine("two", equals_2), + TypeChecker().redefine("two", equals_2), + ) + + def test_remove(self): + self.assertEqual( + TypeChecker({"two": equals_2}).remove("two"), + TypeChecker(), + ) + + def test_remove_unknown_type(self): + with self.assertRaises(UndefinedTypeCheck) as context: + TypeChecker().remove("foobar") + self.assertIn("foobar", str(context.exception)) + + def test_redefine_many(self): + self.assertEqual( + TypeChecker().redefine_many({"foo": int, "bar": str}), + TypeChecker().redefine("foo", int).redefine("bar", str), + ) + + def test_remove_multiple(self): + self.assertEqual( + TypeChecker({"foo": int, "bar": str}).remove("foo", "bar"), + TypeChecker(), + ) + + def test_type_check_can_raise_key_error(self): + """ + Make sure no one writes: + + try: + self._type_checkers[type](...) + except KeyError: + + ignoring the fact that the function itself can raise that. + """ + + error = KeyError("Stuff") + + def raises_keyerror(checker, instance): + raise error + + with self.assertRaises(KeyError) as context: + TypeChecker({"foo": raises_keyerror}).is_type(4, "foo") + + self.assertIs(context.exception, error) + + def test_repr(self): + checker = TypeChecker({"foo": is_namedtuple, "bar": is_namedtuple}) + self.assertEqual(repr(checker), "") + + +class TestCustomTypes(TestCase): + def test_simple_type_can_be_extended(self): + def int_or_str_int(checker, instance): + if not isinstance(instance, (int, str)): + return False + try: + int(instance) + except ValueError: + return False + return True + + CustomValidator = extend( + Draft202012Validator, + type_checker=Draft202012Validator.TYPE_CHECKER.redefine( + "integer", int_or_str_int, + ), + ) + validator = CustomValidator({"type": "integer"}) + + validator.validate(4) + validator.validate("4") + + with self.assertRaises(ValidationError): + validator.validate(4.4) + + with self.assertRaises(ValidationError): + validator.validate("foo") + + def test_object_can_be_extended(self): + schema = {"type": "object"} + + Point = namedtuple("Point", ["x", "y"]) + + type_checker = Draft202012Validator.TYPE_CHECKER.redefine( + "object", is_object_or_named_tuple, + ) + + CustomValidator = extend( + Draft202012Validator, + type_checker=type_checker, + ) + validator = CustomValidator(schema) + + validator.validate(Point(x=4, y=5)) + + def test_object_extensions_require_custom_validators(self): + schema = {"type": "object", "required": ["x"]} + + type_checker = Draft202012Validator.TYPE_CHECKER.redefine( + "object", is_object_or_named_tuple, + ) + + CustomValidator = extend( + Draft202012Validator, + type_checker=type_checker, + ) + validator = CustomValidator(schema) + + Point = namedtuple("Point", ["x", "y"]) + # Cannot handle required + with self.assertRaises(ValidationError): + validator.validate(Point(x=4, y=5)) + + def test_object_extensions_can_handle_custom_validators(self): + schema = { + "type": "object", + "required": ["x"], + "properties": {"x": {"type": "integer"}}, + } + + type_checker = Draft202012Validator.TYPE_CHECKER.redefine( + "object", is_object_or_named_tuple, + ) + + def coerce_named_tuple(fn): + def coerced(validator, value, instance, schema): + if is_namedtuple(instance): + instance = instance._asdict() + return fn(validator, value, instance, schema) + return coerced + + required = coerce_named_tuple(_keywords.required) + properties = coerce_named_tuple(_keywords.properties) + + CustomValidator = extend( + Draft202012Validator, + type_checker=type_checker, + validators={"required": required, "properties": properties}, + ) + + validator = CustomValidator(schema) + + Point = namedtuple("Point", ["x", "y"]) + # Can now process required and properties + validator.validate(Point(x=4, y=5)) + + with self.assertRaises(ValidationError): + validator.validate(Point(x="not an integer", y=5)) + + # As well as still handle objects. + validator.validate({"x": 4, "y": 5}) + + with self.assertRaises(ValidationError): + validator.validate({"x": "not an integer", "y": 5}) + + def test_unknown_type(self): + with self.assertRaises(UnknownType) as e: + Draft202012Validator({}).is_type(12, "some unknown type") + self.assertIn("'some unknown type'", str(e.exception)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py new file mode 100644 index 0000000..d9764b0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py @@ -0,0 +1,138 @@ +from math import nan +from unittest import TestCase + +from jsonschema._utils import equal + + +class TestEqual(TestCase): + def test_none(self): + self.assertTrue(equal(None, None)) + + def test_nan(self): + self.assertTrue(equal(nan, nan)) + + +class TestDictEqual(TestCase): + def test_equal_dictionaries(self): + dict_1 = {"a": "b", "c": "d"} + dict_2 = {"c": "d", "a": "b"} + self.assertTrue(equal(dict_1, dict_2)) + + def test_equal_dictionaries_with_nan(self): + dict_1 = {"a": nan, "c": "d"} + dict_2 = {"c": "d", "a": nan} + self.assertTrue(equal(dict_1, dict_2)) + + def test_missing_key(self): + dict_1 = {"a": "b", "c": "d"} + dict_2 = {"c": "d", "x": "b"} + self.assertFalse(equal(dict_1, dict_2)) + + def test_additional_key(self): + dict_1 = {"a": "b", "c": "d"} + dict_2 = {"c": "d", "a": "b", "x": "x"} + self.assertFalse(equal(dict_1, dict_2)) + + def test_missing_value(self): + dict_1 = {"a": "b", "c": "d"} + dict_2 = {"c": "d", "a": "x"} + self.assertFalse(equal(dict_1, dict_2)) + + def test_empty_dictionaries(self): + dict_1 = {} + dict_2 = {} + self.assertTrue(equal(dict_1, dict_2)) + + def test_one_none(self): + dict_1 = None + dict_2 = {"a": "b", "c": "d"} + self.assertFalse(equal(dict_1, dict_2)) + + def test_same_item(self): + dict_1 = {"a": "b", "c": "d"} + self.assertTrue(equal(dict_1, dict_1)) + + def test_nested_equal(self): + dict_1 = {"a": {"a": "b", "c": "d"}, "c": "d"} + dict_2 = {"c": "d", "a": {"a": "b", "c": "d"}} + self.assertTrue(equal(dict_1, dict_2)) + + def test_nested_dict_unequal(self): + dict_1 = {"a": {"a": "b", "c": "d"}, "c": "d"} + dict_2 = {"c": "d", "a": {"a": "b", "c": "x"}} + self.assertFalse(equal(dict_1, dict_2)) + + def test_mixed_nested_equal(self): + dict_1 = {"a": ["a", "b", "c", "d"], "c": "d"} + dict_2 = {"c": "d", "a": ["a", "b", "c", "d"]} + self.assertTrue(equal(dict_1, dict_2)) + + def test_nested_list_unequal(self): + dict_1 = {"a": ["a", "b", "c", "d"], "c": "d"} + dict_2 = {"c": "d", "a": ["b", "c", "d", "a"]} + self.assertFalse(equal(dict_1, dict_2)) + + +class TestListEqual(TestCase): + def test_equal_lists(self): + list_1 = ["a", "b", "c"] + list_2 = ["a", "b", "c"] + self.assertTrue(equal(list_1, list_2)) + + def test_equal_lists_with_nan(self): + list_1 = ["a", nan, "c"] + list_2 = ["a", nan, "c"] + self.assertTrue(equal(list_1, list_2)) + + def test_unsorted_lists(self): + list_1 = ["a", "b", "c"] + list_2 = ["b", "b", "a"] + self.assertFalse(equal(list_1, list_2)) + + def test_first_list_larger(self): + list_1 = ["a", "b", "c"] + list_2 = ["a", "b"] + self.assertFalse(equal(list_1, list_2)) + + def test_second_list_larger(self): + list_1 = ["a", "b"] + list_2 = ["a", "b", "c"] + self.assertFalse(equal(list_1, list_2)) + + def test_list_with_none_unequal(self): + list_1 = ["a", "b", None] + list_2 = ["a", "b", "c"] + self.assertFalse(equal(list_1, list_2)) + + list_1 = ["a", "b", None] + list_2 = [None, "b", "c"] + self.assertFalse(equal(list_1, list_2)) + + def test_list_with_none_equal(self): + list_1 = ["a", None, "c"] + list_2 = ["a", None, "c"] + self.assertTrue(equal(list_1, list_2)) + + def test_empty_list(self): + list_1 = [] + list_2 = [] + self.assertTrue(equal(list_1, list_2)) + + def test_one_none(self): + list_1 = None + list_2 = [] + self.assertFalse(equal(list_1, list_2)) + + def test_same_list(self): + list_1 = ["a", "b", "c"] + self.assertTrue(equal(list_1, list_1)) + + def test_equal_nested_lists(self): + list_1 = ["a", ["b", "c"], "d"] + list_2 = ["a", ["b", "c"], "d"] + self.assertTrue(equal(list_1, list_2)) + + def test_unequal_nested_lists(self): + list_1 = ["a", ["b", "c"], "d"] + list_2 = ["a", [], "c"] + self.assertFalse(equal(list_1, list_2)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py new file mode 100644 index 0000000..d419ddf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py @@ -0,0 +1,2573 @@ +from __future__ import annotations + +from collections import deque, namedtuple +from contextlib import contextmanager +from decimal import Decimal +from io import BytesIO +from typing import Any +from unittest import TestCase, mock +from urllib.request import pathname2url +import json +import os +import sys +import tempfile +import warnings + +from attrs import define, field +from referencing.jsonschema import DRAFT202012 +import referencing.exceptions + +from jsonschema import ( + FormatChecker, + TypeChecker, + exceptions, + protocols, + validators, +) + + +def fail(validator, errors, instance, schema): + for each in errors: + each.setdefault("message", "You told me to fail!") + yield exceptions.ValidationError(**each) + + +class TestCreateAndExtend(TestCase): + def setUp(self): + self.addCleanup( + self.assertEqual, + validators._META_SCHEMAS, + dict(validators._META_SCHEMAS), + ) + self.addCleanup( + self.assertEqual, + validators._VALIDATORS, + dict(validators._VALIDATORS), + ) + + self.meta_schema = {"$id": "some://meta/schema"} + self.validators = {"fail": fail} + self.type_checker = TypeChecker() + self.Validator = validators.create( + meta_schema=self.meta_schema, + validators=self.validators, + type_checker=self.type_checker, + ) + + def test_attrs(self): + self.assertEqual( + ( + self.Validator.VALIDATORS, + self.Validator.META_SCHEMA, + self.Validator.TYPE_CHECKER, + ), ( + self.validators, + self.meta_schema, + self.type_checker, + ), + ) + + def test_init(self): + schema = {"fail": []} + self.assertEqual(self.Validator(schema).schema, schema) + + def test_iter_errors_successful(self): + schema = {"fail": []} + validator = self.Validator(schema) + + errors = list(validator.iter_errors("hello")) + self.assertEqual(errors, []) + + def test_iter_errors_one_error(self): + schema = {"fail": [{"message": "Whoops!"}]} + validator = self.Validator(schema) + + expected_error = exceptions.ValidationError( + "Whoops!", + instance="goodbye", + schema=schema, + validator="fail", + validator_value=[{"message": "Whoops!"}], + schema_path=deque(["fail"]), + ) + + errors = list(validator.iter_errors("goodbye")) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0]._contents(), expected_error._contents()) + + def test_iter_errors_multiple_errors(self): + schema = { + "fail": [ + {"message": "First"}, + {"message": "Second!", "validator": "asdf"}, + {"message": "Third"}, + ], + } + validator = self.Validator(schema) + + errors = list(validator.iter_errors("goodbye")) + self.assertEqual(len(errors), 3) + + def test_if_a_version_is_provided_it_is_registered(self): + Validator = validators.create( + meta_schema={"$id": "something"}, + version="my version", + ) + self.addCleanup(validators._META_SCHEMAS.pop, "something") + self.addCleanup(validators._VALIDATORS.pop, "my version") + self.assertEqual(Validator.__name__, "MyVersionValidator") + self.assertEqual(Validator.__qualname__, "MyVersionValidator") + + def test_repr(self): + Validator = validators.create( + meta_schema={"$id": "something"}, + version="my version", + ) + self.addCleanup(validators._META_SCHEMAS.pop, "something") + self.addCleanup(validators._VALIDATORS.pop, "my version") + self.assertEqual( + repr(Validator({})), + "MyVersionValidator(schema={}, format_checker=None)", + ) + + def test_long_repr(self): + Validator = validators.create( + meta_schema={"$id": "something"}, + version="my version", + ) + self.addCleanup(validators._META_SCHEMAS.pop, "something") + self.addCleanup(validators._VALIDATORS.pop, "my version") + self.assertEqual( + repr(Validator({"a": list(range(1000))})), ( + "MyVersionValidator(schema={'a': [0, 1, 2, 3, 4, 5, ...]}, " + "format_checker=None)" + ), + ) + + def test_repr_no_version(self): + Validator = validators.create(meta_schema={}) + self.assertEqual( + repr(Validator({})), + "Validator(schema={}, format_checker=None)", + ) + + def test_dashes_are_stripped_from_validator_names(self): + Validator = validators.create( + meta_schema={"$id": "something"}, + version="foo-bar", + ) + self.addCleanup(validators._META_SCHEMAS.pop, "something") + self.addCleanup(validators._VALIDATORS.pop, "foo-bar") + self.assertEqual(Validator.__qualname__, "FooBarValidator") + + def test_if_a_version_is_not_provided_it_is_not_registered(self): + original = dict(validators._META_SCHEMAS) + validators.create(meta_schema={"id": "id"}) + self.assertEqual(validators._META_SCHEMAS, original) + + def test_validates_registers_meta_schema_id(self): + meta_schema_key = "meta schema id" + my_meta_schema = {"id": meta_schema_key} + + validators.create( + meta_schema=my_meta_schema, + version="my version", + id_of=lambda s: s.get("id", ""), + ) + self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key) + self.addCleanup(validators._VALIDATORS.pop, "my version") + + self.assertIn(meta_schema_key, validators._META_SCHEMAS) + + def test_validates_registers_meta_schema_draft6_id(self): + meta_schema_key = "meta schema $id" + my_meta_schema = {"$id": meta_schema_key} + + validators.create( + meta_schema=my_meta_schema, + version="my version", + ) + self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key) + self.addCleanup(validators._VALIDATORS.pop, "my version") + + self.assertIn(meta_schema_key, validators._META_SCHEMAS) + + def test_create_default_types(self): + Validator = validators.create(meta_schema={}, validators=()) + self.assertTrue( + all( + Validator({}).is_type(instance=instance, type=type) + for type, instance in [ + ("array", []), + ("boolean", True), + ("integer", 12), + ("null", None), + ("number", 12.0), + ("object", {}), + ("string", "foo"), + ] + ), + ) + + def test_check_schema_with_different_metaschema(self): + """ + One can create a validator class whose metaschema uses a different + dialect than itself. + """ + + NoEmptySchemasValidator = validators.create( + meta_schema={ + "$schema": validators.Draft202012Validator.META_SCHEMA["$id"], + "not": {"const": {}}, + }, + ) + NoEmptySchemasValidator.check_schema({"foo": "bar"}) + + with self.assertRaises(exceptions.SchemaError): + NoEmptySchemasValidator.check_schema({}) + + NoEmptySchemasValidator({"foo": "bar"}).validate("foo") + + def test_check_schema_with_different_metaschema_defaults_to_self(self): + """ + A validator whose metaschema doesn't declare $schema defaults to its + own validation behavior, not the latest "normal" specification. + """ + + NoEmptySchemasValidator = validators.create( + meta_schema={"fail": [{"message": "Meta schema whoops!"}]}, + validators={"fail": fail}, + ) + with self.assertRaises(exceptions.SchemaError): + NoEmptySchemasValidator.check_schema({}) + + def test_extend(self): + original = dict(self.Validator.VALIDATORS) + new = object() + + Extended = validators.extend( + self.Validator, + validators={"new": new}, + ) + self.assertEqual( + ( + Extended.VALIDATORS, + Extended.META_SCHEMA, + Extended.TYPE_CHECKER, + self.Validator.VALIDATORS, + ), ( + dict(original, new=new), + self.Validator.META_SCHEMA, + self.Validator.TYPE_CHECKER, + original, + ), + ) + + def test_extend_idof(self): + """ + Extending a validator preserves its notion of schema IDs. + """ + def id_of(schema): + return schema.get("__test__", self.Validator.ID_OF(schema)) + correct_id = "the://correct/id/" + meta_schema = { + "$id": "the://wrong/id/", + "__test__": correct_id, + } + Original = validators.create( + meta_schema=meta_schema, + validators=self.validators, + type_checker=self.type_checker, + id_of=id_of, + ) + self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id) + + Derived = validators.extend(Original) + self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id) + + def test_extend_applicable_validators(self): + """ + Extending a validator preserves its notion of applicable validators. + """ + + schema = { + "$defs": {"test": {"type": "number"}}, + "$ref": "#/$defs/test", + "maximum": 1, + } + + draft4 = validators.Draft4Validator(schema) + self.assertTrue(draft4.is_valid(37)) # as $ref ignores siblings + + Derived = validators.extend(validators.Draft4Validator) + self.assertTrue(Derived(schema).is_valid(37)) + + +class TestValidationErrorMessages(TestCase): + def message_for(self, instance, schema, *args, **kwargs): + cls = kwargs.pop("cls", validators._LATEST_VERSION) + cls.check_schema(schema) + validator = cls(schema, *args, **kwargs) + errors = list(validator.iter_errors(instance)) + self.assertTrue(errors, msg=f"No errors were raised for {instance!r}") + self.assertEqual( + len(errors), + 1, + msg=f"Expected exactly one error, found {errors!r}", + ) + return errors[0].message + + def test_single_type_failure(self): + message = self.message_for(instance=1, schema={"type": "string"}) + self.assertEqual(message, "1 is not of type 'string'") + + def test_single_type_list_failure(self): + message = self.message_for(instance=1, schema={"type": ["string"]}) + self.assertEqual(message, "1 is not of type 'string'") + + def test_multiple_type_failure(self): + types = "string", "object" + message = self.message_for(instance=1, schema={"type": list(types)}) + self.assertEqual(message, "1 is not of type 'string', 'object'") + + def test_object_with_named_type_failure(self): + schema = {"type": [{"name": "Foo", "minimum": 3}]} + message = self.message_for( + instance=1, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "1 is not of type 'Foo'") + + def test_minimum(self): + message = self.message_for(instance=1, schema={"minimum": 2}) + self.assertEqual(message, "1 is less than the minimum of 2") + + def test_maximum(self): + message = self.message_for(instance=1, schema={"maximum": 0}) + self.assertEqual(message, "1 is greater than the maximum of 0") + + def test_dependencies_single_element(self): + depend, on = "bar", "foo" + schema = {"dependencies": {depend: on}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "'foo' is a dependency of 'bar'") + + def test_object_without_title_type_failure_draft3(self): + type = {"type": [{"minimum": 3}]} + message = self.message_for( + instance=1, + schema={"type": [type]}, + cls=validators.Draft3Validator, + ) + self.assertEqual( + message, + "1 is not of type {'type': [{'minimum': 3}]}", + ) + + def test_dependencies_list_draft3(self): + depend, on = "bar", "foo" + schema = {"dependencies": {depend: [on]}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "'foo' is a dependency of 'bar'") + + def test_dependencies_list_draft7(self): + depend, on = "bar", "foo" + schema = {"dependencies": {depend: [on]}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft7Validator, + ) + self.assertEqual(message, "'foo' is a dependency of 'bar'") + + def test_additionalItems_single_failure(self): + message = self.message_for( + instance=[2], + schema={"items": [], "additionalItems": False}, + cls=validators.Draft3Validator, + ) + self.assertIn("(2 was unexpected)", message) + + def test_additionalItems_multiple_failures(self): + message = self.message_for( + instance=[1, 2, 3], + schema={"items": [], "additionalItems": False}, + cls=validators.Draft3Validator, + ) + self.assertIn("(1, 2, 3 were unexpected)", message) + + def test_additionalProperties_single_failure(self): + additional = "foo" + schema = {"additionalProperties": False} + message = self.message_for(instance={additional: 2}, schema=schema) + self.assertIn("('foo' was unexpected)", message) + + def test_additionalProperties_multiple_failures(self): + schema = {"additionalProperties": False} + message = self.message_for( + instance=dict.fromkeys(["foo", "bar"]), + schema=schema, + ) + + self.assertIn(repr("foo"), message) + self.assertIn(repr("bar"), message) + self.assertIn("were unexpected)", message) + + def test_const(self): + schema = {"const": 12} + message = self.message_for( + instance={"foo": "bar"}, + schema=schema, + ) + self.assertIn("12 was expected", message) + + def test_contains_draft_6(self): + schema = {"contains": {"const": 12}} + message = self.message_for( + instance=[2, {}, []], + schema=schema, + cls=validators.Draft6Validator, + ) + self.assertEqual( + message, + "None of [2, {}, []] are valid under the given schema", + ) + + def test_invalid_format_default_message(self): + checker = FormatChecker(formats=()) + checker.checks("thing")(lambda value: False) + + schema = {"format": "thing"} + message = self.message_for( + instance="bla", + schema=schema, + format_checker=checker, + ) + + self.assertIn(repr("bla"), message) + self.assertIn(repr("thing"), message) + self.assertIn("is not a", message) + + def test_additionalProperties_false_patternProperties(self): + schema = {"type": "object", + "additionalProperties": False, + "patternProperties": { + "^abc$": {"type": "string"}, + "^def$": {"type": "string"}, + }} + message = self.message_for( + instance={"zebra": 123}, + schema=schema, + cls=validators.Draft4Validator, + ) + self.assertEqual( + message, + "{} does not match any of the regexes: {}, {}".format( + repr("zebra"), repr("^abc$"), repr("^def$"), + ), + ) + message = self.message_for( + instance={"zebra": 123, "fish": 456}, + schema=schema, + cls=validators.Draft4Validator, + ) + self.assertEqual( + message, + "{}, {} do not match any of the regexes: {}, {}".format( + repr("fish"), repr("zebra"), repr("^abc$"), repr("^def$"), + ), + ) + + def test_False_schema(self): + message = self.message_for( + instance="something", + schema=False, + ) + self.assertEqual(message, "False schema does not allow 'something'") + + def test_multipleOf(self): + message = self.message_for( + instance=3, + schema={"multipleOf": 2}, + ) + self.assertEqual(message, "3 is not a multiple of 2") + + def test_minItems(self): + message = self.message_for(instance=[], schema={"minItems": 2}) + self.assertEqual(message, "[] is too short") + + def test_maxItems(self): + message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 2}) + self.assertEqual(message, "[1, 2, 3] is too long") + + def test_minItems_1(self): + message = self.message_for(instance=[], schema={"minItems": 1}) + self.assertEqual(message, "[] should be non-empty") + + def test_maxItems_0(self): + message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 0}) + self.assertEqual(message, "[1, 2, 3] is expected to be empty") + + def test_minLength(self): + message = self.message_for( + instance="", + schema={"minLength": 2}, + ) + self.assertEqual(message, "'' is too short") + + def test_maxLength(self): + message = self.message_for( + instance="abc", + schema={"maxLength": 2}, + ) + self.assertEqual(message, "'abc' is too long") + + def test_minLength_1(self): + message = self.message_for(instance="", schema={"minLength": 1}) + self.assertEqual(message, "'' should be non-empty") + + def test_maxLength_0(self): + message = self.message_for(instance="abc", schema={"maxLength": 0}) + self.assertEqual(message, "'abc' is expected to be empty") + + def test_minProperties(self): + message = self.message_for(instance={}, schema={"minProperties": 2}) + self.assertEqual(message, "{} does not have enough properties") + + def test_maxProperties(self): + message = self.message_for( + instance={"a": {}, "b": {}, "c": {}}, + schema={"maxProperties": 2}, + ) + self.assertEqual( + message, + "{'a': {}, 'b': {}, 'c': {}} has too many properties", + ) + + def test_minProperties_1(self): + message = self.message_for(instance={}, schema={"minProperties": 1}) + self.assertEqual(message, "{} should be non-empty") + + def test_maxProperties_0(self): + message = self.message_for( + instance={1: 2}, + schema={"maxProperties": 0}, + ) + self.assertEqual(message, "{1: 2} is expected to be empty") + + def test_prefixItems_with_items(self): + message = self.message_for( + instance=[1, 2, "foo"], + schema={"items": False, "prefixItems": [{}, {}]}, + ) + self.assertEqual( + message, + "Expected at most 2 items but found 1 extra: 'foo'", + ) + + def test_prefixItems_with_multiple_extra_items(self): + message = self.message_for( + instance=[1, 2, "foo", 5], + schema={"items": False, "prefixItems": [{}, {}]}, + ) + self.assertEqual( + message, + "Expected at most 2 items but found 2 extra: ['foo', 5]", + ) + + def test_pattern(self): + message = self.message_for( + instance="bbb", + schema={"pattern": "^a*$"}, + ) + self.assertEqual(message, "'bbb' does not match '^a*$'") + + def test_does_not_contain(self): + message = self.message_for( + instance=[], + schema={"contains": {"type": "string"}}, + ) + self.assertEqual( + message, + "[] does not contain items matching the given schema", + ) + + def test_contains_too_few(self): + message = self.message_for( + instance=["foo", 1], + schema={"contains": {"type": "string"}, "minContains": 2}, + ) + self.assertEqual( + message, + "Too few items match the given schema " + "(expected at least 2 but only 1 matched)", + ) + + def test_contains_too_few_both_constrained(self): + message = self.message_for( + instance=["foo", 1], + schema={ + "contains": {"type": "string"}, + "minContains": 2, + "maxContains": 4, + }, + ) + self.assertEqual( + message, + "Too few items match the given schema (expected at least 2 but " + "only 1 matched)", + ) + + def test_contains_too_many(self): + message = self.message_for( + instance=["foo", "bar", "baz"], + schema={"contains": {"type": "string"}, "maxContains": 2}, + ) + self.assertEqual( + message, + "Too many items match the given schema (expected at most 2)", + ) + + def test_contains_too_many_both_constrained(self): + message = self.message_for( + instance=["foo"] * 5, + schema={ + "contains": {"type": "string"}, + "minContains": 2, + "maxContains": 4, + }, + ) + self.assertEqual( + message, + "Too many items match the given schema (expected at most 4)", + ) + + def test_exclusiveMinimum(self): + message = self.message_for( + instance=3, + schema={"exclusiveMinimum": 5}, + ) + self.assertEqual( + message, + "3 is less than or equal to the minimum of 5", + ) + + def test_exclusiveMaximum(self): + message = self.message_for(instance=3, schema={"exclusiveMaximum": 2}) + self.assertEqual( + message, + "3 is greater than or equal to the maximum of 2", + ) + + def test_required(self): + message = self.message_for(instance={}, schema={"required": ["foo"]}) + self.assertEqual(message, "'foo' is a required property") + + def test_dependentRequired(self): + message = self.message_for( + instance={"foo": {}}, + schema={"dependentRequired": {"foo": ["bar"]}}, + ) + self.assertEqual(message, "'bar' is a dependency of 'foo'") + + def test_oneOf_matches_none(self): + message = self.message_for(instance={}, schema={"oneOf": [False]}) + self.assertEqual( + message, + "{} is not valid under any of the given schemas", + ) + + def test_oneOf_matches_too_many(self): + message = self.message_for(instance={}, schema={"oneOf": [True, True]}) + self.assertEqual(message, "{} is valid under each of True, True") + + def test_unevaluated_items(self): + schema = {"type": "array", "unevaluatedItems": False} + message = self.message_for(instance=["foo", "bar"], schema=schema) + self.assertIn( + message, + "Unevaluated items are not allowed ('foo', 'bar' were unexpected)", + ) + + def test_unevaluated_items_on_invalid_type(self): + schema = {"type": "array", "unevaluatedItems": False} + message = self.message_for(instance="foo", schema=schema) + self.assertEqual(message, "'foo' is not of type 'array'") + + def test_unevaluated_properties_invalid_against_subschema(self): + schema = { + "properties": {"foo": {"type": "string"}}, + "unevaluatedProperties": {"const": 12}, + } + message = self.message_for( + instance={ + "foo": "foo", + "bar": "bar", + "baz": 12, + }, + schema=schema, + ) + self.assertEqual( + message, + "Unevaluated properties are not valid under the given schema " + "('bar' was unevaluated and invalid)", + ) + + def test_unevaluated_properties_disallowed(self): + schema = {"type": "object", "unevaluatedProperties": False} + message = self.message_for( + instance={ + "foo": "foo", + "bar": "bar", + }, + schema=schema, + ) + self.assertEqual( + message, + "Unevaluated properties are not allowed " + "('bar', 'foo' were unexpected)", + ) + + def test_unevaluated_properties_on_invalid_type(self): + schema = {"type": "object", "unevaluatedProperties": False} + message = self.message_for(instance="foo", schema=schema) + self.assertEqual(message, "'foo' is not of type 'object'") + + def test_single_item(self): + schema = {"prefixItems": [{}], "items": False} + message = self.message_for( + instance=["foo", "bar", "baz"], + schema=schema, + ) + self.assertEqual( + message, + "Expected at most 1 item but found 2 extra: ['bar', 'baz']", + ) + + def test_heterogeneous_additionalItems_with_Items(self): + schema = {"items": [{}], "additionalItems": False} + message = self.message_for( + instance=["foo", "bar", 37], + schema=schema, + cls=validators.Draft7Validator, + ) + self.assertEqual( + message, + "Additional items are not allowed ('bar', 37 were unexpected)", + ) + + def test_heterogeneous_items_prefixItems(self): + schema = {"prefixItems": [{}], "items": False} + message = self.message_for( + instance=["foo", "bar", 37], + schema=schema, + ) + self.assertEqual( + message, + "Expected at most 1 item but found 2 extra: ['bar', 37]", + ) + + def test_heterogeneous_unevaluatedItems_prefixItems(self): + schema = {"prefixItems": [{}], "unevaluatedItems": False} + message = self.message_for( + instance=["foo", "bar", 37], + schema=schema, + ) + self.assertEqual( + message, + "Unevaluated items are not allowed ('bar', 37 were unexpected)", + ) + + def test_heterogeneous_properties_additionalProperties(self): + """ + Not valid deserialized JSON, but this should not blow up. + """ + schema = {"properties": {"foo": {}}, "additionalProperties": False} + message = self.message_for( + instance={"foo": {}, "a": "baz", 37: 12}, + schema=schema, + ) + self.assertEqual( + message, + "Additional properties are not allowed (37, 'a' were unexpected)", + ) + + def test_heterogeneous_properties_unevaluatedProperties(self): + """ + Not valid deserialized JSON, but this should not blow up. + """ + schema = {"properties": {"foo": {}}, "unevaluatedProperties": False} + message = self.message_for( + instance={"foo": {}, "a": "baz", 37: 12}, + schema=schema, + ) + self.assertEqual( + message, + "Unevaluated properties are not allowed (37, 'a' were unexpected)", + ) + + +class TestValidationErrorDetails(TestCase): + # TODO: These really need unit tests for each individual keyword, rather + # than just these higher level tests. + def test_anyOf(self): + instance = 5 + schema = { + "anyOf": [ + {"minimum": 20}, + {"type": "string"}, + ], + } + + validator = validators.Draft4Validator(schema) + errors = list(validator.iter_errors(instance)) + self.assertEqual(len(errors), 1) + e = errors[0] + + self.assertEqual(e.validator, "anyOf") + self.assertEqual(e.validator_value, schema["anyOf"]) + self.assertEqual(e.instance, instance) + self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + self.assertEqual(e.json_path, "$") + + self.assertEqual(e.schema_path, deque(["anyOf"])) + self.assertEqual(e.relative_schema_path, deque(["anyOf"])) + self.assertEqual(e.absolute_schema_path, deque(["anyOf"])) + + self.assertEqual(len(e.context), 2) + + e1, e2 = sorted_errors(e.context) + + self.assertEqual(e1.validator, "minimum") + self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"]) + self.assertEqual(e1.instance, instance) + self.assertEqual(e1.schema, schema["anyOf"][0]) + self.assertIs(e1.parent, e) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e1.absolute_path, deque([])) + self.assertEqual(e1.relative_path, deque([])) + self.assertEqual(e1.json_path, "$") + + self.assertEqual(e1.schema_path, deque([0, "minimum"])) + self.assertEqual(e1.relative_schema_path, deque([0, "minimum"])) + self.assertEqual( + e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]), + ) + + self.assertFalse(e1.context) + + self.assertEqual(e2.validator, "type") + self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"]) + self.assertEqual(e2.instance, instance) + self.assertEqual(e2.schema, schema["anyOf"][1]) + self.assertIs(e2.parent, e) + + self.assertEqual(e2.path, deque([])) + self.assertEqual(e2.relative_path, deque([])) + self.assertEqual(e2.absolute_path, deque([])) + self.assertEqual(e2.json_path, "$") + + self.assertEqual(e2.schema_path, deque([1, "type"])) + self.assertEqual(e2.relative_schema_path, deque([1, "type"])) + self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"])) + + self.assertEqual(len(e2.context), 0) + + def test_type(self): + instance = {"foo": 1} + schema = { + "type": [ + {"type": "integer"}, + { + "type": "object", + "properties": {"foo": {"enum": [2]}}, + }, + ], + } + + validator = validators.Draft3Validator(schema) + errors = list(validator.iter_errors(instance)) + self.assertEqual(len(errors), 1) + e = errors[0] + + self.assertEqual(e.validator, "type") + self.assertEqual(e.validator_value, schema["type"]) + self.assertEqual(e.instance, instance) + self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + self.assertEqual(e.json_path, "$") + + self.assertEqual(e.schema_path, deque(["type"])) + self.assertEqual(e.relative_schema_path, deque(["type"])) + self.assertEqual(e.absolute_schema_path, deque(["type"])) + + self.assertEqual(len(e.context), 2) + + e1, e2 = sorted_errors(e.context) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e1.validator_value, schema["type"][0]["type"]) + self.assertEqual(e1.instance, instance) + self.assertEqual(e1.schema, schema["type"][0]) + self.assertIs(e1.parent, e) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e1.relative_path, deque([])) + self.assertEqual(e1.absolute_path, deque([])) + self.assertEqual(e1.json_path, "$") + + self.assertEqual(e1.schema_path, deque([0, "type"])) + self.assertEqual(e1.relative_schema_path, deque([0, "type"])) + self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"])) + + self.assertFalse(e1.context) + + self.assertEqual(e2.validator, "enum") + self.assertEqual(e2.validator_value, [2]) + self.assertEqual(e2.instance, 1) + self.assertEqual(e2.schema, {"enum": [2]}) + self.assertIs(e2.parent, e) + + self.assertEqual(e2.path, deque(["foo"])) + self.assertEqual(e2.relative_path, deque(["foo"])) + self.assertEqual(e2.absolute_path, deque(["foo"])) + self.assertEqual(e2.json_path, "$.foo") + + self.assertEqual( + e2.schema_path, deque([1, "properties", "foo", "enum"]), + ) + self.assertEqual( + e2.relative_schema_path, deque([1, "properties", "foo", "enum"]), + ) + self.assertEqual( + e2.absolute_schema_path, + deque(["type", 1, "properties", "foo", "enum"]), + ) + + self.assertFalse(e2.context) + + def test_single_nesting(self): + instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"} + schema = { + "properties": { + "foo": {"type": "string"}, + "bar": {"minItems": 2}, + "baz": {"maximum": 10, "enum": [2, 4, 6, 8]}, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2, e3, e4 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["baz"])) + self.assertEqual(e3.path, deque(["baz"])) + self.assertEqual(e4.path, deque(["foo"])) + + self.assertEqual(e1.relative_path, deque(["bar"])) + self.assertEqual(e2.relative_path, deque(["baz"])) + self.assertEqual(e3.relative_path, deque(["baz"])) + self.assertEqual(e4.relative_path, deque(["foo"])) + + self.assertEqual(e1.absolute_path, deque(["bar"])) + self.assertEqual(e2.absolute_path, deque(["baz"])) + self.assertEqual(e3.absolute_path, deque(["baz"])) + self.assertEqual(e4.absolute_path, deque(["foo"])) + + self.assertEqual(e1.json_path, "$.bar") + self.assertEqual(e2.json_path, "$.baz") + self.assertEqual(e3.json_path, "$.baz") + self.assertEqual(e4.json_path, "$.foo") + + self.assertEqual(e1.validator, "minItems") + self.assertEqual(e2.validator, "enum") + self.assertEqual(e3.validator, "maximum") + self.assertEqual(e4.validator, "type") + + def test_multiple_nesting(self): + instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"] + schema = { + "type": "string", + "items": { + "type": ["string", "object"], + "properties": { + "foo": {"enum": [1, 3]}, + "bar": { + "type": "array", + "properties": { + "bar": {"required": True}, + "baz": {"minItems": 2}, + }, + }, + }, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2, e3, e4, e5, e6 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e2.path, deque([0])) + self.assertEqual(e3.path, deque([1, "bar"])) + self.assertEqual(e4.path, deque([1, "bar", "bar"])) + self.assertEqual(e5.path, deque([1, "bar", "baz"])) + self.assertEqual(e6.path, deque([1, "foo"])) + + self.assertEqual(e1.json_path, "$") + self.assertEqual(e2.json_path, "$[0]") + self.assertEqual(e3.json_path, "$[1].bar") + self.assertEqual(e4.json_path, "$[1].bar.bar") + self.assertEqual(e5.json_path, "$[1].bar.baz") + self.assertEqual(e6.json_path, "$[1].foo") + + self.assertEqual(e1.schema_path, deque(["type"])) + self.assertEqual(e2.schema_path, deque(["items", "type"])) + self.assertEqual( + list(e3.schema_path), ["items", "properties", "bar", "type"], + ) + self.assertEqual( + list(e4.schema_path), + ["items", "properties", "bar", "properties", "bar", "required"], + ) + self.assertEqual( + list(e5.schema_path), + ["items", "properties", "bar", "properties", "baz", "minItems"], + ) + self.assertEqual( + list(e6.schema_path), ["items", "properties", "foo", "enum"], + ) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "type") + self.assertEqual(e3.validator, "type") + self.assertEqual(e4.validator, "required") + self.assertEqual(e5.validator, "minItems") + self.assertEqual(e6.validator, "enum") + + def test_recursive(self): + schema = { + "definitions": { + "node": { + "anyOf": [{ + "type": "object", + "required": ["name", "children"], + "properties": { + "name": { + "type": "string", + }, + "children": { + "type": "object", + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/node", + }, + }, + }, + }, + }], + }, + }, + "type": "object", + "required": ["root"], + "properties": {"root": {"$ref": "#/definitions/node"}}, + } + + instance = { + "root": { + "name": "root", + "children": { + "a": { + "name": "a", + "children": { + "ab": { + "name": "ab", + # missing "children" + }, + }, + }, + }, + }, + } + validator = validators.Draft4Validator(schema) + + e, = validator.iter_errors(instance) + self.assertEqual(e.absolute_path, deque(["root"])) + self.assertEqual( + e.absolute_schema_path, deque(["properties", "root", "anyOf"]), + ) + self.assertEqual(e.json_path, "$.root") + + e1, = e.context + self.assertEqual(e1.absolute_path, deque(["root", "children", "a"])) + self.assertEqual( + e1.absolute_schema_path, deque( + [ + "properties", + "root", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + ], + ), + ) + self.assertEqual(e1.json_path, "$.root.children.a") + + e2, = e1.context + self.assertEqual( + e2.absolute_path, deque( + ["root", "children", "a", "children", "ab"], + ), + ) + self.assertEqual( + e2.absolute_schema_path, deque( + [ + "properties", + "root", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + ], + ), + ) + self.assertEqual(e2.json_path, "$.root.children.a.children.ab") + + def test_additionalProperties(self): + instance = {"bar": "bar", "foo": 2} + schema = {"additionalProperties": {"type": "integer", "minimum": 5}} + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["foo"])) + + self.assertEqual(e1.json_path, "$.bar") + self.assertEqual(e2.json_path, "$.foo") + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_patternProperties(self): + instance = {"bar": 1, "foo": 2} + schema = { + "patternProperties": { + "bar": {"type": "string"}, + "foo": {"minimum": 5}, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["foo"])) + + self.assertEqual(e1.json_path, "$.bar") + self.assertEqual(e2.json_path, "$.foo") + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_additionalItems(self): + instance = ["foo", 1] + schema = { + "items": [], + "additionalItems": {"type": "integer", "minimum": 5}, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([0])) + self.assertEqual(e2.path, deque([1])) + + self.assertEqual(e1.json_path, "$[0]") + self.assertEqual(e2.json_path, "$[1]") + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_additionalItems_with_items(self): + instance = ["foo", "bar", 1] + schema = { + "items": [{}], + "additionalItems": {"type": "integer", "minimum": 5}, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([1])) + self.assertEqual(e2.path, deque([2])) + + self.assertEqual(e1.json_path, "$[1]") + self.assertEqual(e2.json_path, "$[2]") + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_propertyNames(self): + instance = {"foo": 12} + schema = {"propertyNames": {"not": {"const": "foo"}}} + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(instance) + + self.assertEqual(error.validator, "not") + self.assertEqual( + error.message, + "'foo' should not be valid under {'const': 'foo'}", + ) + self.assertEqual(error.path, deque([])) + self.assertEqual(error.json_path, "$") + self.assertEqual(error.schema_path, deque(["propertyNames", "not"])) + + def test_if_then(self): + schema = { + "if": {"const": 12}, + "then": {"const": 13}, + } + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(12) + + self.assertEqual(error.validator, "const") + self.assertEqual(error.message, "13 was expected") + self.assertEqual(error.path, deque([])) + self.assertEqual(error.json_path, "$") + self.assertEqual(error.schema_path, deque(["then", "const"])) + + def test_if_else(self): + schema = { + "if": {"const": 12}, + "else": {"const": 13}, + } + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(15) + + self.assertEqual(error.validator, "const") + self.assertEqual(error.message, "13 was expected") + self.assertEqual(error.path, deque([])) + self.assertEqual(error.json_path, "$") + self.assertEqual(error.schema_path, deque(["else", "const"])) + + def test_boolean_schema_False(self): + validator = validators.Draft7Validator(False) + error, = validator.iter_errors(12) + + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.schema, + error.schema_path, + error.json_path, + ), + ( + "False schema does not allow 12", + None, + None, + 12, + False, + deque([]), + "$", + ), + ) + + def test_ref(self): + ref, schema = "someRef", {"additionalProperties": {"type": "integer"}} + validator = validators.Draft7Validator( + {"$ref": ref}, + resolver=validators._RefResolver("", {}, store={ref: schema}), + ) + error, = validator.iter_errors({"foo": "notAnInteger"}) + + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.absolute_path, + error.schema, + error.schema_path, + error.json_path, + ), + ( + "'notAnInteger' is not of type 'integer'", + "type", + "integer", + "notAnInteger", + deque(["foo"]), + {"type": "integer"}, + deque(["additionalProperties", "type"]), + "$.foo", + ), + ) + + def test_prefixItems(self): + schema = {"prefixItems": [{"type": "string"}, {}, {}, {"maximum": 3}]} + validator = validators.Draft202012Validator(schema) + type_error, min_error = validator.iter_errors([1, 2, "foo", 5]) + self.assertEqual( + ( + type_error.message, + type_error.validator, + type_error.validator_value, + type_error.instance, + type_error.absolute_path, + type_error.schema, + type_error.schema_path, + type_error.json_path, + ), + ( + "1 is not of type 'string'", + "type", + "string", + 1, + deque([0]), + {"type": "string"}, + deque(["prefixItems", 0, "type"]), + "$[0]", + ), + ) + self.assertEqual( + ( + min_error.message, + min_error.validator, + min_error.validator_value, + min_error.instance, + min_error.absolute_path, + min_error.schema, + min_error.schema_path, + min_error.json_path, + ), + ( + "5 is greater than the maximum of 3", + "maximum", + 3, + 5, + deque([3]), + {"maximum": 3}, + deque(["prefixItems", 3, "maximum"]), + "$[3]", + ), + ) + + def test_prefixItems_with_items(self): + schema = { + "items": {"type": "string"}, + "prefixItems": [{}], + } + validator = validators.Draft202012Validator(schema) + e1, e2 = validator.iter_errors(["foo", 2, "bar", 4, "baz"]) + self.assertEqual( + ( + e1.message, + e1.validator, + e1.validator_value, + e1.instance, + e1.absolute_path, + e1.schema, + e1.schema_path, + e1.json_path, + ), + ( + "2 is not of type 'string'", + "type", + "string", + 2, + deque([1]), + {"type": "string"}, + deque(["items", "type"]), + "$[1]", + ), + ) + self.assertEqual( + ( + e2.message, + e2.validator, + e2.validator_value, + e2.instance, + e2.absolute_path, + e2.schema, + e2.schema_path, + e2.json_path, + ), + ( + "4 is not of type 'string'", + "type", + "string", + 4, + deque([3]), + {"type": "string"}, + deque(["items", "type"]), + "$[3]", + ), + ) + + def test_contains_too_many(self): + """ + `contains` + `maxContains` produces only one error, even if there are + many more incorrectly matching elements. + """ + schema = {"contains": {"type": "string"}, "maxContains": 2} + validator = validators.Draft202012Validator(schema) + error, = validator.iter_errors(["foo", 2, "bar", 4, "baz", "quux"]) + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.absolute_path, + error.schema, + error.schema_path, + error.json_path, + ), + ( + "Too many items match the given schema (expected at most 2)", + "maxContains", + 2, + ["foo", 2, "bar", 4, "baz", "quux"], + deque([]), + {"contains": {"type": "string"}, "maxContains": 2}, + deque(["contains"]), + "$", + ), + ) + + def test_contains_too_few(self): + schema = {"contains": {"type": "string"}, "minContains": 2} + validator = validators.Draft202012Validator(schema) + error, = validator.iter_errors(["foo", 2, 4]) + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.absolute_path, + error.schema, + error.schema_path, + error.json_path, + ), + ( + ( + "Too few items match the given schema " + "(expected at least 2 but only 1 matched)" + ), + "minContains", + 2, + ["foo", 2, 4], + deque([]), + {"contains": {"type": "string"}, "minContains": 2}, + deque(["contains"]), + "$", + ), + ) + + def test_contains_none(self): + schema = {"contains": {"type": "string"}, "minContains": 2} + validator = validators.Draft202012Validator(schema) + error, = validator.iter_errors([2, 4]) + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.absolute_path, + error.schema, + error.schema_path, + error.json_path, + ), + ( + "[2, 4] does not contain items matching the given schema", + "contains", + {"type": "string"}, + [2, 4], + deque([]), + {"contains": {"type": "string"}, "minContains": 2}, + deque(["contains"]), + "$", + ), + ) + + def test_ref_sibling(self): + schema = { + "$defs": {"foo": {"required": ["bar"]}}, + "properties": { + "aprop": { + "$ref": "#/$defs/foo", + "required": ["baz"], + }, + }, + } + + validator = validators.Draft202012Validator(schema) + e1, e2 = validator.iter_errors({"aprop": {}}) + self.assertEqual( + ( + e1.message, + e1.validator, + e1.validator_value, + e1.instance, + e1.absolute_path, + e1.schema, + e1.schema_path, + e1.relative_schema_path, + e1.json_path, + ), + ( + "'bar' is a required property", + "required", + ["bar"], + {}, + deque(["aprop"]), + {"required": ["bar"]}, + deque(["properties", "aprop", "required"]), + deque(["properties", "aprop", "required"]), + "$.aprop", + ), + ) + self.assertEqual( + ( + e2.message, + e2.validator, + e2.validator_value, + e2.instance, + e2.absolute_path, + e2.schema, + e2.schema_path, + e2.relative_schema_path, + e2.json_path, + ), + ( + "'baz' is a required property", + "required", + ["baz"], + {}, + deque(["aprop"]), + {"$ref": "#/$defs/foo", "required": ["baz"]}, + deque(["properties", "aprop", "required"]), + deque(["properties", "aprop", "required"]), + "$.aprop", + ), + ) + + +class MetaSchemaTestsMixin: + # TODO: These all belong upstream + def test_invalid_properties(self): + with self.assertRaises(exceptions.SchemaError): + self.Validator.check_schema({"properties": 12}) + + def test_minItems_invalid_string(self): + with self.assertRaises(exceptions.SchemaError): + # needs to be an integer + self.Validator.check_schema({"minItems": "1"}) + + def test_enum_allows_empty_arrays(self): + """ + Technically, all the spec says is they SHOULD have elements, not MUST. + + (As of Draft 6. Previous drafts do say MUST). + + See #529. + """ + if self.Validator in { + validators.Draft3Validator, + validators.Draft4Validator, + }: + with self.assertRaises(exceptions.SchemaError): + self.Validator.check_schema({"enum": []}) + else: + self.Validator.check_schema({"enum": []}) + + def test_enum_allows_non_unique_items(self): + """ + Technically, all the spec says is they SHOULD be unique, not MUST. + + (As of Draft 6. Previous drafts do say MUST). + + See #529. + """ + if self.Validator in { + validators.Draft3Validator, + validators.Draft4Validator, + }: + with self.assertRaises(exceptions.SchemaError): + self.Validator.check_schema({"enum": [12, 12]}) + else: + self.Validator.check_schema({"enum": [12, 12]}) + + def test_schema_with_invalid_regex(self): + with self.assertRaises(exceptions.SchemaError): + self.Validator.check_schema({"pattern": "*notaregex"}) + + def test_schema_with_invalid_regex_with_disabled_format_validation(self): + self.Validator.check_schema( + {"pattern": "*notaregex"}, + format_checker=None, + ) + + +class ValidatorTestMixin(MetaSchemaTestsMixin): + def test_it_implements_the_validator_protocol(self): + self.assertIsInstance(self.Validator({}), protocols.Validator) + + def test_valid_instances_are_valid(self): + schema, instance = self.valid + self.assertTrue(self.Validator(schema).is_valid(instance)) + + def test_invalid_instances_are_not_valid(self): + schema, instance = self.invalid + self.assertFalse(self.Validator(schema).is_valid(instance)) + + def test_non_existent_properties_are_ignored(self): + self.Validator({object(): object()}).validate(instance=object()) + + def test_evolve(self): + schema, format_checker = {"type": "integer"}, FormatChecker() + original = self.Validator( + schema, + format_checker=format_checker, + ) + new = original.evolve( + schema={"type": "string"}, + format_checker=self.Validator.FORMAT_CHECKER, + ) + + expected = self.Validator( + {"type": "string"}, + format_checker=self.Validator.FORMAT_CHECKER, + _resolver=new._resolver, + ) + + self.assertEqual(new, expected) + self.assertNotEqual(new, original) + + def test_evolve_with_subclass(self): + """ + Subclassing validators isn't supported public API, but some users have + done it, because we don't actually error entirely when it's done :/ + + We need to deprecate doing so first to help as many of these users + ensure they can move to supported APIs, but this test ensures that in + the interim, we haven't broken those users. + """ + + with self.assertWarns(DeprecationWarning): + @define + class OhNo(self.Validator): + foo = field(factory=lambda: [1, 2, 3]) + _bar = field(default=37) + + validator = OhNo({}, bar=12) + self.assertEqual(validator.foo, [1, 2, 3]) + + new = validator.evolve(schema={"type": "integer"}) + self.assertEqual(new.foo, [1, 2, 3]) + self.assertEqual(new._bar, 12) + + def test_is_type_is_true_for_valid_type(self): + self.assertTrue(self.Validator({}).is_type("foo", "string")) + + def test_is_type_is_false_for_invalid_type(self): + self.assertFalse(self.Validator({}).is_type("foo", "array")) + + def test_is_type_evades_bool_inheriting_from_int(self): + self.assertFalse(self.Validator({}).is_type(True, "integer")) + self.assertFalse(self.Validator({}).is_type(True, "number")) + + def test_it_can_validate_with_decimals(self): + schema = {"items": {"type": "number"}} + Validator = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "number", + lambda checker, thing: isinstance( + thing, (int, float, Decimal), + ) and not isinstance(thing, bool), + ), + ) + + validator = Validator(schema) + validator.validate([1, 1.1, Decimal(1) / Decimal(8)]) + + invalid = ["foo", {}, [], True, None] + self.assertEqual( + [error.instance for error in validator.iter_errors(invalid)], + invalid, + ) + + def test_it_returns_true_for_formats_it_does_not_know_about(self): + validator = self.Validator( + {"format": "carrot"}, format_checker=FormatChecker(), + ) + validator.validate("bugs") + + def test_it_does_not_validate_formats_by_default(self): + validator = self.Validator({}) + self.assertIsNone(validator.format_checker) + + def test_it_validates_formats_if_a_checker_is_provided(self): + checker = FormatChecker() + bad = ValueError("Bad!") + + @checker.checks("foo", raises=ValueError) + def check(value): + if value == "good": + return True + elif value == "bad": + raise bad + else: # pragma: no cover + self.fail(f"What is {value}? [Baby Don't Hurt Me]") + + validator = self.Validator( + {"format": "foo"}, format_checker=checker, + ) + + validator.validate("good") + with self.assertRaises(exceptions.ValidationError) as cm: + validator.validate("bad") + + # Make sure original cause is attached + self.assertIs(cm.exception.cause, bad) + + def test_non_string_custom_type(self): + non_string_type = object() + schema = {"type": [non_string_type]} + Crazy = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + non_string_type, + lambda checker, thing: isinstance(thing, int), + ), + ) + Crazy(schema).validate(15) + + def test_it_properly_formats_tuples_in_errors(self): + """ + A tuple instance properly formats validation errors for uniqueItems. + + See #224 + """ + TupleValidator = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "array", + lambda checker, thing: isinstance(thing, tuple), + ), + ) + with self.assertRaises(exceptions.ValidationError) as e: + TupleValidator({"uniqueItems": True}).validate((1, 1)) + self.assertIn("(1, 1) has non-unique elements", str(e.exception)) + + def test_check_redefined_sequence(self): + """ + Allow array to validate against another defined sequence type + """ + schema = {"type": "array", "uniqueItems": True} + MyMapping = namedtuple("MyMapping", "a, b") + Validator = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine_many( + { + "array": lambda checker, thing: isinstance( + thing, (list, deque), + ), + "object": lambda checker, thing: isinstance( + thing, (dict, MyMapping), + ), + }, + ), + ) + validator = Validator(schema) + + valid_instances = [ + deque(["a", None, "1", "", True]), + deque([[False], [0]]), + [deque([False]), deque([0])], + [[deque([False])], [deque([0])]], + [[[[[deque([False])]]]], [[[[deque([0])]]]]], + [deque([deque([False])]), deque([deque([0])])], + [MyMapping("a", 0), MyMapping("a", False)], + [ + MyMapping("a", [deque([0])]), + MyMapping("a", [deque([False])]), + ], + [ + MyMapping("a", [MyMapping("a", deque([0]))]), + MyMapping("a", [MyMapping("a", deque([False]))]), + ], + [deque(deque(deque([False]))), deque(deque(deque([0])))], + ] + + for instance in valid_instances: + validator.validate(instance) + + invalid_instances = [ + deque(["a", "b", "a"]), + deque([[False], [False]]), + [deque([False]), deque([False])], + [[deque([False])], [deque([False])]], + [[[[[deque([False])]]]], [[[[deque([False])]]]]], + [deque([deque([False])]), deque([deque([False])])], + [MyMapping("a", False), MyMapping("a", False)], + [ + MyMapping("a", [deque([False])]), + MyMapping("a", [deque([False])]), + ], + [ + MyMapping("a", [MyMapping("a", deque([False]))]), + MyMapping("a", [MyMapping("a", deque([False]))]), + ], + [deque(deque(deque([False]))), deque(deque(deque([False])))], + ] + + for instance in invalid_instances: + with self.assertRaises(exceptions.ValidationError): + validator.validate(instance) + + def test_it_creates_a_ref_resolver_if_not_provided(self): + with self.assertWarns(DeprecationWarning): + resolver = self.Validator({}).resolver + self.assertIsInstance(resolver, validators._RefResolver) + + def test_it_upconverts_from_deprecated_RefResolvers(self): + ref, schema = "someCoolRef", {"type": "integer"} + resolver = validators._RefResolver("", {}, store={ref: schema}) + validator = self.Validator({"$ref": ref}, resolver=resolver) + + with self.assertRaises(exceptions.ValidationError): + validator.validate(None) + + def test_it_upconverts_from_yet_older_deprecated_legacy_RefResolvers(self): + """ + Legacy RefResolvers support only the context manager form of + resolution. + """ + + class LegacyRefResolver: + @contextmanager + def resolving(this, ref): + self.assertEqual(ref, "the ref") + yield {"type": "integer"} + + resolver = LegacyRefResolver() + schema = {"$ref": "the ref"} + + with self.assertRaises(exceptions.ValidationError): + self.Validator(schema, resolver=resolver).validate(None) + + +class AntiDraft6LeakMixin: + """ + Make sure functionality from draft 6 doesn't leak backwards in time. + """ + + def test_True_is_not_a_schema(self): + with self.assertRaises(exceptions.SchemaError) as e: + self.Validator.check_schema(True) + self.assertIn("True is not of type", str(e.exception)) + + def test_False_is_not_a_schema(self): + with self.assertRaises(exceptions.SchemaError) as e: + self.Validator.check_schema(False) + self.assertIn("False is not of type", str(e.exception)) + + def test_True_is_not_a_schema_even_if_you_forget_to_check(self): + with self.assertRaises(Exception) as e: + self.Validator(True).validate(12) + self.assertNotIsInstance(e.exception, exceptions.ValidationError) + + def test_False_is_not_a_schema_even_if_you_forget_to_check(self): + with self.assertRaises(Exception) as e: + self.Validator(False).validate(12) + self.assertNotIsInstance(e.exception, exceptions.ValidationError) + + +class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase): + Validator = validators.Draft3Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + def test_any_type_is_valid_for_type_any(self): + validator = self.Validator({"type": "any"}) + validator.validate(object()) + + def test_any_type_is_redefinable(self): + """ + Sigh, because why not. + """ + Crazy = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "any", lambda checker, thing: isinstance(thing, int), + ), + ) + validator = Crazy({"type": "any"}) + validator.validate(12) + with self.assertRaises(exceptions.ValidationError): + validator.validate("foo") + + def test_is_type_is_true_for_any_type(self): + self.assertTrue(self.Validator({"type": "any"}).is_valid(object())) + + def test_is_type_does_not_evade_bool_if_it_is_being_tested(self): + self.assertTrue(self.Validator({}).is_type(True, "boolean")) + self.assertTrue(self.Validator({"type": "any"}).is_valid(True)) + + +class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase): + Validator = validators.Draft4Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + +class TestDraft6Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft6Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + +class TestDraft7Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft7Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + +class TestDraft201909Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft201909Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + +class TestDraft202012Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft202012Validator + valid: tuple[dict, dict] = ({}, {}) + invalid = {"type": "integer"}, "foo" + + +class TestLatestValidator(TestCase): + """ + These really apply to multiple versions but are easiest to test on one. + """ + + def test_ref_resolvers_may_have_boolean_schemas_stored(self): + ref = "someCoolRef" + schema = {"$ref": ref} + resolver = validators._RefResolver("", {}, store={ref: False}) + validator = validators._LATEST_VERSION(schema, resolver=resolver) + + with self.assertRaises(exceptions.ValidationError): + validator.validate(None) + + +class TestValidatorFor(TestCase): + def test_draft_3(self): + schema = {"$schema": "http://json-schema.org/draft-03/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft3Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-03/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft3Validator, + ) + + def test_draft_4(self): + schema = {"$schema": "http://json-schema.org/draft-04/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft4Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-04/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft4Validator, + ) + + def test_draft_6(self): + schema = {"$schema": "http://json-schema.org/draft-06/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft6Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-06/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft6Validator, + ) + + def test_draft_7(self): + schema = {"$schema": "http://json-schema.org/draft-07/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft7Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-07/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft7Validator, + ) + + def test_draft_201909(self): + schema = {"$schema": "https://json-schema.org/draft/2019-09/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft201909Validator, + ) + + schema = {"$schema": "https://json-schema.org/draft/2019-09/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft201909Validator, + ) + + def test_draft_202012(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft202012Validator, + ) + + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft202012Validator, + ) + + def test_True(self): + self.assertIs( + validators.validator_for(True), + validators._LATEST_VERSION, + ) + + def test_False(self): + self.assertIs( + validators.validator_for(False), + validators._LATEST_VERSION, + ) + + def test_custom_validator(self): + Validator = validators.create( + meta_schema={"id": "meta schema id"}, + version="12", + id_of=lambda s: s.get("id", ""), + ) + schema = {"$schema": "meta schema id"} + self.assertIs( + validators.validator_for(schema), + Validator, + ) + + def test_custom_validator_draft6(self): + Validator = validators.create( + meta_schema={"$id": "meta schema $id"}, + version="13", + ) + schema = {"$schema": "meta schema $id"} + self.assertIs( + validators.validator_for(schema), + Validator, + ) + + def test_validator_for_jsonschema_default(self): + self.assertIs(validators.validator_for({}), validators._LATEST_VERSION) + + def test_validator_for_custom_default(self): + self.assertIs(validators.validator_for({}, default=None), None) + + def test_warns_if_meta_schema_specified_was_not_found(self): + with self.assertWarns(DeprecationWarning) as cm: + validators.validator_for(schema={"$schema": "unknownSchema"}) + + self.assertEqual(cm.filename, __file__) + self.assertEqual( + str(cm.warning), + "The metaschema specified by $schema was not found. " + "Using the latest draft to validate, but this will raise " + "an error in the future.", + ) + + def test_does_not_warn_if_meta_schema_is_unspecified(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + validators.validator_for(schema={}, default={}) + self.assertFalse(w) + + def test_validator_for_custom_default_with_schema(self): + schema, default = {"$schema": "mailto:foo@example.com"}, object() + self.assertIs(validators.validator_for(schema, default), default) + + +class TestValidate(TestCase): + def assertUses(self, schema, Validator): + result = [] + with mock.patch.object(Validator, "check_schema", result.append): + validators.validate({}, schema) + self.assertEqual(result, [schema]) + + def test_draft3_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-03/schema#"}, + Validator=validators.Draft3Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-03/schema"}, + Validator=validators.Draft3Validator, + ) + + def test_draft4_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-04/schema#"}, + Validator=validators.Draft4Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-04/schema"}, + Validator=validators.Draft4Validator, + ) + + def test_draft6_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-06/schema#"}, + Validator=validators.Draft6Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-06/schema"}, + Validator=validators.Draft6Validator, + ) + + def test_draft7_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-07/schema#"}, + Validator=validators.Draft7Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-07/schema"}, + Validator=validators.Draft7Validator, + ) + + def test_draft202012_validator_is_chosen(self): + self.assertUses( + schema={ + "$schema": "https://json-schema.org/draft/2020-12/schema#", + }, + Validator=validators.Draft202012Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={ + "$schema": "https://json-schema.org/draft/2020-12/schema", + }, + Validator=validators.Draft202012Validator, + ) + + def test_draft202012_validator_is_the_default(self): + self.assertUses(schema={}, Validator=validators.Draft202012Validator) + + def test_validation_error_message(self): + with self.assertRaises(exceptions.ValidationError) as e: + validators.validate(12, {"type": "string"}) + self.assertRegex( + str(e.exception), + "(?s)Failed validating '.*' in schema.*On instance", + ) + + def test_schema_error_message(self): + with self.assertRaises(exceptions.SchemaError) as e: + validators.validate(12, {"type": 12}) + self.assertRegex( + str(e.exception), + "(?s)Failed validating '.*' in metaschema.*On schema", + ) + + def test_it_uses_best_match(self): + schema = { + "oneOf": [ + {"type": "number", "minimum": 20}, + {"type": "array"}, + ], + } + with self.assertRaises(exceptions.ValidationError) as e: + validators.validate(12, schema) + self.assertIn("12 is less than the minimum of 20", str(e.exception)) + + +class TestThreading(TestCase): + """ + Threading-related functionality tests. + + jsonschema doesn't promise thread safety, and its validation behavior + across multiple threads may change at any time, but that means it isn't + safe to share *validators* across threads, not that anytime one has + multiple threads that jsonschema won't work (it certainly is intended to). + + These tests ensure that this minimal level of functionality continues to + work. + """ + + def test_validation_across_a_second_thread(self): + failed = [] + + def validate(): + try: + validators.validate(instance=37, schema=True) + except: # pragma: no cover # noqa: E722 + failed.append(sys.exc_info()) + + validate() # just verify it succeeds + + from threading import Thread + thread = Thread(target=validate) + thread.start() + thread.join() + self.assertEqual((thread.is_alive(), failed), (False, [])) + + +class TestReferencing(TestCase): + def test_registry_with_retrieve(self): + def retrieve(uri): + return DRAFT202012.create_resource({"type": "integer"}) + + registry = referencing.Registry(retrieve=retrieve) + schema = {"$ref": "https://example.com/"} + validator = validators.Draft202012Validator(schema, registry=registry) + + self.assertEqual( + (validator.is_valid(12), validator.is_valid("foo")), + (True, False), + ) + + def test_custom_registries_do_not_autoretrieve_remote_resources(self): + registry = referencing.Registry() + schema = {"$ref": "https://example.com/"} + validator = validators.Draft202012Validator(schema, registry=registry) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + with self.assertRaises(referencing.exceptions.Unresolvable): + validator.validate(12) + self.assertFalse(w) + + +class TestRefResolver(TestCase): + + base_uri = "" + stored_uri = "foo://stored" + stored_schema = {"stored": "schema"} + + def setUp(self): + self.referrer = {} + self.store = {self.stored_uri: self.stored_schema} + self.resolver = validators._RefResolver( + self.base_uri, self.referrer, self.store, + ) + + def test_it_does_not_retrieve_schema_urls_from_the_network(self): + ref = validators.Draft3Validator.META_SCHEMA["id"] + with mock.patch.object(self.resolver, "resolve_remote") as patched: # noqa: SIM117 + with self.resolver.resolving(ref) as resolved: + pass + self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA) + self.assertFalse(patched.called) + + def test_it_resolves_local_refs(self): + ref = "#/properties/foo" + self.referrer["properties"] = {"foo": object()} + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, self.referrer["properties"]["foo"]) + + def test_it_resolves_local_refs_with_id(self): + schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}} + resolver = validators._RefResolver.from_schema( + schema, + id_of=lambda schema: schema.get("id", ""), + ) + with resolver.resolving("#/a") as resolved: + self.assertEqual(resolved, schema["a"]) + with resolver.resolving("http://bar/schema#/a") as resolved: + self.assertEqual(resolved, schema["a"]) + + def test_it_retrieves_stored_refs(self): + with self.resolver.resolving(self.stored_uri) as resolved: + self.assertIs(resolved, self.stored_schema) + + self.resolver.store["cached_ref"] = {"foo": 12} + with self.resolver.resolving("cached_ref#/foo") as resolved: + self.assertEqual(resolved, 12) + + def test_it_retrieves_unstored_refs_via_requests(self): + ref = "http://bar#baz" + schema = {"baz": 12} + + if "requests" in sys.modules: # pragma: no cover + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema}) + + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, 12) + + def test_it_retrieves_unstored_refs_via_urlopen(self): + ref = "http://bar#baz" + schema = {"baz": 12} + + if "requests" in sys.modules: # pragma: no cover + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = None + + @contextmanager + def fake_urlopen(url): + self.assertEqual(url, "http://bar") + yield BytesIO(json.dumps(schema).encode("utf8")) + + with mock.patch("urllib.request.urlopen", new=fake_urlopen): # noqa: SIM117 + with self.resolver.resolving(ref) as resolved: + pass + self.assertEqual(resolved, 12) + + def test_it_retrieves_local_refs_via_urlopen(self): + with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf: + self.addCleanup(os.remove, tempf.name) + json.dump({"foo": "bar"}, tempf) + + ref = f"file://{pathname2url(tempf.name)}#foo" + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, "bar") + + def test_it_can_construct_a_base_uri_from_a_schema(self): + schema = {"id": "foo"} + resolver = validators._RefResolver.from_schema( + schema, + id_of=lambda schema: schema.get("id", ""), + ) + self.assertEqual(resolver.base_uri, "foo") + self.assertEqual(resolver.resolution_scope, "foo") + with resolver.resolving("") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("#") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("foo") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("foo#") as resolved: + self.assertEqual(resolved, schema) + + def test_it_can_construct_a_base_uri_from_a_schema_without_id(self): + schema = {} + resolver = validators._RefResolver.from_schema(schema) + self.assertEqual(resolver.base_uri, "") + self.assertEqual(resolver.resolution_scope, "") + with resolver.resolving("") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("#") as resolved: + self.assertEqual(resolved, schema) + + def test_custom_uri_scheme_handlers(self): + def handler(url): + self.assertEqual(url, ref) + return schema + + schema = {"foo": "bar"} + ref = "foo://bar" + resolver = validators._RefResolver("", {}, handlers={"foo": handler}) + with resolver.resolving(ref) as resolved: + self.assertEqual(resolved, schema) + + def test_cache_remote_on(self): + response = [object()] + + def handler(url): + try: + return response.pop() + except IndexError: # pragma: no cover + self.fail("Response must not have been cached!") + + ref = "foo://bar" + resolver = validators._RefResolver( + "", {}, cache_remote=True, handlers={"foo": handler}, + ) + with resolver.resolving(ref): + pass + with resolver.resolving(ref): + pass + + def test_cache_remote_off(self): + response = [object()] + + def handler(url): + try: + return response.pop() + except IndexError: # pragma: no cover + self.fail("Handler called twice!") + + ref = "foo://bar" + resolver = validators._RefResolver( + "", {}, cache_remote=False, handlers={"foo": handler}, + ) + with resolver.resolving(ref): + pass + + def test_if_you_give_it_junk_you_get_a_resolution_error(self): + error = ValueError("Oh no! What's this?") + + def handler(url): + raise error + + ref = "foo://bar" + resolver = validators._RefResolver("", {}, handlers={"foo": handler}) + with self.assertRaises(exceptions._RefResolutionError) as err: # noqa: SIM117 + with resolver.resolving(ref): + self.fail("Shouldn't get this far!") # pragma: no cover + self.assertEqual(err.exception, exceptions._RefResolutionError(error)) + + def test_helpful_error_message_on_failed_pop_scope(self): + resolver = validators._RefResolver("", {}) + resolver.pop_scope() + with self.assertRaises(exceptions._RefResolutionError) as exc: + resolver.pop_scope() + self.assertIn("Failed to pop the scope", str(exc.exception)) + + def test_pointer_within_schema_with_different_id(self): + """ + See #1085. + """ + schema = validators.Draft7Validator.META_SCHEMA + one = validators._RefResolver("", schema) + validator = validators.Draft7Validator(schema, resolver=one) + self.assertFalse(validator.is_valid({"maxLength": "foo"})) + + another = { + "allOf": [{"$ref": validators.Draft7Validator.META_SCHEMA["$id"]}], + } + two = validators._RefResolver("", another) + validator = validators.Draft7Validator(another, resolver=two) + self.assertFalse(validator.is_valid({"maxLength": "foo"})) + + def test_newly_created_validator_with_ref_resolver(self): + """ + See https://github.com/python-jsonschema/jsonschema/issues/1061#issuecomment-1624266555. + """ + + def handle(uri): + self.assertEqual(uri, "http://example.com/foo") + return {"type": "integer"} + + resolver = validators._RefResolver("", {}, handlers={"http": handle}) + Validator = validators.create( + meta_schema={}, + validators=validators.Draft4Validator.VALIDATORS, + ) + schema = {"$id": "http://example.com/bar", "$ref": "foo"} + validator = Validator(schema, resolver=resolver) + self.assertEqual( + (validator.is_valid({}), validator.is_valid(37)), + (False, True), + ) + + def test_refresolver_with_pointer_in_schema_with_no_id(self): + """ + See https://github.com/python-jsonschema/jsonschema/issues/1124#issuecomment-1632574249. + """ + + schema = { + "properties": {"x": {"$ref": "#/definitions/x"}}, + "definitions": {"x": {"type": "integer"}}, + } + + validator = validators.Draft202012Validator( + schema, + resolver=validators._RefResolver("", schema), + ) + self.assertEqual( + (validator.is_valid({"x": "y"}), validator.is_valid({"x": 37})), + (False, True), + ) + + + +def sorted_errors(errors): + def key(error): + return ( + [str(e) for e in error.path], + [str(e) for e in error.schema_path], + ) + return sorted(errors, key=key) + + +@define +class ReallyFakeRequests: + + _responses: dict[str, Any] + + def get(self, url): + response = self._responses.get(url) + if url is None: # pragma: no cover + raise ValueError("Unknown URL: " + repr(url)) + return _ReallyFakeJSONResponse(json.dumps(response)) + + +@define +class _ReallyFakeJSONResponse: + + _response: str + + def json(self): + return json.loads(self._response) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py new file mode 100644 index 0000000..63e8bd4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py @@ -0,0 +1,38 @@ +""" +This module acts as a test that type checkers will allow each validator +class to be assigned to a variable of type `type[Validator]` + +The assignation is only valid if type checkers recognize each Validator +implementation as a valid implementer of the protocol. +""" +from jsonschema.protocols import Validator +from jsonschema.validators import ( + Draft3Validator, + Draft4Validator, + Draft6Validator, + Draft7Validator, + Draft201909Validator, + Draft202012Validator, +) + +my_validator: type[Validator] + +my_validator = Draft3Validator +my_validator = Draft4Validator +my_validator = Draft6Validator +my_validator = Draft7Validator +my_validator = Draft201909Validator +my_validator = Draft202012Validator + + +# in order to confirm that none of the above were incorrectly typed as 'Any' +# ensure that each of these assignments to a non-validator variable requires an +# ignore +none_var: None + +none_var = Draft3Validator # type: ignore[assignment] +none_var = Draft4Validator # type: ignore[assignment] +none_var = Draft6Validator # type: ignore[assignment] +none_var = Draft7Validator # type: ignore[assignment] +none_var = Draft201909Validator # type: ignore[assignment] +none_var = Draft202012Validator # type: ignore[assignment] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py new file mode 100644 index 0000000..98ddf6b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py @@ -0,0 +1,1410 @@ +""" +Creation and extension of validators, with implementations for existing drafts. +""" +from __future__ import annotations + +from collections import deque +from collections.abc import Iterable, Mapping, Sequence +from functools import lru_cache +from operator import methodcaller +from typing import TYPE_CHECKING +from urllib.parse import unquote, urldefrag, urljoin, urlsplit +from warnings import warn +import contextlib +import json +import reprlib +import warnings + +from attrs import define, field, fields +from jsonschema_specifications import REGISTRY as SPECIFICATIONS +from rpds import HashTrieMap +import referencing.exceptions +import referencing.jsonschema + +from jsonschema import ( + _format, + _keywords, + _legacy_keywords, + _types, + _typing, + _utils, + exceptions, +) + +if TYPE_CHECKING: + from jsonschema.protocols import Validator + +_UNSET = _utils.Unset() + +_VALIDATORS: dict[str, Validator] = {} +_META_SCHEMAS = _utils.URIDict() + + +def __getattr__(name): + if name == "ErrorTree": + warnings.warn( + "Importing ErrorTree from jsonschema.validators is deprecated. " + "Instead import it from jsonschema.exceptions.", + DeprecationWarning, + stacklevel=2, + ) + from jsonschema.exceptions import ErrorTree + return ErrorTree + elif name == "validators": + warnings.warn( + "Accessing jsonschema.validators.validators is deprecated. " + "Use jsonschema.validators.validator_for with a given schema.", + DeprecationWarning, + stacklevel=2, + ) + return _VALIDATORS + elif name == "meta_schemas": + warnings.warn( + "Accessing jsonschema.validators.meta_schemas is deprecated. " + "Use jsonschema.validators.validator_for with a given schema.", + DeprecationWarning, + stacklevel=2, + ) + return _META_SCHEMAS + elif name == "RefResolver": + warnings.warn( + _RefResolver._DEPRECATION_MESSAGE, + DeprecationWarning, + stacklevel=2, + ) + return _RefResolver + raise AttributeError(f"module {__name__} has no attribute {name}") + + +def validates(version): + """ + Register the decorated validator for a ``version`` of the specification. + + Registered validators and their meta schemas will be considered when + parsing :kw:`$schema` keywords' URIs. + + Arguments: + + version (str): + + An identifier to use as the version's name + + Returns: + + collections.abc.Callable: + + a class decorator to decorate the validator with the version + + """ + + def _validates(cls): + _VALIDATORS[version] = cls + meta_schema_id = cls.ID_OF(cls.META_SCHEMA) + _META_SCHEMAS[meta_schema_id] = cls + return cls + return _validates + + +def _warn_for_remote_retrieve(uri: str): + from urllib.request import Request, urlopen + headers = {"User-Agent": "python-jsonschema (deprecated $ref resolution)"} + request = Request(uri, headers=headers) # noqa: S310 + with urlopen(request) as response: # noqa: S310 + warnings.warn( + "Automatically retrieving remote references can be a security " + "vulnerability and is discouraged by the JSON Schema " + "specifications. Relying on this behavior is deprecated " + "and will shortly become an error. If you are sure you want to " + "remotely retrieve your reference and that it is safe to do so, " + "you can find instructions for doing so via referencing.Registry " + "in the referencing documentation " + "(https://referencing.readthedocs.org).", + DeprecationWarning, + stacklevel=9, # Ha ha ha ha magic numbers :/ + ) + return referencing.Resource.from_contents( + json.load(response), + default_specification=referencing.jsonschema.DRAFT202012, + ) + + +_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( + referencing.Registry(retrieve=_warn_for_remote_retrieve), # type: ignore[call-arg] +) + + +def create( + meta_schema: referencing.jsonschema.ObjectSchema, + validators: ( + Mapping[str, _typing.SchemaKeywordValidator] + | Iterable[tuple[str, _typing.SchemaKeywordValidator]] + ) = (), + version: str | None = None, + type_checker: _types.TypeChecker = _types.draft202012_type_checker, + format_checker: _format.FormatChecker = _format.draft202012_format_checker, + id_of: _typing.id_of = referencing.jsonschema.DRAFT202012.id_of, + applicable_validators: _typing.ApplicableValidators = methodcaller( + "items", + ), +) -> type[Validator]: + """ + Create a new validator class. + + Arguments: + + meta_schema: + + the meta schema for the new validator class + + validators: + + a mapping from names to callables, where each callable will + validate the schema property with the given name. + + Each callable should take 4 arguments: + + 1. a validator instance, + 2. the value of the property being validated within the + instance + 3. the instance + 4. the schema + + version: + + an identifier for the version that this validator class will + validate. If provided, the returned validator class will + have its ``__name__`` set to include the version, and also + will have `jsonschema.validators.validates` automatically + called for the given version. + + type_checker: + + a type checker, used when applying the :kw:`type` keyword. + + If unprovided, a `jsonschema.TypeChecker` will be created + with a set of default types typical of JSON Schema drafts. + + format_checker: + + a format checker, used when applying the :kw:`format` keyword. + + If unprovided, a `jsonschema.FormatChecker` will be created + with a set of default formats typical of JSON Schema drafts. + + id_of: + + A function that given a schema, returns its ID. + + applicable_validators: + + A function that, given a schema, returns the list of + applicable schema keywords and associated values + which will be used to validate the instance. + This is mostly used to support pre-draft 7 versions of JSON Schema + which specified behavior around ignoring keywords if they were + siblings of a ``$ref`` keyword. If you're not attempting to + implement similar behavior, you can typically ignore this argument + and leave it at its default. + + Returns: + + a new `jsonschema.protocols.Validator` class + + """ + # preemptively don't shadow the `Validator.format_checker` local + format_checker_arg = format_checker + + specification = referencing.jsonschema.specification_with( + dialect_id=id_of(meta_schema) or "urn:unknown-dialect", + default=referencing.Specification.OPAQUE, + ) + + @define + class Validator: + + VALIDATORS = dict(validators) # noqa: RUF012 + META_SCHEMA = dict(meta_schema) # noqa: RUF012 + TYPE_CHECKER = type_checker + FORMAT_CHECKER = format_checker_arg + ID_OF = staticmethod(id_of) + + _APPLICABLE_VALIDATORS = applicable_validators + _validators = field(init=False, repr=False, eq=False) + + schema: referencing.jsonschema.Schema = field(repr=reprlib.repr) + _ref_resolver = field(default=None, repr=False, alias="resolver") + format_checker: _format.FormatChecker | None = field(default=None) + # TODO: include new meta-schemas added at runtime + _registry: referencing.jsonschema.SchemaRegistry = field( + default=_REMOTE_WARNING_REGISTRY, + kw_only=True, + repr=False, + ) + _resolver = field( + alias="_resolver", + default=None, + kw_only=True, + repr=False, + ) + + def __init_subclass__(cls): + warnings.warn( + ( + "Subclassing validator classes is not intended to " + "be part of their public API. A future version " + "will make doing so an error, as the behavior of " + "subclasses isn't guaranteed to stay the same " + "between releases of jsonschema. Instead, prefer " + "composition of validators, wrapping them in an object " + "owned entirely by the downstream library." + ), + DeprecationWarning, + stacklevel=2, + ) + + def evolve(self, **changes): + cls = self.__class__ + schema = changes.setdefault("schema", self.schema) + NewValidator = validator_for(schema, default=cls) + + for field in fields(cls): # noqa: F402 + if not field.init: + continue + attr_name = field.name + init_name = field.alias + if init_name not in changes: + changes[init_name] = getattr(self, attr_name) + + return NewValidator(**changes) + + cls.evolve = evolve + + def __attrs_post_init__(self): + if self._resolver is None: + registry = self._registry + if registry is not _REMOTE_WARNING_REGISTRY: + registry = SPECIFICATIONS.combine(registry) + resource = specification.create_resource(self.schema) + self._resolver = registry.resolver_with_root(resource) + + if self.schema is True or self.schema is False: + self._validators = [] + else: + self._validators = [ + (self.VALIDATORS[k], k, v) + for k, v in applicable_validators(self.schema) + if k in self.VALIDATORS + ] + + # REMOVEME: Legacy ref resolution state management. + push_scope = getattr(self._ref_resolver, "push_scope", None) + if push_scope is not None: + id = id_of(self.schema) + if id is not None: + push_scope(id) + + @classmethod + def check_schema(cls, schema, format_checker=_UNSET): + Validator = validator_for(cls.META_SCHEMA, default=cls) + if format_checker is _UNSET: + format_checker = Validator.FORMAT_CHECKER + validator = Validator( + schema=cls.META_SCHEMA, + format_checker=format_checker, + ) + for error in validator.iter_errors(schema): + raise exceptions.SchemaError.create_from(error) + + @property + def resolver(self): + warnings.warn( + ( + f"Accessing {self.__class__.__name__}.resolver is " + "deprecated as of v4.18.0, in favor of the " + "https://github.com/python-jsonschema/referencing " + "library, which provides more compliant referencing " + "behavior as well as more flexible APIs for " + "customization." + ), + DeprecationWarning, + stacklevel=2, + ) + if self._ref_resolver is None: + self._ref_resolver = _RefResolver.from_schema( + self.schema, + id_of=id_of, + ) + return self._ref_resolver + + def evolve(self, **changes): + schema = changes.setdefault("schema", self.schema) + NewValidator = validator_for(schema, default=self.__class__) + + for (attr_name, init_name) in evolve_fields: + if init_name not in changes: + changes[init_name] = getattr(self, attr_name) + + return NewValidator(**changes) + + def iter_errors(self, instance, _schema=None): + if _schema is not None: + warnings.warn( + ( + "Passing a schema to Validator.iter_errors " + "is deprecated and will be removed in a future " + "release. Call validator.evolve(schema=new_schema)." + "iter_errors(...) instead." + ), + DeprecationWarning, + stacklevel=2, + ) + validators = [ + (self.VALIDATORS[k], k, v) + for k, v in applicable_validators(_schema) + if k in self.VALIDATORS + ] + else: + _schema, validators = self.schema, self._validators + + if _schema is True: + return + elif _schema is False: + yield exceptions.ValidationError( + f"False schema does not allow {instance!r}", + validator=None, + validator_value=None, + instance=instance, + schema=_schema, + ) + return + + for validator, k, v in validators: + errors = validator(self, v, instance, _schema) or () + for error in errors: + # set details if not already set by the called fn + error._set( + validator=k, + validator_value=v, + instance=instance, + schema=_schema, + type_checker=self.TYPE_CHECKER, + ) + if k not in {"if", "$ref"}: + error.schema_path.appendleft(k) + yield error + + def descend( + self, + instance, + schema, + path=None, + schema_path=None, + resolver=None, + ): + if schema is True: + return + elif schema is False: + yield exceptions.ValidationError( + f"False schema does not allow {instance!r}", + validator=None, + validator_value=None, + instance=instance, + schema=schema, + ) + return + + if self._ref_resolver is not None: + evolved = self.evolve(schema=schema) + else: + if resolver is None: + resolver = self._resolver.in_subresource( + specification.create_resource(schema), + ) + evolved = self.evolve(schema=schema, _resolver=resolver) + + for k, v in applicable_validators(schema): + validator = evolved.VALIDATORS.get(k) + if validator is None: + continue + + errors = validator(evolved, v, instance, schema) or () + for error in errors: + # set details if not already set by the called fn + error._set( + validator=k, + validator_value=v, + instance=instance, + schema=schema, + type_checker=evolved.TYPE_CHECKER, + ) + if k not in {"if", "$ref"}: + error.schema_path.appendleft(k) + if path is not None: + error.path.appendleft(path) + if schema_path is not None: + error.schema_path.appendleft(schema_path) + yield error + + def validate(self, *args, **kwargs): + for error in self.iter_errors(*args, **kwargs): + raise error + + def is_type(self, instance, type): + try: + return self.TYPE_CHECKER.is_type(instance, type) + except exceptions.UndefinedTypeCheck: + exc = exceptions.UnknownType(type, instance, self.schema) + raise exc from None + + def _validate_reference(self, ref, instance): + if self._ref_resolver is None: + try: + resolved = self._resolver.lookup(ref) + except referencing.exceptions.Unresolvable as err: + raise exceptions._WrappedReferencingError(err) from err + + return self.descend( + instance, + resolved.contents, + resolver=resolved.resolver, + ) + else: + resolve = getattr(self._ref_resolver, "resolve", None) + if resolve is None: + with self._ref_resolver.resolving(ref) as resolved: + return self.descend(instance, resolved) + else: + scope, resolved = resolve(ref) + self._ref_resolver.push_scope(scope) + + try: + return list(self.descend(instance, resolved)) + finally: + self._ref_resolver.pop_scope() + + def is_valid(self, instance, _schema=None): + if _schema is not None: + warnings.warn( + ( + "Passing a schema to Validator.is_valid is deprecated " + "and will be removed in a future release. Call " + "validator.evolve(schema=new_schema).is_valid(...) " + "instead." + ), + DeprecationWarning, + stacklevel=2, + ) + self = self.evolve(schema=_schema) + + error = next(self.iter_errors(instance), None) + return error is None + + evolve_fields = [ + (field.name, field.alias) + for field in fields(Validator) + if field.init + ] + + if version is not None: + safe = version.title().replace(" ", "").replace("-", "") + Validator.__name__ = Validator.__qualname__ = f"{safe}Validator" + Validator = validates(version)(Validator) # type: ignore[misc] + + return Validator # type: ignore[return-value] + + +def extend( + validator, + validators=(), + version=None, + type_checker=None, + format_checker=None, +): + """ + Create a new validator class by extending an existing one. + + Arguments: + + validator (jsonschema.protocols.Validator): + + an existing validator class + + validators (collections.abc.Mapping): + + a mapping of new validator callables to extend with, whose + structure is as in `create`. + + .. note:: + + Any validator callables with the same name as an + existing one will (silently) replace the old validator + callable entirely, effectively overriding any validation + done in the "parent" validator class. + + If you wish to instead extend the behavior of a parent's + validator callable, delegate and call it directly in + the new validator function by retrieving it using + ``OldValidator.VALIDATORS["validation_keyword_name"]``. + + version (str): + + a version for the new validator class + + type_checker (jsonschema.TypeChecker): + + a type checker, used when applying the :kw:`type` keyword. + + If unprovided, the type checker of the extended + `jsonschema.protocols.Validator` will be carried along. + + format_checker (jsonschema.FormatChecker): + + a format checker, used when applying the :kw:`format` keyword. + + If unprovided, the format checker of the extended + `jsonschema.protocols.Validator` will be carried along. + + Returns: + + a new `jsonschema.protocols.Validator` class extending the one + provided + + .. note:: Meta Schemas + + The new validator class will have its parent's meta schema. + + If you wish to change or extend the meta schema in the new + validator class, modify ``META_SCHEMA`` directly on the returned + class. Note that no implicit copying is done, so a copy should + likely be made before modifying it, in order to not affect the + old validator. + + """ + all_validators = dict(validator.VALIDATORS) + all_validators.update(validators) + + if type_checker is None: + type_checker = validator.TYPE_CHECKER + if format_checker is None: + format_checker = validator.FORMAT_CHECKER + return create( + meta_schema=validator.META_SCHEMA, + validators=all_validators, + version=version, + type_checker=type_checker, + format_checker=format_checker, + id_of=validator.ID_OF, + applicable_validators=validator._APPLICABLE_VALIDATORS, + ) + + +Draft3Validator = create( + meta_schema=SPECIFICATIONS.contents( + "http://json-schema.org/draft-03/schema#", + ), + validators={ + "$ref": _keywords.ref, + "additionalItems": _legacy_keywords.additionalItems, + "additionalProperties": _keywords.additionalProperties, + "dependencies": _legacy_keywords.dependencies_draft3, + "disallow": _legacy_keywords.disallow_draft3, + "divisibleBy": _keywords.multipleOf, + "enum": _keywords.enum, + "extends": _legacy_keywords.extends_draft3, + "format": _keywords.format, + "items": _legacy_keywords.items_draft3_draft4, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maximum": _legacy_keywords.maximum_draft3_draft4, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minimum": _legacy_keywords.minimum_draft3_draft4, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "properties": _legacy_keywords.properties_draft3, + "type": _legacy_keywords.type_draft3, + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft3_type_checker, + format_checker=_format.draft3_format_checker, + version="draft3", + id_of=referencing.jsonschema.DRAFT3.id_of, + applicable_validators=_legacy_keywords.ignore_ref_siblings, +) + +Draft4Validator = create( + meta_schema=SPECIFICATIONS.contents( + "http://json-schema.org/draft-04/schema#", + ), + validators={ + "$ref": _keywords.ref, + "additionalItems": _legacy_keywords.additionalItems, + "additionalProperties": _keywords.additionalProperties, + "allOf": _keywords.allOf, + "anyOf": _keywords.anyOf, + "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, + "enum": _keywords.enum, + "format": _keywords.format, + "items": _legacy_keywords.items_draft3_draft4, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maxProperties": _keywords.maxProperties, + "maximum": _legacy_keywords.maximum_draft3_draft4, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minProperties": _keywords.minProperties, + "minimum": _legacy_keywords.minimum_draft3_draft4, + "multipleOf": _keywords.multipleOf, + "not": _keywords.not_, + "oneOf": _keywords.oneOf, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "properties": _keywords.properties, + "required": _keywords.required, + "type": _keywords.type, + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft4_type_checker, + format_checker=_format.draft4_format_checker, + version="draft4", + id_of=referencing.jsonschema.DRAFT4.id_of, + applicable_validators=_legacy_keywords.ignore_ref_siblings, +) + +Draft6Validator = create( + meta_schema=SPECIFICATIONS.contents( + "http://json-schema.org/draft-06/schema#", + ), + validators={ + "$ref": _keywords.ref, + "additionalItems": _legacy_keywords.additionalItems, + "additionalProperties": _keywords.additionalProperties, + "allOf": _keywords.allOf, + "anyOf": _keywords.anyOf, + "const": _keywords.const, + "contains": _legacy_keywords.contains_draft6_draft7, + "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, + "enum": _keywords.enum, + "exclusiveMaximum": _keywords.exclusiveMaximum, + "exclusiveMinimum": _keywords.exclusiveMinimum, + "format": _keywords.format, + "items": _legacy_keywords.items_draft6_draft7_draft201909, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maxProperties": _keywords.maxProperties, + "maximum": _keywords.maximum, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minProperties": _keywords.minProperties, + "minimum": _keywords.minimum, + "multipleOf": _keywords.multipleOf, + "not": _keywords.not_, + "oneOf": _keywords.oneOf, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "properties": _keywords.properties, + "propertyNames": _keywords.propertyNames, + "required": _keywords.required, + "type": _keywords.type, + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft6_type_checker, + format_checker=_format.draft6_format_checker, + version="draft6", + id_of=referencing.jsonschema.DRAFT6.id_of, + applicable_validators=_legacy_keywords.ignore_ref_siblings, +) + +Draft7Validator = create( + meta_schema=SPECIFICATIONS.contents( + "http://json-schema.org/draft-07/schema#", + ), + validators={ + "$ref": _keywords.ref, + "additionalItems": _legacy_keywords.additionalItems, + "additionalProperties": _keywords.additionalProperties, + "allOf": _keywords.allOf, + "anyOf": _keywords.anyOf, + "const": _keywords.const, + "contains": _legacy_keywords.contains_draft6_draft7, + "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, + "enum": _keywords.enum, + "exclusiveMaximum": _keywords.exclusiveMaximum, + "exclusiveMinimum": _keywords.exclusiveMinimum, + "format": _keywords.format, + "if": _keywords.if_, + "items": _legacy_keywords.items_draft6_draft7_draft201909, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maxProperties": _keywords.maxProperties, + "maximum": _keywords.maximum, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minProperties": _keywords.minProperties, + "minimum": _keywords.minimum, + "multipleOf": _keywords.multipleOf, + "not": _keywords.not_, + "oneOf": _keywords.oneOf, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "properties": _keywords.properties, + "propertyNames": _keywords.propertyNames, + "required": _keywords.required, + "type": _keywords.type, + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft7_type_checker, + format_checker=_format.draft7_format_checker, + version="draft7", + id_of=referencing.jsonschema.DRAFT7.id_of, + applicable_validators=_legacy_keywords.ignore_ref_siblings, +) + +Draft201909Validator = create( + meta_schema=SPECIFICATIONS.contents( + "https://json-schema.org/draft/2019-09/schema", + ), + validators={ + "$recursiveRef": _legacy_keywords.recursiveRef, + "$ref": _keywords.ref, + "additionalItems": _legacy_keywords.additionalItems, + "additionalProperties": _keywords.additionalProperties, + "allOf": _keywords.allOf, + "anyOf": _keywords.anyOf, + "const": _keywords.const, + "contains": _keywords.contains, + "dependentRequired": _keywords.dependentRequired, + "dependentSchemas": _keywords.dependentSchemas, + "enum": _keywords.enum, + "exclusiveMaximum": _keywords.exclusiveMaximum, + "exclusiveMinimum": _keywords.exclusiveMinimum, + "format": _keywords.format, + "if": _keywords.if_, + "items": _legacy_keywords.items_draft6_draft7_draft201909, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maxProperties": _keywords.maxProperties, + "maximum": _keywords.maximum, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minProperties": _keywords.minProperties, + "minimum": _keywords.minimum, + "multipleOf": _keywords.multipleOf, + "not": _keywords.not_, + "oneOf": _keywords.oneOf, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "properties": _keywords.properties, + "propertyNames": _keywords.propertyNames, + "required": _keywords.required, + "type": _keywords.type, + "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, + "unevaluatedProperties": ( + _legacy_keywords.unevaluatedProperties_draft2019 + ), + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft201909_type_checker, + format_checker=_format.draft201909_format_checker, + version="draft2019-09", +) + +Draft202012Validator = create( + meta_schema=SPECIFICATIONS.contents( + "https://json-schema.org/draft/2020-12/schema", + ), + validators={ + "$dynamicRef": _keywords.dynamicRef, + "$ref": _keywords.ref, + "additionalProperties": _keywords.additionalProperties, + "allOf": _keywords.allOf, + "anyOf": _keywords.anyOf, + "const": _keywords.const, + "contains": _keywords.contains, + "dependentRequired": _keywords.dependentRequired, + "dependentSchemas": _keywords.dependentSchemas, + "enum": _keywords.enum, + "exclusiveMaximum": _keywords.exclusiveMaximum, + "exclusiveMinimum": _keywords.exclusiveMinimum, + "format": _keywords.format, + "if": _keywords.if_, + "items": _keywords.items, + "maxItems": _keywords.maxItems, + "maxLength": _keywords.maxLength, + "maxProperties": _keywords.maxProperties, + "maximum": _keywords.maximum, + "minItems": _keywords.minItems, + "minLength": _keywords.minLength, + "minProperties": _keywords.minProperties, + "minimum": _keywords.minimum, + "multipleOf": _keywords.multipleOf, + "not": _keywords.not_, + "oneOf": _keywords.oneOf, + "pattern": _keywords.pattern, + "patternProperties": _keywords.patternProperties, + "prefixItems": _keywords.prefixItems, + "properties": _keywords.properties, + "propertyNames": _keywords.propertyNames, + "required": _keywords.required, + "type": _keywords.type, + "unevaluatedItems": _keywords.unevaluatedItems, + "unevaluatedProperties": _keywords.unevaluatedProperties, + "uniqueItems": _keywords.uniqueItems, + }, + type_checker=_types.draft202012_type_checker, + format_checker=_format.draft202012_format_checker, + version="draft2020-12", +) + +_LATEST_VERSION: type[Validator] = Draft202012Validator + + +class _RefResolver: + """ + Resolve JSON References. + + Arguments: + + base_uri (str): + + The URI of the referring document + + referrer: + + The actual referring document + + store (dict): + + A mapping from URIs to documents to cache + + cache_remote (bool): + + Whether remote refs should be cached after first resolution + + handlers (dict): + + A mapping from URI schemes to functions that should be used + to retrieve them + + urljoin_cache (:func:`functools.lru_cache`): + + A cache that will be used for caching the results of joining + the resolution scope to subscopes. + + remote_cache (:func:`functools.lru_cache`): + + A cache that will be used for caching the results of + resolved remote URLs. + + Attributes: + + cache_remote (bool): + + Whether remote refs should be cached after first resolution + + .. deprecated:: v4.18.0 + + ``RefResolver`` has been deprecated in favor of `referencing`. + + """ + + _DEPRECATION_MESSAGE = ( + "jsonschema.RefResolver is deprecated as of v4.18.0, in favor of the " + "https://github.com/python-jsonschema/referencing library, which " + "provides more compliant referencing behavior as well as more " + "flexible APIs for customization. A future release will remove " + "RefResolver. Please file a feature request (on referencing) if you " + "are missing an API for the kind of customization you need." + ) + + def __init__( + self, + base_uri, + referrer, + store=HashTrieMap(), + cache_remote=True, + handlers=(), + urljoin_cache=None, + remote_cache=None, + ): + if urljoin_cache is None: + urljoin_cache = lru_cache(1024)(urljoin) + if remote_cache is None: + remote_cache = lru_cache(1024)(self.resolve_from_url) + + self.referrer = referrer + self.cache_remote = cache_remote + self.handlers = dict(handlers) + + self._scopes_stack = [base_uri] + + self.store = _utils.URIDict( + (uri, each.contents) for uri, each in SPECIFICATIONS.items() + ) + self.store.update( + (id, each.META_SCHEMA) for id, each in _META_SCHEMAS.items() + ) + self.store.update(store) + self.store.update( + (schema["$id"], schema) + for schema in store.values() + if isinstance(schema, Mapping) and "$id" in schema + ) + self.store[base_uri] = referrer + + self._urljoin_cache = urljoin_cache + self._remote_cache = remote_cache + + @classmethod + def from_schema( # noqa: D417 + cls, + schema, + id_of=referencing.jsonschema.DRAFT202012.id_of, + *args, + **kwargs, + ): + """ + Construct a resolver from a JSON schema object. + + Arguments: + + schema: + + the referring schema + + Returns: + + `_RefResolver` + + """ + return cls(base_uri=id_of(schema) or "", referrer=schema, *args, **kwargs) # noqa: B026, E501 + + def push_scope(self, scope): + """ + Enter a given sub-scope. + + Treats further dereferences as being performed underneath the + given scope. + """ + self._scopes_stack.append( + self._urljoin_cache(self.resolution_scope, scope), + ) + + def pop_scope(self): + """ + Exit the most recent entered scope. + + Treats further dereferences as being performed underneath the + original scope. + + Don't call this method more times than `push_scope` has been + called. + """ + try: + self._scopes_stack.pop() + except IndexError: + raise exceptions._RefResolutionError( + "Failed to pop the scope from an empty stack. " + "`pop_scope()` should only be called once for every " + "`push_scope()`", + ) from None + + @property + def resolution_scope(self): + """ + Retrieve the current resolution scope. + """ + return self._scopes_stack[-1] + + @property + def base_uri(self): + """ + Retrieve the current base URI, not including any fragment. + """ + uri, _ = urldefrag(self.resolution_scope) + return uri + + @contextlib.contextmanager + def in_scope(self, scope): + """ + Temporarily enter the given scope for the duration of the context. + + .. deprecated:: v4.0.0 + """ + warnings.warn( + "jsonschema.RefResolver.in_scope is deprecated and will be " + "removed in a future release.", + DeprecationWarning, + stacklevel=3, + ) + self.push_scope(scope) + try: + yield + finally: + self.pop_scope() + + @contextlib.contextmanager + def resolving(self, ref): + """ + Resolve the given ``ref`` and enter its resolution scope. + + Exits the scope on exit of this context manager. + + Arguments: + + ref (str): + + The reference to resolve + + """ + url, resolved = self.resolve(ref) + self.push_scope(url) + try: + yield resolved + finally: + self.pop_scope() + + def _find_in_referrer(self, key): + return self._get_subschemas_cache()[key] + + @lru_cache # noqa: B019 + def _get_subschemas_cache(self): + cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS} + for keyword, subschema in _search_schema( + self.referrer, _match_subschema_keywords, + ): + cache[keyword].append(subschema) + return cache + + @lru_cache # noqa: B019 + def _find_in_subschemas(self, url): + subschemas = self._get_subschemas_cache()["$id"] + if not subschemas: + return None + uri, fragment = urldefrag(url) + for subschema in subschemas: + id = subschema["$id"] + if not isinstance(id, str): + continue + target_uri = self._urljoin_cache(self.resolution_scope, id) + if target_uri.rstrip("/") == uri.rstrip("/"): + if fragment: + subschema = self.resolve_fragment(subschema, fragment) + self.store[url] = subschema + return url, subschema + return None + + def resolve(self, ref): + """ + Resolve the given reference. + """ + url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/") + + match = self._find_in_subschemas(url) + if match is not None: + return match + + return url, self._remote_cache(url) + + def resolve_from_url(self, url): + """ + Resolve the given URL. + """ + url, fragment = urldefrag(url) + if not url: + url = self.base_uri + + try: + document = self.store[url] + except KeyError: + try: + document = self.resolve_remote(url) + except Exception as exc: + raise exceptions._RefResolutionError(exc) from exc + + return self.resolve_fragment(document, fragment) + + def resolve_fragment(self, document, fragment): + """ + Resolve a ``fragment`` within the referenced ``document``. + + Arguments: + + document: + + The referent document + + fragment (str): + + a URI fragment to resolve within it + + """ + fragment = fragment.lstrip("/") + + if not fragment: + return document + + if document is self.referrer: + find = self._find_in_referrer + else: + + def find(key): + yield from _search_schema(document, _match_keyword(key)) + + for keyword in ["$anchor", "$dynamicAnchor"]: + for subschema in find(keyword): + if fragment == subschema[keyword]: + return subschema + for keyword in ["id", "$id"]: + for subschema in find(keyword): + if "#" + fragment == subschema[keyword]: + return subschema + + # Resolve via path + parts = unquote(fragment).split("/") if fragment else [] + for part in parts: + part = part.replace("~1", "/").replace("~0", "~") + + if isinstance(document, Sequence): + try: # noqa: SIM105 + part = int(part) + except ValueError: + pass + try: + document = document[part] + except (TypeError, LookupError) as err: + raise exceptions._RefResolutionError( + f"Unresolvable JSON pointer: {fragment!r}", + ) from err + + return document + + def resolve_remote(self, uri): + """ + Resolve a remote ``uri``. + + If called directly, does not check the store first, but after + retrieving the document at the specified URI it will be saved in + the store if :attr:`cache_remote` is True. + + .. note:: + + If the requests_ library is present, ``jsonschema`` will use it to + request the remote ``uri``, so that the correct encoding is + detected and used. + + If it isn't, or if the scheme of the ``uri`` is not ``http`` or + ``https``, UTF-8 is assumed. + + Arguments: + + uri (str): + + The URI to resolve + + Returns: + + The retrieved document + + .. _requests: https://pypi.org/project/requests/ + + """ + try: + import requests + except ImportError: + requests = None + + scheme = urlsplit(uri).scheme + + if scheme in self.handlers: + result = self.handlers[scheme](uri) + elif scheme in ["http", "https"] and requests: + # Requests has support for detecting the correct encoding of + # json over http + result = requests.get(uri).json() + else: + # Otherwise, pass off to urllib and assume utf-8 + from urllib.request import urlopen + with urlopen(uri) as url: # noqa: S310 + result = json.loads(url.read().decode("utf-8")) + + if self.cache_remote: + self.store[uri] = result + return result + + +_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor") + + +def _match_keyword(keyword): + + def matcher(value): + if keyword in value: + yield value + + return matcher + + +def _match_subschema_keywords(value): + for keyword in _SUBSCHEMAS_KEYWORDS: + if keyword in value: + yield keyword, value + + +def _search_schema(schema, matcher): + """Breadth-first search routine.""" + values = deque([schema]) + while values: + value = values.pop() + if not isinstance(value, dict): + continue + yield from matcher(value) + values.extendleft(value.values()) + + +def validate(instance, schema, cls=None, *args, **kwargs): # noqa: D417 + """ + Validate an instance under the given schema. + + >>> validate([2, 3, 4], {"maxItems": 2}) + Traceback (most recent call last): + ... + ValidationError: [2, 3, 4] is too long + + :func:`~jsonschema.validators.validate` will first verify that the + provided schema is itself valid, since not doing so can lead to less + obvious error messages and fail in less obvious or consistent ways. + + If you know you have a valid schema already, especially + if you intend to validate multiple instances with + the same schema, you likely would prefer using the + `jsonschema.protocols.Validator.validate` method directly on a + specific validator (e.g. ``Draft202012Validator.validate``). + + + Arguments: + + instance: + + The instance to validate + + schema: + + The schema to validate with + + cls (jsonschema.protocols.Validator): + + The class that will be used to validate the instance. + + If the ``cls`` argument is not provided, two things will happen + in accordance with the specification. First, if the schema has a + :kw:`$schema` keyword containing a known meta-schema [#]_ then the + proper validator will be used. The specification recommends that + all schemas contain :kw:`$schema` properties for this reason. If no + :kw:`$schema` property is found, the default validator class is the + latest released draft. + + Any other provided positional and keyword arguments will be passed + on when instantiating the ``cls``. + + Raises: + + `jsonschema.exceptions.ValidationError`: + + if the instance is invalid + + `jsonschema.exceptions.SchemaError`: + + if the schema itself is invalid + + .. rubric:: Footnotes + .. [#] known by a validator registered with + `jsonschema.validators.validates` + + """ + if cls is None: + cls = validator_for(schema) + + cls.check_schema(schema) + validator = cls(schema, *args, **kwargs) + error = exceptions.best_match(validator.iter_errors(instance)) + if error is not None: + raise error + + +def validator_for( + schema, + default: type[Validator] | _utils.Unset = _UNSET, +) -> type[Validator]: + """ + Retrieve the validator class appropriate for validating the given schema. + + Uses the :kw:`$schema` keyword that should be present in the given + schema to look up the appropriate validator class. + + Arguments: + + schema (collections.abc.Mapping or bool): + + the schema to look at + + default: + + the default to return if the appropriate validator class + cannot be determined. + + If unprovided, the default is to return the latest supported + draft. + + Examples: + + The :kw:`$schema` JSON Schema keyword will control which validator + class is returned: + + >>> schema = { + ... "$schema": "https://json-schema.org/draft/2020-12/schema", + ... "type": "integer", + ... } + >>> jsonschema.validators.validator_for(schema) + + + + Here, a draft 7 schema instead will return the draft 7 validator: + + >>> schema = { + ... "$schema": "http://json-schema.org/draft-07/schema#", + ... "type": "integer", + ... } + >>> jsonschema.validators.validator_for(schema) + + + + Schemas with no ``$schema`` keyword will fallback to the default + argument: + + >>> schema = {"type": "integer"} + >>> jsonschema.validators.validator_for( + ... schema, default=Draft7Validator, + ... ) + + + or if none is provided, to the latest version supported. + Always including the keyword when authoring schemas is highly + recommended. + + """ + DefaultValidator = _LATEST_VERSION if default is _UNSET else default + + if schema is True or schema is False or "$schema" not in schema: + return DefaultValidator # type: ignore[return-value] + if schema["$schema"] not in _META_SCHEMAS and default is _UNSET: + warn( + ( + "The metaschema specified by $schema was not found. " + "Using the latest draft to validate, but this will raise " + "an error in the future." + ), + DeprecationWarning, + stacklevel=2, + ) + return _META_SCHEMAS.get(schema["$schema"], DefaultValidator) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA new file mode 100644 index 0000000..ff5daef --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA @@ -0,0 +1,54 @@ +Metadata-Version: 2.4 +Name: jsonschema-specifications +Version: 2025.9.1 +Summary: The JSON Schema meta-schemas and vocabularies, exposed as a Registry +Project-URL: Documentation, https://jsonschema-specifications.readthedocs.io/ +Project-URL: Homepage, https://github.com/python-jsonschema/jsonschema-specifications +Project-URL: Issues, https://github.com/python-jsonschema/jsonschema-specifications/issues/ +Project-URL: Funding, https://github.com/sponsors/Julian +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-jsonschema-specifications?utm_source=pypi-jsonschema-specifications&utm_medium=referral&utm_campaign=pypi-link +Project-URL: Source, https://github.com/python-jsonschema/jsonschema-specifications +Author-email: Julian Berman +License-Expression: MIT +License-File: COPYING +Keywords: data validation,json,json schema,jsonschema,validation +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: File Formats :: JSON +Classifier: Topic :: File Formats :: JSON :: JSON Schema +Requires-Python: >=3.9 +Requires-Dist: referencing>=0.31.0 +Description-Content-Type: text/x-rst + +============================= +``jsonschema-specifications`` +============================= + +|PyPI| |Pythons| |CI| |ReadTheDocs| + +JSON support files from the `JSON Schema Specifications `_ (metaschemas, vocabularies, etc.), packaged for runtime access from Python as a `referencing-based Schema Registry `_. + +.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema-specifications.svg + :alt: PyPI version + :target: https://pypi.org/project/jsonschema-specifications/ + +.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema-specifications.svg + :alt: Supported Python versions + :target: https://pypi.org/project/jsonschema-specifications/ + +.. |CI| image:: https://github.com/python-jsonschema/jsonschema-specifications/workflows/CI/badge.svg + :alt: Build status + :target: https://github.com/python-jsonschema/jsonschema-specifications/actions?query=workflow%3ACI + +.. |ReadTheDocs| image:: https://readthedocs.org/projects/jsonschema-specifications/badge/?version=stable&style=flat + :alt: ReadTheDocs status + :target: https://jsonschema-specifications.readthedocs.io/en/stable/ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD new file mode 100644 index 0000000..11399af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD @@ -0,0 +1,28 @@ +jsonschema_specifications/__init__.py,sha256=qoTB2DKY7qvNrGhMPH6gtmAJRLilmVQ-fFZwT6ryqw0,386 +jsonschema_specifications/_core.py,sha256=tFhc1CMleJ3AJOK_bjxOpFQTdrsUClFGfFxPBU_CebM,1140 +jsonschema_specifications/schemas/draft201909/metaschema.json,sha256=e3YbPhIfCgyh6ioLjizIVrz4AWBLgmjXG6yqICvAwTs,1785 +jsonschema_specifications/schemas/draft201909/vocabularies/applicator,sha256=aJUQDplyb7sQcFhRK77D7P1LJOj9L6zuPlBe5ysNTDE,1860 +jsonschema_specifications/schemas/draft201909/vocabularies/content,sha256=m31PVaTi_bAsQwBo_f-rxzKt3OI42j8d8mkCScM1MnQ,517 +jsonschema_specifications/schemas/draft201909/vocabularies/core,sha256=taLElX9kldClCB8ECevooU5BOayyA_x0hHH47eKvWyw,1531 +jsonschema_specifications/schemas/draft201909/vocabularies/format,sha256=UOu_55BhGoSbjMQAoJwdDg-2q1wNQ6DyIgH9NiUFa_Q,403 +jsonschema_specifications/schemas/draft201909/vocabularies/meta-data,sha256=1H4kRd1qgicaKY2DzGxsuNSuHhXg3Fa-zTehY-zwEoY,892 +jsonschema_specifications/schemas/draft201909/vocabularies/validation,sha256=HlJsHTNac0gF_ILPV5jBK5YK19olF8Zs2lobCTWcPBw,2834 +jsonschema_specifications/schemas/draft202012/metaschema.json,sha256=Qdp29a-3zgYtJI92JGOpL3ykfk4PkFsiS6av7vkd7Q8,2452 +jsonschema_specifications/schemas/draft202012/vocabularies/applicator,sha256=xKbkFHuR_vf-ptwFjLG_k0AvdBS3ZXiosWqvHa1qrO8,1659 +jsonschema_specifications/schemas/draft202012/vocabularies/content,sha256=CDQ3R3ZOSlgUJieTz01lIFenkThjxZUNQyl-jh_axbY,519 +jsonschema_specifications/schemas/draft202012/vocabularies/core,sha256=wtEqjk3RHTNt_IOj9mOqTGnwtJs76wlP_rJbUxb0gD0,1564 +jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation,sha256=q8d1rf79idIjWBcNm_k_Tr0jSVY7u-3WDwK-98gSvMA,448 +jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion,sha256=xSJCuaG7eGsmw-gset1CjDH5yW5XXc6Z5W6l_qptogw,445 +jsonschema_specifications/schemas/draft202012/vocabularies/meta-data,sha256=j3bW4U9Bubku-TO3CM3FFEyLUmhlGtEZGEhfsXVPHHY,892 +jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated,sha256=Lb-8tzmUtnCwl2SSre4f_7RsIWgnhNL1pMpWH54tDLQ,506 +jsonschema_specifications/schemas/draft202012/vocabularies/validation,sha256=cBCjHlQfMtK-ch4t40jfdcmzaHaj7TBId_wKvaHTelg,2834 +jsonschema_specifications/schemas/draft3/metaschema.json,sha256=LPdfZENvtb43Si6qJ6uLfh_WUcm0ba6mxnsC_WTiRYs,2600 +jsonschema_specifications/schemas/draft4/metaschema.json,sha256=4UidC0dV8CeTMCWR0_y48Htok6gqlPJIlfjk7fEbguI,4357 +jsonschema_specifications/schemas/draft6/metaschema.json,sha256=wp386fVINcOgbAOzxdXsDtp3cGVo-cTffPvHVmpRAG0,4437 +jsonschema_specifications/schemas/draft7/metaschema.json,sha256=PVOSCIJhYGxVm2A_OFMpyfGrRbXWZ-uZBodFOwVdQF4,4819 +jsonschema_specifications/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jsonschema_specifications/tests/test_jsonschema_specifications.py,sha256=WkbYRW6A6FoZ0rivShfqVLSCsAiHJ2x8TxqECJTXPTY,1106 +jsonschema_specifications-2025.9.1.dist-info/METADATA,sha256=NavUF1fzK06iR1aSDe1HtwFz13y8BSpabTq1g7Lo2J0,2907 +jsonschema_specifications-2025.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING,sha256=QtzWNJX4e063x3V6-jebtVpT-Ur9el9lfZrfVyNuUVw,1057 +jsonschema_specifications-2025.9.1.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING new file mode 100644 index 0000000..a9f853e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2022 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py new file mode 100644 index 0000000..a423574 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py @@ -0,0 +1,12 @@ +""" +The JSON Schema meta-schemas and vocabularies, exposed as a Registry. +""" + +from referencing.jsonschema import EMPTY_REGISTRY as _EMPTY_REGISTRY + +from jsonschema_specifications._core import _schemas + +#: A `referencing.jsonschema.SchemaRegistry` containing all of the official +#: meta-schemas and vocabularies. +REGISTRY = (_schemas() @ _EMPTY_REGISTRY).crawl() +__all__ = ["REGISTRY"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py new file mode 100644 index 0000000..e67bd71 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py @@ -0,0 +1,38 @@ +""" +Load all the JSON Schema specification's official schemas. +""" + +import json + +try: + from importlib.resources import files +except ImportError: + from importlib_resources import ( # type: ignore[import-not-found, no-redef] + files, + ) + +from referencing import Resource + + +def _schemas(): + """ + All schemas we ship. + """ + # importlib.resources.abc.Traversal doesn't have nice ways to do this that + # I'm aware of... + # + # It can't recurse arbitrarily, e.g. no ``.glob()``. + # + # So this takes some liberties given the real layout of what we ship + # (only 2 levels of nesting, no directories within the second level). + + for version in files(__package__).joinpath("schemas").iterdir(): + if version.name.startswith("."): + continue + for child in version.iterdir(): + children = [child] if child.is_file() else child.iterdir() + for path in children: + if path.name.startswith("."): + continue + contents = json.loads(path.read_text(encoding="utf-8")) + yield Resource.from_contents(contents) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json new file mode 100644 index 0000000..2248a0c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true, + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/meta-data": true, + "https://json-schema.org/draft/2019-09/vocab/format": false, + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "properties": { + "definitions": { + "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + } + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator new file mode 100644 index 0000000..24a1cc4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/applicator": true + }, + "$recursiveAnchor": true, + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "additionalItems": { "$recursiveRef": "#" }, + "unevaluatedItems": { "$recursiveRef": "#" }, + "items": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "#/$defs/schemaArray" } + ] + }, + "contains": { "$recursiveRef": "#" }, + "additionalProperties": { "$recursiveRef": "#" }, + "unevaluatedProperties": { "$recursiveRef": "#" }, + "properties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { + "$recursiveRef": "#" + } + }, + "propertyNames": { "$recursiveRef": "#" }, + "if": { "$recursiveRef": "#" }, + "then": { "$recursiveRef": "#" }, + "else": { "$recursiveRef": "#" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$recursiveRef": "#" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$recursiveRef": "#" } + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content new file mode 100644 index 0000000..f6752a8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "contentSchema": { "$recursiveRef": "#" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core new file mode 100644 index 0000000..eb708a5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true + }, + "$recursiveAnchor": true, + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$anchor": { + "type": "string", + "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveRef": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveAnchor": { + "type": "boolean", + "default": false + }, + "$vocabulary": { + "type": "object", + "propertyNames": { + "type": "string", + "format": "uri" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format new file mode 100644 index 0000000..09bbfdd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/format", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/format": true + }, + "$recursiveAnchor": true, + + "title": "Format vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data new file mode 100644 index 0000000..da04cff --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/meta-data": true + }, + "$recursiveAnchor": true, + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation new file mode 100644 index 0000000..9f59677 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/validation": true + }, + "$recursiveAnchor": true, + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json new file mode 100644 index 0000000..d5e2d31 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/unevaluated"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format-annotation"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator new file mode 100644 index 0000000..ca69923 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/applicator": true + }, + "$dynamicAnchor": "meta", + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "prefixItems": { "$ref": "#/$defs/schemaArray" }, + "items": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "properties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$dynamicRef": "#meta" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$dynamicRef": "#meta" } + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content new file mode 100644 index 0000000..2f6e056 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$dynamicRef": "#meta" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core new file mode 100644 index 0000000..dfc092d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "$dynamicAnchor": "meta", + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "$ref": "#/$defs/uriReferenceString", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { "$ref": "#/$defs/uriString" }, + "$ref": { "$ref": "#/$defs/uriReferenceString" }, + "$anchor": { "$ref": "#/$defs/anchorString" }, + "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, + "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, + "$vocabulary": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/uriString" }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" } + } + }, + "$defs": { + "anchorString": { + "type": "string", + "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" + }, + "uriString": { + "type": "string", + "format": "uri" + }, + "uriReferenceString": { + "type": "string", + "format": "uri-reference" + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation new file mode 100644 index 0000000..51ef7ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for annotation results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion new file mode 100644 index 0000000..5e73fd7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-assertion": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for assertion results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data new file mode 100644 index 0000000..05cbc22 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/meta-data": true + }, + "$dynamicAnchor": "meta", + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated new file mode 100644 index 0000000..5f62a3f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true + }, + "$dynamicAnchor": "meta", + + "title": "Unevaluated applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation new file mode 100644 index 0000000..606b87b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/validation": true + }, + "$dynamicAnchor": "meta", + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json new file mode 100644 index 0000000..8b26b1f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json @@ -0,0 +1,172 @@ +{ + "$schema" : "http://json-schema.org/draft-03/schema#", + "id" : "http://json-schema.org/draft-03/schema#", + "type" : "object", + + "properties" : { + "type" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "uniqueItems" : true, + "default" : "any" + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {} + }, + + "patternProperties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {} + }, + + "additionalProperties" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {} + }, + + "items" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + + "additionalItems" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {} + }, + + "required" : { + "type" : "boolean", + "default" : false + }, + + "dependencies" : { + "type" : "object", + "additionalProperties" : { + "type" : ["string", "array", {"$ref" : "#"}], + "items" : { + "type" : "string" + } + }, + "default" : {} + }, + + "minimum" : { + "type" : "number" + }, + + "maximum" : { + "type" : "number" + }, + + "exclusiveMinimum" : { + "type" : "boolean", + "default" : false + }, + + "exclusiveMaximum" : { + "type" : "boolean", + "default" : false + }, + + "minItems" : { + "type" : "integer", + "minimum" : 0, + "default" : 0 + }, + + "maxItems" : { + "type" : "integer", + "minimum" : 0 + }, + + "uniqueItems" : { + "type" : "boolean", + "default" : false + }, + + "pattern" : { + "type" : "string", + "format" : "regex" + }, + + "minLength" : { + "type" : "integer", + "minimum" : 0, + "default" : 0 + }, + + "maxLength" : { + "type" : "integer" + }, + + "enum" : { + "type" : "array", + "minItems" : 1, + "uniqueItems" : true + }, + + "default" : { + "type" : "any" + }, + + "title" : { + "type" : "string" + }, + + "description" : { + "type" : "string" + }, + + "format" : { + "type" : "string" + }, + + "divisibleBy" : { + "type" : "number", + "minimum" : 0, + "exclusiveMinimum" : true, + "default" : 1 + }, + + "disallow" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "uniqueItems" : true + }, + + "extends" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + + "id" : { + "type" : "string" + }, + + "$ref" : { + "type" : "string" + }, + + "$schema" : { + "type" : "string", + "format" : "uri" + } + }, + + "dependencies" : { + "exclusiveMinimum" : "minimum", + "exclusiveMaximum" : "maximum" + }, + + "default" : {} +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json new file mode 100644 index 0000000..bcbb847 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json @@ -0,0 +1,149 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "$schema": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json new file mode 100644 index 0000000..a0d2bf7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json @@ -0,0 +1,153 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "examples": { + "type": "array", + "items": {} + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array" + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json new file mode 100644 index 0000000..746cde9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json @@ -0,0 +1,166 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": {"$ref": "#"}, + "then": {"$ref": "#"}, + "else": {"$ref": "#"}, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py new file mode 100644 index 0000000..fd2927e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py @@ -0,0 +1,41 @@ +from collections.abc import Mapping +from pathlib import Path + +import pytest + +from jsonschema_specifications import REGISTRY + + +def test_it_contains_metaschemas(): + schema = REGISTRY.contents("http://json-schema.org/draft-07/schema#") + assert isinstance(schema, Mapping) + assert schema["$id"] == "http://json-schema.org/draft-07/schema#" + assert schema["title"] == "Core schema meta-schema" + + +def test_it_is_crawled(): + assert REGISTRY.crawl() == REGISTRY + + +@pytest.mark.parametrize( + "ignored_relative_path", + ["schemas/.DS_Store", "schemas/draft7/.DS_Store"], +) +def test_it_copes_with_dotfiles(ignored_relative_path): + """ + Ignore files like .DS_Store if someone has actually caused one to exist. + + We test here through the private interface as of course the global has + already loaded our schemas. + """ + + import jsonschema_specifications + + package = Path(jsonschema_specifications.__file__).parent + + ignored = package / ignored_relative_path + ignored.touch() + try: + list(jsonschema_specifications._schemas()) + finally: + ignored.unlink() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py new file mode 100644 index 0000000..4f9b685 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/__init__.py @@ -0,0 +1,78 @@ +from .api_jwk import PyJWK, PyJWKSet +from .api_jws import ( + PyJWS, + get_algorithm_by_name, + get_unverified_header, + register_algorithm, + unregister_algorithm, +) +from .api_jwt import PyJWT, decode, decode_complete, encode +from .exceptions import ( + DecodeError, + ExpiredSignatureError, + ImmatureSignatureError, + InvalidAlgorithmError, + InvalidAudienceError, + InvalidIssuedAtError, + InvalidIssuerError, + InvalidKeyError, + InvalidSignatureError, + InvalidTokenError, + MissingRequiredClaimError, + PyJWKClientConnectionError, + PyJWKClientError, + PyJWKError, + PyJWKSetError, + PyJWTError, +) +from .jwks_client import PyJWKClient +from .warnings import InsecureKeyLengthWarning + +__version__ = "2.14.0" + +__title__ = "PyJWT" +__description__ = "JSON Web Token implementation in Python" +__url__ = "https://pyjwt.readthedocs.io" +__uri__ = __url__ +__doc__ = f"{__description__} <{__uri__}>" + +__author__ = "José Padilla" +__email__ = "hello@jpadilla.com" + +__license__ = "MIT" +__copyright__ = "Copyright 2015-2026 José Padilla" + + +__all__ = [ + "PyJWS", + "PyJWT", + "PyJWKClient", + "PyJWK", + "PyJWKSet", + "decode", + "decode_complete", + "encode", + "get_unverified_header", + "register_algorithm", + "unregister_algorithm", + "get_algorithm_by_name", + # Warnings + "InsecureKeyLengthWarning", + # Exceptions + "DecodeError", + "ExpiredSignatureError", + "ImmatureSignatureError", + "InvalidAlgorithmError", + "InvalidAudienceError", + "InvalidIssuedAtError", + "InvalidIssuerError", + "InvalidKeyError", + "InvalidSignatureError", + "InvalidTokenError", + "MissingRequiredClaimError", + "PyJWKClientConnectionError", + "PyJWKClientError", + "PyJWKError", + "PyJWKSetError", + "PyJWTError", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py new file mode 100644 index 0000000..3f350e3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py @@ -0,0 +1,1111 @@ +from __future__ import annotations + +import hashlib +import hmac +import json +import os +import sys +from abc import ABC, abstractmethod +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Literal, + NoReturn, + Union, + cast, + get_args, + overload, +) + +from .exceptions import InvalidKeyError +from .types import HashlibHash, JWKDict +from .utils import ( + base64url_decode, + base64url_encode, + der_to_raw_signature, + force_bytes, + from_base64url_uint, + is_pem_format, + is_ssh_key, + raw_to_der_signature, + to_base64url_uint, +) + +try: + from cryptography import x509 + from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.asymmetric.ec import ( + ECDSA, + SECP256K1, + SECP256R1, + SECP384R1, + SECP521R1, + EllipticCurve, + EllipticCurvePrivateKey, + EllipticCurvePrivateNumbers, + EllipticCurvePublicKey, + EllipticCurvePublicNumbers, + ) + from cryptography.hazmat.primitives.asymmetric.ed448 import ( + Ed448PrivateKey, + Ed448PublicKey, + ) + from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, + Ed25519PublicKey, + ) + from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKey, + RSAPrivateNumbers, + RSAPublicKey, + RSAPublicNumbers, + rsa_crt_dmp1, + rsa_crt_dmq1, + rsa_crt_iqmp, + rsa_recover_prime_factors, + ) + from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, + PublicFormat, + load_der_public_key, + load_pem_private_key, + load_pem_public_key, + load_ssh_public_key, + ) + + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + # Python 3.9 and lower + from typing_extensions import TypeAlias + + # Type aliases for convenience in algorithms method signatures + AllowedRSAKeys: TypeAlias = Union[RSAPrivateKey, RSAPublicKey] + AllowedECKeys: TypeAlias = Union[EllipticCurvePrivateKey, EllipticCurvePublicKey] + AllowedOKPKeys: TypeAlias = Union[ + Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey + ] + AllowedKeys: TypeAlias = Union[AllowedRSAKeys, AllowedECKeys, AllowedOKPKeys] + #: Type alias for allowed ``cryptography`` private keys (requires ``cryptography`` to be installed) + AllowedPrivateKeys: TypeAlias = Union[ + RSAPrivateKey, EllipticCurvePrivateKey, Ed25519PrivateKey, Ed448PrivateKey + ] + #: Type alias for allowed ``cryptography`` public keys (requires ``cryptography`` to be installed) + AllowedPublicKeys: TypeAlias = Union[ + RSAPublicKey, EllipticCurvePublicKey, Ed25519PublicKey, Ed448PublicKey + ] + + if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")): + from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, + ) + + has_crypto = True +except ModuleNotFoundError: + if sys.version_info >= (3, 11): + from typing import Never + else: + from typing_extensions import Never + + AllowedRSAKeys = Never # type: ignore[misc] + AllowedECKeys = Never # type: ignore[misc] + AllowedOKPKeys = Never # type: ignore[misc] + AllowedKeys = Never # type: ignore[misc] + AllowedPrivateKeys = Never # type: ignore[misc] + AllowedPublicKeys = Never # type: ignore[misc] + has_crypto = False + + +requires_cryptography = { + "RS256", + "RS384", + "RS512", + "ES256", + "ES256K", + "ES384", + "ES521", + "ES512", + "PS256", + "PS384", + "PS512", + "EdDSA", +} + + +def get_default_algorithms() -> dict[str, Algorithm]: + """ + Returns the algorithms that are implemented by the library. + """ + default_algorithms: dict[str, Algorithm] = { + "none": NoneAlgorithm(), + "HS256": HMACAlgorithm(HMACAlgorithm.SHA256), + "HS384": HMACAlgorithm(HMACAlgorithm.SHA384), + "HS512": HMACAlgorithm(HMACAlgorithm.SHA512), + } + + if has_crypto: + default_algorithms.update( + { + "RS256": RSAAlgorithm(RSAAlgorithm.SHA256), + "RS384": RSAAlgorithm(RSAAlgorithm.SHA384), + "RS512": RSAAlgorithm(RSAAlgorithm.SHA512), + "ES256": ECAlgorithm(ECAlgorithm.SHA256, SECP256R1), + "ES256K": ECAlgorithm(ECAlgorithm.SHA256, SECP256K1), + "ES384": ECAlgorithm(ECAlgorithm.SHA384, SECP384R1), + "ES521": ECAlgorithm(ECAlgorithm.SHA512, SECP521R1), + "ES512": ECAlgorithm( + ECAlgorithm.SHA512, SECP521R1 + ), # Backward compat for #219 fix + "PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), + "PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), + "PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512), + "EdDSA": OKPAlgorithm(), + } + ) + + return default_algorithms + + +class Algorithm(ABC): + """ + The interface for an algorithm used to sign and verify tokens. + """ + + # pyjwt-964: Validate to ensure the key passed in was decoded to the correct cryptography key family + _crypto_key_types: tuple[type[AllowedKeys], ...] | None = None + + def compute_hash_digest(self, bytestr: bytes) -> bytes: + """ + Compute a hash digest using the specified algorithm's hash algorithm. + + If there is no hash algorithm, raises a NotImplementedError. + """ + # lookup self.hash_alg if defined in a way that mypy can understand + hash_alg = getattr(self, "hash_alg", None) + if hash_alg is None: + raise NotImplementedError + + if ( + has_crypto + and isinstance(hash_alg, type) + and issubclass(hash_alg, hashes.HashAlgorithm) + ): + digest = hashes.Hash(hash_alg(), backend=default_backend()) + digest.update(bytestr) + return bytes(digest.finalize()) + else: + return bytes(hash_alg(bytestr).digest()) + + def check_crypto_key_type(self, key: PublicKeyTypes | PrivateKeyTypes) -> None: + """Check that the key belongs to the right cryptographic family. + + Note that this method only works when ``cryptography`` is installed. + + :param key: Potentially a cryptography key + :type key: :py:data:`PublicKeyTypes ` | :py:data:`PrivateKeyTypes ` + :raises ValueError: if ``cryptography`` is not installed, or this method is called by a non-cryptography algorithm + :raises InvalidKeyError: if the key doesn't match the expected key classes + """ + if not has_crypto or self._crypto_key_types is None: + raise ValueError( + "This method requires the cryptography library, and should only be used by cryptography-based algorithms." + ) + + if not isinstance(key, self._crypto_key_types): + valid_classes = (cls.__name__ for cls in self._crypto_key_types) + actual_class = key.__class__.__name__ + self_class = self.__class__.__name__ + raise InvalidKeyError( + f"Expected one of {valid_classes}, got: {actual_class}. Invalid Key type for {self_class}" + ) + + @abstractmethod + def prepare_key(self, key: Any) -> Any: + """ + Performs necessary validation and conversions on the key and returns + the key value in the proper format for sign() and verify(). + """ + + @abstractmethod + def sign(self, msg: bytes, key: Any) -> bytes: + """ + Returns a digital signature for the specified message + using the specified key value. + """ + + @abstractmethod + def verify(self, msg: bytes, key: Any, sig: bytes) -> bool: + """ + Verifies that the specified digital signature is valid + for the specified message and key values. + """ + + @overload + @staticmethod + @abstractmethod + def to_jwk(key_obj: Any, as_dict: Literal[True]) -> JWKDict: ... # pragma: no cover + + @overload + @staticmethod + @abstractmethod + def to_jwk( + key_obj: Any, as_dict: Literal[False] = False + ) -> str: ... # pragma: no cover + + @staticmethod + @abstractmethod + def to_jwk(key_obj: Any, as_dict: bool = False) -> JWKDict | str: + """ + Serializes a given key into a JWK + """ + + @staticmethod + @abstractmethod + def from_jwk(jwk: str | JWKDict) -> Any: + """ + Deserializes a given key from JWK back into a key object + """ + + def check_key_length(self, key: Any) -> str | None: + """ + Return a warning message if the key is below the minimum + recommended length for this algorithm, or None if adequate. + """ + return None + + +class NoneAlgorithm(Algorithm): + """ + Placeholder for use when no signing or verification + operations are required. + """ + + def prepare_key(self, key: str | None) -> None: + if key == "": + key = None + + if key is not None: + raise InvalidKeyError('When alg = "none", key value must be None.') + + return key + + def sign(self, msg: bytes, key: None) -> bytes: + return b"" + + def verify(self, msg: bytes, key: None, sig: bytes) -> bool: + return False + + @staticmethod + def to_jwk(key_obj: Any, as_dict: bool = False) -> NoReturn: + raise NotImplementedError() + + @staticmethod + def from_jwk(jwk: str | JWKDict) -> NoReturn: + raise NotImplementedError() + + +class HMACAlgorithm(Algorithm): + """ + Performs signing and verification operations using HMAC + and the specified hash function. + """ + + SHA256: ClassVar[HashlibHash] = hashlib.sha256 + SHA384: ClassVar[HashlibHash] = hashlib.sha384 + SHA512: ClassVar[HashlibHash] = hashlib.sha512 + + def __init__(self, hash_alg: HashlibHash) -> None: + self.hash_alg = hash_alg + + @staticmethod + def _is_der_key(key_bytes: bytes) -> bool: + if not has_crypto: + return False + + try: + load_der_public_key(key_bytes) + except (TypeError, ValueError, UnsupportedAlgorithm): + pass + else: + return True + + try: + x509.load_der_x509_certificate(key_bytes) + except (TypeError, ValueError, UnsupportedAlgorithm): + return False + else: + return True + + def prepare_key(self, key: str | bytes) -> bytes: + key_bytes = force_bytes(key) + + if len(key_bytes) == 0: + raise InvalidKeyError("HMAC key must not be empty.") + + if ( + is_pem_format(key_bytes) + or is_ssh_key(key_bytes) + or self._is_der_key(key_bytes) + ): + raise InvalidKeyError( + "The specified key is an asymmetric key or x509 certificate and" + " should not be used as an HMAC secret." + ) + + # Defense against algorithm-confusion attacks: an attacker with + # control over the token header can force this code path by setting + # alg=HS*, and HMACAlgorithm is the only algorithm that accepts + # arbitrary bytes as a valid secret. Other algorithms reject + # non-key-shaped input naturally. Even a symmetric (kty=oct) JWK + # should be loaded via PyJWK / from_jwk rather than fed as raw JSON + # bytes (whose contents are not the secret material). + try: + jwk_obj = json.loads(key_bytes, parse_int=lambda _: 0) + except RecursionError: + try: + decoded_key = key_bytes.decode( + json.detect_encoding(key_bytes), errors="surrogatepass" + ) + except UnicodeError: + decoded_key = "" + stripped_key = decoded_key.lstrip("\ufeff \t\r\n") + has_jwk_member = False + index = 0 + while index < len(decoded_key): + if decoded_key[index] != '"': + index += 1 + continue + end = index + 1 + while end < len(decoded_key): + if decoded_key[end] == "\\": + end += 2 + elif decoded_key[end] == '"': + break + else: + end += 1 + if end >= len(decoded_key): + break + next_index = end + 1 + while ( + next_index < len(decoded_key) + and decoded_key[next_index] in " \t\r\n" + ): + next_index += 1 + if next_index < len(decoded_key) and decoded_key[next_index] == ":": + try: + has_jwk_member = ( + json.loads(decoded_key[index : end + 1]) == "kty" + ) + except ValueError: + pass + if has_jwk_member: + break + index = end + 1 + if stripped_key.startswith("{") or ( + stripped_key.startswith("[") and has_jwk_member + ): + raise InvalidKeyError( + "The specified key looks like a JWK and should not be " + "used directly as an HMAC secret. Load it via " + "PyJWK / HMACAlgorithm.from_jwk first." + ) from None + jwk_obj = None + except ValueError: + jwk_obj = None + contains_jwk_member = False + objects_to_check = [jwk_obj] + while objects_to_check: + obj = objects_to_check.pop() + if isinstance(obj, dict): + if "kty" in obj: + contains_jwk_member = True + break + objects_to_check.extend(obj.values()) + elif isinstance(obj, list): + objects_to_check.extend(obj) + if contains_jwk_member: + raise InvalidKeyError( + "The specified key looks like a JWK and should not be " + "used directly as an HMAC secret. Load it via " + "PyJWK / HMACAlgorithm.from_jwk first." + ) + + return key_bytes + + @overload + @staticmethod + def to_jwk(key_obj: str | bytes, as_dict: Literal[True]) -> JWKDict: ... + + @overload + @staticmethod + def to_jwk(key_obj: str | bytes, as_dict: Literal[False] = False) -> str: ... + + @staticmethod + def to_jwk(key_obj: str | bytes, as_dict: bool = False) -> JWKDict | str: + jwk = { + "k": base64url_encode(force_bytes(key_obj)).decode(), + "kty": "oct", + } + + if as_dict: + return jwk + else: + return json.dumps(jwk) + + @staticmethod + def from_jwk(jwk: str | JWKDict) -> bytes: + try: + if isinstance(jwk, str): + obj: JWKDict = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") from None + + if obj.get("kty") != "oct": + raise InvalidKeyError("Not an HMAC key") + + key_bytes = base64url_decode(obj["k"]) + if len(key_bytes) == 0: + raise InvalidKeyError("HMAC key must not be empty.") + return key_bytes + + def check_key_length(self, key: bytes) -> str | None: + min_length = self.hash_alg().digest_size + if len(key) < min_length: + return ( + f"The HMAC key is {len(key)} bytes long, which is below " + f"the minimum recommended length of {min_length} bytes for " + f"{self.hash_alg().name.upper()}. " + f"See RFC 7518 Section 3.2." + ) + return None + + def sign(self, msg: bytes, key: bytes) -> bytes: + return hmac.new(key, msg, self.hash_alg).digest() + + def verify(self, msg: bytes, key: bytes, sig: bytes) -> bool: + return hmac.compare_digest(sig, self.sign(msg, key)) + + +if has_crypto: + + class RSAAlgorithm(Algorithm): + """ + Performs signing and verification operations using + RSASSA-PKCS-v1_5 and the specified hash function. + """ + + SHA256: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA256 + SHA384: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA384 + SHA512: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA512 + + _crypto_key_types = cast( + tuple[type[AllowedKeys], ...], + get_args(Union[RSAPrivateKey, RSAPublicKey]), + ) + _MIN_KEY_SIZE: ClassVar[int] = 2048 + + def __init__(self, hash_alg: type[hashes.HashAlgorithm]) -> None: + self.hash_alg = hash_alg + + def check_key_length(self, key: AllowedRSAKeys) -> str | None: + if key.key_size < self._MIN_KEY_SIZE: + return ( + f"The RSA key is {key.key_size} bits long, which is below " + f"the minimum recommended size of {self._MIN_KEY_SIZE} bits. " + f"See NIST SP 800-131A." + ) + return None + + def prepare_key(self, key: AllowedRSAKeys | str | bytes) -> AllowedRSAKeys: + if isinstance(key, self._crypto_key_types): + # Cast is required for type narrowing on Python 3.9's mypy + # but redundant on newer mypy versions; suppress both + # diagnostics so the line works across all supported envs. + return cast(AllowedRSAKeys, key) # type: ignore[redundant-cast,unused-ignore] + + if not isinstance(key, (bytes, str)): + raise TypeError("Expecting a PEM-formatted key.") + + key_bytes = force_bytes(key) + + try: + if key_bytes.startswith(b"ssh-rsa"): + public_key: PublicKeyTypes = load_ssh_public_key(key_bytes) + self.check_crypto_key_type(public_key) + return cast(RSAPublicKey, public_key) + else: + private_key: PrivateKeyTypes = load_pem_private_key( + key_bytes, password=None + ) + self.check_crypto_key_type(private_key) + return cast(RSAPrivateKey, private_key) + except ValueError: + try: + public_key = load_pem_public_key(key_bytes) + self.check_crypto_key_type(public_key) + return cast(RSAPublicKey, public_key) + except (ValueError, UnsupportedAlgorithm): + raise InvalidKeyError( + "Could not parse the provided public key." + ) from None + + @overload + @staticmethod + def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[True]) -> JWKDict: ... + + @overload + @staticmethod + def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[False] = False) -> str: ... + + @staticmethod + def to_jwk(key_obj: AllowedRSAKeys, as_dict: bool = False) -> JWKDict | str: + obj: dict[str, Any] | None = None + + if hasattr(key_obj, "private_numbers"): + # Private key + numbers = key_obj.private_numbers() + + obj = { + "kty": "RSA", + "key_ops": ["sign"], + "n": to_base64url_uint(numbers.public_numbers.n).decode(), + "e": to_base64url_uint(numbers.public_numbers.e).decode(), + "d": to_base64url_uint(numbers.d).decode(), + "p": to_base64url_uint(numbers.p).decode(), + "q": to_base64url_uint(numbers.q).decode(), + "dp": to_base64url_uint(numbers.dmp1).decode(), + "dq": to_base64url_uint(numbers.dmq1).decode(), + "qi": to_base64url_uint(numbers.iqmp).decode(), + } + + elif hasattr(key_obj, "verify"): + # Public key + numbers = key_obj.public_numbers() + + obj = { + "kty": "RSA", + "key_ops": ["verify"], + "n": to_base64url_uint(numbers.n).decode(), + "e": to_base64url_uint(numbers.e).decode(), + } + else: + raise InvalidKeyError("Not a public or private key") + + if as_dict: + return obj + else: + return json.dumps(obj) + + @staticmethod + def from_jwk(jwk: str | JWKDict) -> AllowedRSAKeys: + try: + if isinstance(jwk, str): + obj = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") from None + + if obj.get("kty") != "RSA": + raise InvalidKeyError("Not an RSA key") from None + + if "d" in obj and "e" in obj and "n" in obj: + # Private key + if "oth" in obj: + raise InvalidKeyError( + "Unsupported RSA private key: > 2 primes not supported" + ) + + other_props = ["p", "q", "dp", "dq", "qi"] + props_found = [prop in obj for prop in other_props] + any_props_found = any(props_found) + + if any_props_found and not all(props_found): + raise InvalidKeyError( + "RSA key must include all parameters if any are present besides d" + ) from None + + public_numbers = RSAPublicNumbers( + from_base64url_uint(obj["e"]), + from_base64url_uint(obj["n"]), + ) + + if any_props_found: + numbers = RSAPrivateNumbers( + d=from_base64url_uint(obj["d"]), + p=from_base64url_uint(obj["p"]), + q=from_base64url_uint(obj["q"]), + dmp1=from_base64url_uint(obj["dp"]), + dmq1=from_base64url_uint(obj["dq"]), + iqmp=from_base64url_uint(obj["qi"]), + public_numbers=public_numbers, + ) + else: + d = from_base64url_uint(obj["d"]) + p, q = rsa_recover_prime_factors( + public_numbers.n, d, public_numbers.e + ) + + numbers = RSAPrivateNumbers( + d=d, + p=p, + q=q, + dmp1=rsa_crt_dmp1(d, p), + dmq1=rsa_crt_dmq1(d, q), + iqmp=rsa_crt_iqmp(p, q), + public_numbers=public_numbers, + ) + + return numbers.private_key() + elif "n" in obj and "e" in obj: + # Public key + return RSAPublicNumbers( + from_base64url_uint(obj["e"]), + from_base64url_uint(obj["n"]), + ).public_key() + else: + raise InvalidKeyError("Not a public or private key") + + def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes: + signature: bytes = key.sign(msg, padding.PKCS1v15(), self.hash_alg()) + return signature + + def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool: + try: + key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) + return True + except InvalidSignature: + return False + + class ECAlgorithm(Algorithm): + """ + Performs signing and verification operations using + ECDSA and the specified hash function + """ + + SHA256: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA256 + SHA384: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA384 + SHA512: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA512 + + _crypto_key_types = cast( + tuple[type[AllowedKeys], ...], + get_args(Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]), + ) + + def __init__( + self, + hash_alg: type[hashes.HashAlgorithm], + expected_curve: type[EllipticCurve] | None = None, + ) -> None: + self.hash_alg = hash_alg + self.expected_curve = expected_curve + + def _validate_curve(self, key: AllowedECKeys) -> None: + """Validate that the key's curve matches the expected curve.""" + if self.expected_curve is None: + return + + if not isinstance(key.curve, self.expected_curve): + raise InvalidKeyError( + f"The key's curve '{key.curve.name}' does not match the expected " + f"curve '{self.expected_curve.name}' for this algorithm" + ) + + def prepare_key(self, key: AllowedECKeys | str | bytes) -> AllowedECKeys: + if isinstance(key, self._crypto_key_types): + # See note in RSAAlgorithm.prepare_key. + ec_key = cast(AllowedECKeys, key) # type: ignore[redundant-cast,unused-ignore] + self._validate_curve(ec_key) + return ec_key + + if not isinstance(key, (bytes, str)): + raise TypeError("Expecting a PEM-formatted key.") + + key_bytes = force_bytes(key) + + # Attempt to load key. We don't know if it's + # a Signing Key or a Verifying Key, so we try + # the Verifying Key first. + try: + if key_bytes.startswith(b"ecdsa-sha2-"): + public_key: PublicKeyTypes = load_ssh_public_key(key_bytes) + else: + public_key = load_pem_public_key(key_bytes) + + # Explicit check the key to prevent confusing errors from cryptography + self.check_crypto_key_type(public_key) + ec_public_key = cast(EllipticCurvePublicKey, public_key) + self._validate_curve(ec_public_key) + return ec_public_key + except ValueError: + private_key = load_pem_private_key(key_bytes, password=None) + self.check_crypto_key_type(private_key) + ec_private_key = cast(EllipticCurvePrivateKey, private_key) + self._validate_curve(ec_private_key) + return ec_private_key + + def sign(self, msg: bytes, key: EllipticCurvePrivateKey) -> bytes: + der_sig = key.sign(msg, ECDSA(self.hash_alg())) + + return der_to_raw_signature(der_sig, key.curve) + + def verify(self, msg: bytes, key: AllowedECKeys, sig: bytes) -> bool: + try: + der_sig = raw_to_der_signature(sig, key.curve) + except ValueError: + return False + + try: + public_key = ( + key.public_key() + if isinstance(key, EllipticCurvePrivateKey) + else key + ) + public_key.verify(der_sig, msg, ECDSA(self.hash_alg())) + return True + except InvalidSignature: + return False + + @overload + @staticmethod + def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[True]) -> JWKDict: ... + + @overload + @staticmethod + def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[False] = False) -> str: ... + + @staticmethod + def to_jwk(key_obj: AllowedECKeys, as_dict: bool = False) -> JWKDict | str: + if isinstance(key_obj, EllipticCurvePrivateKey): + public_numbers = key_obj.public_key().public_numbers() + elif isinstance(key_obj, EllipticCurvePublicKey): + public_numbers = key_obj.public_numbers() + else: + raise InvalidKeyError("Not a public or private key") + + if isinstance(key_obj.curve, SECP256R1): + crv = "P-256" + elif isinstance(key_obj.curve, SECP384R1): + crv = "P-384" + elif isinstance(key_obj.curve, SECP521R1): + crv = "P-521" + elif isinstance(key_obj.curve, SECP256K1): + crv = "secp256k1" + else: + raise InvalidKeyError(f"Invalid curve: {key_obj.curve}") + + obj: dict[str, Any] = { + "kty": "EC", + "crv": crv, + "x": to_base64url_uint( + public_numbers.x, + bit_length=key_obj.curve.key_size, + ).decode(), + "y": to_base64url_uint( + public_numbers.y, + bit_length=key_obj.curve.key_size, + ).decode(), + } + + if isinstance(key_obj, EllipticCurvePrivateKey): + obj["d"] = to_base64url_uint( + key_obj.private_numbers().private_value, + bit_length=key_obj.curve.key_size, + ).decode() + + if as_dict: + return obj + else: + return json.dumps(obj) + + @staticmethod + def from_jwk(jwk: str | JWKDict) -> AllowedECKeys: + try: + if isinstance(jwk, str): + obj = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") from None + + if obj.get("kty") != "EC": + raise InvalidKeyError("Not an Elliptic curve key") from None + + if "x" not in obj or "y" not in obj: + raise InvalidKeyError("Not an Elliptic curve key") from None + + x = base64url_decode(obj.get("x")) + y = base64url_decode(obj.get("y")) + + curve = obj.get("crv") + curve_obj: EllipticCurve + + if curve == "P-256": + if len(x) == len(y) == 32: + curve_obj = SECP256R1() + else: + raise InvalidKeyError( + "Coords should be 32 bytes for curve P-256" + ) from None + elif curve == "P-384": + if len(x) == len(y) == 48: + curve_obj = SECP384R1() + else: + raise InvalidKeyError( + "Coords should be 48 bytes for curve P-384" + ) from None + elif curve == "P-521": + if len(x) == len(y) == 66: + curve_obj = SECP521R1() + else: + raise InvalidKeyError( + "Coords should be 66 bytes for curve P-521" + ) from None + elif curve == "secp256k1": + if len(x) == len(y) == 32: + curve_obj = SECP256K1() + else: + raise InvalidKeyError( + "Coords should be 32 bytes for curve secp256k1" + ) + else: + raise InvalidKeyError(f"Invalid curve: {curve}") + + public_numbers = EllipticCurvePublicNumbers( + x=int.from_bytes(x, byteorder="big"), + y=int.from_bytes(y, byteorder="big"), + curve=curve_obj, + ) + + if "d" not in obj: + return public_numbers.public_key() + + d = base64url_decode(obj.get("d")) + if len(d) != len(x): + raise InvalidKeyError( + "D should be {} bytes for curve {}", len(x), curve + ) + + return EllipticCurvePrivateNumbers( + int.from_bytes(d, byteorder="big"), public_numbers + ).private_key() + + class RSAPSSAlgorithm(RSAAlgorithm): + """ + Performs a signature using RSASSA-PSS with MGF1 + """ + + def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes: + signature: bytes = key.sign( + msg, + padding.PSS( + mgf=padding.MGF1(self.hash_alg()), + salt_length=self.hash_alg().digest_size, + ), + self.hash_alg(), + ) + return signature + + def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool: + try: + key.verify( + sig, + msg, + padding.PSS( + mgf=padding.MGF1(self.hash_alg()), + salt_length=self.hash_alg().digest_size, + ), + self.hash_alg(), + ) + return True + except InvalidSignature: + return False + + class OKPAlgorithm(Algorithm): + """ + Performs signing and verification operations using EdDSA + + This class requires ``cryptography>=2.6`` to be installed. + """ + + _crypto_key_types = cast( + tuple[type[AllowedKeys], ...], + get_args( + Union[ + Ed25519PrivateKey, + Ed25519PublicKey, + Ed448PrivateKey, + Ed448PublicKey, + ] + ), + ) + + def __init__(self, **kwargs: Any) -> None: + pass + + def prepare_key(self, key: AllowedOKPKeys | str | bytes) -> AllowedOKPKeys: + if not isinstance(key, (str, bytes)): + self.check_crypto_key_type(key) + return key + + key_str = key.decode("utf-8") if isinstance(key, bytes) else key + key_bytes = key.encode("utf-8") if isinstance(key, str) else key + + loaded_key: PublicKeyTypes | PrivateKeyTypes + if "-----BEGIN PUBLIC" in key_str: + loaded_key = load_pem_public_key(key_bytes) + elif "-----BEGIN PRIVATE" in key_str: + loaded_key = load_pem_private_key(key_bytes, password=None) + elif key_str[0:4] == "ssh-": + loaded_key = load_ssh_public_key(key_bytes) + else: + raise InvalidKeyError("Not a public or private key") + + # Explicit check the key to prevent confusing errors from cryptography + self.check_crypto_key_type(loaded_key) + return cast("AllowedOKPKeys", loaded_key) + + def sign( + self, msg: str | bytes, key: Ed25519PrivateKey | Ed448PrivateKey + ) -> bytes: + """ + Sign a message ``msg`` using the EdDSA private key ``key`` + :param str|bytes msg: Message to sign + :param Ed25519PrivateKey}Ed448PrivateKey key: A :class:`.Ed25519PrivateKey` + or :class:`.Ed448PrivateKey` isinstance + :return bytes signature: The signature, as bytes + """ + msg_bytes = msg.encode("utf-8") if isinstance(msg, str) else msg + signature: bytes = key.sign(msg_bytes) + return signature + + def verify( + self, msg: str | bytes, key: AllowedOKPKeys, sig: str | bytes + ) -> bool: + """ + Verify a given ``msg`` against a signature ``sig`` using the EdDSA key ``key`` + + :param str|bytes sig: EdDSA signature to check ``msg`` against + :param str|bytes msg: Message to sign + :param Ed25519PrivateKey|Ed25519PublicKey|Ed448PrivateKey|Ed448PublicKey key: + A private or public EdDSA key instance + :return bool verified: True if signature is valid, False if not. + """ + try: + msg_bytes = msg.encode("utf-8") if isinstance(msg, str) else msg + sig_bytes = sig.encode("utf-8") if isinstance(sig, str) else sig + + public_key = ( + key.public_key() + if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)) + else key + ) + public_key.verify(sig_bytes, msg_bytes) + return True # If no exception was raised, the signature is valid. + except InvalidSignature: + return False + + @overload + @staticmethod + def to_jwk(key: AllowedOKPKeys, as_dict: Literal[True]) -> JWKDict: ... + + @overload + @staticmethod + def to_jwk(key: AllowedOKPKeys, as_dict: Literal[False] = False) -> str: ... + + @staticmethod + def to_jwk(key: AllowedOKPKeys, as_dict: bool = False) -> JWKDict | str: + if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): + x = key.public_bytes( + encoding=Encoding.Raw, + format=PublicFormat.Raw, + ) + crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448" + + obj = { + "x": base64url_encode(force_bytes(x)).decode(), + "kty": "OKP", + "crv": crv, + } + + if as_dict: + return obj + else: + return json.dumps(obj) + + if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)): + d = key.private_bytes( + encoding=Encoding.Raw, + format=PrivateFormat.Raw, + encryption_algorithm=NoEncryption(), + ) + + x = key.public_key().public_bytes( + encoding=Encoding.Raw, + format=PublicFormat.Raw, + ) + + crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448" + obj = { + "x": base64url_encode(force_bytes(x)).decode(), + "d": base64url_encode(force_bytes(d)).decode(), + "kty": "OKP", + "crv": crv, + } + + if as_dict: + return obj + else: + return json.dumps(obj) + + raise InvalidKeyError("Not a public or private key") + + @staticmethod + def from_jwk(jwk: str | JWKDict) -> AllowedOKPKeys: + try: + if isinstance(jwk, str): + obj = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") from None + + if obj.get("kty") != "OKP": + raise InvalidKeyError("Not an Octet Key Pair") + + curve = obj.get("crv") + if curve != "Ed25519" and curve != "Ed448": + raise InvalidKeyError(f"Invalid curve: {curve}") + + if "x" not in obj: + raise InvalidKeyError('OKP should have "x" parameter') + x = base64url_decode(obj.get("x")) + + try: + if "d" not in obj: + if curve == "Ed25519": + return Ed25519PublicKey.from_public_bytes(x) + return Ed448PublicKey.from_public_bytes(x) + d = base64url_decode(obj.get("d")) + if curve == "Ed25519": + return Ed25519PrivateKey.from_private_bytes(d) + return Ed448PrivateKey.from_private_bytes(d) + except ValueError as err: + raise InvalidKeyError("Invalid key parameter") from err diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py new file mode 100644 index 0000000..5ee26b4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +import json +import time +from collections.abc import Iterator +from typing import Any + +from .algorithms import get_default_algorithms, has_crypto, requires_cryptography +from .exceptions import ( + InvalidKeyError, + MissingCryptographyError, + PyJWKError, + PyJWKSetError, + PyJWTError, +) +from .types import JWKDict + + +class PyJWK: + def __init__(self, jwk_data: JWKDict, algorithm: str | None = None) -> None: + """A class that represents a `JSON Web Key `_. + + :param jwk_data: The decoded JWK data. + :type jwk_data: dict[str, typing.Any] + :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used. + :type algorithm: str or None + :raises InvalidKeyError: If the key type (``kty``) is not found or unsupported, or if the curve (``crv``) is not found or unsupported. + :raises MissingCryptographyError: If the algorithm requires ``cryptography`` to be installed and it is not available. + :raises PyJWKError: If unable to find an algorithm for the key. + """ + self._jwk_data = jwk_data + + kty = self._jwk_data.get("kty", None) + if not kty: + raise InvalidKeyError(f"kty is not found: {self._jwk_data}") + + if not algorithm and isinstance(self._jwk_data, dict): + algorithm = self._jwk_data.get("alg", None) + + if not algorithm: + # Determine alg with kty (and crv). + crv = self._jwk_data.get("crv", None) + if kty == "EC": + if crv == "P-256" or not crv: + algorithm = "ES256" + elif crv == "P-384": + algorithm = "ES384" + elif crv == "P-521": + algorithm = "ES512" + elif crv == "secp256k1": + algorithm = "ES256K" + else: + raise InvalidKeyError(f"Unsupported crv: {crv}") + elif kty == "RSA": + algorithm = "RS256" + elif kty == "oct": + algorithm = "HS256" + elif kty == "OKP": + if not crv: + raise InvalidKeyError(f"crv is not found: {self._jwk_data}") + if crv == "Ed25519": + algorithm = "EdDSA" + else: + raise InvalidKeyError(f"Unsupported crv: {crv}") + else: + raise InvalidKeyError(f"Unsupported kty: {kty}") + + if not has_crypto and algorithm in requires_cryptography: + raise MissingCryptographyError( + f"{algorithm} requires 'cryptography' to be installed." + ) + + self.algorithm_name = algorithm + + try: + self.Algorithm = get_default_algorithms()[algorithm] + except KeyError: + raise PyJWKError( + f"Unable to find an algorithm for key: {self._jwk_data}", + ) from None + + try: + self.key = self.Algorithm.from_jwk(self._jwk_data) + except ValueError as error: + raise InvalidKeyError( + f"Unable to construct key from JWK: {error}" + ) from error + + @staticmethod + def from_dict(obj: JWKDict, algorithm: str | None = None) -> PyJWK: + """Creates a :class:`PyJWK` object from a JSON-like dictionary. + + :param obj: The JWK data, as a dictionary + :type obj: dict[str, typing.Any] + :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used. + :type algorithm: str or None + :rtype: PyJWK + """ + return PyJWK(obj, algorithm) + + @staticmethod + def from_json(data: str, algorithm: None = None) -> PyJWK: + """Create a :class:`PyJWK` object from a JSON string. + Implicitly calls :meth:`PyJWK.from_dict()`. + + :param str data: The JWK data, as a JSON string. + :param algorithm: The key algorithm. If not specific, the key's ``alg`` will be used. + :type algorithm: str or None + + :rtype: PyJWK + """ + obj = json.loads(data) + return PyJWK.from_dict(obj, algorithm) + + @property + def key_type(self) -> str | None: + """The `kty` property from the JWK. + + :rtype: str or None + """ + return self._jwk_data.get("kty", None) + + @property + def key_id(self) -> str | None: + """The `kid` property from the JWK. + + :rtype: str or None + """ + return self._jwk_data.get("kid", None) + + @property + def public_key_use(self) -> str | None: + """The `use` property from the JWK. + + :rtype: str or None + """ + return self._jwk_data.get("use", None) + + +class PyJWKSet: + def __init__(self, keys: list[JWKDict]) -> None: + self.keys: list[PyJWK] = [] + + if not keys: + raise PyJWKSetError("The JWK Set did not contain any keys") + + if not isinstance(keys, list): + raise PyJWKSetError("Invalid JWK Set value") + + for key in keys: + try: + self.keys.append(PyJWK(key)) + except PyJWTError as error: + if isinstance(error, MissingCryptographyError): + raise error + # skip unusable keys + continue + + if len(self.keys) == 0: + raise PyJWKSetError( + "The JWK Set did not contain any usable keys. Perhaps 'cryptography' is not installed?" + ) + + @staticmethod + def from_dict(obj: dict[str, Any]) -> PyJWKSet: + keys = obj.get("keys", []) + return PyJWKSet(keys) + + @staticmethod + def from_json(data: str) -> PyJWKSet: + obj = json.loads(data) + return PyJWKSet.from_dict(obj) + + def __getitem__(self, kid: str) -> PyJWK: + for key in self.keys: + if key.key_id == kid: + return key + raise KeyError(f"keyset has no key for kid: {kid}") + + def __iter__(self) -> Iterator[PyJWK]: + return iter(self.keys) + + +class PyJWTSetWithTimestamp: + def __init__(self, jwk_set: PyJWKSet): + self.jwk_set = jwk_set + self.timestamp = time.monotonic() + + def get_jwk_set(self) -> PyJWKSet: + return self.jwk_set + + def get_timestamp(self) -> float: + return self.timestamp diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py new file mode 100644 index 0000000..81a2ccb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py @@ -0,0 +1,472 @@ +from __future__ import annotations + +import binascii +import json +import warnings +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +from .algorithms import ( + Algorithm, + get_default_algorithms, + has_crypto, + requires_cryptography, +) +from .api_jwk import PyJWK +from .exceptions import ( + DecodeError, + InvalidAlgorithmError, + InvalidKeyError, + InvalidSignatureError, + InvalidTokenError, +) +from .utils import base64url_decode, base64url_encode +from .warnings import InsecureKeyLengthWarning, RemovedInPyjwt3Warning + +if TYPE_CHECKING: + from .algorithms import AllowedPrivateKeys, AllowedPublicKeys + from .types import SigOptions + +_ALGORITHM_UNSET = object() +_BASE64URL_ALPHABET = ( + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" +) + + +class PyJWS: + header_typ = "JWT" + + def __init__( + self, + algorithms: Sequence[str] | None = None, + options: SigOptions | None = None, + ) -> None: + self._algorithms = get_default_algorithms() + self._valid_algs = ( + set(algorithms) if algorithms is not None else set(self._algorithms) + ) + + # Remove algorithms that aren't on the whitelist + for key in list(self._algorithms.keys()): + if key not in self._valid_algs: + del self._algorithms[key] + + self.options: SigOptions = self._get_default_options() + if options is not None: + self.options = {**self.options, **options} + + @staticmethod + def _get_default_options() -> SigOptions: + return {"verify_signature": True, "enforce_minimum_key_length": False} + + def register_algorithm(self, alg_id: str, alg_obj: Algorithm) -> None: + """ + Registers a new Algorithm for use when creating and verifying tokens. + + :param str alg_id: the ID of the Algorithm + :param alg_obj: the Algorithm object + :type alg_obj: Algorithm + """ + if alg_id in self._algorithms: + raise ValueError("Algorithm already has a handler.") + + if not isinstance(alg_obj, Algorithm): + raise TypeError("Object is not of type `Algorithm`") + + self._algorithms[alg_id] = alg_obj + self._valid_algs.add(alg_id) + + def unregister_algorithm(self, alg_id: str) -> None: + """ + Unregisters an Algorithm for use when creating and verifying tokens + :param str alg_id: the ID of the Algorithm + :raises KeyError: if algorithm is not registered. + """ + if alg_id not in self._algorithms: + raise KeyError( + "The specified algorithm could not be removed" + " because it is not registered." + ) + + del self._algorithms[alg_id] + self._valid_algs.remove(alg_id) + + def get_algorithms(self) -> list[str]: + """ + Returns a list of supported values for the `alg` parameter. + + :rtype: list[str] + """ + return list(self._valid_algs) + + def get_algorithm_by_name(self, alg_name: str) -> Algorithm: + """ + For a given string name, return the matching Algorithm object. + + Example usage: + >>> jws_obj = PyJWS() + >>> jws_obj.get_algorithm_by_name("RS256") + + :param alg_name: The name of the algorithm to retrieve + :type alg_name: str + :rtype: Algorithm + """ + try: + return self._algorithms[alg_name] + except KeyError as e: + if not has_crypto and alg_name in requires_cryptography: + raise NotImplementedError( + f"Algorithm '{alg_name}' could not be found. Do you have cryptography installed?" + ) from e + raise NotImplementedError("Algorithm not supported") from e + + def encode( + self, + payload: bytes, + key: AllowedPrivateKeys | PyJWK | str | bytes, + algorithm: str | None = _ALGORITHM_UNSET, # type: ignore[assignment] + headers: dict[str, Any] | None = None, + json_encoder: type[json.JSONEncoder] | None = None, + is_payload_detached: bool = False, + sort_headers: bool = True, + ) -> str: + segments: list[bytes] = [] + + # declare a new var to narrow the type for type checkers + if algorithm is _ALGORITHM_UNSET: + if isinstance(key, PyJWK): + algorithm_ = key.algorithm_name + else: + algorithm_ = "HS256" + elif algorithm is None: + if isinstance(key, PyJWK): + algorithm_ = key.algorithm_name + else: + algorithm_ = "none" + else: + algorithm_ = algorithm + + # Prefer headers values if present to function parameters. + if headers: + headers_alg = headers.get("alg") + if headers_alg: + algorithm_ = headers["alg"] + + headers_b64 = headers.get("b64") + if headers_b64 is False: + is_payload_detached = True + + # Header + header: dict[str, Any] = {"typ": self.header_typ, "alg": algorithm_} + + if headers: + self._validate_headers(headers, encoding=True) + header.update(headers) + + if not header["typ"]: + del header["typ"] + + if is_payload_detached: + header["b64"] = False + # RFC 7797 §3: producers MUST list "b64" in "crit" whenever + # "b64" appears in the protected header, so b64-unaware + # verifiers don't silently treat an unencoded payload as + # base64-encoded. + existing_crit = header.get("crit", []) + if not isinstance(existing_crit, list): + raise InvalidTokenError("Invalid 'crit' header: must be a list") + if "b64" not in existing_crit: + header["crit"] = [*existing_crit, "b64"] + elif "b64" in header: + # True is the standard value for b64, so no need for it + del header["b64"] + + json_header = json.dumps( + header, separators=(",", ":"), cls=json_encoder, sort_keys=sort_headers + ).encode() + + segments.append(base64url_encode(json_header)) + + if is_payload_detached: + msg_payload = payload + else: + msg_payload = base64url_encode(payload) + segments.append(msg_payload) + + # Segments + signing_input = b".".join(segments) + + alg_obj = self.get_algorithm_by_name(algorithm_) + if isinstance(key, PyJWK): + key = key.key + key = alg_obj.prepare_key(key) + + key_length_msg = alg_obj.check_key_length(key) + if key_length_msg: + if self.options.get("enforce_minimum_key_length", False): + raise InvalidKeyError(key_length_msg) + else: + warnings.warn(key_length_msg, InsecureKeyLengthWarning, stacklevel=2) + + signature = alg_obj.sign(signing_input, key) + + segments.append(base64url_encode(signature)) + + # Don't put the payload content inside the encoded token when detached + if is_payload_detached: + segments[1] = b"" + encoded_string = b".".join(segments) + + return encoded_string.decode("utf-8") + + def decode_complete( + self, + jwt: str | bytes, + key: AllowedPublicKeys | PyJWK | str | bytes = "", + algorithms: Sequence[str] | None = None, + options: SigOptions | None = None, + detached_payload: bytes | None = None, + **kwargs: dict[str, Any], + ) -> dict[str, Any]: + if kwargs: + warnings.warn( + "passing additional kwargs to decode_complete() is deprecated " + "and will be removed in pyjwt version 3. " + f"Unsupported kwargs: {tuple(kwargs.keys())}", + RemovedInPyjwt3Warning, + stacklevel=2, + ) + merged_options: SigOptions + if options is None: + merged_options = self.options + else: + merged_options = {**self.options, **options} + + verify_signature = merged_options["verify_signature"] + + if verify_signature and not algorithms and not isinstance(key, PyJWK): + raise DecodeError( + 'It is required that you pass in a value for the "algorithms" argument when calling decode().' + ) + + payload, signing_input, header, signature = self._load(jwt) + + self._validate_headers(header) + + if detached_payload is not None and header.get("b64", True) is not False: + raise DecodeError( + 'It is only valid to pass "detached_payload" when the protected header has "b64" set to false.' + ) + + if header.get("b64", True) is False: + # RFC 7797 §3: when "b64" is present in the protected header, + # it MUST also appear in "crit". A token that sets b64=false + # without declaring it critical is malformed. + crit = header.get("crit") or [] + if not isinstance(crit, list) or "b64" not in crit: + raise InvalidTokenError( + "The 'b64' header parameter requires 'b64' to be listed in 'crit'." + ) + if detached_payload is None: + raise DecodeError( + 'It is required that you pass in a value for the "detached_payload" argument to decode a message having the b64 header set to false.' + ) + payload = detached_payload + signing_input = b".".join([signing_input.rsplit(b".", 1)[0], payload]) + + if verify_signature: + self._verify_signature( + signing_input, + header, + signature, + key, + algorithms, + options=merged_options, + ) + + return { + "payload": payload, + "header": header, + "signature": signature, + } + + def decode( + self, + jwt: str | bytes, + key: AllowedPublicKeys | PyJWK | str | bytes = "", + algorithms: Sequence[str] | None = None, + options: SigOptions | None = None, + detached_payload: bytes | None = None, + **kwargs: dict[str, Any], + ) -> Any: + if kwargs: + warnings.warn( + "passing additional kwargs to decode() is deprecated " + "and will be removed in pyjwt version 3. " + f"Unsupported kwargs: {tuple(kwargs.keys())}", + RemovedInPyjwt3Warning, + stacklevel=2, + ) + decoded = self.decode_complete( + jwt, key, algorithms, options, detached_payload=detached_payload + ) + return decoded["payload"] + + def get_unverified_header(self, jwt: str | bytes) -> dict[str, Any]: + """Returns back the JWT header parameters as a `dict` + + Note: The signature is not verified so the header parameters + should not be fully trusted until signature verification is complete + """ + headers = self._load(jwt)[2] + self._validate_headers(headers) + + return headers + + @staticmethod + def _decode_base64url_segment(segment: bytes, name: str) -> bytes: + if len(segment) % 4 == 1 or any( + character not in _BASE64URL_ALPHABET for character in segment + ): + raise DecodeError(f"Invalid {name} padding") + + try: + decoded = base64url_decode(segment) + except (TypeError, binascii.Error) as err: + raise DecodeError(f"Invalid {name} padding") from err + + if base64url_encode(decoded) != segment: + raise DecodeError(f"Invalid {name} padding") + + return decoded + + def _load(self, jwt: str | bytes) -> tuple[bytes, bytes, dict[str, Any], bytes]: + if isinstance(jwt, str): + jwt = jwt.encode("utf-8") + + if not isinstance(jwt, bytes): + raise DecodeError(f"Invalid token type. Token must be a {bytes}") + + try: + signing_input, crypto_segment = jwt.rsplit(b".", 1) + header_segment, payload_segment = signing_input.split(b".", 1) + except ValueError as err: + raise DecodeError("Not enough segments") from err + + header_data = self._decode_base64url_segment(header_segment, "header") + + try: + header: dict[str, Any] = json.loads(header_data) + except (ValueError, RecursionError) as e: + raise DecodeError(f"Invalid header string: {e}") from e + + if not isinstance(header, dict): + raise DecodeError("Invalid header string: must be a json object") + + if header.get("b64", True) is False: + # Detached payload form (RFC 7515 Appendix F): the compact-form + # payload segment must be empty; the caller supplies the actual + # payload via the `detached_payload` argument in decode_complete. + # Skipping the base64 decode here removes an unauthenticated work + # amplifier — otherwise an attacker can inflate the unused + # segment to force CPU + memory cost before the signature is + # even checked. + if payload_segment: + raise DecodeError("Payload segment must be empty when 'b64' is false.") + payload = b"" + else: + payload = self._decode_base64url_segment(payload_segment, "payload") + + signature = self._decode_base64url_segment(crypto_segment, "crypto") + + return (payload, signing_input, header, signature) + + def _verify_signature( + self, + signing_input: bytes, + header: dict[str, Any], + signature: bytes, + key: AllowedPublicKeys | PyJWK | str | bytes = "", + algorithms: Sequence[str] | None = None, + options: SigOptions | None = None, + ) -> None: + effective_options = options if options is not None else self.options + + if algorithms is None and isinstance(key, PyJWK): + algorithms = [key.algorithm_name] + try: + alg = header["alg"] + except KeyError: + raise InvalidAlgorithmError("Algorithm not specified") from None + + if not alg or (algorithms is not None and alg not in algorithms): + raise InvalidAlgorithmError("The specified alg value is not allowed") + + if isinstance(key, PyJWK): + # The PyJWK has a fixed algorithm bound at construction time. + # Verification must use that algorithm, not whatever the token + # header advertises, otherwise the caller's allow-list check + # above degenerates into a string compare with no behavioural + # effect on which algorithm actually verifies the signature. + if alg != key.algorithm_name: + raise InvalidAlgorithmError( + f"Token algorithm {alg!r} does not match the key's " + f"algorithm {key.algorithm_name!r}" + ) + alg_obj = key.Algorithm + prepared_key = alg_obj.prepare_key(key.key) + else: + try: + alg_obj = self.get_algorithm_by_name(alg) + except NotImplementedError as e: + raise InvalidAlgorithmError("Algorithm not supported") from e + prepared_key = alg_obj.prepare_key(key) + + key_length_msg = alg_obj.check_key_length(prepared_key) + if key_length_msg: + if effective_options.get("enforce_minimum_key_length", False): + raise InvalidKeyError(key_length_msg) + else: + warnings.warn(key_length_msg, InsecureKeyLengthWarning, stacklevel=4) + + if not alg_obj.verify(signing_input, prepared_key, signature): + raise InvalidSignatureError("Signature verification failed") + + # Extensions that PyJWT actually understands and supports + _supported_crit: set[str] = {"b64"} + + def _validate_headers( + self, headers: dict[str, Any], *, encoding: bool = False + ) -> None: + if "kid" in headers: + self._validate_kid(headers["kid"]) + if not encoding and "crit" in headers: + self._validate_crit(headers) + + def _validate_kid(self, kid: Any) -> None: + if not isinstance(kid, str): + raise InvalidTokenError("Key ID header parameter must be a string") + + def _validate_crit(self, headers: dict[str, Any]) -> None: + crit = headers["crit"] + if not isinstance(crit, list) or len(crit) == 0: + raise InvalidTokenError("Invalid 'crit' header: must be a non-empty list") + for ext in crit: + if not isinstance(ext, str): + raise InvalidTokenError("Invalid 'crit' header: values must be strings") + if ext not in self._supported_crit: + raise InvalidTokenError(f"Unsupported critical extension: {ext}") + if ext not in headers: + raise InvalidTokenError( + f"Critical extension '{ext}' is missing from headers" + ) + + +_jws_global_obj = PyJWS() +encode = _jws_global_obj.encode +decode_complete = _jws_global_obj.decode_complete +decode = _jws_global_obj.decode +register_algorithm = _jws_global_obj.register_algorithm +unregister_algorithm = _jws_global_obj.unregister_algorithm +get_algorithm_by_name = _jws_global_obj.get_algorithm_by_name +get_unverified_header = _jws_global_obj.get_unverified_header diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py new file mode 100644 index 0000000..051ed34 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py @@ -0,0 +1,595 @@ +from __future__ import annotations + +import json +import os +import warnings +from calendar import timegm +from collections.abc import Container, Iterable, Sequence +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Any, Union, cast + +from .api_jws import PyJWS, _ALGORITHM_UNSET, _jws_global_obj +from .exceptions import ( + DecodeError, + ExpiredSignatureError, + ImmatureSignatureError, + InvalidAudienceError, + InvalidIssuedAtError, + InvalidIssuerError, + InvalidJTIError, + InvalidSubjectError, + MissingRequiredClaimError, +) +from .warnings import RemovedInPyjwt3Warning + +if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")): + import sys + + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + # Python 3.9 and lower + from typing_extensions import TypeAlias + + from .algorithms import AllowedPrivateKeys, AllowedPublicKeys + from .api_jwk import PyJWK + from .types import FullOptions, Options, SigOptions + + AllowedPrivateKeyTypes: TypeAlias = Union[AllowedPrivateKeys, PyJWK, str, bytes] + AllowedPublicKeyTypes: TypeAlias = Union[AllowedPublicKeys, PyJWK, str, bytes] + + +class PyJWT: + def __init__(self, options: Options | None = None) -> None: + self.options: FullOptions + self.options = self._get_default_options() + if options is not None: + self.options = self._merge_options(options) + + self._jws = PyJWS(options=self._get_sig_options()) + + @staticmethod + def _get_default_options() -> FullOptions: + return { + "verify_signature": True, + "verify_exp": True, + "verify_nbf": True, + "verify_iat": True, + "verify_aud": True, + "verify_iss": True, + "verify_sub": True, + "verify_jti": True, + "require": [], + "strict_aud": False, + "enforce_minimum_key_length": False, + } + + def _get_sig_options(self) -> SigOptions: + return { + "verify_signature": self.options["verify_signature"], + "enforce_minimum_key_length": self.options.get( + "enforce_minimum_key_length", False + ), + } + + def _merge_options(self, options: Options | None = None) -> FullOptions: + if options is None: + return self.options + + merged_options = cast("Options", dict(options)) + + # (defensive) set defaults for verify_x to False if verify_signature is False + if not merged_options.get("verify_signature", True): + merged_options["verify_exp"] = merged_options.get("verify_exp", False) + merged_options["verify_nbf"] = merged_options.get("verify_nbf", False) + merged_options["verify_iat"] = merged_options.get("verify_iat", False) + merged_options["verify_aud"] = merged_options.get("verify_aud", False) + merged_options["verify_iss"] = merged_options.get("verify_iss", False) + merged_options["verify_sub"] = merged_options.get("verify_sub", False) + merged_options["verify_jti"] = merged_options.get("verify_jti", False) + return {**self.options, **merged_options} + + def encode( + self, + payload: dict[str, Any], + key: AllowedPrivateKeyTypes, + algorithm: str | None = _ALGORITHM_UNSET, # type: ignore[assignment] + headers: dict[str, Any] | None = None, + json_encoder: type[json.JSONEncoder] | None = None, + sort_headers: bool = True, + ) -> str: + """Encode the ``payload`` as JSON Web Token. + + :param payload: JWT claims, e.g. ``dict(iss=..., aud=..., sub=...)`` + :type payload: dict[str, typing.Any] + :param key: a key suitable for the chosen algorithm: + + * for **asymmetric algorithms**: PEM-formatted private key, a multiline string + * for **symmetric algorithms**: plain string, sufficiently long for security + + :type key: str or bytes or PyJWK or :py:class:`jwt.algorithms.AllowedPrivateKeys` + :param algorithm: algorithm to sign the token with, e.g. ``"ES256"``. + If ``headers`` includes ``alg``, it will be preferred to this parameter. + If ``key`` is a :class:`PyJWK` object, by default the key algorithm will be used. + :type algorithm: str or None + :param headers: additional JWT header fields, e.g. ``dict(kid="my-key-id")``. + :type headers: dict[str, typing.Any] or None + :param json_encoder: custom JSON encoder for ``payload`` and ``headers`` + :type json_encoder: json.JSONEncoder or None + + :rtype: str + :returns: a JSON Web Token + + :raises TypeError: if ``payload`` is not a ``dict`` + """ + # Check that we get a dict + if not isinstance(payload, dict): + raise TypeError( + "Expecting a dict object, as JWT only supports " + "JSON objects as payloads." + ) + + # Payload + payload = payload.copy() + for time_claim in ["exp", "iat", "nbf"]: + # Convert datetime to a intDate value in known time-format claims + if isinstance(payload.get(time_claim), datetime): + payload[time_claim] = timegm(payload[time_claim].utctimetuple()) + + # Issue #1039, iss being set to non-string + if "iss" in payload and not isinstance(payload["iss"], str): + raise TypeError("Issuer (iss) must be a string.") + + json_payload = self._encode_payload( + payload, + headers=headers, + json_encoder=json_encoder, + ) + + return self._jws.encode( + json_payload, + key, + algorithm, + headers, + json_encoder, + sort_headers=sort_headers, + ) + + def _encode_payload( + self, + payload: dict[str, Any], + headers: dict[str, Any] | None = None, + json_encoder: type[json.JSONEncoder] | None = None, + ) -> bytes: + """ + Encode a given payload to the bytes to be signed. + + This method is intended to be overridden by subclasses that need to + encode the payload in a different way, e.g. compress the payload. + """ + return json.dumps( + payload, + separators=(",", ":"), + cls=json_encoder, + ).encode("utf-8") + + def decode_complete( + self, + jwt: str | bytes, + key: AllowedPublicKeyTypes = "", + algorithms: Sequence[str] | None = None, + options: Options | None = None, + # deprecated arg, remove in pyjwt3 + verify: bool | None = None, + # could be used as passthrough to api_jws, consider removal in pyjwt3 + detached_payload: bytes | None = None, + # passthrough arguments to _validate_claims + # consider putting in options + audience: str | Iterable[str] | None = None, + issuer: str | Container[str] | None = None, + subject: str | None = None, + leeway: float | timedelta = 0, + # kwargs + **kwargs: Any, + ) -> dict[str, Any]: + """Identical to ``jwt.decode`` except for return value which is a dictionary containing the token header (JOSE Header), + the token payload (JWT Payload), and token signature (JWT Signature) on the keys "header", "payload", + and "signature" respectively. + + :param jwt: the token to be decoded + :type jwt: str or bytes + :param key: the key suitable for the allowed algorithm + :type key: str or bytes or PyJWK or :py:class:`jwt.algorithms.AllowedPublicKeys` + + :param algorithms: allowed algorithms, e.g. ``["ES256"]`` + + .. warning:: + + Do **not** compute the ``algorithms`` parameter based on + the ``alg`` from the token itself, or on any other data + that an attacker may be able to influence, as that might + expose you to various vulnerabilities (see `RFC 8725 §2.1 + `_). Instead, + either hard-code a fixed value for ``algorithms``, or + configure it in the same place you configure the + ``key``. Make sure not to mix symmetric and asymmetric + algorithms that interpret the ``key`` in different ways + (e.g. HS\\* and RS\\*). + :type algorithms: typing.Sequence[str] or None + + :param jwt.types.Options options: extended decoding and validation options + Refer to :py:class:`jwt.types.Options` for more information. + + :param audience: optional, the value for ``verify_aud`` check + :type audience: str or typing.Iterable[str] or None + :param issuer: optional, the value for ``verify_iss`` check + :type issuer: str or typing.Container[str] or None + :param leeway: a time margin in seconds for the expiration check + :type leeway: float or datetime.timedelta + :rtype: dict[str, typing.Any] + :returns: Decoded JWT with the JOSE Header on the key ``header``, the JWS + Payload on the key ``payload``, and the JWS Signature on the key ``signature``. + """ + if kwargs: + warnings.warn( + "passing additional kwargs to decode_complete() is deprecated " + "and will be removed in pyjwt version 3. " + f"Unsupported kwargs: {tuple(kwargs.keys())}", + RemovedInPyjwt3Warning, + stacklevel=2, + ) + + if options is None: + verify_signature = True + else: + verify_signature = options.get("verify_signature", True) + + # If the user has set the legacy `verify` argument, and it doesn't match + # what the relevant `options` entry for the argument is, inform the user + # that they're likely making a mistake. + if verify is not None and verify != verify_signature: + warnings.warn( + "The `verify` argument to `decode` does nothing in PyJWT 2.0 and newer. " + "The equivalent is setting `verify_signature` to False in the `options` dictionary. " + "This invocation has a mismatch between the kwarg and the option entry.", + category=DeprecationWarning, + stacklevel=2, + ) + + merged_options = self._merge_options(options) + + sig_options: SigOptions = { + "verify_signature": verify_signature, + "enforce_minimum_key_length": merged_options.get( + "enforce_minimum_key_length", False + ), + } + decoded = self._jws.decode_complete( + jwt, + key=key, + algorithms=algorithms, + options=sig_options, + detached_payload=detached_payload, + ) + + payload = self._decode_payload(decoded) + + self._validate_claims( + payload, + merged_options, + audience=audience, + issuer=issuer, + leeway=leeway, + subject=subject, + ) + + decoded["payload"] = payload + return decoded + + def _decode_payload(self, decoded: dict[str, Any]) -> dict[str, Any]: + """ + Decode the payload from a JWS dictionary (payload, signature, header). + + This method is intended to be overridden by subclasses that need to + decode the payload in a different way, e.g. decompress compressed + payloads. + """ + try: + payload: dict[str, Any] = json.loads(decoded["payload"]) + except ValueError as e: + raise DecodeError(f"Invalid payload string: {e}") from e + if not isinstance(payload, dict): + raise DecodeError("Invalid payload string: must be a json object") + return payload + + def decode( + self, + jwt: str | bytes, + key: AllowedPublicKeys | PyJWK | str | bytes = "", + algorithms: Sequence[str] | None = None, + options: Options | None = None, + # deprecated arg, remove in pyjwt3 + verify: bool | None = None, + # could be used as passthrough to api_jws, consider removal in pyjwt3 + detached_payload: bytes | None = None, + # passthrough arguments to _validate_claims + # consider putting in options + audience: str | Iterable[str] | None = None, + subject: str | None = None, + issuer: str | Container[str] | None = None, + leeway: float | timedelta = 0, + # kwargs + **kwargs: Any, + ) -> dict[str, Any]: + """Verify the ``jwt`` token signature and return the token claims. + + :param jwt: the token to be decoded + :type jwt: str or bytes + :param key: the key suitable for the allowed algorithm + :type key: str or bytes or PyJWK or :py:class:`jwt.algorithms.AllowedPublicKeys` + + :param algorithms: allowed algorithms, e.g. ``["ES256"]`` + If ``key`` is a :class:`PyJWK` object, allowed algorithms will default to the key algorithm. + + .. warning:: + + Do **not** compute the ``algorithms`` parameter based on + the ``alg`` from the token itself, or on any other data + that an attacker may be able to influence, as that might + expose you to various vulnerabilities (see `RFC 8725 §2.1 + `_). Instead, + either hard-code a fixed value for ``algorithms``, or + configure it in the same place you configure the + ``key``. Make sure not to mix symmetric and asymmetric + algorithms that interpret the ``key`` in different ways + (e.g. HS\\* and RS\\*). + :type algorithms: typing.Sequence[str] or None + + :param jwt.types.Options options: extended decoding and validation options + Refer to :py:class:`jwt.types.Options` for more information. + + :param audience: optional, the value for ``verify_aud`` check + :type audience: str or typing.Iterable[str] or None + :param subject: optional, the value for ``verify_sub`` check + :type subject: str or None + :param issuer: optional, the value for ``verify_iss`` check + :type issuer: str or typing.Container[str] or None + :param leeway: a time margin in seconds for the expiration check + :type leeway: float or datetime.timedelta + :rtype: dict[str, typing.Any] + :returns: the JWT claims + """ + if kwargs: + warnings.warn( + "passing additional kwargs to decode() is deprecated " + "and will be removed in pyjwt version 3. " + f"Unsupported kwargs: {tuple(kwargs.keys())}", + RemovedInPyjwt3Warning, + stacklevel=2, + ) + decoded = self.decode_complete( + jwt, + key, + algorithms, + options, + verify=verify, + detached_payload=detached_payload, + audience=audience, + subject=subject, + issuer=issuer, + leeway=leeway, + ) + return cast(dict[str, Any], decoded["payload"]) + + def _validate_claims( + self, + payload: dict[str, Any], + options: FullOptions, + audience: Iterable[str] | str | None = None, + issuer: Container[str] | str | None = None, + subject: str | None = None, + leeway: float | timedelta = 0, + ) -> None: + if isinstance(leeway, timedelta): + leeway = leeway.total_seconds() + + if audience is not None and not isinstance(audience, (str, Iterable)): + raise TypeError("audience must be a string, iterable or None") + + self._validate_required_claims(payload, options["require"]) + + now = datetime.now(tz=timezone.utc).timestamp() + + if "iat" in payload and options["verify_iat"]: + self._validate_iat(payload, now, leeway) + + if "nbf" in payload and options["verify_nbf"]: + self._validate_nbf(payload, now, leeway) + + if "exp" in payload and options["verify_exp"]: + self._validate_exp(payload, now, leeway) + + if options["verify_iss"]: + self._validate_iss(payload, issuer) + + if options["verify_aud"]: + self._validate_aud( + payload, audience, strict=options.get("strict_aud", False) + ) + + if options["verify_sub"]: + self._validate_sub(payload, subject) + + if options["verify_jti"]: + self._validate_jti(payload) + + def _validate_required_claims( + self, + payload: dict[str, Any], + claims: Iterable[str], + ) -> None: + for claim in claims: + if payload.get(claim) is None: + raise MissingRequiredClaimError(claim) + + def _validate_sub( + self, payload: dict[str, Any], subject: str | None = None + ) -> None: + """ + Checks whether "sub" if in the payload is valid or not. + This is an Optional claim + + :param payload(dict): The payload which needs to be validated + :param subject(str): The subject of the token + """ + + if "sub" not in payload: + return + + if not isinstance(payload["sub"], str): + raise InvalidSubjectError("Subject must be a string") + + if subject is not None: + if payload.get("sub") != subject: + raise InvalidSubjectError("Invalid subject") + + def _validate_jti(self, payload: dict[str, Any]) -> None: + """ + Checks whether "jti" if in the payload is valid or not + This is an Optional claim + + :param payload(dict): The payload which needs to be validated + """ + + if "jti" not in payload: + return + + if not isinstance(payload.get("jti"), str): + raise InvalidJTIError("JWT ID must be a string") + + def _validate_iat( + self, + payload: dict[str, Any], + now: float, + leeway: float, + ) -> None: + try: + iat = int(payload["iat"]) + except ValueError: + raise InvalidIssuedAtError( + "Issued At claim (iat) must be an integer." + ) from None + if iat > (now + leeway): + raise ImmatureSignatureError("The token is not yet valid (iat)") + + def _validate_nbf( + self, + payload: dict[str, Any], + now: float, + leeway: float, + ) -> None: + try: + nbf = int(payload["nbf"]) + except ValueError: + raise DecodeError("Not Before claim (nbf) must be an integer.") from None + + if nbf > (now + leeway): + raise ImmatureSignatureError("The token is not yet valid (nbf)") + + def _validate_exp( + self, + payload: dict[str, Any], + now: float, + leeway: float, + ) -> None: + try: + exp = int(payload["exp"]) + except ValueError: + raise DecodeError( + "Expiration Time claim (exp) must be an integer." + ) from None + + if exp <= (now - leeway): + raise ExpiredSignatureError("Signature has expired") + + def _validate_aud( + self, + payload: dict[str, Any], + audience: str | Iterable[str] | None, + *, + strict: bool = False, + ) -> None: + if audience is None: + if "aud" not in payload or not payload["aud"]: + return + # Application did not specify an audience, but + # the token has the 'aud' claim + raise InvalidAudienceError("Invalid audience") + + if "aud" not in payload or not payload["aud"]: + # Application specified an audience, but it could not be + # verified since the token does not contain a claim. + raise MissingRequiredClaimError("aud") + + audience_claims = payload["aud"] + + # In strict mode, we forbid list matching: the supplied audience + # must be a string, and it must exactly match the audience claim. + if strict: + # Only a single audience is allowed in strict mode. + if not isinstance(audience, str): + raise InvalidAudienceError("Invalid audience (strict)") + + # Only a single audience claim is allowed in strict mode. + if not isinstance(audience_claims, str): + raise InvalidAudienceError("Invalid claim format in token (strict)") + + if audience != audience_claims: + raise InvalidAudienceError("Audience doesn't match (strict)") + + return + + if isinstance(audience_claims, str): + audience_claims = [audience_claims] + if not isinstance(audience_claims, list): + raise InvalidAudienceError("Invalid claim format in token") + if any(not isinstance(c, str) for c in audience_claims): + raise InvalidAudienceError("Invalid claim format in token") + + if isinstance(audience, str): + audience = [audience] + + if all(aud not in audience_claims for aud in audience): + raise InvalidAudienceError("Audience doesn't match") + + def _validate_iss( + self, payload: dict[str, Any], issuer: Container[str] | str | None + ) -> None: + if issuer is None: + return + + if "iss" not in payload: + raise MissingRequiredClaimError("iss") + + iss = payload["iss"] + if not isinstance(iss, str): + raise InvalidIssuerError("Payload Issuer (iss) must be a string") + + if isinstance(issuer, str): + if iss != issuer: + raise InvalidIssuerError("Invalid issuer") + else: + try: + if iss not in issuer: + raise InvalidIssuerError("Invalid issuer") + except TypeError: + raise InvalidIssuerError( + 'Issuer param must be "str" or "Container[str]"' + ) from None + + +_jwt_global_obj = PyJWT() +_jwt_global_obj._jws = _jws_global_obj +encode = _jwt_global_obj.encode +decode_complete = _jwt_global_obj.decode_complete +decode = _jwt_global_obj.decode diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py new file mode 100644 index 0000000..57559fc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py @@ -0,0 +1,113 @@ +class PyJWTError(Exception): + """ + Base class for all exceptions + """ + + pass + + +class InvalidTokenError(PyJWTError): + """Base exception when ``decode()`` fails on a token""" + + pass + + +class DecodeError(InvalidTokenError): + """Raised when a token cannot be decoded because it failed validation""" + + pass + + +class InvalidSignatureError(DecodeError): + """Raised when a token's signature doesn't match the one provided as part of + the token.""" + + pass + + +class ExpiredSignatureError(InvalidTokenError): + """Raised when a token's ``exp`` claim indicates that it has expired""" + + pass + + +class InvalidAudienceError(InvalidTokenError): + """Raised when a token's ``aud`` claim does not match one of the expected + audience values""" + + pass + + +class InvalidIssuerError(InvalidTokenError): + """Raised when a token's ``iss`` claim does not match the expected issuer""" + + pass + + +class InvalidIssuedAtError(InvalidTokenError): + """Raised when a token's ``iat`` claim is non-numeric""" + + pass + + +class ImmatureSignatureError(InvalidTokenError): + """Raised when a token's ``nbf`` or ``iat`` claims represent a time in the future""" + + pass + + +class InvalidKeyError(PyJWTError): + """Raised when the specified key is not in the proper format""" + + pass + + +class InvalidAlgorithmError(InvalidTokenError): + """Raised when the specified algorithm is not recognized by PyJWT""" + + pass + + +class MissingRequiredClaimError(InvalidTokenError): + """Raised when a claim that is required to be present is not contained + in the claimset""" + + def __init__(self, claim: str) -> None: + self.claim = claim + + def __str__(self) -> str: + return f'Token is missing the "{self.claim}" claim' + + +class PyJWKError(PyJWTError): + pass + + +class MissingCryptographyError(PyJWKError): + """Raised if the algorithm requires ``cryptography`` to be installed and it is not available.""" + + pass + + +class PyJWKSetError(PyJWTError): + pass + + +class PyJWKClientError(PyJWTError): + pass + + +class PyJWKClientConnectionError(PyJWKClientError): + pass + + +class InvalidSubjectError(InvalidTokenError): + """Raised when a token's ``sub`` claim is not a string or doesn't match the expected ``subject``""" + + pass + + +class InvalidJTIError(InvalidTokenError): + """Raised when a token's ``jti`` claim is not a string""" + + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py new file mode 100644 index 0000000..2f42b5d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/help.py @@ -0,0 +1,66 @@ +import json +import platform +import sys + +from . import __version__ as pyjwt_version + +try: + import cryptography + + cryptography_version = cryptography.__version__ +except ModuleNotFoundError: + cryptography_version = "" + + +def info() -> dict[str, dict[str, str]]: + """ + Generate information for a bug report. + Based on the requests package help utility module. + """ + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = {"system": "Unknown", "release": "Unknown"} + + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + pypy_version_info = sys.pypy_version_info # type: ignore[attr-defined] + implementation_version = ( + f"{pypy_version_info.major}." + f"{pypy_version_info.minor}." + f"{pypy_version_info.micro}" + ) + if pypy_version_info.releaselevel != "final": + implementation_version = "".join( + [ + implementation_version, + pypy_version_info.releaselevel, + ] + ) + else: + implementation_version = "Unknown" + + return { + "platform": platform_info, + "implementation": { + "name": implementation, + "version": implementation_version, + }, + "cryptography": {"version": cryptography_version}, + "pyjwt": {"version": pyjwt_version}, + } + + +def main() -> None: + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py new file mode 100644 index 0000000..f3f3ca5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py @@ -0,0 +1,31 @@ +import time +from typing import Optional + +from .api_jwk import PyJWKSet, PyJWTSetWithTimestamp + + +class JWKSetCache: + def __init__(self, lifespan: float) -> None: + self.jwk_set_with_timestamp: Optional[PyJWTSetWithTimestamp] = None + self.lifespan = lifespan + + def put(self, jwk_set: PyJWKSet) -> None: + if jwk_set is not None: + self.jwk_set_with_timestamp = PyJWTSetWithTimestamp(jwk_set) + else: + # clear cache + self.jwk_set_with_timestamp = None + + def get(self) -> Optional[PyJWKSet]: + if self.jwk_set_with_timestamp is None or self.is_expired(): + return None + + return self.jwk_set_with_timestamp.get_jwk_set() + + def is_expired(self) -> bool: + return ( + self.jwk_set_with_timestamp is not None + and self.lifespan > -1 + and time.monotonic() + > self.jwk_set_with_timestamp.get_timestamp() + self.lifespan + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py new file mode 100644 index 0000000..4ef34ce --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py @@ -0,0 +1,302 @@ +from __future__ import annotations + +import json +import math +import threading +import time +import urllib.request +from functools import lru_cache +from ssl import SSLContext +from typing import Any +from urllib.error import HTTPError, URLError +from urllib.parse import urlparse + +from .api_jwk import PyJWK, PyJWKSet +from .api_jwt import decode_complete as decode_token +from .exceptions import PyJWKClientConnectionError, PyJWKClientError +from .jwk_set_cache import JWKSetCache + + +class _NoRedirectHandler(urllib.request.HTTPRedirectHandler): + def redirect_request( + self, + req: urllib.request.Request, + fp: Any, + code: int, + msg: str, + headers: Any, + newurl: str, + ) -> urllib.request.Request | None: + return None + + +class PyJWKClient: + def __init__( + self, + uri: str, + cache_keys: bool = False, + max_cached_keys: int = 16, + cache_jwk_set: bool = True, + lifespan: float = 300, + headers: dict[str, Any] | None = None, + timeout: float = 30, + ssl_context: SSLContext | None = None, + cooldown_duration: float = 30, + ): + """A client for retrieving signing keys from a JWKS endpoint. + + ``PyJWKClient`` uses a two-tier caching system to avoid unnecessary + network requests: + + **Tier 1 — JWK Set cache** (enabled by default): + Caches the entire JSON Web Key Set response from the endpoint. + Controlled by: + + - ``cache_jwk_set``: Set to ``True`` (the default) to enable this + cache. When enabled, the JWK Set is fetched from the network only + when the cache is empty or expired. + - ``lifespan``: Time in seconds before the cached JWK Set expires. + Defaults to ``300`` (5 minutes). Must be greater than 0. + + Unknown key IDs can trigger one forced refresh after the cooldown + period configured by ``cooldown_duration``. Every successful fetch + starts this cooldown, including the initial fetch and cache-expiry + fetches. A newly rotated key may therefore wait for the cooldown + period before it is fetched; set ``cooldown_duration`` to ``0`` to + disable this behavior. The cooldown is bypassed when + ``cache_jwk_set`` is ``False``. + + **Tier 2 — Signing key cache** (disabled by default): + Caches individual signing keys (looked up by ``kid``) using an LRU + cache with **no time-based expiration**. Keys are evicted only when + the cache reaches its maximum size. Controlled by: + + - ``cache_keys``: Set to ``True`` to enable this cache. + Defaults to ``False``. + - ``max_cached_keys``: Maximum number of signing keys to keep in + the LRU cache. Defaults to ``16``. + + :param uri: The URL of the JWKS endpoint. + :type uri: str + :param cache_keys: Enable the per-key LRU cache (Tier 2). + :type cache_keys: bool + :param max_cached_keys: Max entries in the signing key LRU cache. + :type max_cached_keys: int + :param cache_jwk_set: Enable the JWK Set response cache (Tier 1). + :type cache_jwk_set: bool + :param lifespan: TTL in seconds for the JWK Set cache. + :type lifespan: float + :param headers: Optional HTTP headers to include in requests. + :type headers: dict or None + :param timeout: HTTP request timeout in seconds. + :type timeout: float + :param ssl_context: Optional SSL context for the request. + :type ssl_context: ssl.SSLContext or None + :param cooldown_duration: Minimum time in seconds between forced + refreshes after an unknown key ID. Defaults to ``30``. + :type cooldown_duration: float + """ + if headers is None: + headers = {} + # urllib's default OpenerDirector also handles file://, ftp://, and + # data: URIs. Reject anything that isn't http(s) eagerly so a caller + # passing an attacker-influenced URL (e.g. taken from a `jku` token + # header) can't read local files or reach other unintended schemes. + scheme = urlparse(uri).scheme.lower() + if scheme not in ("http", "https"): + raise PyJWKClientError( + f"Invalid JWKS URI scheme {scheme!r}: only 'http' and 'https' " + f"are supported." + ) + self.uri = uri + self.jwk_set_cache: JWKSetCache | None = None + self.headers = headers + self.timeout = timeout + self.ssl_context = ssl_context + if cooldown_duration < 0: + raise PyJWKClientError( + "Cooldown duration must be greater than or equal to 0, " + f'the input is "{cooldown_duration}"' + ) + if not math.isfinite(cooldown_duration): + raise PyJWKClientError( + f'Cooldown duration must be finite, the input is "{cooldown_duration}"' + ) + self.cooldown_duration = cooldown_duration + self._last_successful_fetch: float | None = None + self._client_lock = threading.RLock() + + if cache_jwk_set: + # Init jwt set cache with default or given lifespan. + # Default lifespan is 300 seconds (5 minutes). + if lifespan <= 0: + raise PyJWKClientError( + f'Lifespan must be greater than 0, the input is "{lifespan}"' + ) + self.jwk_set_cache = JWKSetCache(lifespan) + else: + self.jwk_set_cache = None + + if cache_keys: + # Cache signing keys + get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key) + # Ignore mypy (https://github.com/python/mypy/issues/2427) + self.get_signing_key = get_signing_key # type: ignore[method-assign] + + def fetch_data(self) -> Any: + """Fetch the JWK Set from the JWKS endpoint. + + Makes an HTTP request to the configured ``uri`` and returns the + parsed JSON response. If the JWK Set cache is enabled, the + response is stored in the cache. + + :returns: The parsed JWK Set as a dictionary. + :raises PyJWKClientConnectionError: If the HTTP request fails. + """ + try: + r = urllib.request.Request(url=self.uri, headers=self.headers) + handlers: list[Any] = [_NoRedirectHandler()] + if self.ssl_context is not None: + handlers.append(urllib.request.HTTPSHandler(context=self.ssl_context)) + opener = urllib.request.build_opener(*handlers) + with opener.open(r, timeout=self.timeout) as response: + jwk_set = json.load(response) + except (URLError, TimeoutError) as e: + if isinstance(e, HTTPError): + e.close() + raise PyJWKClientConnectionError( + f'Fail to fetch data from the url, err: "{e}"' + ) from e + + # Only update the cache on a successful fetch. Writing in a + # `finally` block with `jwk_set=None` on error clears any + # previously-cached JWKS, turning a transient outage into a cache + # wipe that breaks legitimate auth. + if self.jwk_set_cache is not None: + self.jwk_set_cache.put(jwk_set) + self._last_successful_fetch = time.monotonic() + return jwk_set + + def get_jwk_set(self, refresh: bool = False) -> PyJWKSet: + """Return the JWK Set, using the cache when available. + + :param refresh: Force a fresh fetch from the endpoint, bypassing + the cache. + :type refresh: bool + :returns: The JWK Set. + :rtype: PyJWKSet + :raises PyJWKClientError: If the endpoint does not return a JSON + object. + """ + data = None + if self.jwk_set_cache is not None and not refresh: + data = self.jwk_set_cache.get() + + if data is None: + data = self.fetch_data() + + if not isinstance(data, dict): + raise PyJWKClientError("The JWKS endpoint did not return a JSON object") + + return PyJWKSet.from_dict(data) + + def get_signing_keys(self, refresh: bool = False) -> list[PyJWK]: + """Return all signing keys from the JWK Set. + + Filters the JWK Set to keys whose ``use`` is ``"sig"`` (or + unspecified) and that have a ``kid``. + + :param refresh: Force a fresh fetch from the endpoint, bypassing + the cache. + :type refresh: bool + :returns: A list of signing keys. + :rtype: list[PyJWK] + :raises PyJWKClientError: If no signing keys are found. + """ + jwk_set = self.get_jwk_set(refresh) + return self._get_signing_keys_from_jwk_set(jwk_set) + + @staticmethod + def _get_signing_keys_from_jwk_set(jwk_set: PyJWKSet) -> list[PyJWK]: + signing_keys = [ + jwk_set_key + for jwk_set_key in jwk_set.keys + if jwk_set_key.public_key_use in ["sig", None] and jwk_set_key.key_id + ] + + if not signing_keys: + raise PyJWKClientError("The JWKS endpoint did not contain any signing keys") + + return signing_keys + + def get_signing_key(self, kid: str) -> PyJWK: + """Return the signing key matching the given ``kid``. + + If no match is found in the current JWK Set, the set is refreshed + from the endpoint and the lookup is retried once when the refresh + cooldown permits it. + + :param kid: The key ID to look up. + :type kid: str + :returns: The matching signing key. + :rtype: PyJWK + :raises PyJWKClientError: If no matching key is found after + refreshing. + """ + with self._client_lock: + signing_keys = self.get_signing_keys() + signing_key = self.match_kid(signing_keys, kid) + + if not signing_key: + cooling_down = ( + self.jwk_set_cache is not None + and self._last_successful_fetch is not None + and time.monotonic() - self._last_successful_fetch + < self.cooldown_duration + ) + if not cooling_down: + signing_keys = self.get_signing_keys(refresh=True) + self._last_successful_fetch = time.monotonic() + signing_key = self.match_kid(signing_keys, kid) + + if not signing_key: + raise PyJWKClientError( + f'Unable to find a signing key that matches: "{kid}"' + ) + + return signing_key + + def get_signing_key_from_jwt(self, token: str | bytes) -> PyJWK: + """Return the signing key for a JWT by reading its ``kid`` header. + + Extracts the ``kid`` from the token's unverified header and + delegates to :meth:`get_signing_key`. + + :param token: The encoded JWT. + :type token: str or bytes + :returns: The matching signing key. + :rtype: PyJWK + """ + unverified = decode_token(token, options={"verify_signature": False}) + header = unverified["header"] + return self.get_signing_key(header.get("kid")) + + @staticmethod + def match_kid(signing_keys: list[PyJWK], kid: str) -> PyJWK | None: + """Find a key in *signing_keys* that matches *kid*. + + :param signing_keys: The list of keys to search. + :type signing_keys: list[PyJWK] + :param kid: The key ID to match. + :type kid: str + :returns: The matching key, or ``None`` if not found. + :rtype: PyJWK or None + """ + signing_key = None + + for key in signing_keys: + if key.key_id == kid: + signing_key = key + break + + return signing_key diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py new file mode 100644 index 0000000..61bfdf0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/types.py @@ -0,0 +1,69 @@ +from typing import Any, Callable, TypedDict + +JWKDict = dict[str, Any] + +HashlibHash = Callable[..., Any] + + +class SigOptions(TypedDict, total=False): + """Options for PyJWS class (TypedDict). Note that this is a smaller set of options than + for :py:func:`jwt.decode()`.""" + + verify_signature: bool + """verify the JWT cryptographic signature""" + enforce_minimum_key_length: bool + """Default: ``False``. Raise :py:class:`jwt.exceptions.InvalidKeyError` instead of warning when keys are below minimum recommended length.""" + + +class Options(TypedDict, total=False): + """Options for :py:func:`jwt.decode()` and :py:func:`jwt.decode_complete()` (TypedDict). + + .. warning:: + + Some claims, such as ``exp``, ``iat``, ``jti``, ``nbf``, and ``sub``, + will only be verified if present. Please refer to the documentation below + for which ones, and make sure to include them in the ``require`` param + if you want to make sure that they are always present (and therefore always verified + if ``verify_{claim} = True`` for that claim). + """ + + verify_signature: bool + """Default: ``True``. Verify the JWT cryptographic signature.""" + require: list[str] + """Default: ``[]``. List of claims that must be present. + Example: ``require=["exp", "iat", "nbf"]``. + **Only verifies that the claims exists**. Does not verify that the claims are valid.""" + strict_aud: bool + """Default: ``False``. (requires ``verify_aud=True``) Check that the ``aud`` claim is a single value (not a list), and matches ``audience`` exactly.""" + verify_aud: bool + """Default: ``verify_signature``. Check that ``aud`` (audience) claim matches ``audience``.""" + verify_exp: bool + """Default: ``verify_signature``. Check that ``exp`` (expiration) claim value is in the future (if present in payload). """ + verify_iat: bool + """Default: ``verify_signature``. Check that ``iat`` (issued at) claim value is an integer (if present in payload). """ + verify_iss: bool + """Default: ``verify_signature``. Check that ``iss`` (issuer) claim matches ``issuer``. """ + verify_jti: bool + """Default: ``verify_signature``. Check that ``jti`` (JWT ID) claim is a string (if present in payload). """ + verify_nbf: bool + """Default: ``verify_signature``. Check that ``nbf`` (not before) claim value is in the past (if present in payload). """ + verify_sub: bool + """Default: ``verify_signature``. Check that ``sub`` (subject) claim is a string and matches ``subject`` (if present in payload). """ + enforce_minimum_key_length: bool + """Default: ``False``. Raise :py:class:`jwt.exceptions.InvalidKeyError` instead of warning when keys are below minimum recommended length.""" + + +# The only difference between Options and FullOptions is that FullOptions +# required _every_ value to be there; Options doesn't require any +class FullOptions(TypedDict): + verify_signature: bool + require: list[str] + strict_aud: bool + verify_aud: bool + verify_exp: bool + verify_iat: bool + verify_iss: bool + verify_jti: bool + verify_nbf: bool + verify_sub: bool + enforce_minimum_key_length: bool diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py new file mode 100644 index 0000000..914cb50 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/utils.py @@ -0,0 +1,146 @@ +import base64 +import binascii +import re +from typing import Optional, Union + +try: + from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve + from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature, + encode_dss_signature, + ) +except ModuleNotFoundError: + pass + + +def force_bytes(value: Union[bytes, str]) -> bytes: + if isinstance(value, str): + return value.encode("utf-8") + elif isinstance(value, bytes): + return value + else: + raise TypeError("Expected a string value") + + +def base64url_decode(input: Union[bytes, str]) -> bytes: + input_bytes = force_bytes(input) + + rem = len(input_bytes) % 4 + + if rem > 0: + input_bytes += b"=" * (4 - rem) + + return base64.urlsafe_b64decode(input_bytes) + + +def base64url_encode(input: bytes) -> bytes: + return base64.urlsafe_b64encode(input).replace(b"=", b"") + + +def to_base64url_uint(val: int, *, bit_length: Optional[int] = None) -> bytes: + if val < 0: + raise ValueError("Must be a positive integer") + + int_bytes = bytes_from_int(val, bit_length=bit_length) + + if len(int_bytes) == 0: + int_bytes = b"\x00" + + return base64url_encode(int_bytes) + + +def from_base64url_uint(val: Union[bytes, str]) -> int: + data = base64url_decode(force_bytes(val)) + return int.from_bytes(data, byteorder="big") + + +def number_to_bytes(num: int, num_bytes: int) -> bytes: + padded_hex = "%0*x" % (2 * num_bytes, num) + return binascii.a2b_hex(padded_hex.encode("ascii")) + + +def bytes_to_number(string: bytes) -> int: + return int(binascii.b2a_hex(string), 16) + + +def bytes_from_int(val: int, *, bit_length: Optional[int] = None) -> bytes: + if bit_length is None: + bit_length = val.bit_length() + byte_length = (bit_length + 7) // 8 + + return val.to_bytes(byte_length, "big", signed=False) + + +def der_to_raw_signature(der_sig: bytes, curve: "EllipticCurve") -> bytes: + num_bits = curve.key_size + num_bytes = (num_bits + 7) // 8 + + r, s = decode_dss_signature(der_sig) + + return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes) + + +def raw_to_der_signature(raw_sig: bytes, curve: "EllipticCurve") -> bytes: + num_bits = curve.key_size + num_bytes = (num_bits + 7) // 8 + + if len(raw_sig) != 2 * num_bytes: + raise ValueError("Invalid signature") + + r = bytes_to_number(raw_sig[:num_bytes]) + s = bytes_to_number(raw_sig[num_bytes:]) + + return bytes(encode_dss_signature(r, s)) + + +# Based on https://github.com/hynek/pem/blob/7ad94db26b0bc21d10953f5dbad3acfdfacf57aa/src/pem/_core.py#L224-L252 +_PEMS = { + b"CERTIFICATE", + b"TRUSTED CERTIFICATE", + b"PRIVATE KEY", + b"PUBLIC KEY", + b"ENCRYPTED PRIVATE KEY", + b"OPENSSH PRIVATE KEY", + b"DSA PRIVATE KEY", + b"RSA PRIVATE KEY", + b"RSA PUBLIC KEY", + b"EC PRIVATE KEY", + b"DH PARAMETERS", + b"NEW CERTIFICATE REQUEST", + b"CERTIFICATE REQUEST", + b"SSH2 PUBLIC KEY", + b"SSH2 ENCRYPTED PRIVATE KEY", + b"X509 CRL", +} + +# Accept PEM formatting variants that the cryptography loader accepts, while +# retaining the explicit asymmetric-key marker check. +_PEM_MARKER_RE = re.compile( + b"(?=(----[- ](BEGIN|END) (" + b"|".join(_PEMS) + b")[- ]----))" +) + + +def is_pem_format(key: bytes) -> bool: + begin_markers: dict[bytes, int] = {} + for marker in _PEM_MARKER_RE.finditer(key): + marker_type, label = marker.group(2), marker.group(3) + if marker_type == b"BEGIN": + begin_markers[label] = marker.start(1) + len(marker.group(1)) + elif label in begin_markers and begin_markers[label] < marker.start(1): + return True + return False + + +# Based on https://github.com/pyca/cryptography/blob/bcb70852d577b3f490f015378c75cba74986297b/src/cryptography/hazmat/primitives/serialization/ssh.py#L40-L46 +_SSH_KEY_FORMATS = ( + b"ssh-ed25519", + b"ssh-rsa", + b"ssh-dss", + b"ecdsa-sha2-nistp256", + b"ecdsa-sha2-nistp384", + b"ecdsa-sha2-nistp521", +) + + +def is_ssh_key(key: bytes) -> bool: + return key.startswith(_SSH_KEY_FORMATS) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py new file mode 100644 index 0000000..8a41a36 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/jwt/warnings.py @@ -0,0 +1,11 @@ +class RemovedInPyjwt3Warning(DeprecationWarning): + """Warning for features that will be removed in PyJWT 3.""" + + pass + + +class InsecureKeyLengthWarning(UserWarning): + """Warning emitted when a cryptographic key is shorter than the minimum + recommended length. See :ref:`key-length-validation` for details.""" + + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA new file mode 100644 index 0000000..19afdb6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA @@ -0,0 +1,2606 @@ +Metadata-Version: 2.4 +Name: mcp +Version: 1.26.0 +Summary: Model Context Protocol SDK +Project-URL: Homepage, https://modelcontextprotocol.io +Project-URL: Repository, https://github.com/modelcontextprotocol/python-sdk +Project-URL: Issues, https://github.com/modelcontextprotocol/python-sdk/issues +Author: Anthropic, PBC. +Maintainer-email: David Soria Parra , Justin Spahr-Summers +License: MIT +License-File: LICENSE +Keywords: automation,git,llm,mcp +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.10 +Requires-Dist: anyio>=4.5 +Requires-Dist: httpx-sse>=0.4 +Requires-Dist: httpx>=0.27.1 +Requires-Dist: jsonschema>=4.20.0 +Requires-Dist: pydantic-settings>=2.5.2 +Requires-Dist: pydantic<3.0.0,>=2.11.0 +Requires-Dist: pyjwt[crypto]>=2.10.1 +Requires-Dist: python-multipart>=0.0.9 +Requires-Dist: pywin32>=310; sys_platform == 'win32' +Requires-Dist: sse-starlette>=1.6.1 +Requires-Dist: starlette>=0.27 +Requires-Dist: typing-extensions>=4.9.0 +Requires-Dist: typing-inspection>=0.4.1 +Requires-Dist: uvicorn>=0.31.1; sys_platform != 'emscripten' +Provides-Extra: cli +Requires-Dist: python-dotenv>=1.0.0; extra == 'cli' +Requires-Dist: typer>=0.16.0; extra == 'cli' +Provides-Extra: rich +Requires-Dist: rich>=13.9.4; extra == 'rich' +Provides-Extra: ws +Requires-Dist: websockets>=15.0.1; extra == 'ws' +Description-Content-Type: text/markdown + +# MCP Python SDK + +
+ +Python implementation of the Model Context Protocol (MCP) + +[![PyPI][pypi-badge]][pypi-url] +[![MIT licensed][mit-badge]][mit-url] +[![Python Version][python-badge]][python-url] +[![Documentation][docs-badge]][docs-url] +[![Protocol][protocol-badge]][protocol-url] +[![Specification][spec-badge]][spec-url] + +
+ + +## Table of Contents + +- [MCP Python SDK](#mcp-python-sdk) + - [Overview](#overview) + - [Installation](#installation) + - [Adding MCP to your python project](#adding-mcp-to-your-python-project) + - [Running the standalone MCP development tools](#running-the-standalone-mcp-development-tools) + - [Quickstart](#quickstart) + - [What is MCP?](#what-is-mcp) + - [Core Concepts](#core-concepts) + - [Server](#server) + - [Resources](#resources) + - [Tools](#tools) + - [Structured Output](#structured-output) + - [Prompts](#prompts) + - [Images](#images) + - [Context](#context) + - [Getting Context in Functions](#getting-context-in-functions) + - [Context Properties and Methods](#context-properties-and-methods) + - [Completions](#completions) + - [Elicitation](#elicitation) + - [Sampling](#sampling) + - [Logging and Notifications](#logging-and-notifications) + - [Authentication](#authentication) + - [FastMCP Properties](#fastmcp-properties) + - [Session Properties and Methods](#session-properties-and-methods) + - [Request Context Properties](#request-context-properties) + - [Running Your Server](#running-your-server) + - [Development Mode](#development-mode) + - [Claude Desktop Integration](#claude-desktop-integration) + - [Direct Execution](#direct-execution) + - [Streamable HTTP Transport](#streamable-http-transport) + - [CORS Configuration for Browser-Based Clients](#cors-configuration-for-browser-based-clients) + - [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server) + - [StreamableHTTP servers](#streamablehttp-servers) + - [Basic mounting](#basic-mounting) + - [Host-based routing](#host-based-routing) + - [Multiple servers with path configuration](#multiple-servers-with-path-configuration) + - [Path configuration at initialization](#path-configuration-at-initialization) + - [SSE servers](#sse-servers) + - [Advanced Usage](#advanced-usage) + - [Low-Level Server](#low-level-server) + - [Structured Output Support](#structured-output-support) + - [Pagination (Advanced)](#pagination-advanced) + - [Writing MCP Clients](#writing-mcp-clients) + - [Client Display Utilities](#client-display-utilities) + - [OAuth Authentication for Clients](#oauth-authentication-for-clients) + - [Parsing Tool Results](#parsing-tool-results) + - [MCP Primitives](#mcp-primitives) + - [Server Capabilities](#server-capabilities) + - [Documentation](#documentation) + - [Contributing](#contributing) + - [License](#license) + +[pypi-badge]: https://img.shields.io/pypi/v/mcp.svg +[pypi-url]: https://pypi.org/project/mcp/ +[mit-badge]: https://img.shields.io/pypi/l/mcp.svg +[mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE +[python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg +[python-url]: https://www.python.org/downloads/ +[docs-badge]: https://img.shields.io/badge/docs-python--sdk-blue.svg +[docs-url]: https://modelcontextprotocol.github.io/python-sdk/ +[protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg +[protocol-url]: https://modelcontextprotocol.io +[spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg +[spec-url]: https://modelcontextprotocol.io/specification/latest + +## Overview + +The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Python SDK implements the full MCP specification, making it easy to: + +- Build MCP clients that can connect to any MCP server +- Create MCP servers that expose resources, prompts and tools +- Use standard transports like stdio, SSE, and Streamable HTTP +- Handle all MCP protocol messages and lifecycle events + +## Installation + +### Adding MCP to your python project + +We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects. + +If you haven't created a uv-managed project yet, create one: + + ```bash + uv init mcp-server-demo + cd mcp-server-demo + ``` + + Then add MCP to your project dependencies: + + ```bash + uv add "mcp[cli]" + ``` + +Alternatively, for projects using pip for dependencies: + +```bash +pip install "mcp[cli]" +``` + +### Running the standalone MCP development tools + +To run the mcp command with uv: + +```bash +uv run mcp +``` + +## Quickstart + +Let's create a simple MCP server that exposes a calculator tool and some data: + + +```python +""" +FastMCP quickstart example. + +Run from the repository root: + uv run examples/snippets/servers/fastmcp_quickstart.py +""" + +from mcp.server.fastmcp import FastMCP + +# Create an MCP server +mcp = FastMCP("Demo", json_response=True) + + +# Add an addition tool +@mcp.tool() +def add(a: int, b: int) -> int: + """Add two numbers""" + return a + b + + +# Add a dynamic greeting resource +@mcp.resource("greeting://{name}") +def get_greeting(name: str) -> str: + """Get a personalized greeting""" + return f"Hello, {name}!" + + +# Add a prompt +@mcp.prompt() +def greet_user(name: str, style: str = "friendly") -> str: + """Generate a greeting prompt""" + styles = { + "friendly": "Please write a warm, friendly greeting", + "formal": "Please write a formal, professional greeting", + "casual": "Please write a casual, relaxed greeting", + } + + return f"{styles.get(style, styles['friendly'])} for someone named {name}." + + +# Run with streamable HTTP transport +if __name__ == "__main__": + mcp.run(transport="streamable-http") +``` + +_Full example: [examples/snippets/servers/fastmcp_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/fastmcp_quickstart.py)_ + + +You can install this server in [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) and interact with it right away. First, run the server: + +```bash +uv run --with mcp examples/snippets/servers/fastmcp_quickstart.py +``` + +Then add it to Claude Code: + +```bash +claude mcp add --transport http my-server http://localhost:8000/mcp +``` + +Alternatively, you can test it with the MCP Inspector. Start the server as above, then in a separate terminal: + +```bash +npx -y @modelcontextprotocol/inspector +``` + +In the inspector UI, connect to `http://localhost:8000/mcp`. + +## What is MCP? + +The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: + +- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) +- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) +- Define interaction patterns through **Prompts** (reusable templates for LLM interactions) +- And more! + +## Core Concepts + +### Server + +The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: + + +```python +"""Example showing lifespan support for startup/shutdown with strong typing.""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from dataclasses import dataclass + +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession + + +# Mock database class for example +class Database: + """Mock database class for example.""" + + @classmethod + async def connect(cls) -> "Database": + """Connect to database.""" + return cls() + + async def disconnect(self) -> None: + """Disconnect from database.""" + pass + + def query(self) -> str: + """Execute a query.""" + return "Query result" + + +@dataclass +class AppContext: + """Application context with typed dependencies.""" + + db: Database + + +@asynccontextmanager +async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: + """Manage application lifecycle with type-safe context.""" + # Initialize on startup + db = await Database.connect() + try: + yield AppContext(db=db) + finally: + # Cleanup on shutdown + await db.disconnect() + + +# Pass lifespan to server +mcp = FastMCP("My App", lifespan=app_lifespan) + + +# Access type-safe lifespan context in tools +@mcp.tool() +def query_db(ctx: Context[ServerSession, AppContext]) -> str: + """Tool that uses initialized resources.""" + db = ctx.request_context.lifespan_context.db + return db.query() +``` + +_Full example: [examples/snippets/servers/lifespan_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lifespan_example.py)_ + + +### Resources + +Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: + + +```python +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP(name="Resource Example") + + +@mcp.resource("file://documents/{name}") +def read_document(name: str) -> str: + """Read a document by name.""" + # This would normally read from disk + return f"Content of {name}" + + +@mcp.resource("config://settings") +def get_settings() -> str: + """Get application settings.""" + return """{ + "theme": "dark", + "language": "en", + "debug": false +}""" +``` + +_Full example: [examples/snippets/servers/basic_resource.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_resource.py)_ + + +### Tools + +Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: + + +```python +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP(name="Tool Example") + + +@mcp.tool() +def sum(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b + + +@mcp.tool() +def get_weather(city: str, unit: str = "celsius") -> str: + """Get weather for a city.""" + # This would normally call a weather API + return f"Weather in {city}: 22degrees{unit[0].upper()}" +``` + +_Full example: [examples/snippets/servers/basic_tool.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_tool.py)_ + + +Tools can optionally receive a Context object by including a parameter with the `Context` type annotation. This context is automatically injected by the FastMCP framework and provides access to MCP capabilities: + + +```python +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession + +mcp = FastMCP(name="Progress Example") + + +@mcp.tool() +async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str: + """Execute a task with progress updates.""" + await ctx.info(f"Starting: {task_name}") + + for i in range(steps): + progress = (i + 1) / steps + await ctx.report_progress( + progress=progress, + total=1.0, + message=f"Step {i + 1}/{steps}", + ) + await ctx.debug(f"Completed step {i + 1}") + + return f"Task '{task_name}' completed" +``` + +_Full example: [examples/snippets/servers/tool_progress.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py)_ + + +#### Structured Output + +Tools will return structured results by default, if their return type +annotation is compatible. Otherwise, they will return unstructured results. + +Structured output supports these return types: + +- Pydantic models (BaseModel subclasses) +- TypedDicts +- Dataclasses and other classes with type hints +- `dict[str, T]` (where T is any JSON-serializable type) +- Primitive types (str, int, float, bool, bytes, None) - wrapped in `{"result": value}` +- Generic types (list, tuple, Union, Optional, etc.) - wrapped in `{"result": value}` + +Classes without type hints cannot be serialized for structured output. Only +classes with properly annotated attributes will be converted to Pydantic models +for schema generation and validation. + +Structured results are automatically validated against the output schema +generated from the annotation. This ensures the tool returns well-typed, +validated data that clients can easily process. + +**Note:** For backward compatibility, unstructured results are also +returned. Unstructured results are provided for backward compatibility +with previous versions of the MCP specification, and are quirks-compatible +with previous versions of FastMCP in the current version of the SDK. + +**Note:** In cases where a tool function's return type annotation +causes the tool to be classified as structured _and this is undesirable_, +the classification can be suppressed by passing `structured_output=False` +to the `@tool` decorator. + +##### Advanced: Direct CallToolResult + +For full control over tool responses including the `_meta` field (for passing data to client applications without exposing it to the model), you can return `CallToolResult` directly: + + +```python +"""Example showing direct CallToolResult return for advanced control.""" + +from typing import Annotated + +from pydantic import BaseModel + +from mcp.server.fastmcp import FastMCP +from mcp.types import CallToolResult, TextContent + +mcp = FastMCP("CallToolResult Example") + + +class ValidationModel(BaseModel): + """Model for validating structured output.""" + + status: str + data: dict[str, int] + + +@mcp.tool() +def advanced_tool() -> CallToolResult: + """Return CallToolResult directly for full control including _meta field.""" + return CallToolResult( + content=[TextContent(type="text", text="Response visible to the model")], + _meta={"hidden": "data for client applications only"}, + ) + + +@mcp.tool() +def validated_tool() -> Annotated[CallToolResult, ValidationModel]: + """Return CallToolResult with structured output validation.""" + return CallToolResult( + content=[TextContent(type="text", text="Validated response")], + structuredContent={"status": "success", "data": {"result": 42}}, + _meta={"internal": "metadata"}, + ) + + +@mcp.tool() +def empty_result_tool() -> CallToolResult: + """For empty results, return CallToolResult with empty content.""" + return CallToolResult(content=[]) +``` + +_Full example: [examples/snippets/servers/direct_call_tool_result.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_call_tool_result.py)_ + + +**Important:** `CallToolResult` must always be returned (no `Optional` or `Union`). For empty results, use `CallToolResult(content=[])`. For optional simple types, use `str | None` without `CallToolResult`. + + +```python +"""Example showing structured output with tools.""" + +from typing import TypedDict + +from pydantic import BaseModel, Field + +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("Structured Output Example") + + +# Using Pydantic models for rich structured data +class WeatherData(BaseModel): + """Weather information structure.""" + + temperature: float = Field(description="Temperature in Celsius") + humidity: float = Field(description="Humidity percentage") + condition: str + wind_speed: float + + +@mcp.tool() +def get_weather(city: str) -> WeatherData: + """Get weather for a city - returns structured data.""" + # Simulated weather data + return WeatherData( + temperature=22.5, + humidity=45.0, + condition="sunny", + wind_speed=5.2, + ) + + +# Using TypedDict for simpler structures +class LocationInfo(TypedDict): + latitude: float + longitude: float + name: str + + +@mcp.tool() +def get_location(address: str) -> LocationInfo: + """Get location coordinates""" + return LocationInfo(latitude=51.5074, longitude=-0.1278, name="London, UK") + + +# Using dict[str, Any] for flexible schemas +@mcp.tool() +def get_statistics(data_type: str) -> dict[str, float]: + """Get various statistics""" + return {"mean": 42.5, "median": 40.0, "std_dev": 5.2} + + +# Ordinary classes with type hints work for structured output +class UserProfile: + name: str + age: int + email: str | None = None + + def __init__(self, name: str, age: int, email: str | None = None): + self.name = name + self.age = age + self.email = email + + +@mcp.tool() +def get_user(user_id: str) -> UserProfile: + """Get user profile - returns structured data""" + return UserProfile(name="Alice", age=30, email="alice@example.com") + + +# Classes WITHOUT type hints cannot be used for structured output +class UntypedConfig: + def __init__(self, setting1, setting2): # type: ignore[reportMissingParameterType] + self.setting1 = setting1 + self.setting2 = setting2 + + +@mcp.tool() +def get_config() -> UntypedConfig: + """This returns unstructured output - no schema generated""" + return UntypedConfig("value1", "value2") + + +# Lists and other types are wrapped automatically +@mcp.tool() +def list_cities() -> list[str]: + """Get a list of cities""" + return ["London", "Paris", "Tokyo"] + # Returns: {"result": ["London", "Paris", "Tokyo"]} + + +@mcp.tool() +def get_temperature(city: str) -> float: + """Get temperature as a simple float""" + return 22.5 + # Returns: {"result": 22.5} +``` + +_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_ + + +### Prompts + +Prompts are reusable templates that help LLMs interact with your server effectively: + + +```python +from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp.prompts import base + +mcp = FastMCP(name="Prompt Example") + + +@mcp.prompt(title="Code Review") +def review_code(code: str) -> str: + return f"Please review this code:\n\n{code}" + + +@mcp.prompt(title="Debug Assistant") +def debug_error(error: str) -> list[base.Message]: + return [ + base.UserMessage("I'm seeing this error:"), + base.UserMessage(error), + base.AssistantMessage("I'll help debug that. What have you tried so far?"), + ] +``` + +_Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_prompt.py)_ + + +### Icons + +MCP servers can provide icons for UI display. Icons can be added to the server implementation, tools, resources, and prompts: + +```python +from mcp.server.fastmcp import FastMCP, Icon + +# Create an icon from a file path or URL +icon = Icon( + src="icon.png", + mimeType="image/png", + sizes="64x64" +) + +# Add icons to server +mcp = FastMCP( + "My Server", + website_url="https://example.com", + icons=[icon] +) + +# Add icons to tools, resources, and prompts +@mcp.tool(icons=[icon]) +def my_tool(): + """Tool with an icon.""" + return "result" + +@mcp.resource("demo://resource", icons=[icon]) +def my_resource(): + """Resource with an icon.""" + return "content" +``` + +_Full example: [examples/fastmcp/icons_demo.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/fastmcp/icons_demo.py)_ + +### Images + +FastMCP provides an `Image` class that automatically handles image data: + + +```python +"""Example showing image handling with FastMCP.""" + +from PIL import Image as PILImage + +from mcp.server.fastmcp import FastMCP, Image + +mcp = FastMCP("Image Example") + + +@mcp.tool() +def create_thumbnail(image_path: str) -> Image: + """Create a thumbnail from an image""" + img = PILImage.open(image_path) + img.thumbnail((100, 100)) + return Image(data=img.tobytes(), format="png") +``` + +_Full example: [examples/snippets/servers/images.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/images.py)_ + + +### Context + +The Context object is automatically injected into tool and resource functions that request it via type hints. It provides access to MCP capabilities like logging, progress reporting, resource reading, user interaction, and request metadata. + +#### Getting Context in Functions + +To use context in a tool or resource function, add a parameter with the `Context` type annotation: + +```python +from mcp.server.fastmcp import Context, FastMCP + +mcp = FastMCP(name="Context Example") + + +@mcp.tool() +async def my_tool(x: int, ctx: Context) -> str: + """Tool that uses context capabilities.""" + # The context parameter can have any name as long as it's type-annotated + return await process_with_context(x, ctx) +``` + +#### Context Properties and Methods + +The Context object provides the following capabilities: + +- `ctx.request_id` - Unique ID for the current request +- `ctx.client_id` - Client ID if available +- `ctx.fastmcp` - Access to the FastMCP server instance (see [FastMCP Properties](#fastmcp-properties)) +- `ctx.session` - Access to the underlying session for advanced communication (see [Session Properties and Methods](#session-properties-and-methods)) +- `ctx.request_context` - Access to request-specific data and lifespan resources (see [Request Context Properties](#request-context-properties)) +- `await ctx.debug(message)` - Send debug log message +- `await ctx.info(message)` - Send info log message +- `await ctx.warning(message)` - Send warning log message +- `await ctx.error(message)` - Send error log message +- `await ctx.log(level, message, logger_name=None)` - Send log with custom level +- `await ctx.report_progress(progress, total=None, message=None)` - Report operation progress +- `await ctx.read_resource(uri)` - Read a resource by URI +- `await ctx.elicit(message, schema)` - Request additional information from user with validation + + +```python +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession + +mcp = FastMCP(name="Progress Example") + + +@mcp.tool() +async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str: + """Execute a task with progress updates.""" + await ctx.info(f"Starting: {task_name}") + + for i in range(steps): + progress = (i + 1) / steps + await ctx.report_progress( + progress=progress, + total=1.0, + message=f"Step {i + 1}/{steps}", + ) + await ctx.debug(f"Completed step {i + 1}") + + return f"Task '{task_name}' completed" +``` + +_Full example: [examples/snippets/servers/tool_progress.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py)_ + + +### Completions + +MCP supports providing completion suggestions for prompt arguments and resource template parameters. With the context parameter, servers can provide completions based on previously resolved values: + +Client usage: + + +```python +""" +cd to the `examples/snippets` directory and run: + uv run completion-client +""" + +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from mcp.types import PromptReference, ResourceTemplateReference + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "completion", "stdio"], # Server with completion support + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) + + +async def run(): + """Run the completion client example.""" + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize the connection + await session.initialize() + + # List available resource templates + templates = await session.list_resource_templates() + print("Available resource templates:") + for template in templates.resourceTemplates: + print(f" - {template.uriTemplate}") + + # List available prompts + prompts = await session.list_prompts() + print("\nAvailable prompts:") + for prompt in prompts.prompts: + print(f" - {prompt.name}") + + # Complete resource template arguments + if templates.resourceTemplates: + template = templates.resourceTemplates[0] + print(f"\nCompleting arguments for resource template: {template.uriTemplate}") + + # Complete without context + result = await session.complete( + ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate), + argument={"name": "owner", "value": "model"}, + ) + print(f"Completions for 'owner' starting with 'model': {result.completion.values}") + + # Complete with context - repo suggestions based on owner + result = await session.complete( + ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate), + argument={"name": "repo", "value": ""}, + context_arguments={"owner": "modelcontextprotocol"}, + ) + print(f"Completions for 'repo' with owner='modelcontextprotocol': {result.completion.values}") + + # Complete prompt arguments + if prompts.prompts: + prompt_name = prompts.prompts[0].name + print(f"\nCompleting arguments for prompt: {prompt_name}") + + result = await session.complete( + ref=PromptReference(type="ref/prompt", name=prompt_name), + argument={"name": "style", "value": ""}, + ) + print(f"Completions for 'style' argument: {result.completion.values}") + + +def main(): + """Entry point for the completion client.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() +``` + +_Full example: [examples/snippets/clients/completion_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/completion_client.py)_ + +### Elicitation + +Request additional information from users. This example shows an Elicitation during a Tool Call: + + +```python +"""Elicitation examples demonstrating form and URL mode elicitation. + +Form mode elicitation collects structured, non-sensitive data through a schema. +URL mode elicitation directs users to external URLs for sensitive operations +like OAuth flows, credential collection, or payment processing. +""" + +import uuid + +from pydantic import BaseModel, Field + +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession +from mcp.shared.exceptions import UrlElicitationRequiredError +from mcp.types import ElicitRequestURLParams + +mcp = FastMCP(name="Elicitation Example") + + +class BookingPreferences(BaseModel): + """Schema for collecting user preferences.""" + + checkAlternative: bool = Field(description="Would you like to check another date?") + alternativeDate: str = Field( + default="2024-12-26", + description="Alternative date (YYYY-MM-DD)", + ) + + +@mcp.tool() +async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerSession, None]) -> str: + """Book a table with date availability check. + + This demonstrates form mode elicitation for collecting non-sensitive user input. + """ + # Check if date is available + if date == "2024-12-25": + # Date unavailable - ask user for alternative + result = await ctx.elicit( + message=(f"No tables available for {party_size} on {date}. Would you like to try another date?"), + schema=BookingPreferences, + ) + + if result.action == "accept" and result.data: + if result.data.checkAlternative: + return f"[SUCCESS] Booked for {result.data.alternativeDate}" + return "[CANCELLED] No booking made" + return "[CANCELLED] Booking cancelled" + + # Date available + return f"[SUCCESS] Booked for {date} at {time}" + + +@mcp.tool() +async def secure_payment(amount: float, ctx: Context[ServerSession, None]) -> str: + """Process a secure payment requiring URL confirmation. + + This demonstrates URL mode elicitation using ctx.elicit_url() for + operations that require out-of-band user interaction. + """ + elicitation_id = str(uuid.uuid4()) + + result = await ctx.elicit_url( + message=f"Please confirm payment of ${amount:.2f}", + url=f"https://payments.example.com/confirm?amount={amount}&id={elicitation_id}", + elicitation_id=elicitation_id, + ) + + if result.action == "accept": + # In a real app, the payment confirmation would happen out-of-band + # and you'd verify the payment status from your backend + return f"Payment of ${amount:.2f} initiated - check your browser to complete" + elif result.action == "decline": + return "Payment declined by user" + return "Payment cancelled" + + +@mcp.tool() +async def connect_service(service_name: str, ctx: Context[ServerSession, None]) -> str: + """Connect to a third-party service requiring OAuth authorization. + + This demonstrates the "throw error" pattern using UrlElicitationRequiredError. + Use this pattern when the tool cannot proceed without user authorization. + """ + elicitation_id = str(uuid.uuid4()) + + # Raise UrlElicitationRequiredError to signal that the client must complete + # a URL elicitation before this request can be processed. + # The MCP framework will convert this to a -32042 error response. + raise UrlElicitationRequiredError( + [ + ElicitRequestURLParams( + mode="url", + message=f"Authorization required to connect to {service_name}", + url=f"https://{service_name}.example.com/oauth/authorize?elicit={elicitation_id}", + elicitationId=elicitation_id, + ) + ] + ) +``` + +_Full example: [examples/snippets/servers/elicitation.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/elicitation.py)_ + + +Elicitation schemas support default values for all field types. Default values are automatically included in the JSON schema sent to clients, allowing them to pre-populate forms. + +The `elicit()` method returns an `ElicitationResult` with: + +- `action`: "accept", "decline", or "cancel" +- `data`: The validated response (only when accepted) +- `validation_error`: Any validation error message + +### Sampling + +Tools can interact with LLMs through sampling (generating text): + + +```python +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession +from mcp.types import SamplingMessage, TextContent + +mcp = FastMCP(name="Sampling Example") + + +@mcp.tool() +async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str: + """Generate a poem using LLM sampling.""" + prompt = f"Write a short poem about {topic}" + + result = await ctx.session.create_message( + messages=[ + SamplingMessage( + role="user", + content=TextContent(type="text", text=prompt), + ) + ], + max_tokens=100, + ) + + # Since we're not passing tools param, result.content is single content + if result.content.type == "text": + return result.content.text + return str(result.content) +``` + +_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_ + + +### Logging and Notifications + +Tools can send logs and notifications through the context: + + +```python +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession + +mcp = FastMCP(name="Notifications Example") + + +@mcp.tool() +async def process_data(data: str, ctx: Context[ServerSession, None]) -> str: + """Process data with logging.""" + # Different log levels + await ctx.debug(f"Debug: Processing '{data}'") + await ctx.info("Info: Starting processing") + await ctx.warning("Warning: This is experimental") + await ctx.error("Error: (This is just a demo)") + + # Notify about resource changes + await ctx.session.send_resource_list_changed() + + return f"Processed: {data}" +``` + +_Full example: [examples/snippets/servers/notifications.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/notifications.py)_ + + +### Authentication + +Authentication can be used by servers that want to expose tools accessing protected resources. + +`mcp.server.auth` implements OAuth 2.1 resource server functionality, where MCP servers act as Resource Servers (RS) that validate tokens issued by separate Authorization Servers (AS). This follows the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) and implements RFC 9728 (Protected Resource Metadata) for AS discovery. + +MCP servers can use authentication by providing an implementation of the `TokenVerifier` protocol: + + +```python +""" +Run from the repository root: + uv run examples/snippets/servers/oauth_server.py +""" + +from pydantic import AnyHttpUrl + +from mcp.server.auth.provider import AccessToken, TokenVerifier +from mcp.server.auth.settings import AuthSettings +from mcp.server.fastmcp import FastMCP + + +class SimpleTokenVerifier(TokenVerifier): + """Simple token verifier for demonstration.""" + + async def verify_token(self, token: str) -> AccessToken | None: + pass # This is where you would implement actual token validation + + +# Create FastMCP instance as a Resource Server +mcp = FastMCP( + "Weather Service", + json_response=True, + # Token verifier for authentication + token_verifier=SimpleTokenVerifier(), + # Auth settings for RFC 9728 Protected Resource Metadata + auth=AuthSettings( + issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL + resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL + required_scopes=["user"], + ), +) + + +@mcp.tool() +async def get_weather(city: str = "London") -> dict[str, str]: + """Get weather data for a city""" + return { + "city": city, + "temperature": "22", + "condition": "Partly cloudy", + "humidity": "65%", + } + + +if __name__ == "__main__": + mcp.run(transport="streamable-http") +``` + +_Full example: [examples/snippets/servers/oauth_server.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/oauth_server.py)_ + + +For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](examples/servers/simple-auth/). + +**Architecture:** + +- **Authorization Server (AS)**: Handles OAuth flows, user authentication, and token issuance +- **Resource Server (RS)**: Your MCP server that validates tokens and serves protected resources +- **Client**: Discovers AS through RFC 9728, obtains tokens, and uses them with the MCP server + +See [TokenVerifier](src/mcp/server/auth/provider.py) for more details on implementing token validation. + +### FastMCP Properties + +The FastMCP server instance accessible via `ctx.fastmcp` provides access to server configuration and metadata: + +- `ctx.fastmcp.name` - The server's name as defined during initialization +- `ctx.fastmcp.instructions` - Server instructions/description provided to clients +- `ctx.fastmcp.website_url` - Optional website URL for the server +- `ctx.fastmcp.icons` - Optional list of icons for UI display +- `ctx.fastmcp.settings` - Complete server configuration object containing: + - `debug` - Debug mode flag + - `log_level` - Current logging level + - `host` and `port` - Server network configuration + - `mount_path`, `sse_path`, `streamable_http_path` - Transport paths + - `stateless_http` - Whether the server operates in stateless mode + - And other configuration options + +```python +@mcp.tool() +def server_info(ctx: Context) -> dict: + """Get information about the current server.""" + return { + "name": ctx.fastmcp.name, + "instructions": ctx.fastmcp.instructions, + "debug_mode": ctx.fastmcp.settings.debug, + "log_level": ctx.fastmcp.settings.log_level, + "host": ctx.fastmcp.settings.host, + "port": ctx.fastmcp.settings.port, + } +``` + +### Session Properties and Methods + +The session object accessible via `ctx.session` provides advanced control over client communication: + +- `ctx.session.client_params` - Client initialization parameters and declared capabilities +- `await ctx.session.send_log_message(level, data, logger)` - Send log messages with full control +- `await ctx.session.create_message(messages, max_tokens)` - Request LLM sampling/completion +- `await ctx.session.send_progress_notification(token, progress, total, message)` - Direct progress updates +- `await ctx.session.send_resource_updated(uri)` - Notify clients that a specific resource changed +- `await ctx.session.send_resource_list_changed()` - Notify clients that the resource list changed +- `await ctx.session.send_tool_list_changed()` - Notify clients that the tool list changed +- `await ctx.session.send_prompt_list_changed()` - Notify clients that the prompt list changed + +```python +@mcp.tool() +async def notify_data_update(resource_uri: str, ctx: Context) -> str: + """Update data and notify clients of the change.""" + # Perform data update logic here + + # Notify clients that this specific resource changed + await ctx.session.send_resource_updated(AnyUrl(resource_uri)) + + # If this affects the overall resource list, notify about that too + await ctx.session.send_resource_list_changed() + + return f"Updated {resource_uri} and notified clients" +``` + +### Request Context Properties + +The request context accessible via `ctx.request_context` contains request-specific information and resources: + +- `ctx.request_context.lifespan_context` - Access to resources initialized during server startup + - Database connections, configuration objects, shared services + - Type-safe access to resources defined in your server's lifespan function +- `ctx.request_context.meta` - Request metadata from the client including: + - `progressToken` - Token for progress notifications + - Other client-provided metadata +- `ctx.request_context.request` - The original MCP request object for advanced processing +- `ctx.request_context.request_id` - Unique identifier for this request + +```python +# Example with typed lifespan context +@dataclass +class AppContext: + db: Database + config: AppConfig + +@mcp.tool() +def query_with_config(query: str, ctx: Context) -> str: + """Execute a query using shared database and configuration.""" + # Access typed lifespan context + app_ctx: AppContext = ctx.request_context.lifespan_context + + # Use shared resources + connection = app_ctx.db + settings = app_ctx.config + + # Execute query with configuration + result = connection.execute(query, timeout=settings.query_timeout) + return str(result) +``` + +_Full lifespan example: [examples/snippets/servers/lifespan_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lifespan_example.py)_ + +## Running Your Server + +### Development Mode + +The fastest way to test and debug your server is with the MCP Inspector: + +```bash +uv run mcp dev server.py + +# Add dependencies +uv run mcp dev server.py --with pandas --with numpy + +# Mount local code +uv run mcp dev server.py --with-editable . +``` + +### Claude Desktop Integration + +Once your server is ready, install it in Claude Desktop: + +```bash +uv run mcp install server.py + +# Custom name +uv run mcp install server.py --name "My Analytics Server" + +# Environment variables +uv run mcp install server.py -v API_KEY=abc123 -v DB_URL=postgres://... +uv run mcp install server.py -f .env +``` + +### Direct Execution + +For advanced scenarios like custom deployments: + + +```python +"""Example showing direct execution of an MCP server. + +This is the simplest way to run an MCP server directly. +cd to the `examples/snippets` directory and run: + uv run direct-execution-server + or + python servers/direct_execution.py +""" + +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("My App") + + +@mcp.tool() +def hello(name: str = "World") -> str: + """Say hello to someone.""" + return f"Hello, {name}!" + + +def main(): + """Entry point for the direct execution server.""" + mcp.run() + + +if __name__ == "__main__": + main() +``` + +_Full example: [examples/snippets/servers/direct_execution.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_execution.py)_ + + +Run it with: + +```bash +python servers/direct_execution.py +# or +uv run mcp run servers/direct_execution.py +``` + +Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMCP and not the low-level server variant. + +### Streamable HTTP Transport + +> **Note**: Streamable HTTP transport is the recommended transport for production deployments. Use `stateless_http=True` and `json_response=True` for optimal scalability. + + +```python +""" +Run from the repository root: + uv run examples/snippets/servers/streamable_config.py +""" + +from mcp.server.fastmcp import FastMCP + +# Stateless server with JSON responses (recommended) +mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True) + +# Other configuration options: +# Stateless server with SSE streaming responses +# mcp = FastMCP("StatelessServer", stateless_http=True) + +# Stateful server with session persistence +# mcp = FastMCP("StatefulServer") + + +# Add a simple tool to demonstrate the server +@mcp.tool() +def greet(name: str = "World") -> str: + """Greet someone by name.""" + return f"Hello, {name}!" + + +# Run server with streamable_http transport +if __name__ == "__main__": + mcp.run(transport="streamable-http") +``` + +_Full example: [examples/snippets/servers/streamable_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)_ + + +You can mount multiple FastMCP servers in a Starlette application: + + +```python +""" +Run from the repository root: + uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload +""" + +import contextlib + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Create the Echo server +echo_mcp = FastMCP(name="EchoServer", stateless_http=True, json_response=True) + + +@echo_mcp.tool() +def echo(message: str) -> str: + """A simple echo tool""" + return f"Echo: {message}" + + +# Create the Math server +math_mcp = FastMCP(name="MathServer", stateless_http=True, json_response=True) + + +@math_mcp.tool() +def add_two(n: int) -> int: + """Tool to add two to the input""" + return n + 2 + + +# Create a combined lifespan to manage both session managers +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with contextlib.AsyncExitStack() as stack: + await stack.enter_async_context(echo_mcp.session_manager.run()) + await stack.enter_async_context(math_mcp.session_manager.run()) + yield + + +# Create the Starlette app and mount the MCP servers +app = Starlette( + routes=[ + Mount("/echo", echo_mcp.streamable_http_app()), + Mount("/math", math_mcp.streamable_http_app()), + ], + lifespan=lifespan, +) + +# Note: Clients connect to http://localhost:8000/echo/mcp and http://localhost:8000/math/mcp +# To mount at the root of each path (e.g., /echo instead of /echo/mcp): +# echo_mcp.settings.streamable_http_path = "/" +# math_mcp.settings.streamable_http_path = "/" +``` + +_Full example: [examples/snippets/servers/streamable_starlette_mount.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_starlette_mount.py)_ + + +For low level server with Streamable HTTP implementations, see: + +- Stateful server: [`examples/servers/simple-streamablehttp/`](examples/servers/simple-streamablehttp/) +- Stateless server: [`examples/servers/simple-streamablehttp-stateless/`](examples/servers/simple-streamablehttp-stateless/) + +The streamable HTTP transport supports: + +- Stateful and stateless operation modes +- Resumability with event stores +- JSON or SSE response formats +- Better scalability for multi-node deployments + +#### CORS Configuration for Browser-Based Clients + +If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it: + +```python +from starlette.applications import Starlette +from starlette.middleware.cors import CORSMiddleware + +# Create your Starlette app first +starlette_app = Starlette(routes=[...]) + +# Then wrap it with CORS middleware +starlette_app = CORSMiddleware( + starlette_app, + allow_origins=["*"], # Configure appropriately for production + allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods + expose_headers=["Mcp-Session-Id"], +) +``` + +This configuration is necessary because: + +- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management +- Browsers restrict access to response headers unless explicitly exposed via CORS +- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses + +### Mounting to an Existing ASGI Server + +By default, SSE servers are mounted at `/sse` and Streamable HTTP servers are mounted at `/mcp`. You can customize these paths using the methods described below. + +For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes). + +#### StreamableHTTP servers + +You can mount the StreamableHTTP server to an existing ASGI server using the `streamable_http_app` method. This allows you to integrate the StreamableHTTP server with other ASGI applications. + +##### Basic mounting + + +```python +""" +Basic example showing how to mount StreamableHTTP server in Starlette. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload +""" + +import contextlib + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Create MCP server +mcp = FastMCP("My App", json_response=True) + + +@mcp.tool() +def hello() -> str: + """A simple hello tool""" + return "Hello from MCP!" + + +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + +# Mount the StreamableHTTP server to the existing ASGI server +app = Starlette( + routes=[ + Mount("/", app=mcp.streamable_http_app()), + ], + lifespan=lifespan, +) +``` + +_Full example: [examples/snippets/servers/streamable_http_basic_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_basic_mounting.py)_ + + +##### Host-based routing + + +```python +""" +Example showing how to mount StreamableHTTP server using Host-based routing. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload +""" + +import contextlib + +from starlette.applications import Starlette +from starlette.routing import Host + +from mcp.server.fastmcp import FastMCP + +# Create MCP server +mcp = FastMCP("MCP Host App", json_response=True) + + +@mcp.tool() +def domain_info() -> str: + """Get domain-specific information""" + return "This is served from mcp.acme.corp" + + +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + +# Mount using Host-based routing +app = Starlette( + routes=[ + Host("mcp.acme.corp", app=mcp.streamable_http_app()), + ], + lifespan=lifespan, +) +``` + +_Full example: [examples/snippets/servers/streamable_http_host_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_host_mounting.py)_ + + +##### Multiple servers with path configuration + + +```python +""" +Example showing how to mount multiple StreamableHTTP servers with path configuration. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload +""" + +import contextlib + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Create multiple MCP servers +api_mcp = FastMCP("API Server", json_response=True) +chat_mcp = FastMCP("Chat Server", json_response=True) + + +@api_mcp.tool() +def api_status() -> str: + """Get API status""" + return "API is running" + + +@chat_mcp.tool() +def send_message(message: str) -> str: + """Send a chat message""" + return f"Message sent: {message}" + + +# Configure servers to mount at the root of each path +# This means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp +api_mcp.settings.streamable_http_path = "/" +chat_mcp.settings.streamable_http_path = "/" + + +# Create a combined lifespan to manage both session managers +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with contextlib.AsyncExitStack() as stack: + await stack.enter_async_context(api_mcp.session_manager.run()) + await stack.enter_async_context(chat_mcp.session_manager.run()) + yield + + +# Mount the servers +app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ], + lifespan=lifespan, +) +``` + +_Full example: [examples/snippets/servers/streamable_http_multiple_servers.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_multiple_servers.py)_ + + +##### Path configuration at initialization + + +```python +""" +Example showing path configuration during FastMCP initialization. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_path_config:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Configure streamable_http_path during initialization +# This server will mount at the root of wherever it's mounted +mcp_at_root = FastMCP( + "My Server", + json_response=True, + streamable_http_path="/", +) + + +@mcp_at_root.tool() +def process_data(data: str) -> str: + """Process some data""" + return f"Processed: {data}" + + +# Mount at /process - endpoints will be at /process instead of /process/mcp +app = Starlette( + routes=[ + Mount("/process", app=mcp_at_root.streamable_http_app()), + ] +) +``` + +_Full example: [examples/snippets/servers/streamable_http_path_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_path_config.py)_ + + +#### SSE servers + +> **Note**: SSE transport is being superseded by [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). + +You can mount the SSE server to an existing ASGI server using the `sse_app` method. This allows you to integrate the SSE server with other ASGI applications. + +```python +from starlette.applications import Starlette +from starlette.routing import Mount, Host +from mcp.server.fastmcp import FastMCP + + +mcp = FastMCP("My App") + +# Mount the SSE server to the existing ASGI server +app = Starlette( + routes=[ + Mount('/', app=mcp.sse_app()), + ] +) + +# or dynamically mount as host +app.router.routes.append(Host('mcp.acme.corp', app=mcp.sse_app())) +``` + +When mounting multiple MCP servers under different paths, you can configure the mount path in several ways: + +```python +from starlette.applications import Starlette +from starlette.routing import Mount +from mcp.server.fastmcp import FastMCP + +# Create multiple MCP servers +github_mcp = FastMCP("GitHub API") +browser_mcp = FastMCP("Browser") +curl_mcp = FastMCP("Curl") +search_mcp = FastMCP("Search") + +# Method 1: Configure mount paths via settings (recommended for persistent configuration) +github_mcp.settings.mount_path = "/github" +browser_mcp.settings.mount_path = "/browser" + +# Method 2: Pass mount path directly to sse_app (preferred for ad-hoc mounting) +# This approach doesn't modify the server's settings permanently + +# Create Starlette app with multiple mounted servers +app = Starlette( + routes=[ + # Using settings-based configuration + Mount("/github", app=github_mcp.sse_app()), + Mount("/browser", app=browser_mcp.sse_app()), + # Using direct mount path parameter + Mount("/curl", app=curl_mcp.sse_app("/curl")), + Mount("/search", app=search_mcp.sse_app("/search")), + ] +) + +# Method 3: For direct execution, you can also pass the mount path to run() +if __name__ == "__main__": + search_mcp.run(transport="sse", mount_path="/search") +``` + +For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes). + +## Advanced Usage + +### Low-Level Server + +For more control, you can use the low-level server implementation directly. This gives you full access to the protocol and allows you to customize every aspect of your server, including lifecycle management through the lifespan API: + + +```python +""" +Run from the repository root: + uv run examples/snippets/servers/lowlevel/lifespan.py +""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from typing import Any + +import mcp.server.stdio +import mcp.types as types +from mcp.server.lowlevel import NotificationOptions, Server +from mcp.server.models import InitializationOptions + + +# Mock database class for example +class Database: + """Mock database class for example.""" + + @classmethod + async def connect(cls) -> "Database": + """Connect to database.""" + print("Database connected") + return cls() + + async def disconnect(self) -> None: + """Disconnect from database.""" + print("Database disconnected") + + async def query(self, query_str: str) -> list[dict[str, str]]: + """Execute a query.""" + # Simulate database query + return [{"id": "1", "name": "Example", "query": query_str}] + + +@asynccontextmanager +async def server_lifespan(_server: Server) -> AsyncIterator[dict[str, Any]]: + """Manage server startup and shutdown lifecycle.""" + # Initialize resources on startup + db = await Database.connect() + try: + yield {"db": db} + finally: + # Clean up on shutdown + await db.disconnect() + + +# Pass lifespan to server +server = Server("example-server", lifespan=server_lifespan) + + +@server.list_tools() +async def handle_list_tools() -> list[types.Tool]: + """List available tools.""" + return [ + types.Tool( + name="query_db", + description="Query the database", + inputSchema={ + "type": "object", + "properties": {"query": {"type": "string", "description": "SQL query to execute"}}, + "required": ["query"], + }, + ) + ] + + +@server.call_tool() +async def query_db(name: str, arguments: dict[str, Any]) -> list[types.TextContent]: + """Handle database query tool call.""" + if name != "query_db": + raise ValueError(f"Unknown tool: {name}") + + # Access lifespan context + ctx = server.request_context + db = ctx.lifespan_context["db"] + + # Execute query + results = await db.query(arguments["query"]) + + return [types.TextContent(type="text", text=f"Query results: {results}")] + + +async def run(): + """Run the server with lifespan management.""" + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="example-server", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + +if __name__ == "__main__": + import asyncio + + asyncio.run(run()) +``` + +_Full example: [examples/snippets/servers/lowlevel/lifespan.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/lifespan.py)_ + + +The lifespan API provides: + +- A way to initialize resources when the server starts and clean them up when it stops +- Access to initialized resources through the request context in handlers +- Type-safe context passing between lifespan and request handlers + + +```python +""" +Run from the repository root: +uv run examples/snippets/servers/lowlevel/basic.py +""" + +import asyncio + +import mcp.server.stdio +import mcp.types as types +from mcp.server.lowlevel import NotificationOptions, Server +from mcp.server.models import InitializationOptions + +# Create a server instance +server = Server("example-server") + + +@server.list_prompts() +async def handle_list_prompts() -> list[types.Prompt]: + """List available prompts.""" + return [ + types.Prompt( + name="example-prompt", + description="An example prompt template", + arguments=[types.PromptArgument(name="arg1", description="Example argument", required=True)], + ) + ] + + +@server.get_prompt() +async def handle_get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult: + """Get a specific prompt by name.""" + if name != "example-prompt": + raise ValueError(f"Unknown prompt: {name}") + + arg1_value = (arguments or {}).get("arg1", "default") + + return types.GetPromptResult( + description="Example prompt", + messages=[ + types.PromptMessage( + role="user", + content=types.TextContent(type="text", text=f"Example prompt text with argument: {arg1_value}"), + ) + ], + ) + + +async def run(): + """Run the basic low-level server.""" + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="example", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + +if __name__ == "__main__": + asyncio.run(run()) +``` + +_Full example: [examples/snippets/servers/lowlevel/basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/basic.py)_ + + +Caution: The `uv run mcp run` and `uv run mcp dev` tool doesn't support low-level server. + +#### Structured Output Support + +The low-level server supports structured output for tools, allowing you to return both human-readable content and machine-readable structured data. Tools can define an `outputSchema` to validate their structured output: + + +```python +""" +Run from the repository root: + uv run examples/snippets/servers/lowlevel/structured_output.py +""" + +import asyncio +from typing import Any + +import mcp.server.stdio +import mcp.types as types +from mcp.server.lowlevel import NotificationOptions, Server +from mcp.server.models import InitializationOptions + +server = Server("example-server") + + +@server.list_tools() +async def list_tools() -> list[types.Tool]: + """List available tools with structured output schemas.""" + return [ + types.Tool( + name="get_weather", + description="Get current weather for a city", + inputSchema={ + "type": "object", + "properties": {"city": {"type": "string", "description": "City name"}}, + "required": ["city"], + }, + outputSchema={ + "type": "object", + "properties": { + "temperature": {"type": "number", "description": "Temperature in Celsius"}, + "condition": {"type": "string", "description": "Weather condition"}, + "humidity": {"type": "number", "description": "Humidity percentage"}, + "city": {"type": "string", "description": "City name"}, + }, + "required": ["temperature", "condition", "humidity", "city"], + }, + ) + ] + + +@server.call_tool() +async def call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]: + """Handle tool calls with structured output.""" + if name == "get_weather": + city = arguments["city"] + + # Simulated weather data - in production, call a weather API + weather_data = { + "temperature": 22.5, + "condition": "partly cloudy", + "humidity": 65, + "city": city, # Include the requested city + } + + # low-level server will validate structured output against the tool's + # output schema, and additionally serialize it into a TextContent block + # for backwards compatibility with pre-2025-06-18 clients. + return weather_data + else: + raise ValueError(f"Unknown tool: {name}") + + +async def run(): + """Run the structured output server.""" + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="structured-output-example", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + +if __name__ == "__main__": + asyncio.run(run()) +``` + +_Full example: [examples/snippets/servers/lowlevel/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/structured_output.py)_ + + +Tools can return data in four ways: + +1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18) +2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18) +3. **Both**: Return a tuple of (content, structured_data) preferred option to use for backwards compatibility +4. **Direct CallToolResult**: Return `CallToolResult` directly for full control (including `_meta` field) + +When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early. + +##### Returning CallToolResult Directly + +For full control over the response including the `_meta` field (for passing data to client applications without exposing it to the model), return `CallToolResult` directly: + + +```python +""" +Run from the repository root: + uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py +""" + +import asyncio +from typing import Any + +import mcp.server.stdio +import mcp.types as types +from mcp.server.lowlevel import NotificationOptions, Server +from mcp.server.models import InitializationOptions + +server = Server("example-server") + + +@server.list_tools() +async def list_tools() -> list[types.Tool]: + """List available tools.""" + return [ + types.Tool( + name="advanced_tool", + description="Tool with full control including _meta field", + inputSchema={ + "type": "object", + "properties": {"message": {"type": "string"}}, + "required": ["message"], + }, + ) + ] + + +@server.call_tool() +async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult: + """Handle tool calls by returning CallToolResult directly.""" + if name == "advanced_tool": + message = str(arguments.get("message", "")) + return types.CallToolResult( + content=[types.TextContent(type="text", text=f"Processed: {message}")], + structuredContent={"result": "success", "message": message}, + _meta={"hidden": "data for client applications only"}, + ) + + raise ValueError(f"Unknown tool: {name}") + + +async def run(): + """Run the server.""" + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="example", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + +if __name__ == "__main__": + asyncio.run(run()) +``` + +_Full example: [examples/snippets/servers/lowlevel/direct_call_tool_result.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/direct_call_tool_result.py)_ + + +**Note:** When returning `CallToolResult`, you bypass the automatic content/structured conversion. You must construct the complete response yourself. + +### Pagination (Advanced) + +For servers that need to handle large datasets, the low-level server provides paginated versions of list operations. This is an optional optimization - most servers won't need pagination unless they're dealing with hundreds or thousands of items. + +#### Server-side Implementation + + +```python +""" +Example of implementing pagination with MCP server decorators. +""" + +from pydantic import AnyUrl + +import mcp.types as types +from mcp.server.lowlevel import Server + +# Initialize the server +server = Server("paginated-server") + +# Sample data to paginate +ITEMS = [f"Item {i}" for i in range(1, 101)] # 100 items + + +@server.list_resources() +async def list_resources_paginated(request: types.ListResourcesRequest) -> types.ListResourcesResult: + """List resources with pagination support.""" + page_size = 10 + + # Extract cursor from request params + cursor = request.params.cursor if request.params is not None else None + + # Parse cursor to get offset + start = 0 if cursor is None else int(cursor) + end = start + page_size + + # Get page of resources + page_items = [ + types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}") + for item in ITEMS[start:end] + ] + + # Determine next cursor + next_cursor = str(end) if end < len(ITEMS) else None + + return types.ListResourcesResult(resources=page_items, nextCursor=next_cursor) +``` + +_Full example: [examples/snippets/servers/pagination_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/pagination_example.py)_ + + +#### Client-side Consumption + + +```python +""" +Example of consuming paginated MCP endpoints from a client. +""" + +import asyncio + +from mcp.client.session import ClientSession +from mcp.client.stdio import StdioServerParameters, stdio_client +from mcp.types import PaginatedRequestParams, Resource + + +async def list_all_resources() -> None: + """Fetch all resources using pagination.""" + async with stdio_client(StdioServerParameters(command="uv", args=["run", "mcp-simple-pagination"])) as ( + read, + write, + ): + async with ClientSession(read, write) as session: + await session.initialize() + + all_resources: list[Resource] = [] + cursor = None + + while True: + # Fetch a page of resources + result = await session.list_resources(params=PaginatedRequestParams(cursor=cursor)) + all_resources.extend(result.resources) + + print(f"Fetched {len(result.resources)} resources") + + # Check if there are more pages + if result.nextCursor: + cursor = result.nextCursor + else: + break + + print(f"Total resources: {len(all_resources)}") + + +if __name__ == "__main__": + asyncio.run(list_all_resources()) +``` + +_Full example: [examples/snippets/clients/pagination_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/pagination_client.py)_ + + +#### Key Points + +- **Cursors are opaque strings** - the server defines the format (numeric offsets, timestamps, etc.) +- **Return `nextCursor=None`** when there are no more pages +- **Backward compatible** - clients that don't support pagination will still work (they'll just get the first page) +- **Flexible page sizes** - Each endpoint can define its own page size based on data characteristics + +See the [simple-pagination example](examples/servers/simple-pagination) for a complete implementation. + +### Writing MCP Clients + +The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports): + + +```python +""" +cd to the `examples/snippets/clients` directory and run: + uv run client +""" + +import asyncio +import os + +from pydantic import AnyUrl + +from mcp import ClientSession, StdioServerParameters, types +from mcp.client.stdio import stdio_client +from mcp.shared.context import RequestContext + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "fastmcp_quickstart", "stdio"], # We're already in snippets dir + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) + + +# Optional: create a sampling callback +async def handle_sampling_message( + context: RequestContext[ClientSession, None], params: types.CreateMessageRequestParams +) -> types.CreateMessageResult: + print(f"Sampling request: {params.messages}") + return types.CreateMessageResult( + role="assistant", + content=types.TextContent( + type="text", + text="Hello, world! from model", + ), + model="gpt-3.5-turbo", + stopReason="endTurn", + ) + + +async def run(): + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session: + # Initialize the connection + await session.initialize() + + # List available prompts + prompts = await session.list_prompts() + print(f"Available prompts: {[p.name for p in prompts.prompts]}") + + # Get a prompt (greet_user prompt from fastmcp_quickstart) + if prompts.prompts: + prompt = await session.get_prompt("greet_user", arguments={"name": "Alice", "style": "friendly"}) + print(f"Prompt result: {prompt.messages[0].content}") + + # List available resources + resources = await session.list_resources() + print(f"Available resources: {[r.uri for r in resources.resources]}") + + # List available tools + tools = await session.list_tools() + print(f"Available tools: {[t.name for t in tools.tools]}") + + # Read a resource (greeting resource from fastmcp_quickstart) + resource_content = await session.read_resource(AnyUrl("greeting://World")) + content_block = resource_content.contents[0] + if isinstance(content_block, types.TextContent): + print(f"Resource content: {content_block.text}") + + # Call a tool (add tool from fastmcp_quickstart) + result = await session.call_tool("add", arguments={"a": 5, "b": 3}) + result_unstructured = result.content[0] + if isinstance(result_unstructured, types.TextContent): + print(f"Tool result: {result_unstructured.text}") + result_structured = result.structuredContent + print(f"Structured tool result: {result_structured}") + + +def main(): + """Entry point for the client script.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() +``` + +_Full example: [examples/snippets/clients/stdio_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/stdio_client.py)_ + + +Clients can also connect using [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http): + + +```python +""" +Run from the repository root: + uv run examples/snippets/clients/streamable_basic.py +""" + +import asyncio + +from mcp import ClientSession +from mcp.client.streamable_http import streamable_http_client + + +async def main(): + # Connect to a streamable HTTP server + async with streamable_http_client("http://localhost:8000/mcp") as ( + read_stream, + write_stream, + _, + ): + # Create a session using the client streams + async with ClientSession(read_stream, write_stream) as session: + # Initialize the connection + await session.initialize() + # List available tools + tools = await session.list_tools() + print(f"Available tools: {[tool.name for tool in tools.tools]}") + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_ + + +### Client Display Utilities + +When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts: + + +```python +""" +cd to the `examples/snippets` directory and run: + uv run display-utilities-client +""" + +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from mcp.shared.metadata_utils import get_display_name + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "fastmcp_quickstart", "stdio"], + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) + + +async def display_tools(session: ClientSession): + """Display available tools with human-readable names""" + tools_response = await session.list_tools() + + for tool in tools_response.tools: + # get_display_name() returns the title if available, otherwise the name + display_name = get_display_name(tool) + print(f"Tool: {display_name}") + if tool.description: + print(f" {tool.description}") + + +async def display_resources(session: ClientSession): + """Display available resources with human-readable names""" + resources_response = await session.list_resources() + + for resource in resources_response.resources: + display_name = get_display_name(resource) + print(f"Resource: {display_name} ({resource.uri})") + + templates_response = await session.list_resource_templates() + for template in templates_response.resourceTemplates: + display_name = get_display_name(template) + print(f"Resource Template: {display_name}") + + +async def run(): + """Run the display utilities example.""" + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize the connection + await session.initialize() + + print("=== Available Tools ===") + await display_tools(session) + + print("\n=== Available Resources ===") + await display_resources(session) + + +def main(): + """Entry point for the display utilities client.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() +``` + +_Full example: [examples/snippets/clients/display_utilities.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/display_utilities.py)_ + + +The `get_display_name()` function implements the proper precedence rules for displaying names: + +- For tools: `title` > `annotations.title` > `name` +- For other objects: `title` > `name` + +This ensures your client UI shows the most user-friendly names that servers provide. + +### OAuth Authentication for Clients + +The SDK includes [authorization support](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for connecting to protected MCP servers: + + +```python +""" +Before running, specify running MCP RS server URL. +To spin up RS server locally, see + examples/servers/simple-auth/README.md + +cd to the `examples/snippets` directory and run: + uv run oauth-client +""" + +import asyncio +from urllib.parse import parse_qs, urlparse + +import httpx +from pydantic import AnyUrl + +from mcp import ClientSession +from mcp.client.auth import OAuthClientProvider, TokenStorage +from mcp.client.streamable_http import streamable_http_client +from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken + + +class InMemoryTokenStorage(TokenStorage): + """Demo In-memory token storage implementation.""" + + def __init__(self): + self.tokens: OAuthToken | None = None + self.client_info: OAuthClientInformationFull | None = None + + async def get_tokens(self) -> OAuthToken | None: + """Get stored tokens.""" + return self.tokens + + async def set_tokens(self, tokens: OAuthToken) -> None: + """Store tokens.""" + self.tokens = tokens + + async def get_client_info(self) -> OAuthClientInformationFull | None: + """Get stored client information.""" + return self.client_info + + async def set_client_info(self, client_info: OAuthClientInformationFull) -> None: + """Store client information.""" + self.client_info = client_info + + +async def handle_redirect(auth_url: str) -> None: + print(f"Visit: {auth_url}") + + +async def handle_callback() -> tuple[str, str | None]: + callback_url = input("Paste callback URL: ") + params = parse_qs(urlparse(callback_url).query) + return params["code"][0], params.get("state", [None])[0] + + +async def main(): + """Run the OAuth client example.""" + oauth_auth = OAuthClientProvider( + server_url="http://localhost:8001", + client_metadata=OAuthClientMetadata( + client_name="Example MCP Client", + redirect_uris=[AnyUrl("http://localhost:3000/callback")], + grant_types=["authorization_code", "refresh_token"], + response_types=["code"], + scope="user", + ), + storage=InMemoryTokenStorage(), + redirect_handler=handle_redirect, + callback_handler=handle_callback, + ) + + async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client: + async with streamable_http_client("http://localhost:8001/mcp", http_client=custom_client) as (read, write, _): + async with ClientSession(read, write) as session: + await session.initialize() + + tools = await session.list_tools() + print(f"Available tools: {[tool.name for tool in tools.tools]}") + + resources = await session.list_resources() + print(f"Available resources: {[r.uri for r in resources.resources]}") + + +def run(): + asyncio.run(main()) + + +if __name__ == "__main__": + run() +``` + +_Full example: [examples/snippets/clients/oauth_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py)_ + + +For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/). + +### Parsing Tool Results + +When calling tools through MCP, the `CallToolResult` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs. + +```python +"""examples/snippets/clients/parsing_tool_results.py""" + +import asyncio + +from mcp import ClientSession, StdioServerParameters, types +from mcp.client.stdio import stdio_client + + +async def parse_tool_results(): + """Demonstrates how to parse different types of content in CallToolResult.""" + server_params = StdioServerParameters( + command="python", args=["path/to/mcp_server.py"] + ) + + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + + # Example 1: Parsing text content + result = await session.call_tool("get_data", {"format": "text"}) + for content in result.content: + if isinstance(content, types.TextContent): + print(f"Text: {content.text}") + + # Example 2: Parsing structured content from JSON tools + result = await session.call_tool("get_user", {"id": "123"}) + if hasattr(result, "structuredContent") and result.structuredContent: + # Access structured data directly + user_data = result.structuredContent + print(f"User: {user_data.get('name')}, Age: {user_data.get('age')}") + + # Example 3: Parsing embedded resources + result = await session.call_tool("read_config", {}) + for content in result.content: + if isinstance(content, types.EmbeddedResource): + resource = content.resource + if isinstance(resource, types.TextResourceContents): + print(f"Config from {resource.uri}: {resource.text}") + elif isinstance(resource, types.BlobResourceContents): + print(f"Binary data from {resource.uri}") + + # Example 4: Parsing image content + result = await session.call_tool("generate_chart", {"data": [1, 2, 3]}) + for content in result.content: + if isinstance(content, types.ImageContent): + print(f"Image ({content.mimeType}): {len(content.data)} bytes") + + # Example 5: Handling errors + result = await session.call_tool("failing_tool", {}) + if result.isError: + print("Tool execution failed!") + for content in result.content: + if isinstance(content, types.TextContent): + print(f"Error: {content.text}") + + +async def main(): + await parse_tool_results() + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +### MCP Primitives + +The MCP protocol defines three core primitives that servers can implement: + +| Primitive | Control | Description | Example Use | +|-----------|-----------------------|-----------------------------------------------------|------------------------------| +| Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options | +| Resources | Application-controlled| Contextual data managed by the client application | File contents, API responses | +| Tools | Model-controlled | Functions exposed to the LLM to take actions | API calls, data updates | + +### Server Capabilities + +MCP servers declare capabilities during initialization: + +| Capability | Feature Flag | Description | +|--------------|------------------------------|------------------------------------| +| `prompts` | `listChanged` | Prompt template management | +| `resources` | `subscribe`
`listChanged`| Resource exposure and updates | +| `tools` | `listChanged` | Tool discovery and execution | +| `logging` | - | Server logging configuration | +| `completions`| - | Argument completion suggestions | + +## Documentation + +- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/) +- [Experimental Features (Tasks)](https://modelcontextprotocol.github.io/python-sdk/experimental/tasks/) +- [Model Context Protocol documentation](https://modelcontextprotocol.io) +- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest) +- [Officially supported servers](https://github.com/modelcontextprotocol/servers) + +## Contributing + +We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](CONTRIBUTING.md) to get started. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD new file mode 100644 index 0000000..66e4ac7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD @@ -0,0 +1,115 @@ +mcp/__init__.py,sha256=9mrheVmhQg7bZ2JNzXnaIIYFQ-HVE7fVyG7AJQz4r3I,3224 +mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mcp/types.py,sha256=aCQQnNHquvQ8XauCmoMBWxuI7JrhSXZeaBSvryP5veU,64908 +mcp/cli/__init__.py,sha256=NJrJIUQmjc6FtSxvGNt1zeXnSLj8-Mli86laODTtQzE,107 +mcp/cli/claude.py,sha256=MKUuZGrBmMSslD2weLIxU7RdQXZl2OvuMMq-_-EAlgY,5086 +mcp/cli/cli.py,sha256=w6mWBmcy4y7nxLQDa4qSbxKV0BWeJqU_SFZf3qdOubs,15179 +mcp/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mcp/client/__main__.py,sha256=_aXYJ3xJiQGURxEEfvV-4MAqxRJwIFptFaLHyJgfvRU,2588 +mcp/client/session.py,sha256=A31De5OwhsBRD-iF32lraArJQ_vYHJYkYYrI_XudAX8,23495 +mcp/client/session_group.py,sha256=wXt4sZI-WRXdy5PPv-hNwRhMuzEhKMkQf3bFvaOITEc,17423 +mcp/client/sse.py,sha256=foXGvt297jQOFV0AnFVfQhqRZVlnp1REjW7R1Bl1FFc,8257 +mcp/client/streamable_http.py,sha256=jrAcPU2onG5z1JSpYCnbzy_gXQv__nYHGx5Amv22L1s,28324 +mcp/client/websocket.py,sha256=mH6zD_nRnMr11uSFdDTy2TZ2YeS9S18v-JJ2rQ7tXck,3486 +mcp/client/auth/__init__.py,sha256=hvlPclaDk3EkHr_nrt6tx37iv_rQ8SQgosM0OhJSeQ4,488 +mcp/client/auth/exceptions.py,sha256=uUDHX_P0S3R_tt7oAS8ALQrUgOiV2Dgc0QQAdaaBcW8,264 +mcp/client/auth/oauth2.py,sha256=4n8H8M5-2rCXH9kBX81ZDkKW4XEhY5N8U3B018cIYuk,27638 +mcp/client/auth/utils.py,sha256=Mm443kj9pHY09mUYflmr89W8jexZalvDS96b_31tUIE,12069 +mcp/client/auth/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mcp/client/auth/extensions/client_credentials.py,sha256=XvCarBQnB4DREuYEVygZbe0P9Wj7z8dSx_3SqajMKzQ,20897 +mcp/client/experimental/__init__.py,sha256=r923QPMrHQbLQYA_F48MLy2Y8ODdPWJhx8SuzhXcaEA,219 +mcp/client/experimental/task_handlers.py,sha256=yDrCoiCha5iBBVpjqZ66uk3ZoDiQznqU29xKGY4Dh_Q,10791 +mcp/client/experimental/tasks.py,sha256=LLEbfmIwCNk0PCpPSnhK6U0dcaqxENbdzLQXeGsqVjI,7055 +mcp/client/stdio/__init__.py,sha256=ojInz-HjRW8Wq0ExFIajmc9Uf8xrZFOZv5vzptxs9lQ,9403 +mcp/os/__init__.py,sha256=uqaGIfCF4I0IPeSsEEZMNwZuDF91U0WKAIbDcKzPRLo,43 +mcp/os/posix/__init__.py,sha256=l_2j8jhhgw69s_-eH4dCDk4QYxNlxKGIpaOCvcbv1Qk,40 +mcp/os/posix/utilities.py,sha256=mTEYV74M9z9s2deXWctpUGTS1MNtkOr7IYCXU3mxlNI,1930 +mcp/os/win32/__init__.py,sha256=7xyI7KWpZhDS3omGEBcu3200Q-DjIsS7c-a-vrn28Y4,42 +mcp/os/win32/utilities.py,sha256=M5j9NF9QhgCHqQeHYpc9z0ksFsNy1F43vYqQl3LXq-Y,10808 +mcp/server/__init__.py,sha256=jeMjAkvChhKLEy1fpeKJ4d_bT1vnnmuGm11otQ7ncBg,202 +mcp/server/__main__.py,sha256=KakUSquF7R44Ugwg3N5VQCJsPnsEVrbwk-UnRHerVww,1328 +mcp/server/elicitation.py,sha256=euqjotipBszsirdZurr2fbKemRYwR7-zlZlijwMdSNY,6917 +mcp/server/models.py,sha256=w-xEPy_qGds2_bEpejN6iaWeh0UvMtKqwlXzPN_ChZ0,422 +mcp/server/session.py,sha256=A0pB1LsQnWgJUowQDhUy45BZHY2_qi29fuJ26jLPc0k,27555 +mcp/server/sse.py,sha256=QqzE9gWpG35HfQo6gbsCmZPV4ud02EtZcewNGTwKvF8,10826 +mcp/server/stdio.py,sha256=LkFigRZC4uIDUA2JjnsKlgOuLh9IWFBNVgHcs97JsM8,3358 +mcp/server/streamable_http.py,sha256=75k3sVYmcBe6t0zmk0XgiOuAq3RSMCr50j8248Wy7W0,46836 +mcp/server/streamable_http_manager.py,sha256=OoFoMo4CUKCVbRgVzsI5-MsLS4qj7rWSMBdrkem8uu4,12183 +mcp/server/transport_security.py,sha256=T8oLE1lh2WXW6cVvPiNUSmG5WmxMvdCCXkm7JdSvmIs,4938 +mcp/server/validation.py,sha256=owYWMCEg5KLesnXmNbyaTifJyJKJYp79FszXgLPsG7c,3633 +mcp/server/websocket.py,sha256=sw9aSmyhOZlqK-jPQd9RS03zn9XaLR4D6HL6vi8DtBY,2352 +mcp/server/auth/__init__.py,sha256=xmxBzGAYvejOEzxqvZxiRkootIdlpp5WrCNiPKvuKdk,51 +mcp/server/auth/errors.py,sha256=8YjlejJPl-qMjZrbJTDzMPBOAOITn0Da0pC5QWn9QdE,224 +mcp/server/auth/json_response.py,sha256=EMv3qAH62vRuw8XBHdFU3kYp0S8Jd6DjRGfwc7Jc9XU,377 +mcp/server/auth/provider.py,sha256=itp10kM6TGcQGL_zMUVpQ28FK3p-q_zFK5V3CJNoHBE,9846 +mcp/server/auth/routes.py,sha256=j4B5b2Pw2VJnCOq3bW2t3mRvgxJ3oI5wzQn0z3AlZ50,9131 +mcp/server/auth/settings.py,sha256=Nj8tNCXANHMCmX6nxCa-GUrA9knJ9f8zCuMxMDFN4D4,1031 +mcp/server/auth/handlers/__init__.py,sha256=T2ueQWVpLDbMIAiOcROyumxRwqa8Po5lA9zd_Cbt2Uc,58 +mcp/server/auth/handlers/authorize.py,sha256=fAz-L-5Qv6KeHjpoBOAUE6sYgTuG0ydPA7y2xjb4pIM,9728 +mcp/server/auth/handlers/metadata.py,sha256=mWPkrs1DfG-Qzq1nZU2HzwxSwk8K_vXe-ZpEDZ0k_QA,853 +mcp/server/auth/handlers/register.py,sha256=CtH9c03oZtJgUoBVr-VgrAoJ6WXBw2RGNk5n70eBlPc,5928 +mcp/server/auth/handlers/revoke.py,sha256=DIVjn09k7qTkAXrkTsRBVlR-XM9rsmTJzKlXyNdJqDE,3113 +mcp/server/auth/handlers/token.py,sha256=yt5kUvB8zhBgONpYZJOQTFyoJZBhC0UKPUE3GQMkots,10223 +mcp/server/auth/middleware/__init__.py,sha256=XhRUi_w-ie_QG7XT5EfqNG-NPZvXtJtgHSB_tPVYNIw,42 +mcp/server/auth/middleware/auth_context.py,sha256=0hYDUr2GL0VqTVQtCCZCDop2JwGox_IuD0XBZQbtpT0,1696 +mcp/server/auth/middleware/bearer_auth.py,sha256=-t3Ar5bNuIJtYSlF2CsvmAozEX2uySo4chp2Rzgbw4s,4423 +mcp/server/auth/middleware/client_auth.py,sha256=lIIGufkj8rwkNVjh63tPH1UOY5h4Bc08zuvGmubUz40,4723 +mcp/server/experimental/__init__.py,sha256=DtyLxeneqNnUc4ZDKeD9KBFEX9ku2iRwOqsME9k5Gvc,373 +mcp/server/experimental/request_context.py,sha256=MAH7Z-Xoe0vENzcPtu4_lVGywDZHp-7FaUJ8bYDt8Jo,8448 +mcp/server/experimental/session_features.py,sha256=fwwIb-Uj2hhjnrE2ZOfEL5Y5UgNa8gg8SiIl5QpN8YA,7870 +mcp/server/experimental/task_context.py,sha256=M970tK_ZM_mle0gRfgSTvd5NztUMW5Wx3yCYExRfo8I,23021 +mcp/server/experimental/task_result_handler.py,sha256=EyTcsYcdwzK49_z8RnBfRX1WQKaFcEzKF3tnpEIUcvg,8379 +mcp/server/experimental/task_support.py,sha256=gP2NiEIaycpRsLa0ThQG8VFOoMGUaoh0mrn4BNfQChQ,3691 +mcp/server/fastmcp/__init__.py,sha256=4EnleAYdPF_hgPexfq2zxGRtU_Fd3ZrjXegKckuj-K8,297 +mcp/server/fastmcp/exceptions.py,sha256=q9djUDmpwmGEWcHI8q4UzJBtf7s7UtgL--OB7OaGzyQ,435 +mcp/server/fastmcp/server.py,sha256=q9Jb2B34oDCO66kM3htygwfYUsfv4Ku54KXGrPW3ubc,52706 +mcp/server/fastmcp/prompts/__init__.py,sha256=4BsMxoYolpoxg74xkkkzCFL8vvdkLVJ5cIPNs1ND1Jo,99 +mcp/server/fastmcp/prompts/base.py,sha256=s5-zpCjfd-LLbqMoa2oJnzUoVUGW4EVhi8ufvWSjHns,7088 +mcp/server/fastmcp/prompts/manager.py,sha256=IXIv__muOQhXgipCmKWJ4cyPjjawWkjmeahe6G29oyU,1835 +mcp/server/fastmcp/resources/__init__.py,sha256=e4S369jBoJt07ez9_ZaJefzGfz4kr9nGwG4KPMzMHc8,464 +mcp/server/fastmcp/resources/base.py,sha256=aw7d46dfMGCz9fYNtShxCJ9PlnNGmoev4JfPpstC8xI,1852 +mcp/server/fastmcp/resources/resource_manager.py,sha256=f_yS7f4-hs-RryiLHlxXmf68bHd4cZnIPkmXbwzhaVg,4007 +mcp/server/fastmcp/resources/templates.py,sha256=dLcTfoEV6nu8CkDq7WsNI1qmwGFan4mMewvYUroLPPA,4947 +mcp/server/fastmcp/resources/types.py,sha256=g6bfXFNGGfoOGiq37TMvG87kxXrgjSgV6UzLS4M6Lz4,7214 +mcp/server/fastmcp/tools/__init__.py,sha256=ZboxhyMJDl87Svjov8YwNYwNZi55P9VhmpTjBZLesnk,96 +mcp/server/fastmcp/tools/base.py,sha256=h2JrpiTTMhj_t1QhQNyIhFPjMTD97-rciQnUe8YAylQ,4740 +mcp/server/fastmcp/tools/tool_manager.py,sha256=rNjJ6_ug6HZEJuYIaN7qqTGgEqiCTha6T0eeSZG9KnY,2972 +mcp/server/fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31 +mcp/server/fastmcp/utilities/context_injection.py,sha256=BVm4Svi5RK-nlscwYZT75WuLPccRAJ_INdZxxVSM-lg,2028 +mcp/server/fastmcp/utilities/func_metadata.py,sha256=unwzYeTu8JRVxqYZe-jiCEdtia2KpOnKHWiTEXugBjc,22121 +mcp/server/fastmcp/utilities/logging.py,sha256=e8kBHAYHB00gntMTaWMN6uT4kwiiOdL14qDXEZU0x-k,1063 +mcp/server/fastmcp/utilities/types.py,sha256=xXRshurGRIjVnr1lVPwcZomSSN4_wvxpNb2jS6RUCw4,3546 +mcp/server/lowlevel/__init__.py,sha256=mS7slrVoWzHMYqDHDIJ7oRZxwPZG_I63UVQiJN311YY,93 +mcp/server/lowlevel/experimental.py,sha256=s0BoxBuMKXXmINiShqBOo456PWyKF4Juf9PG8ydE4V0,10545 +mcp/server/lowlevel/func_inspection.py,sha256=uLEpY5IHgWicIfbYbaI4aDYfUgMRxuFdTu20duvH2pw,2466 +mcp/server/lowlevel/helper_types.py,sha256=LVC0N6JMPynVs4fMJSfBmqKIigHNLsRNhBvbU06rg6A,251 +mcp/server/lowlevel/server.py,sha256=kQiQVVOV3oMD3q72SWOwVkQ_DgvdHSL2GlFIHXDDvao,33499 +mcp/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mcp/shared/_httpx_utils.py,sha256=bfvypq5zsbvL4dgtNfX-lp8R_FqmzDtWEK61K7Y-Sos,2906 +mcp/shared/auth.py,sha256=KndMQ-tXUUGZzXw4fx_2QFLhcGBzQCQi9w8MF6eNSVU,6446 +mcp/shared/auth_utils.py,sha256=urxND9gJd48mHI0XgwjLx5Eq2GlMBrs4y9HebSQtyY4,3006 +mcp/shared/context.py,sha256=QMRHwXCIJsvf4U3vcgqSAXaaUEMKhfj_9XlCI-kUknE,1188 +mcp/shared/exceptions.py,sha256=glHYWFVmdInhHRnlwrpr5w_iALLM0OlNZAM3djNBDXg,2433 +mcp/shared/memory.py,sha256=DtPFbfalB14Nm5hZS5zZLAwrIkqwZFVcBUwFcBAU1TM,3951 +mcp/shared/message.py,sha256=K7wy_fCaPo7p0r8_2Pd223Ab0Q9DMABuCitK3xq0cQI,1550 +mcp/shared/metadata_utils.py,sha256=9Tp9b0PkfjBhm2SlwHnod1W4Bf6vZB0p5J2E7Sv25U4,1712 +mcp/shared/progress.py,sha256=Ys4V_DTQhYsug6KS6Bb3E4T0CDyOOaIecIXAIMFs1tU,1756 +mcp/shared/response_router.py,sha256=en9M764wGHNkWl6dn9Ki7amIRCclHaKsfbnTfpL9KR0,2043 +mcp/shared/session.py,sha256=N5J5m4Y2mnYy1ujy7-yxGLu8fNaau1S7FPawecsJ1_Q,24086 +mcp/shared/tool_name_validation.py,sha256=mTEvgzsMskayyilLaIkP7lIaA4je8cnkj9xYIc6htgo,4531 +mcp/shared/version.py,sha256=GKwM5WHAwd2Dmrq7TyrAHEGWhTO_9UKJJUmC791JcMk,156 +mcp/shared/experimental/__init__.py,sha256=BhhD176Lv_NmLZ3nK6n3zVyCbCxEj5VMwPuqjm3fqcs,209 +mcp/shared/experimental/tasks/__init__.py,sha256=jYCm-mbvvEblzcSWEvQMTlbRD3iUJss-PGF4sMI9OMc,433 +mcp/shared/experimental/tasks/capabilities.py,sha256=wkO_u4698o5yYtV8RPCRj0oaKWfQLMk7l05-ICdjGmo,3571 +mcp/shared/experimental/tasks/context.py,sha256=an3XBnocKUwdlKX8hR71wowzDixBKwsUe9uCBXrsq1w,2875 +mcp/shared/experimental/tasks/helpers.py,sha256=OF7obHXpVoQmrqft-ZcEXOA9bY8xHvnMfd2YGz3xeLo,5517 +mcp/shared/experimental/tasks/in_memory_task_store.py,sha256=cmCPo59XOQPe-kF5uG1Yd-6nHZDaOhrEZJZc35v3I8U,7558 +mcp/shared/experimental/tasks/message_queue.py,sha256=vf5Dmwb-XkpytbK6TixouFWj8ci0wxeN2STpj7IUoiw,7436 +mcp/shared/experimental/tasks/polling.py,sha256=6BCbo7hYGG19bG_RJUYvnYLJ65WHWbWJ9gwri7Aiw80,1369 +mcp/shared/experimental/tasks/resolver.py,sha256=5VvPexlwcL1TZq2j_7fAdMziYlbHDNHk1UW4l1hVH1M,1801 +mcp/shared/experimental/tasks/store.py,sha256=552Kbp7UqLoIruPZnrIkZFu5m0iiQz0quNuHOKI-Yts,3808 +mcp-1.26.0.dist-info/METADATA,sha256=faPMQb0c9UArFqMQZ9WdGFxZdVjfGHy2I71OQ-hBlws,89485 +mcp-1.26.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87 +mcp-1.26.0.dist-info/entry_points.txt,sha256=gnGBbo3yGF0YJNXfZpgkAg1-CQtKxnzxrlA53c8viYA,42 +mcp-1.26.0.dist-info/licenses/LICENSE,sha256=XhPbvB0SD8KgPOzefJFCSuLX3hG2PVje0vRDHiYe5Q0,1071 +mcp-1.26.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL new file mode 100644 index 0000000..ae8ec1b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.28.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt new file mode 100644 index 0000000..df7969b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +mcp = mcp.cli:app [cli] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..3d48435 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Anthropic, PBC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py new file mode 100644 index 0000000..fbec40d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/__init__.py @@ -0,0 +1,135 @@ +from .client.session import ClientSession +from .client.session_group import ClientSessionGroup +from .client.stdio import StdioServerParameters, stdio_client +from .server.session import ServerSession +from .server.stdio import stdio_server +from .shared.exceptions import McpError, UrlElicitationRequiredError +from .types import ( + CallToolRequest, + ClientCapabilities, + ClientNotification, + ClientRequest, + ClientResult, + CompleteRequest, + CreateMessageRequest, + CreateMessageResult, + CreateMessageResultWithTools, + ErrorData, + GetPromptRequest, + GetPromptResult, + Implementation, + IncludeContext, + InitializedNotification, + InitializeRequest, + InitializeResult, + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + ListPromptsRequest, + ListPromptsResult, + ListResourcesRequest, + ListResourcesResult, + ListToolsResult, + LoggingLevel, + LoggingMessageNotification, + Notification, + PingRequest, + ProgressNotification, + PromptsCapability, + ReadResourceRequest, + ReadResourceResult, + Resource, + ResourcesCapability, + ResourceUpdatedNotification, + RootsCapability, + SamplingCapability, + SamplingContent, + SamplingContextCapability, + SamplingMessage, + SamplingMessageContentBlock, + SamplingToolsCapability, + ServerCapabilities, + ServerNotification, + ServerRequest, + ServerResult, + SetLevelRequest, + StopReason, + SubscribeRequest, + Tool, + ToolChoice, + ToolResultContent, + ToolsCapability, + ToolUseContent, + UnsubscribeRequest, +) +from .types import ( + Role as SamplingRole, +) + +__all__ = [ + "CallToolRequest", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "ClientSession", + "ClientSessionGroup", + "CompleteRequest", + "CreateMessageRequest", + "CreateMessageResult", + "CreateMessageResultWithTools", + "ErrorData", + "GetPromptRequest", + "GetPromptResult", + "Implementation", + "IncludeContext", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "McpError", + "Notification", + "PingRequest", + "ProgressNotification", + "PromptsCapability", + "ReadResourceRequest", + "ReadResourceResult", + "Resource", + "ResourcesCapability", + "ResourceUpdatedNotification", + "RootsCapability", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingRole", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "ServerSession", + "SetLevelRequest", + "StdioServerParameters", + "StopReason", + "SubscribeRequest", + "Tool", + "ToolChoice", + "ToolResultContent", + "ToolsCapability", + "ToolUseContent", + "UnsubscribeRequest", + "UrlElicitationRequiredError", + "stdio_client", + "stdio_server", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py new file mode 100644 index 0000000..b29bce8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py @@ -0,0 +1,6 @@ +"""FastMCP CLI package.""" + +from .cli import app + +if __name__ == "__main__": # pragma: no cover + app() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py new file mode 100644 index 0000000..f2dc688 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py @@ -0,0 +1,148 @@ +"""Claude app integration utilities.""" + +import json +import os +import shutil +import sys +from pathlib import Path +from typing import Any + +from mcp.server.fastmcp.utilities.logging import get_logger + +logger = get_logger(__name__) + +MCP_PACKAGE = "mcp[cli]" + + +def get_claude_config_path() -> Path | None: # pragma: no cover + """Get the Claude config directory based on platform.""" + if sys.platform == "win32": + path = Path(Path.home(), "AppData", "Roaming", "Claude") + elif sys.platform == "darwin": + path = Path(Path.home(), "Library", "Application Support", "Claude") + elif sys.platform.startswith("linux"): + path = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude") + else: + return None + + if path.exists(): + return path + return None + + +def get_uv_path() -> str: + """Get the full path to the uv executable.""" + uv_path = shutil.which("uv") + if not uv_path: # pragma: no cover + logger.error( + "uv executable not found in PATH, falling back to 'uv'. Please ensure uv is installed and in your PATH" + ) + return "uv" # Fall back to just "uv" if not found + return uv_path + + +def update_claude_config( + file_spec: str, + server_name: str, + *, + with_editable: Path | None = None, + with_packages: list[str] | None = None, + env_vars: dict[str, str] | None = None, +) -> bool: + """Add or update a FastMCP server in Claude's configuration. + + Args: + file_spec: Path to the server file, optionally with :object suffix + server_name: Name for the server in Claude's config + with_editable: Optional directory to install in editable mode + with_packages: Optional list of additional packages to install + env_vars: Optional dictionary of environment variables. These are merged with + any existing variables, with new values taking precedence. + + Raises: + RuntimeError: If Claude Desktop's config directory is not found, indicating + Claude Desktop may not be installed or properly set up. + """ + config_dir = get_claude_config_path() + uv_path = get_uv_path() + if not config_dir: # pragma: no cover + raise RuntimeError( + "Claude Desktop config directory not found. Please ensure Claude Desktop" + " is installed and has been run at least once to initialize its config." + ) + + config_file = config_dir / "claude_desktop_config.json" + if not config_file.exists(): # pragma: no cover + try: + config_file.write_text("{}") + except Exception: + logger.exception( + "Failed to create Claude config file", + extra={ + "config_file": str(config_file), + }, + ) + return False + + try: + config = json.loads(config_file.read_text()) + if "mcpServers" not in config: + config["mcpServers"] = {} + + # Always preserve existing env vars and merge with new ones + if server_name in config["mcpServers"] and "env" in config["mcpServers"][server_name]: # pragma: no cover + existing_env = config["mcpServers"][server_name]["env"] + if env_vars: + # New vars take precedence over existing ones + env_vars = {**existing_env, **env_vars} + else: + env_vars = existing_env + + # Build uv run command + args = ["run", "--frozen"] + + # Collect all packages in a set to deduplicate + packages = {MCP_PACKAGE} + if with_packages: # pragma: no cover + packages.update(pkg for pkg in with_packages if pkg) + + # Add all packages with --with + for pkg in sorted(packages): + args.extend(["--with", pkg]) + + if with_editable: # pragma: no cover + args.extend(["--with-editable", str(with_editable)]) + + # Convert file path to absolute before adding to command + # Split off any :object suffix first + if ":" in file_spec: + file_path, server_object = file_spec.rsplit(":", 1) + file_spec = f"{Path(file_path).resolve()}:{server_object}" + else: # pragma: no cover + file_spec = str(Path(file_spec).resolve()) + + # Add fastmcp run command + args.extend(["mcp", "run", file_spec]) + + server_config: dict[str, Any] = {"command": uv_path, "args": args} + + # Add environment variables if specified + if env_vars: # pragma: no cover + server_config["env"] = env_vars + + config["mcpServers"][server_name] = server_config + + config_file.write_text(json.dumps(config, indent=2)) + logger.info( + f"Added server '{server_name}' to Claude config", + extra={"config_file": str(config_file)}, + ) + return True + except Exception: # pragma: no cover + logger.exception( + "Failed to update Claude config", + extra={ + "config_file": str(config_file), + }, + ) + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py new file mode 100644 index 0000000..c4cae0d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py @@ -0,0 +1,488 @@ +"""MCP CLI tools.""" + +import importlib.metadata +import importlib.util +import os +import subprocess +import sys +from pathlib import Path +from typing import Annotated, Any + +from mcp.server import FastMCP +from mcp.server import Server as LowLevelServer + +try: + import typer +except ImportError: # pragma: no cover + print("Error: typer is required. Install with 'pip install mcp[cli]'") + sys.exit(1) + +try: + from mcp.cli import claude + from mcp.server.fastmcp.utilities.logging import get_logger +except ImportError: # pragma: no cover + print("Error: mcp.server.fastmcp is not installed or not in PYTHONPATH") + sys.exit(1) + +try: + import dotenv +except ImportError: # pragma: no cover + dotenv = None + +logger = get_logger("cli") + +app = typer.Typer( + name="mcp", + help="MCP development tools", + add_completion=False, + no_args_is_help=True, # Show help if no args provided +) + + +def _get_npx_command(): + """Get the correct npx command for the current platform.""" + if sys.platform == "win32": + # Try both npx.cmd and npx.exe on Windows + for cmd in ["npx.cmd", "npx.exe", "npx"]: + try: + subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True) + return cmd + except subprocess.CalledProcessError: + continue + return None + return "npx" # On Unix-like systems, just use npx + + +def _parse_env_var(env_var: str) -> tuple[str, str]: # pragma: no cover + """Parse environment variable string in format KEY=VALUE.""" + if "=" not in env_var: + logger.error(f"Invalid environment variable format: {env_var}. Must be KEY=VALUE") + sys.exit(1) + key, value = env_var.split("=", 1) + return key.strip(), value.strip() + + +def _build_uv_command( + file_spec: str, + with_editable: Path | None = None, + with_packages: list[str] | None = None, +) -> list[str]: + """Build the uv run command that runs an MCP server through mcp run.""" + cmd = ["uv"] + + cmd.extend(["run", "--with", "mcp"]) + + if with_editable: + cmd.extend(["--with-editable", str(with_editable)]) + + if with_packages: + for pkg in with_packages: + if pkg: # pragma: no cover + cmd.extend(["--with", pkg]) + + # Add mcp run command + cmd.extend(["mcp", "run", file_spec]) + return cmd + + +def _parse_file_path(file_spec: str) -> tuple[Path, str | None]: + """Parse a file path that may include a server object specification. + + Args: + file_spec: Path to file, optionally with :object suffix + + Returns: + Tuple of (file_path, server_object) + """ + # First check if we have a Windows path (e.g., C:\...) + has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":" + + # Split on the last colon, but only if it's not part of the Windows drive letter + # and there's actually another colon in the string after the drive letter + if ":" in (file_spec[2:] if has_windows_drive else file_spec): + file_str, server_object = file_spec.rsplit(":", 1) + else: + file_str, server_object = file_spec, None + + # Resolve the file path + file_path = Path(file_str).expanduser().resolve() + if not file_path.exists(): + logger.error(f"File not found: {file_path}") + sys.exit(1) + if not file_path.is_file(): + logger.error(f"Not a file: {file_path}") + sys.exit(1) + + return file_path, server_object + + +def _import_server(file: Path, server_object: str | None = None): # pragma: no cover + """Import an MCP server from a file. + + Args: + file: Path to the file + server_object: Optional object name in format "module:object" or just "object" + + Returns: + The server object + """ + # Add parent directory to Python path so imports can be resolved + file_dir = str(file.parent) + if file_dir not in sys.path: + sys.path.insert(0, file_dir) + + # Import the module + spec = importlib.util.spec_from_file_location("server_module", file) + if not spec or not spec.loader: + logger.error("Could not load module", extra={"file": str(file)}) + sys.exit(1) + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + def _check_server_object(server_object: Any, object_name: str): + """Helper function to check that the server object is supported + + Args: + server_object: The server object to check. + + Returns: + True if it's supported. + """ + if not isinstance(server_object, FastMCP): + logger.error(f"The server object {object_name} is of type {type(server_object)} (expecting {FastMCP}).") + if isinstance(server_object, LowLevelServer): + logger.warning( + "Note that only FastMCP server is supported. Low level Server class is not yet supported." + ) + return False + return True + + # If no object specified, try common server names + if not server_object: + # Look for the most common server object names + for name in ["mcp", "server", "app"]: + if hasattr(module, name): + if not _check_server_object(getattr(module, name), f"{file}:{name}"): + logger.error(f"Ignoring object '{file}:{name}' as it's not a valid server object") + continue + return getattr(module, name) + + logger.error( + f"No server object found in {file}. Please either:\n" + "1. Use a standard variable name (mcp, server, or app)\n" + "2. Specify the object name with file:object syntax" + "3. If the server creates the FastMCP object within main() " + " or another function, refactor the FastMCP object to be a " + " global variable named mcp, server, or app.", + extra={"file": str(file)}, + ) + sys.exit(1) + + # Handle module:object syntax + if ":" in server_object: + module_name, object_name = server_object.split(":", 1) + try: + server_module = importlib.import_module(module_name) + server = getattr(server_module, object_name, None) + except ImportError: + logger.error( + f"Could not import module '{module_name}'", + extra={"file": str(file)}, + ) + sys.exit(1) + else: + # Just object name + server = getattr(module, server_object, None) + + if server is None: + logger.error( + f"Server object '{server_object}' not found", + extra={"file": str(file)}, + ) + sys.exit(1) + + if not _check_server_object(server, server_object): + sys.exit(1) + + return server + + +@app.command() +def version() -> None: # pragma: no cover + """Show the MCP version.""" + try: + version = importlib.metadata.version("mcp") + print(f"MCP version {version}") + except importlib.metadata.PackageNotFoundError: + print("MCP version unknown (package not installed)") + sys.exit(1) + + +@app.command() +def dev( + file_spec: str = typer.Argument( + ..., + help="Python file to run, optionally with :object suffix", + ), + with_editable: Annotated[ + Path | None, + typer.Option( + "--with-editable", + "-e", + help="Directory containing pyproject.toml to install in editable mode", + exists=True, + file_okay=False, + resolve_path=True, + ), + ] = None, + with_packages: Annotated[ + list[str], + typer.Option( + "--with", + help="Additional packages to install", + ), + ] = [], +) -> None: # pragma: no cover + """Run an MCP server with the MCP Inspector.""" + file, server_object = _parse_file_path(file_spec) + + logger.debug( + "Starting dev server", + extra={ + "file": str(file), + "server_object": server_object, + "with_editable": str(with_editable) if with_editable else None, + "with_packages": with_packages, + }, + ) + + try: + # Import server to get dependencies + server = _import_server(file, server_object) + if hasattr(server, "dependencies"): + with_packages = list(set(with_packages + server.dependencies)) + + uv_cmd = _build_uv_command(file_spec, with_editable, with_packages) + + # Get the correct npx command + npx_cmd = _get_npx_command() + if not npx_cmd: + logger.error( + "npx not found. Please ensure Node.js and npm are properly installed and added to your system PATH." + ) + sys.exit(1) + + # Run the MCP Inspector command with shell=True on Windows + shell = sys.platform == "win32" + process = subprocess.run( + [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd, + check=True, + shell=shell, + env=dict(os.environ.items()), # Convert to list of tuples for env update + ) + sys.exit(process.returncode) + except subprocess.CalledProcessError as e: + logger.error( + "Dev server failed", + extra={ + "file": str(file), + "error": str(e), + "returncode": e.returncode, + }, + ) + sys.exit(e.returncode) + except FileNotFoundError: + logger.error( + "npx not found. Please ensure Node.js and npm are properly installed " + "and added to your system PATH. You may need to restart your terminal " + "after installation.", + extra={"file": str(file)}, + ) + sys.exit(1) + + +@app.command() +def run( + file_spec: str = typer.Argument( + ..., + help="Python file to run, optionally with :object suffix", + ), + transport: Annotated[ + str | None, + typer.Option( + "--transport", + "-t", + help="Transport protocol to use (stdio or sse)", + ), + ] = None, +) -> None: # pragma: no cover + """Run an MCP server. + + The server can be specified in two ways:\n + 1. Module approach: server.py - runs the module directly, expecting a server.run() call.\n + 2. Import approach: server.py:app - imports and runs the specified server object.\n\n + + Note: This command runs the server directly. You are responsible for ensuring + all dependencies are available.\n + For dependency management, use `mcp install` or `mcp dev` instead. + """ # noqa: E501 + file, server_object = _parse_file_path(file_spec) + + logger.debug( + "Running server", + extra={ + "file": str(file), + "server_object": server_object, + "transport": transport, + }, + ) + + try: + # Import and get server object + server = _import_server(file, server_object) + + # Run the server + kwargs = {} + if transport: + kwargs["transport"] = transport + + server.run(**kwargs) + + except Exception: + logger.exception( + "Failed to run server", + extra={ + "file": str(file), + }, + ) + sys.exit(1) + + +@app.command() +def install( + file_spec: str = typer.Argument( + ..., + help="Python file to run, optionally with :object suffix", + ), + server_name: Annotated[ + str | None, + typer.Option( + "--name", + "-n", + help="Custom name for the server (defaults to server's name attribute or file name)", + ), + ] = None, + with_editable: Annotated[ + Path | None, + typer.Option( + "--with-editable", + "-e", + help="Directory containing pyproject.toml to install in editable mode", + exists=True, + file_okay=False, + resolve_path=True, + ), + ] = None, + with_packages: Annotated[ + list[str], + typer.Option( + "--with", + help="Additional packages to install", + ), + ] = [], + env_vars: Annotated[ + list[str], + typer.Option( + "--env-var", + "-v", + help="Environment variables in KEY=VALUE format", + ), + ] = [], + env_file: Annotated[ + Path | None, + typer.Option( + "--env-file", + "-f", + help="Load environment variables from a .env file", + exists=True, + file_okay=True, + dir_okay=False, + resolve_path=True, + ), + ] = None, +) -> None: # pragma: no cover + """Install an MCP server in the Claude desktop app. + + Environment variables are preserved once added and only updated if new values + are explicitly provided. + """ + file, server_object = _parse_file_path(file_spec) + + logger.debug( + "Installing server", + extra={ + "file": str(file), + "server_name": server_name, + "server_object": server_object, + "with_editable": str(with_editable) if with_editable else None, + "with_packages": with_packages, + }, + ) + + if not claude.get_claude_config_path(): + logger.error("Claude app not found") + sys.exit(1) + + # Try to import server to get its name, but fall back to file name if dependencies + # missing + name = server_name + server = None + if not name: + try: + server = _import_server(file, server_object) + name = server.name + except (ImportError, ModuleNotFoundError) as e: + logger.debug( + "Could not import server (likely missing dependencies), using file name", + extra={"error": str(e)}, + ) + name = file.stem + + # Get server dependencies if available + server_dependencies = getattr(server, "dependencies", []) if server else [] + if server_dependencies: + with_packages = list(set(with_packages + server_dependencies)) + + # Process environment variables if provided + env_dict: dict[str, str] | None = None + if env_file or env_vars: + env_dict = {} + # Load from .env file if specified + if env_file: + if dotenv: + try: + env_dict |= {k: v for k, v in dotenv.dotenv_values(env_file).items() if v is not None} + except (OSError, ValueError): + logger.exception("Failed to load .env file") + sys.exit(1) + else: + logger.error("python-dotenv is not installed. Cannot load .env file.") + sys.exit(1) + + # Add command line environment variables + for env_var in env_vars: + key, value = _parse_env_var(env_var) + env_dict[key] = value + + if claude.update_claude_config( + file_spec, + name, + with_editable=with_editable, + with_packages=with_packages, + env_vars=env_dict, + ): + logger.info(f"Successfully installed {name} in Claude app") + else: + logger.error(f"Failed to install {name} in Claude app") + sys.exit(1) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py new file mode 100644 index 0000000..2efe05d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py @@ -0,0 +1,85 @@ +import argparse +import logging +import sys +from functools import partial +from urllib.parse import urlparse + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +import mcp.types as types +from mcp.client.session import ClientSession +from mcp.client.sse import sse_client +from mcp.client.stdio import StdioServerParameters, stdio_client +from mcp.shared.message import SessionMessage +from mcp.shared.session import RequestResponder + +if not sys.warnoptions: + import warnings + + warnings.simplefilter("ignore") + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("client") + + +async def message_handler( + message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, +) -> None: + if isinstance(message, Exception): + logger.error("Error: %s", message) + return + + logger.info("Received message from server: %s", message) + + +async def run_session( + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception], + write_stream: MemoryObjectSendStream[SessionMessage], + client_info: types.Implementation | None = None, +): + async with ClientSession( + read_stream, + write_stream, + message_handler=message_handler, + client_info=client_info, + ) as session: + logger.info("Initializing session") + await session.initialize() + logger.info("Initialized") + + +async def main(command_or_url: str, args: list[str], env: list[tuple[str, str]]): + env_dict = dict(env) + + if urlparse(command_or_url).scheme in ("http", "https"): + # Use SSE client for HTTP(S) URLs + async with sse_client(command_or_url) as streams: + await run_session(*streams) + else: + # Use stdio client for commands + server_parameters = StdioServerParameters(command=command_or_url, args=args, env=env_dict) + async with stdio_client(server_parameters) as streams: + await run_session(*streams) + + +def cli(): + parser = argparse.ArgumentParser() + parser.add_argument("command_or_url", help="Command or URL to connect to") + parser.add_argument("args", nargs="*", help="Additional arguments") + parser.add_argument( + "-e", + "--env", + nargs=2, + action="append", + metavar=("KEY", "VALUE"), + help="Environment variables to set. Can be used multiple times.", + default=[], + ) + + args = parser.parse_args() + anyio.run(partial(main, args.command_or_url, args.args, args.env), backend="trio") + + +if __name__ == "__main__": + cli() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py new file mode 100644 index 0000000..252dfd9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py @@ -0,0 +1,21 @@ +""" +OAuth2 Authentication implementation for HTTPX. + +Implements authorization code flow with PKCE and automatic token refresh. +""" + +from mcp.client.auth.exceptions import OAuthFlowError, OAuthRegistrationError, OAuthTokenError +from mcp.client.auth.oauth2 import ( + OAuthClientProvider, + PKCEParameters, + TokenStorage, +) + +__all__ = [ + "OAuthClientProvider", + "OAuthFlowError", + "OAuthRegistrationError", + "OAuthTokenError", + "PKCEParameters", + "TokenStorage", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py new file mode 100644 index 0000000..5ce8777 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py @@ -0,0 +1,10 @@ +class OAuthFlowError(Exception): + """Base exception for OAuth flow errors.""" + + +class OAuthTokenError(OAuthFlowError): + """Raised when token operations fail.""" + + +class OAuthRegistrationError(OAuthFlowError): + """Raised when client registration fails.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py new file mode 100644 index 0000000..e2f3f08 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py @@ -0,0 +1,487 @@ +""" +OAuth client credential extensions for MCP. + +Provides OAuth providers for machine-to-machine authentication flows: +- ClientCredentialsOAuthProvider: For client_credentials with client_id + client_secret +- PrivateKeyJWTOAuthProvider: For client_credentials with private_key_jwt authentication + (typically using a pre-built JWT from workload identity federation) +- RFC7523OAuthClientProvider: For jwt-bearer grant (RFC 7523 Section 2.1) +""" + +import time +from collections.abc import Awaitable, Callable +from typing import Any, Literal +from uuid import uuid4 + +import httpx +import jwt +from pydantic import BaseModel, Field + +from mcp.client.auth import OAuthClientProvider, OAuthFlowError, OAuthTokenError, TokenStorage +from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata + + +class ClientCredentialsOAuthProvider(OAuthClientProvider): + """OAuth provider for client_credentials grant with client_id + client_secret. + + This provider sets client_info directly, bypassing dynamic client registration. + Use this when you already have client credentials (client_id and client_secret). + + Example: + ```python + provider = ClientCredentialsOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + client_secret="my-client-secret", + ) + ``` + """ + + def __init__( + self, + server_url: str, + storage: TokenStorage, + client_id: str, + client_secret: str, + token_endpoint_auth_method: Literal["client_secret_basic", "client_secret_post"] = "client_secret_basic", + scopes: str | None = None, + ) -> None: + """Initialize client_credentials OAuth provider. + + Args: + server_url: The MCP server URL. + storage: Token storage implementation. + client_id: The OAuth client ID. + client_secret: The OAuth client secret. + token_endpoint_auth_method: Authentication method for token endpoint. + Either "client_secret_basic" (default) or "client_secret_post". + scopes: Optional space-separated list of scopes to request. + """ + # Build minimal client_metadata for the base class + client_metadata = OAuthClientMetadata( + redirect_uris=None, + grant_types=["client_credentials"], + token_endpoint_auth_method=token_endpoint_auth_method, + scope=scopes, + ) + super().__init__(server_url, client_metadata, storage, None, None, 300.0) + # Store client_info to be set during _initialize - no dynamic registration needed + self._fixed_client_info = OAuthClientInformationFull( + redirect_uris=None, + client_id=client_id, + client_secret=client_secret, + grant_types=["client_credentials"], + token_endpoint_auth_method=token_endpoint_auth_method, + scope=scopes, + ) + + async def _initialize(self) -> None: + """Load stored tokens and set pre-configured client_info.""" + self.context.current_tokens = await self.context.storage.get_tokens() + self.context.client_info = self._fixed_client_info + self._initialized = True + + async def _perform_authorization(self) -> httpx.Request: + """Perform client_credentials authorization.""" + return await self._exchange_token_client_credentials() + + async def _exchange_token_client_credentials(self) -> httpx.Request: + """Build token exchange request for client_credentials grant.""" + token_data: dict[str, Any] = { + "grant_type": "client_credentials", + } + + headers: dict[str, str] = {"Content-Type": "application/x-www-form-urlencoded"} + + # Use standard auth methods (client_secret_basic, client_secret_post, none) + token_data, headers = self.context.prepare_token_auth(token_data, headers) + + if self.context.should_include_resource_param(self.context.protocol_version): + token_data["resource"] = self.context.get_resource_url() + + if self.context.client_metadata.scope: + token_data["scope"] = self.context.client_metadata.scope + + token_url = self._get_token_endpoint() + return httpx.Request("POST", token_url, data=token_data, headers=headers) + + +def static_assertion_provider(token: str) -> Callable[[str], Awaitable[str]]: + """Create an assertion provider that returns a static JWT token. + + Use this when you have a pre-built JWT (e.g., from workload identity federation) + that doesn't need the audience parameter. + + Example: + ```python + provider = PrivateKeyJWTOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + assertion_provider=static_assertion_provider(my_prebuilt_jwt), + ) + ``` + + Args: + token: The pre-built JWT assertion string. + + Returns: + An async callback suitable for use as an assertion_provider. + """ + + async def provider(audience: str) -> str: + return token + + return provider + + +class SignedJWTParameters(BaseModel): + """Parameters for creating SDK-signed JWT assertions. + + Use `create_assertion_provider()` to create an assertion provider callback + for use with `PrivateKeyJWTOAuthProvider`. + + Example: + ```python + jwt_params = SignedJWTParameters( + issuer="my-client-id", + subject="my-client-id", + signing_key=private_key_pem, + ) + provider = PrivateKeyJWTOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + assertion_provider=jwt_params.create_assertion_provider(), + ) + ``` + """ + + issuer: str = Field(description="Issuer for JWT assertions (typically client_id).") + subject: str = Field(description="Subject identifier for JWT assertions (typically client_id).") + signing_key: str = Field(description="Private key for JWT signing (PEM format).") + signing_algorithm: str = Field(default="RS256", description="Algorithm for signing JWT assertions.") + lifetime_seconds: int = Field(default=300, description="Lifetime of generated JWT in seconds.") + additional_claims: dict[str, Any] | None = Field(default=None, description="Additional claims.") + + def create_assertion_provider(self) -> Callable[[str], Awaitable[str]]: + """Create an assertion provider callback for use with PrivateKeyJWTOAuthProvider. + + Returns: + An async callback that takes the audience (authorization server issuer URL) + and returns a signed JWT assertion. + """ + + async def provider(audience: str) -> str: + now = int(time.time()) + claims: dict[str, Any] = { + "iss": self.issuer, + "sub": self.subject, + "aud": audience, + "exp": now + self.lifetime_seconds, + "iat": now, + "jti": str(uuid4()), + } + if self.additional_claims: + claims.update(self.additional_claims) + + return jwt.encode(claims, self.signing_key, algorithm=self.signing_algorithm) + + return provider + + +class PrivateKeyJWTOAuthProvider(OAuthClientProvider): + """OAuth provider for client_credentials grant with private_key_jwt authentication. + + Uses RFC 7523 Section 2.2 for client authentication via JWT assertion. + + The JWT assertion's audience MUST be the authorization server's issuer identifier + (per RFC 7523bis security updates). The `assertion_provider` callback receives + this audience value and must return a JWT with that audience. + + **Option 1: Pre-built JWT via Workload Identity Federation** + + In production scenarios, the JWT assertion is typically obtained from a workload + identity provider (e.g., GCP, AWS IAM, Azure AD): + + ```python + async def get_workload_identity_token(audience: str) -> str: + # Fetch JWT from your identity provider + # The JWT's audience must match the provided audience parameter + return await fetch_token_from_identity_provider(audience=audience) + + provider = PrivateKeyJWTOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + assertion_provider=get_workload_identity_token, + ) + ``` + + **Option 2: Static pre-built JWT** + + If you have a static JWT that doesn't need the audience parameter: + + ```python + provider = PrivateKeyJWTOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + assertion_provider=static_assertion_provider(my_prebuilt_jwt), + ) + ``` + + **Option 3: SDK-signed JWT (for testing/simple setups)** + + For testing or simple deployments, use `SignedJWTParameters.create_assertion_provider()`: + + ```python + jwt_params = SignedJWTParameters( + issuer="my-client-id", + subject="my-client-id", + signing_key=private_key_pem, + ) + provider = PrivateKeyJWTOAuthProvider( + server_url="https://api.example.com", + storage=my_token_storage, + client_id="my-client-id", + assertion_provider=jwt_params.create_assertion_provider(), + ) + ``` + """ + + def __init__( + self, + server_url: str, + storage: TokenStorage, + client_id: str, + assertion_provider: Callable[[str], Awaitable[str]], + scopes: str | None = None, + ) -> None: + """Initialize private_key_jwt OAuth provider. + + Args: + server_url: The MCP server URL. + storage: Token storage implementation. + client_id: The OAuth client ID. + assertion_provider: Async callback that takes the audience (authorization + server's issuer identifier) and returns a JWT assertion. Use + `SignedJWTParameters.create_assertion_provider()` for SDK-signed JWTs, + `static_assertion_provider()` for pre-built JWTs, or provide your own + callback for workload identity federation. + scopes: Optional space-separated list of scopes to request. + """ + # Build minimal client_metadata for the base class + client_metadata = OAuthClientMetadata( + redirect_uris=None, + grant_types=["client_credentials"], + token_endpoint_auth_method="private_key_jwt", + scope=scopes, + ) + super().__init__(server_url, client_metadata, storage, None, None, 300.0) + self._assertion_provider = assertion_provider + # Store client_info to be set during _initialize - no dynamic registration needed + self._fixed_client_info = OAuthClientInformationFull( + redirect_uris=None, + client_id=client_id, + grant_types=["client_credentials"], + token_endpoint_auth_method="private_key_jwt", + scope=scopes, + ) + + async def _initialize(self) -> None: + """Load stored tokens and set pre-configured client_info.""" + self.context.current_tokens = await self.context.storage.get_tokens() + self.context.client_info = self._fixed_client_info + self._initialized = True + + async def _perform_authorization(self) -> httpx.Request: + """Perform client_credentials authorization with private_key_jwt.""" + return await self._exchange_token_client_credentials() + + async def _add_client_authentication_jwt(self, *, token_data: dict[str, Any]) -> None: + """Add JWT assertion for client authentication to token endpoint parameters.""" + if not self.context.oauth_metadata: + raise OAuthFlowError("Missing OAuth metadata for private_key_jwt flow") # pragma: no cover + + # Audience MUST be the issuer identifier of the authorization server + # https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01 + audience = str(self.context.oauth_metadata.issuer) + assertion = await self._assertion_provider(audience) + + # RFC 7523 Section 2.2: client authentication via JWT + token_data["client_assertion"] = assertion + token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + + async def _exchange_token_client_credentials(self) -> httpx.Request: + """Build token exchange request for client_credentials grant with private_key_jwt.""" + token_data: dict[str, Any] = { + "grant_type": "client_credentials", + } + + headers: dict[str, str] = {"Content-Type": "application/x-www-form-urlencoded"} + + # Add JWT client authentication (RFC 7523 Section 2.2) + await self._add_client_authentication_jwt(token_data=token_data) + + if self.context.should_include_resource_param(self.context.protocol_version): + token_data["resource"] = self.context.get_resource_url() + + if self.context.client_metadata.scope: + token_data["scope"] = self.context.client_metadata.scope + + token_url = self._get_token_endpoint() + return httpx.Request("POST", token_url, data=token_data, headers=headers) + + +class JWTParameters(BaseModel): + """JWT parameters.""" + + assertion: str | None = Field( + default=None, + description="JWT assertion for JWT authentication. " + "Will be used instead of generating a new assertion if provided.", + ) + + issuer: str | None = Field(default=None, description="Issuer for JWT assertions.") + subject: str | None = Field(default=None, description="Subject identifier for JWT assertions.") + audience: str | None = Field(default=None, description="Audience for JWT assertions.") + claims: dict[str, Any] | None = Field(default=None, description="Additional claims for JWT assertions.") + jwt_signing_algorithm: str | None = Field(default="RS256", description="Algorithm for signing JWT assertions.") + jwt_signing_key: str | None = Field(default=None, description="Private key for JWT signing.") + jwt_lifetime_seconds: int = Field(default=300, description="Lifetime of generated JWT in seconds.") + + def to_assertion(self, with_audience_fallback: str | None = None) -> str: + if self.assertion is not None: + # Prebuilt JWT (e.g. acquired out-of-band) + assertion = self.assertion + else: + if not self.jwt_signing_key: + raise OAuthFlowError("Missing signing key for JWT bearer grant") # pragma: no cover + if not self.issuer: + raise OAuthFlowError("Missing issuer for JWT bearer grant") # pragma: no cover + if not self.subject: + raise OAuthFlowError("Missing subject for JWT bearer grant") # pragma: no cover + + audience = self.audience if self.audience else with_audience_fallback + if not audience: + raise OAuthFlowError("Missing audience for JWT bearer grant") # pragma: no cover + + now = int(time.time()) + claims: dict[str, Any] = { + "iss": self.issuer, + "sub": self.subject, + "aud": audience, + "exp": now + self.jwt_lifetime_seconds, + "iat": now, + "jti": str(uuid4()), + } + claims.update(self.claims or {}) + + assertion = jwt.encode( + claims, + self.jwt_signing_key, + algorithm=self.jwt_signing_algorithm or "RS256", + ) + return assertion + + +class RFC7523OAuthClientProvider(OAuthClientProvider): + """OAuth client provider for RFC 7523 jwt-bearer grant. + + .. deprecated:: + Use :class:`ClientCredentialsOAuthProvider` for client_credentials with + client_id + client_secret, or :class:`PrivateKeyJWTOAuthProvider` for + client_credentials with private_key_jwt authentication instead. + + This provider supports the jwt-bearer authorization grant (RFC 7523 Section 2.1) + where the JWT itself is the authorization grant. + """ + + def __init__( + self, + server_url: str, + client_metadata: OAuthClientMetadata, + storage: TokenStorage, + redirect_handler: Callable[[str], Awaitable[None]] | None = None, + callback_handler: Callable[[], Awaitable[tuple[str, str | None]]] | None = None, + timeout: float = 300.0, + jwt_parameters: JWTParameters | None = None, + ) -> None: + import warnings + + warnings.warn( + "RFC7523OAuthClientProvider is deprecated. Use ClientCredentialsOAuthProvider " + "or PrivateKeyJWTOAuthProvider instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(server_url, client_metadata, storage, redirect_handler, callback_handler, timeout) + self.jwt_parameters = jwt_parameters + + async def _exchange_token_authorization_code( + self, auth_code: str, code_verifier: str, *, token_data: dict[str, Any] | None = None + ) -> httpx.Request: # pragma: no cover + """Build token exchange request for authorization_code flow.""" + token_data = token_data or {} + if self.context.client_metadata.token_endpoint_auth_method == "private_key_jwt": + self._add_client_authentication_jwt(token_data=token_data) + return await super()._exchange_token_authorization_code(auth_code, code_verifier, token_data=token_data) + + async def _perform_authorization(self) -> httpx.Request: # pragma: no cover + """Perform the authorization flow.""" + if "urn:ietf:params:oauth:grant-type:jwt-bearer" in self.context.client_metadata.grant_types: + token_request = await self._exchange_token_jwt_bearer() + return token_request + else: + return await super()._perform_authorization() + + def _add_client_authentication_jwt(self, *, token_data: dict[str, Any]): # pragma: no cover + """Add JWT assertion for client authentication to token endpoint parameters.""" + if not self.jwt_parameters: + raise OAuthTokenError("Missing JWT parameters for private_key_jwt flow") + if not self.context.oauth_metadata: + raise OAuthTokenError("Missing OAuth metadata for private_key_jwt flow") + + # We need to set the audience to the issuer identifier of the authorization server + # https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01#name-updates-to-rfc-7523 + issuer = str(self.context.oauth_metadata.issuer) + assertion = self.jwt_parameters.to_assertion(with_audience_fallback=issuer) + + # When using private_key_jwt, in a client_credentials flow, we use RFC 7523 Section 2.2 + token_data["client_assertion"] = assertion + token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + # We need to set the audience to the resource server, the audience is difference from the one in claims + # it represents the resource server that will validate the token + token_data["audience"] = self.context.get_resource_url() + + async def _exchange_token_jwt_bearer(self) -> httpx.Request: + """Build token exchange request for JWT bearer grant.""" + if not self.context.client_info: + raise OAuthFlowError("Missing client info") # pragma: no cover + if not self.jwt_parameters: + raise OAuthFlowError("Missing JWT parameters") # pragma: no cover + if not self.context.oauth_metadata: + raise OAuthTokenError("Missing OAuth metadata") # pragma: no cover + + # We need to set the audience to the issuer identifier of the authorization server + # https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01#name-updates-to-rfc-7523 + issuer = str(self.context.oauth_metadata.issuer) + assertion = self.jwt_parameters.to_assertion(with_audience_fallback=issuer) + + token_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "assertion": assertion, + } + + if self.context.should_include_resource_param(self.context.protocol_version): # pragma: no branch + token_data["resource"] = self.context.get_resource_url() + + if self.context.client_metadata.scope: # pragma: no branch + token_data["scope"] = self.context.client_metadata.scope + + token_url = self._get_token_endpoint() + return httpx.Request( + "POST", token_url, data=token_data, headers={"Content-Type": "application/x-www-form-urlencoded"} + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py new file mode 100644 index 0000000..cd96a75 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py @@ -0,0 +1,616 @@ +""" +OAuth2 Authentication implementation for HTTPX. + +Implements authorization code flow with PKCE and automatic token refresh. +""" + +import base64 +import hashlib +import logging +import secrets +import string +import time +from collections.abc import AsyncGenerator, Awaitable, Callable +from dataclasses import dataclass, field +from typing import Any, Protocol +from urllib.parse import quote, urlencode, urljoin, urlparse + +import anyio +import httpx +from pydantic import BaseModel, Field, ValidationError + +from mcp.client.auth.exceptions import OAuthFlowError, OAuthTokenError +from mcp.client.auth.utils import ( + build_oauth_authorization_server_metadata_discovery_urls, + build_protected_resource_metadata_discovery_urls, + create_client_info_from_metadata_url, + create_client_registration_request, + create_oauth_metadata_request, + extract_field_from_www_auth, + extract_resource_metadata_from_www_auth, + extract_scope_from_www_auth, + get_client_metadata_scopes, + handle_auth_metadata_response, + handle_protected_resource_response, + handle_registration_response, + handle_token_response_scopes, + is_valid_client_metadata_url, + should_use_client_metadata_url, +) +from mcp.client.streamable_http import MCP_PROTOCOL_VERSION +from mcp.shared.auth import ( + OAuthClientInformationFull, + OAuthClientMetadata, + OAuthMetadata, + OAuthToken, + ProtectedResourceMetadata, +) +from mcp.shared.auth_utils import ( + calculate_token_expiry, + check_resource_allowed, + resource_url_from_server_url, +) + +logger = logging.getLogger(__name__) + + +class PKCEParameters(BaseModel): + """PKCE (Proof Key for Code Exchange) parameters.""" + + code_verifier: str = Field(..., min_length=43, max_length=128) + code_challenge: str = Field(..., min_length=43, max_length=128) + + @classmethod + def generate(cls) -> "PKCEParameters": + """Generate new PKCE parameters.""" + code_verifier = "".join(secrets.choice(string.ascii_letters + string.digits + "-._~") for _ in range(128)) + digest = hashlib.sha256(code_verifier.encode()).digest() + code_challenge = base64.urlsafe_b64encode(digest).decode().rstrip("=") + return cls(code_verifier=code_verifier, code_challenge=code_challenge) + + +class TokenStorage(Protocol): + """Protocol for token storage implementations.""" + + async def get_tokens(self) -> OAuthToken | None: + """Get stored tokens.""" + ... + + async def set_tokens(self, tokens: OAuthToken) -> None: + """Store tokens.""" + ... + + async def get_client_info(self) -> OAuthClientInformationFull | None: + """Get stored client information.""" + ... + + async def set_client_info(self, client_info: OAuthClientInformationFull) -> None: + """Store client information.""" + ... + + +@dataclass +class OAuthContext: + """OAuth flow context.""" + + server_url: str + client_metadata: OAuthClientMetadata + storage: TokenStorage + redirect_handler: Callable[[str], Awaitable[None]] | None + callback_handler: Callable[[], Awaitable[tuple[str, str | None]]] | None + timeout: float = 300.0 + client_metadata_url: str | None = None + + # Discovered metadata + protected_resource_metadata: ProtectedResourceMetadata | None = None + oauth_metadata: OAuthMetadata | None = None + auth_server_url: str | None = None + protocol_version: str | None = None + + # Client registration + client_info: OAuthClientInformationFull | None = None + + # Token management + current_tokens: OAuthToken | None = None + token_expiry_time: float | None = None + + # State + lock: anyio.Lock = field(default_factory=anyio.Lock) + + def get_authorization_base_url(self, server_url: str) -> str: + """Extract base URL by removing path component.""" + parsed = urlparse(server_url) + return f"{parsed.scheme}://{parsed.netloc}" + + def update_token_expiry(self, token: OAuthToken) -> None: + """Update token expiry time using shared util function.""" + self.token_expiry_time = calculate_token_expiry(token.expires_in) + + def is_token_valid(self) -> bool: + """Check if current token is valid.""" + return bool( + self.current_tokens + and self.current_tokens.access_token + and (not self.token_expiry_time or time.time() <= self.token_expiry_time) + ) + + def can_refresh_token(self) -> bool: + """Check if token can be refreshed.""" + return bool(self.current_tokens and self.current_tokens.refresh_token and self.client_info) + + def clear_tokens(self) -> None: + """Clear current tokens.""" + self.current_tokens = None + self.token_expiry_time = None + + def get_resource_url(self) -> str: + """Get resource URL for RFC 8707. + + Uses PRM resource if it's a valid parent, otherwise uses canonical server URL. + """ + resource = resource_url_from_server_url(self.server_url) + + # If PRM provides a resource that's a valid parent, use it + if self.protected_resource_metadata and self.protected_resource_metadata.resource: + prm_resource = str(self.protected_resource_metadata.resource) + if check_resource_allowed(requested_resource=resource, configured_resource=prm_resource): + resource = prm_resource + + return resource + + def should_include_resource_param(self, protocol_version: str | None = None) -> bool: + """Determine if the resource parameter should be included in OAuth requests. + + Returns True if: + - Protected resource metadata is available, OR + - MCP-Protocol-Version header is 2025-06-18 or later + """ + # If we have protected resource metadata, include the resource param + if self.protected_resource_metadata is not None: + return True + + # If no protocol version provided, don't include resource param + if not protocol_version: + return False + + # Check if protocol version is 2025-06-18 or later + # Version format is YYYY-MM-DD, so string comparison works + return protocol_version >= "2025-06-18" + + def prepare_token_auth( + self, data: dict[str, str], headers: dict[str, str] | None = None + ) -> tuple[dict[str, str], dict[str, str]]: + """Prepare authentication for token requests. + + Args: + data: The form data to send + headers: Optional headers dict to update + + Returns: + Tuple of (updated_data, updated_headers) + """ + if headers is None: + headers = {} # pragma: no cover + + if not self.client_info: + return data, headers # pragma: no cover + + auth_method = self.client_info.token_endpoint_auth_method + + if auth_method == "client_secret_basic" and self.client_info.client_id and self.client_info.client_secret: + # URL-encode client ID and secret per RFC 6749 Section 2.3.1 + encoded_id = quote(self.client_info.client_id, safe="") + encoded_secret = quote(self.client_info.client_secret, safe="") + credentials = f"{encoded_id}:{encoded_secret}" + encoded_credentials = base64.b64encode(credentials.encode()).decode() + headers["Authorization"] = f"Basic {encoded_credentials}" + # Don't include client_secret in body for basic auth + data = {k: v for k, v in data.items() if k != "client_secret"} + elif auth_method == "client_secret_post" and self.client_info.client_secret: + # Include client_secret in request body + data["client_secret"] = self.client_info.client_secret + # For auth_method == "none", don't add any client_secret + + return data, headers + + +class OAuthClientProvider(httpx.Auth): + """ + OAuth2 authentication for httpx. + Handles OAuth flow with automatic client registration and token storage. + """ + + requires_response_body = True + + def __init__( + self, + server_url: str, + client_metadata: OAuthClientMetadata, + storage: TokenStorage, + redirect_handler: Callable[[str], Awaitable[None]] | None = None, + callback_handler: Callable[[], Awaitable[tuple[str, str | None]]] | None = None, + timeout: float = 300.0, + client_metadata_url: str | None = None, + ): + """Initialize OAuth2 authentication. + + Args: + server_url: The MCP server URL. + client_metadata: OAuth client metadata for registration. + storage: Token storage implementation. + redirect_handler: Handler for authorization redirects. + callback_handler: Handler for authorization callbacks. + timeout: Timeout for the OAuth flow. + client_metadata_url: URL-based client ID. When provided and the server + advertises client_id_metadata_document_supported=true, this URL will be + used as the client_id instead of performing dynamic client registration. + Must be a valid HTTPS URL with a non-root pathname. + + Raises: + ValueError: If client_metadata_url is provided but not a valid HTTPS URL + with a non-root pathname. + """ + # Validate client_metadata_url if provided + if client_metadata_url is not None and not is_valid_client_metadata_url(client_metadata_url): + raise ValueError( + f"client_metadata_url must be a valid HTTPS URL with a non-root pathname, got: {client_metadata_url}" + ) + + self.context = OAuthContext( + server_url=server_url, + client_metadata=client_metadata, + storage=storage, + redirect_handler=redirect_handler, + callback_handler=callback_handler, + timeout=timeout, + client_metadata_url=client_metadata_url, + ) + self._initialized = False + + async def _handle_protected_resource_response(self, response: httpx.Response) -> bool: + """ + Handle protected resource metadata discovery response. + + Per SEP-985, supports fallback when discovery fails at one URL. + + Returns: + True if metadata was successfully discovered, False if we should try next URL + """ + if response.status_code == 200: + try: + content = await response.aread() + metadata = ProtectedResourceMetadata.model_validate_json(content) + self.context.protected_resource_metadata = metadata + if metadata.authorization_servers: # pragma: no branch + self.context.auth_server_url = str(metadata.authorization_servers[0]) + return True + + except ValidationError: # pragma: no cover + # Invalid metadata - try next URL + logger.warning(f"Invalid protected resource metadata at {response.request.url}") + return False + elif response.status_code == 404: # pragma: no cover + # Not found - try next URL in fallback chain + logger.debug(f"Protected resource metadata not found at {response.request.url}, trying next URL") + return False + else: + # Other error - fail immediately + raise OAuthFlowError( + f"Protected Resource Metadata request failed: {response.status_code}" + ) # pragma: no cover + + async def _perform_authorization(self) -> httpx.Request: + """Perform the authorization flow.""" + auth_code, code_verifier = await self._perform_authorization_code_grant() + token_request = await self._exchange_token_authorization_code(auth_code, code_verifier) + return token_request + + async def _perform_authorization_code_grant(self) -> tuple[str, str]: + """Perform the authorization redirect and get auth code.""" + if self.context.client_metadata.redirect_uris is None: + raise OAuthFlowError("No redirect URIs provided for authorization code grant") # pragma: no cover + if not self.context.redirect_handler: + raise OAuthFlowError("No redirect handler provided for authorization code grant") # pragma: no cover + if not self.context.callback_handler: + raise OAuthFlowError("No callback handler provided for authorization code grant") # pragma: no cover + + if self.context.oauth_metadata and self.context.oauth_metadata.authorization_endpoint: + auth_endpoint = str(self.context.oauth_metadata.authorization_endpoint) # pragma: no cover + else: + auth_base_url = self.context.get_authorization_base_url(self.context.server_url) + auth_endpoint = urljoin(auth_base_url, "/authorize") + + if not self.context.client_info: + raise OAuthFlowError("No client info available for authorization") # pragma: no cover + + # Generate PKCE parameters + pkce_params = PKCEParameters.generate() + state = secrets.token_urlsafe(32) + + auth_params = { + "response_type": "code", + "client_id": self.context.client_info.client_id, + "redirect_uri": str(self.context.client_metadata.redirect_uris[0]), + "state": state, + "code_challenge": pkce_params.code_challenge, + "code_challenge_method": "S256", + } + + # Only include resource param if conditions are met + if self.context.should_include_resource_param(self.context.protocol_version): + auth_params["resource"] = self.context.get_resource_url() # RFC 8707 # pragma: no cover + + if self.context.client_metadata.scope: # pragma: no branch + auth_params["scope"] = self.context.client_metadata.scope + + authorization_url = f"{auth_endpoint}?{urlencode(auth_params)}" + await self.context.redirect_handler(authorization_url) + + # Wait for callback + auth_code, returned_state = await self.context.callback_handler() + + if returned_state is None or not secrets.compare_digest(returned_state, state): + raise OAuthFlowError(f"State parameter mismatch: {returned_state} != {state}") # pragma: no cover + + if not auth_code: + raise OAuthFlowError("No authorization code received") # pragma: no cover + + # Return auth code and code verifier for token exchange + return auth_code, pkce_params.code_verifier + + def _get_token_endpoint(self) -> str: + if self.context.oauth_metadata and self.context.oauth_metadata.token_endpoint: + token_url = str(self.context.oauth_metadata.token_endpoint) + else: + auth_base_url = self.context.get_authorization_base_url(self.context.server_url) + token_url = urljoin(auth_base_url, "/token") + return token_url + + async def _exchange_token_authorization_code( + self, auth_code: str, code_verifier: str, *, token_data: dict[str, Any] | None = {} + ) -> httpx.Request: + """Build token exchange request for authorization_code flow.""" + if self.context.client_metadata.redirect_uris is None: + raise OAuthFlowError("No redirect URIs provided for authorization code grant") # pragma: no cover + if not self.context.client_info: + raise OAuthFlowError("Missing client info") # pragma: no cover + + token_url = self._get_token_endpoint() + token_data = token_data or {} + token_data.update( + { + "grant_type": "authorization_code", + "code": auth_code, + "redirect_uri": str(self.context.client_metadata.redirect_uris[0]), + "client_id": self.context.client_info.client_id, + "code_verifier": code_verifier, + } + ) + + # Only include resource param if conditions are met + if self.context.should_include_resource_param(self.context.protocol_version): + token_data["resource"] = self.context.get_resource_url() # RFC 8707 + + # Prepare authentication based on preferred method + headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_data, headers = self.context.prepare_token_auth(token_data, headers) + + return httpx.Request("POST", token_url, data=token_data, headers=headers) + + async def _handle_token_response(self, response: httpx.Response) -> None: + """Handle token exchange response.""" + if response.status_code != 200: + body = await response.aread() # pragma: no cover + body_text = body.decode("utf-8") # pragma: no cover + raise OAuthTokenError(f"Token exchange failed ({response.status_code}): {body_text}") # pragma: no cover + + # Parse and validate response with scope validation + token_response = await handle_token_response_scopes(response) + + # Store tokens in context + self.context.current_tokens = token_response + self.context.update_token_expiry(token_response) + await self.context.storage.set_tokens(token_response) + + async def _refresh_token(self) -> httpx.Request: + """Build token refresh request.""" + if not self.context.current_tokens or not self.context.current_tokens.refresh_token: + raise OAuthTokenError("No refresh token available") # pragma: no cover + + if not self.context.client_info or not self.context.client_info.client_id: + raise OAuthTokenError("No client info available") # pragma: no cover + + if self.context.oauth_metadata and self.context.oauth_metadata.token_endpoint: + token_url = str(self.context.oauth_metadata.token_endpoint) # pragma: no cover + else: + auth_base_url = self.context.get_authorization_base_url(self.context.server_url) + token_url = urljoin(auth_base_url, "/token") + + refresh_data: dict[str, str] = { + "grant_type": "refresh_token", + "refresh_token": self.context.current_tokens.refresh_token, + "client_id": self.context.client_info.client_id, + } + + # Only include resource param if conditions are met + if self.context.should_include_resource_param(self.context.protocol_version): + refresh_data["resource"] = self.context.get_resource_url() # RFC 8707 + + # Prepare authentication based on preferred method + headers = {"Content-Type": "application/x-www-form-urlencoded"} + refresh_data, headers = self.context.prepare_token_auth(refresh_data, headers) + + return httpx.Request("POST", token_url, data=refresh_data, headers=headers) + + async def _handle_refresh_response(self, response: httpx.Response) -> bool: # pragma: no cover + """Handle token refresh response. Returns True if successful.""" + if response.status_code != 200: + logger.warning(f"Token refresh failed: {response.status_code}") + self.context.clear_tokens() + return False + + try: + content = await response.aread() + token_response = OAuthToken.model_validate_json(content) + + self.context.current_tokens = token_response + self.context.update_token_expiry(token_response) + await self.context.storage.set_tokens(token_response) + + return True + except ValidationError: + logger.exception("Invalid refresh response") + self.context.clear_tokens() + return False + + async def _initialize(self) -> None: # pragma: no cover + """Load stored tokens and client info.""" + self.context.current_tokens = await self.context.storage.get_tokens() + self.context.client_info = await self.context.storage.get_client_info() + self._initialized = True + + def _add_auth_header(self, request: httpx.Request) -> None: + """Add authorization header to request if we have valid tokens.""" + if self.context.current_tokens and self.context.current_tokens.access_token: # pragma: no branch + request.headers["Authorization"] = f"Bearer {self.context.current_tokens.access_token}" + + async def _handle_oauth_metadata_response(self, response: httpx.Response) -> None: + content = await response.aread() + metadata = OAuthMetadata.model_validate_json(content) + self.context.oauth_metadata = metadata + + async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]: + """HTTPX auth flow integration.""" + async with self.context.lock: + if not self._initialized: + await self._initialize() # pragma: no cover + + # Capture protocol version from request headers + self.context.protocol_version = request.headers.get(MCP_PROTOCOL_VERSION) + + if not self.context.is_token_valid() and self.context.can_refresh_token(): + # Try to refresh token + refresh_request = await self._refresh_token() # pragma: no cover + refresh_response = yield refresh_request # pragma: no cover + + if not await self._handle_refresh_response(refresh_response): # pragma: no cover + # Refresh failed, need full re-authentication + self._initialized = False + + if self.context.is_token_valid(): + self._add_auth_header(request) + + response = yield request + + if response.status_code == 401: + # Perform full OAuth flow + try: + # OAuth flow must be inline due to generator constraints + www_auth_resource_metadata_url = extract_resource_metadata_from_www_auth(response) + + # Step 1: Discover protected resource metadata (SEP-985 with fallback support) + prm_discovery_urls = build_protected_resource_metadata_discovery_urls( + www_auth_resource_metadata_url, self.context.server_url + ) + + for url in prm_discovery_urls: # pragma: no branch + discovery_request = create_oauth_metadata_request(url) + + discovery_response = yield discovery_request # sending request + + prm = await handle_protected_resource_response(discovery_response) + if prm: + self.context.protected_resource_metadata = prm + + # todo: try all authorization_servers to find the OASM + assert ( + len(prm.authorization_servers) > 0 + ) # this is always true as authorization_servers has a min length of 1 + + self.context.auth_server_url = str(prm.authorization_servers[0]) + break + else: + logger.debug(f"Protected resource metadata discovery failed: {url}") + + asm_discovery_urls = build_oauth_authorization_server_metadata_discovery_urls( + self.context.auth_server_url, self.context.server_url + ) + + # Step 2: Discover OAuth Authorization Server Metadata (OASM) (with fallback for legacy servers) + for url in asm_discovery_urls: # pragma: no cover + oauth_metadata_request = create_oauth_metadata_request(url) + oauth_metadata_response = yield oauth_metadata_request + + ok, asm = await handle_auth_metadata_response(oauth_metadata_response) + if not ok: + break + if ok and asm: + self.context.oauth_metadata = asm + break + else: + logger.debug(f"OAuth metadata discovery failed: {url}") + + # Step 3: Apply scope selection strategy + self.context.client_metadata.scope = get_client_metadata_scopes( + extract_scope_from_www_auth(response), + self.context.protected_resource_metadata, + self.context.oauth_metadata, + ) + + # Step 4: Register client or use URL-based client ID (CIMD) + if not self.context.client_info: + if should_use_client_metadata_url( + self.context.oauth_metadata, self.context.client_metadata_url + ): + # Use URL-based client ID (CIMD) + logger.debug(f"Using URL-based client ID (CIMD): {self.context.client_metadata_url}") + client_information = create_client_info_from_metadata_url( + self.context.client_metadata_url, # type: ignore[arg-type] + redirect_uris=self.context.client_metadata.redirect_uris, + ) + self.context.client_info = client_information + await self.context.storage.set_client_info(client_information) + else: + # Fallback to Dynamic Client Registration + registration_request = create_client_registration_request( + self.context.oauth_metadata, + self.context.client_metadata, + self.context.get_authorization_base_url(self.context.server_url), + ) + registration_response = yield registration_request + client_information = await handle_registration_response(registration_response) + self.context.client_info = client_information + await self.context.storage.set_client_info(client_information) + + # Step 5: Perform authorization and complete token exchange + token_response = yield await self._perform_authorization() + await self._handle_token_response(token_response) + except Exception: # pragma: no cover + logger.exception("OAuth flow error") + raise + + # Retry with new tokens + self._add_auth_header(request) + yield request + elif response.status_code == 403: + # Step 1: Extract error field from WWW-Authenticate header + error = extract_field_from_www_auth(response, "error") + + # Step 2: Check if we need to step-up authorization + if error == "insufficient_scope": # pragma: no branch + try: + # Step 2a: Update the required scopes + self.context.client_metadata.scope = get_client_metadata_scopes( + extract_scope_from_www_auth(response), self.context.protected_resource_metadata + ) + + # Step 2b: Perform (re-)authorization and token exchange + token_response = yield await self._perform_authorization() + await self._handle_token_response(token_response) + except Exception: # pragma: no cover + logger.exception("OAuth flow error") + raise + + # Retry with new tokens + self._add_auth_header(request) + yield request diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py new file mode 100644 index 0000000..b4426be --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py @@ -0,0 +1,336 @@ +import logging +import re +from urllib.parse import urljoin, urlparse + +from httpx import Request, Response +from pydantic import AnyUrl, ValidationError + +from mcp.client.auth import OAuthRegistrationError, OAuthTokenError +from mcp.client.streamable_http import MCP_PROTOCOL_VERSION +from mcp.shared.auth import ( + OAuthClientInformationFull, + OAuthClientMetadata, + OAuthMetadata, + OAuthToken, + ProtectedResourceMetadata, +) +from mcp.types import LATEST_PROTOCOL_VERSION + +logger = logging.getLogger(__name__) + + +def extract_field_from_www_auth(response: Response, field_name: str) -> str | None: + """ + Extract field from WWW-Authenticate header. + + Returns: + Field value if found in WWW-Authenticate header, None otherwise + """ + www_auth_header = response.headers.get("WWW-Authenticate") + if not www_auth_header: + return None + + # Pattern matches: field_name="value" or field_name=value (unquoted) + pattern = rf'{field_name}=(?:"([^"]+)"|([^\s,]+))' + match = re.search(pattern, www_auth_header) + + if match: + # Return quoted value if present, otherwise unquoted value + return match.group(1) or match.group(2) + + return None + + +def extract_scope_from_www_auth(response: Response) -> str | None: + """ + Extract scope parameter from WWW-Authenticate header as per RFC6750. + + Returns: + Scope string if found in WWW-Authenticate header, None otherwise + """ + return extract_field_from_www_auth(response, "scope") + + +def extract_resource_metadata_from_www_auth(response: Response) -> str | None: + """ + Extract protected resource metadata URL from WWW-Authenticate header as per RFC9728. + + Returns: + Resource metadata URL if found in WWW-Authenticate header, None otherwise + """ + if not response or response.status_code != 401: + return None # pragma: no cover + + return extract_field_from_www_auth(response, "resource_metadata") + + +def build_protected_resource_metadata_discovery_urls(www_auth_url: str | None, server_url: str) -> list[str]: + """ + Build ordered list of URLs to try for protected resource metadata discovery. + + Per SEP-985, the client MUST: + 1. Try resource_metadata from WWW-Authenticate header (if present) + 2. Fall back to path-based well-known URI: /.well-known/oauth-protected-resource/{path} + 3. Fall back to root-based well-known URI: /.well-known/oauth-protected-resource + + Args: + www_auth_url: optional resource_metadata url extracted from the WWW-Authenticate header + server_url: server url + + Returns: + Ordered list of URLs to try for discovery + """ + urls: list[str] = [] + + # Priority 1: WWW-Authenticate header with resource_metadata parameter + if www_auth_url: + urls.append(www_auth_url) + + # Priority 2-3: Well-known URIs (RFC 9728) + parsed = urlparse(server_url) + base_url = f"{parsed.scheme}://{parsed.netloc}" + + # Priority 2: Path-based well-known URI (if server has a path component) + if parsed.path and parsed.path != "/": + path_based_url = urljoin(base_url, f"/.well-known/oauth-protected-resource{parsed.path}") + urls.append(path_based_url) + + # Priority 3: Root-based well-known URI + root_based_url = urljoin(base_url, "/.well-known/oauth-protected-resource") + urls.append(root_based_url) + + return urls + + +def get_client_metadata_scopes( + www_authenticate_scope: str | None, + protected_resource_metadata: ProtectedResourceMetadata | None, + authorization_server_metadata: OAuthMetadata | None = None, +) -> str | None: + """Select scopes as outlined in the 'Scope Selection Strategy' in the MCP spec.""" + # Per MCP spec, scope selection priority order: + # 1. Use scope from WWW-Authenticate header (if provided) + # 2. Use all scopes from PRM scopes_supported (if available) + # 3. Omit scope parameter if neither is available + + if www_authenticate_scope is not None: + # Priority 1: WWW-Authenticate header scope + return www_authenticate_scope + elif protected_resource_metadata is not None and protected_resource_metadata.scopes_supported is not None: + # Priority 2: PRM scopes_supported + return " ".join(protected_resource_metadata.scopes_supported) + elif authorization_server_metadata is not None and authorization_server_metadata.scopes_supported is not None: + return " ".join(authorization_server_metadata.scopes_supported) # pragma: no cover + else: + # Priority 3: Omit scope parameter + return None + + +def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]: + """ + Generate ordered list of (url, type) tuples for discovery attempts. + + Args: + auth_server_url: URL for the OAuth Authorization Metadata URL if found, otherwise None + server_url: URL for the MCP server, used as a fallback if auth_server_url is None + """ + + if not auth_server_url: + # Legacy path using the 2025-03-26 spec: + # link: https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization + parsed = urlparse(server_url) + return [f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-authorization-server"] + + urls: list[str] = [] + parsed = urlparse(auth_server_url) + base_url = f"{parsed.scheme}://{parsed.netloc}" + + # RFC 8414: Path-aware OAuth discovery + if parsed.path and parsed.path != "/": + oauth_path = f"/.well-known/oauth-authorization-server{parsed.path.rstrip('/')}" + urls.append(urljoin(base_url, oauth_path)) + + # RFC 8414 section 5: Path-aware OIDC discovery + # See https://www.rfc-editor.org/rfc/rfc8414.html#section-5 + oidc_path = f"/.well-known/openid-configuration{parsed.path.rstrip('/')}" + urls.append(urljoin(base_url, oidc_path)) + + # https://openid.net/specs/openid-connect-discovery-1_0.html + oidc_path = f"{parsed.path.rstrip('/')}/.well-known/openid-configuration" + urls.append(urljoin(base_url, oidc_path)) + return urls + + # OAuth root + urls.append(urljoin(base_url, "/.well-known/oauth-authorization-server")) + + # OIDC 1.0 fallback (appends to full URL per OIDC spec) + # https://openid.net/specs/openid-connect-discovery-1_0.html + urls.append(urljoin(base_url, "/.well-known/openid-configuration")) + + return urls + + +async def handle_protected_resource_response( + response: Response, +) -> ProtectedResourceMetadata | None: + """ + Handle protected resource metadata discovery response. + + Per SEP-985, supports fallback when discovery fails at one URL. + + Returns: + True if metadata was successfully discovered, False if we should try next URL + """ + if response.status_code == 200: + try: + content = await response.aread() + metadata = ProtectedResourceMetadata.model_validate_json(content) + return metadata + + except ValidationError: # pragma: no cover + # Invalid metadata - try next URL + return None + else: + # Not found - try next URL in fallback chain + return None + + +async def handle_auth_metadata_response(response: Response) -> tuple[bool, OAuthMetadata | None]: + if response.status_code == 200: + try: + content = await response.aread() + asm = OAuthMetadata.model_validate_json(content) + return True, asm + except ValidationError: # pragma: no cover + return True, None + elif response.status_code < 400 or response.status_code >= 500: + return False, None # Non-4XX error, stop trying + return True, None + + +def create_oauth_metadata_request(url: str) -> Request: + return Request("GET", url, headers={MCP_PROTOCOL_VERSION: LATEST_PROTOCOL_VERSION}) + + +def create_client_registration_request( + auth_server_metadata: OAuthMetadata | None, client_metadata: OAuthClientMetadata, auth_base_url: str +) -> Request: + """Build registration request or skip if already registered.""" + + if auth_server_metadata and auth_server_metadata.registration_endpoint: + registration_url = str(auth_server_metadata.registration_endpoint) + else: + registration_url = urljoin(auth_base_url, "/register") + + registration_data = client_metadata.model_dump(by_alias=True, mode="json", exclude_none=True) + + return Request("POST", registration_url, json=registration_data, headers={"Content-Type": "application/json"}) + + +async def handle_registration_response(response: Response) -> OAuthClientInformationFull: + """Handle registration response.""" + if response.status_code not in (200, 201): + await response.aread() + raise OAuthRegistrationError(f"Registration failed: {response.status_code} {response.text}") + + try: + content = await response.aread() + client_info = OAuthClientInformationFull.model_validate_json(content) + return client_info + # self.context.client_info = client_info + # await self.context.storage.set_client_info(client_info) + except ValidationError as e: # pragma: no cover + raise OAuthRegistrationError(f"Invalid registration response: {e}") + + +def is_valid_client_metadata_url(url: str | None) -> bool: + """Validate that a URL is suitable for use as a client_id (CIMD). + + The URL must be HTTPS with a non-root pathname. + + Args: + url: The URL to validate + + Returns: + True if the URL is a valid HTTPS URL with a non-root pathname + """ + if not url: + return False + try: + parsed = urlparse(url) + return parsed.scheme == "https" and parsed.path not in ("", "/") + except Exception: + return False + + +def should_use_client_metadata_url( + oauth_metadata: OAuthMetadata | None, + client_metadata_url: str | None, +) -> bool: + """Determine if URL-based client ID (CIMD) should be used instead of DCR. + + URL-based client IDs should be used when: + 1. The server advertises client_id_metadata_document_supported=true + 2. The client has a valid client_metadata_url configured + + Args: + oauth_metadata: OAuth authorization server metadata + client_metadata_url: URL-based client ID (already validated) + + Returns: + True if CIMD should be used, False if DCR should be used + """ + if not client_metadata_url: + return False + + if not oauth_metadata: + return False + + return oauth_metadata.client_id_metadata_document_supported is True + + +def create_client_info_from_metadata_url( + client_metadata_url: str, redirect_uris: list[AnyUrl] | None = None +) -> OAuthClientInformationFull: + """Create client information using a URL-based client ID (CIMD). + + When using URL-based client IDs, the URL itself becomes the client_id + and no client_secret is used (token_endpoint_auth_method="none"). + + Args: + client_metadata_url: The URL to use as the client_id + redirect_uris: The redirect URIs from the client metadata (passed through for + compatibility with OAuthClientInformationFull which inherits from OAuthClientMetadata) + + Returns: + OAuthClientInformationFull with the URL as client_id + """ + return OAuthClientInformationFull( + client_id=client_metadata_url, + token_endpoint_auth_method="none", + redirect_uris=redirect_uris, + ) + + +async def handle_token_response_scopes( + response: Response, +) -> OAuthToken: + """Parse and validate token response with optional scope validation. + + Parses token response JSON. Callers should check response.status_code before calling. + + Args: + response: HTTP response from token endpoint (status already checked by caller) + + Returns: + Validated OAuthToken model + + Raises: + OAuthTokenError: If response JSON is invalid + """ + try: + content = await response.aread() + token_response = OAuthToken.model_validate_json(content) + return token_response + except ValidationError as e: # pragma: no cover + raise OAuthTokenError(f"Invalid token response: {e}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py new file mode 100644 index 0000000..b6579b1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py @@ -0,0 +1,9 @@ +""" +Experimental client features. + +WARNING: These APIs are experimental and may change without notice. +""" + +from mcp.client.experimental.tasks import ExperimentalClientFeatures + +__all__ = ["ExperimentalClientFeatures"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py new file mode 100644 index 0000000..a475086 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py @@ -0,0 +1,290 @@ +""" +Experimental task handler protocols for server -> client requests. + +This module provides Protocol types and default handlers for when servers +send task-related requests to clients (the reverse of normal client -> server flow). + +WARNING: These APIs are experimental and may change without notice. + +Use cases: +- Server sends task-augmented sampling/elicitation request to client +- Client creates a local task, spawns background work, returns CreateTaskResult +- Server polls client's task status via tasks/get, tasks/result, etc. +""" + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Protocol + +from pydantic import TypeAdapter + +import mcp.types as types +from mcp.shared.context import RequestContext +from mcp.shared.session import RequestResponder + +if TYPE_CHECKING: + from mcp.client.session import ClientSession + + +class GetTaskHandlerFnT(Protocol): + """Handler for tasks/get requests from server. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.GetTaskRequestParams, + ) -> types.GetTaskResult | types.ErrorData: ... # pragma: no branch + + +class GetTaskResultHandlerFnT(Protocol): + """Handler for tasks/result requests from server. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.GetTaskPayloadRequestParams, + ) -> types.GetTaskPayloadResult | types.ErrorData: ... # pragma: no branch + + +class ListTasksHandlerFnT(Protocol): + """Handler for tasks/list requests from server. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.PaginatedRequestParams | None, + ) -> types.ListTasksResult | types.ErrorData: ... # pragma: no branch + + +class CancelTaskHandlerFnT(Protocol): + """Handler for tasks/cancel requests from server. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.CancelTaskRequestParams, + ) -> types.CancelTaskResult | types.ErrorData: ... # pragma: no branch + + +class TaskAugmentedSamplingFnT(Protocol): + """Handler for task-augmented sampling/createMessage requests from server. + + When server sends a CreateMessageRequest with task field, this callback + is invoked. The callback should create a task, spawn background work, + and return CreateTaskResult immediately. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.CreateMessageRequestParams, + task_metadata: types.TaskMetadata, + ) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch + + +class TaskAugmentedElicitationFnT(Protocol): + """Handler for task-augmented elicitation/create requests from server. + + When server sends an ElicitRequest with task field, this callback + is invoked. The callback should create a task, spawn background work, + and return CreateTaskResult immediately. + + WARNING: This is experimental and may change without notice. + """ + + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.ElicitRequestParams, + task_metadata: types.TaskMetadata, + ) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch + + +async def default_get_task_handler( + context: RequestContext["ClientSession", Any], + params: types.GetTaskRequestParams, +) -> types.GetTaskResult | types.ErrorData: + return types.ErrorData( + code=types.METHOD_NOT_FOUND, + message="tasks/get not supported", + ) + + +async def default_get_task_result_handler( + context: RequestContext["ClientSession", Any], + params: types.GetTaskPayloadRequestParams, +) -> types.GetTaskPayloadResult | types.ErrorData: + return types.ErrorData( + code=types.METHOD_NOT_FOUND, + message="tasks/result not supported", + ) + + +async def default_list_tasks_handler( + context: RequestContext["ClientSession", Any], + params: types.PaginatedRequestParams | None, +) -> types.ListTasksResult | types.ErrorData: + return types.ErrorData( + code=types.METHOD_NOT_FOUND, + message="tasks/list not supported", + ) + + +async def default_cancel_task_handler( + context: RequestContext["ClientSession", Any], + params: types.CancelTaskRequestParams, +) -> types.CancelTaskResult | types.ErrorData: + return types.ErrorData( + code=types.METHOD_NOT_FOUND, + message="tasks/cancel not supported", + ) + + +async def default_task_augmented_sampling( + context: RequestContext["ClientSession", Any], + params: types.CreateMessageRequestParams, + task_metadata: types.TaskMetadata, +) -> types.CreateTaskResult | types.ErrorData: + return types.ErrorData( + code=types.INVALID_REQUEST, + message="Task-augmented sampling not supported", + ) + + +async def default_task_augmented_elicitation( + context: RequestContext["ClientSession", Any], + params: types.ElicitRequestParams, + task_metadata: types.TaskMetadata, +) -> types.CreateTaskResult | types.ErrorData: + return types.ErrorData( + code=types.INVALID_REQUEST, + message="Task-augmented elicitation not supported", + ) + + +@dataclass +class ExperimentalTaskHandlers: + """Container for experimental task handlers. + + Groups all task-related handlers that handle server -> client requests. + This includes both pure task requests (get, list, cancel, result) and + task-augmented request handlers (sampling, elicitation with task field). + + WARNING: These APIs are experimental and may change without notice. + + Example: + handlers = ExperimentalTaskHandlers( + get_task=my_get_task_handler, + list_tasks=my_list_tasks_handler, + ) + session = ClientSession(..., experimental_task_handlers=handlers) + """ + + # Pure task request handlers + get_task: GetTaskHandlerFnT = field(default=default_get_task_handler) + get_task_result: GetTaskResultHandlerFnT = field(default=default_get_task_result_handler) + list_tasks: ListTasksHandlerFnT = field(default=default_list_tasks_handler) + cancel_task: CancelTaskHandlerFnT = field(default=default_cancel_task_handler) + + # Task-augmented request handlers + augmented_sampling: TaskAugmentedSamplingFnT = field(default=default_task_augmented_sampling) + augmented_elicitation: TaskAugmentedElicitationFnT = field(default=default_task_augmented_elicitation) + + def build_capability(self) -> types.ClientTasksCapability | None: + """Build ClientTasksCapability from the configured handlers. + + Returns a capability object that reflects which handlers are configured + (i.e., not using the default "not supported" handlers). + + Returns: + ClientTasksCapability if any handlers are provided, None otherwise + """ + has_list = self.list_tasks is not default_list_tasks_handler + has_cancel = self.cancel_task is not default_cancel_task_handler + has_sampling = self.augmented_sampling is not default_task_augmented_sampling + has_elicitation = self.augmented_elicitation is not default_task_augmented_elicitation + + # If no handlers are provided, return None + if not any([has_list, has_cancel, has_sampling, has_elicitation]): + return None + + # Build requests capability if any request handlers are provided + requests_capability: types.ClientTasksRequestsCapability | None = None + if has_sampling or has_elicitation: + requests_capability = types.ClientTasksRequestsCapability( + sampling=types.TasksSamplingCapability(createMessage=types.TasksCreateMessageCapability()) + if has_sampling + else None, + elicitation=types.TasksElicitationCapability(create=types.TasksCreateElicitationCapability()) + if has_elicitation + else None, + ) + + return types.ClientTasksCapability( + list=types.TasksListCapability() if has_list else None, + cancel=types.TasksCancelCapability() if has_cancel else None, + requests=requests_capability, + ) + + @staticmethod + def handles_request(request: types.ServerRequest) -> bool: + """Check if this handler handles the given request type.""" + return isinstance( + request.root, + types.GetTaskRequest | types.GetTaskPayloadRequest | types.ListTasksRequest | types.CancelTaskRequest, + ) + + async def handle_request( + self, + ctx: RequestContext["ClientSession", Any], + responder: RequestResponder[types.ServerRequest, types.ClientResult], + ) -> None: + """Handle a task-related request from the server. + + Call handles_request() first to check if this handler can handle the request. + """ + client_response_type: TypeAdapter[types.ClientResult | types.ErrorData] = TypeAdapter( + types.ClientResult | types.ErrorData + ) + + match responder.request.root: + case types.GetTaskRequest(params=params): + response = await self.get_task(ctx, params) + client_response = client_response_type.validate_python(response) + await responder.respond(client_response) + + case types.GetTaskPayloadRequest(params=params): + response = await self.get_task_result(ctx, params) + client_response = client_response_type.validate_python(response) + await responder.respond(client_response) + + case types.ListTasksRequest(params=params): + response = await self.list_tasks(ctx, params) + client_response = client_response_type.validate_python(response) + await responder.respond(client_response) + + case types.CancelTaskRequest(params=params): + response = await self.cancel_task(ctx, params) + client_response = client_response_type.validate_python(response) + await responder.respond(client_response) + + case _: # pragma: no cover + raise ValueError(f"Unhandled request type: {type(responder.request.root)}") + + +# Backwards compatibility aliases +default_task_augmented_sampling_callback = default_task_augmented_sampling +default_task_augmented_elicitation_callback = default_task_augmented_elicitation diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py new file mode 100644 index 0000000..ce9c387 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py @@ -0,0 +1,224 @@ +""" +Experimental client-side task support. + +This module provides client methods for interacting with MCP tasks. + +WARNING: These APIs are experimental and may change without notice. + +Example: + # Call a tool as a task + result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"}) + task_id = result.task.taskId + + # Get task status + status = await session.experimental.get_task(task_id) + + # Get task result when complete + if status.status == "completed": + result = await session.experimental.get_task_result(task_id, CallToolResult) + + # List all tasks + tasks = await session.experimental.list_tasks() + + # Cancel a task + await session.experimental.cancel_task(task_id) +""" + +from collections.abc import AsyncIterator +from typing import TYPE_CHECKING, Any, TypeVar + +import mcp.types as types +from mcp.shared.experimental.tasks.polling import poll_until_terminal + +if TYPE_CHECKING: + from mcp.client.session import ClientSession + +ResultT = TypeVar("ResultT", bound=types.Result) + + +class ExperimentalClientFeatures: + """ + Experimental client features for tasks and other experimental APIs. + + WARNING: These APIs are experimental and may change without notice. + + Access via session.experimental: + status = await session.experimental.get_task(task_id) + """ + + def __init__(self, session: "ClientSession") -> None: + self._session = session + + async def call_tool_as_task( + self, + name: str, + arguments: dict[str, Any] | None = None, + *, + ttl: int = 60000, + meta: dict[str, Any] | None = None, + ) -> types.CreateTaskResult: + """Call a tool as a task, returning a CreateTaskResult for polling. + + This is a convenience method for calling tools that support task execution. + The server will return a task reference instead of the immediate result, + which can then be polled via `get_task()` and retrieved via `get_task_result()`. + + Args: + name: The tool name + arguments: Tool arguments + ttl: Task time-to-live in milliseconds (default: 60000 = 1 minute) + meta: Optional metadata to include in the request + + Returns: + CreateTaskResult containing the task reference + + Example: + # Create task + result = await session.experimental.call_tool_as_task( + "long_running_tool", {"input": "data"} + ) + task_id = result.task.taskId + + # Poll for completion + while True: + status = await session.experimental.get_task(task_id) + if status.status == "completed": + break + await asyncio.sleep(0.5) + + # Get result + final = await session.experimental.get_task_result(task_id, CallToolResult) + """ + _meta: types.RequestParams.Meta | None = None + if meta is not None: + _meta = types.RequestParams.Meta(**meta) + + return await self._session.send_request( + types.ClientRequest( + types.CallToolRequest( + params=types.CallToolRequestParams( + name=name, + arguments=arguments, + task=types.TaskMetadata(ttl=ttl), + _meta=_meta, + ), + ) + ), + types.CreateTaskResult, + ) + + async def get_task(self, task_id: str) -> types.GetTaskResult: + """ + Get the current status of a task. + + Args: + task_id: The task identifier + + Returns: + GetTaskResult containing the task status and metadata + """ + return await self._session.send_request( + types.ClientRequest( + types.GetTaskRequest( + params=types.GetTaskRequestParams(taskId=task_id), + ) + ), + types.GetTaskResult, + ) + + async def get_task_result( + self, + task_id: str, + result_type: type[ResultT], + ) -> ResultT: + """ + Get the result of a completed task. + + The result type depends on the original request type: + - tools/call tasks return CallToolResult + - Other request types return their corresponding result type + + Args: + task_id: The task identifier + result_type: The expected result type (e.g., CallToolResult) + + Returns: + The task result, validated against result_type + """ + return await self._session.send_request( + types.ClientRequest( + types.GetTaskPayloadRequest( + params=types.GetTaskPayloadRequestParams(taskId=task_id), + ) + ), + result_type, + ) + + async def list_tasks( + self, + cursor: str | None = None, + ) -> types.ListTasksResult: + """ + List all tasks. + + Args: + cursor: Optional pagination cursor + + Returns: + ListTasksResult containing tasks and optional next cursor + """ + params = types.PaginatedRequestParams(cursor=cursor) if cursor else None + return await self._session.send_request( + types.ClientRequest( + types.ListTasksRequest(params=params), + ), + types.ListTasksResult, + ) + + async def cancel_task(self, task_id: str) -> types.CancelTaskResult: + """ + Cancel a running task. + + Args: + task_id: The task identifier + + Returns: + CancelTaskResult with the updated task state + """ + return await self._session.send_request( + types.ClientRequest( + types.CancelTaskRequest( + params=types.CancelTaskRequestParams(taskId=task_id), + ) + ), + types.CancelTaskResult, + ) + + async def poll_task(self, task_id: str) -> AsyncIterator[types.GetTaskResult]: + """ + Poll a task until it reaches a terminal status. + + Yields GetTaskResult for each poll, allowing the caller to react to + status changes (e.g., handle input_required). Exits when task reaches + a terminal status (completed, failed, cancelled). + + Respects the pollInterval hint from the server. + + Args: + task_id: The task identifier + + Yields: + GetTaskResult for each poll + + Example: + async for status in session.experimental.poll_task(task_id): + print(f"Status: {status.status}") + if status.status == "input_required": + # Handle elicitation request via tasks/result + pass + + # Task is now terminal, get the result + result = await session.experimental.get_task_result(task_id, CallToolResult) + """ + async for status in poll_until_terminal(self.get_task, task_id): + yield status diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py new file mode 100644 index 0000000..8519f15 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session.py @@ -0,0 +1,615 @@ +import logging +from datetime import timedelta +from typing import Any, Protocol, overload + +import anyio.lowlevel +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import AnyUrl, TypeAdapter +from typing_extensions import deprecated + +import mcp.types as types +from mcp.client.experimental import ExperimentalClientFeatures +from mcp.client.experimental.task_handlers import ExperimentalTaskHandlers +from mcp.shared.context import RequestContext +from mcp.shared.message import SessionMessage +from mcp.shared.session import BaseSession, ProgressFnT, RequestResponder +from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS + +DEFAULT_CLIENT_INFO = types.Implementation(name="mcp", version="0.1.0") + +logger = logging.getLogger("client") + + +class SamplingFnT(Protocol): + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.CreateMessageRequestParams, + ) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch + + +class ElicitationFnT(Protocol): + async def __call__( + self, + context: RequestContext["ClientSession", Any], + params: types.ElicitRequestParams, + ) -> types.ElicitResult | types.ErrorData: ... # pragma: no branch + + +class ListRootsFnT(Protocol): + async def __call__( + self, context: RequestContext["ClientSession", Any] + ) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch + + +class LoggingFnT(Protocol): + async def __call__( + self, + params: types.LoggingMessageNotificationParams, + ) -> None: ... # pragma: no branch + + +class MessageHandlerFnT(Protocol): + async def __call__( + self, + message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, + ) -> None: ... # pragma: no branch + + +async def _default_message_handler( + message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, +) -> None: + await anyio.lowlevel.checkpoint() + + +async def _default_sampling_callback( + context: RequestContext["ClientSession", Any], + params: types.CreateMessageRequestParams, +) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: + return types.ErrorData( + code=types.INVALID_REQUEST, + message="Sampling not supported", + ) + + +async def _default_elicitation_callback( + context: RequestContext["ClientSession", Any], + params: types.ElicitRequestParams, +) -> types.ElicitResult | types.ErrorData: + return types.ErrorData( # pragma: no cover + code=types.INVALID_REQUEST, + message="Elicitation not supported", + ) + + +async def _default_list_roots_callback( + context: RequestContext["ClientSession", Any], +) -> types.ListRootsResult | types.ErrorData: + return types.ErrorData( + code=types.INVALID_REQUEST, + message="List roots not supported", + ) + + +async def _default_logging_callback( + params: types.LoggingMessageNotificationParams, +) -> None: + pass + + +ClientResponse: TypeAdapter[types.ClientResult | types.ErrorData] = TypeAdapter(types.ClientResult | types.ErrorData) + + +class ClientSession( + BaseSession[ + types.ClientRequest, + types.ClientNotification, + types.ClientResult, + types.ServerRequest, + types.ServerNotification, + ] +): + def __init__( + self, + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception], + write_stream: MemoryObjectSendStream[SessionMessage], + read_timeout_seconds: timedelta | None = None, + sampling_callback: SamplingFnT | None = None, + elicitation_callback: ElicitationFnT | None = None, + list_roots_callback: ListRootsFnT | None = None, + logging_callback: LoggingFnT | None = None, + message_handler: MessageHandlerFnT | None = None, + client_info: types.Implementation | None = None, + *, + sampling_capabilities: types.SamplingCapability | None = None, + experimental_task_handlers: ExperimentalTaskHandlers | None = None, + ) -> None: + super().__init__( + read_stream, + write_stream, + types.ServerRequest, + types.ServerNotification, + read_timeout_seconds=read_timeout_seconds, + ) + self._client_info = client_info or DEFAULT_CLIENT_INFO + self._sampling_callback = sampling_callback or _default_sampling_callback + self._sampling_capabilities = sampling_capabilities + self._elicitation_callback = elicitation_callback or _default_elicitation_callback + self._list_roots_callback = list_roots_callback or _default_list_roots_callback + self._logging_callback = logging_callback or _default_logging_callback + self._message_handler = message_handler or _default_message_handler + self._tool_output_schemas: dict[str, dict[str, Any] | None] = {} + self._server_capabilities: types.ServerCapabilities | None = None + self._experimental_features: ExperimentalClientFeatures | None = None + + # Experimental: Task handlers (use defaults if not provided) + self._task_handlers = experimental_task_handlers or ExperimentalTaskHandlers() + + async def initialize(self) -> types.InitializeResult: + sampling = ( + (self._sampling_capabilities or types.SamplingCapability()) + if self._sampling_callback is not _default_sampling_callback + else None + ) + elicitation = ( + types.ElicitationCapability( + form=types.FormElicitationCapability(), + url=types.UrlElicitationCapability(), + ) + if self._elicitation_callback is not _default_elicitation_callback + else None + ) + roots = ( + # TODO: Should this be based on whether we + # _will_ send notifications, or only whether + # they're supported? + types.RootsCapability(listChanged=True) + if self._list_roots_callback is not _default_list_roots_callback + else None + ) + + result = await self.send_request( + types.ClientRequest( + types.InitializeRequest( + params=types.InitializeRequestParams( + protocolVersion=types.LATEST_PROTOCOL_VERSION, + capabilities=types.ClientCapabilities( + sampling=sampling, + elicitation=elicitation, + experimental=None, + roots=roots, + tasks=self._task_handlers.build_capability(), + ), + clientInfo=self._client_info, + ), + ) + ), + types.InitializeResult, + ) + + if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS: + raise RuntimeError(f"Unsupported protocol version from the server: {result.protocolVersion}") + + self._server_capabilities = result.capabilities + + await self.send_notification(types.ClientNotification(types.InitializedNotification())) + + return result + + def get_server_capabilities(self) -> types.ServerCapabilities | None: + """Return the server capabilities received during initialization. + + Returns None if the session has not been initialized yet. + """ + return self._server_capabilities + + @property + def experimental(self) -> ExperimentalClientFeatures: + """Experimental APIs for tasks and other features. + + WARNING: These APIs are experimental and may change without notice. + + Example: + status = await session.experimental.get_task(task_id) + result = await session.experimental.get_task_result(task_id, CallToolResult) + """ + if self._experimental_features is None: + self._experimental_features = ExperimentalClientFeatures(self) + return self._experimental_features + + async def send_ping(self) -> types.EmptyResult: + """Send a ping request.""" + return await self.send_request( + types.ClientRequest(types.PingRequest()), + types.EmptyResult, + ) + + async def send_progress_notification( + self, + progress_token: str | int, + progress: float, + total: float | None = None, + message: str | None = None, + ) -> None: + """Send a progress notification.""" + await self.send_notification( + types.ClientNotification( + types.ProgressNotification( + params=types.ProgressNotificationParams( + progressToken=progress_token, + progress=progress, + total=total, + message=message, + ), + ), + ) + ) + + async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResult: + """Send a logging/setLevel request.""" + return await self.send_request( # pragma: no cover + types.ClientRequest( + types.SetLevelRequest( + params=types.SetLevelRequestParams(level=level), + ) + ), + types.EmptyResult, + ) + + @overload + @deprecated("Use list_resources(params=PaginatedRequestParams(...)) instead") + async def list_resources(self, cursor: str | None) -> types.ListResourcesResult: ... + + @overload + async def list_resources(self, *, params: types.PaginatedRequestParams | None) -> types.ListResourcesResult: ... + + @overload + async def list_resources(self) -> types.ListResourcesResult: ... + + async def list_resources( + self, + cursor: str | None = None, + *, + params: types.PaginatedRequestParams | None = None, + ) -> types.ListResourcesResult: + """Send a resources/list request. + + Args: + cursor: Simple cursor string for pagination (deprecated, use params instead) + params: Full pagination parameters including cursor and any future fields + """ + if params is not None and cursor is not None: + raise ValueError("Cannot specify both cursor and params") + + if params is not None: + request_params = params + elif cursor is not None: + request_params = types.PaginatedRequestParams(cursor=cursor) + else: + request_params = None + + return await self.send_request( + types.ClientRequest(types.ListResourcesRequest(params=request_params)), + types.ListResourcesResult, + ) + + @overload + @deprecated("Use list_resource_templates(params=PaginatedRequestParams(...)) instead") + async def list_resource_templates(self, cursor: str | None) -> types.ListResourceTemplatesResult: ... + + @overload + async def list_resource_templates( + self, *, params: types.PaginatedRequestParams | None + ) -> types.ListResourceTemplatesResult: ... + + @overload + async def list_resource_templates(self) -> types.ListResourceTemplatesResult: ... + + async def list_resource_templates( + self, + cursor: str | None = None, + *, + params: types.PaginatedRequestParams | None = None, + ) -> types.ListResourceTemplatesResult: + """Send a resources/templates/list request. + + Args: + cursor: Simple cursor string for pagination (deprecated, use params instead) + params: Full pagination parameters including cursor and any future fields + """ + if params is not None and cursor is not None: + raise ValueError("Cannot specify both cursor and params") + + if params is not None: + request_params = params + elif cursor is not None: + request_params = types.PaginatedRequestParams(cursor=cursor) + else: + request_params = None + + return await self.send_request( + types.ClientRequest(types.ListResourceTemplatesRequest(params=request_params)), + types.ListResourceTemplatesResult, + ) + + async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult: + """Send a resources/read request.""" + return await self.send_request( + types.ClientRequest( + types.ReadResourceRequest( + params=types.ReadResourceRequestParams(uri=uri), + ) + ), + types.ReadResourceResult, + ) + + async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: + """Send a resources/subscribe request.""" + return await self.send_request( # pragma: no cover + types.ClientRequest( + types.SubscribeRequest( + params=types.SubscribeRequestParams(uri=uri), + ) + ), + types.EmptyResult, + ) + + async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: + """Send a resources/unsubscribe request.""" + return await self.send_request( # pragma: no cover + types.ClientRequest( + types.UnsubscribeRequest( + params=types.UnsubscribeRequestParams(uri=uri), + ) + ), + types.EmptyResult, + ) + + async def call_tool( + self, + name: str, + arguments: dict[str, Any] | None = None, + read_timeout_seconds: timedelta | None = None, + progress_callback: ProgressFnT | None = None, + *, + meta: dict[str, Any] | None = None, + ) -> types.CallToolResult: + """Send a tools/call request with optional progress callback support.""" + + _meta: types.RequestParams.Meta | None = None + if meta is not None: + _meta = types.RequestParams.Meta(**meta) + + result = await self.send_request( + types.ClientRequest( + types.CallToolRequest( + params=types.CallToolRequestParams(name=name, arguments=arguments, _meta=_meta), + ) + ), + types.CallToolResult, + request_read_timeout_seconds=read_timeout_seconds, + progress_callback=progress_callback, + ) + + if not result.isError: + await self._validate_tool_result(name, result) + + return result + + async def _validate_tool_result(self, name: str, result: types.CallToolResult) -> None: + """Validate the structured content of a tool result against its output schema.""" + if name not in self._tool_output_schemas: + # refresh output schema cache + await self.list_tools() + + output_schema = None + if name in self._tool_output_schemas: + output_schema = self._tool_output_schemas.get(name) + else: + logger.warning(f"Tool {name} not listed by server, cannot validate any structured content") + + if output_schema is not None: + from jsonschema import SchemaError, ValidationError, validate + + if result.structuredContent is None: + raise RuntimeError( + f"Tool {name} has an output schema but did not return structured content" + ) # pragma: no cover + try: + validate(result.structuredContent, output_schema) + except ValidationError as e: + raise RuntimeError(f"Invalid structured content returned by tool {name}: {e}") # pragma: no cover + except SchemaError as e: # pragma: no cover + raise RuntimeError(f"Invalid schema for tool {name}: {e}") # pragma: no cover + + @overload + @deprecated("Use list_prompts(params=PaginatedRequestParams(...)) instead") + async def list_prompts(self, cursor: str | None) -> types.ListPromptsResult: ... + + @overload + async def list_prompts(self, *, params: types.PaginatedRequestParams | None) -> types.ListPromptsResult: ... + + @overload + async def list_prompts(self) -> types.ListPromptsResult: ... + + async def list_prompts( + self, + cursor: str | None = None, + *, + params: types.PaginatedRequestParams | None = None, + ) -> types.ListPromptsResult: + """Send a prompts/list request. + + Args: + cursor: Simple cursor string for pagination (deprecated, use params instead) + params: Full pagination parameters including cursor and any future fields + """ + if params is not None and cursor is not None: + raise ValueError("Cannot specify both cursor and params") + + if params is not None: + request_params = params + elif cursor is not None: + request_params = types.PaginatedRequestParams(cursor=cursor) + else: + request_params = None + + return await self.send_request( + types.ClientRequest(types.ListPromptsRequest(params=request_params)), + types.ListPromptsResult, + ) + + async def get_prompt(self, name: str, arguments: dict[str, str] | None = None) -> types.GetPromptResult: + """Send a prompts/get request.""" + return await self.send_request( + types.ClientRequest( + types.GetPromptRequest( + params=types.GetPromptRequestParams(name=name, arguments=arguments), + ) + ), + types.GetPromptResult, + ) + + async def complete( + self, + ref: types.ResourceTemplateReference | types.PromptReference, + argument: dict[str, str], + context_arguments: dict[str, str] | None = None, + ) -> types.CompleteResult: + """Send a completion/complete request.""" + context = None + if context_arguments is not None: + context = types.CompletionContext(arguments=context_arguments) + + return await self.send_request( + types.ClientRequest( + types.CompleteRequest( + params=types.CompleteRequestParams( + ref=ref, + argument=types.CompletionArgument(**argument), + context=context, + ), + ) + ), + types.CompleteResult, + ) + + @overload + @deprecated("Use list_tools(params=PaginatedRequestParams(...)) instead") + async def list_tools(self, cursor: str | None) -> types.ListToolsResult: ... + + @overload + async def list_tools(self, *, params: types.PaginatedRequestParams | None) -> types.ListToolsResult: ... + + @overload + async def list_tools(self) -> types.ListToolsResult: ... + + async def list_tools( + self, + cursor: str | None = None, + *, + params: types.PaginatedRequestParams | None = None, + ) -> types.ListToolsResult: + """Send a tools/list request. + + Args: + cursor: Simple cursor string for pagination (deprecated, use params instead) + params: Full pagination parameters including cursor and any future fields + """ + if params is not None and cursor is not None: + raise ValueError("Cannot specify both cursor and params") + + if params is not None: + request_params = params + elif cursor is not None: + request_params = types.PaginatedRequestParams(cursor=cursor) + else: + request_params = None + + result = await self.send_request( + types.ClientRequest(types.ListToolsRequest(params=request_params)), + types.ListToolsResult, + ) + + # Cache tool output schemas for future validation + # Note: don't clear the cache, as we may be using a cursor + for tool in result.tools: + self._tool_output_schemas[tool.name] = tool.outputSchema + + return result + + async def send_roots_list_changed(self) -> None: # pragma: no cover + """Send a roots/list_changed notification.""" + await self.send_notification(types.ClientNotification(types.RootsListChangedNotification())) + + async def _received_request(self, responder: RequestResponder[types.ServerRequest, types.ClientResult]) -> None: + ctx = RequestContext[ClientSession, Any]( + request_id=responder.request_id, + meta=responder.request_meta, + session=self, + lifespan_context=None, + ) + + # Delegate to experimental task handler if applicable + if self._task_handlers.handles_request(responder.request): + with responder: + await self._task_handlers.handle_request(ctx, responder) + return None + + # Core request handling + match responder.request.root: + case types.CreateMessageRequest(params=params): + with responder: + # Check if this is a task-augmented request + if params.task is not None: + response = await self._task_handlers.augmented_sampling(ctx, params, params.task) + else: + response = await self._sampling_callback(ctx, params) + client_response = ClientResponse.validate_python(response) + await responder.respond(client_response) + + case types.ElicitRequest(params=params): + with responder: + # Check if this is a task-augmented request + if params.task is not None: + response = await self._task_handlers.augmented_elicitation(ctx, params, params.task) + else: + response = await self._elicitation_callback(ctx, params) + client_response = ClientResponse.validate_python(response) + await responder.respond(client_response) + + case types.ListRootsRequest(): + with responder: + response = await self._list_roots_callback(ctx) + client_response = ClientResponse.validate_python(response) + await responder.respond(client_response) + + case types.PingRequest(): # pragma: no cover + with responder: + return await responder.respond(types.ClientResult(root=types.EmptyResult())) + + case _: # pragma: no cover + pass # Task requests handled above by _task_handlers + + return None + + async def _handle_incoming( + self, + req: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, + ) -> None: + """Handle incoming messages by forwarding to the message handler.""" + await self._message_handler(req) + + async def _received_notification(self, notification: types.ServerNotification) -> None: + """Handle notifications from the server.""" + # Process specific notification types + match notification.root: + case types.LoggingMessageNotification(params=params): + await self._logging_callback(params) + case types.ElicitCompleteNotification(params=params): + # Handle elicitation completion notification + # Clients MAY use this to retry requests or update UI + # The notification contains the elicitationId of the completed elicitation + pass + case _: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py new file mode 100644 index 0000000..f82677d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py @@ -0,0 +1,447 @@ +""" +SessionGroup concurrently manages multiple MCP session connections. + +Tools, resources, and prompts are aggregated across servers. Servers may +be connected to or disconnected from at any point after initialization. + +This abstractions can handle naming collisions using a custom user-provided +hook. +""" + +import contextlib +import logging +from collections.abc import Callable +from dataclasses import dataclass +from datetime import timedelta +from types import TracebackType +from typing import Any, TypeAlias, overload + +import anyio +import httpx +from pydantic import BaseModel +from typing_extensions import Self, deprecated + +import mcp +from mcp import types +from mcp.client.session import ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT +from mcp.client.sse import sse_client +from mcp.client.stdio import StdioServerParameters +from mcp.client.streamable_http import streamable_http_client +from mcp.shared._httpx_utils import create_mcp_http_client +from mcp.shared.exceptions import McpError +from mcp.shared.session import ProgressFnT + + +class SseServerParameters(BaseModel): + """Parameters for intializing a sse_client.""" + + # The endpoint URL. + url: str + + # Optional headers to include in requests. + headers: dict[str, Any] | None = None + + # HTTP timeout for regular operations. + timeout: float = 5 + + # Timeout for SSE read operations. + sse_read_timeout: float = 60 * 5 + + +class StreamableHttpParameters(BaseModel): + """Parameters for intializing a streamable_http_client.""" + + # The endpoint URL. + url: str + + # Optional headers to include in requests. + headers: dict[str, Any] | None = None + + # HTTP timeout for regular operations. + timeout: timedelta = timedelta(seconds=30) + + # Timeout for SSE read operations. + sse_read_timeout: timedelta = timedelta(seconds=60 * 5) + + # Close the client session when the transport closes. + terminate_on_close: bool = True + + +ServerParameters: TypeAlias = StdioServerParameters | SseServerParameters | StreamableHttpParameters + + +# Use dataclass instead of pydantic BaseModel +# because pydantic BaseModel cannot handle Protocol fields. +@dataclass +class ClientSessionParameters: + """Parameters for establishing a client session to an MCP server.""" + + read_timeout_seconds: timedelta | None = None + sampling_callback: SamplingFnT | None = None + elicitation_callback: ElicitationFnT | None = None + list_roots_callback: ListRootsFnT | None = None + logging_callback: LoggingFnT | None = None + message_handler: MessageHandlerFnT | None = None + client_info: types.Implementation | None = None + + +class ClientSessionGroup: + """Client for managing connections to multiple MCP servers. + + This class is responsible for encapsulating management of server connections. + It aggregates tools, resources, and prompts from all connected servers. + + For auxiliary handlers, such as resource subscription, this is delegated to + the client and can be accessed via the session. + + Example Usage: + name_fn = lambda name, server_info: f"{(server_info.name)}_{name}" + async with ClientSessionGroup(component_name_hook=name_fn) as group: + for server_param in server_params: + await group.connect_to_server(server_param) + ... + + """ + + class _ComponentNames(BaseModel): + """Used for reverse index to find components.""" + + prompts: set[str] = set() + resources: set[str] = set() + tools: set[str] = set() + + # Standard MCP components. + _prompts: dict[str, types.Prompt] + _resources: dict[str, types.Resource] + _tools: dict[str, types.Tool] + + # Client-server connection management. + _sessions: dict[mcp.ClientSession, _ComponentNames] + _tool_to_session: dict[str, mcp.ClientSession] + _exit_stack: contextlib.AsyncExitStack + _session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack] + + # Optional fn consuming (component_name, serverInfo) for custom names. + # This is provide a means to mitigate naming conflicts across servers. + # Example: (tool_name, serverInfo) => "{result.serverInfo.name}.{tool_name}" + _ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str] + _component_name_hook: _ComponentNameHook | None + + def __init__( + self, + exit_stack: contextlib.AsyncExitStack | None = None, + component_name_hook: _ComponentNameHook | None = None, + ) -> None: + """Initializes the MCP client.""" + + self._tools = {} + self._resources = {} + self._prompts = {} + + self._sessions = {} + self._tool_to_session = {} + if exit_stack is None: + self._exit_stack = contextlib.AsyncExitStack() + self._owns_exit_stack = True + else: + self._exit_stack = exit_stack + self._owns_exit_stack = False + self._session_exit_stacks = {} + self._component_name_hook = component_name_hook + + async def __aenter__(self) -> Self: # pragma: no cover + # Enter the exit stack only if we created it ourselves + if self._owns_exit_stack: + await self._exit_stack.__aenter__() + return self + + async def __aexit__( + self, + _exc_type: type[BaseException] | None, + _exc_val: BaseException | None, + _exc_tb: TracebackType | None, + ) -> bool | None: # pragma: no cover + """Closes session exit stacks and main exit stack upon completion.""" + + # Only close the main exit stack if we created it + if self._owns_exit_stack: + await self._exit_stack.aclose() + + # Concurrently close session stacks. + async with anyio.create_task_group() as tg: + for exit_stack in self._session_exit_stacks.values(): + tg.start_soon(exit_stack.aclose) + + @property + def sessions(self) -> list[mcp.ClientSession]: + """Returns the list of sessions being managed.""" + return list(self._sessions.keys()) # pragma: no cover + + @property + def prompts(self) -> dict[str, types.Prompt]: + """Returns the prompts as a dictionary of names to prompts.""" + return self._prompts + + @property + def resources(self) -> dict[str, types.Resource]: + """Returns the resources as a dictionary of names to resources.""" + return self._resources + + @property + def tools(self) -> dict[str, types.Tool]: + """Returns the tools as a dictionary of names to tools.""" + return self._tools + + @overload + async def call_tool( + self, + name: str, + arguments: dict[str, Any], + read_timeout_seconds: timedelta | None = None, + progress_callback: ProgressFnT | None = None, + *, + meta: dict[str, Any] | None = None, + ) -> types.CallToolResult: ... + + @overload + @deprecated("The 'args' parameter is deprecated. Use 'arguments' instead.") + async def call_tool( + self, + name: str, + *, + args: dict[str, Any], + read_timeout_seconds: timedelta | None = None, + progress_callback: ProgressFnT | None = None, + meta: dict[str, Any] | None = None, + ) -> types.CallToolResult: ... + + async def call_tool( + self, + name: str, + arguments: dict[str, Any] | None = None, + read_timeout_seconds: timedelta | None = None, + progress_callback: ProgressFnT | None = None, + *, + meta: dict[str, Any] | None = None, + args: dict[str, Any] | None = None, + ) -> types.CallToolResult: + """Executes a tool given its name and arguments.""" + session = self._tool_to_session[name] + session_tool_name = self.tools[name].name + return await session.call_tool( + session_tool_name, + arguments if args is None else args, + read_timeout_seconds=read_timeout_seconds, + progress_callback=progress_callback, + meta=meta, + ) + + async def disconnect_from_server(self, session: mcp.ClientSession) -> None: + """Disconnects from a single MCP server.""" + + session_known_for_components = session in self._sessions + session_known_for_stack = session in self._session_exit_stacks + + if not session_known_for_components and not session_known_for_stack: + raise McpError( + types.ErrorData( + code=types.INVALID_PARAMS, + message="Provided session is not managed or already disconnected.", + ) + ) + + if session_known_for_components: # pragma: no cover + component_names = self._sessions.pop(session) # Pop from _sessions tracking + + # Remove prompts associated with the session. + for name in component_names.prompts: + if name in self._prompts: + del self._prompts[name] + # Remove resources associated with the session. + for name in component_names.resources: + if name in self._resources: + del self._resources[name] + # Remove tools associated with the session. + for name in component_names.tools: + if name in self._tools: + del self._tools[name] + if name in self._tool_to_session: + del self._tool_to_session[name] + + # Clean up the session's resources via its dedicated exit stack + if session_known_for_stack: + session_stack_to_close = self._session_exit_stacks.pop(session) # pragma: no cover + await session_stack_to_close.aclose() # pragma: no cover + + async def connect_with_session( + self, server_info: types.Implementation, session: mcp.ClientSession + ) -> mcp.ClientSession: + """Connects to a single MCP server.""" + await self._aggregate_components(server_info, session) + return session + + async def connect_to_server( + self, + server_params: ServerParameters, + session_params: ClientSessionParameters | None = None, + ) -> mcp.ClientSession: + """Connects to a single MCP server.""" + server_info, session = await self._establish_session(server_params, session_params or ClientSessionParameters()) + return await self.connect_with_session(server_info, session) + + async def _establish_session( + self, + server_params: ServerParameters, + session_params: ClientSessionParameters, + ) -> tuple[types.Implementation, mcp.ClientSession]: + """Establish a client session to an MCP server.""" + + session_stack = contextlib.AsyncExitStack() + try: + # Create read and write streams that facilitate io with the server. + if isinstance(server_params, StdioServerParameters): + client = mcp.stdio_client(server_params) + read, write = await session_stack.enter_async_context(client) + elif isinstance(server_params, SseServerParameters): + client = sse_client( + url=server_params.url, + headers=server_params.headers, + timeout=server_params.timeout, + sse_read_timeout=server_params.sse_read_timeout, + ) + read, write = await session_stack.enter_async_context(client) + else: + httpx_client = create_mcp_http_client( + headers=server_params.headers, + timeout=httpx.Timeout( + server_params.timeout.total_seconds(), + read=server_params.sse_read_timeout.total_seconds(), + ), + ) + await session_stack.enter_async_context(httpx_client) + + client = streamable_http_client( + url=server_params.url, + http_client=httpx_client, + terminate_on_close=server_params.terminate_on_close, + ) + read, write, _ = await session_stack.enter_async_context(client) + + session = await session_stack.enter_async_context( + mcp.ClientSession( + read, + write, + read_timeout_seconds=session_params.read_timeout_seconds, + sampling_callback=session_params.sampling_callback, + elicitation_callback=session_params.elicitation_callback, + list_roots_callback=session_params.list_roots_callback, + logging_callback=session_params.logging_callback, + message_handler=session_params.message_handler, + client_info=session_params.client_info, + ) + ) + + result = await session.initialize() + + # Session successfully initialized. + # Store its stack and register the stack with the main group stack. + self._session_exit_stacks[session] = session_stack + # session_stack itself becomes a resource managed by the + # main _exit_stack. + await self._exit_stack.enter_async_context(session_stack) + + return result.serverInfo, session + except Exception: # pragma: no cover + # If anything during this setup fails, ensure the session-specific + # stack is closed. + await session_stack.aclose() + raise + + async def _aggregate_components(self, server_info: types.Implementation, session: mcp.ClientSession) -> None: + """Aggregates prompts, resources, and tools from a given session.""" + + # Create a reverse index so we can find all prompts, resources, and + # tools belonging to this session. Used for removing components from + # the session group via self.disconnect_from_server. + component_names = self._ComponentNames() + + # Temporary components dicts. We do not want to modify the aggregate + # lists in case of an intermediate failure. + prompts_temp: dict[str, types.Prompt] = {} + resources_temp: dict[str, types.Resource] = {} + tools_temp: dict[str, types.Tool] = {} + tool_to_session_temp: dict[str, mcp.ClientSession] = {} + + # Query the server for its prompts and aggregate to list. + try: + prompts = (await session.list_prompts()).prompts + for prompt in prompts: + name = self._component_name(prompt.name, server_info) + prompts_temp[name] = prompt + component_names.prompts.add(name) + except McpError as err: # pragma: no cover + logging.warning(f"Could not fetch prompts: {err}") + + # Query the server for its resources and aggregate to list. + try: + resources = (await session.list_resources()).resources + for resource in resources: + name = self._component_name(resource.name, server_info) + resources_temp[name] = resource + component_names.resources.add(name) + except McpError as err: # pragma: no cover + logging.warning(f"Could not fetch resources: {err}") + + # Query the server for its tools and aggregate to list. + try: + tools = (await session.list_tools()).tools + for tool in tools: + name = self._component_name(tool.name, server_info) + tools_temp[name] = tool + tool_to_session_temp[name] = session + component_names.tools.add(name) + except McpError as err: # pragma: no cover + logging.warning(f"Could not fetch tools: {err}") + + # Clean up exit stack for session if we couldn't retrieve anything + # from the server. + if not any((prompts_temp, resources_temp, tools_temp)): + del self._session_exit_stacks[session] # pragma: no cover + + # Check for duplicates. + matching_prompts = prompts_temp.keys() & self._prompts.keys() + if matching_prompts: + raise McpError( # pragma: no cover + types.ErrorData( + code=types.INVALID_PARAMS, + message=f"{matching_prompts} already exist in group prompts.", + ) + ) + matching_resources = resources_temp.keys() & self._resources.keys() + if matching_resources: + raise McpError( # pragma: no cover + types.ErrorData( + code=types.INVALID_PARAMS, + message=f"{matching_resources} already exist in group resources.", + ) + ) + matching_tools = tools_temp.keys() & self._tools.keys() + if matching_tools: + raise McpError( + types.ErrorData( + code=types.INVALID_PARAMS, + message=f"{matching_tools} already exist in group tools.", + ) + ) + + # Aggregate components. + self._sessions[session] = component_names + self._prompts.update(prompts_temp) + self._resources.update(resources_temp) + self._tools.update(tools_temp) + self._tool_to_session.update(tool_to_session_temp) + + def _component_name(self, name: str, server_info: types.Implementation) -> str: + if self._component_name_hook: + return self._component_name_hook(name, server_info) + return name diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py new file mode 100644 index 0000000..b2ac677 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py @@ -0,0 +1,164 @@ +import logging +from collections.abc import Callable +from contextlib import asynccontextmanager +from typing import Any +from urllib.parse import parse_qs, urljoin, urlparse + +import anyio +import httpx +from anyio.abc import TaskStatus +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from httpx_sse import aconnect_sse +from httpx_sse._exceptions import SSEError + +import mcp.types as types +from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client +from mcp.shared.message import SessionMessage + +logger = logging.getLogger(__name__) + + +def remove_request_params(url: str) -> str: + return urljoin(url, urlparse(url).path) + + +def _extract_session_id_from_endpoint(endpoint_url: str) -> str | None: + query_params = parse_qs(urlparse(endpoint_url).query) + return query_params.get("sessionId", [None])[0] or query_params.get("session_id", [None])[0] + + +@asynccontextmanager +async def sse_client( + url: str, + headers: dict[str, Any] | None = None, + timeout: float = 5, + sse_read_timeout: float = 60 * 5, + httpx_client_factory: McpHttpClientFactory = create_mcp_http_client, + auth: httpx.Auth | None = None, + on_session_created: Callable[[str], None] | None = None, +): + """ + Client transport for SSE. + + `sse_read_timeout` determines how long (in seconds) the client will wait for a new + event before disconnecting. All other HTTP operations are controlled by `timeout`. + + Args: + url: The SSE endpoint URL. + headers: Optional headers to include in requests. + timeout: HTTP timeout for regular operations. + sse_read_timeout: Timeout for SSE read operations. + auth: Optional HTTPX authentication handler. + on_session_created: Optional callback invoked with the session ID when received. + """ + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + async with anyio.create_task_group() as tg: + try: + logger.debug(f"Connecting to SSE endpoint: {remove_request_params(url)}") + async with httpx_client_factory( + headers=headers, auth=auth, timeout=httpx.Timeout(timeout, read=sse_read_timeout) + ) as client: + async with aconnect_sse( + client, + "GET", + url, + ) as event_source: + event_source.response.raise_for_status() + logger.debug("SSE connection established") + + async def sse_reader( + task_status: TaskStatus[str] = anyio.TASK_STATUS_IGNORED, + ): + try: + async for sse in event_source.aiter_sse(): # pragma: no branch + logger.debug(f"Received SSE event: {sse.event}") + match sse.event: + case "endpoint": + endpoint_url = urljoin(url, sse.data) + logger.debug(f"Received endpoint URL: {endpoint_url}") + + url_parsed = urlparse(url) + endpoint_parsed = urlparse(endpoint_url) + if ( # pragma: no cover + url_parsed.netloc != endpoint_parsed.netloc + or url_parsed.scheme != endpoint_parsed.scheme + ): + error_msg = ( # pragma: no cover + f"Endpoint origin does not match connection origin: {endpoint_url}" + ) + logger.error(error_msg) # pragma: no cover + raise ValueError(error_msg) # pragma: no cover + + if on_session_created: + session_id = _extract_session_id_from_endpoint(endpoint_url) + if session_id: + on_session_created(session_id) + + task_status.started(endpoint_url) + + case "message": + # Skip empty data (keep-alive pings) + if not sse.data: + continue + try: + message = types.JSONRPCMessage.model_validate_json( # noqa: E501 + sse.data + ) + logger.debug(f"Received server message: {message}") + except Exception as exc: # pragma: no cover + logger.exception("Error parsing server message") # pragma: no cover + await read_stream_writer.send(exc) # pragma: no cover + continue # pragma: no cover + + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + case _: # pragma: no cover + logger.warning(f"Unknown SSE event: {sse.event}") # pragma: no cover + except SSEError as sse_exc: # pragma: no cover + logger.exception("Encountered SSE exception") # pragma: no cover + raise sse_exc # pragma: no cover + except Exception as exc: # pragma: no cover + logger.exception("Error in sse_reader") # pragma: no cover + await read_stream_writer.send(exc) # pragma: no cover + finally: + await read_stream_writer.aclose() + + async def post_writer(endpoint_url: str): + try: + async with write_stream_reader: + async for session_message in write_stream_reader: + logger.debug(f"Sending client message: {session_message}") + response = await client.post( + endpoint_url, + json=session_message.message.model_dump( + by_alias=True, + mode="json", + exclude_none=True, + ), + ) + response.raise_for_status() + logger.debug(f"Client message sent successfully: {response.status_code}") + except Exception: # pragma: no cover + logger.exception("Error in post_writer") # pragma: no cover + finally: + await write_stream.aclose() + + endpoint_url = await tg.start(sse_reader) + logger.debug(f"Starting post writer with endpoint URL: {endpoint_url}") + tg.start_soon(post_writer, endpoint_url) + + try: + yield read_stream, write_stream + finally: + tg.cancel_scope.cancel() + finally: + await read_stream_writer.aclose() + await write_stream.aclose() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py new file mode 100644 index 0000000..0d76bb9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py @@ -0,0 +1,278 @@ +import logging +import os +import sys +from contextlib import asynccontextmanager +from pathlib import Path +from typing import Literal, TextIO + +import anyio +import anyio.lowlevel +from anyio.abc import Process +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from anyio.streams.text import TextReceiveStream +from pydantic import BaseModel, Field + +import mcp.types as types +from mcp.os.posix.utilities import terminate_posix_process_tree +from mcp.os.win32.utilities import ( + FallbackProcess, + create_windows_process, + get_windows_executable_command, + terminate_windows_process_tree, +) +from mcp.shared.message import SessionMessage + +logger = logging.getLogger(__name__) + +# Environment variables to inherit by default +DEFAULT_INHERITED_ENV_VARS = ( + [ + "APPDATA", + "HOMEDRIVE", + "HOMEPATH", + "LOCALAPPDATA", + "PATH", + "PATHEXT", + "PROCESSOR_ARCHITECTURE", + "SYSTEMDRIVE", + "SYSTEMROOT", + "TEMP", + "USERNAME", + "USERPROFILE", + ] + if sys.platform == "win32" + else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"] +) + +# Timeout for process termination before falling back to force kill +PROCESS_TERMINATION_TIMEOUT = 2.0 + + +def get_default_environment() -> dict[str, str]: + """ + Returns a default environment object including only environment variables deemed + safe to inherit. + """ + env: dict[str, str] = {} + + for key in DEFAULT_INHERITED_ENV_VARS: + value = os.environ.get(key) + if value is None: + continue # pragma: no cover + + if value.startswith("()"): # pragma: no cover + # Skip functions, which are a security risk + continue # pragma: no cover + + env[key] = value + + return env + + +class StdioServerParameters(BaseModel): + command: str + """The executable to run to start the server.""" + + args: list[str] = Field(default_factory=list) + """Command line arguments to pass to the executable.""" + + env: dict[str, str] | None = None + """ + The environment to use when spawning the process. + + If not specified, the result of get_default_environment() will be used. + """ + + cwd: str | Path | None = None + """The working directory to use when spawning the process.""" + + encoding: str = "utf-8" + """ + The text encoding used when sending/receiving messages to the server + + defaults to utf-8 + """ + + encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict" + """ + The text encoding error handler. + + See https://docs.python.org/3/library/codecs.html#codec-base-classes for + explanations of possible values + """ + + +@asynccontextmanager +async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stderr): + """ + Client transport for stdio: this will connect to a server by spawning a + process and communicating with it over stdin/stdout. + """ + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + try: + command = _get_executable_command(server.command) + + # Open process with stderr piped for capture + process = await _create_platform_compatible_process( + command=command, + args=server.args, + env=({**get_default_environment(), **server.env} if server.env is not None else get_default_environment()), + errlog=errlog, + cwd=server.cwd, + ) + except OSError: + # Clean up streams if process creation fails + await read_stream.aclose() + await write_stream.aclose() + await read_stream_writer.aclose() + await write_stream_reader.aclose() + raise + + async def stdout_reader(): + assert process.stdout, "Opened process is missing stdout" + + try: + async with read_stream_writer: + buffer = "" + async for chunk in TextReceiveStream( + process.stdout, + encoding=server.encoding, + errors=server.encoding_error_handler, + ): + lines = (buffer + chunk).split("\n") + buffer = lines.pop() + + for line in lines: + try: + message = types.JSONRPCMessage.model_validate_json(line) + except Exception as exc: # pragma: no cover + logger.exception("Failed to parse JSONRPC message from server") + await read_stream_writer.send(exc) + continue + + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + except anyio.ClosedResourceError: # pragma: no cover + await anyio.lowlevel.checkpoint() + + async def stdin_writer(): + assert process.stdin, "Opened process is missing stdin" + + try: + async with write_stream_reader: + async for session_message in write_stream_reader: + json = session_message.message.model_dump_json(by_alias=True, exclude_none=True) + await process.stdin.send( + (json + "\n").encode( + encoding=server.encoding, + errors=server.encoding_error_handler, + ) + ) + except anyio.ClosedResourceError: # pragma: no cover + await anyio.lowlevel.checkpoint() + + async with ( + anyio.create_task_group() as tg, + process, + ): + tg.start_soon(stdout_reader) + tg.start_soon(stdin_writer) + try: + yield read_stream, write_stream + finally: + # MCP spec: stdio shutdown sequence + # 1. Close input stream to server + # 2. Wait for server to exit, or send SIGTERM if it doesn't exit in time + # 3. Send SIGKILL if still not exited + if process.stdin: # pragma: no branch + try: + await process.stdin.aclose() + except Exception: # pragma: no cover + # stdin might already be closed, which is fine + pass + + try: + # Give the process time to exit gracefully after stdin closes + with anyio.fail_after(PROCESS_TERMINATION_TIMEOUT): + await process.wait() + except TimeoutError: + # Process didn't exit from stdin closure, use platform-specific termination + # which handles SIGTERM -> SIGKILL escalation + await _terminate_process_tree(process) + except ProcessLookupError: # pragma: no cover + # Process already exited, which is fine + pass + await read_stream.aclose() + await write_stream.aclose() + await read_stream_writer.aclose() + await write_stream_reader.aclose() + + +def _get_executable_command(command: str) -> str: + """ + Get the correct executable command normalized for the current platform. + + Args: + command: Base command (e.g., 'uvx', 'npx') + + Returns: + str: Platform-appropriate command + """ + if sys.platform == "win32": # pragma: no cover + return get_windows_executable_command(command) + else: + return command # pragma: no cover + + +async def _create_platform_compatible_process( + command: str, + args: list[str], + env: dict[str, str] | None = None, + errlog: TextIO = sys.stderr, + cwd: Path | str | None = None, +): + """ + Creates a subprocess in a platform-compatible way. + + Unix: Creates process in a new session/process group for killpg support + Windows: Creates process in a Job Object for reliable child termination + """ + if sys.platform == "win32": # pragma: no cover + process = await create_windows_process(command, args, env, errlog, cwd) + else: + process = await anyio.open_process( + [command, *args], + env=env, + stderr=errlog, + cwd=cwd, + start_new_session=True, + ) # pragma: no cover + + return process + + +async def _terminate_process_tree(process: Process | FallbackProcess, timeout_seconds: float = 2.0) -> None: + """ + Terminate a process and all its children using platform-specific methods. + + Unix: Uses os.killpg() for atomic process group termination + Windows: Uses Job Objects via pywin32 for reliable child process cleanup + + Args: + process: The process to terminate + timeout_seconds: Timeout in seconds before force killing (default: 2.0) + """ + if sys.platform == "win32": # pragma: no cover + await terminate_windows_process_tree(process, timeout_seconds) + else: # pragma: no cover + # FallbackProcess should only be used for Windows compatibility + assert isinstance(process, Process) + await terminate_posix_process_tree(process, timeout_seconds) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py new file mode 100644 index 0000000..ed28fcc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py @@ -0,0 +1,722 @@ +""" +StreamableHTTP Client Transport Module + +This module implements the StreamableHTTP transport for MCP clients, +providing support for HTTP POST requests with optional SSE streaming responses +and session management. +""" + +import contextlib +import logging +from collections.abc import AsyncGenerator, Awaitable, Callable +from contextlib import asynccontextmanager +from dataclasses import dataclass +from datetime import timedelta +from typing import Any, overload +from warnings import warn + +import anyio +import httpx +from anyio.abc import TaskGroup +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from httpx_sse import EventSource, ServerSentEvent, aconnect_sse +from typing_extensions import deprecated + +from mcp.shared._httpx_utils import ( + McpHttpClientFactory, + create_mcp_http_client, +) +from mcp.shared.message import ClientMessageMetadata, SessionMessage +from mcp.types import ( + ErrorData, + InitializeResult, + JSONRPCError, + JSONRPCMessage, + JSONRPCNotification, + JSONRPCRequest, + JSONRPCResponse, + RequestId, +) + +logger = logging.getLogger(__name__) + + +SessionMessageOrError = SessionMessage | Exception +StreamWriter = MemoryObjectSendStream[SessionMessageOrError] +StreamReader = MemoryObjectReceiveStream[SessionMessage] +GetSessionIdCallback = Callable[[], str | None] + +MCP_SESSION_ID = "mcp-session-id" +MCP_PROTOCOL_VERSION = "mcp-protocol-version" +LAST_EVENT_ID = "last-event-id" + +# Reconnection defaults +DEFAULT_RECONNECTION_DELAY_MS = 1000 # 1 second fallback when server doesn't provide retry +MAX_RECONNECTION_ATTEMPTS = 2 # Max retry attempts before giving up +CONTENT_TYPE = "content-type" +ACCEPT = "accept" + + +JSON = "application/json" +SSE = "text/event-stream" + +# Sentinel value for detecting unset optional parameters +_UNSET = object() + + +class StreamableHTTPError(Exception): + """Base exception for StreamableHTTP transport errors.""" + + +class ResumptionError(StreamableHTTPError): + """Raised when resumption request is invalid.""" + + +@dataclass +class RequestContext: + """Context for a request operation.""" + + client: httpx.AsyncClient + session_id: str | None + session_message: SessionMessage + metadata: ClientMessageMetadata | None + read_stream_writer: StreamWriter + headers: dict[str, str] | None = None # Deprecated - no longer used + sse_read_timeout: float | None = None # Deprecated - no longer used + + +class StreamableHTTPTransport: + """StreamableHTTP client transport implementation.""" + + @overload + def __init__(self, url: str) -> None: ... + + @overload + @deprecated( + "Parameters headers, timeout, sse_read_timeout, and auth are deprecated. " + "Configure these on the httpx.AsyncClient instead." + ) + def __init__( + self, + url: str, + headers: dict[str, str] | None = None, + timeout: float | timedelta = 30, + sse_read_timeout: float | timedelta = 60 * 5, + auth: httpx.Auth | None = None, + ) -> None: ... + + def __init__( + self, + url: str, + headers: Any = _UNSET, + timeout: Any = _UNSET, + sse_read_timeout: Any = _UNSET, + auth: Any = _UNSET, + ) -> None: + """Initialize the StreamableHTTP transport. + + Args: + url: The endpoint URL. + headers: Optional headers to include in requests. + timeout: HTTP timeout for regular operations. + sse_read_timeout: Timeout for SSE read operations. + auth: Optional HTTPX authentication handler. + """ + # Check for deprecated parameters and issue runtime warning + deprecated_params: list[str] = [] + if headers is not _UNSET: + deprecated_params.append("headers") + if timeout is not _UNSET: + deprecated_params.append("timeout") + if sse_read_timeout is not _UNSET: + deprecated_params.append("sse_read_timeout") + if auth is not _UNSET: + deprecated_params.append("auth") + + if deprecated_params: + warn( + f"Parameters {', '.join(deprecated_params)} are deprecated and will be ignored. " + "Configure these on the httpx.AsyncClient instead.", + DeprecationWarning, + stacklevel=2, + ) + + self.url = url + self.session_id = None + self.protocol_version = None + + def _prepare_headers(self) -> dict[str, str]: + """Build MCP-specific request headers. + + These headers will be merged with the httpx.AsyncClient's default headers, + with these MCP-specific headers taking precedence. + """ + headers: dict[str, str] = {} + # Add MCP protocol headers + headers[ACCEPT] = f"{JSON}, {SSE}" + headers[CONTENT_TYPE] = JSON + # Add session headers if available + if self.session_id: + headers[MCP_SESSION_ID] = self.session_id + if self.protocol_version: + headers[MCP_PROTOCOL_VERSION] = self.protocol_version + return headers + + def _is_initialization_request(self, message: JSONRPCMessage) -> bool: + """Check if the message is an initialization request.""" + return isinstance(message.root, JSONRPCRequest) and message.root.method == "initialize" + + def _is_initialized_notification(self, message: JSONRPCMessage) -> bool: + """Check if the message is an initialized notification.""" + return isinstance(message.root, JSONRPCNotification) and message.root.method == "notifications/initialized" + + def _maybe_extract_session_id_from_response( + self, + response: httpx.Response, + ) -> None: + """Extract and store session ID from response headers.""" + new_session_id = response.headers.get(MCP_SESSION_ID) + if new_session_id: + self.session_id = new_session_id + logger.info(f"Received session ID: {self.session_id}") + + def _maybe_extract_protocol_version_from_message( + self, + message: JSONRPCMessage, + ) -> None: + """Extract protocol version from initialization response message.""" + if isinstance(message.root, JSONRPCResponse) and message.root.result: # pragma: no branch + try: + # Parse the result as InitializeResult for type safety + init_result = InitializeResult.model_validate(message.root.result) + self.protocol_version = str(init_result.protocolVersion) + logger.info(f"Negotiated protocol version: {self.protocol_version}") + except Exception as exc: # pragma: no cover + logger.warning( + f"Failed to parse initialization response as InitializeResult: {exc}" + ) # pragma: no cover + logger.warning(f"Raw result: {message.root.result}") + + async def _handle_sse_event( + self, + sse: ServerSentEvent, + read_stream_writer: StreamWriter, + original_request_id: RequestId | None = None, + resumption_callback: Callable[[str], Awaitable[None]] | None = None, + is_initialization: bool = False, + ) -> bool: + """Handle an SSE event, returning True if the response is complete.""" + if sse.event == "message": + # Handle priming events (empty data with ID) for resumability + if not sse.data: + # Call resumption callback for priming events that have an ID + if sse.id and resumption_callback: + await resumption_callback(sse.id) + return False + try: + message = JSONRPCMessage.model_validate_json(sse.data) + logger.debug(f"SSE message: {message}") + + # Extract protocol version from initialization response + if is_initialization: + self._maybe_extract_protocol_version_from_message(message) + + # If this is a response and we have original_request_id, replace it + if original_request_id is not None and isinstance(message.root, JSONRPCResponse | JSONRPCError): + message.root.id = original_request_id + + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + + # Call resumption token callback if we have an ID + if sse.id and resumption_callback: + await resumption_callback(sse.id) + + # If this is a response or error return True indicating completion + # Otherwise, return False to continue listening + return isinstance(message.root, JSONRPCResponse | JSONRPCError) + + except Exception as exc: # pragma: no cover + logger.exception("Error parsing SSE message") + await read_stream_writer.send(exc) + return False + else: # pragma: no cover + logger.warning(f"Unknown SSE event: {sse.event}") + return False + + async def handle_get_stream( + self, + client: httpx.AsyncClient, + read_stream_writer: StreamWriter, + ) -> None: + """Handle GET stream for server-initiated messages with auto-reconnect.""" + last_event_id: str | None = None + retry_interval_ms: int | None = None + attempt: int = 0 + + while attempt < MAX_RECONNECTION_ATTEMPTS: # pragma: no branch + try: + if not self.session_id: + return + + headers = self._prepare_headers() + if last_event_id: + headers[LAST_EVENT_ID] = last_event_id # pragma: no cover + + async with aconnect_sse( + client, + "GET", + self.url, + headers=headers, + ) as event_source: + event_source.response.raise_for_status() + logger.debug("GET SSE connection established") + + async for sse in event_source.aiter_sse(): + # Track last event ID for reconnection + if sse.id: + last_event_id = sse.id # pragma: no cover + # Track retry interval from server + if sse.retry is not None: + retry_interval_ms = sse.retry # pragma: no cover + + await self._handle_sse_event(sse, read_stream_writer) + + # Stream ended normally (server closed) - reset attempt counter + attempt = 0 + + except Exception as exc: # pragma: no cover + logger.debug(f"GET stream error: {exc}") + attempt += 1 + + if attempt >= MAX_RECONNECTION_ATTEMPTS: # pragma: no cover + logger.debug(f"GET stream max reconnection attempts ({MAX_RECONNECTION_ATTEMPTS}) exceeded") + return + + # Wait before reconnecting + delay_ms = retry_interval_ms if retry_interval_ms is not None else DEFAULT_RECONNECTION_DELAY_MS + logger.info(f"GET stream disconnected, reconnecting in {delay_ms}ms...") + await anyio.sleep(delay_ms / 1000.0) + + async def _handle_resumption_request(self, ctx: RequestContext) -> None: + """Handle a resumption request using GET with SSE.""" + headers = self._prepare_headers() + if ctx.metadata and ctx.metadata.resumption_token: + headers[LAST_EVENT_ID] = ctx.metadata.resumption_token + else: + raise ResumptionError("Resumption request requires a resumption token") # pragma: no cover + + # Extract original request ID to map responses + original_request_id = None + if isinstance(ctx.session_message.message.root, JSONRPCRequest): # pragma: no branch + original_request_id = ctx.session_message.message.root.id + + async with aconnect_sse( + ctx.client, + "GET", + self.url, + headers=headers, + ) as event_source: + event_source.response.raise_for_status() + logger.debug("Resumption GET SSE connection established") + + async for sse in event_source.aiter_sse(): # pragma: no branch + is_complete = await self._handle_sse_event( + sse, + ctx.read_stream_writer, + original_request_id, + ctx.metadata.on_resumption_token_update if ctx.metadata else None, + ) + if is_complete: + await event_source.response.aclose() + break + + async def _handle_post_request(self, ctx: RequestContext) -> None: + """Handle a POST request with response processing.""" + headers = self._prepare_headers() + message = ctx.session_message.message + is_initialization = self._is_initialization_request(message) + + async with ctx.client.stream( + "POST", + self.url, + json=message.model_dump(by_alias=True, mode="json", exclude_none=True), + headers=headers, + ) as response: + if response.status_code == 202: + logger.debug("Received 202 Accepted") + return + + if response.status_code == 404: # pragma: no branch + if isinstance(message.root, JSONRPCRequest): + await self._send_session_terminated_error( # pragma: no cover + ctx.read_stream_writer, # pragma: no cover + message.root.id, # pragma: no cover + ) # pragma: no cover + return # pragma: no cover + + response.raise_for_status() + if is_initialization: + self._maybe_extract_session_id_from_response(response) + + # Per https://modelcontextprotocol.io/specification/2025-06-18/basic#notifications: + # The server MUST NOT send a response to notifications. + if isinstance(message.root, JSONRPCRequest): + content_type = response.headers.get(CONTENT_TYPE, "").lower() + if content_type.startswith(JSON): + await self._handle_json_response(response, ctx.read_stream_writer, is_initialization) + elif content_type.startswith(SSE): + await self._handle_sse_response(response, ctx, is_initialization) + else: + await self._handle_unexpected_content_type( # pragma: no cover + content_type, # pragma: no cover + ctx.read_stream_writer, # pragma: no cover + ) # pragma: no cover + + async def _handle_json_response( + self, + response: httpx.Response, + read_stream_writer: StreamWriter, + is_initialization: bool = False, + ) -> None: + """Handle JSON response from the server.""" + try: + content = await response.aread() + message = JSONRPCMessage.model_validate_json(content) + + # Extract protocol version from initialization response + if is_initialization: + self._maybe_extract_protocol_version_from_message(message) + + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + except Exception as exc: # pragma: no cover + logger.exception("Error parsing JSON response") + await read_stream_writer.send(exc) + + async def _handle_sse_response( + self, + response: httpx.Response, + ctx: RequestContext, + is_initialization: bool = False, + ) -> None: + """Handle SSE response from the server.""" + last_event_id: str | None = None + retry_interval_ms: int | None = None + + try: + event_source = EventSource(response) + async for sse in event_source.aiter_sse(): # pragma: no branch + # Track last event ID for potential reconnection + if sse.id: + last_event_id = sse.id + + # Track retry interval from server + if sse.retry is not None: + retry_interval_ms = sse.retry + + is_complete = await self._handle_sse_event( + sse, + ctx.read_stream_writer, + resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None), + is_initialization=is_initialization, + ) + # If the SSE event indicates completion, like returning respose/error + # break the loop + if is_complete: + await response.aclose() + return # Normal completion, no reconnect needed + except Exception as e: # pragma: no cover + logger.debug(f"SSE stream ended: {e}") + + # Stream ended without response - reconnect if we received an event with ID + if last_event_id is not None: # pragma: no branch + logger.info("SSE stream disconnected, reconnecting...") + await self._handle_reconnection(ctx, last_event_id, retry_interval_ms) + + async def _handle_reconnection( + self, + ctx: RequestContext, + last_event_id: str, + retry_interval_ms: int | None = None, + attempt: int = 0, + ) -> None: + """Reconnect with Last-Event-ID to resume stream after server disconnect.""" + # Bail if max retries exceeded + if attempt >= MAX_RECONNECTION_ATTEMPTS: # pragma: no cover + logger.debug(f"Max reconnection attempts ({MAX_RECONNECTION_ATTEMPTS}) exceeded") + return + + # Always wait - use server value or default + delay_ms = retry_interval_ms if retry_interval_ms is not None else DEFAULT_RECONNECTION_DELAY_MS + await anyio.sleep(delay_ms / 1000.0) + + headers = self._prepare_headers() + headers[LAST_EVENT_ID] = last_event_id + + # Extract original request ID to map responses + original_request_id = None + if isinstance(ctx.session_message.message.root, JSONRPCRequest): # pragma: no branch + original_request_id = ctx.session_message.message.root.id + + try: + async with aconnect_sse( + ctx.client, + "GET", + self.url, + headers=headers, + ) as event_source: + event_source.response.raise_for_status() + logger.info("Reconnected to SSE stream") + + # Track for potential further reconnection + reconnect_last_event_id: str = last_event_id + reconnect_retry_ms = retry_interval_ms + + async for sse in event_source.aiter_sse(): + if sse.id: # pragma: no branch + reconnect_last_event_id = sse.id + if sse.retry is not None: + reconnect_retry_ms = sse.retry + + is_complete = await self._handle_sse_event( + sse, + ctx.read_stream_writer, + original_request_id, + ctx.metadata.on_resumption_token_update if ctx.metadata else None, + ) + if is_complete: + await event_source.response.aclose() + return + + # Stream ended again without response - reconnect again (reset attempt counter) + logger.info("SSE stream disconnected, reconnecting...") + await self._handle_reconnection(ctx, reconnect_last_event_id, reconnect_retry_ms, 0) + except Exception as e: # pragma: no cover + logger.debug(f"Reconnection failed: {e}") + # Try to reconnect again if we still have an event ID + await self._handle_reconnection(ctx, last_event_id, retry_interval_ms, attempt + 1) + + async def _handle_unexpected_content_type( + self, + content_type: str, + read_stream_writer: StreamWriter, + ) -> None: # pragma: no cover + """Handle unexpected content type in response.""" + error_msg = f"Unexpected content type: {content_type}" # pragma: no cover + logger.error(error_msg) # pragma: no cover + await read_stream_writer.send(ValueError(error_msg)) # pragma: no cover + + async def _send_session_terminated_error( + self, + read_stream_writer: StreamWriter, + request_id: RequestId, + ) -> None: + """Send a session terminated error response.""" + jsonrpc_error = JSONRPCError( + jsonrpc="2.0", + id=request_id, + error=ErrorData(code=32600, message="Session terminated"), + ) + session_message = SessionMessage(JSONRPCMessage(jsonrpc_error)) + await read_stream_writer.send(session_message) + + async def post_writer( + self, + client: httpx.AsyncClient, + write_stream_reader: StreamReader, + read_stream_writer: StreamWriter, + write_stream: MemoryObjectSendStream[SessionMessage], + start_get_stream: Callable[[], None], + tg: TaskGroup, + ) -> None: + """Handle writing requests to the server.""" + try: + async with write_stream_reader: + async for session_message in write_stream_reader: + message = session_message.message + metadata = ( + session_message.metadata + if isinstance(session_message.metadata, ClientMessageMetadata) + else None + ) + + # Check if this is a resumption request + is_resumption = bool(metadata and metadata.resumption_token) + + logger.debug(f"Sending client message: {message}") + + # Handle initialized notification + if self._is_initialized_notification(message): + start_get_stream() + + ctx = RequestContext( + client=client, + session_id=self.session_id, + session_message=session_message, + metadata=metadata, + read_stream_writer=read_stream_writer, + ) + + async def handle_request_async(): + if is_resumption: + await self._handle_resumption_request(ctx) + else: + await self._handle_post_request(ctx) + + # If this is a request, start a new task to handle it + if isinstance(message.root, JSONRPCRequest): + tg.start_soon(handle_request_async) + else: + await handle_request_async() + + except Exception: + logger.exception("Error in post_writer") # pragma: no cover + finally: + await read_stream_writer.aclose() + await write_stream.aclose() + + async def terminate_session(self, client: httpx.AsyncClient) -> None: # pragma: no cover + """Terminate the session by sending a DELETE request.""" + if not self.session_id: + return + + try: + headers = self._prepare_headers() + response = await client.delete(self.url, headers=headers) + + if response.status_code == 405: + logger.debug("Server does not allow session termination") + elif response.status_code not in (200, 204): + logger.warning(f"Session termination failed: {response.status_code}") + except Exception as exc: + logger.warning(f"Session termination failed: {exc}") + + def get_session_id(self) -> str | None: + """Get the current session ID.""" + return self.session_id + + +@asynccontextmanager +async def streamable_http_client( + url: str, + *, + http_client: httpx.AsyncClient | None = None, + terminate_on_close: bool = True, +) -> AsyncGenerator[ + tuple[ + MemoryObjectReceiveStream[SessionMessage | Exception], + MemoryObjectSendStream[SessionMessage], + GetSessionIdCallback, + ], + None, +]: + """ + Client transport for StreamableHTTP. + + Args: + url: The MCP server endpoint URL. + http_client: Optional pre-configured httpx.AsyncClient. If None, a default + client with recommended MCP timeouts will be created. To configure headers, + authentication, or other HTTP settings, create an httpx.AsyncClient and pass it here. + terminate_on_close: If True, send a DELETE request to terminate the session + when the context exits. + + Yields: + Tuple containing: + - read_stream: Stream for reading messages from the server + - write_stream: Stream for sending messages to the server + - get_session_id_callback: Function to retrieve the current session ID + + Example: + See examples/snippets/clients/ for usage patterns. + """ + read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0) + write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0) + + # Determine if we need to create and manage the client + client_provided = http_client is not None + client = http_client + + if client is None: + # Create default client with recommended MCP timeouts + client = create_mcp_http_client() + + transport = StreamableHTTPTransport(url) + + async with anyio.create_task_group() as tg: + try: + logger.debug(f"Connecting to StreamableHTTP endpoint: {url}") + + async with contextlib.AsyncExitStack() as stack: + # Only manage client lifecycle if we created it + if not client_provided: + await stack.enter_async_context(client) + + def start_get_stream() -> None: + tg.start_soon(transport.handle_get_stream, client, read_stream_writer) + + tg.start_soon( + transport.post_writer, + client, + write_stream_reader, + read_stream_writer, + write_stream, + start_get_stream, + tg, + ) + + try: + yield ( + read_stream, + write_stream, + transport.get_session_id, + ) + finally: + if transport.session_id and terminate_on_close: + await transport.terminate_session(client) + tg.cancel_scope.cancel() + finally: + await read_stream_writer.aclose() + await write_stream.aclose() + + +@asynccontextmanager +@deprecated("Use `streamable_http_client` instead.") +async def streamablehttp_client( + url: str, + headers: dict[str, str] | None = None, + timeout: float | timedelta = 30, + sse_read_timeout: float | timedelta = 60 * 5, + terminate_on_close: bool = True, + httpx_client_factory: McpHttpClientFactory = create_mcp_http_client, + auth: httpx.Auth | None = None, +) -> AsyncGenerator[ + tuple[ + MemoryObjectReceiveStream[SessionMessage | Exception], + MemoryObjectSendStream[SessionMessage], + GetSessionIdCallback, + ], + None, +]: + # Convert timeout parameters + timeout_seconds = timeout.total_seconds() if isinstance(timeout, timedelta) else timeout + sse_read_timeout_seconds = ( + sse_read_timeout.total_seconds() if isinstance(sse_read_timeout, timedelta) else sse_read_timeout + ) + + # Create httpx client using the factory with old-style parameters + client = httpx_client_factory( + headers=headers, + timeout=httpx.Timeout(timeout_seconds, read=sse_read_timeout_seconds), + auth=auth, + ) + + # Manage client lifecycle since we created it + async with client: + async with streamable_http_client( + url, + http_client=client, + terminate_on_close=terminate_on_close, + ) as streams: + yield streams diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py new file mode 100644 index 0000000..e8c8d9a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py @@ -0,0 +1,86 @@ +import json +import logging +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import ValidationError +from websockets.asyncio.client import connect as ws_connect +from websockets.typing import Subprotocol + +import mcp.types as types +from mcp.shared.message import SessionMessage + +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def websocket_client( + url: str, +) -> AsyncGenerator[ + tuple[MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage]], + None, +]: + """ + WebSocket client transport for MCP, symmetrical to the server version. + + Connects to 'url' using the 'mcp' subprotocol, then yields: + (read_stream, write_stream) + + - read_stream: As you read from this stream, you'll receive either valid + JSONRPCMessage objects or Exception objects (when validation fails). + - write_stream: Write JSONRPCMessage objects to this stream to send them + over the WebSocket to the server. + """ + + # Create two in-memory streams: + # - One for incoming messages (read_stream, written by ws_reader) + # - One for outgoing messages (write_stream, read by ws_writer) + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + # Connect using websockets, requesting the "mcp" subprotocol + async with ws_connect(url, subprotocols=[Subprotocol("mcp")]) as ws: + + async def ws_reader(): + """ + Reads text messages from the WebSocket, parses them as JSON-RPC messages, + and sends them into read_stream_writer. + """ + async with read_stream_writer: + async for raw_text in ws: + try: + message = types.JSONRPCMessage.model_validate_json(raw_text) + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + except ValidationError as exc: # pragma: no cover + # If JSON parse or model validation fails, send the exception + await read_stream_writer.send(exc) + + async def ws_writer(): + """ + Reads JSON-RPC messages from write_stream_reader and + sends them to the server. + """ + async with write_stream_reader: + async for session_message in write_stream_reader: + # Convert to a dict, then to JSON + msg_dict = session_message.message.model_dump(by_alias=True, mode="json", exclude_none=True) + await ws.send(json.dumps(msg_dict)) + + async with anyio.create_task_group() as tg: + # Start reader and writer tasks + tg.start_soon(ws_reader) + tg.start_soon(ws_writer) + + # Yield the receive/send streams + yield (read_stream, write_stream) + + # Once the caller's 'async with' block exits, we shut down + tg.cancel_scope.cancel() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py new file mode 100644 index 0000000..fa5dbc8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py @@ -0,0 +1 @@ +"""Platform-specific utilities for MCP.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py new file mode 100644 index 0000000..23aff8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py @@ -0,0 +1 @@ +"""POSIX-specific utilities for MCP.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py new file mode 100644 index 0000000..dd1aea3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py @@ -0,0 +1,60 @@ +""" +POSIX-specific functionality for stdio client operations. +""" + +import logging +import os +import signal + +import anyio +from anyio.abc import Process + +logger = logging.getLogger(__name__) + + +async def terminate_posix_process_tree(process: Process, timeout_seconds: float = 2.0) -> None: + """ + Terminate a process and all its children on POSIX systems. + + Uses os.killpg() for atomic process group termination. + + Args: + process: The process to terminate + timeout_seconds: Timeout in seconds before force killing (default: 2.0) + """ + pid = getattr(process, "pid", None) or getattr(getattr(process, "popen", None), "pid", None) + if not pid: + # No PID means there's no process to terminate - it either never started, + # already exited, or we have an invalid process object + return + + try: + pgid = os.getpgid(pid) + os.killpg(pgid, signal.SIGTERM) + + with anyio.move_on_after(timeout_seconds): + while True: + try: + # Check if process group still exists (signal 0 = check only) + os.killpg(pgid, 0) + await anyio.sleep(0.1) + except ProcessLookupError: + return + + try: + os.killpg(pgid, signal.SIGKILL) + except ProcessLookupError: + pass + + except (ProcessLookupError, PermissionError, OSError) as e: + logger.warning(f"Process group termination failed for PID {pid}: {e}, falling back to simple terminate") + try: + process.terminate() + with anyio.fail_after(timeout_seconds): + await process.wait() + except Exception: + logger.warning(f"Process termination failed for PID {pid}, attempting force kill") + try: + process.kill() + except Exception: + logger.exception(f"Failed to kill process {pid}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py new file mode 100644 index 0000000..f1ebab9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py @@ -0,0 +1 @@ +"""Windows-specific utilities for MCP.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py new file mode 100644 index 0000000..962be02 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py @@ -0,0 +1,338 @@ +""" +Windows-specific functionality for stdio client operations. +""" + +import logging +import shutil +import subprocess +import sys +from pathlib import Path +from typing import BinaryIO, TextIO, cast + +import anyio +from anyio import to_thread +from anyio.abc import Process +from anyio.streams.file import FileReadStream, FileWriteStream +from typing_extensions import deprecated + +logger = logging.getLogger("client.stdio.win32") + +# Windows-specific imports for Job Objects +if sys.platform == "win32": + import pywintypes + import win32api + import win32con + import win32job +else: + # Type stubs for non-Windows platforms + win32api = None + win32con = None + win32job = None + pywintypes = None + +JobHandle = int + + +def get_windows_executable_command(command: str) -> str: + """ + Get the correct executable command normalized for Windows. + + On Windows, commands might exist with specific extensions (.exe, .cmd, etc.) + that need to be located for proper execution. + + Args: + command: Base command (e.g., 'uvx', 'npx') + + Returns: + str: Windows-appropriate command path + """ + try: + # First check if command exists in PATH as-is + if command_path := shutil.which(command): + return command_path + + # Check for Windows-specific extensions + for ext in [".cmd", ".bat", ".exe", ".ps1"]: + ext_version = f"{command}{ext}" + if ext_path := shutil.which(ext_version): + return ext_path + + # For regular commands or if we couldn't find special versions + return command + except OSError: + # Handle file system errors during path resolution + # (permissions, broken symlinks, etc.) + return command + + +class FallbackProcess: + """ + A fallback process wrapper for Windows to handle async I/O + when using subprocess.Popen, which provides sync-only FileIO objects. + + This wraps stdin and stdout into async-compatible + streams (FileReadStream, FileWriteStream), + so that MCP clients expecting async streams can work properly. + """ + + def __init__(self, popen_obj: subprocess.Popen[bytes]): + self.popen: subprocess.Popen[bytes] = popen_obj + self.stdin_raw = popen_obj.stdin # type: ignore[assignment] + self.stdout_raw = popen_obj.stdout # type: ignore[assignment] + self.stderr = popen_obj.stderr # type: ignore[assignment] + + self.stdin = FileWriteStream(cast(BinaryIO, self.stdin_raw)) if self.stdin_raw else None + self.stdout = FileReadStream(cast(BinaryIO, self.stdout_raw)) if self.stdout_raw else None + + async def __aenter__(self): + """Support async context manager entry.""" + return self + + async def __aexit__( + self, + exc_type: BaseException | None, + exc_val: BaseException | None, + exc_tb: object | None, + ) -> None: + """Terminate and wait on process exit inside a thread.""" + self.popen.terminate() + await to_thread.run_sync(self.popen.wait) + + # Close the file handles to prevent ResourceWarning + if self.stdin: + await self.stdin.aclose() + if self.stdout: + await self.stdout.aclose() + if self.stdin_raw: + self.stdin_raw.close() + if self.stdout_raw: + self.stdout_raw.close() + if self.stderr: + self.stderr.close() + + async def wait(self): + """Async wait for process completion.""" + return await to_thread.run_sync(self.popen.wait) + + def terminate(self): + """Terminate the subprocess immediately.""" + return self.popen.terminate() + + def kill(self) -> None: + """Kill the subprocess immediately (alias for terminate).""" + self.terminate() + + @property + def pid(self) -> int: + """Return the process ID.""" + return self.popen.pid + + +# ------------------------ +# Updated function +# ------------------------ + + +async def create_windows_process( + command: str, + args: list[str], + env: dict[str, str] | None = None, + errlog: TextIO | None = sys.stderr, + cwd: Path | str | None = None, +) -> Process | FallbackProcess: + """ + Creates a subprocess in a Windows-compatible way with Job Object support. + + Attempt to use anyio's open_process for async subprocess creation. + In some cases this will throw NotImplementedError on Windows, e.g. + when using the SelectorEventLoop which does not support async subprocesses. + In that case, we fall back to using subprocess.Popen. + + The process is automatically added to a Job Object to ensure all child + processes are terminated when the parent is terminated. + + Args: + command (str): The executable to run + args (list[str]): List of command line arguments + env (dict[str, str] | None): Environment variables + errlog (TextIO | None): Where to send stderr output (defaults to sys.stderr) + cwd (Path | str | None): Working directory for the subprocess + + Returns: + Process | FallbackProcess: Async-compatible subprocess with stdin and stdout streams + """ + job = _create_job_object() + process = None + + try: + # First try using anyio with Windows-specific flags to hide console window + process = await anyio.open_process( + [command, *args], + env=env, + # Ensure we don't create console windows for each process + creationflags=subprocess.CREATE_NO_WINDOW # type: ignore + if hasattr(subprocess, "CREATE_NO_WINDOW") + else 0, + stderr=errlog, + cwd=cwd, + ) + except NotImplementedError: + # If Windows doesn't support async subprocess creation, use fallback + process = await _create_windows_fallback_process(command, args, env, errlog, cwd) + except Exception: + # Try again without creation flags + process = await anyio.open_process( + [command, *args], + env=env, + stderr=errlog, + cwd=cwd, + ) + + _maybe_assign_process_to_job(process, job) + return process + + +async def _create_windows_fallback_process( + command: str, + args: list[str], + env: dict[str, str] | None = None, + errlog: TextIO | None = sys.stderr, + cwd: Path | str | None = None, +) -> FallbackProcess: + """ + Create a subprocess using subprocess.Popen as a fallback when anyio fails. + + This function wraps the sync subprocess.Popen in an async-compatible interface. + """ + try: + # Try launching with creationflags to avoid opening a new console window + popen_obj = subprocess.Popen( + [command, *args], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=errlog, + env=env, + cwd=cwd, + bufsize=0, # Unbuffered output + creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), + ) + except Exception: + # If creationflags failed, fallback without them + popen_obj = subprocess.Popen( + [command, *args], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=errlog, + env=env, + cwd=cwd, + bufsize=0, + ) + return FallbackProcess(popen_obj) + + +def _create_job_object() -> int | None: + """ + Create a Windows Job Object configured to terminate all processes when closed. + """ + if sys.platform != "win32" or not win32job: + return None + + try: + job = win32job.CreateJobObject(None, "") + extended_info = win32job.QueryInformationJobObject(job, win32job.JobObjectExtendedLimitInformation) + + extended_info["BasicLimitInformation"]["LimitFlags"] |= win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + win32job.SetInformationJobObject(job, win32job.JobObjectExtendedLimitInformation, extended_info) + return job + except Exception as e: + logger.warning(f"Failed to create Job Object for process tree management: {e}") + return None + + +def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: JobHandle | None) -> None: + """ + Try to assign a process to a job object. If assignment fails + for any reason, the job handle is closed. + """ + if not job: + return + + if sys.platform != "win32" or not win32api or not win32con or not win32job: + return + + try: + process_handle = win32api.OpenProcess( + win32con.PROCESS_SET_QUOTA | win32con.PROCESS_TERMINATE, False, process.pid + ) + if not process_handle: + raise Exception("Failed to open process handle") + + try: + win32job.AssignProcessToJobObject(job, process_handle) + process._job_object = job + finally: + win32api.CloseHandle(process_handle) + except Exception as e: + logger.warning(f"Failed to assign process {process.pid} to Job Object: {e}") + if win32api: + win32api.CloseHandle(job) + + +async def terminate_windows_process_tree(process: Process | FallbackProcess, timeout_seconds: float = 2.0) -> None: + """ + Terminate a process and all its children on Windows. + + If the process has an associated job object, it will be terminated. + Otherwise, falls back to basic process termination. + + Args: + process: The process to terminate + timeout_seconds: Timeout in seconds before force killing (default: 2.0) + """ + if sys.platform != "win32": + return + + job = getattr(process, "_job_object", None) + if job and win32job: + try: + win32job.TerminateJobObject(job, 1) + except Exception: + # Job might already be terminated + pass + finally: + if win32api: + try: + win32api.CloseHandle(job) + except Exception: + pass + + # Always try to terminate the process itself as well + try: + process.terminate() + except Exception: + pass + + +@deprecated( + "terminate_windows_process is deprecated and will be removed in a future version. " + "Process termination is now handled internally by the stdio_client context manager." +) +async def terminate_windows_process(process: Process | FallbackProcess): + """ + Terminate a Windows process. + + Note: On Windows, terminating a process with process.terminate() doesn't + always guarantee immediate process termination. + So we give it 2s to exit, or we call process.kill() + which sends a SIGKILL equivalent signal. + + Args: + process: The process to terminate + """ + try: + process.terminate() + with anyio.fail_after(2.0): + await process.wait() + except TimeoutError: + # Force kill if it doesn't terminate + process.kill() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py new file mode 100644 index 0000000..0feed36 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py @@ -0,0 +1,5 @@ +from .fastmcp import FastMCP +from .lowlevel import NotificationOptions, Server +from .models import InitializationOptions + +__all__ = ["Server", "FastMCP", "NotificationOptions", "InitializationOptions"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py new file mode 100644 index 0000000..1970eca --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py @@ -0,0 +1,50 @@ +import importlib.metadata +import logging +import sys + +import anyio + +from mcp.server.models import InitializationOptions +from mcp.server.session import ServerSession +from mcp.server.stdio import stdio_server +from mcp.types import ServerCapabilities + +if not sys.warnoptions: + import warnings + + warnings.simplefilter("ignore") + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("server") + + +async def receive_loop(session: ServerSession): + logger.info("Starting receive loop") + async for message in session.incoming_messages: + if isinstance(message, Exception): + logger.error("Error: %s", message) + continue + + logger.info("Received message from client: %s", message) + + +async def main(): + version = importlib.metadata.version("mcp") + async with stdio_server() as (read_stream, write_stream): + async with ( + ServerSession( + read_stream, + write_stream, + InitializationOptions( + server_name="mcp", + server_version=version, + capabilities=ServerCapabilities(), + ), + ) as session, + write_stream, + ): + await receive_loop(session) + + +if __name__ == "__main__": + anyio.run(main, backend="trio") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py new file mode 100644 index 0000000..6888ffe --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py @@ -0,0 +1,3 @@ +""" +MCP OAuth server authorization components. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py new file mode 100644 index 0000000..117deea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py @@ -0,0 +1,5 @@ +from pydantic import ValidationError + + +def stringify_pydantic_error(validation_error: ValidationError) -> str: + return "\n".join(f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}" for e in validation_error.errors()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py new file mode 100644 index 0000000..e99a62d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py @@ -0,0 +1,3 @@ +""" +Request handlers for MCP authorization endpoints. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py new file mode 100644 index 0000000..3570d28 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py @@ -0,0 +1,224 @@ +import logging +from dataclasses import dataclass +from typing import Any, Literal + +from pydantic import AnyUrl, BaseModel, Field, RootModel, ValidationError +from starlette.datastructures import FormData, QueryParams +from starlette.requests import Request +from starlette.responses import RedirectResponse, Response + +from mcp.server.auth.errors import stringify_pydantic_error +from mcp.server.auth.json_response import PydanticJSONResponse +from mcp.server.auth.provider import ( + AuthorizationErrorCode, + AuthorizationParams, + AuthorizeError, + OAuthAuthorizationServerProvider, + construct_redirect_uri, +) +from mcp.shared.auth import InvalidRedirectUriError, InvalidScopeError + +logger = logging.getLogger(__name__) + + +class AuthorizationRequest(BaseModel): + # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + client_id: str = Field(..., description="The client ID") + redirect_uri: AnyUrl | None = Field(None, description="URL to redirect to after authorization") + + # see OAuthClientMetadata; we only support `code` + response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow") + code_challenge: str = Field(..., description="PKCE code challenge") + code_challenge_method: Literal["S256"] = Field("S256", description="PKCE code challenge method, must be S256") + state: str | None = Field(None, description="Optional state parameter") + scope: str | None = Field( + None, + description="Optional scope; if specified, should be a space-separated list of scope strings", + ) + resource: str | None = Field( + None, + description="RFC 8707 resource indicator - the MCP server this token will be used with", + ) + + +class AuthorizationErrorResponse(BaseModel): + error: AuthorizationErrorCode + error_description: str | None + error_uri: AnyUrl | None = None + # must be set if provided in the request + state: str | None = None + + +def best_effort_extract_string(key: str, params: None | FormData | QueryParams) -> str | None: + if params is None: # pragma: no cover + return None + value = params.get(key) + if isinstance(value, str): + return value + return None + + +class AnyUrlModel(RootModel[AnyUrl]): + root: AnyUrl + + +@dataclass +class AuthorizationHandler: + provider: OAuthAuthorizationServerProvider[Any, Any, Any] + + async def handle(self, request: Request) -> Response: + # implements authorization requests for grant_type=code; + # see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + + state = None + redirect_uri = None + client = None + params = None + + async def error_response( + error: AuthorizationErrorCode, + error_description: str | None, + attempt_load_client: bool = True, + ): + # Error responses take two different formats: + # 1. The request has a valid client ID & redirect_uri: we issue a redirect + # back to the redirect_uri with the error response fields as query + # parameters. This allows the client to be notified of the error. + # 2. Otherwise, we return an error response directly to the end user; + # we choose to do so in JSON, but this is left undefined in the + # specification. + # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 + # + # This logic is a bit awkward to handle, because the error might be thrown + # very early in request validation, before we've done the usual Pydantic + # validation, loaded the client, etc. To handle this, error_response() + # contains fallback logic which attempts to load the parameters directly + # from the request. + + nonlocal client, redirect_uri, state + if client is None and attempt_load_client: + # make last-ditch attempt to load the client + client_id = best_effort_extract_string("client_id", params) + client = await self.provider.get_client(client_id) if client_id else None + if redirect_uri is None and client: + # make last-ditch effort to load the redirect uri + try: + if params is not None and "redirect_uri" not in params: + raw_redirect_uri = None + else: + raw_redirect_uri = AnyUrlModel.model_validate( + best_effort_extract_string("redirect_uri", params) + ).root + redirect_uri = client.validate_redirect_uri(raw_redirect_uri) + except (ValidationError, InvalidRedirectUriError): + # if the redirect URI is invalid, ignore it & just return the + # initial error + pass + + # the error response MUST contain the state specified by the client, if any + if state is None: # pragma: no cover + # make last-ditch effort to load state + state = best_effort_extract_string("state", params) + + error_resp = AuthorizationErrorResponse( + error=error, + error_description=error_description, + state=state, + ) + + if redirect_uri and client: + return RedirectResponse( + url=construct_redirect_uri(str(redirect_uri), **error_resp.model_dump(exclude_none=True)), + status_code=302, + headers={"Cache-Control": "no-store"}, + ) + else: + return PydanticJSONResponse( + status_code=400, + content=error_resp, + headers={"Cache-Control": "no-store"}, + ) + + try: + # Parse request parameters + if request.method == "GET": + # Convert query_params to dict for pydantic validation + params = request.query_params + else: + # Parse form data for POST requests + params = await request.form() + + # Save state if it exists, even before validation + state = best_effort_extract_string("state", params) + + try: + auth_request = AuthorizationRequest.model_validate(params) + state = auth_request.state # Update with validated state + except ValidationError as validation_error: + error: AuthorizationErrorCode = "invalid_request" + for e in validation_error.errors(): + if e["loc"] == ("response_type",) and e["type"] == "literal_error": + error = "unsupported_response_type" + break + return await error_response(error, stringify_pydantic_error(validation_error)) + + # Get client information + client = await self.provider.get_client( + auth_request.client_id, + ) + if not client: + # For client_id validation errors, return direct error (no redirect) + return await error_response( + error="invalid_request", + error_description=f"Client ID '{auth_request.client_id}' not found", + attempt_load_client=False, + ) + + # Validate redirect_uri against client's registered URIs + try: + redirect_uri = client.validate_redirect_uri(auth_request.redirect_uri) + except InvalidRedirectUriError as validation_error: + # For redirect_uri validation errors, return direct error (no redirect) + return await error_response( + error="invalid_request", + error_description=validation_error.message, + ) + + # Validate scope - for scope errors, we can redirect + try: + scopes = client.validate_scope(auth_request.scope) + except InvalidScopeError as validation_error: + # For scope errors, redirect with error parameters + return await error_response( + error="invalid_scope", + error_description=validation_error.message, + ) + + # Setup authorization parameters + auth_params = AuthorizationParams( + state=state, + scopes=scopes, + code_challenge=auth_request.code_challenge, + redirect_uri=redirect_uri, + redirect_uri_provided_explicitly=auth_request.redirect_uri is not None, + resource=auth_request.resource, # RFC 8707 + ) + + try: + # Let the provider pick the next URI to redirect to + return RedirectResponse( + url=await self.provider.authorize( + client, + auth_params, + ), + status_code=302, + headers={"Cache-Control": "no-store"}, + ) + except AuthorizeError as e: + # Handle authorization errors as defined in RFC 6749 Section 4.1.2.1 + return await error_response(error=e.error, error_description=e.error_description) + + except Exception as validation_error: # pragma: no cover + # Catch-all for unexpected errors + logger.exception("Unexpected error in authorization_handler", exc_info=validation_error) + return await error_response(error="server_error", error_description="An unexpected error occurred") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py new file mode 100644 index 0000000..f126442 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass + +from starlette.requests import Request +from starlette.responses import Response + +from mcp.server.auth.json_response import PydanticJSONResponse +from mcp.shared.auth import OAuthMetadata, ProtectedResourceMetadata + + +@dataclass +class MetadataHandler: + metadata: OAuthMetadata + + async def handle(self, request: Request) -> Response: + return PydanticJSONResponse( + content=self.metadata, + headers={"Cache-Control": "public, max-age=3600"}, # Cache for 1 hour + ) + + +@dataclass +class ProtectedResourceMetadataHandler: + metadata: ProtectedResourceMetadata + + async def handle(self, request: Request) -> Response: + return PydanticJSONResponse( + content=self.metadata, + headers={"Cache-Control": "public, max-age=3600"}, # Cache for 1 hour + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py new file mode 100644 index 0000000..c65473d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py @@ -0,0 +1,136 @@ +import secrets +import time +from dataclasses import dataclass +from typing import Any +from uuid import uuid4 + +from pydantic import BaseModel, RootModel, ValidationError +from starlette.requests import Request +from starlette.responses import Response + +from mcp.server.auth.errors import stringify_pydantic_error +from mcp.server.auth.json_response import PydanticJSONResponse +from mcp.server.auth.provider import OAuthAuthorizationServerProvider, RegistrationError, RegistrationErrorCode +from mcp.server.auth.settings import ClientRegistrationOptions +from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata + + +class RegistrationRequest(RootModel[OAuthClientMetadata]): + # this wrapper is a no-op; it's just to separate out the types exposed to the + # provider from what we use in the HTTP handler + root: OAuthClientMetadata + + +class RegistrationErrorResponse(BaseModel): + error: RegistrationErrorCode + error_description: str | None + + +@dataclass +class RegistrationHandler: + provider: OAuthAuthorizationServerProvider[Any, Any, Any] + options: ClientRegistrationOptions + + async def handle(self, request: Request) -> Response: + # Implements dynamic client registration as defined in https://datatracker.ietf.org/doc/html/rfc7591#section-3.1 + try: + # Parse request body as JSON + body = await request.json() + client_metadata = OAuthClientMetadata.model_validate(body) + + # Scope validation is handled below + except ValidationError as validation_error: + return PydanticJSONResponse( + content=RegistrationErrorResponse( + error="invalid_client_metadata", + error_description=stringify_pydantic_error(validation_error), + ), + status_code=400, + ) + + client_id = str(uuid4()) + + # If auth method is None, default to client_secret_post + if client_metadata.token_endpoint_auth_method is None: + client_metadata.token_endpoint_auth_method = "client_secret_post" + + client_secret = None + if client_metadata.token_endpoint_auth_method != "none": # pragma: no branch + # cryptographically secure random 32-byte hex string + client_secret = secrets.token_hex(32) + + if client_metadata.scope is None and self.options.default_scopes is not None: + client_metadata.scope = " ".join(self.options.default_scopes) + elif client_metadata.scope is not None and self.options.valid_scopes is not None: + requested_scopes = set(client_metadata.scope.split()) + valid_scopes = set(self.options.valid_scopes) + if not requested_scopes.issubset(valid_scopes): # pragma: no branch + return PydanticJSONResponse( + content=RegistrationErrorResponse( + error="invalid_client_metadata", + error_description="Requested scopes are not valid: " + f"{', '.join(requested_scopes - valid_scopes)}", + ), + status_code=400, + ) + if not {"authorization_code", "refresh_token"}.issubset(set(client_metadata.grant_types)): + return PydanticJSONResponse( + content=RegistrationErrorResponse( + error="invalid_client_metadata", + error_description="grant_types must be authorization_code and refresh_token", + ), + status_code=400, + ) + + # The MCP spec requires servers to use the authorization `code` flow + # with PKCE + if "code" not in client_metadata.response_types: + return PydanticJSONResponse( + content=RegistrationErrorResponse( + error="invalid_client_metadata", + error_description="response_types must include 'code' for authorization_code grant", + ), + status_code=400, + ) + + client_id_issued_at = int(time.time()) + client_secret_expires_at = ( + client_id_issued_at + self.options.client_secret_expiry_seconds + if self.options.client_secret_expiry_seconds is not None + else None + ) + + client_info = OAuthClientInformationFull( + client_id=client_id, + client_id_issued_at=client_id_issued_at, + client_secret=client_secret, + client_secret_expires_at=client_secret_expires_at, + # passthrough information from the client request + redirect_uris=client_metadata.redirect_uris, + token_endpoint_auth_method=client_metadata.token_endpoint_auth_method, + grant_types=client_metadata.grant_types, + response_types=client_metadata.response_types, + client_name=client_metadata.client_name, + client_uri=client_metadata.client_uri, + logo_uri=client_metadata.logo_uri, + scope=client_metadata.scope, + contacts=client_metadata.contacts, + tos_uri=client_metadata.tos_uri, + policy_uri=client_metadata.policy_uri, + jwks_uri=client_metadata.jwks_uri, + jwks=client_metadata.jwks, + software_id=client_metadata.software_id, + software_version=client_metadata.software_version, + ) + try: + # Register client + await self.provider.register_client(client_info) + + # Return client information + return PydanticJSONResponse(content=client_info, status_code=201) + except RegistrationError as e: + # Handle registration errors as defined in RFC 7591 Section 3.2.2 + return PydanticJSONResponse( + content=RegistrationErrorResponse(error=e.error, error_description=e.error_description), + status_code=400, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py new file mode 100644 index 0000000..fa8cfc9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py @@ -0,0 +1,91 @@ +from dataclasses import dataclass +from functools import partial +from typing import Any, Literal + +from pydantic import BaseModel, ValidationError +from starlette.requests import Request +from starlette.responses import Response + +from mcp.server.auth.errors import ( + stringify_pydantic_error, +) +from mcp.server.auth.json_response import PydanticJSONResponse +from mcp.server.auth.middleware.client_auth import AuthenticationError, ClientAuthenticator +from mcp.server.auth.provider import AccessToken, OAuthAuthorizationServerProvider, RefreshToken + + +class RevocationRequest(BaseModel): + """ + # See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 + """ + + token: str + token_type_hint: Literal["access_token", "refresh_token"] | None = None + client_id: str + client_secret: str | None + + +class RevocationErrorResponse(BaseModel): + error: Literal["invalid_request", "unauthorized_client"] + error_description: str | None = None + + +@dataclass +class RevocationHandler: + provider: OAuthAuthorizationServerProvider[Any, Any, Any] + client_authenticator: ClientAuthenticator + + async def handle(self, request: Request) -> Response: + """ + Handler for the OAuth 2.0 Token Revocation endpoint. + """ + try: + client = await self.client_authenticator.authenticate_request(request) + except AuthenticationError as e: # pragma: no cover + return PydanticJSONResponse( + status_code=401, + content=RevocationErrorResponse( + error="unauthorized_client", + error_description=e.message, + ), + ) + + try: + form_data = await request.form() + revocation_request = RevocationRequest.model_validate(dict(form_data)) + except ValidationError as e: + return PydanticJSONResponse( + status_code=400, + content=RevocationErrorResponse( + error="invalid_request", + error_description=stringify_pydantic_error(e), + ), + ) + + loaders = [ + self.provider.load_access_token, + partial(self.provider.load_refresh_token, client), + ] + if revocation_request.token_type_hint == "refresh_token": # pragma: no cover + loaders = reversed(loaders) + + token: None | AccessToken | RefreshToken = None + for loader in loaders: + token = await loader(revocation_request.token) + if token is not None: + break + + # if token is not found, just return HTTP 200 per the RFC + if token and token.client_id == client.client_id: + # Revoke token; provider is not meant to be able to do validation + # at this point that would result in an error + await self.provider.revoke_token(token) + + # Return successful empty response + return Response( + status_code=200, + headers={ + "Cache-Control": "no-store", + "Pragma": "no-cache", + }, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py new file mode 100644 index 0000000..4467da6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py @@ -0,0 +1,241 @@ +import base64 +import hashlib +import time +from dataclasses import dataclass +from typing import Annotated, Any, Literal + +from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, RootModel, ValidationError +from starlette.requests import Request + +from mcp.server.auth.errors import stringify_pydantic_error +from mcp.server.auth.json_response import PydanticJSONResponse +from mcp.server.auth.middleware.client_auth import AuthenticationError, ClientAuthenticator +from mcp.server.auth.provider import OAuthAuthorizationServerProvider, TokenError, TokenErrorCode +from mcp.shared.auth import OAuthToken + + +class AuthorizationCodeRequest(BaseModel): + # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3 + grant_type: Literal["authorization_code"] + code: str = Field(..., description="The authorization code") + redirect_uri: AnyUrl | None = Field(None, description="Must be the same as redirect URI provided in /authorize") + client_id: str + # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + client_secret: str | None = None + # See https://datatracker.ietf.org/doc/html/rfc7636#section-4.5 + code_verifier: str = Field(..., description="PKCE code verifier") + # RFC 8707 resource indicator + resource: str | None = Field(None, description="Resource indicator for the token") + + +class RefreshTokenRequest(BaseModel): + # See https://datatracker.ietf.org/doc/html/rfc6749#section-6 + grant_type: Literal["refresh_token"] + refresh_token: str = Field(..., description="The refresh token") + scope: str | None = Field(None, description="Optional scope parameter") + client_id: str + # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + client_secret: str | None = None + # RFC 8707 resource indicator + resource: str | None = Field(None, description="Resource indicator for the token") + + +class TokenRequest( + RootModel[ + Annotated[ + AuthorizationCodeRequest | RefreshTokenRequest, + Field(discriminator="grant_type"), + ] + ] +): + root: Annotated[ + AuthorizationCodeRequest | RefreshTokenRequest, + Field(discriminator="grant_type"), + ] + + +class TokenErrorResponse(BaseModel): + """ + See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 + """ + + error: TokenErrorCode + error_description: str | None = None + error_uri: AnyHttpUrl | None = None + + +class TokenSuccessResponse(RootModel[OAuthToken]): + # this is just a wrapper over OAuthToken; the only reason we do this + # is to have some separation between the HTTP response type, and the + # type returned by the provider + root: OAuthToken + + +@dataclass +class TokenHandler: + provider: OAuthAuthorizationServerProvider[Any, Any, Any] + client_authenticator: ClientAuthenticator + + def response(self, obj: TokenSuccessResponse | TokenErrorResponse): + status_code = 200 + if isinstance(obj, TokenErrorResponse): + status_code = 400 + + return PydanticJSONResponse( + content=obj, + status_code=status_code, + headers={ + "Cache-Control": "no-store", + "Pragma": "no-cache", + }, + ) + + async def handle(self, request: Request): + try: + client_info = await self.client_authenticator.authenticate_request(request) + except AuthenticationError as e: + # Authentication failures should return 401 + return PydanticJSONResponse( + content=TokenErrorResponse( + error="unauthorized_client", + error_description=e.message, + ), + status_code=401, + headers={ + "Cache-Control": "no-store", + "Pragma": "no-cache", + }, + ) + + try: + form_data = await request.form() + token_request = TokenRequest.model_validate(dict(form_data)).root + except ValidationError as validation_error: # pragma: no cover + return self.response( + TokenErrorResponse( + error="invalid_request", + error_description=stringify_pydantic_error(validation_error), + ) + ) + + if token_request.grant_type not in client_info.grant_types: # pragma: no cover + return self.response( + TokenErrorResponse( + error="unsupported_grant_type", + error_description=(f"Unsupported grant type (supported grant types are {client_info.grant_types})"), + ) + ) + + tokens: OAuthToken + + match token_request: + case AuthorizationCodeRequest(): + auth_code = await self.provider.load_authorization_code(client_info, token_request.code) + if auth_code is None or auth_code.client_id != token_request.client_id: + # if code belongs to different client, pretend it doesn't exist + return self.response( + TokenErrorResponse( + error="invalid_grant", + error_description="authorization code does not exist", + ) + ) + + # make auth codes expire after a deadline + # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5 + if auth_code.expires_at < time.time(): + return self.response( + TokenErrorResponse( + error="invalid_grant", + error_description="authorization code has expired", + ) + ) + + # verify redirect_uri doesn't change between /authorize and /tokens + # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6 + if auth_code.redirect_uri_provided_explicitly: + authorize_request_redirect_uri = auth_code.redirect_uri + else: # pragma: no cover + authorize_request_redirect_uri = None + + # Convert both sides to strings for comparison to handle AnyUrl vs string issues + token_redirect_str = str(token_request.redirect_uri) if token_request.redirect_uri is not None else None + auth_redirect_str = ( + str(authorize_request_redirect_uri) if authorize_request_redirect_uri is not None else None + ) + + if token_redirect_str != auth_redirect_str: + return self.response( + TokenErrorResponse( + error="invalid_request", + error_description=("redirect_uri did not match the one used when creating auth code"), + ) + ) + + # Verify PKCE code verifier + sha256 = hashlib.sha256(token_request.code_verifier.encode()).digest() + hashed_code_verifier = base64.urlsafe_b64encode(sha256).decode().rstrip("=") + + if hashed_code_verifier != auth_code.code_challenge: + # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6 + return self.response( + TokenErrorResponse( + error="invalid_grant", + error_description="incorrect code_verifier", + ) + ) + + try: + # Exchange authorization code for tokens + tokens = await self.provider.exchange_authorization_code(client_info, auth_code) + except TokenError as e: + return self.response( + TokenErrorResponse( + error=e.error, + error_description=e.error_description, + ) + ) + + case RefreshTokenRequest(): # pragma: no cover + refresh_token = await self.provider.load_refresh_token(client_info, token_request.refresh_token) + if refresh_token is None or refresh_token.client_id != token_request.client_id: + # if token belongs to different client, pretend it doesn't exist + return self.response( + TokenErrorResponse( + error="invalid_grant", + error_description="refresh token does not exist", + ) + ) + + if refresh_token.expires_at and refresh_token.expires_at < time.time(): + # if the refresh token has expired, pretend it doesn't exist + return self.response( + TokenErrorResponse( + error="invalid_grant", + error_description="refresh token has expired", + ) + ) + + # Parse scopes if provided + scopes = token_request.scope.split(" ") if token_request.scope else refresh_token.scopes + + for scope in scopes: + if scope not in refresh_token.scopes: + return self.response( + TokenErrorResponse( + error="invalid_scope", + error_description=(f"cannot request scope `{scope}` not provided by refresh token"), + ) + ) + + try: + # Exchange refresh token for new tokens + tokens = await self.provider.exchange_refresh_token(client_info, refresh_token, scopes) + except TokenError as e: + return self.response( + TokenErrorResponse( + error=e.error, + error_description=e.error_description, + ) + ) + + return self.response(TokenSuccessResponse(root=tokens)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py new file mode 100644 index 0000000..bd95bd6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py @@ -0,0 +1,10 @@ +from typing import Any + +from starlette.responses import JSONResponse + + +class PydanticJSONResponse(JSONResponse): + # use pydantic json serialization instead of the stock `json.dumps`, + # so that we can handle serializing pydantic models like AnyHttpUrl + def render(self, content: Any) -> bytes: + return content.model_dump_json(exclude_none=True).encode("utf-8") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py new file mode 100644 index 0000000..ba3ff63 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py @@ -0,0 +1,3 @@ +""" +Middleware for MCP authorization. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py new file mode 100644 index 0000000..e2116c3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py @@ -0,0 +1,48 @@ +import contextvars + +from starlette.types import ASGIApp, Receive, Scope, Send + +from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser +from mcp.server.auth.provider import AccessToken + +# Create a contextvar to store the authenticated user +# The default is None, indicating no authenticated user is present +auth_context_var = contextvars.ContextVar[AuthenticatedUser | None]("auth_context", default=None) + + +def get_access_token() -> AccessToken | None: + """ + Get the access token from the current context. + + Returns: + The access token if an authenticated user is available, None otherwise. + """ + auth_user = auth_context_var.get() + return auth_user.access_token if auth_user else None + + +class AuthContextMiddleware: + """ + Middleware that extracts the authenticated user from the request + and sets it in a contextvar for easy access throughout the request lifecycle. + + This middleware should be added after the AuthenticationMiddleware in the + middleware stack to ensure that the user is properly authenticated before + being stored in the context. + """ + + def __init__(self, app: ASGIApp): + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + user = scope.get("user") + if isinstance(user, AuthenticatedUser): + # Set the authenticated user in the contextvar + token = auth_context_var.set(user) + try: + await self.app(scope, receive, send) + finally: + auth_context_var.reset(token) + else: + # No authenticated user, just process the request + await self.app(scope, receive, send) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py new file mode 100644 index 0000000..64c9b88 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py @@ -0,0 +1,128 @@ +import json +import time +from typing import Any + +from pydantic import AnyHttpUrl +from starlette.authentication import AuthCredentials, AuthenticationBackend, SimpleUser +from starlette.requests import HTTPConnection +from starlette.types import Receive, Scope, Send + +from mcp.server.auth.provider import AccessToken, TokenVerifier + + +class AuthenticatedUser(SimpleUser): + """User with authentication info.""" + + def __init__(self, auth_info: AccessToken): + super().__init__(auth_info.client_id) + self.access_token = auth_info + self.scopes = auth_info.scopes + + +class BearerAuthBackend(AuthenticationBackend): + """ + Authentication backend that validates Bearer tokens using a TokenVerifier. + """ + + def __init__(self, token_verifier: TokenVerifier): + self.token_verifier = token_verifier + + async def authenticate(self, conn: HTTPConnection): + auth_header = next( + (conn.headers.get(key) for key in conn.headers if key.lower() == "authorization"), + None, + ) + if not auth_header or not auth_header.lower().startswith("bearer "): + return None + + token = auth_header[7:] # Remove "Bearer " prefix + + # Validate the token with the verifier + auth_info = await self.token_verifier.verify_token(token) + + if not auth_info: + return None + + if auth_info.expires_at and auth_info.expires_at < int(time.time()): + return None + + return AuthCredentials(auth_info.scopes), AuthenticatedUser(auth_info) + + +class RequireAuthMiddleware: + """ + Middleware that requires a valid Bearer token in the Authorization header. + + This will validate the token with the auth provider and store the resulting + auth info in the request state. + """ + + def __init__( + self, + app: Any, + required_scopes: list[str], + resource_metadata_url: AnyHttpUrl | None = None, + ): + """ + Initialize the middleware. + + Args: + app: ASGI application + required_scopes: List of scopes that the token must have + resource_metadata_url: Optional protected resource metadata URL for WWW-Authenticate header + """ + self.app = app + self.required_scopes = required_scopes + self.resource_metadata_url = resource_metadata_url + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + auth_user = scope.get("user") + if not isinstance(auth_user, AuthenticatedUser): + await self._send_auth_error( + send, status_code=401, error="invalid_token", description="Authentication required" + ) + return + + auth_credentials = scope.get("auth") + + for required_scope in self.required_scopes: + # auth_credentials should always be provided; this is just paranoia + if auth_credentials is None or required_scope not in auth_credentials.scopes: + await self._send_auth_error( + send, status_code=403, error="insufficient_scope", description=f"Required scope: {required_scope}" + ) + return + + await self.app(scope, receive, send) + + async def _send_auth_error(self, send: Send, status_code: int, error: str, description: str) -> None: + """Send an authentication error response with WWW-Authenticate header.""" + # Build WWW-Authenticate header value + www_auth_parts = [f'error="{error}"', f'error_description="{description}"'] + if self.resource_metadata_url: # pragma: no cover + www_auth_parts.append(f'resource_metadata="{self.resource_metadata_url}"') + + www_authenticate = f"Bearer {', '.join(www_auth_parts)}" + + # Send response + body = {"error": error, "error_description": description} + body_bytes = json.dumps(body).encode() + + await send( + { + "type": "http.response.start", + "status": status_code, + "headers": [ + (b"content-type", b"application/json"), + (b"content-length", str(len(body_bytes)).encode()), + (b"www-authenticate", www_authenticate.encode()), + ], + } + ) + + await send( + { + "type": "http.response.body", + "body": body_bytes, + } + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py new file mode 100644 index 0000000..6126c6e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py @@ -0,0 +1,115 @@ +import base64 +import binascii +import hmac +import time +from typing import Any +from urllib.parse import unquote + +from starlette.requests import Request + +from mcp.server.auth.provider import OAuthAuthorizationServerProvider +from mcp.shared.auth import OAuthClientInformationFull + + +class AuthenticationError(Exception): + def __init__(self, message: str): + self.message = message # pragma: no cover + + +class ClientAuthenticator: + """ + ClientAuthenticator is a callable which validates requests from a client + application, used to verify /token calls. + If, during registration, the client requested to be issued a secret, the + authenticator asserts that /token calls must be authenticated with + that same token. + NOTE: clients can opt for no authentication during registration, in which case this + logic is skipped. + """ + + def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]): + """ + Initialize the dependency. + + Args: + provider: Provider to look up client information + """ + self.provider = provider + + async def authenticate_request(self, request: Request) -> OAuthClientInformationFull: + """ + Authenticate a client from an HTTP request. + + Extracts client credentials from the appropriate location based on the + client's registered authentication method and validates them. + + Args: + request: The HTTP request containing client credentials + + Returns: + The authenticated client information + + Raises: + AuthenticationError: If authentication fails + """ + form_data = await request.form() + client_id = form_data.get("client_id") + if not client_id: + raise AuthenticationError("Missing client_id") + + client = await self.provider.get_client(str(client_id)) + if not client: + raise AuthenticationError("Invalid client_id") # pragma: no cover + + request_client_secret: str | None = None + auth_header = request.headers.get("Authorization", "") + + if client.token_endpoint_auth_method == "client_secret_basic": + if not auth_header.startswith("Basic "): + raise AuthenticationError("Missing or invalid Basic authentication in Authorization header") + + try: + encoded_credentials = auth_header[6:] # Remove "Basic " prefix + decoded = base64.b64decode(encoded_credentials).decode("utf-8") + if ":" not in decoded: + raise ValueError("Invalid Basic auth format") + basic_client_id, request_client_secret = decoded.split(":", 1) + + # URL-decode both parts per RFC 6749 Section 2.3.1 + basic_client_id = unquote(basic_client_id) + request_client_secret = unquote(request_client_secret) + + if basic_client_id != client_id: + raise AuthenticationError("Client ID mismatch in Basic auth") + except (ValueError, UnicodeDecodeError, binascii.Error): + raise AuthenticationError("Invalid Basic authentication header") + + elif client.token_endpoint_auth_method == "client_secret_post": + raw_form_data = form_data.get("client_secret") + # form_data.get() can return a UploadFile or None, so we need to check if it's a string + if isinstance(raw_form_data, str): + request_client_secret = str(raw_form_data) + + elif client.token_endpoint_auth_method == "none": + request_client_secret = None + else: + raise AuthenticationError( # pragma: no cover + f"Unsupported auth method: {client.token_endpoint_auth_method}" + ) + + # If client from the store expects a secret, validate that the request provides + # that secret + if client.client_secret: # pragma: no branch + if not request_client_secret: + raise AuthenticationError("Client secret is required") # pragma: no cover + + # hmac.compare_digest requires that both arguments are either bytes or a `str` containing + # only ASCII characters. Since we do not control `request_client_secret`, we encode both + # arguments to bytes. + if not hmac.compare_digest(client.client_secret.encode(), request_client_secret.encode()): + raise AuthenticationError("Invalid client_secret") # pragma: no cover + + if client.client_secret_expires_at and client.client_secret_expires_at < int(time.time()): + raise AuthenticationError("Client secret has expired") # pragma: no cover + + return client diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py new file mode 100644 index 0000000..96296c1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py @@ -0,0 +1,301 @@ +from dataclasses import dataclass +from typing import Generic, Literal, Protocol, TypeVar +from urllib.parse import parse_qs, urlencode, urlparse, urlunparse + +from pydantic import AnyUrl, BaseModel + +from mcp.shared.auth import OAuthClientInformationFull, OAuthToken + + +class AuthorizationParams(BaseModel): + state: str | None + scopes: list[str] | None + code_challenge: str + redirect_uri: AnyUrl + redirect_uri_provided_explicitly: bool + resource: str | None = None # RFC 8707 resource indicator + + +class AuthorizationCode(BaseModel): + code: str + scopes: list[str] + expires_at: float + client_id: str + code_challenge: str + redirect_uri: AnyUrl + redirect_uri_provided_explicitly: bool + resource: str | None = None # RFC 8707 resource indicator + + +class RefreshToken(BaseModel): + token: str + client_id: str + scopes: list[str] + expires_at: int | None = None + + +class AccessToken(BaseModel): + token: str + client_id: str + scopes: list[str] + expires_at: int | None = None + resource: str | None = None # RFC 8707 resource indicator + + +RegistrationErrorCode = Literal[ + "invalid_redirect_uri", + "invalid_client_metadata", + "invalid_software_statement", + "unapproved_software_statement", +] + + +@dataclass(frozen=True) +class RegistrationError(Exception): + error: RegistrationErrorCode + error_description: str | None = None + + +AuthorizationErrorCode = Literal[ + "invalid_request", + "unauthorized_client", + "access_denied", + "unsupported_response_type", + "invalid_scope", + "server_error", + "temporarily_unavailable", +] + + +@dataclass(frozen=True) +class AuthorizeError(Exception): + error: AuthorizationErrorCode + error_description: str | None = None + + +TokenErrorCode = Literal[ + "invalid_request", + "invalid_client", + "invalid_grant", + "unauthorized_client", + "unsupported_grant_type", + "invalid_scope", +] + + +@dataclass(frozen=True) +class TokenError(Exception): + error: TokenErrorCode + error_description: str | None = None + + +class TokenVerifier(Protocol): + """Protocol for verifying bearer tokens.""" + + async def verify_token(self, token: str) -> AccessToken | None: + """Verify a bearer token and return access info if valid.""" + + +# NOTE: FastMCP doesn't render any of these types in the user response, so it's +# OK to add fields to subclasses which should not be exposed externally. +AuthorizationCodeT = TypeVar("AuthorizationCodeT", bound=AuthorizationCode) +RefreshTokenT = TypeVar("RefreshTokenT", bound=RefreshToken) +AccessTokenT = TypeVar("AccessTokenT", bound=AccessToken) + + +class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, RefreshTokenT, AccessTokenT]): + async def get_client(self, client_id: str) -> OAuthClientInformationFull | None: + """ + Retrieves client information by client ID. + + Implementors MAY raise NotImplementedError if dynamic client registration is + disabled in ClientRegistrationOptions. + + Args: + client_id: The ID of the client to retrieve. + + Returns: + The client information, or None if the client does not exist. + """ + + async def register_client(self, client_info: OAuthClientInformationFull) -> None: + """ + Saves client information as part of registering it. + + Implementors MAY raise NotImplementedError if dynamic client registration is + disabled in ClientRegistrationOptions. + + Args: + client_info: The client metadata to register. + + Raises: + RegistrationError: If the client metadata is invalid. + """ + + async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str: + """ + Called as part of the /authorize endpoint, and returns a URL that the client + will be redirected to. + Many MCP implementations will redirect to a third-party provider to perform + a second OAuth exchange with that provider. In this sort of setup, the client + has an OAuth connection with the MCP server, and the MCP server has an OAuth + connection with the 3rd-party provider. At the end of this flow, the client + should be redirected to the redirect_uri from params.redirect_uri. + + +--------+ +------------+ +-------------------+ + | | | | | | + | Client | --> | MCP Server | --> | 3rd Party OAuth | + | | | | | Server | + +--------+ +------------+ +-------------------+ + | ^ | + +------------+ | | | + | | | | Redirect | + |redirect_uri|<-----+ +------------------+ + | | + +------------+ + + Implementations will need to define another handler on the MCP server return + flow to perform the second redirect, and generate and store an authorization + code as part of completing the OAuth authorization step. + + Implementations SHOULD generate an authorization code with at least 160 bits of + entropy, + and MUST generate an authorization code with at least 128 bits of entropy. + See https://datatracker.ietf.org/doc/html/rfc6749#section-10.10. + + Args: + client: The client requesting authorization. + params: The parameters of the authorization request. + + Returns: + A URL to redirect the client to for authorization. + + Raises: + AuthorizeError: If the authorization request is invalid. + """ + ... + + async def load_authorization_code( + self, client: OAuthClientInformationFull, authorization_code: str + ) -> AuthorizationCodeT | None: + """ + Loads an AuthorizationCode by its code. + + Args: + client: The client that requested the authorization code. + authorization_code: The authorization code to get the challenge for. + + Returns: + The AuthorizationCode, or None if not found + """ + ... + + async def exchange_authorization_code( + self, client: OAuthClientInformationFull, authorization_code: AuthorizationCodeT + ) -> OAuthToken: + """ + Exchanges an authorization code for an access token and refresh token. + + Args: + client: The client exchanging the authorization code. + authorization_code: The authorization code to exchange. + + Returns: + The OAuth token, containing access and refresh tokens. + + Raises: + TokenError: If the request is invalid + """ + ... + + async def load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshTokenT | None: + """ + Loads a RefreshToken by its token string. + + Args: + client: The client that is requesting to load the refresh token. + refresh_token: The refresh token string to load. + + Returns: + The RefreshToken object if found, or None if not found. + """ + ... + + async def exchange_refresh_token( + self, + client: OAuthClientInformationFull, + refresh_token: RefreshTokenT, + scopes: list[str], + ) -> OAuthToken: + """ + Exchanges a refresh token for an access token and refresh token. + + Implementations SHOULD rotate both the access token and refresh token. + + Args: + client: The client exchanging the refresh token. + refresh_token: The refresh token to exchange. + scopes: Optional scopes to request with the new access token. + + Returns: + The OAuth token, containing access and refresh tokens. + + Raises: + TokenError: If the request is invalid + """ + ... + + async def load_access_token(self, token: str) -> AccessTokenT | None: + """ + Loads an access token by its token. + + Args: + token: The access token to verify. + + Returns: + The AuthInfo, or None if the token is invalid. + """ + + async def revoke_token( + self, + token: AccessTokenT | RefreshTokenT, + ) -> None: + """ + Revokes an access or refresh token. + + If the given token is invalid or already revoked, this method should do nothing. + + Implementations SHOULD revoke both the access token and its corresponding + refresh token, regardless of which of the access token or refresh token is + provided. + + Args: + token: the token to revoke + """ + + +def construct_redirect_uri(redirect_uri_base: str, **params: str | None) -> str: + parsed_uri = urlparse(redirect_uri_base) + query_params = [(k, v) for k, vs in parse_qs(parsed_uri.query).items() for v in vs] + for k, v in params.items(): + if v is not None: + query_params.append((k, v)) + + redirect_uri = urlunparse(parsed_uri._replace(query=urlencode(query_params))) + return redirect_uri + + +class ProviderTokenVerifier(TokenVerifier): + """Token verifier that uses an OAuthAuthorizationServerProvider. + + This is provided for backwards compatibility with existing auth_server_provider + configurations. For new implementations using AS/RS separation, consider using + the TokenVerifier protocol with a dedicated implementation like IntrospectionTokenVerifier. + """ + + def __init__(self, provider: "OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]"): + self.provider = provider + + async def verify_token(self, token: str) -> AccessToken | None: + """Verify token using the provider's load_access_token method.""" + return await self.provider.load_access_token(token) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py new file mode 100644 index 0000000..71a9c8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py @@ -0,0 +1,253 @@ +from collections.abc import Awaitable, Callable +from typing import Any +from urllib.parse import urlparse + +from pydantic import AnyHttpUrl +from starlette.middleware.cors import CORSMiddleware +from starlette.requests import Request +from starlette.responses import Response +from starlette.routing import Route, request_response # type: ignore +from starlette.types import ASGIApp + +from mcp.server.auth.handlers.authorize import AuthorizationHandler +from mcp.server.auth.handlers.metadata import MetadataHandler +from mcp.server.auth.handlers.register import RegistrationHandler +from mcp.server.auth.handlers.revoke import RevocationHandler +from mcp.server.auth.handlers.token import TokenHandler +from mcp.server.auth.middleware.client_auth import ClientAuthenticator +from mcp.server.auth.provider import OAuthAuthorizationServerProvider +from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions +from mcp.server.streamable_http import MCP_PROTOCOL_VERSION_HEADER +from mcp.shared.auth import OAuthMetadata + + +def validate_issuer_url(url: AnyHttpUrl): + """ + Validate that the issuer URL meets OAuth 2.0 requirements. + + Args: + url: The issuer URL to validate + + Raises: + ValueError: If the issuer URL is invalid + """ + + # RFC 8414 requires HTTPS, but we allow localhost HTTP for testing + if ( + url.scheme != "https" + and url.host != "localhost" + and (url.host is not None and not url.host.startswith("127.0.0.1")) + ): + raise ValueError("Issuer URL must be HTTPS") # pragma: no cover + + # No fragments or query parameters allowed + if url.fragment: + raise ValueError("Issuer URL must not have a fragment") # pragma: no cover + if url.query: + raise ValueError("Issuer URL must not have a query string") # pragma: no cover + + +AUTHORIZATION_PATH = "/authorize" +TOKEN_PATH = "/token" +REGISTRATION_PATH = "/register" +REVOCATION_PATH = "/revoke" + + +def cors_middleware( + handler: Callable[[Request], Response | Awaitable[Response]], + allow_methods: list[str], +) -> ASGIApp: + cors_app = CORSMiddleware( + app=request_response(handler), + allow_origins="*", + allow_methods=allow_methods, + allow_headers=[MCP_PROTOCOL_VERSION_HEADER], + ) + return cors_app + + +def create_auth_routes( + provider: OAuthAuthorizationServerProvider[Any, Any, Any], + issuer_url: AnyHttpUrl, + service_documentation_url: AnyHttpUrl | None = None, + client_registration_options: ClientRegistrationOptions | None = None, + revocation_options: RevocationOptions | None = None, +) -> list[Route]: + validate_issuer_url(issuer_url) + + client_registration_options = client_registration_options or ClientRegistrationOptions() + revocation_options = revocation_options or RevocationOptions() + metadata = build_metadata( + issuer_url, + service_documentation_url, + client_registration_options, + revocation_options, + ) + client_authenticator = ClientAuthenticator(provider) + + # Create routes + # Allow CORS requests for endpoints meant to be hit by the OAuth client + # (with the client secret). This is intended to support things like MCP Inspector, + # where the client runs in a web browser. + routes = [ + Route( + "/.well-known/oauth-authorization-server", + endpoint=cors_middleware( + MetadataHandler(metadata).handle, + ["GET", "OPTIONS"], + ), + methods=["GET", "OPTIONS"], + ), + Route( + AUTHORIZATION_PATH, + # do not allow CORS for authorization endpoint; + # clients should just redirect to this + endpoint=AuthorizationHandler(provider).handle, + methods=["GET", "POST"], + ), + Route( + TOKEN_PATH, + endpoint=cors_middleware( + TokenHandler(provider, client_authenticator).handle, + ["POST", "OPTIONS"], + ), + methods=["POST", "OPTIONS"], + ), + ] + + if client_registration_options.enabled: # pragma: no branch + registration_handler = RegistrationHandler( + provider, + options=client_registration_options, + ) + routes.append( + Route( + REGISTRATION_PATH, + endpoint=cors_middleware( + registration_handler.handle, + ["POST", "OPTIONS"], + ), + methods=["POST", "OPTIONS"], + ) + ) + + if revocation_options.enabled: # pragma: no branch + revocation_handler = RevocationHandler(provider, client_authenticator) + routes.append( + Route( + REVOCATION_PATH, + endpoint=cors_middleware( + revocation_handler.handle, + ["POST", "OPTIONS"], + ), + methods=["POST", "OPTIONS"], + ) + ) + + return routes + + +def build_metadata( + issuer_url: AnyHttpUrl, + service_documentation_url: AnyHttpUrl | None, + client_registration_options: ClientRegistrationOptions, + revocation_options: RevocationOptions, +) -> OAuthMetadata: + authorization_url = AnyHttpUrl(str(issuer_url).rstrip("/") + AUTHORIZATION_PATH) + token_url = AnyHttpUrl(str(issuer_url).rstrip("/") + TOKEN_PATH) + + # Create metadata + metadata = OAuthMetadata( + issuer=issuer_url, + authorization_endpoint=authorization_url, + token_endpoint=token_url, + scopes_supported=client_registration_options.valid_scopes, + response_types_supported=["code"], + response_modes_supported=None, + grant_types_supported=["authorization_code", "refresh_token"], + token_endpoint_auth_methods_supported=["client_secret_post", "client_secret_basic"], + token_endpoint_auth_signing_alg_values_supported=None, + service_documentation=service_documentation_url, + ui_locales_supported=None, + op_policy_uri=None, + op_tos_uri=None, + introspection_endpoint=None, + code_challenge_methods_supported=["S256"], + ) + + # Add registration endpoint if supported + if client_registration_options.enabled: # pragma: no branch + metadata.registration_endpoint = AnyHttpUrl(str(issuer_url).rstrip("/") + REGISTRATION_PATH) + + # Add revocation endpoint if supported + if revocation_options.enabled: # pragma: no branch + metadata.revocation_endpoint = AnyHttpUrl(str(issuer_url).rstrip("/") + REVOCATION_PATH) + metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post", "client_secret_basic"] + + return metadata + + +def build_resource_metadata_url(resource_server_url: AnyHttpUrl) -> AnyHttpUrl: + """ + Build RFC 9728 compliant protected resource metadata URL. + + Inserts /.well-known/oauth-protected-resource between host and resource path + as specified in RFC 9728 §3.1. + + Args: + resource_server_url: The resource server URL (e.g., https://example.com/mcp) + + Returns: + The metadata URL (e.g., https://example.com/.well-known/oauth-protected-resource/mcp) + """ + parsed = urlparse(str(resource_server_url)) + # Handle trailing slash: if path is just "/", treat as empty + resource_path = parsed.path if parsed.path != "/" else "" + return AnyHttpUrl(f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-protected-resource{resource_path}") + + +def create_protected_resource_routes( + resource_url: AnyHttpUrl, + authorization_servers: list[AnyHttpUrl], + scopes_supported: list[str] | None = None, + resource_name: str | None = None, + resource_documentation: AnyHttpUrl | None = None, +) -> list[Route]: + """ + Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728). + + Args: + resource_url: The URL of this resource server + authorization_servers: List of authorization servers that can issue tokens + scopes_supported: Optional list of scopes supported by this resource + + Returns: + List of Starlette routes for protected resource metadata + """ + from mcp.server.auth.handlers.metadata import ProtectedResourceMetadataHandler + from mcp.shared.auth import ProtectedResourceMetadata + + metadata = ProtectedResourceMetadata( + resource=resource_url, + authorization_servers=authorization_servers, + scopes_supported=scopes_supported, + resource_name=resource_name, + resource_documentation=resource_documentation, + # bearer_methods_supported defaults to ["header"] in the model + ) + + handler = ProtectedResourceMetadataHandler(metadata) + + # RFC 9728 §3.1: Register route at /.well-known/oauth-protected-resource + resource path + metadata_url = build_resource_metadata_url(resource_url) + # Extract just the path part for route registration + parsed = urlparse(str(metadata_url)) + well_known_path = parsed.path + + return [ + Route( + well_known_path, + endpoint=cors_middleware(handler.handle, ["GET", "OPTIONS"]), + methods=["GET", "OPTIONS"], + ) + ] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py new file mode 100644 index 0000000..1649826 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py @@ -0,0 +1,30 @@ +from pydantic import AnyHttpUrl, BaseModel, Field + + +class ClientRegistrationOptions(BaseModel): + enabled: bool = False + client_secret_expiry_seconds: int | None = None + valid_scopes: list[str] | None = None + default_scopes: list[str] | None = None + + +class RevocationOptions(BaseModel): + enabled: bool = False + + +class AuthSettings(BaseModel): + issuer_url: AnyHttpUrl = Field( + ..., + description="OAuth authorization server URL that issues tokens for this resource server.", + ) + service_documentation_url: AnyHttpUrl | None = None + client_registration_options: ClientRegistrationOptions | None = None + revocation_options: RevocationOptions | None = None + required_scopes: list[str] | None = None + + # Resource Server settings (when operating as RS only) + resource_server_url: AnyHttpUrl | None = Field( + ..., + description="The URL of the MCP server to be used as the resource identifier " + "and base route to look up OAuth Protected Resource Metadata.", + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py new file mode 100644 index 0000000..4919541 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py @@ -0,0 +1,190 @@ +"""Elicitation utilities for MCP servers.""" + +from __future__ import annotations + +import types +from collections.abc import Sequence +from typing import Generic, Literal, TypeVar, Union, get_args, get_origin + +from pydantic import BaseModel + +from mcp.server.session import ServerSession +from mcp.types import RequestId + +ElicitSchemaModelT = TypeVar("ElicitSchemaModelT", bound=BaseModel) + + +class AcceptedElicitation(BaseModel, Generic[ElicitSchemaModelT]): + """Result when user accepts the elicitation.""" + + action: Literal["accept"] = "accept" + data: ElicitSchemaModelT + + +class DeclinedElicitation(BaseModel): + """Result when user declines the elicitation.""" + + action: Literal["decline"] = "decline" + + +class CancelledElicitation(BaseModel): + """Result when user cancels the elicitation.""" + + action: Literal["cancel"] = "cancel" + + +ElicitationResult = AcceptedElicitation[ElicitSchemaModelT] | DeclinedElicitation | CancelledElicitation + + +class AcceptedUrlElicitation(BaseModel): + """Result when user accepts a URL mode elicitation.""" + + action: Literal["accept"] = "accept" + + +UrlElicitationResult = AcceptedUrlElicitation | DeclinedElicitation | CancelledElicitation + + +# Primitive types allowed in elicitation schemas +_ELICITATION_PRIMITIVE_TYPES = (str, int, float, bool) + + +def _validate_elicitation_schema(schema: type[BaseModel]) -> None: + """Validate that a Pydantic model only contains primitive field types.""" + for field_name, field_info in schema.model_fields.items(): + annotation = field_info.annotation + + if annotation is None or annotation is types.NoneType: # pragma: no cover + continue + elif _is_primitive_field(annotation): + continue + elif _is_string_sequence(annotation): + continue + else: + raise TypeError( + f"Elicitation schema field '{field_name}' must be a primitive type " + f"{_ELICITATION_PRIMITIVE_TYPES}, a sequence of strings (list[str], etc.), " + f"or Optional of these types. Nested models and complex types are not allowed." + ) + + +def _is_string_sequence(annotation: type) -> bool: + """Check if annotation is a sequence of strings (list[str], Sequence[str], etc).""" + origin = get_origin(annotation) + # Check if it's a sequence-like type with str elements + if origin: + try: + if issubclass(origin, Sequence): + args = get_args(annotation) + # Should have single str type arg + return len(args) == 1 and args[0] is str + except TypeError: # pragma: no cover + # origin is not a class, so it can't be a subclass of Sequence + pass + return False + + +def _is_primitive_field(annotation: type) -> bool: + """Check if a field is a primitive type allowed in elicitation schemas.""" + # Handle basic primitive types + if annotation in _ELICITATION_PRIMITIVE_TYPES: + return True + + # Handle Union types + origin = get_origin(annotation) + if origin is Union or origin is types.UnionType: + args = get_args(annotation) + # All args must be primitive types, None, or string sequences + return all( + arg is types.NoneType or arg in _ELICITATION_PRIMITIVE_TYPES or _is_string_sequence(arg) for arg in args + ) + + return False + + +async def elicit_with_validation( + session: ServerSession, + message: str, + schema: type[ElicitSchemaModelT], + related_request_id: RequestId | None = None, +) -> ElicitationResult[ElicitSchemaModelT]: + """Elicit information from the client/user with schema validation (form mode). + + This method can be used to interactively ask for additional information from the + client within a tool's execution. The client might display the message to the + user and collect a response according to the provided schema. Or in case a + client is an agent, it might decide how to handle the elicitation -- either by asking + the user or automatically generating a response. + + For sensitive data like credentials or OAuth flows, use elicit_url() instead. + """ + # Validate that schema only contains primitive types and fail loudly if not + _validate_elicitation_schema(schema) + + json_schema = schema.model_json_schema() + + result = await session.elicit_form( + message=message, + requestedSchema=json_schema, + related_request_id=related_request_id, + ) + + if result.action == "accept" and result.content is not None: + # Validate and parse the content using the schema + validated_data = schema.model_validate(result.content) + return AcceptedElicitation(data=validated_data) + elif result.action == "decline": + return DeclinedElicitation() + elif result.action == "cancel": # pragma: no cover + return CancelledElicitation() + else: # pragma: no cover + # This should never happen, but handle it just in case + raise ValueError(f"Unexpected elicitation action: {result.action}") + + +async def elicit_url( + session: ServerSession, + message: str, + url: str, + elicitation_id: str, + related_request_id: RequestId | None = None, +) -> UrlElicitationResult: + """Elicit information from the user via out-of-band URL navigation (URL mode). + + This method directs the user to an external URL where sensitive interactions can + occur without passing data through the MCP client. Use this for: + - Collecting sensitive credentials (API keys, passwords) + - OAuth authorization flows with third-party services + - Payment and subscription flows + - Any interaction where data should not pass through the LLM context + + The response indicates whether the user consented to navigate to the URL. + The actual interaction happens out-of-band. When the elicitation completes, + the server should send an ElicitCompleteNotification to notify the client. + + Args: + session: The server session + message: Human-readable explanation of why the interaction is needed + url: The URL the user should navigate to + elicitation_id: Unique identifier for tracking this elicitation + related_request_id: Optional ID of the request that triggered this elicitation + + Returns: + UrlElicitationResult indicating accept, decline, or cancel + """ + result = await session.elicit_url( + message=message, + url=url, + elicitation_id=elicitation_id, + related_request_id=related_request_id, + ) + + if result.action == "accept": + return AcceptedUrlElicitation() + elif result.action == "decline": + return DeclinedElicitation() + elif result.action == "cancel": + return CancelledElicitation() + else: # pragma: no cover + # This should never happen, but handle it just in case + raise ValueError(f"Unexpected elicitation action: {result.action}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py new file mode 100644 index 0000000..824bb8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py @@ -0,0 +1,11 @@ +""" +Server-side experimental features. + +WARNING: These APIs are experimental and may change without notice. + +Import directly from submodules: +- mcp.server.experimental.task_context.ServerTaskContext +- mcp.server.experimental.task_support.TaskSupport +- mcp.server.experimental.task_result_handler.TaskResultHandler +- mcp.server.experimental.request_context.Experimental +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py new file mode 100644 index 0000000..78e75be --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py @@ -0,0 +1,238 @@ +""" +Experimental request context features. + +This module provides the Experimental class which gives access to experimental +features within a request context, such as task-augmented request handling. + +WARNING: These APIs are experimental and may change without notice. +""" + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass, field +from typing import Any + +from mcp.server.experimental.task_context import ServerTaskContext +from mcp.server.experimental.task_support import TaskSupport +from mcp.server.session import ServerSession +from mcp.shared.exceptions import McpError +from mcp.shared.experimental.tasks.helpers import MODEL_IMMEDIATE_RESPONSE_KEY, is_terminal +from mcp.types import ( + METHOD_NOT_FOUND, + TASK_FORBIDDEN, + TASK_REQUIRED, + ClientCapabilities, + CreateTaskResult, + ErrorData, + Result, + TaskExecutionMode, + TaskMetadata, + Tool, +) + + +@dataclass +class Experimental: + """ + Experimental features context for task-augmented requests. + + Provides helpers for validating task execution compatibility and + running tasks with automatic lifecycle management. + + WARNING: This API is experimental and may change without notice. + """ + + task_metadata: TaskMetadata | None = None + _client_capabilities: ClientCapabilities | None = field(default=None, repr=False) + _session: ServerSession | None = field(default=None, repr=False) + _task_support: TaskSupport | None = field(default=None, repr=False) + + @property + def is_task(self) -> bool: + """Check if this request is task-augmented.""" + return self.task_metadata is not None + + @property + def client_supports_tasks(self) -> bool: + """Check if the client declared task support.""" + if self._client_capabilities is None: + return False + return self._client_capabilities.tasks is not None + + def validate_task_mode( + self, + tool_task_mode: TaskExecutionMode | None, + *, + raise_error: bool = True, + ) -> ErrorData | None: + """ + Validate that the request is compatible with the tool's task execution mode. + + Per MCP spec: + - "required": Clients MUST invoke as task. Server returns -32601 if not. + - "forbidden" (or None): Clients MUST NOT invoke as task. Server returns -32601 if they do. + - "optional": Either is acceptable. + + Args: + tool_task_mode: The tool's execution.taskSupport value + ("forbidden", "optional", "required", or None) + raise_error: If True, raises McpError on validation failure. If False, returns ErrorData. + + Returns: + None if valid, ErrorData if invalid and raise_error=False + + Raises: + McpError: If invalid and raise_error=True + """ + + mode = tool_task_mode or TASK_FORBIDDEN + + error: ErrorData | None = None + + if mode == TASK_REQUIRED and not self.is_task: + error = ErrorData( + code=METHOD_NOT_FOUND, + message="This tool requires task-augmented invocation", + ) + elif mode == TASK_FORBIDDEN and self.is_task: + error = ErrorData( + code=METHOD_NOT_FOUND, + message="This tool does not support task-augmented invocation", + ) + + if error is not None and raise_error: + raise McpError(error) + + return error + + def validate_for_tool( + self, + tool: Tool, + *, + raise_error: bool = True, + ) -> ErrorData | None: + """ + Validate that the request is compatible with the given tool. + + Convenience wrapper around validate_task_mode that extracts the mode from a Tool. + + Args: + tool: The Tool definition + raise_error: If True, raises McpError on validation failure. + + Returns: + None if valid, ErrorData if invalid and raise_error=False + """ + mode = tool.execution.taskSupport if tool.execution else None + return self.validate_task_mode(mode, raise_error=raise_error) + + def can_use_tool(self, tool_task_mode: TaskExecutionMode | None) -> bool: + """ + Check if this client can use a tool with the given task mode. + + Useful for filtering tool lists or providing warnings. + Returns False if tool requires "required" but client doesn't support tasks. + + Args: + tool_task_mode: The tool's execution.taskSupport value + + Returns: + True if the client can use this tool, False otherwise + """ + mode = tool_task_mode or TASK_FORBIDDEN + if mode == TASK_REQUIRED and not self.client_supports_tasks: + return False + return True + + async def run_task( + self, + work: Callable[[ServerTaskContext], Awaitable[Result]], + *, + task_id: str | None = None, + model_immediate_response: str | None = None, + ) -> CreateTaskResult: + """ + Create a task, spawn background work, and return CreateTaskResult immediately. + + This is the recommended way to handle task-augmented tool calls. It: + 1. Creates a task in the store + 2. Spawns the work function in a background task + 3. Returns CreateTaskResult immediately + + The work function receives a ServerTaskContext with: + - elicit() for sending elicitation requests + - create_message() for sampling requests + - update_status() for progress updates + - complete()/fail() for finishing the task + + When work() returns a Result, the task is auto-completed with that result. + If work() raises an exception, the task is auto-failed. + + Args: + work: Async function that does the actual work + task_id: Optional task ID (generated if not provided) + model_immediate_response: Optional string to include in _meta as + io.modelcontextprotocol/model-immediate-response + + Returns: + CreateTaskResult to return to the client + + Raises: + RuntimeError: If task support is not enabled or task_metadata is missing + + Example: + @server.call_tool() + async def handle_tool(name: str, args: dict): + ctx = server.request_context + + async def work(task: ServerTaskContext) -> CallToolResult: + result = await task.elicit( + message="Are you sure?", + requestedSchema={"type": "object", ...} + ) + confirmed = result.content.get("confirm", False) + return CallToolResult(content=[TextContent(text="Done" if confirmed else "Cancelled")]) + + return await ctx.experimental.run_task(work) + + WARNING: This API is experimental and may change without notice. + """ + if self._task_support is None: + raise RuntimeError("Task support not enabled. Call server.experimental.enable_tasks() first.") + if self._session is None: + raise RuntimeError("Session not available.") + if self.task_metadata is None: + raise RuntimeError( + "Request is not task-augmented (no task field in params). " + "The client must send a task-augmented request." + ) + + support = self._task_support + # Access task_group via TaskSupport - raises if not in run() context + task_group = support.task_group + + task = await support.store.create_task(self.task_metadata, task_id) + + task_ctx = ServerTaskContext( + task=task, + store=support.store, + session=self._session, + queue=support.queue, + handler=support.handler, + ) + + async def execute() -> None: + try: + result = await work(task_ctx) + if not is_terminal(task_ctx.task.status): + await task_ctx.complete(result) + except Exception as e: + if not is_terminal(task_ctx.task.status): + await task_ctx.fail(str(e)) + + task_group.start_soon(execute) + + meta: dict[str, Any] | None = None + if model_immediate_response is not None: + meta = {MODEL_IMMEDIATE_RESPONSE_KEY: model_immediate_response} + + return CreateTaskResult(task=task, **{"_meta": meta} if meta else {}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py new file mode 100644 index 0000000..4842da5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py @@ -0,0 +1,220 @@ +""" +Experimental server session features for server→client task operations. + +This module provides the server-side equivalent of ExperimentalClientFeatures, +allowing the server to send task-augmented requests to the client and poll for results. + +WARNING: These APIs are experimental and may change without notice. +""" + +from collections.abc import AsyncIterator +from typing import TYPE_CHECKING, Any, TypeVar + +import mcp.types as types +from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages +from mcp.shared.experimental.tasks.capabilities import ( + require_task_augmented_elicitation, + require_task_augmented_sampling, +) +from mcp.shared.experimental.tasks.polling import poll_until_terminal + +if TYPE_CHECKING: + from mcp.server.session import ServerSession + +ResultT = TypeVar("ResultT", bound=types.Result) + + +class ExperimentalServerSessionFeatures: + """ + Experimental server session features for server→client task operations. + + This provides the server-side equivalent of ExperimentalClientFeatures, + allowing the server to send task-augmented requests to the client and + poll for results. + + WARNING: These APIs are experimental and may change without notice. + + Access via session.experimental: + result = await session.experimental.elicit_as_task(...) + """ + + def __init__(self, session: "ServerSession") -> None: + self._session = session + + async def get_task(self, task_id: str) -> types.GetTaskResult: + """ + Send tasks/get to the client to get task status. + + Args: + task_id: The task identifier + + Returns: + GetTaskResult containing the task status + """ + return await self._session.send_request( + types.ServerRequest(types.GetTaskRequest(params=types.GetTaskRequestParams(taskId=task_id))), + types.GetTaskResult, + ) + + async def get_task_result( + self, + task_id: str, + result_type: type[ResultT], + ) -> ResultT: + """ + Send tasks/result to the client to retrieve the final result. + + Args: + task_id: The task identifier + result_type: The expected result type + + Returns: + The task result, validated against result_type + """ + return await self._session.send_request( + types.ServerRequest(types.GetTaskPayloadRequest(params=types.GetTaskPayloadRequestParams(taskId=task_id))), + result_type, + ) + + async def poll_task(self, task_id: str) -> AsyncIterator[types.GetTaskResult]: + """ + Poll a client task until it reaches terminal status. + + Yields GetTaskResult for each poll, allowing the caller to react to + status changes. Exits when task reaches a terminal status. + + Respects the pollInterval hint from the client. + + Args: + task_id: The task identifier + + Yields: + GetTaskResult for each poll + """ + async for status in poll_until_terminal(self.get_task, task_id): + yield status + + async def elicit_as_task( + self, + message: str, + requestedSchema: types.ElicitRequestedSchema, + *, + ttl: int = 60000, + ) -> types.ElicitResult: + """ + Send a task-augmented elicitation to the client and poll until complete. + + The client will create a local task, process the elicitation asynchronously, + and return the result when ready. This method handles the full flow: + 1. Send elicitation with task field + 2. Receive CreateTaskResult from client + 3. Poll client's task until terminal + 4. Retrieve and return the final ElicitResult + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response + ttl: Task time-to-live in milliseconds + + Returns: + The client's elicitation response + + Raises: + McpError: If client doesn't support task-augmented elicitation + """ + client_caps = self._session.client_params.capabilities if self._session.client_params else None + require_task_augmented_elicitation(client_caps) + + create_result = await self._session.send_request( + types.ServerRequest( + types.ElicitRequest( + params=types.ElicitRequestFormParams( + message=message, + requestedSchema=requestedSchema, + task=types.TaskMetadata(ttl=ttl), + ) + ) + ), + types.CreateTaskResult, + ) + + task_id = create_result.task.taskId + + async for _ in self.poll_task(task_id): + pass + + return await self.get_task_result(task_id, types.ElicitResult) + + async def create_message_as_task( + self, + messages: list[types.SamplingMessage], + *, + max_tokens: int, + ttl: int = 60000, + system_prompt: str | None = None, + include_context: types.IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: types.ModelPreferences | None = None, + tools: list[types.Tool] | None = None, + tool_choice: types.ToolChoice | None = None, + ) -> types.CreateMessageResult: + """ + Send a task-augmented sampling request and poll until complete. + + The client will create a local task, process the sampling request + asynchronously, and return the result when ready. + + Args: + messages: The conversation messages for sampling + max_tokens: Maximum tokens in the response + ttl: Task time-to-live in milliseconds + system_prompt: Optional system prompt + include_context: Context inclusion strategy + temperature: Sampling temperature + stop_sequences: Stop sequences + metadata: Additional metadata + model_preferences: Model selection preferences + tools: Optional list of tools the LLM can use during sampling + tool_choice: Optional control over tool usage behavior + + Returns: + The sampling result from the client + + Raises: + McpError: If client doesn't support task-augmented sampling or tools + ValueError: If tool_use or tool_result message structure is invalid + """ + client_caps = self._session.client_params.capabilities if self._session.client_params else None + require_task_augmented_sampling(client_caps) + validate_sampling_tools(client_caps, tools, tool_choice) + validate_tool_use_result_messages(messages) + + create_result = await self._session.send_request( + types.ServerRequest( + types.CreateMessageRequest( + params=types.CreateMessageRequestParams( + messages=messages, + maxTokens=max_tokens, + systemPrompt=system_prompt, + includeContext=include_context, + temperature=temperature, + stopSequences=stop_sequences, + metadata=metadata, + modelPreferences=model_preferences, + tools=tools, + toolChoice=tool_choice, + task=types.TaskMetadata(ttl=ttl), + ) + ) + ), + types.CreateTaskResult, + ) + + task_id = create_result.task.taskId + + async for _ in self.poll_task(task_id): + pass + + return await self.get_task_result(task_id, types.CreateMessageResult) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py new file mode 100644 index 0000000..e6e14fc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py @@ -0,0 +1,612 @@ +""" +ServerTaskContext - Server-integrated task context with elicitation and sampling. + +This wraps the pure TaskContext and adds server-specific functionality: +- Elicitation (task.elicit()) +- Sampling (task.create_message()) +- Status notifications +""" + +from typing import Any + +import anyio + +from mcp.server.experimental.task_result_handler import TaskResultHandler +from mcp.server.session import ServerSession +from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages +from mcp.shared.exceptions import McpError +from mcp.shared.experimental.tasks.capabilities import ( + require_task_augmented_elicitation, + require_task_augmented_sampling, +) +from mcp.shared.experimental.tasks.context import TaskContext +from mcp.shared.experimental.tasks.message_queue import QueuedMessage, TaskMessageQueue +from mcp.shared.experimental.tasks.resolver import Resolver +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.types import ( + INVALID_REQUEST, + TASK_STATUS_INPUT_REQUIRED, + TASK_STATUS_WORKING, + ClientCapabilities, + CreateMessageResult, + CreateTaskResult, + ElicitationCapability, + ElicitRequestedSchema, + ElicitResult, + ErrorData, + IncludeContext, + ModelPreferences, + RequestId, + Result, + SamplingCapability, + SamplingMessage, + ServerNotification, + Task, + TaskMetadata, + TaskStatusNotification, + TaskStatusNotificationParams, + Tool, + ToolChoice, +) + + +class ServerTaskContext: + """ + Server-integrated task context with elicitation and sampling. + + This wraps a pure TaskContext and adds server-specific functionality: + - elicit() for sending elicitation requests to the client + - create_message() for sampling requests + - Status notifications via the session + + Example: + async def my_task_work(task: ServerTaskContext) -> CallToolResult: + await task.update_status("Starting...") + + result = await task.elicit( + message="Continue?", + requestedSchema={"type": "object", "properties": {"ok": {"type": "boolean"}}} + ) + + if result.content.get("ok"): + return CallToolResult(content=[TextContent(text="Done!")]) + else: + return CallToolResult(content=[TextContent(text="Cancelled")]) + """ + + def __init__( + self, + *, + task: Task, + store: TaskStore, + session: ServerSession, + queue: TaskMessageQueue, + handler: TaskResultHandler | None = None, + ): + """ + Create a ServerTaskContext. + + Args: + task: The Task object + store: The task store + session: The server session + queue: The message queue for elicitation/sampling + handler: The result handler for response routing (required for elicit/create_message) + """ + self._ctx = TaskContext(task=task, store=store) + self._session = session + self._queue = queue + self._handler = handler + self._store = store + + # Delegate pure properties to inner context + + @property + def task_id(self) -> str: + """The task identifier.""" + return self._ctx.task_id + + @property + def task(self) -> Task: + """The current task state.""" + return self._ctx.task + + @property + def is_cancelled(self) -> bool: + """Whether cancellation has been requested.""" + return self._ctx.is_cancelled + + def request_cancellation(self) -> None: + """Request cancellation of this task.""" + self._ctx.request_cancellation() + + # Enhanced methods with notifications + + async def update_status(self, message: str, *, notify: bool = True) -> None: + """ + Update the task's status message. + + Args: + message: The new status message + notify: Whether to send a notification to the client + """ + await self._ctx.update_status(message) + if notify: + await self._send_notification() + + async def complete(self, result: Result, *, notify: bool = True) -> None: + """ + Mark the task as completed with the given result. + + Args: + result: The task result + notify: Whether to send a notification to the client + """ + await self._ctx.complete(result) + if notify: + await self._send_notification() + + async def fail(self, error: str, *, notify: bool = True) -> None: + """ + Mark the task as failed with an error message. + + Args: + error: The error message + notify: Whether to send a notification to the client + """ + await self._ctx.fail(error) + if notify: + await self._send_notification() + + async def _send_notification(self) -> None: + """Send a task status notification to the client.""" + task = self._ctx.task + await self._session.send_notification( + ServerNotification( + TaskStatusNotification( + params=TaskStatusNotificationParams( + taskId=task.taskId, + status=task.status, + statusMessage=task.statusMessage, + createdAt=task.createdAt, + lastUpdatedAt=task.lastUpdatedAt, + ttl=task.ttl, + pollInterval=task.pollInterval, + ) + ) + ) + ) + + # Server-specific methods: elicitation and sampling + + def _check_elicitation_capability(self) -> None: + """Check if the client supports elicitation.""" + if not self._session.check_client_capability(ClientCapabilities(elicitation=ElicitationCapability())): + raise McpError( + ErrorData( + code=INVALID_REQUEST, + message="Client does not support elicitation capability", + ) + ) + + def _check_sampling_capability(self) -> None: + """Check if the client supports sampling.""" + if not self._session.check_client_capability(ClientCapabilities(sampling=SamplingCapability())): + raise McpError( + ErrorData( + code=INVALID_REQUEST, + message="Client does not support sampling capability", + ) + ) + + async def elicit( + self, + message: str, + requestedSchema: ElicitRequestedSchema, + ) -> ElicitResult: + """ + Send an elicitation request via the task message queue. + + This method: + 1. Checks client capability + 2. Updates task status to "input_required" + 3. Queues the elicitation request + 4. Waits for the response (delivered via tasks/result round-trip) + 5. Updates task status back to "working" + 6. Returns the result + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response structure + + Returns: + The client's response + + Raises: + McpError: If client doesn't support elicitation capability + """ + self._check_elicitation_capability() + + if self._handler is None: + raise RuntimeError("handler is required for elicit(). Pass handler= to ServerTaskContext.") + + # Update status to input_required + await self._store.update_task(self.task_id, status=TASK_STATUS_INPUT_REQUIRED) + + # Build the request using session's helper + request = self._session._build_elicit_form_request( # pyright: ignore[reportPrivateUsage] + message=message, + requestedSchema=requestedSchema, + related_task_id=self.task_id, + ) + request_id: RequestId = request.id + + resolver: Resolver[dict[str, Any]] = Resolver() + self._handler._pending_requests[request_id] = resolver # pyright: ignore[reportPrivateUsage] + + queued = QueuedMessage( + type="request", + message=request, + resolver=resolver, + original_request_id=request_id, + ) + await self._queue.enqueue(self.task_id, queued) + + try: + # Wait for response (routed back via TaskResultHandler) + response_data = await resolver.wait() + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + return ElicitResult.model_validate(response_data) + except anyio.get_cancelled_exc_class(): # pragma: no cover + # Coverage can't track async exception handlers reliably. + # This path is tested in test_elicit_restores_status_on_cancellation + # which verifies status is restored to "working" after cancellation. + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + raise + + async def elicit_url( + self, + message: str, + url: str, + elicitation_id: str, + ) -> ElicitResult: + """ + Send a URL mode elicitation request via the task message queue. + + This directs the user to an external URL for out-of-band interactions + like OAuth flows, credential collection, or payment processing. + + This method: + 1. Checks client capability + 2. Updates task status to "input_required" + 3. Queues the elicitation request + 4. Waits for the response (delivered via tasks/result round-trip) + 5. Updates task status back to "working" + 6. Returns the result + + Args: + message: Human-readable explanation of why the interaction is needed + url: The URL the user should navigate to + elicitation_id: Unique identifier for tracking this elicitation + + Returns: + The client's response indicating acceptance, decline, or cancellation + + Raises: + McpError: If client doesn't support elicitation capability + RuntimeError: If handler is not configured + """ + self._check_elicitation_capability() + + if self._handler is None: + raise RuntimeError("handler is required for elicit_url(). Pass handler= to ServerTaskContext.") + + # Update status to input_required + await self._store.update_task(self.task_id, status=TASK_STATUS_INPUT_REQUIRED) + + # Build the request using session's helper + request = self._session._build_elicit_url_request( # pyright: ignore[reportPrivateUsage] + message=message, + url=url, + elicitation_id=elicitation_id, + related_task_id=self.task_id, + ) + request_id: RequestId = request.id + + resolver: Resolver[dict[str, Any]] = Resolver() + self._handler._pending_requests[request_id] = resolver # pyright: ignore[reportPrivateUsage] + + queued = QueuedMessage( + type="request", + message=request, + resolver=resolver, + original_request_id=request_id, + ) + await self._queue.enqueue(self.task_id, queued) + + try: + # Wait for response (routed back via TaskResultHandler) + response_data = await resolver.wait() + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + return ElicitResult.model_validate(response_data) + except anyio.get_cancelled_exc_class(): # pragma: no cover + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + raise + + async def create_message( + self, + messages: list[SamplingMessage], + *, + max_tokens: int, + system_prompt: str | None = None, + include_context: IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: ModelPreferences | None = None, + tools: list[Tool] | None = None, + tool_choice: ToolChoice | None = None, + ) -> CreateMessageResult: + """ + Send a sampling request via the task message queue. + + This method: + 1. Checks client capability + 2. Updates task status to "input_required" + 3. Queues the sampling request + 4. Waits for the response (delivered via tasks/result round-trip) + 5. Updates task status back to "working" + 6. Returns the result + + Args: + messages: The conversation messages for sampling + max_tokens: Maximum tokens in the response + system_prompt: Optional system prompt + include_context: Context inclusion strategy + temperature: Sampling temperature + stop_sequences: Stop sequences + metadata: Additional metadata + model_preferences: Model selection preferences + tools: Optional list of tools the LLM can use during sampling + tool_choice: Optional control over tool usage behavior + + Returns: + The sampling result from the client + + Raises: + McpError: If client doesn't support sampling capability or tools + ValueError: If tool_use or tool_result message structure is invalid + """ + self._check_sampling_capability() + client_caps = self._session.client_params.capabilities if self._session.client_params else None + validate_sampling_tools(client_caps, tools, tool_choice) + validate_tool_use_result_messages(messages) + + if self._handler is None: + raise RuntimeError("handler is required for create_message(). Pass handler= to ServerTaskContext.") + + # Update status to input_required + await self._store.update_task(self.task_id, status=TASK_STATUS_INPUT_REQUIRED) + + # Build the request using session's helper + request = self._session._build_create_message_request( # pyright: ignore[reportPrivateUsage] + messages=messages, + max_tokens=max_tokens, + system_prompt=system_prompt, + include_context=include_context, + temperature=temperature, + stop_sequences=stop_sequences, + metadata=metadata, + model_preferences=model_preferences, + tools=tools, + tool_choice=tool_choice, + related_task_id=self.task_id, + ) + request_id: RequestId = request.id + + resolver: Resolver[dict[str, Any]] = Resolver() + self._handler._pending_requests[request_id] = resolver # pyright: ignore[reportPrivateUsage] + + queued = QueuedMessage( + type="request", + message=request, + resolver=resolver, + original_request_id=request_id, + ) + await self._queue.enqueue(self.task_id, queued) + + try: + # Wait for response (routed back via TaskResultHandler) + response_data = await resolver.wait() + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + return CreateMessageResult.model_validate(response_data) + except anyio.get_cancelled_exc_class(): # pragma: no cover + # Coverage can't track async exception handlers reliably. + # This path is tested in test_create_message_restores_status_on_cancellation + # which verifies status is restored to "working" after cancellation. + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + raise + + async def elicit_as_task( + self, + message: str, + requestedSchema: ElicitRequestedSchema, + *, + ttl: int = 60000, + ) -> ElicitResult: + """ + Send a task-augmented elicitation via the queue, then poll client. + + This is for use inside a task-augmented tool call when you want the client + to handle the elicitation as its own task. The elicitation request is queued + and delivered when the client calls tasks/result. After the client responds + with CreateTaskResult, we poll the client's task until complete. + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response structure + ttl: Task time-to-live in milliseconds for the client's task + + Returns: + The client's elicitation response + + Raises: + McpError: If client doesn't support task-augmented elicitation + RuntimeError: If handler is not configured + """ + client_caps = self._session.client_params.capabilities if self._session.client_params else None + require_task_augmented_elicitation(client_caps) + + if self._handler is None: + raise RuntimeError("handler is required for elicit_as_task()") + + # Update status to input_required + await self._store.update_task(self.task_id, status=TASK_STATUS_INPUT_REQUIRED) + + request = self._session._build_elicit_form_request( # pyright: ignore[reportPrivateUsage] + message=message, + requestedSchema=requestedSchema, + related_task_id=self.task_id, + task=TaskMetadata(ttl=ttl), + ) + request_id: RequestId = request.id + + resolver: Resolver[dict[str, Any]] = Resolver() + self._handler._pending_requests[request_id] = resolver # pyright: ignore[reportPrivateUsage] + + queued = QueuedMessage( + type="request", + message=request, + resolver=resolver, + original_request_id=request_id, + ) + await self._queue.enqueue(self.task_id, queued) + + try: + # Wait for initial response (CreateTaskResult from client) + response_data = await resolver.wait() + create_result = CreateTaskResult.model_validate(response_data) + client_task_id = create_result.task.taskId + + # Poll the client's task using session.experimental + async for _ in self._session.experimental.poll_task(client_task_id): + pass + + # Get final result from client + result = await self._session.experimental.get_task_result( + client_task_id, + ElicitResult, + ) + + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + return result + + except anyio.get_cancelled_exc_class(): # pragma: no cover + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + raise + + async def create_message_as_task( + self, + messages: list[SamplingMessage], + *, + max_tokens: int, + ttl: int = 60000, + system_prompt: str | None = None, + include_context: IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: ModelPreferences | None = None, + tools: list[Tool] | None = None, + tool_choice: ToolChoice | None = None, + ) -> CreateMessageResult: + """ + Send a task-augmented sampling request via the queue, then poll client. + + This is for use inside a task-augmented tool call when you want the client + to handle the sampling as its own task. The request is queued and delivered + when the client calls tasks/result. After the client responds with + CreateTaskResult, we poll the client's task until complete. + + Args: + messages: The conversation messages for sampling + max_tokens: Maximum tokens in the response + ttl: Task time-to-live in milliseconds for the client's task + system_prompt: Optional system prompt + include_context: Context inclusion strategy + temperature: Sampling temperature + stop_sequences: Stop sequences + metadata: Additional metadata + model_preferences: Model selection preferences + tools: Optional list of tools the LLM can use during sampling + tool_choice: Optional control over tool usage behavior + + Returns: + The sampling result from the client + + Raises: + McpError: If client doesn't support task-augmented sampling or tools + ValueError: If tool_use or tool_result message structure is invalid + RuntimeError: If handler is not configured + """ + client_caps = self._session.client_params.capabilities if self._session.client_params else None + require_task_augmented_sampling(client_caps) + validate_sampling_tools(client_caps, tools, tool_choice) + validate_tool_use_result_messages(messages) + + if self._handler is None: + raise RuntimeError("handler is required for create_message_as_task()") + + # Update status to input_required + await self._store.update_task(self.task_id, status=TASK_STATUS_INPUT_REQUIRED) + + # Build request WITH task field for task-augmented sampling + request = self._session._build_create_message_request( # pyright: ignore[reportPrivateUsage] + messages=messages, + max_tokens=max_tokens, + system_prompt=system_prompt, + include_context=include_context, + temperature=temperature, + stop_sequences=stop_sequences, + metadata=metadata, + model_preferences=model_preferences, + tools=tools, + tool_choice=tool_choice, + related_task_id=self.task_id, + task=TaskMetadata(ttl=ttl), + ) + request_id: RequestId = request.id + + resolver: Resolver[dict[str, Any]] = Resolver() + self._handler._pending_requests[request_id] = resolver # pyright: ignore[reportPrivateUsage] + + queued = QueuedMessage( + type="request", + message=request, + resolver=resolver, + original_request_id=request_id, + ) + await self._queue.enqueue(self.task_id, queued) + + try: + # Wait for initial response (CreateTaskResult from client) + response_data = await resolver.wait() + create_result = CreateTaskResult.model_validate(response_data) + client_task_id = create_result.task.taskId + + # Poll the client's task using session.experimental + async for _ in self._session.experimental.poll_task(client_task_id): + pass + + # Get final result from client + result = await self._session.experimental.get_task_result( + client_task_id, + CreateMessageResult, + ) + + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + return result + + except anyio.get_cancelled_exc_class(): # pragma: no cover + await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING) + raise diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py new file mode 100644 index 0000000..0b86921 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py @@ -0,0 +1,235 @@ +""" +TaskResultHandler - Integrated handler for tasks/result endpoint. + +This implements the dequeue-send-wait pattern from the MCP Tasks spec: +1. Dequeue all pending messages for the task +2. Send them to the client via transport with relatedRequestId routing +3. Wait if task is not in terminal state +4. Return final result when task completes + +This is the core of the task message queue pattern. +""" + +import logging +from typing import Any + +import anyio + +from mcp.server.session import ServerSession +from mcp.shared.exceptions import McpError +from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY, is_terminal +from mcp.shared.experimental.tasks.message_queue import TaskMessageQueue +from mcp.shared.experimental.tasks.resolver import Resolver +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.shared.message import ServerMessageMetadata, SessionMessage +from mcp.types import ( + INVALID_PARAMS, + ErrorData, + GetTaskPayloadRequest, + GetTaskPayloadResult, + JSONRPCMessage, + RelatedTaskMetadata, + RequestId, +) + +logger = logging.getLogger(__name__) + + +class TaskResultHandler: + """ + Handler for tasks/result that implements the message queue pattern. + + This handler: + 1. Dequeues pending messages (elicitations, notifications) for the task + 2. Sends them to the client via the response stream + 3. Waits for responses and resolves them back to callers + 4. Blocks until task reaches terminal state + 5. Returns the final result + + Usage: + # Create handler with store and queue + handler = TaskResultHandler(task_store, message_queue) + + # Register it with the server + @server.experimental.get_task_result() + async def handle_task_result(req: GetTaskPayloadRequest) -> GetTaskPayloadResult: + ctx = server.request_context + return await handler.handle(req, ctx.session, ctx.request_id) + + # Or use the convenience method + handler.register(server) + """ + + def __init__( + self, + store: TaskStore, + queue: TaskMessageQueue, + ): + self._store = store + self._queue = queue + # Map from internal request ID to resolver for routing responses + self._pending_requests: dict[RequestId, Resolver[dict[str, Any]]] = {} + + async def send_message( + self, + session: ServerSession, + message: SessionMessage, + ) -> None: + """ + Send a message via the session. + + This is a helper for delivering queued task messages. + """ + await session.send_message(message) + + async def handle( + self, + request: GetTaskPayloadRequest, + session: ServerSession, + request_id: RequestId, + ) -> GetTaskPayloadResult: + """ + Handle a tasks/result request. + + This implements the dequeue-send-wait loop: + 1. Dequeue all pending messages + 2. Send each via transport with relatedRequestId = this request's ID + 3. If task not terminal, wait for status change + 4. Loop until task is terminal + 5. Return final result + + Args: + request: The GetTaskPayloadRequest + session: The server session for sending messages + request_id: The request ID for relatedRequestId routing + + Returns: + GetTaskPayloadResult with the task's final payload + """ + task_id = request.params.taskId + + while True: + task = await self._store.get_task(task_id) + if task is None: + raise McpError( + ErrorData( + code=INVALID_PARAMS, + message=f"Task not found: {task_id}", + ) + ) + + await self._deliver_queued_messages(task_id, session, request_id) + + # If task is terminal, return result + if is_terminal(task.status): + result = await self._store.get_result(task_id) + # GetTaskPayloadResult is a Result with extra="allow" + # The stored result contains the actual payload data + # Per spec: tasks/result MUST include _meta with related-task metadata + related_task = RelatedTaskMetadata(taskId=task_id) + related_task_meta: dict[str, Any] = {RELATED_TASK_METADATA_KEY: related_task.model_dump(by_alias=True)} + if result is not None: + result_data = result.model_dump(by_alias=True) + existing_meta: dict[str, Any] = result_data.get("_meta") or {} + result_data["_meta"] = {**existing_meta, **related_task_meta} + return GetTaskPayloadResult.model_validate(result_data) + return GetTaskPayloadResult.model_validate({"_meta": related_task_meta}) + + # Wait for task update (status change or new messages) + await self._wait_for_task_update(task_id) + + async def _deliver_queued_messages( + self, + task_id: str, + session: ServerSession, + request_id: RequestId, + ) -> None: + """ + Dequeue and send all pending messages for a task. + + Each message is sent via the session's write stream with + relatedRequestId set so responses route back to this stream. + """ + while True: + message = await self._queue.dequeue(task_id) + if message is None: + break + + # If this is a request (not notification), wait for response + if message.type == "request" and message.resolver is not None: + # Store the resolver so we can route the response back + original_id = message.original_request_id + if original_id is not None: + self._pending_requests[original_id] = message.resolver + + logger.debug("Delivering queued message for task %s: %s", task_id, message.type) + + # Send the message with relatedRequestId for routing + session_message = SessionMessage( + message=JSONRPCMessage(message.message), + metadata=ServerMessageMetadata(related_request_id=request_id), + ) + await self.send_message(session, session_message) + + async def _wait_for_task_update(self, task_id: str) -> None: + """ + Wait for task to be updated (status change or new message). + + Races between store update and queue message - first one wins. + """ + async with anyio.create_task_group() as tg: + + async def wait_for_store() -> None: + try: + await self._store.wait_for_update(task_id) + except Exception: + pass + finally: + tg.cancel_scope.cancel() + + async def wait_for_queue() -> None: + try: + await self._queue.wait_for_message(task_id) + except Exception: + pass + finally: + tg.cancel_scope.cancel() + + tg.start_soon(wait_for_store) + tg.start_soon(wait_for_queue) + + def route_response(self, request_id: RequestId, response: dict[str, Any]) -> bool: + """ + Route a response back to the waiting resolver. + + This is called when a response arrives for a queued request. + + Args: + request_id: The request ID from the response + response: The response data + + Returns: + True if response was routed, False if no pending request + """ + resolver = self._pending_requests.pop(request_id, None) + if resolver is not None and not resolver.done(): + resolver.set_result(response) + return True + return False + + def route_error(self, request_id: RequestId, error: ErrorData) -> bool: + """ + Route an error back to the waiting resolver. + + Args: + request_id: The request ID from the error response + error: The error data + + Returns: + True if error was routed, False if no pending request + """ + resolver = self._pending_requests.pop(request_id, None) + if resolver is not None and not resolver.done(): + resolver.set_exception(McpError(error)) + return True + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py new file mode 100644 index 0000000..dbb2ed6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py @@ -0,0 +1,115 @@ +""" +TaskSupport - Configuration for experimental task support. + +This module provides the TaskSupport class which encapsulates all the +infrastructure needed for task-augmented requests: store, queue, and handler. +""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from dataclasses import dataclass, field + +import anyio +from anyio.abc import TaskGroup + +from mcp.server.experimental.task_result_handler import TaskResultHandler +from mcp.server.session import ServerSession +from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore +from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue, TaskMessageQueue +from mcp.shared.experimental.tasks.store import TaskStore + + +@dataclass +class TaskSupport: + """ + Configuration for experimental task support. + + Encapsulates the task store, message queue, result handler, and task group + for spawning background work. + + When enabled on a server, this automatically: + - Configures response routing for each session + - Provides default handlers for task operations + - Manages a task group for background task execution + + Example: + # Simple in-memory setup + server.experimental.enable_tasks() + + # Custom store/queue for distributed systems + server.experimental.enable_tasks( + store=RedisTaskStore(redis_url), + queue=RedisTaskMessageQueue(redis_url), + ) + """ + + store: TaskStore + queue: TaskMessageQueue + handler: TaskResultHandler = field(init=False) + _task_group: TaskGroup | None = field(init=False, default=None) + + def __post_init__(self) -> None: + """Create the result handler from store and queue.""" + self.handler = TaskResultHandler(self.store, self.queue) + + @property + def task_group(self) -> TaskGroup: + """Get the task group for spawning background work. + + Raises: + RuntimeError: If not within a run() context + """ + if self._task_group is None: + raise RuntimeError("TaskSupport not running. Ensure Server.run() is active.") + return self._task_group + + @asynccontextmanager + async def run(self) -> AsyncIterator[None]: + """ + Run the task support lifecycle. + + This creates a task group for spawning background task work. + Called automatically by Server.run(). + + Usage: + async with task_support.run(): + # Task group is now available + ... + """ + async with anyio.create_task_group() as tg: + self._task_group = tg + try: + yield + finally: + self._task_group = None + + def configure_session(self, session: ServerSession) -> None: + """ + Configure a session for task support. + + This registers the result handler as a response router so that + responses to queued requests (elicitation, sampling) are routed + back to the waiting resolvers. + + Called automatically by Server.run() for each new session. + + Args: + session: The session to configure + """ + session.add_response_router(self.handler) + + @classmethod + def in_memory(cls) -> "TaskSupport": + """ + Create in-memory task support. + + Suitable for development, testing, and single-process servers. + For distributed systems, provide custom store and queue implementations. + + Returns: + TaskSupport configured with in-memory store and queue + """ + return cls( + store=InMemoryTaskStore(), + queue=InMemoryTaskMessageQueue(), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py new file mode 100644 index 0000000..a89902c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py @@ -0,0 +1,11 @@ +"""FastMCP - A more ergonomic interface for MCP servers.""" + +from importlib.metadata import version + +from mcp.types import Icon + +from .server import Context, FastMCP +from .utilities.types import Audio, Image + +__version__ = version("mcp") +__all__ = ["FastMCP", "Context", "Image", "Audio", "Icon"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py new file mode 100644 index 0000000..fb5bda1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py @@ -0,0 +1,21 @@ +"""Custom exceptions for FastMCP.""" + + +class FastMCPError(Exception): + """Base error for FastMCP.""" + + +class ValidationError(FastMCPError): + """Error in validating parameters or return values.""" + + +class ResourceError(FastMCPError): + """Error in resource operations.""" + + +class ToolError(FastMCPError): + """Error in tool operations.""" + + +class InvalidSignature(Exception): + """Invalid signature for use with FastMCP.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py new file mode 100644 index 0000000..7637269 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py @@ -0,0 +1,4 @@ +from .base import Prompt +from .manager import PromptManager + +__all__ = ["Prompt", "PromptManager"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py new file mode 100644 index 0000000..48c65b5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py @@ -0,0 +1,183 @@ +"""Base classes for FastMCP prompts.""" + +from __future__ import annotations + +import inspect +from collections.abc import Awaitable, Callable, Sequence +from typing import TYPE_CHECKING, Any, Literal + +import pydantic_core +from pydantic import BaseModel, Field, TypeAdapter, validate_call + +from mcp.server.fastmcp.utilities.context_injection import find_context_parameter, inject_context +from mcp.server.fastmcp.utilities.func_metadata import func_metadata +from mcp.types import ContentBlock, Icon, TextContent + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + from mcp.shared.context import LifespanContextT, RequestT + + +class Message(BaseModel): + """Base class for all prompt messages.""" + + role: Literal["user", "assistant"] + content: ContentBlock + + def __init__(self, content: str | ContentBlock, **kwargs: Any): + if isinstance(content, str): + content = TextContent(type="text", text=content) + super().__init__(content=content, **kwargs) + + +class UserMessage(Message): + """A message from the user.""" + + role: Literal["user", "assistant"] = "user" + + def __init__(self, content: str | ContentBlock, **kwargs: Any): + super().__init__(content=content, **kwargs) + + +class AssistantMessage(Message): + """A message from the assistant.""" + + role: Literal["user", "assistant"] = "assistant" + + def __init__(self, content: str | ContentBlock, **kwargs: Any): + super().__init__(content=content, **kwargs) + + +message_validator = TypeAdapter[UserMessage | AssistantMessage](UserMessage | AssistantMessage) + +SyncPromptResult = str | Message | dict[str, Any] | Sequence[str | Message | dict[str, Any]] +PromptResult = SyncPromptResult | Awaitable[SyncPromptResult] + + +class PromptArgument(BaseModel): + """An argument that can be passed to a prompt.""" + + name: str = Field(description="Name of the argument") + description: str | None = Field(None, description="Description of what the argument does") + required: bool = Field(default=False, description="Whether the argument is required") + + +class Prompt(BaseModel): + """A prompt template that can be rendered with parameters.""" + + name: str = Field(description="Name of the prompt") + title: str | None = Field(None, description="Human-readable title of the prompt") + description: str | None = Field(None, description="Description of what the prompt does") + arguments: list[PromptArgument] | None = Field(None, description="Arguments that can be passed to the prompt") + fn: Callable[..., PromptResult | Awaitable[PromptResult]] = Field(exclude=True) + icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this prompt") + context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context", exclude=True) + + @classmethod + def from_function( + cls, + fn: Callable[..., PromptResult | Awaitable[PromptResult]], + name: str | None = None, + title: str | None = None, + description: str | None = None, + icons: list[Icon] | None = None, + context_kwarg: str | None = None, + ) -> Prompt: + """Create a Prompt from a function. + + The function can return: + - A string (converted to a message) + - A Message object + - A dict (converted to a message) + - A sequence of any of the above + """ + func_name = name or fn.__name__ + + if func_name == "": # pragma: no cover + raise ValueError("You must provide a name for lambda functions") + + # Find context parameter if it exists + if context_kwarg is None: # pragma: no branch + context_kwarg = find_context_parameter(fn) + + # Get schema from func_metadata, excluding context parameter + func_arg_metadata = func_metadata( + fn, + skip_names=[context_kwarg] if context_kwarg is not None else [], + ) + parameters = func_arg_metadata.arg_model.model_json_schema() + + # Convert parameters to PromptArguments + arguments: list[PromptArgument] = [] + if "properties" in parameters: # pragma: no branch + for param_name, param in parameters["properties"].items(): + required = param_name in parameters.get("required", []) + arguments.append( + PromptArgument( + name=param_name, + description=param.get("description"), + required=required, + ) + ) + + # ensure the arguments are properly cast + fn = validate_call(fn) + + return cls( + name=func_name, + title=title, + description=description or fn.__doc__ or "", + arguments=arguments, + fn=fn, + icons=icons, + context_kwarg=context_kwarg, + ) + + async def render( + self, + arguments: dict[str, Any] | None = None, + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + ) -> list[Message]: + """Render the prompt with arguments.""" + # Validate required arguments + if self.arguments: + required = {arg.name for arg in self.arguments if arg.required} + provided = set(arguments or {}) + missing = required - provided + if missing: + raise ValueError(f"Missing required arguments: {missing}") + + try: + # Add context to arguments if needed + call_args = inject_context(self.fn, arguments or {}, context, self.context_kwarg) + + # Call function and check if result is a coroutine + result = self.fn(**call_args) + if inspect.iscoroutine(result): + result = await result + + # Validate messages + if not isinstance(result, list | tuple): + result = [result] + + # Convert result to messages + messages: list[Message] = [] + for msg in result: # type: ignore[reportUnknownVariableType] + try: + if isinstance(msg, Message): + messages.append(msg) + elif isinstance(msg, dict): + messages.append(message_validator.validate_python(msg)) + elif isinstance(msg, str): + content = TextContent(type="text", text=msg) + messages.append(UserMessage(content=content)) + else: # pragma: no cover + content = pydantic_core.to_json(msg, fallback=str, indent=2).decode() + messages.append(Message(role="user", content=content)) + except Exception: # pragma: no cover + raise ValueError(f"Could not convert prompt result to message: {msg}") + + return messages + except Exception as e: # pragma: no cover + raise ValueError(f"Error rendering prompt {self.name}: {e}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py new file mode 100644 index 0000000..6d032c7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py @@ -0,0 +1,60 @@ +"""Prompt management functionality.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from mcp.server.fastmcp.prompts.base import Message, Prompt +from mcp.server.fastmcp.utilities.logging import get_logger + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + from mcp.shared.context import LifespanContextT, RequestT + +logger = get_logger(__name__) + + +class PromptManager: + """Manages FastMCP prompts.""" + + def __init__(self, warn_on_duplicate_prompts: bool = True): + self._prompts: dict[str, Prompt] = {} + self.warn_on_duplicate_prompts = warn_on_duplicate_prompts + + def get_prompt(self, name: str) -> Prompt | None: + """Get prompt by name.""" + return self._prompts.get(name) + + def list_prompts(self) -> list[Prompt]: + """List all registered prompts.""" + return list(self._prompts.values()) + + def add_prompt( + self, + prompt: Prompt, + ) -> Prompt: + """Add a prompt to the manager.""" + + # Check for duplicates + existing = self._prompts.get(prompt.name) + if existing: + if self.warn_on_duplicate_prompts: + logger.warning(f"Prompt already exists: {prompt.name}") + return existing + + self._prompts[prompt.name] = prompt + return prompt + + async def render_prompt( + self, + name: str, + arguments: dict[str, Any] | None = None, + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + ) -> list[Message]: + """Render a prompt by name with arguments.""" + prompt = self.get_prompt(name) + if not prompt: + raise ValueError(f"Unknown prompt: {name}") + + return await prompt.render(arguments, context=context) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py new file mode 100644 index 0000000..b5805fb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py @@ -0,0 +1,23 @@ +from .base import Resource +from .resource_manager import ResourceManager +from .templates import ResourceTemplate +from .types import ( + BinaryResource, + DirectoryResource, + FileResource, + FunctionResource, + HttpResource, + TextResource, +) + +__all__ = [ + "Resource", + "TextResource", + "BinaryResource", + "FunctionResource", + "FileResource", + "HttpResource", + "DirectoryResource", + "ResourceTemplate", + "ResourceManager", +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py new file mode 100644 index 0000000..e34b97a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py @@ -0,0 +1,50 @@ +"""Base classes and interfaces for FastMCP resources.""" + +import abc +from typing import Annotated, Any + +from pydantic import ( + AnyUrl, + BaseModel, + ConfigDict, + Field, + UrlConstraints, + ValidationInfo, + field_validator, +) + +from mcp.types import Annotations, Icon + + +class Resource(BaseModel, abc.ABC): + """Base class for all resources.""" + + model_config = ConfigDict(validate_default=True) + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource") + name: str | None = Field(description="Name of the resource", default=None) + title: str | None = Field(description="Human-readable title of the resource", default=None) + description: str | None = Field(description="Description of the resource", default=None) + mime_type: str = Field( + default="text/plain", + description="MIME type of the resource content", + pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+(;\s*[a-zA-Z0-9\-_.]+=[a-zA-Z0-9\-_.]+)*$", + ) + icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this resource") + annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource") + meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this resource") + + @field_validator("name", mode="before") + @classmethod + def set_default_name(cls, name: str | None, info: ValidationInfo) -> str: + """Set default name from URI if not provided.""" + if name: + return name + if uri := info.data.get("uri"): + return str(uri) + raise ValueError("Either name or uri must be provided") + + @abc.abstractmethod + async def read(self) -> str | bytes: + """Read the resource content.""" + pass # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py new file mode 100644 index 0000000..20f67bb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py @@ -0,0 +1,115 @@ +"""Resource manager functionality.""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING, Any + +from pydantic import AnyUrl + +from mcp.server.fastmcp.resources.base import Resource +from mcp.server.fastmcp.resources.templates import ResourceTemplate +from mcp.server.fastmcp.utilities.logging import get_logger +from mcp.types import Annotations, Icon + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + from mcp.shared.context import LifespanContextT, RequestT + +logger = get_logger(__name__) + + +class ResourceManager: + """Manages FastMCP resources.""" + + def __init__(self, warn_on_duplicate_resources: bool = True): + self._resources: dict[str, Resource] = {} + self._templates: dict[str, ResourceTemplate] = {} + self.warn_on_duplicate_resources = warn_on_duplicate_resources + + def add_resource(self, resource: Resource) -> Resource: + """Add a resource to the manager. + + Args: + resource: A Resource instance to add + + Returns: + The added resource. If a resource with the same URI already exists, + returns the existing resource. + """ + logger.debug( + "Adding resource", + extra={ + "uri": resource.uri, + "type": type(resource).__name__, + "resource_name": resource.name, + }, + ) + existing = self._resources.get(str(resource.uri)) + if existing: + if self.warn_on_duplicate_resources: + logger.warning(f"Resource already exists: {resource.uri}") + return existing + self._resources[str(resource.uri)] = resource + return resource + + def add_template( + self, + fn: Callable[..., Any], + uri_template: str, + name: str | None = None, + title: str | None = None, + description: str | None = None, + mime_type: str | None = None, + icons: list[Icon] | None = None, + annotations: Annotations | None = None, + meta: dict[str, Any] | None = None, + ) -> ResourceTemplate: + """Add a template from a function.""" + template = ResourceTemplate.from_function( + fn, + uri_template=uri_template, + name=name, + title=title, + description=description, + mime_type=mime_type, + icons=icons, + annotations=annotations, + meta=meta, + ) + self._templates[template.uri_template] = template + return template + + async def get_resource( + self, + uri: AnyUrl | str, + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + ) -> Resource | None: + """Get resource by URI, checking concrete resources first, then templates.""" + uri_str = str(uri) + logger.debug("Getting resource", extra={"uri": uri_str}) + + # First check concrete resources + if resource := self._resources.get(uri_str): + return resource + + # Then check templates + for template in self._templates.values(): + if params := template.matches(uri_str): + try: + return await template.create_resource(uri_str, params, context=context) + except Exception as e: # pragma: no cover + raise ValueError(f"Error creating resource from template: {e}") + + raise ValueError(f"Unknown resource: {uri}") + + def list_resources(self) -> list[Resource]: + """List all registered resources.""" + logger.debug("Listing resources", extra={"count": len(self._resources)}) + return list(self._resources.values()) + + def list_templates(self) -> list[ResourceTemplate]: + """List all registered templates.""" + logger.debug("Listing templates", extra={"count": len(self._templates)}) + return list(self._templates.values()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py new file mode 100644 index 0000000..89a8ceb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py @@ -0,0 +1,122 @@ +"""Resource template functionality.""" + +from __future__ import annotations + +import inspect +import re +from collections.abc import Callable +from typing import TYPE_CHECKING, Any + +from pydantic import BaseModel, Field, validate_call + +from mcp.server.fastmcp.resources.types import FunctionResource, Resource +from mcp.server.fastmcp.utilities.context_injection import find_context_parameter, inject_context +from mcp.server.fastmcp.utilities.func_metadata import func_metadata +from mcp.types import Annotations, Icon + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + from mcp.shared.context import LifespanContextT, RequestT + + +class ResourceTemplate(BaseModel): + """A template for dynamically creating resources.""" + + uri_template: str = Field(description="URI template with parameters (e.g. weather://{city}/current)") + name: str = Field(description="Name of the resource") + title: str | None = Field(description="Human-readable title of the resource", default=None) + description: str | None = Field(description="Description of what the resource does") + mime_type: str = Field(default="text/plain", description="MIME type of the resource content") + icons: list[Icon] | None = Field(default=None, description="Optional list of icons for the resource template") + annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource template") + meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this resource template") + fn: Callable[..., Any] = Field(exclude=True) + parameters: dict[str, Any] = Field(description="JSON schema for function parameters") + context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context") + + @classmethod + def from_function( + cls, + fn: Callable[..., Any], + uri_template: str, + name: str | None = None, + title: str | None = None, + description: str | None = None, + mime_type: str | None = None, + icons: list[Icon] | None = None, + annotations: Annotations | None = None, + meta: dict[str, Any] | None = None, + context_kwarg: str | None = None, + ) -> ResourceTemplate: + """Create a template from a function.""" + func_name = name or fn.__name__ + if func_name == "": + raise ValueError("You must provide a name for lambda functions") # pragma: no cover + + # Find context parameter if it exists + if context_kwarg is None: # pragma: no branch + context_kwarg = find_context_parameter(fn) + + # Get schema from func_metadata, excluding context parameter + func_arg_metadata = func_metadata( + fn, + skip_names=[context_kwarg] if context_kwarg is not None else [], + ) + parameters = func_arg_metadata.arg_model.model_json_schema() + + # ensure the arguments are properly cast + fn = validate_call(fn) + + return cls( + uri_template=uri_template, + name=func_name, + title=title, + description=description or fn.__doc__ or "", + mime_type=mime_type or "text/plain", + icons=icons, + annotations=annotations, + meta=meta, + fn=fn, + parameters=parameters, + context_kwarg=context_kwarg, + ) + + def matches(self, uri: str) -> dict[str, Any] | None: + """Check if URI matches template and extract parameters.""" + # Convert template to regex pattern + pattern = self.uri_template.replace("{", "(?P<").replace("}", ">[^/]+)") + match = re.match(f"^{pattern}$", uri) + if match: + return match.groupdict() + return None + + async def create_resource( + self, + uri: str, + params: dict[str, Any], + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + ) -> Resource: + """Create a resource from the template with the given parameters.""" + try: + # Add context to params if needed + params = inject_context(self.fn, params, context, self.context_kwarg) + + # Call function and check if result is a coroutine + result = self.fn(**params) + if inspect.iscoroutine(result): + result = await result + + return FunctionResource( + uri=uri, # type: ignore + name=self.name, + title=self.title, + description=self.description, + mime_type=self.mime_type, + icons=self.icons, + annotations=self.annotations, + meta=self.meta, + fn=lambda: result, # Capture result in closure + ) + except Exception as e: + raise ValueError(f"Error creating resource from template: {e}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py new file mode 100644 index 0000000..5f72430 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py @@ -0,0 +1,203 @@ +"""Concrete resource implementations.""" + +import inspect +import json +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import anyio +import anyio.to_thread +import httpx +import pydantic +import pydantic_core +from pydantic import AnyUrl, Field, ValidationInfo, validate_call + +from mcp.server.fastmcp.resources.base import Resource +from mcp.types import Annotations, Icon + + +class TextResource(Resource): + """A resource that reads from a string.""" + + text: str = Field(description="Text content of the resource") + + async def read(self) -> str: + """Read the text content.""" + return self.text # pragma: no cover + + +class BinaryResource(Resource): + """A resource that reads from bytes.""" + + data: bytes = Field(description="Binary content of the resource") + + async def read(self) -> bytes: + """Read the binary content.""" + return self.data # pragma: no cover + + +class FunctionResource(Resource): + """A resource that defers data loading by wrapping a function. + + The function is only called when the resource is read, allowing for lazy loading + of potentially expensive data. This is particularly useful when listing resources, + as the function won't be called until the resource is actually accessed. + + The function can return: + - str for text content (default) + - bytes for binary content + - other types will be converted to JSON + """ + + fn: Callable[[], Any] = Field(exclude=True) + + async def read(self) -> str | bytes: + """Read the resource by calling the wrapped function.""" + try: + # Call the function first to see if it returns a coroutine + result = self.fn() + # If it's a coroutine, await it + if inspect.iscoroutine(result): + result = await result + + if isinstance(result, Resource): # pragma: no cover + return await result.read() + elif isinstance(result, bytes): + return result + elif isinstance(result, str): + return result + else: + return pydantic_core.to_json(result, fallback=str, indent=2).decode() + except Exception as e: + raise ValueError(f"Error reading resource {self.uri}: {e}") + + @classmethod + def from_function( + cls, + fn: Callable[..., Any], + uri: str, + name: str | None = None, + title: str | None = None, + description: str | None = None, + mime_type: str | None = None, + icons: list[Icon] | None = None, + annotations: Annotations | None = None, + meta: dict[str, Any] | None = None, + ) -> "FunctionResource": + """Create a FunctionResource from a function.""" + func_name = name or fn.__name__ + if func_name == "": # pragma: no cover + raise ValueError("You must provide a name for lambda functions") + + # ensure the arguments are properly cast + fn = validate_call(fn) + + return cls( + uri=AnyUrl(uri), + name=func_name, + title=title, + description=description or fn.__doc__ or "", + mime_type=mime_type or "text/plain", + fn=fn, + icons=icons, + annotations=annotations, + meta=meta, + ) + + +class FileResource(Resource): + """A resource that reads from a file. + + Set is_binary=True to read file as binary data instead of text. + """ + + path: Path = Field(description="Path to the file") + is_binary: bool = Field( + default=False, + description="Whether to read the file as binary data", + ) + mime_type: str = Field( + default="text/plain", + description="MIME type of the resource content", + ) + + @pydantic.field_validator("path") + @classmethod + def validate_absolute_path(cls, path: Path) -> Path: # pragma: no cover + """Ensure path is absolute.""" + if not path.is_absolute(): + raise ValueError("Path must be absolute") + return path + + @pydantic.field_validator("is_binary") + @classmethod + def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool: + """Set is_binary based on mime_type if not explicitly set.""" + if is_binary: + return True + mime_type = info.data.get("mime_type", "text/plain") + return not mime_type.startswith("text/") + + async def read(self) -> str | bytes: + """Read the file content.""" + try: + if self.is_binary: + return await anyio.to_thread.run_sync(self.path.read_bytes) + return await anyio.to_thread.run_sync(self.path.read_text) + except Exception as e: + raise ValueError(f"Error reading file {self.path}: {e}") + + +class HttpResource(Resource): + """A resource that reads from an HTTP endpoint.""" + + url: str = Field(description="URL to fetch content from") + mime_type: str = Field(default="application/json", description="MIME type of the resource content") + + async def read(self) -> str | bytes: + """Read the HTTP content.""" + async with httpx.AsyncClient() as client: # pragma: no cover + response = await client.get(self.url) + response.raise_for_status() + return response.text + + +class DirectoryResource(Resource): + """A resource that lists files in a directory.""" + + path: Path = Field(description="Path to the directory") + recursive: bool = Field(default=False, description="Whether to list files recursively") + pattern: str | None = Field(default=None, description="Optional glob pattern to filter files") + mime_type: str = Field(default="application/json", description="MIME type of the resource content") + + @pydantic.field_validator("path") + @classmethod + def validate_absolute_path(cls, path: Path) -> Path: # pragma: no cover + """Ensure path is absolute.""" + if not path.is_absolute(): + raise ValueError("Path must be absolute") + return path + + def list_files(self) -> list[Path]: # pragma: no cover + """List files in the directory.""" + if not self.path.exists(): + raise FileNotFoundError(f"Directory not found: {self.path}") + if not self.path.is_dir(): + raise NotADirectoryError(f"Not a directory: {self.path}") + + try: + if self.pattern: + return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern)) + return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*")) + except Exception as e: + raise ValueError(f"Error listing directory {self.path}: {e}") + + async def read(self) -> str: # Always returns JSON string # pragma: no cover + """Read the directory listing.""" + try: + files = await anyio.to_thread.run_sync(self.list_files) + file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()] + return json.dumps({"files": file_list}, indent=2) + except Exception as e: + raise ValueError(f"Error reading directory {self.path}: {e}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py new file mode 100644 index 0000000..7a43bd7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py @@ -0,0 +1,1349 @@ +"""FastMCP - A more ergonomic interface for MCP servers.""" + +from __future__ import annotations as _annotations + +import inspect +import re +from collections.abc import ( + AsyncIterator, + Awaitable, + Callable, + Collection, + Iterable, + Sequence, +) +from contextlib import AbstractAsyncContextManager, asynccontextmanager +from typing import Any, Generic, Literal + +import anyio +import pydantic_core +from pydantic import BaseModel +from pydantic.networks import AnyUrl +from pydantic_settings import BaseSettings, SettingsConfigDict +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.authentication import AuthenticationMiddleware +from starlette.requests import Request +from starlette.responses import Response +from starlette.routing import Mount, Route +from starlette.types import Receive, Scope, Send + +from mcp.server.auth.middleware.auth_context import AuthContextMiddleware +from mcp.server.auth.middleware.bearer_auth import ( + BearerAuthBackend, + RequireAuthMiddleware, +) +from mcp.server.auth.provider import ( + OAuthAuthorizationServerProvider, + ProviderTokenVerifier, + TokenVerifier, +) +from mcp.server.auth.settings import AuthSettings +from mcp.server.elicitation import ( + ElicitationResult, + ElicitSchemaModelT, + UrlElicitationResult, + elicit_with_validation, +) +from mcp.server.elicitation import ( + elicit_url as _elicit_url, +) +from mcp.server.fastmcp.exceptions import ResourceError +from mcp.server.fastmcp.prompts import Prompt, PromptManager +from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager +from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.server.fastmcp.utilities.context_injection import find_context_parameter +from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger +from mcp.server.lowlevel.helper_types import ReadResourceContents +from mcp.server.lowlevel.server import LifespanResultT +from mcp.server.lowlevel.server import Server as MCPServer +from mcp.server.lowlevel.server import lifespan as default_lifespan +from mcp.server.session import ServerSession, ServerSessionT +from mcp.server.sse import SseServerTransport +from mcp.server.stdio import stdio_server +from mcp.server.streamable_http import EventStore +from mcp.server.streamable_http_manager import StreamableHTTPSessionManager +from mcp.server.transport_security import TransportSecuritySettings +from mcp.shared.context import LifespanContextT, RequestContext, RequestT +from mcp.types import Annotations, AnyFunction, ContentBlock, GetPromptResult, Icon, ToolAnnotations +from mcp.types import Prompt as MCPPrompt +from mcp.types import PromptArgument as MCPPromptArgument +from mcp.types import Resource as MCPResource +from mcp.types import ResourceTemplate as MCPResourceTemplate +from mcp.types import Tool as MCPTool + +logger = get_logger(__name__) + + +class Settings(BaseSettings, Generic[LifespanResultT]): + """FastMCP server settings. + + All settings can be configured via environment variables with the prefix FASTMCP_. + For example, FASTMCP_DEBUG=true will set debug=True. + """ + + model_config = SettingsConfigDict( + env_prefix="FASTMCP_", + env_file=".env", + env_nested_delimiter="__", + nested_model_default_partial_update=True, + extra="ignore", + ) + + # Server settings + debug: bool + log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + + # HTTP settings + host: str + port: int + mount_path: str + sse_path: str + message_path: str + streamable_http_path: str + + # StreamableHTTP settings + json_response: bool + stateless_http: bool + """Define if the server should create a new transport per request.""" + + # resource settings + warn_on_duplicate_resources: bool + + # tool settings + warn_on_duplicate_tools: bool + + # prompt settings + warn_on_duplicate_prompts: bool + + # TODO(Marcelo): Investigate if this is used. If it is, it's probably a good idea to remove it. + dependencies: list[str] + """A list of dependencies to install in the server environment.""" + + lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None + """A async context manager that will be called when the server is started.""" + + auth: AuthSettings | None + + # Transport security settings (DNS rebinding protection) + transport_security: TransportSecuritySettings | None + + +def lifespan_wrapper( + app: FastMCP[LifespanResultT], + lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]], +) -> Callable[[MCPServer[LifespanResultT, Request]], AbstractAsyncContextManager[LifespanResultT]]: + @asynccontextmanager + async def wrap( + _: MCPServer[LifespanResultT, Request], + ) -> AsyncIterator[LifespanResultT]: + async with lifespan(app) as context: + yield context + + return wrap + + +class FastMCP(Generic[LifespanResultT]): + def __init__( # noqa: PLR0913 + self, + name: str | None = None, + instructions: str | None = None, + website_url: str | None = None, + icons: list[Icon] | None = None, + auth_server_provider: (OAuthAuthorizationServerProvider[Any, Any, Any] | None) = None, + token_verifier: TokenVerifier | None = None, + event_store: EventStore | None = None, + retry_interval: int | None = None, + *, + tools: list[Tool] | None = None, + debug: bool = False, + log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO", + host: str = "127.0.0.1", + port: int = 8000, + mount_path: str = "/", + sse_path: str = "/sse", + message_path: str = "/messages/", + streamable_http_path: str = "/mcp", + json_response: bool = False, + stateless_http: bool = False, + warn_on_duplicate_resources: bool = True, + warn_on_duplicate_tools: bool = True, + warn_on_duplicate_prompts: bool = True, + dependencies: Collection[str] = (), + lifespan: (Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None) = None, + auth: AuthSettings | None = None, + transport_security: TransportSecuritySettings | None = None, + ): + # Auto-enable DNS rebinding protection for localhost (IPv4 and IPv6) + if transport_security is None and host in ("127.0.0.1", "localhost", "::1"): + transport_security = TransportSecuritySettings( + enable_dns_rebinding_protection=True, + allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"], + allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"], + ) + + self.settings = Settings( + debug=debug, + log_level=log_level, + host=host, + port=port, + mount_path=mount_path, + sse_path=sse_path, + message_path=message_path, + streamable_http_path=streamable_http_path, + json_response=json_response, + stateless_http=stateless_http, + warn_on_duplicate_resources=warn_on_duplicate_resources, + warn_on_duplicate_tools=warn_on_duplicate_tools, + warn_on_duplicate_prompts=warn_on_duplicate_prompts, + dependencies=list(dependencies), + lifespan=lifespan, + auth=auth, + transport_security=transport_security, + ) + + self._mcp_server = MCPServer( + name=name or "FastMCP", + instructions=instructions, + website_url=website_url, + icons=icons, + # TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an FastMCP and Server. + # We need to create a Lifespan type that is a generic on the server type, like Starlette does. + lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan), # type: ignore + ) + self._tool_manager = ToolManager(tools=tools, warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools) + self._resource_manager = ResourceManager(warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources) + self._prompt_manager = PromptManager(warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts) + # Validate auth configuration + if self.settings.auth is not None: + if auth_server_provider and token_verifier: # pragma: no cover + raise ValueError("Cannot specify both auth_server_provider and token_verifier") + if not auth_server_provider and not token_verifier: # pragma: no cover + raise ValueError("Must specify either auth_server_provider or token_verifier when auth is enabled") + elif auth_server_provider or token_verifier: # pragma: no cover + raise ValueError("Cannot specify auth_server_provider or token_verifier without auth settings") + + self._auth_server_provider = auth_server_provider + self._token_verifier = token_verifier + + # Create token verifier from provider if needed (backwards compatibility) + if auth_server_provider and not token_verifier: # pragma: no cover + self._token_verifier = ProviderTokenVerifier(auth_server_provider) + self._event_store = event_store + self._retry_interval = retry_interval + self._custom_starlette_routes: list[Route] = [] + self.dependencies = self.settings.dependencies + self._session_manager: StreamableHTTPSessionManager | None = None + + # Set up MCP protocol handlers + self._setup_handlers() + + # Configure logging + configure_logging(self.settings.log_level) + + @property + def name(self) -> str: + return self._mcp_server.name + + @property + def instructions(self) -> str | None: + return self._mcp_server.instructions + + @property + def website_url(self) -> str | None: + return self._mcp_server.website_url + + @property + def icons(self) -> list[Icon] | None: + return self._mcp_server.icons + + @property + def session_manager(self) -> StreamableHTTPSessionManager: + """Get the StreamableHTTP session manager. + + This is exposed to enable advanced use cases like mounting multiple + FastMCP servers in a single FastAPI application. + + Raises: + RuntimeError: If called before streamable_http_app() has been called. + """ + if self._session_manager is None: # pragma: no cover + raise RuntimeError( + "Session manager can only be accessed after" + "calling streamable_http_app()." + "The session manager is created lazily" + "to avoid unnecessary initialization." + ) + return self._session_manager # pragma: no cover + + def run( + self, + transport: Literal["stdio", "sse", "streamable-http"] = "stdio", + mount_path: str | None = None, + ) -> None: + """Run the FastMCP server. Note this is a synchronous function. + + Args: + transport: Transport protocol to use ("stdio", "sse", or "streamable-http") + mount_path: Optional mount path for SSE transport + """ + TRANSPORTS = Literal["stdio", "sse", "streamable-http"] + if transport not in TRANSPORTS.__args__: # type: ignore # pragma: no cover + raise ValueError(f"Unknown transport: {transport}") + + match transport: + case "stdio": + anyio.run(self.run_stdio_async) + case "sse": # pragma: no cover + anyio.run(lambda: self.run_sse_async(mount_path)) + case "streamable-http": # pragma: no cover + anyio.run(self.run_streamable_http_async) + + def _setup_handlers(self) -> None: + """Set up core MCP protocol handlers.""" + self._mcp_server.list_tools()(self.list_tools) + # Note: we disable the lowlevel server's input validation. + # FastMCP does ad hoc conversion of incoming data before validating - + # for now we preserve this for backwards compatibility. + self._mcp_server.call_tool(validate_input=False)(self.call_tool) + self._mcp_server.list_resources()(self.list_resources) + self._mcp_server.read_resource()(self.read_resource) + self._mcp_server.list_prompts()(self.list_prompts) + self._mcp_server.get_prompt()(self.get_prompt) + self._mcp_server.list_resource_templates()(self.list_resource_templates) + + async def list_tools(self) -> list[MCPTool]: + """List all available tools.""" + tools = self._tool_manager.list_tools() + return [ + MCPTool( + name=info.name, + title=info.title, + description=info.description, + inputSchema=info.parameters, + outputSchema=info.output_schema, + annotations=info.annotations, + icons=info.icons, + _meta=info.meta, + ) + for info in tools + ] + + def get_context(self) -> Context[ServerSession, LifespanResultT, Request]: + """ + Returns a Context object. Note that the context will only be valid + during a request; outside a request, most methods will error. + """ + try: + request_context = self._mcp_server.request_context + except LookupError: + request_context = None + return Context(request_context=request_context, fastmcp=self) + + async def call_tool(self, name: str, arguments: dict[str, Any]) -> Sequence[ContentBlock] | dict[str, Any]: + """Call a tool by name with arguments.""" + context = self.get_context() + return await self._tool_manager.call_tool(name, arguments, context=context, convert_result=True) + + async def list_resources(self) -> list[MCPResource]: + """List all available resources.""" + + resources = self._resource_manager.list_resources() + return [ + MCPResource( + uri=resource.uri, + name=resource.name or "", + title=resource.title, + description=resource.description, + mimeType=resource.mime_type, + icons=resource.icons, + annotations=resource.annotations, + _meta=resource.meta, + ) + for resource in resources + ] + + async def list_resource_templates(self) -> list[MCPResourceTemplate]: + templates = self._resource_manager.list_templates() + return [ + MCPResourceTemplate( + uriTemplate=template.uri_template, + name=template.name, + title=template.title, + description=template.description, + mimeType=template.mime_type, + icons=template.icons, + annotations=template.annotations, + _meta=template.meta, + ) + for template in templates + ] + + async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContents]: + """Read a resource by URI.""" + + context = self.get_context() + resource = await self._resource_manager.get_resource(uri, context=context) + if not resource: # pragma: no cover + raise ResourceError(f"Unknown resource: {uri}") + + try: + content = await resource.read() + return [ReadResourceContents(content=content, mime_type=resource.mime_type, meta=resource.meta)] + except Exception as e: # pragma: no cover + logger.exception(f"Error reading resource {uri}") + raise ResourceError(str(e)) + + def add_tool( + self, + fn: AnyFunction, + name: str | None = None, + title: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + icons: list[Icon] | None = None, + meta: dict[str, Any] | None = None, + structured_output: bool | None = None, + ) -> None: + """Add a tool to the server. + + The tool function can optionally request a Context object by adding a parameter + with the Context type annotation. See the @tool decorator for examples. + + Args: + fn: The function to register as a tool + name: Optional name for the tool (defaults to function name) + title: Optional human-readable title for the tool + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + structured_output: Controls whether the tool's output is structured or unstructured + - If None, auto-detects based on the function's return type annotation + - If True, creates a structured tool (return type annotation permitting) + - If False, unconditionally creates an unstructured tool + """ + self._tool_manager.add_tool( + fn, + name=name, + title=title, + description=description, + annotations=annotations, + icons=icons, + meta=meta, + structured_output=structured_output, + ) + + def remove_tool(self, name: str) -> None: + """Remove a tool from the server by name. + + Args: + name: The name of the tool to remove + + Raises: + ToolError: If the tool does not exist + """ + self._tool_manager.remove_tool(name) + + def tool( + self, + name: str | None = None, + title: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + icons: list[Icon] | None = None, + meta: dict[str, Any] | None = None, + structured_output: bool | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a tool. + + Tools can optionally request a Context object by adding a parameter with the + Context type annotation. The context provides access to MCP capabilities like + logging, progress reporting, and resource access. + + Args: + name: Optional name for the tool (defaults to function name) + title: Optional human-readable title for the tool + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + structured_output: Controls whether the tool's output is structured or unstructured + - If None, auto-detects based on the function's return type annotation + - If True, creates a structured tool (return type annotation permitting) + - If False, unconditionally creates an unstructured tool + + Example: + @server.tool() + def my_tool(x: int) -> str: + return str(x) + + @server.tool() + def tool_with_context(x: int, ctx: Context) -> str: + ctx.info(f"Processing {x}") + return str(x) + + @server.tool() + async def async_tool(x: int, context: Context) -> str: + await context.report_progress(50, 100) + return str(x) + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + self.add_tool( + fn, + name=name, + title=title, + description=description, + annotations=annotations, + icons=icons, + meta=meta, + structured_output=structured_output, + ) + return fn + + return decorator + + def completion(self): + """Decorator to register a completion handler. + + The completion handler receives: + - ref: PromptReference or ResourceTemplateReference + - argument: CompletionArgument with name and partial value + - context: Optional CompletionContext with previously resolved arguments + + Example: + @mcp.completion() + async def handle_completion(ref, argument, context): + if isinstance(ref, ResourceTemplateReference): + # Return completions based on ref, argument, and context + return Completion(values=["option1", "option2"]) + return None + """ + return self._mcp_server.completion() + + def add_resource(self, resource: Resource) -> None: + """Add a resource to the server. + + Args: + resource: A Resource instance to add + """ + self._resource_manager.add_resource(resource) + + def resource( + self, + uri: str, + *, + name: str | None = None, + title: str | None = None, + description: str | None = None, + mime_type: str | None = None, + icons: list[Icon] | None = None, + annotations: Annotations | None = None, + meta: dict[str, Any] | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a function as a resource. + + The function will be called when the resource is read to generate its content. + The function can return: + - str for text content + - bytes for binary content + - other types will be converted to JSON + + If the URI contains parameters (e.g. "resource://{param}") or the function + has parameters, it will be registered as a template resource. + + Args: + uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}") + name: Optional name for the resource + title: Optional human-readable title for the resource + description: Optional description of the resource + mime_type: Optional MIME type for the resource + meta: Optional metadata dictionary for the resource + + Example: + @server.resource("resource://my-resource") + def get_data() -> str: + return "Hello, world!" + + @server.resource("resource://my-resource") + async get_data() -> str: + data = await fetch_data() + return f"Hello, world! {data}" + + @server.resource("resource://{city}/weather") + def get_weather(city: str) -> str: + return f"Weather for {city}" + + @server.resource("resource://{city}/weather") + async def get_weather(city: str) -> str: + data = await fetch_weather(city) + return f"Weather for {city}: {data}" + """ + # Check if user passed function directly instead of calling decorator + if callable(uri): + raise TypeError( + "The @resource decorator was used incorrectly. " + "Did you forget to call it? Use @resource('uri') instead of @resource" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + # Check if this should be a template + sig = inspect.signature(fn) + has_uri_params = "{" in uri and "}" in uri + has_func_params = bool(sig.parameters) + + if has_uri_params or has_func_params: + # Check for Context parameter to exclude from validation + context_param = find_context_parameter(fn) + + # Validate that URI params match function params (excluding context) + uri_params = set(re.findall(r"{(\w+)}", uri)) + # We need to remove the context_param from the resource function if + # there is any. + func_params = {p for p in sig.parameters.keys() if p != context_param} + + if uri_params != func_params: + raise ValueError( + f"Mismatch between URI parameters {uri_params} and function parameters {func_params}" + ) + + # Register as template + self._resource_manager.add_template( + fn=fn, + uri_template=uri, + name=name, + title=title, + description=description, + mime_type=mime_type, + icons=icons, + annotations=annotations, + meta=meta, + ) + else: + # Register as regular resource + resource = FunctionResource.from_function( + fn=fn, + uri=uri, + name=name, + title=title, + description=description, + mime_type=mime_type, + icons=icons, + annotations=annotations, + meta=meta, + ) + self.add_resource(resource) + return fn + + return decorator + + def add_prompt(self, prompt: Prompt) -> None: + """Add a prompt to the server. + + Args: + prompt: A Prompt instance to add + """ + self._prompt_manager.add_prompt(prompt) + + def prompt( + self, + name: str | None = None, + title: str | None = None, + description: str | None = None, + icons: list[Icon] | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a prompt. + + Args: + name: Optional name for the prompt (defaults to function name) + title: Optional human-readable title for the prompt + description: Optional description of what the prompt does + + Example: + @server.prompt() + def analyze_table(table_name: str) -> list[Message]: + schema = read_table_schema(table_name) + return [ + { + "role": "user", + "content": f"Analyze this schema:\n{schema}" + } + ] + + @server.prompt() + async def analyze_file(path: str) -> list[Message]: + content = await read_file(path) + return [ + { + "role": "user", + "content": { + "type": "resource", + "resource": { + "uri": f"file://{path}", + "text": content + } + } + } + ] + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @prompt decorator was used incorrectly. " + "Did you forget to call it? Use @prompt() instead of @prompt" + ) + + def decorator(func: AnyFunction) -> AnyFunction: + prompt = Prompt.from_function(func, name=name, title=title, description=description, icons=icons) + self.add_prompt(prompt) + return func + + return decorator + + def custom_route( + self, + path: str, + methods: list[str], + name: str | None = None, + include_in_schema: bool = True, + ): + """ + Decorator to register a custom HTTP route on the FastMCP server. + + Allows adding arbitrary HTTP endpoints outside the standard MCP protocol, + which can be useful for OAuth callbacks, health checks, or admin APIs. + The handler function must be an async function that accepts a Starlette + Request and returns a Response. + + Routes using this decorator will not require authorization. It is intended + for uses that are either a part of authorization flows or intended to be + public such as health check endpoints. + + Args: + path: URL path for the route (e.g., "/oauth/callback") + methods: List of HTTP methods to support (e.g., ["GET", "POST"]) + name: Optional name for the route (to reference this route with + Starlette's reverse URL lookup feature) + include_in_schema: Whether to include in OpenAPI schema, defaults to True + + Example: + @server.custom_route("/health", methods=["GET"]) + async def health_check(request: Request) -> Response: + return JSONResponse({"status": "ok"}) + """ + + def decorator( # pragma: no cover + func: Callable[[Request], Awaitable[Response]], + ) -> Callable[[Request], Awaitable[Response]]: + self._custom_starlette_routes.append( + Route( + path, + endpoint=func, + methods=methods, + name=name, + include_in_schema=include_in_schema, + ) + ) + return func + + return decorator # pragma: no cover + + async def run_stdio_async(self) -> None: + """Run the server using stdio transport.""" + async with stdio_server() as (read_stream, write_stream): + await self._mcp_server.run( + read_stream, + write_stream, + self._mcp_server.create_initialization_options(), + ) + + async def run_sse_async(self, mount_path: str | None = None) -> None: # pragma: no cover + """Run the server using SSE transport.""" + import uvicorn + + starlette_app = self.sse_app(mount_path) + + config = uvicorn.Config( + starlette_app, + host=self.settings.host, + port=self.settings.port, + log_level=self.settings.log_level.lower(), + ) + server = uvicorn.Server(config) + await server.serve() + + async def run_streamable_http_async(self) -> None: # pragma: no cover + """Run the server using StreamableHTTP transport.""" + import uvicorn + + starlette_app = self.streamable_http_app() + + config = uvicorn.Config( + starlette_app, + host=self.settings.host, + port=self.settings.port, + log_level=self.settings.log_level.lower(), + ) + server = uvicorn.Server(config) + await server.serve() + + def _normalize_path(self, mount_path: str, endpoint: str) -> str: + """ + Combine mount path and endpoint to return a normalized path. + + Args: + mount_path: The mount path (e.g. "/github" or "/") + endpoint: The endpoint path (e.g. "/messages/") + + Returns: + Normalized path (e.g. "/github/messages/") + """ + # Special case: root path + if mount_path == "/": + return endpoint + + # Remove trailing slash from mount path + if mount_path.endswith("/"): + mount_path = mount_path[:-1] + + # Ensure endpoint starts with slash + if not endpoint.startswith("/"): + endpoint = "/" + endpoint + + # Combine paths + return mount_path + endpoint + + def sse_app(self, mount_path: str | None = None) -> Starlette: + """Return an instance of the SSE server app.""" + from starlette.middleware import Middleware + from starlette.routing import Mount, Route + + # Update mount_path in settings if provided + if mount_path is not None: + self.settings.mount_path = mount_path + + # Create normalized endpoint considering the mount path + normalized_message_endpoint = self._normalize_path(self.settings.mount_path, self.settings.message_path) + + # Set up auth context and dependencies + + sse = SseServerTransport( + normalized_message_endpoint, + security_settings=self.settings.transport_security, + ) + + async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no cover + # Add client ID from auth context into request context if available + + async with sse.connect_sse( + scope, + receive, + send, + ) as streams: + await self._mcp_server.run( + streams[0], + streams[1], + self._mcp_server.create_initialization_options(), + ) + return Response() + + # Create routes + routes: list[Route | Mount] = [] + middleware: list[Middleware] = [] + required_scopes = [] + + # Set up auth if configured + if self.settings.auth: # pragma: no cover + required_scopes = self.settings.auth.required_scopes or [] + + # Add auth middleware if token verifier is available + if self._token_verifier: + middleware = [ + # extract auth info from request (but do not require it) + Middleware( + AuthenticationMiddleware, + backend=BearerAuthBackend(self._token_verifier), + ), + # Add the auth context middleware to store + # authenticated user in a contextvar + Middleware(AuthContextMiddleware), + ] + + # Add auth endpoints if auth server provider is configured + if self._auth_server_provider: + from mcp.server.auth.routes import create_auth_routes + + routes.extend( + create_auth_routes( + provider=self._auth_server_provider, + issuer_url=self.settings.auth.issuer_url, + service_documentation_url=self.settings.auth.service_documentation_url, + client_registration_options=self.settings.auth.client_registration_options, + revocation_options=self.settings.auth.revocation_options, + ) + ) + + # When auth is configured, require authentication + if self._token_verifier: # pragma: no cover + # Determine resource metadata URL + resource_metadata_url = None + if self.settings.auth and self.settings.auth.resource_server_url: + from mcp.server.auth.routes import build_resource_metadata_url + + # Build compliant metadata URL for WWW-Authenticate header + resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) + + # Auth is enabled, wrap the endpoints with RequireAuthMiddleware + routes.append( + Route( + self.settings.sse_path, + endpoint=RequireAuthMiddleware(handle_sse, required_scopes, resource_metadata_url), + methods=["GET"], + ) + ) + routes.append( + Mount( + self.settings.message_path, + app=RequireAuthMiddleware(sse.handle_post_message, required_scopes, resource_metadata_url), + ) + ) + else: # pragma: no cover + # Auth is disabled, no need for RequireAuthMiddleware + # Since handle_sse is an ASGI app, we need to create a compatible endpoint + async def sse_endpoint(request: Request) -> Response: + # Convert the Starlette request to ASGI parameters + return await handle_sse(request.scope, request.receive, request._send) # type: ignore[reportPrivateUsage] + + routes.append( + Route( + self.settings.sse_path, + endpoint=sse_endpoint, + methods=["GET"], + ) + ) + routes.append( + Mount( + self.settings.message_path, + app=sse.handle_post_message, + ) + ) + # Add protected resource metadata endpoint if configured as RS + if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover + from mcp.server.auth.routes import create_protected_resource_routes + + routes.extend( + create_protected_resource_routes( + resource_url=self.settings.auth.resource_server_url, + authorization_servers=[self.settings.auth.issuer_url], + scopes_supported=self.settings.auth.required_scopes, + ) + ) + + # mount these routes last, so they have the lowest route matching precedence + routes.extend(self._custom_starlette_routes) + + # Create Starlette app with routes and middleware + return Starlette(debug=self.settings.debug, routes=routes, middleware=middleware) + + def streamable_http_app(self) -> Starlette: + """Return an instance of the StreamableHTTP server app.""" + from starlette.middleware import Middleware + + # Create session manager on first call (lazy initialization) + if self._session_manager is None: # pragma: no branch + self._session_manager = StreamableHTTPSessionManager( + app=self._mcp_server, + event_store=self._event_store, + retry_interval=self._retry_interval, + json_response=self.settings.json_response, + stateless=self.settings.stateless_http, # Use the stateless setting + security_settings=self.settings.transport_security, + ) + + # Create the ASGI handler + streamable_http_app = StreamableHTTPASGIApp(self._session_manager) + + # Create routes + routes: list[Route | Mount] = [] + middleware: list[Middleware] = [] + required_scopes = [] + + # Set up auth if configured + if self.settings.auth: # pragma: no cover + required_scopes = self.settings.auth.required_scopes or [] + + # Add auth middleware if token verifier is available + if self._token_verifier: + middleware = [ + Middleware( + AuthenticationMiddleware, + backend=BearerAuthBackend(self._token_verifier), + ), + Middleware(AuthContextMiddleware), + ] + + # Add auth endpoints if auth server provider is configured + if self._auth_server_provider: + from mcp.server.auth.routes import create_auth_routes + + routes.extend( + create_auth_routes( + provider=self._auth_server_provider, + issuer_url=self.settings.auth.issuer_url, + service_documentation_url=self.settings.auth.service_documentation_url, + client_registration_options=self.settings.auth.client_registration_options, + revocation_options=self.settings.auth.revocation_options, + ) + ) + + # Set up routes with or without auth + if self._token_verifier: # pragma: no cover + # Determine resource metadata URL + resource_metadata_url = None + if self.settings.auth and self.settings.auth.resource_server_url: + from mcp.server.auth.routes import build_resource_metadata_url + + # Build compliant metadata URL for WWW-Authenticate header + resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) + + routes.append( + Route( + self.settings.streamable_http_path, + endpoint=RequireAuthMiddleware(streamable_http_app, required_scopes, resource_metadata_url), + ) + ) + else: + # Auth is disabled, no wrapper needed + routes.append( + Route( + self.settings.streamable_http_path, + endpoint=streamable_http_app, + ) + ) + + # Add protected resource metadata endpoint if configured as RS + if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover + from mcp.server.auth.routes import create_protected_resource_routes + + routes.extend( + create_protected_resource_routes( + resource_url=self.settings.auth.resource_server_url, + authorization_servers=[self.settings.auth.issuer_url], + scopes_supported=self.settings.auth.required_scopes, + ) + ) + + routes.extend(self._custom_starlette_routes) + + return Starlette( + debug=self.settings.debug, + routes=routes, + middleware=middleware, + lifespan=lambda app: self.session_manager.run(), + ) + + async def list_prompts(self) -> list[MCPPrompt]: + """List all available prompts.""" + prompts = self._prompt_manager.list_prompts() + return [ + MCPPrompt( + name=prompt.name, + title=prompt.title, + description=prompt.description, + arguments=[ + MCPPromptArgument( + name=arg.name, + description=arg.description, + required=arg.required, + ) + for arg in (prompt.arguments or []) + ], + icons=prompt.icons, + ) + for prompt in prompts + ] + + async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult: + """Get a prompt by name with arguments.""" + try: + prompt = self._prompt_manager.get_prompt(name) + if not prompt: + raise ValueError(f"Unknown prompt: {name}") + + messages = await prompt.render(arguments, context=self.get_context()) + + return GetPromptResult( + description=prompt.description, + messages=pydantic_core.to_jsonable_python(messages), + ) + except Exception as e: + logger.exception(f"Error getting prompt {name}") + raise ValueError(str(e)) + + +class StreamableHTTPASGIApp: + """ + ASGI application for Streamable HTTP server transport. + """ + + def __init__(self, session_manager: StreamableHTTPSessionManager): + self.session_manager = session_manager + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: # pragma: no cover + await self.session_manager.handle_request(scope, receive, send) + + +class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]): + """Context object providing access to MCP capabilities. + + This provides a cleaner interface to MCP's RequestContext functionality. + It gets injected into tool and resource functions that request it via type hints. + + To use context in a tool function, add a parameter with the Context type annotation: + + ```python + @server.tool() + def my_tool(x: int, ctx: Context) -> str: + # Log messages to the client + ctx.info(f"Processing {x}") + ctx.debug("Debug info") + ctx.warning("Warning message") + ctx.error("Error message") + + # Report progress + ctx.report_progress(50, 100) + + # Access resources + data = ctx.read_resource("resource://data") + + # Get request info + request_id = ctx.request_id + client_id = ctx.client_id + + return str(x) + ``` + + The context parameter name can be anything as long as it's annotated with Context. + The context is optional - tools that don't need it can omit the parameter. + """ + + _request_context: RequestContext[ServerSessionT, LifespanContextT, RequestT] | None + _fastmcp: FastMCP | None + + def __init__( + self, + *, + request_context: (RequestContext[ServerSessionT, LifespanContextT, RequestT] | None) = None, + fastmcp: FastMCP | None = None, + **kwargs: Any, + ): + super().__init__(**kwargs) + self._request_context = request_context + self._fastmcp = fastmcp + + @property + def fastmcp(self) -> FastMCP: + """Access to the FastMCP server.""" + if self._fastmcp is None: # pragma: no cover + raise ValueError("Context is not available outside of a request") + return self._fastmcp # pragma: no cover + + @property + def request_context( + self, + ) -> RequestContext[ServerSessionT, LifespanContextT, RequestT]: + """Access to the underlying request context.""" + if self._request_context is None: # pragma: no cover + raise ValueError("Context is not available outside of a request") + return self._request_context + + async def report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None: + """Report progress for the current operation. + + Args: + progress: Current progress value e.g. 24 + total: Optional total value e.g. 100 + message: Optional message e.g. Starting render... + """ + progress_token = self.request_context.meta.progressToken if self.request_context.meta else None + + if progress_token is None: # pragma: no cover + return + + await self.request_context.session.send_progress_notification( + progress_token=progress_token, + progress=progress, + total=total, + message=message, + ) + + async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContents]: + """Read a resource by URI. + + Args: + uri: Resource URI to read + + Returns: + The resource content as either text or bytes + """ + assert self._fastmcp is not None, "Context is not available outside of a request" + return await self._fastmcp.read_resource(uri) + + async def elicit( + self, + message: str, + schema: type[ElicitSchemaModelT], + ) -> ElicitationResult[ElicitSchemaModelT]: + """Elicit information from the client/user. + + This method can be used to interactively ask for additional information from the + client within a tool's execution. The client might display the message to the + user and collect a response according to the provided schema. Or in case a + client is an agent, it might decide how to handle the elicitation -- either by asking + the user or automatically generating a response. + + Args: + schema: A Pydantic model class defining the expected response structure, according to the specification, + only primive types are allowed. + message: Optional message to present to the user. If not provided, will use + a default message based on the schema + + Returns: + An ElicitationResult containing the action taken and the data if accepted + + Note: + Check the result.action to determine if the user accepted, declined, or cancelled. + The result.data will only be populated if action is "accept" and validation succeeded. + """ + + return await elicit_with_validation( + session=self.request_context.session, + message=message, + schema=schema, + related_request_id=self.request_id, + ) + + async def elicit_url( + self, + message: str, + url: str, + elicitation_id: str, + ) -> UrlElicitationResult: + """Request URL mode elicitation from the client. + + This directs the user to an external URL for out-of-band interactions + that must not pass through the MCP client. Use this for: + - Collecting sensitive credentials (API keys, passwords) + - OAuth authorization flows with third-party services + - Payment and subscription flows + - Any interaction where data should not pass through the LLM context + + The response indicates whether the user consented to navigate to the URL. + The actual interaction happens out-of-band. When the elicitation completes, + call `self.session.send_elicit_complete(elicitation_id)` to notify the client. + + Args: + message: Human-readable explanation of why the interaction is needed + url: The URL the user should navigate to + elicitation_id: Unique identifier for tracking this elicitation + + Returns: + UrlElicitationResult indicating accept, decline, or cancel + """ + return await _elicit_url( + session=self.request_context.session, + message=message, + url=url, + elicitation_id=elicitation_id, + related_request_id=self.request_id, + ) + + async def log( + self, + level: Literal["debug", "info", "warning", "error"], + message: str, + *, + logger_name: str | None = None, + ) -> None: + """Send a log message to the client. + + Args: + level: Log level (debug, info, warning, error) + message: Log message + logger_name: Optional logger name + **extra: Additional structured data to include + """ + await self.request_context.session.send_log_message( + level=level, + data=message, + logger=logger_name, + related_request_id=self.request_id, + ) + + @property + def client_id(self) -> str | None: + """Get the client ID if available.""" + return ( + getattr(self.request_context.meta, "client_id", None) if self.request_context.meta else None + ) # pragma: no cover + + @property + def request_id(self) -> str: + """Get the unique ID for this request.""" + return str(self.request_context.request_id) + + @property + def session(self): + """Access to the underlying session for advanced usage.""" + return self.request_context.session + + async def close_sse_stream(self) -> None: + """Close the SSE stream to trigger client reconnection. + + This method closes the HTTP connection for the current request, triggering + client reconnection. Events continue to be stored in the event store and will + be replayed when the client reconnects with Last-Event-ID. + + Use this to implement polling behavior during long-running operations - + client will reconnect after the retry interval specified in the priming event. + + Note: + This is a no-op if not using StreamableHTTP transport with event_store. + The callback is only available when event_store is configured. + """ + if self._request_context and self._request_context.close_sse_stream: # pragma: no cover + await self._request_context.close_sse_stream() + + async def close_standalone_sse_stream(self) -> None: + """Close the standalone GET SSE stream to trigger client reconnection. + + This method closes the HTTP connection for the standalone GET stream used + for unsolicited server-to-client notifications. The client SHOULD reconnect + with Last-Event-ID to resume receiving notifications. + + Note: + This is a no-op if not using StreamableHTTP transport with event_store. + Currently, client reconnection for standalone GET streams is NOT + implemented - this is a known gap. + """ + if self._request_context and self._request_context.close_standalone_sse_stream: # pragma: no cover + await self._request_context.close_standalone_sse_stream() + + # Convenience methods for common log levels + async def debug(self, message: str, **extra: Any) -> None: + """Send a debug log message.""" + await self.log("debug", message, **extra) + + async def info(self, message: str, **extra: Any) -> None: + """Send an info log message.""" + await self.log("info", message, **extra) + + async def warning(self, message: str, **extra: Any) -> None: + """Send a warning log message.""" + await self.log("warning", message, **extra) + + async def error(self, message: str, **extra: Any) -> None: + """Send an error log message.""" + await self.log("error", message, **extra) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py new file mode 100644 index 0000000..ae9c656 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py @@ -0,0 +1,4 @@ +from .base import Tool +from .tool_manager import ToolManager + +__all__ = ["Tool", "ToolManager"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py new file mode 100644 index 0000000..1ae6d90 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py @@ -0,0 +1,126 @@ +from __future__ import annotations as _annotations + +import functools +import inspect +from collections.abc import Callable +from functools import cached_property +from typing import TYPE_CHECKING, Any + +from pydantic import BaseModel, Field + +from mcp.server.fastmcp.exceptions import ToolError +from mcp.server.fastmcp.utilities.context_injection import find_context_parameter +from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metadata +from mcp.shared.exceptions import UrlElicitationRequiredError +from mcp.shared.tool_name_validation import validate_and_warn_tool_name +from mcp.types import Icon, ToolAnnotations + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + from mcp.shared.context import LifespanContextT, RequestT + + +class Tool(BaseModel): + """Internal tool registration info.""" + + fn: Callable[..., Any] = Field(exclude=True) + name: str = Field(description="Name of the tool") + title: str | None = Field(None, description="Human-readable title of the tool") + description: str = Field(description="Description of what the tool does") + parameters: dict[str, Any] = Field(description="JSON schema for tool parameters") + fn_metadata: FuncMetadata = Field( + description="Metadata about the function including a pydantic model for tool arguments" + ) + is_async: bool = Field(description="Whether the tool is async") + context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context") + annotations: ToolAnnotations | None = Field(None, description="Optional annotations for the tool") + icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this tool") + meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this tool") + + @cached_property + def output_schema(self) -> dict[str, Any] | None: + return self.fn_metadata.output_schema + + @classmethod + def from_function( + cls, + fn: Callable[..., Any], + name: str | None = None, + title: str | None = None, + description: str | None = None, + context_kwarg: str | None = None, + annotations: ToolAnnotations | None = None, + icons: list[Icon] | None = None, + meta: dict[str, Any] | None = None, + structured_output: bool | None = None, + ) -> Tool: + """Create a Tool from a function.""" + func_name = name or fn.__name__ + + validate_and_warn_tool_name(func_name) + + if func_name == "": + raise ValueError("You must provide a name for lambda functions") + + func_doc = description or fn.__doc__ or "" + is_async = _is_async_callable(fn) + + if context_kwarg is None: # pragma: no branch + context_kwarg = find_context_parameter(fn) + + func_arg_metadata = func_metadata( + fn, + skip_names=[context_kwarg] if context_kwarg is not None else [], + structured_output=structured_output, + ) + parameters = func_arg_metadata.arg_model.model_json_schema(by_alias=True) + + return cls( + fn=fn, + name=func_name, + title=title, + description=func_doc, + parameters=parameters, + fn_metadata=func_arg_metadata, + is_async=is_async, + context_kwarg=context_kwarg, + annotations=annotations, + icons=icons, + meta=meta, + ) + + async def run( + self, + arguments: dict[str, Any], + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + convert_result: bool = False, + ) -> Any: + """Run the tool with arguments.""" + try: + result = await self.fn_metadata.call_fn_with_arg_validation( + self.fn, + self.is_async, + arguments, + {self.context_kwarg: context} if self.context_kwarg is not None else None, + ) + + if convert_result: + result = self.fn_metadata.convert_result(result) + + return result + except UrlElicitationRequiredError: + # Re-raise UrlElicitationRequiredError so it can be properly handled + # as an MCP error response with code -32042 + raise + except Exception as e: + raise ToolError(f"Error executing tool {self.name}: {e}") from e + + +def _is_async_callable(obj: Any) -> bool: + while isinstance(obj, functools.partial): # pragma: no cover + obj = obj.func + + return inspect.iscoroutinefunction(obj) or ( + callable(obj) and inspect.iscoroutinefunction(getattr(obj, "__call__", None)) + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py new file mode 100644 index 0000000..095753d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py @@ -0,0 +1,93 @@ +from __future__ import annotations as _annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING, Any + +from mcp.server.fastmcp.exceptions import ToolError +from mcp.server.fastmcp.tools.base import Tool +from mcp.server.fastmcp.utilities.logging import get_logger +from mcp.shared.context import LifespanContextT, RequestT +from mcp.types import Icon, ToolAnnotations + +if TYPE_CHECKING: + from mcp.server.fastmcp.server import Context + from mcp.server.session import ServerSessionT + +logger = get_logger(__name__) + + +class ToolManager: + """Manages FastMCP tools.""" + + def __init__( + self, + warn_on_duplicate_tools: bool = True, + *, + tools: list[Tool] | None = None, + ): + self._tools: dict[str, Tool] = {} + if tools is not None: + for tool in tools: + if warn_on_duplicate_tools and tool.name in self._tools: + logger.warning(f"Tool already exists: {tool.name}") + self._tools[tool.name] = tool + + self.warn_on_duplicate_tools = warn_on_duplicate_tools + + def get_tool(self, name: str) -> Tool | None: + """Get tool by name.""" + return self._tools.get(name) + + def list_tools(self) -> list[Tool]: + """List all registered tools.""" + return list(self._tools.values()) + + def add_tool( + self, + fn: Callable[..., Any], + name: str | None = None, + title: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + icons: list[Icon] | None = None, + meta: dict[str, Any] | None = None, + structured_output: bool | None = None, + ) -> Tool: + """Add a tool to the server.""" + tool = Tool.from_function( + fn, + name=name, + title=title, + description=description, + annotations=annotations, + icons=icons, + meta=meta, + structured_output=structured_output, + ) + existing = self._tools.get(tool.name) + if existing: + if self.warn_on_duplicate_tools: + logger.warning(f"Tool already exists: {tool.name}") + return existing + self._tools[tool.name] = tool + return tool + + def remove_tool(self, name: str) -> None: + """Remove a tool by name.""" + if name not in self._tools: + raise ToolError(f"Unknown tool: {name}") + del self._tools[name] + + async def call_tool( + self, + name: str, + arguments: dict[str, Any], + context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, + convert_result: bool = False, + ) -> Any: + """Call a tool by name with arguments.""" + tool = self.get_tool(name) + if not tool: + raise ToolError(f"Unknown tool: {name}") + + return await tool.run(arguments, context=context, convert_result=convert_result) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py new file mode 100644 index 0000000..be448f9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py @@ -0,0 +1 @@ +"""FastMCP utility modules.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py new file mode 100644 index 0000000..66d0cba --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py @@ -0,0 +1,68 @@ +"""Context injection utilities for FastMCP.""" + +from __future__ import annotations + +import inspect +import typing +from collections.abc import Callable +from typing import Any + + +def find_context_parameter(fn: Callable[..., Any]) -> str | None: + """Find the parameter that should receive the Context object. + + Searches through the function's signature to find a parameter + with a Context type annotation. + + Args: + fn: The function to inspect + + Returns: + The name of the context parameter, or None if not found + """ + from mcp.server.fastmcp.server import Context + + # Get type hints to properly resolve string annotations + try: + hints = typing.get_type_hints(fn) + except Exception: + # If we can't resolve type hints, we can't find the context parameter + return None + + # Check each parameter's type hint + for param_name, annotation in hints.items(): + # Handle direct Context type + if inspect.isclass(annotation) and issubclass(annotation, Context): + return param_name + + # Handle generic types like Optional[Context] + origin = typing.get_origin(annotation) + if origin is not None: + args = typing.get_args(annotation) + for arg in args: + if inspect.isclass(arg) and issubclass(arg, Context): + return param_name + + return None + + +def inject_context( + fn: Callable[..., Any], + kwargs: dict[str, Any], + context: Any | None, + context_kwarg: str | None, +) -> dict[str, Any]: + """Inject context into function kwargs if needed. + + Args: + fn: The function that will be called + kwargs: The current keyword arguments + context: The context object to inject (if any) + context_kwarg: The name of the parameter to inject into + + Returns: + Updated kwargs with context injected if applicable + """ + if context_kwarg is not None and context is not None: + return {**kwargs, context_kwarg: context} + return kwargs diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py new file mode 100644 index 0000000..fa443d2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py @@ -0,0 +1,533 @@ +import inspect +import json +from collections.abc import Awaitable, Callable, Sequence +from itertools import chain +from types import GenericAlias +from typing import Annotated, Any, cast, get_args, get_origin, get_type_hints + +import pydantic_core +from pydantic import ( + BaseModel, + ConfigDict, + Field, + RootModel, + WithJsonSchema, + create_model, +) +from pydantic.fields import FieldInfo +from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind +from typing_extensions import is_typeddict +from typing_inspection.introspection import ( + UNKNOWN, + AnnotationSource, + ForbiddenQualifier, + inspect_annotation, + is_union_origin, +) + +from mcp.server.fastmcp.exceptions import InvalidSignature +from mcp.server.fastmcp.utilities.logging import get_logger +from mcp.server.fastmcp.utilities.types import Audio, Image +from mcp.types import CallToolResult, ContentBlock, TextContent + +logger = get_logger(__name__) + + +class StrictJsonSchema(GenerateJsonSchema): + """A JSON schema generator that raises exceptions instead of emitting warnings. + + This is used to detect non-serializable types during schema generation. + """ + + def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None: + # Raise an exception instead of emitting a warning + raise ValueError(f"JSON schema warning: {kind} - {detail}") + + +class ArgModelBase(BaseModel): + """A model representing the arguments to a function.""" + + def model_dump_one_level(self) -> dict[str, Any]: + """Return a dict of the model's fields, one level deep. + + That is, sub-models etc are not dumped - they are kept as pydantic models. + """ + kwargs: dict[str, Any] = {} + for field_name, field_info in self.__class__.model_fields.items(): + value = getattr(self, field_name) + # Use the alias if it exists, otherwise use the field name + output_name = field_info.alias if field_info.alias else field_name + kwargs[output_name] = value + return kwargs + + model_config = ConfigDict( + arbitrary_types_allowed=True, + ) + + +class FuncMetadata(BaseModel): + arg_model: Annotated[type[ArgModelBase], WithJsonSchema(None)] + output_schema: dict[str, Any] | None = None + output_model: Annotated[type[BaseModel], WithJsonSchema(None)] | None = None + wrap_output: bool = False + + async def call_fn_with_arg_validation( + self, + fn: Callable[..., Any | Awaitable[Any]], + fn_is_async: bool, + arguments_to_validate: dict[str, Any], + arguments_to_pass_directly: dict[str, Any] | None, + ) -> Any: + """Call the given function with arguments validated and injected. + + Arguments are first attempted to be parsed from JSON, then validated against + the argument model, before being passed to the function. + """ + arguments_pre_parsed = self.pre_parse_json(arguments_to_validate) + arguments_parsed_model = self.arg_model.model_validate(arguments_pre_parsed) + arguments_parsed_dict = arguments_parsed_model.model_dump_one_level() + + arguments_parsed_dict |= arguments_to_pass_directly or {} + + if fn_is_async: + return await fn(**arguments_parsed_dict) + else: + return fn(**arguments_parsed_dict) + + def convert_result(self, result: Any) -> Any: + """ + Convert the result of a function call to the appropriate format for + the lowlevel server tool call handler: + + - If output_model is None, return the unstructured content directly. + - If output_model is not None, convert the result to structured output format + (dict[str, Any]) and return both unstructured and structured content. + + Note: we return unstructured content here **even though the lowlevel server + tool call handler provides generic backwards compatibility serialization of + structured content**. This is for FastMCP backwards compatibility: we need to + retain FastMCP's ad hoc conversion logic for constructing unstructured output + from function return values, whereas the lowlevel server simply serializes + the structured output. + """ + if isinstance(result, CallToolResult): + if self.output_schema is not None: + assert self.output_model is not None, "Output model must be set if output schema is defined" + self.output_model.model_validate(result.structuredContent) + return result + + unstructured_content = _convert_to_content(result) + + if self.output_schema is None: + return unstructured_content + else: + if self.wrap_output: + result = {"result": result} + + assert self.output_model is not None, "Output model must be set if output schema is defined" + validated = self.output_model.model_validate(result) + structured_content = validated.model_dump(mode="json", by_alias=True) + + return (unstructured_content, structured_content) + + def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]: + """Pre-parse data from JSON. + + Return a dict with same keys as input but with values parsed from JSON + if appropriate. + + This is to handle cases like `["a", "b", "c"]` being passed in as JSON inside + a string rather than an actual list. Claude desktop is prone to this - in fact + it seems incapable of NOT doing this. For sub-models, it tends to pass + dicts (JSON objects) as JSON strings, which can be pre-parsed here. + """ + new_data = data.copy() # Shallow copy + + # Build a mapping from input keys (including aliases) to field info + key_to_field_info: dict[str, FieldInfo] = {} + for field_name, field_info in self.arg_model.model_fields.items(): + # Map both the field name and its alias (if any) to the field info + key_to_field_info[field_name] = field_info + if field_info.alias: + key_to_field_info[field_info.alias] = field_info + + for data_key, data_value in data.items(): + if data_key not in key_to_field_info: # pragma: no cover + continue + + field_info = key_to_field_info[data_key] + if isinstance(data_value, str) and field_info.annotation is not str: + try: + pre_parsed = json.loads(data_value) + except json.JSONDecodeError: + continue # Not JSON - skip + if isinstance(pre_parsed, str | int | float): + # This is likely that the raw value is e.g. `"hello"` which we + # Should really be parsed as '"hello"' in Python - but if we parse + # it as JSON it'll turn into just 'hello'. So we skip it. + continue + new_data[data_key] = pre_parsed + assert new_data.keys() == data.keys() + return new_data + + model_config = ConfigDict( + arbitrary_types_allowed=True, + ) + + +def func_metadata( + func: Callable[..., Any], + skip_names: Sequence[str] = (), + structured_output: bool | None = None, +) -> FuncMetadata: + """Given a function, return metadata including a pydantic model representing its + signature. + + The use case for this is + ``` + meta = func_metadata(func) + validated_args = meta.arg_model.model_validate(some_raw_data_dict) + return func(**validated_args.model_dump_one_level()) + ``` + + **critically** it also provides pre-parse helper to attempt to parse things from + JSON. + + Args: + func: The function to convert to a pydantic model + skip_names: A list of parameter names to skip. These will not be included in + the model. + structured_output: Controls whether the tool's output is structured or unstructured + - If None, auto-detects based on the function's return type annotation + - If True, creates a structured tool (return type annotation permitting) + - If False, unconditionally creates an unstructured tool + + If structured, creates a Pydantic model for the function's result based on its annotation. + Supports various return types: + - BaseModel subclasses (used directly) + - Primitive types (str, int, float, bool, bytes, None) - wrapped in a + model with a 'result' field + - TypedDict - converted to a Pydantic model with same fields + - Dataclasses and other annotated classes - converted to Pydantic models + - Generic types (list, dict, Union, etc.) - wrapped in a model with a 'result' field + + Returns: + A FuncMetadata object containing: + - arg_model: A pydantic model representing the function's arguments + - output_model: A pydantic model for the return type if output is structured + - output_conversion: Records how function output should be converted before returning. + """ + try: + sig = inspect.signature(func, eval_str=True) + except NameError as e: # pragma: no cover + # This raise could perhaps be skipped, and we (FastMCP) just call + # model_rebuild right before using it 🤷 + raise InvalidSignature(f"Unable to evaluate type annotations for callable {func.__name__!r}") from e + params = sig.parameters + dynamic_pydantic_model_params: dict[str, Any] = {} + for param in params.values(): + if param.name.startswith("_"): # pragma: no cover + raise InvalidSignature(f"Parameter {param.name} of {func.__name__} cannot start with '_'") + if param.name in skip_names: + continue + + annotation = param.annotation if param.annotation is not inspect.Parameter.empty else Any + field_name = param.name + field_kwargs: dict[str, Any] = {} + field_metadata: list[Any] = [] + + if param.annotation is inspect.Parameter.empty: + field_metadata.append(WithJsonSchema({"title": param.name, "type": "string"})) + # Check if the parameter name conflicts with BaseModel attributes + # This is necessary because Pydantic warns about shadowing parent attributes + if hasattr(BaseModel, field_name) and callable(getattr(BaseModel, field_name)): + # Use an alias to avoid the shadowing warning + field_kwargs["alias"] = field_name + # Use a prefixed field name + field_name = f"field_{field_name}" + + if param.default is not inspect.Parameter.empty: + dynamic_pydantic_model_params[field_name] = ( + Annotated[(annotation, *field_metadata, Field(**field_kwargs))], + param.default, + ) + else: + dynamic_pydantic_model_params[field_name] = Annotated[(annotation, *field_metadata, Field(**field_kwargs))] + + arguments_model = create_model( + f"{func.__name__}Arguments", + __base__=ArgModelBase, + **dynamic_pydantic_model_params, + ) + + if structured_output is False: + return FuncMetadata(arg_model=arguments_model) + + # set up structured output support based on return type annotation + + if sig.return_annotation is inspect.Parameter.empty and structured_output is True: + raise InvalidSignature(f"Function {func.__name__}: return annotation required for structured output") + + try: + inspected_return_ann = inspect_annotation(sig.return_annotation, annotation_source=AnnotationSource.FUNCTION) + except ForbiddenQualifier as e: + raise InvalidSignature(f"Function {func.__name__}: return annotation contains an invalid type qualifier") from e + + return_type_expr = inspected_return_ann.type + + # `AnnotationSource.FUNCTION` allows no type qualifier to be used, so `return_type_expr` is guaranteed to *not* be + # unknown (i.e. a bare `Final`). + assert return_type_expr is not UNKNOWN + + if is_union_origin(get_origin(return_type_expr)): + args = get_args(return_type_expr) + # Check if CallToolResult appears in the union (excluding None for Optional check) + if any(isinstance(arg, type) and issubclass(arg, CallToolResult) for arg in args if arg is not type(None)): + raise InvalidSignature( + f"Function {func.__name__}: CallToolResult cannot be used in Union or Optional types. " + "To return empty results, use: CallToolResult(content=[])" + ) + + original_annotation: Any + # if the typehint is CallToolResult, the user either intends to return without validation + # or they provided validation as Annotated metadata + if isinstance(return_type_expr, type) and issubclass(return_type_expr, CallToolResult): + if inspected_return_ann.metadata: + return_type_expr = inspected_return_ann.metadata[0] + if len(inspected_return_ann.metadata) >= 2: + # Reconstruct the original annotation, by preserving the remaining metadata, + # i.e. from `Annotated[CallToolResult, ReturnType, Gt(1)]` to + # `Annotated[ReturnType, Gt(1)]`: + original_annotation = Annotated[ + (return_type_expr, *inspected_return_ann.metadata[1:]) + ] # pragma: no cover + else: + # We only had `Annotated[CallToolResult, ReturnType]`, treat the original annotation + # as beging `ReturnType`: + original_annotation = return_type_expr + else: + return FuncMetadata(arg_model=arguments_model) + else: + original_annotation = sig.return_annotation + + output_model, output_schema, wrap_output = _try_create_model_and_schema( + original_annotation, return_type_expr, func.__name__ + ) + + if output_model is None and structured_output is True: + # Model creation failed or produced warnings - no structured output + raise InvalidSignature( + f"Function {func.__name__}: return type {return_type_expr} is not serializable for structured output" + ) + + return FuncMetadata( + arg_model=arguments_model, + output_schema=output_schema, + output_model=output_model, + wrap_output=wrap_output, + ) + + +def _try_create_model_and_schema( + original_annotation: Any, + type_expr: Any, + func_name: str, +) -> tuple[type[BaseModel] | None, dict[str, Any] | None, bool]: + """Try to create a model and schema for the given annotation without warnings. + + Args: + original_annotation: The original return annotation (may be wrapped in `Annotated`). + type_expr: The underlying type expression derived from the return annotation + (`Annotated` and type qualifiers were stripped). + func_name: The name of the function. + + Returns: + tuple of (model or None, schema or None, wrap_output) + Model and schema are None if warnings occur or creation fails. + wrap_output is True if the result needs to be wrapped in {"result": ...} + """ + model = None + wrap_output = False + + # First handle special case: None + if type_expr is None: + model = _create_wrapped_model(func_name, original_annotation) + wrap_output = True + + # Handle GenericAlias types (list[str], dict[str, int], Union[str, int], etc.) + elif isinstance(type_expr, GenericAlias): + origin = get_origin(type_expr) + + # Special case: dict with string keys can use RootModel + if origin is dict: + args = get_args(type_expr) + if len(args) == 2 and args[0] is str: + # TODO: should we use the original annotation? We are loosing any potential `Annotated` + # metadata for Pydantic here: + model = _create_dict_model(func_name, type_expr) + else: + # dict with non-str keys needs wrapping + model = _create_wrapped_model(func_name, original_annotation) + wrap_output = True + else: + # All other generic types need wrapping (list, tuple, Union, Optional, etc.) + model = _create_wrapped_model(func_name, original_annotation) + wrap_output = True + + # Handle regular type objects + elif isinstance(type_expr, type): + type_annotation = cast(type[Any], type_expr) + + # Case 1: BaseModel subclasses (can be used directly) + if issubclass(type_annotation, BaseModel): + model = type_annotation + + # Case 2: TypedDicts: + elif is_typeddict(type_annotation): + model = _create_model_from_typeddict(type_annotation) + + # Case 3: Primitive types that need wrapping + elif type_annotation in (str, int, float, bool, bytes, type(None)): + model = _create_wrapped_model(func_name, original_annotation) + wrap_output = True + + # Case 4: Other class types (dataclasses, regular classes with annotations) + else: + type_hints = get_type_hints(type_annotation) + if type_hints: + # Classes with type hints can be converted to Pydantic models + model = _create_model_from_class(type_annotation, type_hints) + # Classes without type hints are not serializable - model remains None + + # Handle any other types not covered above + else: + # This includes typing constructs that aren't GenericAlias in Python 3.10 + # (e.g., Union, Optional in some Python versions) + model = _create_wrapped_model(func_name, original_annotation) + wrap_output = True + + if model: + # If we successfully created a model, try to get its schema + # Use StrictJsonSchema to raise exceptions instead of warnings + try: + schema = model.model_json_schema(schema_generator=StrictJsonSchema) + except (TypeError, ValueError, pydantic_core.SchemaError, pydantic_core.ValidationError) as e: + # These are expected errors when a type can't be converted to a Pydantic schema + # TypeError: When Pydantic can't handle the type + # ValueError: When there are issues with the type definition (including our custom warnings) + # SchemaError: When Pydantic can't build a schema + # ValidationError: When validation fails + logger.info(f"Cannot create schema for type {type_expr} in {func_name}: {type(e).__name__}: {e}") + return None, None, False + + return model, schema, wrap_output + + return None, None, False + + +_no_default = object() + + +def _create_model_from_class(cls: type[Any], type_hints: dict[str, Any]) -> type[BaseModel]: + """Create a Pydantic model from an ordinary class. + + The created model will: + - Have the same name as the class + - Have fields with the same names and types as the class's fields + - Include all fields whose type does not include None in the set of required fields + + Precondition: cls must have type hints (i.e., `type_hints` is non-empty) + """ + model_fields: dict[str, Any] = {} + for field_name, field_type in type_hints.items(): + if field_name.startswith("_"): # pragma: no cover + continue + + default = getattr(cls, field_name, _no_default) + if default is _no_default: + model_fields[field_name] = field_type + else: + model_fields[field_name] = (field_type, default) + + return create_model(cls.__name__, __config__=ConfigDict(from_attributes=True), **model_fields) + + +def _create_model_from_typeddict(td_type: type[Any]) -> type[BaseModel]: + """Create a Pydantic model from a TypedDict. + + The created model will have the same name and fields as the TypedDict. + """ + type_hints = get_type_hints(td_type) + required_keys = getattr(td_type, "__required_keys__", set(type_hints.keys())) + + model_fields: dict[str, Any] = {} + for field_name, field_type in type_hints.items(): + if field_name not in required_keys: + # For optional TypedDict fields, set default=None + # This makes them not required in the Pydantic model + # The model should use exclude_unset=True when dumping to get TypedDict semantics + model_fields[field_name] = (field_type, None) + else: + model_fields[field_name] = field_type + + return create_model(td_type.__name__, **model_fields) + + +def _create_wrapped_model(func_name: str, annotation: Any) -> type[BaseModel]: + """Create a model that wraps a type in a 'result' field. + + This is used for primitive types, generic types like list/dict, etc. + """ + model_name = f"{func_name}Output" + + return create_model(model_name, result=annotation) + + +def _create_dict_model(func_name: str, dict_annotation: Any) -> type[BaseModel]: + """Create a RootModel for dict[str, T] types.""" + + class DictModel(RootModel[dict_annotation]): + pass + + # Give it a meaningful name + DictModel.__name__ = f"{func_name}DictOutput" + DictModel.__qualname__ = f"{func_name}DictOutput" + + return DictModel + + +def _convert_to_content( + result: Any, +) -> Sequence[ContentBlock]: + """ + Convert a result to a sequence of content objects. + + Note: This conversion logic comes from previous versions of FastMCP and is being + retained for purposes of backwards compatibility. It produces different unstructured + output than the lowlevel server tool call handler, which just serializes structured + content verbatim. + """ + if result is None: # pragma: no cover + return [] + + if isinstance(result, ContentBlock): + return [result] + + if isinstance(result, Image): + return [result.to_image_content()] + + if isinstance(result, Audio): + return [result.to_audio_content()] + + if isinstance(result, list | tuple): + return list( + chain.from_iterable( + _convert_to_content(item) + for item in result # type: ignore + ) + ) + + if not isinstance(result, str): + result = pydantic_core.to_json(result, fallback=str, indent=2).decode() + + return [TextContent(type="text", text=result)] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py new file mode 100644 index 0000000..4b47d3b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py @@ -0,0 +1,43 @@ +"""Logging utilities for FastMCP.""" + +import logging +from typing import Literal + + +def get_logger(name: str) -> logging.Logger: + """Get a logger nested under MCPnamespace. + + Args: + name: the name of the logger, which will be prefixed with 'FastMCP.' + + Returns: + a configured logger instance + """ + return logging.getLogger(name) + + +def configure_logging( + level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO", +) -> None: + """Configure logging for MCP. + + Args: + level: the log level to use + """ + handlers: list[logging.Handler] = [] + try: # pragma: no cover + from rich.console import Console + from rich.logging import RichHandler + + handlers.append(RichHandler(console=Console(stderr=True), rich_tracebacks=True)) + except ImportError: # pragma: no cover + pass + + if not handlers: # pragma: no cover + handlers.append(logging.StreamHandler()) + + logging.basicConfig( + level=level, + format="%(message)s", + handlers=handlers, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py new file mode 100644 index 0000000..d6928ca --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py @@ -0,0 +1,101 @@ +"""Common types used across FastMCP.""" + +import base64 +from pathlib import Path + +from mcp.types import AudioContent, ImageContent + + +class Image: + """Helper class for returning images from tools.""" + + def __init__( + self, + path: str | Path | None = None, + data: bytes | None = None, + format: str | None = None, + ): + if path is None and data is None: # pragma: no cover + raise ValueError("Either path or data must be provided") + if path is not None and data is not None: # pragma: no cover + raise ValueError("Only one of path or data can be provided") + + self.path = Path(path) if path else None + self.data = data + self._format = format + self._mime_type = self._get_mime_type() + + def _get_mime_type(self) -> str: + """Get MIME type from format or guess from file extension.""" + if self._format: # pragma: no cover + return f"image/{self._format.lower()}" + + if self.path: + suffix = self.path.suffix.lower() + return { + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + }.get(suffix, "application/octet-stream") + return "image/png" # pragma: no cover # default for raw binary data + + def to_image_content(self) -> ImageContent: + """Convert to MCP ImageContent.""" + if self.path: + with open(self.path, "rb") as f: + data = base64.b64encode(f.read()).decode() + elif self.data is not None: # pragma: no cover + data = base64.b64encode(self.data).decode() + else: # pragma: no cover + raise ValueError("No image data available") + + return ImageContent(type="image", data=data, mimeType=self._mime_type) + + +class Audio: + """Helper class for returning audio from tools.""" + + def __init__( + self, + path: str | Path | None = None, + data: bytes | None = None, + format: str | None = None, + ): + if not bool(path) ^ bool(data): # pragma: no cover + raise ValueError("Either path or data can be provided") + + self.path = Path(path) if path else None + self.data = data + self._format = format + self._mime_type = self._get_mime_type() + + def _get_mime_type(self) -> str: + """Get MIME type from format or guess from file extension.""" + if self._format: # pragma: no cover + return f"audio/{self._format.lower()}" + + if self.path: + suffix = self.path.suffix.lower() + return { + ".wav": "audio/wav", + ".mp3": "audio/mpeg", + ".ogg": "audio/ogg", + ".flac": "audio/flac", + ".aac": "audio/aac", + ".m4a": "audio/mp4", + }.get(suffix, "application/octet-stream") + return "audio/wav" # pragma: no cover # default for raw binary data + + def to_audio_content(self) -> AudioContent: + """Convert to MCP AudioContent.""" + if self.path: + with open(self.path, "rb") as f: + data = base64.b64encode(f.read()).decode() + elif self.data is not None: # pragma: no cover + data = base64.b64encode(self.data).decode() + else: # pragma: no cover + raise ValueError("No audio data available") + + return AudioContent(type="audio", data=data, mimeType=self._mime_type) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py new file mode 100644 index 0000000..66df389 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py @@ -0,0 +1,3 @@ +from .server import NotificationOptions, Server + +__all__ = ["Server", "NotificationOptions"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py new file mode 100644 index 0000000..0e6655b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py @@ -0,0 +1,288 @@ +"""Experimental handlers for the low-level MCP server. + +WARNING: These APIs are experimental and may change without notice. +""" + +from __future__ import annotations + +import logging +from collections.abc import Awaitable, Callable +from typing import TYPE_CHECKING + +from mcp.server.experimental.task_support import TaskSupport +from mcp.server.lowlevel.func_inspection import create_call_wrapper +from mcp.shared.exceptions import McpError +from mcp.shared.experimental.tasks.helpers import cancel_task +from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore +from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue, TaskMessageQueue +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.types import ( + INVALID_PARAMS, + CancelTaskRequest, + CancelTaskResult, + ErrorData, + GetTaskPayloadRequest, + GetTaskPayloadResult, + GetTaskRequest, + GetTaskResult, + ListTasksRequest, + ListTasksResult, + ServerCapabilities, + ServerResult, + ServerTasksCapability, + ServerTasksRequestsCapability, + TasksCancelCapability, + TasksListCapability, + TasksToolsCapability, +) + +if TYPE_CHECKING: + from mcp.server.lowlevel.server import Server + +logger = logging.getLogger(__name__) + + +class ExperimentalHandlers: + """Experimental request/notification handlers. + + WARNING: These APIs are experimental and may change without notice. + """ + + def __init__( + self, + server: Server, + request_handlers: dict[type, Callable[..., Awaitable[ServerResult]]], + notification_handlers: dict[type, Callable[..., Awaitable[None]]], + ): + self._server = server + self._request_handlers = request_handlers + self._notification_handlers = notification_handlers + self._task_support: TaskSupport | None = None + + @property + def task_support(self) -> TaskSupport | None: + """Get the task support configuration, if enabled.""" + return self._task_support + + def update_capabilities(self, capabilities: ServerCapabilities) -> None: + # Only add tasks capability if handlers are registered + if not any( + req_type in self._request_handlers + for req_type in [GetTaskRequest, ListTasksRequest, CancelTaskRequest, GetTaskPayloadRequest] + ): + return + + capabilities.tasks = ServerTasksCapability() + if ListTasksRequest in self._request_handlers: + capabilities.tasks.list = TasksListCapability() + if CancelTaskRequest in self._request_handlers: + capabilities.tasks.cancel = TasksCancelCapability() + + capabilities.tasks.requests = ServerTasksRequestsCapability( + tools=TasksToolsCapability() + ) # assuming always supported for now + + def enable_tasks( + self, + store: TaskStore | None = None, + queue: TaskMessageQueue | None = None, + ) -> TaskSupport: + """ + Enable experimental task support. + + This sets up the task infrastructure and auto-registers default handlers + for tasks/get, tasks/result, tasks/list, and tasks/cancel. + + Args: + store: Custom TaskStore implementation (defaults to InMemoryTaskStore) + queue: Custom TaskMessageQueue implementation (defaults to InMemoryTaskMessageQueue) + + Returns: + The TaskSupport configuration object + + Example: + # Simple in-memory setup + server.experimental.enable_tasks() + + # Custom store/queue for distributed systems + server.experimental.enable_tasks( + store=RedisTaskStore(redis_url), + queue=RedisTaskMessageQueue(redis_url), + ) + + WARNING: This API is experimental and may change without notice. + """ + if store is None: + store = InMemoryTaskStore() + if queue is None: + queue = InMemoryTaskMessageQueue() + + self._task_support = TaskSupport(store=store, queue=queue) + + # Auto-register default handlers + self._register_default_task_handlers() + + return self._task_support + + def _register_default_task_handlers(self) -> None: + """Register default handlers for task operations.""" + assert self._task_support is not None + support = self._task_support + + # Register get_task handler if not already registered + if GetTaskRequest not in self._request_handlers: + + async def _default_get_task(req: GetTaskRequest) -> ServerResult: + task = await support.store.get_task(req.params.taskId) + if task is None: + raise McpError( + ErrorData( + code=INVALID_PARAMS, + message=f"Task not found: {req.params.taskId}", + ) + ) + return ServerResult( + GetTaskResult( + taskId=task.taskId, + status=task.status, + statusMessage=task.statusMessage, + createdAt=task.createdAt, + lastUpdatedAt=task.lastUpdatedAt, + ttl=task.ttl, + pollInterval=task.pollInterval, + ) + ) + + self._request_handlers[GetTaskRequest] = _default_get_task + + # Register get_task_result handler if not already registered + if GetTaskPayloadRequest not in self._request_handlers: + + async def _default_get_task_result(req: GetTaskPayloadRequest) -> ServerResult: + ctx = self._server.request_context + result = await support.handler.handle(req, ctx.session, ctx.request_id) + return ServerResult(result) + + self._request_handlers[GetTaskPayloadRequest] = _default_get_task_result + + # Register list_tasks handler if not already registered + if ListTasksRequest not in self._request_handlers: + + async def _default_list_tasks(req: ListTasksRequest) -> ServerResult: + cursor = req.params.cursor if req.params else None + tasks, next_cursor = await support.store.list_tasks(cursor) + return ServerResult(ListTasksResult(tasks=tasks, nextCursor=next_cursor)) + + self._request_handlers[ListTasksRequest] = _default_list_tasks + + # Register cancel_task handler if not already registered + if CancelTaskRequest not in self._request_handlers: + + async def _default_cancel_task(req: CancelTaskRequest) -> ServerResult: + result = await cancel_task(support.store, req.params.taskId) + return ServerResult(result) + + self._request_handlers[CancelTaskRequest] = _default_cancel_task + + def list_tasks( + self, + ) -> Callable[ + [Callable[[ListTasksRequest], Awaitable[ListTasksResult]]], + Callable[[ListTasksRequest], Awaitable[ListTasksResult]], + ]: + """Register a handler for listing tasks. + + WARNING: This API is experimental and may change without notice. + """ + + def decorator( + func: Callable[[ListTasksRequest], Awaitable[ListTasksResult]], + ) -> Callable[[ListTasksRequest], Awaitable[ListTasksResult]]: + logger.debug("Registering handler for ListTasksRequest") + wrapper = create_call_wrapper(func, ListTasksRequest) + + async def handler(req: ListTasksRequest) -> ServerResult: + result = await wrapper(req) + return ServerResult(result) + + self._request_handlers[ListTasksRequest] = handler + return func + + return decorator + + def get_task( + self, + ) -> Callable[ + [Callable[[GetTaskRequest], Awaitable[GetTaskResult]]], Callable[[GetTaskRequest], Awaitable[GetTaskResult]] + ]: + """Register a handler for getting task status. + + WARNING: This API is experimental and may change without notice. + """ + + def decorator( + func: Callable[[GetTaskRequest], Awaitable[GetTaskResult]], + ) -> Callable[[GetTaskRequest], Awaitable[GetTaskResult]]: + logger.debug("Registering handler for GetTaskRequest") + wrapper = create_call_wrapper(func, GetTaskRequest) + + async def handler(req: GetTaskRequest) -> ServerResult: + result = await wrapper(req) + return ServerResult(result) + + self._request_handlers[GetTaskRequest] = handler + return func + + return decorator + + def get_task_result( + self, + ) -> Callable[ + [Callable[[GetTaskPayloadRequest], Awaitable[GetTaskPayloadResult]]], + Callable[[GetTaskPayloadRequest], Awaitable[GetTaskPayloadResult]], + ]: + """Register a handler for getting task results/payload. + + WARNING: This API is experimental and may change without notice. + """ + + def decorator( + func: Callable[[GetTaskPayloadRequest], Awaitable[GetTaskPayloadResult]], + ) -> Callable[[GetTaskPayloadRequest], Awaitable[GetTaskPayloadResult]]: + logger.debug("Registering handler for GetTaskPayloadRequest") + wrapper = create_call_wrapper(func, GetTaskPayloadRequest) + + async def handler(req: GetTaskPayloadRequest) -> ServerResult: + result = await wrapper(req) + return ServerResult(result) + + self._request_handlers[GetTaskPayloadRequest] = handler + return func + + return decorator + + def cancel_task( + self, + ) -> Callable[ + [Callable[[CancelTaskRequest], Awaitable[CancelTaskResult]]], + Callable[[CancelTaskRequest], Awaitable[CancelTaskResult]], + ]: + """Register a handler for cancelling tasks. + + WARNING: This API is experimental and may change without notice. + """ + + def decorator( + func: Callable[[CancelTaskRequest], Awaitable[CancelTaskResult]], + ) -> Callable[[CancelTaskRequest], Awaitable[CancelTaskResult]]: + logger.debug("Registering handler for CancelTaskRequest") + wrapper = create_call_wrapper(func, CancelTaskRequest) + + async def handler(req: CancelTaskRequest) -> ServerResult: + result = await wrapper(req) + return ServerResult(result) + + self._request_handlers[CancelTaskRequest] = handler + return func + + return decorator diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py new file mode 100644 index 0000000..6231aa8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py @@ -0,0 +1,54 @@ +import inspect +from collections.abc import Callable +from typing import Any, TypeVar, get_type_hints + +T = TypeVar("T") +R = TypeVar("R") + + +def create_call_wrapper(func: Callable[..., R], request_type: type[T]) -> Callable[[T], R]: + """ + Create a wrapper function that knows how to call func with the request object. + + Returns a wrapper function that takes the request and calls func appropriately. + + The wrapper handles three calling patterns: + 1. Positional-only parameter typed as request_type (no default): func(req) + 2. Positional/keyword parameter typed as request_type (no default): func(**{param_name: req}) + 3. No request parameter or parameter with default: func() + """ + try: + sig = inspect.signature(func) + type_hints = get_type_hints(func) + except (ValueError, TypeError, NameError): # pragma: no cover + return lambda _: func() + + # Check for positional-only parameter typed as request_type + for param_name, param in sig.parameters.items(): + if param.kind == inspect.Parameter.POSITIONAL_ONLY: + param_type = type_hints.get(param_name) + if param_type == request_type: # pragma: no branch + # Check if it has a default - if so, treat as old style + if param.default is not inspect.Parameter.empty: # pragma: no cover + return lambda _: func() + # Found positional-only parameter with correct type and no default + return lambda req: func(req) + + # Check for any positional/keyword parameter typed as request_type + for param_name, param in sig.parameters.items(): + if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY): # pragma: no branch + param_type = type_hints.get(param_name) + if param_type == request_type: + # Check if it has a default - if so, treat as old style + if param.default is not inspect.Parameter.empty: # pragma: no cover + return lambda _: func() + + # Found keyword parameter with correct type and no default + # Need to capture param_name in closure properly + def make_keyword_wrapper(name: str) -> Callable[[Any], Any]: + return lambda req: func(**{name: req}) + + return make_keyword_wrapper(param_name) + + # No request parameter found - use old style + return lambda _: func() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py new file mode 100644 index 0000000..fecc716 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from typing import Any + + +@dataclass +class ReadResourceContents: + """Contents returned from a read_resource call.""" + + content: str | bytes + mime_type: str | None = None + meta: dict[str, Any] | None = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py new file mode 100644 index 0000000..6745362 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py @@ -0,0 +1,803 @@ +""" +MCP Server Module + +This module provides a framework for creating an MCP (Model Context Protocol) server. +It allows you to easily define and handle various types of requests and notifications +in an asynchronous manner. + +Usage: +1. Create a Server instance: + server = Server("your_server_name") + +2. Define request handlers using decorators: + @server.list_prompts() + async def handle_list_prompts(request: types.ListPromptsRequest) -> types.ListPromptsResult: + # Implementation + + @server.get_prompt() + async def handle_get_prompt( + name: str, arguments: dict[str, str] | None + ) -> types.GetPromptResult: + # Implementation + + @server.list_tools() + async def handle_list_tools(request: types.ListToolsRequest) -> types.ListToolsResult: + # Implementation + + @server.call_tool() + async def handle_call_tool( + name: str, arguments: dict | None + ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + # Implementation + + @server.list_resource_templates() + async def handle_list_resource_templates() -> list[types.ResourceTemplate]: + # Implementation + +3. Define notification handlers if needed: + @server.progress_notification() + async def handle_progress( + progress_token: str | int, progress: float, total: float | None, + message: str | None + ) -> None: + # Implementation + +4. Run the server: + async def main(): + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="your_server_name", + server_version="your_version", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + asyncio.run(main()) + +The Server class provides methods to register handlers for various MCP requests and +notifications. It automatically manages the request context and handles incoming +messages from the client. +""" + +from __future__ import annotations as _annotations + +import base64 +import contextvars +import json +import logging +import warnings +from collections.abc import AsyncIterator, Awaitable, Callable, Iterable +from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager +from typing import Any, Generic, TypeAlias, cast + +import anyio +import jsonschema +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import AnyUrl +from typing_extensions import TypeVar + +import mcp.types as types +from mcp.server.experimental.request_context import Experimental +from mcp.server.lowlevel.experimental import ExperimentalHandlers +from mcp.server.lowlevel.func_inspection import create_call_wrapper +from mcp.server.lowlevel.helper_types import ReadResourceContents +from mcp.server.models import InitializationOptions +from mcp.server.session import ServerSession +from mcp.shared.context import RequestContext +from mcp.shared.exceptions import McpError, UrlElicitationRequiredError +from mcp.shared.message import ServerMessageMetadata, SessionMessage +from mcp.shared.session import RequestResponder +from mcp.shared.tool_name_validation import validate_and_warn_tool_name + +logger = logging.getLogger(__name__) + +LifespanResultT = TypeVar("LifespanResultT", default=Any) +RequestT = TypeVar("RequestT", default=Any) + +# type aliases for tool call results +StructuredContent: TypeAlias = dict[str, Any] +UnstructuredContent: TypeAlias = Iterable[types.ContentBlock] +CombinationContent: TypeAlias = tuple[UnstructuredContent, StructuredContent] + +# This will be properly typed in each Server instance's context +request_ctx: contextvars.ContextVar[RequestContext[ServerSession, Any, Any]] = contextvars.ContextVar("request_ctx") + + +class NotificationOptions: + def __init__( + self, + prompts_changed: bool = False, + resources_changed: bool = False, + tools_changed: bool = False, + ): + self.prompts_changed = prompts_changed + self.resources_changed = resources_changed + self.tools_changed = tools_changed + + +@asynccontextmanager +async def lifespan(_: Server[LifespanResultT, RequestT]) -> AsyncIterator[dict[str, Any]]: + """Default lifespan context manager that does nothing. + + Args: + server: The server instance this lifespan is managing + + Returns: + An empty context object + """ + yield {} + + +class Server(Generic[LifespanResultT, RequestT]): + def __init__( + self, + name: str, + version: str | None = None, + instructions: str | None = None, + website_url: str | None = None, + icons: list[types.Icon] | None = None, + lifespan: Callable[ + [Server[LifespanResultT, RequestT]], + AbstractAsyncContextManager[LifespanResultT], + ] = lifespan, + ): + self.name = name + self.version = version + self.instructions = instructions + self.website_url = website_url + self.icons = icons + self.lifespan = lifespan + self.request_handlers: dict[type, Callable[..., Awaitable[types.ServerResult]]] = { + types.PingRequest: _ping_handler, + } + self.notification_handlers: dict[type, Callable[..., Awaitable[None]]] = {} + self._tool_cache: dict[str, types.Tool] = {} + self._experimental_handlers: ExperimentalHandlers | None = None + logger.debug("Initializing server %r", name) + + def create_initialization_options( + self, + notification_options: NotificationOptions | None = None, + experimental_capabilities: dict[str, dict[str, Any]] | None = None, + ) -> InitializationOptions: + """Create initialization options from this server instance.""" + + def pkg_version(package: str) -> str: + try: + from importlib.metadata import version + + return version(package) + except Exception: # pragma: no cover + pass + + return "unknown" # pragma: no cover + + return InitializationOptions( + server_name=self.name, + server_version=self.version if self.version else pkg_version("mcp"), + capabilities=self.get_capabilities( + notification_options or NotificationOptions(), + experimental_capabilities or {}, + ), + instructions=self.instructions, + website_url=self.website_url, + icons=self.icons, + ) + + def get_capabilities( + self, + notification_options: NotificationOptions, + experimental_capabilities: dict[str, dict[str, Any]], + ) -> types.ServerCapabilities: + """Convert existing handlers to a ServerCapabilities object.""" + prompts_capability = None + resources_capability = None + tools_capability = None + logging_capability = None + completions_capability = None + + # Set prompt capabilities if handler exists + if types.ListPromptsRequest in self.request_handlers: + prompts_capability = types.PromptsCapability(listChanged=notification_options.prompts_changed) + + # Set resource capabilities if handler exists + if types.ListResourcesRequest in self.request_handlers: + resources_capability = types.ResourcesCapability( + subscribe=False, listChanged=notification_options.resources_changed + ) + + # Set tool capabilities if handler exists + if types.ListToolsRequest in self.request_handlers: + tools_capability = types.ToolsCapability(listChanged=notification_options.tools_changed) + + # Set logging capabilities if handler exists + if types.SetLevelRequest in self.request_handlers: # pragma: no cover + logging_capability = types.LoggingCapability() + + # Set completions capabilities if handler exists + if types.CompleteRequest in self.request_handlers: + completions_capability = types.CompletionsCapability() + + capabilities = types.ServerCapabilities( + prompts=prompts_capability, + resources=resources_capability, + tools=tools_capability, + logging=logging_capability, + experimental=experimental_capabilities, + completions=completions_capability, + ) + if self._experimental_handlers: + self._experimental_handlers.update_capabilities(capabilities) + return capabilities + + @property + def request_context( + self, + ) -> RequestContext[ServerSession, LifespanResultT, RequestT]: + """If called outside of a request context, this will raise a LookupError.""" + return request_ctx.get() + + @property + def experimental(self) -> ExperimentalHandlers: + """Experimental APIs for tasks and other features. + + WARNING: These APIs are experimental and may change without notice. + """ + + # We create this inline so we only add these capabilities _if_ they're actually used + if self._experimental_handlers is None: + self._experimental_handlers = ExperimentalHandlers(self, self.request_handlers, self.notification_handlers) + return self._experimental_handlers + + def list_prompts(self): + def decorator( + func: Callable[[], Awaitable[list[types.Prompt]]] + | Callable[[types.ListPromptsRequest], Awaitable[types.ListPromptsResult]], + ): + logger.debug("Registering handler for PromptListRequest") + + wrapper = create_call_wrapper(func, types.ListPromptsRequest) + + async def handler(req: types.ListPromptsRequest): + result = await wrapper(req) + # Handle both old style (list[Prompt]) and new style (ListPromptsResult) + if isinstance(result, types.ListPromptsResult): + return types.ServerResult(result) + else: + # Old style returns list[Prompt] + return types.ServerResult(types.ListPromptsResult(prompts=result)) + + self.request_handlers[types.ListPromptsRequest] = handler + return func + + return decorator + + def get_prompt(self): + def decorator( + func: Callable[[str, dict[str, str] | None], Awaitable[types.GetPromptResult]], + ): + logger.debug("Registering handler for GetPromptRequest") + + async def handler(req: types.GetPromptRequest): + prompt_get = await func(req.params.name, req.params.arguments) + return types.ServerResult(prompt_get) + + self.request_handlers[types.GetPromptRequest] = handler + return func + + return decorator + + def list_resources(self): + def decorator( + func: Callable[[], Awaitable[list[types.Resource]]] + | Callable[[types.ListResourcesRequest], Awaitable[types.ListResourcesResult]], + ): + logger.debug("Registering handler for ListResourcesRequest") + + wrapper = create_call_wrapper(func, types.ListResourcesRequest) + + async def handler(req: types.ListResourcesRequest): + result = await wrapper(req) + # Handle both old style (list[Resource]) and new style (ListResourcesResult) + if isinstance(result, types.ListResourcesResult): + return types.ServerResult(result) + else: + # Old style returns list[Resource] + return types.ServerResult(types.ListResourcesResult(resources=result)) + + self.request_handlers[types.ListResourcesRequest] = handler + return func + + return decorator + + def list_resource_templates(self): + def decorator(func: Callable[[], Awaitable[list[types.ResourceTemplate]]]): + logger.debug("Registering handler for ListResourceTemplatesRequest") + + async def handler(_: Any): + templates = await func() + return types.ServerResult(types.ListResourceTemplatesResult(resourceTemplates=templates)) + + self.request_handlers[types.ListResourceTemplatesRequest] = handler + return func + + return decorator + + def read_resource(self): + def decorator( + func: Callable[[AnyUrl], Awaitable[str | bytes | Iterable[ReadResourceContents]]], + ): + logger.debug("Registering handler for ReadResourceRequest") + + async def handler(req: types.ReadResourceRequest): + result = await func(req.params.uri) + + def create_content(data: str | bytes, mime_type: str | None, meta: dict[str, Any] | None = None): + # Note: ResourceContents uses Field(alias="_meta"), so we must use the alias key + meta_kwargs: dict[str, Any] = {"_meta": meta} if meta is not None else {} + match data: + case str() as data: + return types.TextResourceContents( + uri=req.params.uri, + text=data, + mimeType=mime_type or "text/plain", + **meta_kwargs, + ) + case bytes() as data: # pragma: no cover + return types.BlobResourceContents( + uri=req.params.uri, + blob=base64.b64encode(data).decode(), + mimeType=mime_type or "application/octet-stream", + **meta_kwargs, + ) + + match result: + case str() | bytes() as data: # pragma: no cover + warnings.warn( + "Returning str or bytes from read_resource is deprecated. " + "Use Iterable[ReadResourceContents] instead.", + DeprecationWarning, + stacklevel=2, + ) + content = create_content(data, None) + case Iterable() as contents: + contents_list = [ + create_content( + content_item.content, content_item.mime_type, getattr(content_item, "meta", None) + ) + for content_item in contents + ] + return types.ServerResult( + types.ReadResourceResult( + contents=contents_list, + ) + ) + case _: # pragma: no cover + raise ValueError(f"Unexpected return type from read_resource: {type(result)}") + + return types.ServerResult( # pragma: no cover + types.ReadResourceResult( + contents=[content], + ) + ) + + self.request_handlers[types.ReadResourceRequest] = handler + return func + + return decorator + + def set_logging_level(self): # pragma: no cover + def decorator(func: Callable[[types.LoggingLevel], Awaitable[None]]): + logger.debug("Registering handler for SetLevelRequest") + + async def handler(req: types.SetLevelRequest): + await func(req.params.level) + return types.ServerResult(types.EmptyResult()) + + self.request_handlers[types.SetLevelRequest] = handler + return func + + return decorator + + def subscribe_resource(self): # pragma: no cover + def decorator(func: Callable[[AnyUrl], Awaitable[None]]): + logger.debug("Registering handler for SubscribeRequest") + + async def handler(req: types.SubscribeRequest): + await func(req.params.uri) + return types.ServerResult(types.EmptyResult()) + + self.request_handlers[types.SubscribeRequest] = handler + return func + + return decorator + + def unsubscribe_resource(self): # pragma: no cover + def decorator(func: Callable[[AnyUrl], Awaitable[None]]): + logger.debug("Registering handler for UnsubscribeRequest") + + async def handler(req: types.UnsubscribeRequest): + await func(req.params.uri) + return types.ServerResult(types.EmptyResult()) + + self.request_handlers[types.UnsubscribeRequest] = handler + return func + + return decorator + + def list_tools(self): + def decorator( + func: Callable[[], Awaitable[list[types.Tool]]] + | Callable[[types.ListToolsRequest], Awaitable[types.ListToolsResult]], + ): + logger.debug("Registering handler for ListToolsRequest") + + wrapper = create_call_wrapper(func, types.ListToolsRequest) + + async def handler(req: types.ListToolsRequest): + result = await wrapper(req) + + # Handle both old style (list[Tool]) and new style (ListToolsResult) + if isinstance(result, types.ListToolsResult): # pragma: no cover + # Refresh the tool cache with returned tools + for tool in result.tools: + validate_and_warn_tool_name(tool.name) + self._tool_cache[tool.name] = tool + return types.ServerResult(result) + else: + # Old style returns list[Tool] + # Clear and refresh the entire tool cache + self._tool_cache.clear() + for tool in result: + validate_and_warn_tool_name(tool.name) + self._tool_cache[tool.name] = tool + return types.ServerResult(types.ListToolsResult(tools=result)) + + self.request_handlers[types.ListToolsRequest] = handler + return func + + return decorator + + def _make_error_result(self, error_message: str) -> types.ServerResult: + """Create a ServerResult with an error CallToolResult.""" + return types.ServerResult( + types.CallToolResult( + content=[types.TextContent(type="text", text=error_message)], + isError=True, + ) + ) + + async def _get_cached_tool_definition(self, tool_name: str) -> types.Tool | None: + """Get tool definition from cache, refreshing if necessary. + + Returns the Tool object if found, None otherwise. + """ + if tool_name not in self._tool_cache: + if types.ListToolsRequest in self.request_handlers: + logger.debug("Tool cache miss for %s, refreshing cache", tool_name) + await self.request_handlers[types.ListToolsRequest](None) + + tool = self._tool_cache.get(tool_name) + if tool is None: + logger.warning("Tool '%s' not listed, no validation will be performed", tool_name) + + return tool + + def call_tool(self, *, validate_input: bool = True): + """Register a tool call handler. + + Args: + validate_input: If True, validates input against inputSchema. Default is True. + + The handler validates input against inputSchema (if validate_input=True), calls the tool function, + and builds a CallToolResult with the results: + - Unstructured content (iterable of ContentBlock): returned in content + - Structured content (dict): returned in structuredContent, serialized JSON text returned in content + - Both: returned in content and structuredContent + + If outputSchema is defined, validates structuredContent or errors if missing. + """ + + def decorator( + func: Callable[ + ..., + Awaitable[ + UnstructuredContent + | StructuredContent + | CombinationContent + | types.CallToolResult + | types.CreateTaskResult + ], + ], + ): + logger.debug("Registering handler for CallToolRequest") + + async def handler(req: types.CallToolRequest): + try: + tool_name = req.params.name + arguments = req.params.arguments or {} + tool = await self._get_cached_tool_definition(tool_name) + + # input validation + if validate_input and tool: + try: + jsonschema.validate(instance=arguments, schema=tool.inputSchema) + except jsonschema.ValidationError as e: + return self._make_error_result(f"Input validation error: {e.message}") + + # tool call + results = await func(tool_name, arguments) + + # output normalization + unstructured_content: UnstructuredContent + maybe_structured_content: StructuredContent | None + if isinstance(results, types.CallToolResult): + return types.ServerResult(results) + elif isinstance(results, types.CreateTaskResult): + # Task-augmented execution returns task info instead of result + return types.ServerResult(results) + elif isinstance(results, tuple) and len(results) == 2: + # tool returned both structured and unstructured content + unstructured_content, maybe_structured_content = cast(CombinationContent, results) + elif isinstance(results, dict): + # tool returned structured content only + maybe_structured_content = cast(StructuredContent, results) + unstructured_content = [types.TextContent(type="text", text=json.dumps(results, indent=2))] + elif hasattr(results, "__iter__"): # pragma: no cover + # tool returned unstructured content only + unstructured_content = cast(UnstructuredContent, results) + maybe_structured_content = None + else: # pragma: no cover + return self._make_error_result(f"Unexpected return type from tool: {type(results).__name__}") + + # output validation + if tool and tool.outputSchema is not None: + if maybe_structured_content is None: + return self._make_error_result( + "Output validation error: outputSchema defined but no structured output returned" + ) + else: + try: + jsonschema.validate(instance=maybe_structured_content, schema=tool.outputSchema) + except jsonschema.ValidationError as e: + return self._make_error_result(f"Output validation error: {e.message}") + + # result + return types.ServerResult( + types.CallToolResult( + content=list(unstructured_content), + structuredContent=maybe_structured_content, + isError=False, + ) + ) + except UrlElicitationRequiredError: + # Re-raise UrlElicitationRequiredError so it can be properly handled + # by _handle_request, which converts it to an error response with code -32042 + raise + except Exception as e: + return self._make_error_result(str(e)) + + self.request_handlers[types.CallToolRequest] = handler + return func + + return decorator + + def progress_notification(self): + def decorator( + func: Callable[[str | int, float, float | None, str | None], Awaitable[None]], + ): + logger.debug("Registering handler for ProgressNotification") + + async def handler(req: types.ProgressNotification): + await func( + req.params.progressToken, + req.params.progress, + req.params.total, + req.params.message, + ) + + self.notification_handlers[types.ProgressNotification] = handler + return func + + return decorator + + def completion(self): + """Provides completions for prompts and resource templates""" + + def decorator( + func: Callable[ + [ + types.PromptReference | types.ResourceTemplateReference, + types.CompletionArgument, + types.CompletionContext | None, + ], + Awaitable[types.Completion | None], + ], + ): + logger.debug("Registering handler for CompleteRequest") + + async def handler(req: types.CompleteRequest): + completion = await func(req.params.ref, req.params.argument, req.params.context) + return types.ServerResult( + types.CompleteResult( + completion=completion + if completion is not None + else types.Completion(values=[], total=None, hasMore=None), + ) + ) + + self.request_handlers[types.CompleteRequest] = handler + return func + + return decorator + + async def run( + self, + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception], + write_stream: MemoryObjectSendStream[SessionMessage], + initialization_options: InitializationOptions, + # When False, exceptions are returned as messages to the client. + # When True, exceptions are raised, which will cause the server to shut down + # but also make tracing exceptions much easier during testing and when using + # in-process servers. + raise_exceptions: bool = False, + # When True, the server is stateless and + # clients can perform initialization with any node. The client must still follow + # the initialization lifecycle, but can do so with any available node + # rather than requiring initialization for each connection. + stateless: bool = False, + ): + async with AsyncExitStack() as stack: + lifespan_context = await stack.enter_async_context(self.lifespan(self)) + session = await stack.enter_async_context( + ServerSession( + read_stream, + write_stream, + initialization_options, + stateless=stateless, + ) + ) + + # Configure task support for this session if enabled + task_support = self._experimental_handlers.task_support if self._experimental_handlers else None + if task_support is not None: + task_support.configure_session(session) + await stack.enter_async_context(task_support.run()) + + async with anyio.create_task_group() as tg: + async for message in session.incoming_messages: + logger.debug("Received message: %s", message) + + tg.start_soon( + self._handle_message, + message, + session, + lifespan_context, + raise_exceptions, + ) + + async def _handle_message( + self, + message: RequestResponder[types.ClientRequest, types.ServerResult] | types.ClientNotification | Exception, + session: ServerSession, + lifespan_context: LifespanResultT, + raise_exceptions: bool = False, + ): + with warnings.catch_warnings(record=True) as w: + match message: + case RequestResponder(request=types.ClientRequest(root=req)) as responder: + with responder: + await self._handle_request(message, req, session, lifespan_context, raise_exceptions) + case types.ClientNotification(root=notify): + await self._handle_notification(notify) + case Exception(): # pragma: no cover + logger.error(f"Received exception from stream: {message}") + await session.send_log_message( + level="error", + data="Internal Server Error", + logger="mcp.server.exception_handler", + ) + if raise_exceptions: + raise message + + for warning in w: # pragma: no cover + logger.info("Warning: %s: %s", warning.category.__name__, warning.message) + + async def _handle_request( + self, + message: RequestResponder[types.ClientRequest, types.ServerResult], + req: types.ClientRequestType, + session: ServerSession, + lifespan_context: LifespanResultT, + raise_exceptions: bool, + ): + logger.info("Processing request of type %s", type(req).__name__) + + if handler := self.request_handlers.get(type(req)): + logger.debug("Dispatching request of type %s", type(req).__name__) + + token = None + try: + # Extract request context and close_sse_stream from message metadata + request_data = None + close_sse_stream_cb = None + close_standalone_sse_stream_cb = None + if message.message_metadata is not None and isinstance( + message.message_metadata, ServerMessageMetadata + ): # pragma: no cover + request_data = message.message_metadata.request_context + close_sse_stream_cb = message.message_metadata.close_sse_stream + close_standalone_sse_stream_cb = message.message_metadata.close_standalone_sse_stream + + # Set our global state that can be retrieved via + # app.get_request_context() + client_capabilities = session.client_params.capabilities if session.client_params else None + task_support = self._experimental_handlers.task_support if self._experimental_handlers else None + # Get task metadata from request params if present + task_metadata = None + if hasattr(req, "params") and req.params is not None: + task_metadata = getattr(req.params, "task", None) + token = request_ctx.set( + RequestContext( + message.request_id, + message.request_meta, + session, + lifespan_context, + Experimental( + task_metadata=task_metadata, + _client_capabilities=client_capabilities, + _session=session, + _task_support=task_support, + ), + request=request_data, + close_sse_stream=close_sse_stream_cb, + close_standalone_sse_stream=close_standalone_sse_stream_cb, + ) + ) + response = await handler(req) + except McpError as err: # pragma: no cover + response = err.error + except anyio.get_cancelled_exc_class(): # pragma: no cover + logger.info( + "Request %s cancelled - duplicate response suppressed", + message.request_id, + ) + return + except Exception as err: # pragma: no cover + if raise_exceptions: + raise err + response = types.ErrorData(code=0, message=str(err), data=None) + finally: + # Reset the global state after we are done + if token is not None: # pragma: no branch + request_ctx.reset(token) + + await message.respond(response) + else: # pragma: no cover + await message.respond( + types.ErrorData( + code=types.METHOD_NOT_FOUND, + message="Method not found", + ) + ) + + logger.debug("Response sent") + + async def _handle_notification(self, notify: Any): + if handler := self.notification_handlers.get(type(notify)): # type: ignore + logger.debug("Dispatching notification of type %s", type(notify).__name__) + + try: + await handler(notify) + except Exception: # pragma: no cover + logger.exception("Uncaught exception in notification handler") + + +async def _ping_handler(request: types.PingRequest) -> types.ServerResult: + return types.ServerResult(types.EmptyResult()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py new file mode 100644 index 0000000..ddf716c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/models.py @@ -0,0 +1,20 @@ +""" +This module provides simpler types to use with the server for managing prompts +and tools. +""" + +from pydantic import BaseModel + +from mcp.types import ( + Icon, + ServerCapabilities, +) + + +class InitializationOptions(BaseModel): + server_name: str + server_version: str + capabilities: ServerCapabilities + instructions: str | None = None + website_url: str | None = None + icons: list[Icon] | None = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py new file mode 100644 index 0000000..8f0baa3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/session.py @@ -0,0 +1,691 @@ +""" +ServerSession Module + +This module provides the ServerSession class, which manages communication between the +server and client in the MCP (Model Context Protocol) framework. It is most commonly +used in MCP servers to interact with the client. + +Common usage pattern: +``` + server = Server(name) + + @server.call_tool() + async def handle_tool_call(ctx: RequestContext, arguments: dict[str, Any]) -> Any: + # Check client capabilities before proceeding + if ctx.session.check_client_capability( + types.ClientCapabilities(experimental={"advanced_tools": dict()}) + ): + # Perform advanced tool operations + result = await perform_advanced_tool_operation(arguments) + else: + # Fall back to basic tool operations + result = await perform_basic_tool_operation(arguments) + + return result + + @server.list_prompts() + async def handle_list_prompts(ctx: RequestContext) -> list[types.Prompt]: + # Access session for any necessary checks or operations + if ctx.session.client_params: + # Customize prompts based on client initialization parameters + return generate_custom_prompts(ctx.session.client_params) + else: + return default_prompts +``` + +The ServerSession class is typically used internally by the Server class and should not +be instantiated directly by users of the MCP framework. +""" + +from enum import Enum +from typing import Any, TypeVar, overload + +import anyio +import anyio.lowlevel +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import AnyUrl + +import mcp.types as types +from mcp.server.experimental.session_features import ExperimentalServerSessionFeatures +from mcp.server.models import InitializationOptions +from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages +from mcp.shared.experimental.tasks.capabilities import check_tasks_capability +from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY +from mcp.shared.message import ServerMessageMetadata, SessionMessage +from mcp.shared.session import ( + BaseSession, + RequestResponder, +) +from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS + + +class InitializationState(Enum): + NotInitialized = 1 + Initializing = 2 + Initialized = 3 + + +ServerSessionT = TypeVar("ServerSessionT", bound="ServerSession") + +ServerRequestResponder = ( + RequestResponder[types.ClientRequest, types.ServerResult] | types.ClientNotification | Exception +) + + +class ServerSession( + BaseSession[ + types.ServerRequest, + types.ServerNotification, + types.ServerResult, + types.ClientRequest, + types.ClientNotification, + ] +): + _initialized: InitializationState = InitializationState.NotInitialized + _client_params: types.InitializeRequestParams | None = None + _experimental_features: ExperimentalServerSessionFeatures | None = None + + def __init__( + self, + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception], + write_stream: MemoryObjectSendStream[SessionMessage], + init_options: InitializationOptions, + stateless: bool = False, + ) -> None: + super().__init__(read_stream, write_stream, types.ClientRequest, types.ClientNotification) + self._initialization_state = ( + InitializationState.Initialized if stateless else InitializationState.NotInitialized + ) + + self._init_options = init_options + self._incoming_message_stream_writer, self._incoming_message_stream_reader = anyio.create_memory_object_stream[ + ServerRequestResponder + ](0) + self._exit_stack.push_async_callback(lambda: self._incoming_message_stream_reader.aclose()) + + @property + def client_params(self) -> types.InitializeRequestParams | None: + return self._client_params # pragma: no cover + + @property + def experimental(self) -> ExperimentalServerSessionFeatures: + """Experimental APIs for server→client task operations. + + WARNING: These APIs are experimental and may change without notice. + """ + if self._experimental_features is None: + self._experimental_features = ExperimentalServerSessionFeatures(self) + return self._experimental_features + + def check_client_capability(self, capability: types.ClientCapabilities) -> bool: # pragma: no cover + """Check if the client supports a specific capability.""" + if self._client_params is None: + return False + + client_caps = self._client_params.capabilities + + if capability.roots is not None: + if client_caps.roots is None: + return False + if capability.roots.listChanged and not client_caps.roots.listChanged: + return False + + if capability.sampling is not None: + if client_caps.sampling is None: + return False + if capability.sampling.context is not None and client_caps.sampling.context is None: + return False + if capability.sampling.tools is not None and client_caps.sampling.tools is None: + return False + + if capability.elicitation is not None and client_caps.elicitation is None: + return False + + if capability.experimental is not None: + if client_caps.experimental is None: + return False + for exp_key, exp_value in capability.experimental.items(): + if exp_key not in client_caps.experimental or client_caps.experimental[exp_key] != exp_value: + return False + + if capability.tasks is not None: + if client_caps.tasks is None: + return False + if not check_tasks_capability(capability.tasks, client_caps.tasks): + return False + + return True + + async def _receive_loop(self) -> None: + async with self._incoming_message_stream_writer: + await super()._receive_loop() + + async def _received_request(self, responder: RequestResponder[types.ClientRequest, types.ServerResult]): + match responder.request.root: + case types.InitializeRequest(params=params): + requested_version = params.protocolVersion + self._initialization_state = InitializationState.Initializing + self._client_params = params + with responder: + await responder.respond( + types.ServerResult( + types.InitializeResult( + protocolVersion=requested_version + if requested_version in SUPPORTED_PROTOCOL_VERSIONS + else types.LATEST_PROTOCOL_VERSION, + capabilities=self._init_options.capabilities, + serverInfo=types.Implementation( + name=self._init_options.server_name, + version=self._init_options.server_version, + websiteUrl=self._init_options.website_url, + icons=self._init_options.icons, + ), + instructions=self._init_options.instructions, + ) + ) + ) + self._initialization_state = InitializationState.Initialized + case types.PingRequest(): + # Ping requests are allowed at any time + pass + case _: + if self._initialization_state != InitializationState.Initialized: + raise RuntimeError("Received request before initialization was complete") + + async def _received_notification(self, notification: types.ClientNotification) -> None: + # Need this to avoid ASYNC910 + await anyio.lowlevel.checkpoint() + match notification.root: + case types.InitializedNotification(): + self._initialization_state = InitializationState.Initialized + case _: + if self._initialization_state != InitializationState.Initialized: # pragma: no cover + raise RuntimeError("Received notification before initialization was complete") + + async def send_log_message( + self, + level: types.LoggingLevel, + data: Any, + logger: str | None = None, + related_request_id: types.RequestId | None = None, + ) -> None: + """Send a log message notification.""" + await self.send_notification( + types.ServerNotification( + types.LoggingMessageNotification( + params=types.LoggingMessageNotificationParams( + level=level, + data=data, + logger=logger, + ), + ) + ), + related_request_id, + ) + + async def send_resource_updated(self, uri: AnyUrl) -> None: # pragma: no cover + """Send a resource updated notification.""" + await self.send_notification( + types.ServerNotification( + types.ResourceUpdatedNotification( + params=types.ResourceUpdatedNotificationParams(uri=uri), + ) + ) + ) + + @overload + async def create_message( + self, + messages: list[types.SamplingMessage], + *, + max_tokens: int, + system_prompt: str | None = None, + include_context: types.IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: types.ModelPreferences | None = None, + tools: None = None, + tool_choice: types.ToolChoice | None = None, + related_request_id: types.RequestId | None = None, + ) -> types.CreateMessageResult: + """Overload: Without tools, returns single content.""" + ... + + @overload + async def create_message( + self, + messages: list[types.SamplingMessage], + *, + max_tokens: int, + system_prompt: str | None = None, + include_context: types.IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: types.ModelPreferences | None = None, + tools: list[types.Tool], + tool_choice: types.ToolChoice | None = None, + related_request_id: types.RequestId | None = None, + ) -> types.CreateMessageResultWithTools: + """Overload: With tools, returns array-capable content.""" + ... + + async def create_message( + self, + messages: list[types.SamplingMessage], + *, + max_tokens: int, + system_prompt: str | None = None, + include_context: types.IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: types.ModelPreferences | None = None, + tools: list[types.Tool] | None = None, + tool_choice: types.ToolChoice | None = None, + related_request_id: types.RequestId | None = None, + ) -> types.CreateMessageResult | types.CreateMessageResultWithTools: + """Send a sampling/create_message request. + + Args: + messages: The conversation messages to send. + max_tokens: Maximum number of tokens to generate. + system_prompt: Optional system prompt. + include_context: Optional context inclusion setting. + Should only be set to "thisServer" or "allServers" + if the client has sampling.context capability. + temperature: Optional sampling temperature. + stop_sequences: Optional stop sequences. + metadata: Optional metadata to pass through to the LLM provider. + model_preferences: Optional model selection preferences. + tools: Optional list of tools the LLM can use during sampling. + Requires client to have sampling.tools capability. + tool_choice: Optional control over tool usage behavior. + Requires client to have sampling.tools capability. + related_request_id: Optional ID of a related request. + + Returns: + The sampling result from the client. + + Raises: + McpError: If tools are provided but client doesn't support them. + ValueError: If tool_use or tool_result message structure is invalid. + """ + client_caps = self._client_params.capabilities if self._client_params else None + validate_sampling_tools(client_caps, tools, tool_choice) + validate_tool_use_result_messages(messages) + + request = types.ServerRequest( + types.CreateMessageRequest( + params=types.CreateMessageRequestParams( + messages=messages, + systemPrompt=system_prompt, + includeContext=include_context, + temperature=temperature, + maxTokens=max_tokens, + stopSequences=stop_sequences, + metadata=metadata, + modelPreferences=model_preferences, + tools=tools, + toolChoice=tool_choice, + ), + ) + ) + metadata_obj = ServerMessageMetadata(related_request_id=related_request_id) + + # Use different result types based on whether tools are provided + if tools is not None: + return await self.send_request( + request=request, + result_type=types.CreateMessageResultWithTools, + metadata=metadata_obj, + ) + return await self.send_request( + request=request, + result_type=types.CreateMessageResult, + metadata=metadata_obj, + ) + + async def list_roots(self) -> types.ListRootsResult: + """Send a roots/list request.""" + return await self.send_request( + types.ServerRequest(types.ListRootsRequest()), + types.ListRootsResult, + ) + + async def elicit( + self, + message: str, + requestedSchema: types.ElicitRequestedSchema, + related_request_id: types.RequestId | None = None, + ) -> types.ElicitResult: + """Send a form mode elicitation/create request. + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response structure + related_request_id: Optional ID of the request that triggered this elicitation + + Returns: + The client's response + + Note: + This method is deprecated in favor of elicit_form(). It remains for + backward compatibility but new code should use elicit_form(). + """ + return await self.elicit_form(message, requestedSchema, related_request_id) + + async def elicit_form( + self, + message: str, + requestedSchema: types.ElicitRequestedSchema, + related_request_id: types.RequestId | None = None, + ) -> types.ElicitResult: + """Send a form mode elicitation/create request. + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response structure + related_request_id: Optional ID of the request that triggered this elicitation + + Returns: + The client's response with form data + """ + return await self.send_request( + types.ServerRequest( + types.ElicitRequest( + params=types.ElicitRequestFormParams( + message=message, + requestedSchema=requestedSchema, + ), + ) + ), + types.ElicitResult, + metadata=ServerMessageMetadata(related_request_id=related_request_id), + ) + + async def elicit_url( + self, + message: str, + url: str, + elicitation_id: str, + related_request_id: types.RequestId | None = None, + ) -> types.ElicitResult: + """Send a URL mode elicitation/create request. + + This directs the user to an external URL for out-of-band interactions + like OAuth flows, credential collection, or payment processing. + + Args: + message: Human-readable explanation of why the interaction is needed + url: The URL the user should navigate to + elicitation_id: Unique identifier for tracking this elicitation + related_request_id: Optional ID of the request that triggered this elicitation + + Returns: + The client's response indicating acceptance, decline, or cancellation + """ + return await self.send_request( + types.ServerRequest( + types.ElicitRequest( + params=types.ElicitRequestURLParams( + message=message, + url=url, + elicitationId=elicitation_id, + ), + ) + ), + types.ElicitResult, + metadata=ServerMessageMetadata(related_request_id=related_request_id), + ) + + async def send_ping(self) -> types.EmptyResult: # pragma: no cover + """Send a ping request.""" + return await self.send_request( + types.ServerRequest(types.PingRequest()), + types.EmptyResult, + ) + + async def send_progress_notification( + self, + progress_token: str | int, + progress: float, + total: float | None = None, + message: str | None = None, + related_request_id: str | None = None, + ) -> None: + """Send a progress notification.""" + await self.send_notification( + types.ServerNotification( + types.ProgressNotification( + params=types.ProgressNotificationParams( + progressToken=progress_token, + progress=progress, + total=total, + message=message, + ), + ) + ), + related_request_id, + ) + + async def send_resource_list_changed(self) -> None: # pragma: no cover + """Send a resource list changed notification.""" + await self.send_notification(types.ServerNotification(types.ResourceListChangedNotification())) + + async def send_tool_list_changed(self) -> None: # pragma: no cover + """Send a tool list changed notification.""" + await self.send_notification(types.ServerNotification(types.ToolListChangedNotification())) + + async def send_prompt_list_changed(self) -> None: # pragma: no cover + """Send a prompt list changed notification.""" + await self.send_notification(types.ServerNotification(types.PromptListChangedNotification())) + + async def send_elicit_complete( + self, + elicitation_id: str, + related_request_id: types.RequestId | None = None, + ) -> None: + """Send an elicitation completion notification. + + This should be sent when a URL mode elicitation has been completed + out-of-band to inform the client that it may retry any requests + that were waiting for this elicitation. + + Args: + elicitation_id: The unique identifier of the completed elicitation + related_request_id: Optional ID of the request that triggered this + """ + await self.send_notification( + types.ServerNotification( + types.ElicitCompleteNotification( + params=types.ElicitCompleteNotificationParams(elicitationId=elicitation_id) + ) + ), + related_request_id, + ) + + def _build_elicit_form_request( + self, + message: str, + requestedSchema: types.ElicitRequestedSchema, + related_task_id: str | None = None, + task: types.TaskMetadata | None = None, + ) -> types.JSONRPCRequest: + """Build a form mode elicitation request without sending it. + + Args: + message: The message to present to the user + requestedSchema: Schema defining the expected response structure + related_task_id: If provided, adds io.modelcontextprotocol/related-task metadata + task: If provided, makes this a task-augmented request + + Returns: + A JSONRPCRequest ready to be sent or queued + """ + params = types.ElicitRequestFormParams( + message=message, + requestedSchema=requestedSchema, + task=task, + ) + params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True) + + # Add related-task metadata if associated with a parent task + if related_task_id is not None: + # Defensive: model_dump() never includes _meta, but guard against future changes + if "_meta" not in params_data: # pragma: no cover + params_data["_meta"] = {} + params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata( + taskId=related_task_id + ).model_dump(by_alias=True) + + request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id + if related_task_id is None: + self._request_id += 1 + + return types.JSONRPCRequest( + jsonrpc="2.0", + id=request_id, + method="elicitation/create", + params=params_data, + ) + + def _build_elicit_url_request( + self, + message: str, + url: str, + elicitation_id: str, + related_task_id: str | None = None, + ) -> types.JSONRPCRequest: + """Build a URL mode elicitation request without sending it. + + Args: + message: Human-readable explanation of why the interaction is needed + url: The URL the user should navigate to + elicitation_id: Unique identifier for tracking this elicitation + related_task_id: If provided, adds io.modelcontextprotocol/related-task metadata + + Returns: + A JSONRPCRequest ready to be sent or queued + """ + params = types.ElicitRequestURLParams( + message=message, + url=url, + elicitationId=elicitation_id, + ) + params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True) + + # Add related-task metadata if associated with a parent task + if related_task_id is not None: + # Defensive: model_dump() never includes _meta, but guard against future changes + if "_meta" not in params_data: # pragma: no cover + params_data["_meta"] = {} + params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata( + taskId=related_task_id + ).model_dump(by_alias=True) + + request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id + if related_task_id is None: + self._request_id += 1 + + return types.JSONRPCRequest( + jsonrpc="2.0", + id=request_id, + method="elicitation/create", + params=params_data, + ) + + def _build_create_message_request( + self, + messages: list[types.SamplingMessage], + *, + max_tokens: int, + system_prompt: str | None = None, + include_context: types.IncludeContext | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + metadata: dict[str, Any] | None = None, + model_preferences: types.ModelPreferences | None = None, + tools: list[types.Tool] | None = None, + tool_choice: types.ToolChoice | None = None, + related_task_id: str | None = None, + task: types.TaskMetadata | None = None, + ) -> types.JSONRPCRequest: + """Build a sampling/createMessage request without sending it. + + Args: + messages: The conversation messages to send + max_tokens: Maximum number of tokens to generate + system_prompt: Optional system prompt + include_context: Optional context inclusion setting + temperature: Optional sampling temperature + stop_sequences: Optional stop sequences + metadata: Optional metadata to pass through to the LLM provider + model_preferences: Optional model selection preferences + tools: Optional list of tools the LLM can use during sampling + tool_choice: Optional control over tool usage behavior + related_task_id: If provided, adds io.modelcontextprotocol/related-task metadata + task: If provided, makes this a task-augmented request + + Returns: + A JSONRPCRequest ready to be sent or queued + """ + params = types.CreateMessageRequestParams( + messages=messages, + systemPrompt=system_prompt, + includeContext=include_context, + temperature=temperature, + maxTokens=max_tokens, + stopSequences=stop_sequences, + metadata=metadata, + modelPreferences=model_preferences, + tools=tools, + toolChoice=tool_choice, + task=task, + ) + params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True) + + # Add related-task metadata if associated with a parent task + if related_task_id is not None: + # Defensive: model_dump() never includes _meta, but guard against future changes + if "_meta" not in params_data: # pragma: no cover + params_data["_meta"] = {} + params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata( + taskId=related_task_id + ).model_dump(by_alias=True) + + request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id + if related_task_id is None: + self._request_id += 1 + + return types.JSONRPCRequest( + jsonrpc="2.0", + id=request_id, + method="sampling/createMessage", + params=params_data, + ) + + async def send_message(self, message: SessionMessage) -> None: + """Send a raw session message. + + This is primarily used by TaskResultHandler to deliver queued messages + (elicitation/sampling requests) to the client during task execution. + + WARNING: This is a low-level experimental method that may change without + notice. Prefer using higher-level methods like send_notification() or + send_request() for normal operations. + + Args: + message: The session message to send + """ + await self._write_stream.send(message) + + async def _handle_incoming(self, req: ServerRequestResponder) -> None: + await self._incoming_message_stream_writer.send(req) + + @property + def incoming_messages( + self, + ) -> MemoryObjectReceiveStream[ServerRequestResponder]: + return self._incoming_message_stream_reader diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py new file mode 100644 index 0000000..19af93f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py @@ -0,0 +1,249 @@ +""" +SSE Server Transport Module + +This module implements a Server-Sent Events (SSE) transport layer for MCP servers. + +Example usage: +``` + # Create an SSE transport at an endpoint + sse = SseServerTransport("/messages/") + + # Create Starlette routes for SSE and message handling + routes = [ + Route("/sse", endpoint=handle_sse, methods=["GET"]), + Mount("/messages/", app=sse.handle_post_message), + ] + + # Define handler functions + async def handle_sse(request): + async with sse.connect_sse( + request.scope, request.receive, request._send + ) as streams: + await app.run( + streams[0], streams[1], app.create_initialization_options() + ) + # Return empty response to avoid NoneType error + return Response() + + # Create and run Starlette app + starlette_app = Starlette(routes=routes) + uvicorn.run(starlette_app, host="127.0.0.1", port=port) +``` + +Note: The handle_sse function must return a Response to avoid a "TypeError: 'NoneType' +object is not callable" error when client disconnects. The example above returns +an empty Response() after the SSE connection ends to fix this. + +See SseServerTransport class documentation for more details. +""" + +import logging +from contextlib import asynccontextmanager +from typing import Any +from urllib.parse import quote +from uuid import UUID, uuid4 + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import ValidationError +from sse_starlette import EventSourceResponse +from starlette.requests import Request +from starlette.responses import Response +from starlette.types import Receive, Scope, Send + +import mcp.types as types +from mcp.server.transport_security import ( + TransportSecurityMiddleware, + TransportSecuritySettings, +) +from mcp.shared.message import ServerMessageMetadata, SessionMessage + +logger = logging.getLogger(__name__) + + +class SseServerTransport: + """ + SSE server transport for MCP. This class provides _two_ ASGI applications, + suitable to be used with a framework like Starlette and a server like Hypercorn: + + 1. connect_sse() is an ASGI application which receives incoming GET requests, + and sets up a new SSE stream to send server messages to the client. + 2. handle_post_message() is an ASGI application which receives incoming POST + requests, which should contain client messages that link to a + previously-established SSE session. + """ + + _endpoint: str + _read_stream_writers: dict[UUID, MemoryObjectSendStream[SessionMessage | Exception]] + _security: TransportSecurityMiddleware + + def __init__(self, endpoint: str, security_settings: TransportSecuritySettings | None = None) -> None: + """ + Creates a new SSE server transport, which will direct the client to POST + messages to the relative path given. + + Args: + endpoint: A relative path where messages should be posted + (e.g., "/messages/"). + security_settings: Optional security settings for DNS rebinding protection. + + Note: + We use relative paths instead of full URLs for several reasons: + 1. Security: Prevents cross-origin requests by ensuring clients only connect + to the same origin they established the SSE connection with + 2. Flexibility: The server can be mounted at any path without needing to + know its full URL + 3. Portability: The same endpoint configuration works across different + environments (development, staging, production) + + Raises: + ValueError: If the endpoint is a full URL instead of a relative path + """ + + super().__init__() + + # Validate that endpoint is a relative path and not a full URL + if "://" in endpoint or endpoint.startswith("//") or "?" in endpoint or "#" in endpoint: + raise ValueError( + f"Given endpoint: {endpoint} is not a relative path (e.g., '/messages/'), " + "expecting a relative path (e.g., '/messages/')." + ) + + # Ensure endpoint starts with a forward slash + if not endpoint.startswith("/"): + endpoint = "/" + endpoint + + self._endpoint = endpoint + self._read_stream_writers = {} + self._security = TransportSecurityMiddleware(security_settings) + logger.debug(f"SseServerTransport initialized with endpoint: {endpoint}") + + @asynccontextmanager + async def connect_sse(self, scope: Scope, receive: Receive, send: Send): # pragma: no cover + if scope["type"] != "http": + logger.error("connect_sse received non-HTTP request") + raise ValueError("connect_sse can only handle HTTP requests") + + # Validate request headers for DNS rebinding protection + request = Request(scope, receive) + error_response = await self._security.validate_request(request, is_post=False) + if error_response: + await error_response(scope, receive, send) + raise ValueError("Request validation failed") + + logger.debug("Setting up SSE connection") + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + session_id = uuid4() + self._read_stream_writers[session_id] = read_stream_writer + logger.debug(f"Created new session with ID: {session_id}") + + # Determine the full path for the message endpoint to be sent to the client. + # scope['root_path'] is the prefix where the current Starlette app + # instance is mounted. + # e.g., "" if top-level, or "/api_prefix" if mounted under "/api_prefix". + root_path = scope.get("root_path", "") + + # self._endpoint is the path *within* this app, e.g., "/messages". + # Concatenating them gives the full absolute path from the server root. + # e.g., "" + "/messages" -> "/messages" + # e.g., "/api_prefix" + "/messages" -> "/api_prefix/messages" + full_message_path_for_client = root_path.rstrip("/") + self._endpoint + + # This is the URI (path + query) the client will use to POST messages. + client_post_uri_data = f"{quote(full_message_path_for_client)}?session_id={session_id.hex}" + + sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, Any]](0) + + async def sse_writer(): + logger.debug("Starting SSE writer") + async with sse_stream_writer, write_stream_reader: + await sse_stream_writer.send({"event": "endpoint", "data": client_post_uri_data}) + logger.debug(f"Sent endpoint event: {client_post_uri_data}") + + async for session_message in write_stream_reader: + logger.debug(f"Sending message via SSE: {session_message}") + await sse_stream_writer.send( + { + "event": "message", + "data": session_message.message.model_dump_json(by_alias=True, exclude_none=True), + } + ) + + async with anyio.create_task_group() as tg: + + async def response_wrapper(scope: Scope, receive: Receive, send: Send): + """ + The EventSourceResponse returning signals a client close / disconnect. + In this case we close our side of the streams to signal the client that + the connection has been closed. + """ + await EventSourceResponse(content=sse_stream_reader, data_sender_callable=sse_writer)( + scope, receive, send + ) + await read_stream_writer.aclose() + await write_stream_reader.aclose() + logging.debug(f"Client session disconnected {session_id}") + + logger.debug("Starting SSE response task") + tg.start_soon(response_wrapper, scope, receive, send) + + logger.debug("Yielding read and write streams") + yield (read_stream, write_stream) + + async def handle_post_message(self, scope: Scope, receive: Receive, send: Send) -> None: # pragma: no cover + logger.debug("Handling POST message") + request = Request(scope, receive) + + # Validate request headers for DNS rebinding protection + error_response = await self._security.validate_request(request, is_post=True) + if error_response: + return await error_response(scope, receive, send) + + session_id_param = request.query_params.get("session_id") + if session_id_param is None: + logger.warning("Received request without session_id") + response = Response("session_id is required", status_code=400) + return await response(scope, receive, send) + + try: + session_id = UUID(hex=session_id_param) + logger.debug(f"Parsed session ID: {session_id}") + except ValueError: + logger.warning(f"Received invalid session ID: {session_id_param}") + response = Response("Invalid session ID", status_code=400) + return await response(scope, receive, send) + + writer = self._read_stream_writers.get(session_id) + if not writer: + logger.warning(f"Could not find session for ID: {session_id}") + response = Response("Could not find session", status_code=404) + return await response(scope, receive, send) + + body = await request.body() + logger.debug(f"Received JSON: {body}") + + try: + message = types.JSONRPCMessage.model_validate_json(body) + logger.debug(f"Validated client message: {message}") + except ValidationError as err: + logger.exception("Failed to parse message") + response = Response("Could not parse message", status_code=400) + await response(scope, receive, send) + await writer.send(err) + return + + # Pass the ASGI scope for framework-agnostic access to request data + metadata = ServerMessageMetadata(request_context=request) + session_message = SessionMessage(message, metadata=metadata) + logger.debug(f"Sending session message to writer: {session_message}") + response = Response("Accepted", status_code=202) + await response(scope, receive, send) + await writer.send(session_message) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py new file mode 100644 index 0000000..bcb9247 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py @@ -0,0 +1,88 @@ +""" +Stdio Server Transport Module + +This module provides functionality for creating an stdio-based transport layer +that can be used to communicate with an MCP client through standard input/output +streams. + +Example usage: +``` + async def run_server(): + async with stdio_server() as (read_stream, write_stream): + # read_stream contains incoming JSONRPCMessages from stdin + # write_stream allows sending JSONRPCMessages to stdout + server = await create_my_server() + await server.run(read_stream, write_stream, init_options) + + anyio.run(run_server) +``` +""" + +import sys +from contextlib import asynccontextmanager +from io import TextIOWrapper + +import anyio +import anyio.lowlevel +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +import mcp.types as types +from mcp.shared.message import SessionMessage + + +@asynccontextmanager +async def stdio_server( + stdin: anyio.AsyncFile[str] | None = None, + stdout: anyio.AsyncFile[str] | None = None, +): + """ + Server transport for stdio: this communicates with an MCP client by reading + from the current process' stdin and writing to stdout. + """ + # Purposely not using context managers for these, as we don't want to close + # standard process handles. Encoding of stdin/stdout as text streams on + # python is platform-dependent (Windows is particularly problematic), so we + # re-wrap the underlying binary stream to ensure UTF-8. + if not stdin: + stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8")) + if not stdout: + stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8")) + + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + async def stdin_reader(): + try: + async with read_stream_writer: + async for line in stdin: + try: + message = types.JSONRPCMessage.model_validate_json(line) + except Exception as exc: # pragma: no cover + await read_stream_writer.send(exc) + continue + + session_message = SessionMessage(message) + await read_stream_writer.send(session_message) + except anyio.ClosedResourceError: # pragma: no cover + await anyio.lowlevel.checkpoint() + + async def stdout_writer(): + try: + async with write_stream_reader: + async for session_message in write_stream_reader: + json = session_message.message.model_dump_json(by_alias=True, exclude_none=True) + await stdout.write(json + "\n") + await stdout.flush() + except anyio.ClosedResourceError: # pragma: no cover + await anyio.lowlevel.checkpoint() + + async with anyio.create_task_group() as tg: + tg.start_soon(stdin_reader) + tg.start_soon(stdout_writer) + yield read_stream, write_stream diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py new file mode 100644 index 0000000..2613b53 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py @@ -0,0 +1,1067 @@ +""" +StreamableHTTP Server Transport Module + +This module implements an HTTP transport layer with Streamable HTTP. + +The transport handles bidirectional communication using HTTP requests and +responses, with streaming support for long-running operations. +""" + +import json +import logging +import re +from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator, Awaitable, Callable +from contextlib import asynccontextmanager +from dataclasses import dataclass +from http import HTTPStatus +from typing import Any + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import ValidationError +from sse_starlette import EventSourceResponse +from starlette.requests import Request +from starlette.responses import Response +from starlette.types import Receive, Scope, Send + +from mcp.server.transport_security import ( + TransportSecurityMiddleware, + TransportSecuritySettings, +) +from mcp.shared.message import ServerMessageMetadata, SessionMessage +from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS +from mcp.types import ( + DEFAULT_NEGOTIATED_VERSION, + INTERNAL_ERROR, + INVALID_PARAMS, + INVALID_REQUEST, + PARSE_ERROR, + ErrorData, + JSONRPCError, + JSONRPCMessage, + JSONRPCRequest, + JSONRPCResponse, + RequestId, +) + +logger = logging.getLogger(__name__) + + +# Header names +MCP_SESSION_ID_HEADER = "mcp-session-id" +MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" +LAST_EVENT_ID_HEADER = "last-event-id" + +# Content types +CONTENT_TYPE_JSON = "application/json" +CONTENT_TYPE_SSE = "text/event-stream" + +# Special key for the standalone GET stream +GET_STREAM_KEY = "_GET_stream" + +# Session ID validation pattern (visible ASCII characters ranging from 0x21 to 0x7E) +# Pattern ensures entire string contains only valid characters by using ^ and $ anchors +SESSION_ID_PATTERN = re.compile(r"^[\x21-\x7E]+$") + +# Type aliases +StreamId = str +EventId = str + + +@dataclass +class EventMessage: + """ + A JSONRPCMessage with an optional event ID for stream resumability. + """ + + message: JSONRPCMessage + event_id: str | None = None + + +EventCallback = Callable[[EventMessage], Awaitable[None]] + + +class EventStore(ABC): + """ + Interface for resumability support via event storage. + """ + + @abstractmethod + async def store_event(self, stream_id: StreamId, message: JSONRPCMessage | None) -> EventId: + """ + Stores an event for later retrieval. + + Args: + stream_id: ID of the stream the event belongs to + message: The JSON-RPC message to store, or None for priming events + + Returns: + The generated event ID for the stored event + """ + pass # pragma: no cover + + @abstractmethod + async def replay_events_after( + self, + last_event_id: EventId, + send_callback: EventCallback, + ) -> StreamId | None: + """ + Replays events that occurred after the specified event ID. + + Args: + last_event_id: The ID of the last event the client received + send_callback: A callback function to send events to the client + + Returns: + The stream ID of the replayed events + """ + pass # pragma: no cover + + +class StreamableHTTPServerTransport: + """ + HTTP server transport with event streaming support for MCP. + + Handles JSON-RPC messages in HTTP POST requests with SSE streaming. + Supports optional JSON responses and session management. + """ + + # Server notification streams for POST requests as well as standalone SSE stream + _read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] | None = None + _read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] | None = None + _write_stream: MemoryObjectSendStream[SessionMessage] | None = None + _write_stream_reader: MemoryObjectReceiveStream[SessionMessage] | None = None + _security: TransportSecurityMiddleware + + def __init__( + self, + mcp_session_id: str | None, + is_json_response_enabled: bool = False, + event_store: EventStore | None = None, + security_settings: TransportSecuritySettings | None = None, + retry_interval: int | None = None, + ) -> None: + """ + Initialize a new StreamableHTTP server transport. + + Args: + mcp_session_id: Optional session identifier for this connection. + Must contain only visible ASCII characters (0x21-0x7E). + is_json_response_enabled: If True, return JSON responses for requests + instead of SSE streams. Default is False. + event_store: Event store for resumability support. If provided, + resumability will be enabled, allowing clients to + reconnect and resume messages. + security_settings: Optional security settings for DNS rebinding protection. + retry_interval: Retry interval in milliseconds to suggest to clients in SSE + retry field. When set, the server will send a retry field in + SSE priming events to control client reconnection timing for + polling behavior. Only used when event_store is provided. + + Raises: + ValueError: If the session ID contains invalid characters. + """ + if mcp_session_id is not None and not SESSION_ID_PATTERN.fullmatch(mcp_session_id): + raise ValueError("Session ID must only contain visible ASCII characters (0x21-0x7E)") + + self.mcp_session_id = mcp_session_id + self.is_json_response_enabled = is_json_response_enabled + self._event_store = event_store + self._security = TransportSecurityMiddleware(security_settings) + self._retry_interval = retry_interval + self._request_streams: dict[ + RequestId, + tuple[ + MemoryObjectSendStream[EventMessage], + MemoryObjectReceiveStream[EventMessage], + ], + ] = {} + self._sse_stream_writers: dict[RequestId, MemoryObjectSendStream[dict[str, str]]] = {} + self._terminated = False + + @property + def is_terminated(self) -> bool: + """Check if this transport has been explicitly terminated.""" + return self._terminated + + def close_sse_stream(self, request_id: RequestId) -> None: # pragma: no cover + """Close SSE connection for a specific request without terminating the stream. + + This method closes the HTTP connection for the specified request, triggering + client reconnection. Events continue to be stored in the event store and will + be replayed when the client reconnects with Last-Event-ID. + + Use this to implement polling behavior during long-running operations - + client will reconnect after the retry interval specified in the priming event. + + Args: + request_id: The request ID whose SSE stream should be closed. + + Note: + This is a no-op if there is no active stream for the request ID. + Requires event_store to be configured for events to be stored during + the disconnect. + """ + writer = self._sse_stream_writers.pop(request_id, None) + if writer: + writer.close() + + # Also close and remove request streams + if request_id in self._request_streams: + send_stream, receive_stream = self._request_streams.pop(request_id) + send_stream.close() + receive_stream.close() + + def close_standalone_sse_stream(self) -> None: # pragma: no cover + """Close the standalone GET SSE stream, triggering client reconnection. + + This method closes the HTTP connection for the standalone GET stream used + for unsolicited server-to-client notifications. The client SHOULD reconnect + with Last-Event-ID to resume receiving notifications. + + Use this to implement polling behavior for the notification stream - + client will reconnect after the retry interval specified in the priming event. + + Note: + This is a no-op if there is no active standalone SSE stream. + Requires event_store to be configured for events to be stored during + the disconnect. + Currently, client reconnection for standalone GET streams is NOT + implemented - this is a known gap (see test_standalone_get_stream_reconnection). + """ + self.close_sse_stream(GET_STREAM_KEY) + + def _create_session_message( # pragma: no cover + self, + message: JSONRPCMessage, + request: Request, + request_id: RequestId, + protocol_version: str, + ) -> SessionMessage: + """Create a session message with metadata including close_sse_stream callback. + + The close_sse_stream callbacks are only provided when the client supports + resumability (protocol version >= 2025-11-25). Old clients can't resume if + the stream is closed early because they didn't receive a priming event. + """ + # Only provide close callbacks when client supports resumability + if self._event_store and protocol_version >= "2025-11-25": + + async def close_stream_callback() -> None: + self.close_sse_stream(request_id) + + async def close_standalone_stream_callback() -> None: + self.close_standalone_sse_stream() + + metadata = ServerMessageMetadata( + request_context=request, + close_sse_stream=close_stream_callback, + close_standalone_sse_stream=close_standalone_stream_callback, + ) + else: + metadata = ServerMessageMetadata(request_context=request) + + return SessionMessage(message, metadata=metadata) + + async def _maybe_send_priming_event( + self, + request_id: RequestId, + sse_stream_writer: MemoryObjectSendStream[dict[str, Any]], + protocol_version: str, + ) -> None: + """Send priming event for SSE resumability if event_store is configured. + + Only sends priming events to clients with protocol version >= 2025-11-25, + which includes the fix for handling empty SSE data. Older clients would + crash trying to parse empty data as JSON. + """ + if not self._event_store: + return + # Priming events have empty data which older clients cannot handle. + if protocol_version < "2025-11-25": + return + priming_event_id = await self._event_store.store_event( + str(request_id), # Convert RequestId to StreamId (str) + None, # Priming event has no payload + ) + priming_event: dict[str, str | int] = {"id": priming_event_id, "data": ""} + if self._retry_interval is not None: + priming_event["retry"] = self._retry_interval + await sse_stream_writer.send(priming_event) + + def _create_error_response( + self, + error_message: str, + status_code: HTTPStatus, + error_code: int = INVALID_REQUEST, + headers: dict[str, str] | None = None, + ) -> Response: + """Create an error response with a simple string message.""" + response_headers = {"Content-Type": CONTENT_TYPE_JSON} + if headers: # pragma: no cover + response_headers.update(headers) + + if self.mcp_session_id: + response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id + + # Return a properly formatted JSON error response + error_response = JSONRPCError( + jsonrpc="2.0", + id="server-error", # We don't have a request ID for general errors + error=ErrorData( + code=error_code, + message=error_message, + ), + ) + + return Response( + error_response.model_dump_json(by_alias=True, exclude_none=True), + status_code=status_code, + headers=response_headers, + ) + + def _create_json_response( # pragma: no cover + self, + response_message: JSONRPCMessage | None, + status_code: HTTPStatus = HTTPStatus.OK, + headers: dict[str, str] | None = None, + ) -> Response: + """Create a JSON response from a JSONRPCMessage""" + response_headers = {"Content-Type": CONTENT_TYPE_JSON} + if headers: + response_headers.update(headers) + + if self.mcp_session_id: + response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id + + return Response( + response_message.model_dump_json(by_alias=True, exclude_none=True) if response_message else None, + status_code=status_code, + headers=response_headers, + ) + + def _get_session_id(self, request: Request) -> str | None: # pragma: no cover + """Extract the session ID from request headers.""" + return request.headers.get(MCP_SESSION_ID_HEADER) + + def _create_event_data(self, event_message: EventMessage) -> dict[str, str]: # pragma: no cover + """Create event data dictionary from an EventMessage.""" + event_data = { + "event": "message", + "data": event_message.message.model_dump_json(by_alias=True, exclude_none=True), + } + + # If an event ID was provided, include it + if event_message.event_id: + event_data["id"] = event_message.event_id + + return event_data + + async def _clean_up_memory_streams(self, request_id: RequestId) -> None: # pragma: no cover + """Clean up memory streams for a given request ID.""" + if request_id in self._request_streams: + try: + # Close the request stream + await self._request_streams[request_id][0].aclose() + await self._request_streams[request_id][1].aclose() + except Exception: + # During cleanup, we catch all exceptions since streams might be in various states + logger.debug("Error closing memory streams - may already be closed") + finally: + # Remove the request stream from the mapping + self._request_streams.pop(request_id, None) + + async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> None: + """Application entry point that handles all HTTP requests""" + request = Request(scope, receive) + + # Validate request headers for DNS rebinding protection + is_post = request.method == "POST" + error_response = await self._security.validate_request(request, is_post=is_post) + if error_response: # pragma: no cover + await error_response(scope, receive, send) + return + + if self._terminated: # pragma: no cover + # If the session has been terminated, return 404 Not Found + response = self._create_error_response( + "Not Found: Session has been terminated", + HTTPStatus.NOT_FOUND, + ) + await response(scope, receive, send) + return + + if request.method == "POST": + await self._handle_post_request(scope, request, receive, send) + elif request.method == "GET": # pragma: no cover + await self._handle_get_request(request, send) + elif request.method == "DELETE": # pragma: no cover + await self._handle_delete_request(request, send) + else: # pragma: no cover + await self._handle_unsupported_request(request, send) + + def _check_accept_headers(self, request: Request) -> tuple[bool, bool]: + """Check if the request accepts the required media types.""" + accept_header = request.headers.get("accept", "") + accept_types = [media_type.strip() for media_type in accept_header.split(",")] + + has_json = any(media_type.startswith(CONTENT_TYPE_JSON) for media_type in accept_types) + has_sse = any(media_type.startswith(CONTENT_TYPE_SSE) for media_type in accept_types) + + return has_json, has_sse + + def _check_content_type(self, request: Request) -> bool: + """Check if the request has the correct Content-Type.""" + content_type = request.headers.get("content-type", "") + content_type_parts = [part.strip() for part in content_type.split(";")[0].split(",")] + + return any(part == CONTENT_TYPE_JSON for part in content_type_parts) + + async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool: # pragma: no cover + """Validate Accept header based on response mode. Returns True if valid.""" + has_json, has_sse = self._check_accept_headers(request) + if self.is_json_response_enabled: + # For JSON-only responses, only require application/json + if not has_json: + response = self._create_error_response( + "Not Acceptable: Client must accept application/json", + HTTPStatus.NOT_ACCEPTABLE, + ) + await response(scope, request.receive, send) + return False + # For SSE responses, require both content types + elif not (has_json and has_sse): + response = self._create_error_response( + "Not Acceptable: Client must accept both application/json and text/event-stream", + HTTPStatus.NOT_ACCEPTABLE, + ) + await response(scope, request.receive, send) + return False + return True + + async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None: + """Handle POST requests containing JSON-RPC messages.""" + writer = self._read_stream_writer + if writer is None: # pragma: no cover + raise ValueError("No read stream writer available. Ensure connect() is called first.") + try: + # Validate Accept header + if not await self._validate_accept_header(request, scope, send): + return + + # Validate Content-Type + if not self._check_content_type(request): # pragma: no cover + response = self._create_error_response( + "Unsupported Media Type: Content-Type must be application/json", + HTTPStatus.UNSUPPORTED_MEDIA_TYPE, + ) + await response(scope, receive, send) + return + + # Parse the body - only read it once + body = await request.body() + + try: + raw_message = json.loads(body) + except json.JSONDecodeError as e: + response = self._create_error_response(f"Parse error: {str(e)}", HTTPStatus.BAD_REQUEST, PARSE_ERROR) + await response(scope, receive, send) + return + + try: # pragma: no cover + message = JSONRPCMessage.model_validate(raw_message) + except ValidationError as e: # pragma: no cover + response = self._create_error_response( + f"Validation error: {str(e)}", + HTTPStatus.BAD_REQUEST, + INVALID_PARAMS, + ) + await response(scope, receive, send) + return + + # Check if this is an initialization request + is_initialization_request = ( + isinstance(message.root, JSONRPCRequest) and message.root.method == "initialize" + ) # pragma: no cover + + if is_initialization_request: # pragma: no cover + # Check if the server already has an established session + if self.mcp_session_id: + # Check if request has a session ID + request_session_id = self._get_session_id(request) + + # If request has a session ID but doesn't match, return 404 + if request_session_id and request_session_id != self.mcp_session_id: + response = self._create_error_response( + "Not Found: Invalid or expired session ID", + HTTPStatus.NOT_FOUND, + ) + await response(scope, receive, send) + return + elif not await self._validate_request_headers(request, send): # pragma: no cover + return + + # For notifications and responses only, return 202 Accepted + if not isinstance(message.root, JSONRPCRequest): # pragma: no cover + # Create response object and send it + response = self._create_json_response( + None, + HTTPStatus.ACCEPTED, + ) + await response(scope, receive, send) + + # Process the message after sending the response + metadata = ServerMessageMetadata(request_context=request) + session_message = SessionMessage(message, metadata=metadata) + await writer.send(session_message) + + return + + # Extract protocol version for priming event decision. + # For initialize requests, get from request params. + # For other requests, get from header (already validated). + protocol_version = ( + str(message.root.params.get("protocolVersion", DEFAULT_NEGOTIATED_VERSION)) + if is_initialization_request and message.root.params + else request.headers.get(MCP_PROTOCOL_VERSION_HEADER, DEFAULT_NEGOTIATED_VERSION) + ) + + # Extract the request ID outside the try block for proper scope + request_id = str(message.root.id) # pragma: no cover + # Register this stream for the request ID + self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](0) # pragma: no cover + request_stream_reader = self._request_streams[request_id][1] # pragma: no cover + + if self.is_json_response_enabled: # pragma: no cover + # Process the message + metadata = ServerMessageMetadata(request_context=request) + session_message = SessionMessage(message, metadata=metadata) + await writer.send(session_message) + try: + # Process messages from the request-specific stream + # We need to collect all messages until we get a response + response_message = None + + # Use similar approach to SSE writer for consistency + async for event_message in request_stream_reader: + # If it's a response, this is what we're waiting for + if isinstance(event_message.message.root, JSONRPCResponse | JSONRPCError): + response_message = event_message.message + break + # For notifications and request, keep waiting + else: + logger.debug(f"received: {event_message.message.root.method}") + + # At this point we should have a response + if response_message: + # Create JSON response + response = self._create_json_response(response_message) + await response(scope, receive, send) + else: + # This shouldn't happen in normal operation + logger.error("No response message received before stream closed") + response = self._create_error_response( + "Error processing request: No response received", + HTTPStatus.INTERNAL_SERVER_ERROR, + ) + await response(scope, receive, send) + except Exception: + logger.exception("Error processing JSON response") + response = self._create_error_response( + "Error processing request", + HTTPStatus.INTERNAL_SERVER_ERROR, + INTERNAL_ERROR, + ) + await response(scope, receive, send) + finally: + await self._clean_up_memory_streams(request_id) + else: # pragma: no cover + # Create SSE stream + sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0) + + # Store writer reference so close_sse_stream() can close it + self._sse_stream_writers[request_id] = sse_stream_writer + + async def sse_writer(): + # Get the request ID from the incoming request message + try: + async with sse_stream_writer, request_stream_reader: + # Send priming event for SSE resumability + await self._maybe_send_priming_event(request_id, sse_stream_writer, protocol_version) + + # Process messages from the request-specific stream + async for event_message in request_stream_reader: + # Build the event data + event_data = self._create_event_data(event_message) + await sse_stream_writer.send(event_data) + + # If response, remove from pending streams and close + if isinstance( + event_message.message.root, + JSONRPCResponse | JSONRPCError, + ): + break + except anyio.ClosedResourceError: + # Expected when close_sse_stream() is called + logger.debug("SSE stream closed by close_sse_stream()") + except Exception: + logger.exception("Error in SSE writer") + finally: + logger.debug("Closing SSE writer") + self._sse_stream_writers.pop(request_id, None) + await self._clean_up_memory_streams(request_id) + + # Create and start EventSourceResponse + # SSE stream mode (original behavior) + # Set up headers + headers = { + "Cache-Control": "no-cache, no-transform", + "Connection": "keep-alive", + "Content-Type": CONTENT_TYPE_SSE, + **({MCP_SESSION_ID_HEADER: self.mcp_session_id} if self.mcp_session_id else {}), + } + response = EventSourceResponse( + content=sse_stream_reader, + data_sender_callable=sse_writer, + headers=headers, + ) + + # Start the SSE response (this will send headers immediately) + try: + # First send the response to establish the SSE connection + async with anyio.create_task_group() as tg: + tg.start_soon(response, scope, receive, send) + # Then send the message to be processed by the server + session_message = self._create_session_message(message, request, request_id, protocol_version) + await writer.send(session_message) + except Exception: + logger.exception("SSE response error") + await sse_stream_writer.aclose() + await sse_stream_reader.aclose() + await self._clean_up_memory_streams(request_id) + + except Exception as err: # pragma: no cover + logger.exception("Error handling POST request") + response = self._create_error_response( + f"Error handling POST request: {err}", + HTTPStatus.INTERNAL_SERVER_ERROR, + INTERNAL_ERROR, + ) + await response(scope, receive, send) + if writer: + await writer.send(Exception(err)) + return + + async def _handle_get_request(self, request: Request, send: Send) -> None: # pragma: no cover + """ + Handle GET request to establish SSE. + + This allows the server to communicate to the client without the client + first sending data via HTTP POST. The server can send JSON-RPC requests + and notifications on this stream. + """ + writer = self._read_stream_writer + if writer is None: + raise ValueError("No read stream writer available. Ensure connect() is called first.") + + # Validate Accept header - must include text/event-stream + _, has_sse = self._check_accept_headers(request) + + if not has_sse: + response = self._create_error_response( + "Not Acceptable: Client must accept text/event-stream", + HTTPStatus.NOT_ACCEPTABLE, + ) + await response(request.scope, request.receive, send) + return + + if not await self._validate_request_headers(request, send): + return + + # Handle resumability: check for Last-Event-ID header + if last_event_id := request.headers.get(LAST_EVENT_ID_HEADER): + await self._replay_events(last_event_id, request, send) + return + + headers = { + "Cache-Control": "no-cache, no-transform", + "Connection": "keep-alive", + "Content-Type": CONTENT_TYPE_SSE, + } + + if self.mcp_session_id: + headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id + + # Check if we already have an active GET stream + if GET_STREAM_KEY in self._request_streams: + response = self._create_error_response( + "Conflict: Only one SSE stream is allowed per session", + HTTPStatus.CONFLICT, + ) + await response(request.scope, request.receive, send) + return + + # Create SSE stream + sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0) + + async def standalone_sse_writer(): + try: + # Create a standalone message stream for server-initiated messages + + self._request_streams[GET_STREAM_KEY] = anyio.create_memory_object_stream[EventMessage](0) + standalone_stream_reader = self._request_streams[GET_STREAM_KEY][1] + + async with sse_stream_writer, standalone_stream_reader: + # Process messages from the standalone stream + async for event_message in standalone_stream_reader: + # For the standalone stream, we handle: + # - JSONRPCNotification (server sends notifications to client) + # - JSONRPCRequest (server sends requests to client) + # We should NOT receive JSONRPCResponse + + # Send the message via SSE + event_data = self._create_event_data(event_message) + await sse_stream_writer.send(event_data) + except Exception: + logger.exception("Error in standalone SSE writer") + finally: + logger.debug("Closing standalone SSE writer") + await self._clean_up_memory_streams(GET_STREAM_KEY) + + # Create and start EventSourceResponse + response = EventSourceResponse( + content=sse_stream_reader, + data_sender_callable=standalone_sse_writer, + headers=headers, + ) + + try: + # This will send headers immediately and establish the SSE connection + await response(request.scope, request.receive, send) + except Exception: + logger.exception("Error in standalone SSE response") + await sse_stream_writer.aclose() + await sse_stream_reader.aclose() + await self._clean_up_memory_streams(GET_STREAM_KEY) + + async def _handle_delete_request(self, request: Request, send: Send) -> None: # pragma: no cover + """Handle DELETE requests for explicit session termination.""" + # Validate session ID + if not self.mcp_session_id: + # If no session ID set, return Method Not Allowed + response = self._create_error_response( + "Method Not Allowed: Session termination not supported", + HTTPStatus.METHOD_NOT_ALLOWED, + ) + await response(request.scope, request.receive, send) + return + + if not await self._validate_request_headers(request, send): + return + + await self.terminate() + + response = self._create_json_response( + None, + HTTPStatus.OK, + ) + await response(request.scope, request.receive, send) + + async def terminate(self) -> None: + """Terminate the current session, closing all streams. + + Once terminated, all requests with this session ID will receive 404 Not Found. + """ + + self._terminated = True + logger.info(f"Terminating session: {self.mcp_session_id}") + + # We need a copy of the keys to avoid modification during iteration + request_stream_keys = list(self._request_streams.keys()) + + # Close all request streams asynchronously + for key in request_stream_keys: # pragma: no cover + await self._clean_up_memory_streams(key) + + # Clear the request streams dictionary immediately + self._request_streams.clear() + try: + if self._read_stream_writer is not None: # pragma: no branch + await self._read_stream_writer.aclose() + if self._read_stream is not None: # pragma: no branch + await self._read_stream.aclose() + if self._write_stream_reader is not None: # pragma: no branch + await self._write_stream_reader.aclose() + if self._write_stream is not None: # pragma: no branch + await self._write_stream.aclose() + except Exception as e: # pragma: no cover + # During cleanup, we catch all exceptions since streams might be in various states + logger.debug(f"Error closing streams: {e}") + + async def _handle_unsupported_request(self, request: Request, send: Send) -> None: # pragma: no cover + """Handle unsupported HTTP methods.""" + headers = { + "Content-Type": CONTENT_TYPE_JSON, + "Allow": "GET, POST, DELETE", + } + if self.mcp_session_id: + headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id + + response = self._create_error_response( + "Method Not Allowed", + HTTPStatus.METHOD_NOT_ALLOWED, + headers=headers, + ) + await response(request.scope, request.receive, send) + + async def _validate_request_headers(self, request: Request, send: Send) -> bool: # pragma: no cover + if not await self._validate_session(request, send): + return False + if not await self._validate_protocol_version(request, send): + return False + return True + + async def _validate_session(self, request: Request, send: Send) -> bool: # pragma: no cover + """Validate the session ID in the request.""" + if not self.mcp_session_id: + # If we're not using session IDs, return True + return True + + # Get the session ID from the request headers + request_session_id = self._get_session_id(request) + + # If no session ID provided but required, return error + if not request_session_id: + response = self._create_error_response( + "Bad Request: Missing session ID", + HTTPStatus.BAD_REQUEST, + ) + await response(request.scope, request.receive, send) + return False + + # If session ID doesn't match, return error + if request_session_id != self.mcp_session_id: + response = self._create_error_response( + "Not Found: Invalid or expired session ID", + HTTPStatus.NOT_FOUND, + ) + await response(request.scope, request.receive, send) + return False + + return True + + async def _validate_protocol_version(self, request: Request, send: Send) -> bool: # pragma: no cover + """Validate the protocol version header in the request.""" + # Get the protocol version from the request headers + protocol_version = request.headers.get(MCP_PROTOCOL_VERSION_HEADER) + + # If no protocol version provided, assume default version + if protocol_version is None: + protocol_version = DEFAULT_NEGOTIATED_VERSION + + # Check if the protocol version is supported + if protocol_version not in SUPPORTED_PROTOCOL_VERSIONS: + supported_versions = ", ".join(SUPPORTED_PROTOCOL_VERSIONS) + response = self._create_error_response( + f"Bad Request: Unsupported protocol version: {protocol_version}. " + + f"Supported versions: {supported_versions}", + HTTPStatus.BAD_REQUEST, + ) + await response(request.scope, request.receive, send) + return False + + return True + + async def _replay_events(self, last_event_id: str, request: Request, send: Send) -> None: # pragma: no cover + """ + Replays events that would have been sent after the specified event ID. + Only used when resumability is enabled. + """ + event_store = self._event_store + if not event_store: + return + + try: + headers = { + "Cache-Control": "no-cache, no-transform", + "Connection": "keep-alive", + "Content-Type": CONTENT_TYPE_SSE, + } + + if self.mcp_session_id: + headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id + + # Get protocol version from header (already validated in _validate_protocol_version) + replay_protocol_version = request.headers.get(MCP_PROTOCOL_VERSION_HEADER, DEFAULT_NEGOTIATED_VERSION) + + # Create SSE stream for replay + sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0) + + async def replay_sender(): + try: + async with sse_stream_writer: + # Define an async callback for sending events + async def send_event(event_message: EventMessage) -> None: + event_data = self._create_event_data(event_message) + await sse_stream_writer.send(event_data) + + # Replay past events and get the stream ID + stream_id = await event_store.replay_events_after(last_event_id, send_event) + + # If stream ID not in mapping, create it + if stream_id and stream_id not in self._request_streams: + # Register SSE writer so close_sse_stream() can close it + self._sse_stream_writers[stream_id] = sse_stream_writer + + # Send priming event for this new connection + await self._maybe_send_priming_event(stream_id, sse_stream_writer, replay_protocol_version) + + # Create new request streams for this connection + self._request_streams[stream_id] = anyio.create_memory_object_stream[EventMessage](0) + msg_reader = self._request_streams[stream_id][1] + + # Forward messages to SSE + async with msg_reader: + async for event_message in msg_reader: + event_data = self._create_event_data(event_message) + + await sse_stream_writer.send(event_data) + except anyio.ClosedResourceError: + # Expected when close_sse_stream() is called + logger.debug("Replay SSE stream closed by close_sse_stream()") + except Exception: + logger.exception("Error in replay sender") + + # Create and start EventSourceResponse + response = EventSourceResponse( + content=sse_stream_reader, + data_sender_callable=replay_sender, + headers=headers, + ) + + try: + await response(request.scope, request.receive, send) + except Exception: + logger.exception("Error in replay response") + finally: + await sse_stream_writer.aclose() + await sse_stream_reader.aclose() + + except Exception: + logger.exception("Error replaying events") + response = self._create_error_response( + "Error replaying events", + HTTPStatus.INTERNAL_SERVER_ERROR, + INTERNAL_ERROR, + ) + await response(request.scope, request.receive, send) + + @asynccontextmanager + async def connect( + self, + ) -> AsyncGenerator[ + tuple[ + MemoryObjectReceiveStream[SessionMessage | Exception], + MemoryObjectSendStream[SessionMessage], + ], + None, + ]: + """Context manager that provides read and write streams for a connection. + + Yields: + Tuple of (read_stream, write_stream) for bidirectional communication + """ + + # Create the memory streams for this connection + + read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0) + write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0) + + # Store the streams + self._read_stream_writer = read_stream_writer + self._read_stream = read_stream + self._write_stream_reader = write_stream_reader + self._write_stream = write_stream + + # Start a task group for message routing + async with anyio.create_task_group() as tg: + # Create a message router that distributes messages to request streams + async def message_router(): # pragma: no cover + try: + async for session_message in write_stream_reader: + # Determine which request stream(s) should receive this message + message = session_message.message + target_request_id = None + # Check if this is a response + if isinstance(message.root, JSONRPCResponse | JSONRPCError): + response_id = str(message.root.id) + # If this response is for an existing request stream, + # send it there + target_request_id = response_id + # Extract related_request_id from meta if it exists + elif ( + session_message.metadata is not None + and isinstance( + session_message.metadata, + ServerMessageMetadata, + ) + and session_message.metadata.related_request_id is not None + ): + target_request_id = str(session_message.metadata.related_request_id) + + request_stream_id = target_request_id if target_request_id is not None else GET_STREAM_KEY + + # Store the event if we have an event store, + # regardless of whether a client is connected + # messages will be replayed on the re-connect + event_id = None + if self._event_store: + event_id = await self._event_store.store_event(request_stream_id, message) + logger.debug(f"Stored {event_id} from {request_stream_id}") + + if request_stream_id in self._request_streams: + try: + # Send both the message and the event ID + await self._request_streams[request_stream_id][0].send(EventMessage(message, event_id)) + except ( + anyio.BrokenResourceError, + anyio.ClosedResourceError, + ): + # Stream might be closed, remove from registry + self._request_streams.pop(request_stream_id, None) + else: + logger.debug( + f"""Request stream {request_stream_id} not found + for message. Still processing message as the client + might reconnect and replay.""" + ) + except anyio.ClosedResourceError: + if self._terminated: + logger.debug("Read stream closed by client") + else: + logger.exception("Unexpected closure of read stream in message router") + except Exception: + logger.exception("Error in message router") + + # Start the message router + tg.start_soon(message_router) + + try: + # Yield the streams for the caller to use + yield read_stream, write_stream + finally: + for stream_id in list(self._request_streams.keys()): # pragma: no cover + await self._clean_up_memory_streams(stream_id) + self._request_streams.clear() + + # Clean up the read and write streams + try: + await read_stream_writer.aclose() + await read_stream.aclose() + await write_stream_reader.aclose() + await write_stream.aclose() + except Exception as e: # pragma: no cover + # During cleanup, we catch all exceptions since streams might be in various states + logger.debug(f"Error closing streams: {e}") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py new file mode 100644 index 0000000..6a16724 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py @@ -0,0 +1,297 @@ +"""StreamableHTTP Session Manager for MCP servers.""" + +from __future__ import annotations + +import contextlib +import logging +from collections.abc import AsyncIterator +from http import HTTPStatus +from typing import Any +from uuid import uuid4 + +import anyio +from anyio.abc import TaskStatus +from starlette.requests import Request +from starlette.responses import Response +from starlette.types import Receive, Scope, Send + +from mcp.server.lowlevel.server import Server as MCPServer +from mcp.server.streamable_http import ( + MCP_SESSION_ID_HEADER, + EventStore, + StreamableHTTPServerTransport, +) +from mcp.server.transport_security import TransportSecuritySettings +from mcp.types import INVALID_REQUEST, ErrorData, JSONRPCError + +logger = logging.getLogger(__name__) + + +class StreamableHTTPSessionManager: + """ + Manages StreamableHTTP sessions with optional resumability via event store. + + This class abstracts away the complexity of session management, event storage, + and request handling for StreamableHTTP transports. It handles: + + 1. Session tracking for clients + 2. Resumability via an optional event store + 3. Connection management and lifecycle + 4. Request handling and transport setup + + Important: Only one StreamableHTTPSessionManager instance should be created + per application. The instance cannot be reused after its run() context has + completed. If you need to restart the manager, create a new instance. + + Args: + app: The MCP server instance + event_store: Optional event store for resumability support. + If provided, enables resumable connections where clients + can reconnect and receive missed events. + If None, sessions are still tracked but not resumable. + json_response: Whether to use JSON responses instead of SSE streams + stateless: If True, creates a completely fresh transport for each request + with no session tracking or state persistence between requests. + security_settings: Optional transport security settings. + retry_interval: Retry interval in milliseconds to suggest to clients in SSE + retry field. Used for SSE polling behavior. + """ + + def __init__( + self, + app: MCPServer[Any, Any], + event_store: EventStore | None = None, + json_response: bool = False, + stateless: bool = False, + security_settings: TransportSecuritySettings | None = None, + retry_interval: int | None = None, + ): + self.app = app + self.event_store = event_store + self.json_response = json_response + self.stateless = stateless + self.security_settings = security_settings + self.retry_interval = retry_interval + + # Session tracking (only used if not stateless) + self._session_creation_lock = anyio.Lock() + self._server_instances: dict[str, StreamableHTTPServerTransport] = {} + + # The task group will be set during lifespan + self._task_group = None + # Thread-safe tracking of run() calls + self._run_lock = anyio.Lock() + self._has_started = False + + @contextlib.asynccontextmanager + async def run(self) -> AsyncIterator[None]: + """ + Run the session manager with proper lifecycle management. + + This creates and manages the task group for all session operations. + + Important: This method can only be called once per instance. The same + StreamableHTTPSessionManager instance cannot be reused after this + context manager exits. Create a new instance if you need to restart. + + Use this in the lifespan context manager of your Starlette app: + + @contextlib.asynccontextmanager + async def lifespan(app: Starlette) -> AsyncIterator[None]: + async with session_manager.run(): + yield + """ + # Thread-safe check to ensure run() is only called once + async with self._run_lock: + if self._has_started: + raise RuntimeError( + "StreamableHTTPSessionManager .run() can only be called " + "once per instance. Create a new instance if you need to run again." + ) + self._has_started = True + + async with anyio.create_task_group() as tg: + # Store the task group for later use + self._task_group = tg + logger.info("StreamableHTTP session manager started") + try: + yield # Let the application run + finally: + logger.info("StreamableHTTP session manager shutting down") + # Cancel task group to stop all spawned tasks + tg.cancel_scope.cancel() + self._task_group = None + # Clear any remaining server instances + self._server_instances.clear() + + async def handle_request( + self, + scope: Scope, + receive: Receive, + send: Send, + ) -> None: + """ + Process ASGI request with proper session handling and transport setup. + + Dispatches to the appropriate handler based on stateless mode. + + Args: + scope: ASGI scope + receive: ASGI receive function + send: ASGI send function + """ + if self._task_group is None: + raise RuntimeError("Task group is not initialized. Make sure to use run().") + + # Dispatch to the appropriate handler + if self.stateless: + await self._handle_stateless_request(scope, receive, send) + else: + await self._handle_stateful_request(scope, receive, send) + + async def _handle_stateless_request( + self, + scope: Scope, + receive: Receive, + send: Send, + ) -> None: + """ + Process request in stateless mode - creating a new transport for each request. + + Args: + scope: ASGI scope + receive: ASGI receive function + send: ASGI send function + """ + logger.debug("Stateless mode: Creating new transport for this request") + # No session ID needed in stateless mode + http_transport = StreamableHTTPServerTransport( + mcp_session_id=None, # No session tracking in stateless mode + is_json_response_enabled=self.json_response, + event_store=None, # No event store in stateless mode + security_settings=self.security_settings, + ) + + # Start server in a new task + async def run_stateless_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED): + async with http_transport.connect() as streams: + read_stream, write_stream = streams + task_status.started() + try: + await self.app.run( + read_stream, + write_stream, + self.app.create_initialization_options(), + stateless=True, + ) + except Exception: # pragma: no cover + logger.exception("Stateless session crashed") + + # Assert task group is not None for type checking + assert self._task_group is not None + # Start the server task + await self._task_group.start(run_stateless_server) + + # Handle the HTTP request and return the response + await http_transport.handle_request(scope, receive, send) + + # Terminate the transport after the request is handled + await http_transport.terminate() + + async def _handle_stateful_request( + self, + scope: Scope, + receive: Receive, + send: Send, + ) -> None: + """ + Process request in stateful mode - maintaining session state between requests. + + Args: + scope: ASGI scope + receive: ASGI receive function + send: ASGI send function + """ + request = Request(scope, receive) + request_mcp_session_id = request.headers.get(MCP_SESSION_ID_HEADER) + + # Existing session case + if request_mcp_session_id is not None and request_mcp_session_id in self._server_instances: # pragma: no cover + transport = self._server_instances[request_mcp_session_id] + logger.debug("Session already exists, handling request directly") + await transport.handle_request(scope, receive, send) + return + + if request_mcp_session_id is None: + # New session case + logger.debug("Creating new transport") + async with self._session_creation_lock: + new_session_id = uuid4().hex + http_transport = StreamableHTTPServerTransport( + mcp_session_id=new_session_id, + is_json_response_enabled=self.json_response, + event_store=self.event_store, # May be None (no resumability) + security_settings=self.security_settings, + retry_interval=self.retry_interval, + ) + + assert http_transport.mcp_session_id is not None + self._server_instances[http_transport.mcp_session_id] = http_transport + logger.info(f"Created new transport with session ID: {new_session_id}") + + # Define the server runner + async def run_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED) -> None: + async with http_transport.connect() as streams: + read_stream, write_stream = streams + task_status.started() + try: + await self.app.run( + read_stream, + write_stream, + self.app.create_initialization_options(), + stateless=False, # Stateful mode + ) + except Exception as e: + logger.error( + f"Session {http_transport.mcp_session_id} crashed: {e}", + exc_info=True, + ) + finally: + # Only remove from instances if not terminated + if ( # pragma: no branch + http_transport.mcp_session_id + and http_transport.mcp_session_id in self._server_instances + and not http_transport.is_terminated + ): + logger.info( + "Cleaning up crashed session " + f"{http_transport.mcp_session_id} from " + "active instances." + ) + del self._server_instances[http_transport.mcp_session_id] + + # Assert task group is not None for type checking + assert self._task_group is not None + # Start the server task + await self._task_group.start(run_server) + + # Handle the HTTP request and return the response + await http_transport.handle_request(scope, receive, send) + else: + # Unknown or expired session ID - return 404 per MCP spec + # TODO: Align error code once spec clarifies + # See: https://github.com/modelcontextprotocol/python-sdk/issues/1821 + error_response = JSONRPCError( + jsonrpc="2.0", + id="server-error", + error=ErrorData( + code=INVALID_REQUEST, + message="Session not found", + ), + ) + response = Response( + content=error_response.model_dump_json(by_alias=True, exclude_none=True), + status_code=HTTPStatus.NOT_FOUND, + media_type="application/json", + ) + await response(scope, receive, send) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py new file mode 100644 index 0000000..ee1e450 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py @@ -0,0 +1,127 @@ +"""DNS rebinding protection for MCP server transports.""" + +import logging + +from pydantic import BaseModel, Field +from starlette.requests import Request +from starlette.responses import Response + +logger = logging.getLogger(__name__) + + +class TransportSecuritySettings(BaseModel): + """Settings for MCP transport security features. + + These settings help protect against DNS rebinding attacks by validating + incoming request headers. + """ + + enable_dns_rebinding_protection: bool = Field( + default=True, + description="Enable DNS rebinding protection (recommended for production)", + ) + + allowed_hosts: list[str] = Field( + default=[], + description="List of allowed Host header values. Only applies when " + + "enable_dns_rebinding_protection is True.", + ) + + allowed_origins: list[str] = Field( + default=[], + description="List of allowed Origin header values. Only applies when " + + "enable_dns_rebinding_protection is True.", + ) + + +class TransportSecurityMiddleware: + """Middleware to enforce DNS rebinding protection for MCP transport endpoints.""" + + def __init__(self, settings: TransportSecuritySettings | None = None): + # If not specified, disable DNS rebinding protection by default + # for backwards compatibility + self.settings = settings or TransportSecuritySettings(enable_dns_rebinding_protection=False) + + def _validate_host(self, host: str | None) -> bool: # pragma: no cover + """Validate the Host header against allowed values.""" + if not host: + logger.warning("Missing Host header in request") + return False + + # Check exact match first + if host in self.settings.allowed_hosts: + return True + + # Check wildcard port patterns + for allowed in self.settings.allowed_hosts: + if allowed.endswith(":*"): + # Extract base host from pattern + base_host = allowed[:-2] + # Check if the actual host starts with base host and has a port + if host.startswith(base_host + ":"): + return True + + logger.warning(f"Invalid Host header: {host}") + return False + + def _validate_origin(self, origin: str | None) -> bool: # pragma: no cover + """Validate the Origin header against allowed values.""" + # Origin can be absent for same-origin requests + if not origin: + return True + + # Check exact match first + if origin in self.settings.allowed_origins: + return True + + # Check wildcard port patterns + for allowed in self.settings.allowed_origins: + if allowed.endswith(":*"): + # Extract base origin from pattern + base_origin = allowed[:-2] + # Check if the actual origin starts with base origin and has a port + if origin.startswith(base_origin + ":"): + return True + + logger.warning(f"Invalid Origin header: {origin}") + return False + + def _validate_content_type(self, content_type: str | None) -> bool: # pragma: no cover + """Validate the Content-Type header for POST requests.""" + if not content_type: + logger.warning("Missing Content-Type header in POST request") + return False + + # Content-Type must start with application/json + if not content_type.lower().startswith("application/json"): + logger.warning(f"Invalid Content-Type header: {content_type}") + return False + + return True + + async def validate_request(self, request: Request, is_post: bool = False) -> Response | None: + """Validate request headers for DNS rebinding protection. + + Returns None if validation passes, or an error Response if validation fails. + """ + # Always validate Content-Type for POST requests + if is_post: # pragma: no branch + content_type = request.headers.get("content-type") + if not self._validate_content_type(content_type): # pragma: no cover + return Response("Invalid Content-Type header", status_code=400) + + # Skip remaining validation if DNS rebinding protection is disabled + if not self.settings.enable_dns_rebinding_protection: + return None + + # Validate Host header # pragma: no cover + host = request.headers.get("host") # pragma: no cover + if not self._validate_host(host): # pragma: no cover + return Response("Invalid Host header", status_code=421) # pragma: no cover + + # Validate Origin header # pragma: no cover + origin = request.headers.get("origin") # pragma: no cover + if not self._validate_origin(origin): # pragma: no cover + return Response("Invalid Origin header", status_code=403) # pragma: no cover + + return None # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py new file mode 100644 index 0000000..2ccd705 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py @@ -0,0 +1,104 @@ +""" +Shared validation functions for server requests. + +This module provides validation logic for sampling and elicitation requests +that is shared across normal and task-augmented code paths. +""" + +from mcp.shared.exceptions import McpError +from mcp.types import ( + INVALID_PARAMS, + ClientCapabilities, + ErrorData, + SamplingMessage, + Tool, + ToolChoice, +) + + +def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool: + """ + Check if the client supports sampling tools capability. + + Args: + client_caps: The client's declared capabilities + + Returns: + True if client supports sampling.tools, False otherwise + """ + if client_caps is None: + return False + if client_caps.sampling is None: + return False + if client_caps.sampling.tools is None: + return False + return True + + +def validate_sampling_tools( + client_caps: ClientCapabilities | None, + tools: list[Tool] | None, + tool_choice: ToolChoice | None, +) -> None: + """ + Validate that the client supports sampling tools if tools are being used. + + Args: + client_caps: The client's declared capabilities + tools: The tools list, if provided + tool_choice: The tool choice setting, if provided + + Raises: + McpError: If tools/tool_choice are provided but client doesn't support them + """ + if tools is not None or tool_choice is not None: + if not check_sampling_tools_capability(client_caps): + raise McpError( + ErrorData( + code=INVALID_PARAMS, + message="Client does not support sampling tools capability", + ) + ) + + +def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None: + """ + Validate tool_use/tool_result message structure per SEP-1577. + + This validation ensures: + 1. Messages with tool_result content contain ONLY tool_result content + 2. tool_result messages are preceded by a message with tool_use + 3. tool_result IDs match the tool_use IDs from the previous message + + See: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577 + + Args: + messages: The list of sampling messages to validate + + Raises: + ValueError: If the message structure is invalid + """ + if not messages: + return + + last_content = messages[-1].content_as_list + has_tool_results = any(c.type == "tool_result" for c in last_content) + + previous_content = messages[-2].content_as_list if len(messages) >= 2 else None + has_previous_tool_use = previous_content and any(c.type == "tool_use" for c in previous_content) + + if has_tool_results: + # Per spec: "SamplingMessage with tool result content blocks + # MUST NOT contain other content types." + if any(c.type != "tool_result" for c in last_content): + raise ValueError("The last message must contain only tool_result content if any is present") + if previous_content is None: + raise ValueError("tool_result requires a previous message containing tool_use") + if not has_previous_tool_use: + raise ValueError("tool_result blocks do not match any tool_use in the previous message") + + if has_previous_tool_use and previous_content: + tool_use_ids = {c.id for c in previous_content if c.type == "tool_use"} + tool_result_ids = {c.toolUseId for c in last_content if c.type == "tool_result"} + if tool_use_ids != tool_result_ids: + raise ValueError("ids of tool_result blocks and tool_use blocks from previous message do not match") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py new file mode 100644 index 0000000..5d5efd1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py @@ -0,0 +1,62 @@ +import logging +from contextlib import asynccontextmanager + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic_core import ValidationError +from starlette.types import Receive, Scope, Send +from starlette.websockets import WebSocket + +import mcp.types as types +from mcp.shared.message import SessionMessage + +logger = logging.getLogger(__name__) + + +@asynccontextmanager # pragma: no cover +async def websocket_server(scope: Scope, receive: Receive, send: Send): + """ + WebSocket server transport for MCP. This is an ASGI application, suitable to be + used with a framework like Starlette and a server like Hypercorn. + """ + + websocket = WebSocket(scope, receive, send) + await websocket.accept(subprotocol="mcp") + + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] + + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] + + read_stream_writer, read_stream = anyio.create_memory_object_stream(0) + write_stream, write_stream_reader = anyio.create_memory_object_stream(0) + + async def ws_reader(): + try: + async with read_stream_writer: + async for msg in websocket.iter_text(): + try: + client_message = types.JSONRPCMessage.model_validate_json(msg) + except ValidationError as exc: + await read_stream_writer.send(exc) + continue + + session_message = SessionMessage(client_message) + await read_stream_writer.send(session_message) + except anyio.ClosedResourceError: + await websocket.close() + + async def ws_writer(): + try: + async with write_stream_reader: + async for session_message in write_stream_reader: + obj = session_message.message.model_dump_json(by_alias=True, exclude_none=True) + await websocket.send_text(obj) + except anyio.ClosedResourceError: + await websocket.close() + + async with anyio.create_task_group() as tg: + tg.start_soon(ws_reader) + tg.start_soon(ws_writer) + yield (read_stream, write_stream) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py new file mode 100644 index 0000000..945ef80 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py @@ -0,0 +1,87 @@ +"""Utilities for creating standardized httpx AsyncClient instances.""" + +from typing import Any, Protocol + +import httpx + +__all__ = ["create_mcp_http_client", "MCP_DEFAULT_TIMEOUT", "MCP_DEFAULT_SSE_READ_TIMEOUT"] + +# Default MCP timeout configuration +MCP_DEFAULT_TIMEOUT = 30.0 # General operations (seconds) +MCP_DEFAULT_SSE_READ_TIMEOUT = 300.0 # SSE streams - 5 minutes (seconds) + + +class McpHttpClientFactory(Protocol): # pragma: no branch + def __call__( # pragma: no branch + self, + headers: dict[str, str] | None = None, + timeout: httpx.Timeout | None = None, + auth: httpx.Auth | None = None, + ) -> httpx.AsyncClient: ... + + +def create_mcp_http_client( + headers: dict[str, str] | None = None, + timeout: httpx.Timeout | None = None, + auth: httpx.Auth | None = None, +) -> httpx.AsyncClient: + """Create a standardized httpx AsyncClient with MCP defaults. + + This function provides common defaults used throughout the MCP codebase: + - follow_redirects=True (always enabled) + - Default timeout of 30 seconds if not specified + + Args: + headers: Optional headers to include with all requests. + timeout: Request timeout as httpx.Timeout object. + Defaults to 30 seconds if not specified. + auth: Optional authentication handler. + + Returns: + Configured httpx.AsyncClient instance with MCP defaults. + + Note: + The returned AsyncClient must be used as a context manager to ensure + proper cleanup of connections. + + Examples: + # Basic usage with MCP defaults + async with create_mcp_http_client() as client: + response = await client.get("https://api.example.com") + + # With custom headers + headers = {"Authorization": "Bearer token"} + async with create_mcp_http_client(headers) as client: + response = await client.get("/endpoint") + + # With both custom headers and timeout + timeout = httpx.Timeout(60.0, read=300.0) + async with create_mcp_http_client(headers, timeout) as client: + response = await client.get("/long-request") + + # With authentication + from httpx import BasicAuth + auth = BasicAuth(username="user", password="pass") + async with create_mcp_http_client(headers, timeout, auth) as client: + response = await client.get("/protected-endpoint") + """ + # Set MCP defaults + kwargs: dict[str, Any] = { + "follow_redirects": True, + } + + # Handle timeout + if timeout is None: + kwargs["timeout"] = httpx.Timeout(MCP_DEFAULT_TIMEOUT, read=MCP_DEFAULT_SSE_READ_TIMEOUT) + else: + kwargs["timeout"] = timeout + + # Handle headers + if headers is not None: + kwargs["headers"] = headers + + # Handle authentication + if auth is not None: # pragma: no cover + kwargs["auth"] = auth + + return httpx.AsyncClient(**kwargs) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py new file mode 100644 index 0000000..d329099 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py @@ -0,0 +1,159 @@ +from typing import Any, Literal + +from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator + + +class OAuthToken(BaseModel): + """ + See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 + """ + + access_token: str + token_type: Literal["Bearer"] = "Bearer" + expires_in: int | None = None + scope: str | None = None + refresh_token: str | None = None + + @field_validator("token_type", mode="before") + @classmethod + def normalize_token_type(cls, v: str | None) -> str | None: + if isinstance(v, str): + # Bearer is title-cased in the spec, so we normalize it + # https://datatracker.ietf.org/doc/html/rfc6750#section-4 + return v.title() + return v # pragma: no cover + + +class InvalidScopeError(Exception): + def __init__(self, message: str): + self.message = message + + +class InvalidRedirectUriError(Exception): + def __init__(self, message: str): + self.message = message + + +class OAuthClientMetadata(BaseModel): + """ + RFC 7591 OAuth 2.0 Dynamic Client Registration metadata. + See https://datatracker.ietf.org/doc/html/rfc7591#section-2 + for the full specification. + """ + + redirect_uris: list[AnyUrl] | None = Field(..., min_length=1) + # supported auth methods for the token endpoint + token_endpoint_auth_method: ( + Literal["none", "client_secret_post", "client_secret_basic", "private_key_jwt"] | None + ) = None + # supported grant_types of this implementation + grant_types: list[ + Literal["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"] | str + ] = [ + "authorization_code", + "refresh_token", + ] + # The MCP spec requires the "code" response type, but OAuth + # servers may also return additional types they support + response_types: list[str] = ["code"] + scope: str | None = None + + # these fields are currently unused, but we support & store them for potential + # future use + client_name: str | None = None + client_uri: AnyHttpUrl | None = None + logo_uri: AnyHttpUrl | None = None + contacts: list[str] | None = None + tos_uri: AnyHttpUrl | None = None + policy_uri: AnyHttpUrl | None = None + jwks_uri: AnyHttpUrl | None = None + jwks: Any | None = None + software_id: str | None = None + software_version: str | None = None + + def validate_scope(self, requested_scope: str | None) -> list[str] | None: + if requested_scope is None: + return None + requested_scopes = requested_scope.split(" ") + allowed_scopes = [] if self.scope is None else self.scope.split(" ") + for scope in requested_scopes: + if scope not in allowed_scopes: # pragma: no branch + raise InvalidScopeError(f"Client was not registered with scope {scope}") + return requested_scopes # pragma: no cover + + def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl: + if redirect_uri is not None: + # Validate redirect_uri against client's registered redirect URIs + if self.redirect_uris is None or redirect_uri not in self.redirect_uris: + raise InvalidRedirectUriError(f"Redirect URI '{redirect_uri}' not registered for client") + return redirect_uri + elif self.redirect_uris is not None and len(self.redirect_uris) == 1: + return self.redirect_uris[0] + else: + raise InvalidRedirectUriError("redirect_uri must be specified when client has multiple registered URIs") + + +class OAuthClientInformationFull(OAuthClientMetadata): + """ + RFC 7591 OAuth 2.0 Dynamic Client Registration full response + (client information plus metadata). + """ + + client_id: str | None = None + client_secret: str | None = None + client_id_issued_at: int | None = None + client_secret_expires_at: int | None = None + + +class OAuthMetadata(BaseModel): + """ + RFC 8414 OAuth 2.0 Authorization Server Metadata. + See https://datatracker.ietf.org/doc/html/rfc8414#section-2 + """ + + issuer: AnyHttpUrl + authorization_endpoint: AnyHttpUrl + token_endpoint: AnyHttpUrl + registration_endpoint: AnyHttpUrl | None = None + scopes_supported: list[str] | None = None + response_types_supported: list[str] = ["code"] + response_modes_supported: list[str] | None = None + grant_types_supported: list[str] | None = None + token_endpoint_auth_methods_supported: list[str] | None = None + token_endpoint_auth_signing_alg_values_supported: list[str] | None = None + service_documentation: AnyHttpUrl | None = None + ui_locales_supported: list[str] | None = None + op_policy_uri: AnyHttpUrl | None = None + op_tos_uri: AnyHttpUrl | None = None + revocation_endpoint: AnyHttpUrl | None = None + revocation_endpoint_auth_methods_supported: list[str] | None = None + revocation_endpoint_auth_signing_alg_values_supported: list[str] | None = None + introspection_endpoint: AnyHttpUrl | None = None + introspection_endpoint_auth_methods_supported: list[str] | None = None + introspection_endpoint_auth_signing_alg_values_supported: list[str] | None = None + code_challenge_methods_supported: list[str] | None = None + client_id_metadata_document_supported: bool | None = None + + +class ProtectedResourceMetadata(BaseModel): + """ + RFC 9728 OAuth 2.0 Protected Resource Metadata. + See https://datatracker.ietf.org/doc/html/rfc9728#section-2 + """ + + resource: AnyHttpUrl + authorization_servers: list[AnyHttpUrl] = Field(..., min_length=1) + jwks_uri: AnyHttpUrl | None = None + scopes_supported: list[str] | None = None + bearer_methods_supported: list[str] | None = Field(default=["header"]) # MCP only supports header method + resource_signing_alg_values_supported: list[str] | None = None + resource_name: str | None = None + resource_documentation: AnyHttpUrl | None = None + resource_policy_uri: AnyHttpUrl | None = None + resource_tos_uri: AnyHttpUrl | None = None + # tls_client_certificate_bound_access_tokens default is False, but ommited here for clarity + tls_client_certificate_bound_access_tokens: bool | None = None + authorization_details_types_supported: list[str] | None = None + dpop_signing_alg_values_supported: list[str] | None = None + # dpop_bound_access_tokens_required default is False, but ommited here for clarity + dpop_bound_access_tokens_required: bool | None = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py new file mode 100644 index 0000000..8f3c542 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py @@ -0,0 +1,85 @@ +"""Utilities for OAuth 2.0 Resource Indicators (RFC 8707) and PKCE (RFC 7636).""" + +import time +from urllib.parse import urlparse, urlsplit, urlunsplit + +from pydantic import AnyUrl, HttpUrl + + +def resource_url_from_server_url(url: str | HttpUrl | AnyUrl) -> str: + """Convert server URL to canonical resource URL per RFC 8707. + + RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component". + Returns absolute URI with lowercase scheme/host for canonical form. + + Args: + url: Server URL to convert + + Returns: + Canonical resource URL string + """ + # Convert to string if needed + url_str = str(url) + + # Parse the URL and remove fragment, create canonical form + parsed = urlsplit(url_str) + canonical = urlunsplit(parsed._replace(scheme=parsed.scheme.lower(), netloc=parsed.netloc.lower(), fragment="")) + + return canonical + + +def check_resource_allowed(requested_resource: str, configured_resource: str) -> bool: + """Check if a requested resource URL matches a configured resource URL. + + A requested resource matches if it has the same scheme, domain, port, + and its path starts with the configured resource's path. This allows + hierarchical matching where a token for a parent resource can be used + for child resources. + + Args: + requested_resource: The resource URL being requested + configured_resource: The resource URL that has been configured + + Returns: + True if the requested resource matches the configured resource + """ + # Parse both URLs + requested = urlparse(requested_resource) + configured = urlparse(configured_resource) + + # Compare scheme, host, and port (origin) + if requested.scheme.lower() != configured.scheme.lower() or requested.netloc.lower() != configured.netloc.lower(): + return False + + # Handle cases like requested=/foo and configured=/foo/ + requested_path = requested.path + configured_path = configured.path + + # If requested path is shorter, it cannot be a child + if len(requested_path) < len(configured_path): + return False + + # Check if the requested path starts with the configured path + # Ensure both paths end with / for proper comparison + # This ensures that paths like "/api123" don't incorrectly match "/api" + if not requested_path.endswith("/"): + requested_path += "/" + if not configured_path.endswith("/"): + configured_path += "/" + + return requested_path.startswith(configured_path) + + +def calculate_token_expiry(expires_in: int | str | None) -> float | None: + """Calculate token expiry timestamp from expires_in seconds. + + Args: + expires_in: Seconds until token expiration (may be string from some servers) + + Returns: + Unix timestamp when token expires, or None if no expiry specified + """ + if expires_in is None: + return None # pragma: no cover + # Defensive: handle servers that return expires_in as string + return time.time() + int(expires_in) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py new file mode 100644 index 0000000..5cf6588 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py @@ -0,0 +1,32 @@ +""" +Request context for MCP handlers. +""" + +from dataclasses import dataclass, field +from typing import Any, Generic + +from typing_extensions import TypeVar + +from mcp.shared.message import CloseSSEStreamCallback +from mcp.shared.session import BaseSession +from mcp.types import RequestId, RequestParams + +SessionT = TypeVar("SessionT", bound=BaseSession[Any, Any, Any, Any, Any]) +LifespanContextT = TypeVar("LifespanContextT") +RequestT = TypeVar("RequestT", default=Any) + + +@dataclass +class RequestContext(Generic[SessionT, LifespanContextT, RequestT]): + request_id: RequestId + meta: RequestParams.Meta | None + session: SessionT + lifespan_context: LifespanContextT + # NOTE: This is typed as Any to avoid circular imports. The actual type is + # mcp.server.experimental.request_context.Experimental, but importing it here + # triggers mcp.server.__init__ -> fastmcp -> tools -> back to this module. + # The Server sets this to an Experimental instance at runtime. + experimental: Any = field(default=None) + request: RequestT | None = None + close_sse_stream: CloseSSEStreamCallback | None = None + close_standalone_sse_stream: CloseSSEStreamCallback | None = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py new file mode 100644 index 0000000..4943114 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Any, cast + +from mcp.types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData + + +class McpError(Exception): + """ + Exception type raised when an error arrives over an MCP connection. + """ + + error: ErrorData + + def __init__(self, error: ErrorData): + """Initialize McpError.""" + super().__init__(error.message) + self.error = error + + +class UrlElicitationRequiredError(McpError): + """ + Specialized error for when a tool requires URL mode elicitation(s) before proceeding. + + Servers can raise this error from tool handlers to indicate that the client + must complete one or more URL elicitations before the request can be processed. + + Example: + raise UrlElicitationRequiredError([ + ElicitRequestURLParams( + mode="url", + message="Authorization required for your files", + url="https://example.com/oauth/authorize", + elicitationId="auth-001" + ) + ]) + """ + + def __init__( + self, + elicitations: list[ElicitRequestURLParams], + message: str | None = None, + ): + """Initialize UrlElicitationRequiredError.""" + if message is None: + message = f"URL elicitation{'s' if len(elicitations) > 1 else ''} required" + + self._elicitations = elicitations + + error = ErrorData( + code=URL_ELICITATION_REQUIRED, + message=message, + data={"elicitations": [e.model_dump(by_alias=True, exclude_none=True) for e in elicitations]}, + ) + super().__init__(error) + + @property + def elicitations(self) -> list[ElicitRequestURLParams]: + """The list of URL elicitations required before the request can proceed.""" + return self._elicitations + + @classmethod + def from_error(cls, error: ErrorData) -> UrlElicitationRequiredError: + """Reconstruct from an ErrorData received over the wire.""" + if error.code != URL_ELICITATION_REQUIRED: + raise ValueError(f"Expected error code {URL_ELICITATION_REQUIRED}, got {error.code}") + + data = cast(dict[str, Any], error.data or {}) + raw_elicitations = cast(list[dict[str, Any]], data.get("elicitations", [])) + elicitations = [ElicitRequestURLParams.model_validate(e) for e in raw_elicitations] + return cls(elicitations, error.message) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py new file mode 100644 index 0000000..9b1b147 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py @@ -0,0 +1,7 @@ +""" +Pure experimental MCP features (no server dependencies). + +WARNING: These APIs are experimental and may change without notice. + +For server-integrated experimental features, use mcp.server.experimental. +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py new file mode 100644 index 0000000..37d81af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py @@ -0,0 +1,12 @@ +""" +Pure task state management for MCP. + +WARNING: These APIs are experimental and may change without notice. + +Import directly from submodules: +- mcp.shared.experimental.tasks.store.TaskStore +- mcp.shared.experimental.tasks.context.TaskContext +- mcp.shared.experimental.tasks.in_memory_task_store.InMemoryTaskStore +- mcp.shared.experimental.tasks.message_queue.TaskMessageQueue +- mcp.shared.experimental.tasks.helpers.is_terminal +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py new file mode 100644 index 0000000..307fcdd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py @@ -0,0 +1,115 @@ +""" +Tasks capability checking utilities. + +This module provides functions for checking and requiring task-related +capabilities. All tasks capability logic is centralized here to keep +the main session code clean. + +WARNING: These APIs are experimental and may change without notice. +""" + +from mcp.shared.exceptions import McpError +from mcp.types import ( + INVALID_REQUEST, + ClientCapabilities, + ClientTasksCapability, + ErrorData, +) + + +def check_tasks_capability( + required: ClientTasksCapability, + client: ClientTasksCapability, +) -> bool: + """ + Check if client's tasks capability matches the required capability. + + Args: + required: The capability being checked for + client: The client's declared capabilities + + Returns: + True if client has the required capability, False otherwise + """ + if required.requests is None: + return True + if client.requests is None: + return False + + # Check elicitation.create + if required.requests.elicitation is not None: + if client.requests.elicitation is None: + return False + if required.requests.elicitation.create is not None: + if client.requests.elicitation.create is None: + return False + + # Check sampling.createMessage + if required.requests.sampling is not None: + if client.requests.sampling is None: + return False + if required.requests.sampling.createMessage is not None: + if client.requests.sampling.createMessage is None: + return False + + return True + + +def has_task_augmented_elicitation(caps: ClientCapabilities) -> bool: + """Check if capabilities include task-augmented elicitation support.""" + if caps.tasks is None: + return False + if caps.tasks.requests is None: + return False + if caps.tasks.requests.elicitation is None: + return False + return caps.tasks.requests.elicitation.create is not None + + +def has_task_augmented_sampling(caps: ClientCapabilities) -> bool: + """Check if capabilities include task-augmented sampling support.""" + if caps.tasks is None: + return False + if caps.tasks.requests is None: + return False + if caps.tasks.requests.sampling is None: + return False + return caps.tasks.requests.sampling.createMessage is not None + + +def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) -> None: + """ + Raise McpError if client doesn't support task-augmented elicitation. + + Args: + client_caps: The client's declared capabilities, or None if not initialized + + Raises: + McpError: If client doesn't support task-augmented elicitation + """ + if client_caps is None or not has_task_augmented_elicitation(client_caps): + raise McpError( + ErrorData( + code=INVALID_REQUEST, + message="Client does not support task-augmented elicitation", + ) + ) + + +def require_task_augmented_sampling(client_caps: ClientCapabilities | None) -> None: + """ + Raise McpError if client doesn't support task-augmented sampling. + + Args: + client_caps: The client's declared capabilities, or None if not initialized + + Raises: + McpError: If client doesn't support task-augmented sampling + """ + if client_caps is None or not has_task_augmented_sampling(client_caps): + raise McpError( + ErrorData( + code=INVALID_REQUEST, + message="Client does not support task-augmented sampling", + ) + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py new file mode 100644 index 0000000..12d1595 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py @@ -0,0 +1,101 @@ +""" +TaskContext - Pure task state management. + +This module provides TaskContext, which manages task state without any +server/session dependencies. It can be used standalone for distributed +workers or wrapped by ServerTaskContext for full server integration. +""" + +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.types import TASK_STATUS_COMPLETED, TASK_STATUS_FAILED, Result, Task + + +class TaskContext: + """ + Pure task state management - no session dependencies. + + This class handles: + - Task state (status, result) + - Cancellation tracking + - Store interactions + + For server-integrated features (elicit, create_message, notifications), + use ServerTaskContext from mcp.server.experimental. + + Example (distributed worker): + async def worker_job(task_id: str): + store = RedisTaskStore(redis_url) + task = await store.get_task(task_id) + ctx = TaskContext(task=task, store=store) + + await ctx.update_status("Working...") + result = await do_work() + await ctx.complete(result) + """ + + def __init__(self, task: Task, store: TaskStore): + self._task = task + self._store = store + self._cancelled = False + + @property + def task_id(self) -> str: + """The task identifier.""" + return self._task.taskId + + @property + def task(self) -> Task: + """The current task state.""" + return self._task + + @property + def is_cancelled(self) -> bool: + """Whether cancellation has been requested.""" + return self._cancelled + + def request_cancellation(self) -> None: + """ + Request cancellation of this task. + + This sets is_cancelled=True. Task work should check this + periodically and exit gracefully if set. + """ + self._cancelled = True + + async def update_status(self, message: str) -> None: + """ + Update the task's status message. + + Args: + message: The new status message + """ + self._task = await self._store.update_task( + self.task_id, + status_message=message, + ) + + async def complete(self, result: Result) -> None: + """ + Mark the task as completed with the given result. + + Args: + result: The task result + """ + await self._store.store_result(self.task_id, result) + self._task = await self._store.update_task( + self.task_id, + status=TASK_STATUS_COMPLETED, + ) + + async def fail(self, error: str) -> None: + """ + Mark the task as failed with an error message. + + Args: + error: The error message + """ + self._task = await self._store.update_task( + self.task_id, + status=TASK_STATUS_FAILED, + status_message=error, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py new file mode 100644 index 0000000..5c87f9e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py @@ -0,0 +1,181 @@ +""" +Helper functions for pure task management. + +These helpers work with pure TaskContext and don't require server dependencies. +For server-integrated task helpers, use mcp.server.experimental. +""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from uuid import uuid4 + +from mcp.shared.exceptions import McpError +from mcp.shared.experimental.tasks.context import TaskContext +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.types import ( + INVALID_PARAMS, + TASK_STATUS_CANCELLED, + TASK_STATUS_COMPLETED, + TASK_STATUS_FAILED, + TASK_STATUS_WORKING, + CancelTaskResult, + ErrorData, + Task, + TaskMetadata, + TaskStatus, +) + +# Metadata key for model-immediate-response (per MCP spec) +# Servers MAY include this in CreateTaskResult._meta to provide an immediate +# response string while the task executes in the background. +MODEL_IMMEDIATE_RESPONSE_KEY = "io.modelcontextprotocol/model-immediate-response" + +# Metadata key for associating requests with a task (per MCP spec) +RELATED_TASK_METADATA_KEY = "io.modelcontextprotocol/related-task" + + +def is_terminal(status: TaskStatus) -> bool: + """ + Check if a task status represents a terminal state. + + Terminal states are those where the task has finished and will not change. + + Args: + status: The task status to check + + Returns: + True if the status is terminal (completed, failed, or cancelled) + """ + return status in (TASK_STATUS_COMPLETED, TASK_STATUS_FAILED, TASK_STATUS_CANCELLED) + + +async def cancel_task( + store: TaskStore, + task_id: str, +) -> CancelTaskResult: + """ + Cancel a task with spec-compliant validation. + + Per spec: "Receivers MUST reject cancellation of terminal status tasks + with -32602 (Invalid params)" + + This helper validates that the task exists and is not in a terminal state + before setting it to "cancelled". + + Args: + store: The task store + task_id: The task identifier to cancel + + Returns: + CancelTaskResult with the cancelled task state + + Raises: + McpError: With INVALID_PARAMS (-32602) if: + - Task does not exist + - Task is already in a terminal state (completed, failed, cancelled) + + Example: + @server.experimental.cancel_task() + async def handle_cancel(request: CancelTaskRequest) -> CancelTaskResult: + return await cancel_task(store, request.params.taskId) + """ + task = await store.get_task(task_id) + if task is None: + raise McpError( + ErrorData( + code=INVALID_PARAMS, + message=f"Task not found: {task_id}", + ) + ) + + if is_terminal(task.status): + raise McpError( + ErrorData( + code=INVALID_PARAMS, + message=f"Cannot cancel task in terminal state '{task.status}'", + ) + ) + + # Update task to cancelled status + cancelled_task = await store.update_task(task_id, status=TASK_STATUS_CANCELLED) + return CancelTaskResult(**cancelled_task.model_dump()) + + +def generate_task_id() -> str: + """Generate a unique task ID.""" + return str(uuid4()) + + +def create_task_state( + metadata: TaskMetadata, + task_id: str | None = None, +) -> Task: + """ + Create a Task object with initial state. + + This is a helper for TaskStore implementations. + + Args: + metadata: Task metadata + task_id: Optional task ID (generated if not provided) + + Returns: + A new Task in "working" status + """ + now = datetime.now(timezone.utc) + return Task( + taskId=task_id or generate_task_id(), + status=TASK_STATUS_WORKING, + createdAt=now, + lastUpdatedAt=now, + ttl=metadata.ttl, + pollInterval=500, # Default 500ms poll interval + ) + + +@asynccontextmanager +async def task_execution( + task_id: str, + store: TaskStore, +) -> AsyncIterator[TaskContext]: + """ + Context manager for safe task execution (pure, no server dependencies). + + Loads a task from the store and provides a TaskContext for the work. + If an unhandled exception occurs, the task is automatically marked as failed + and the exception is suppressed (since the failure is captured in task state). + + This is useful for distributed workers that don't have a server session. + + Args: + task_id: The task identifier to execute + store: The task store (must be accessible by the worker) + + Yields: + TaskContext for updating status and completing/failing the task + + Raises: + ValueError: If the task is not found in the store + + Example (distributed worker): + async def worker_process(task_id: str): + store = RedisTaskStore(redis_url) + async with task_execution(task_id, store) as ctx: + await ctx.update_status("Working...") + result = await do_work() + await ctx.complete(result) + """ + task = await store.get_task(task_id) + if task is None: + raise ValueError(f"Task {task_id} not found") + + ctx = TaskContext(task, store) + try: + yield ctx + except Exception as e: + # Auto-fail the task if an exception occurs and task isn't already terminal + # Exception is suppressed since failure is captured in task state + if not is_terminal(ctx.task.status): + await ctx.fail(str(e)) + # Don't re-raise - the failure is recorded in task state diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py new file mode 100644 index 0000000..7b630ce --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py @@ -0,0 +1,219 @@ +""" +In-memory implementation of TaskStore for demonstration purposes. + +This implementation stores all tasks in memory and provides automatic cleanup +based on the TTL duration specified in the task metadata using lazy expiration. + +Note: This is not suitable for production use as all data is lost on restart. +For production, consider implementing TaskStore with a database or distributed cache. +""" + +from dataclasses import dataclass, field +from datetime import datetime, timedelta, timezone + +import anyio + +from mcp.shared.experimental.tasks.helpers import create_task_state, is_terminal +from mcp.shared.experimental.tasks.store import TaskStore +from mcp.types import Result, Task, TaskMetadata, TaskStatus + + +@dataclass +class StoredTask: + """Internal storage representation of a task.""" + + task: Task + result: Result | None = None + # Time when this task should be removed (None = never) + expires_at: datetime | None = field(default=None) + + +class InMemoryTaskStore(TaskStore): + """ + A simple in-memory implementation of TaskStore. + + Features: + - Automatic TTL-based cleanup (lazy expiration) + - Thread-safe for single-process async use + - Pagination support for list_tasks + + Limitations: + - All data lost on restart + - Not suitable for distributed systems + - No persistence + + For production, implement TaskStore with Redis, PostgreSQL, etc. + """ + + def __init__(self, page_size: int = 10) -> None: + self._tasks: dict[str, StoredTask] = {} + self._page_size = page_size + self._update_events: dict[str, anyio.Event] = {} + + def _calculate_expiry(self, ttl_ms: int | None) -> datetime | None: + """Calculate expiry time from TTL in milliseconds.""" + if ttl_ms is None: + return None + return datetime.now(timezone.utc) + timedelta(milliseconds=ttl_ms) + + def _is_expired(self, stored: StoredTask) -> bool: + """Check if a task has expired.""" + if stored.expires_at is None: + return False + return datetime.now(timezone.utc) >= stored.expires_at + + def _cleanup_expired(self) -> None: + """Remove all expired tasks. Called lazily during access operations.""" + expired_ids = [task_id for task_id, stored in self._tasks.items() if self._is_expired(stored)] + for task_id in expired_ids: + del self._tasks[task_id] + + async def create_task( + self, + metadata: TaskMetadata, + task_id: str | None = None, + ) -> Task: + """Create a new task with the given metadata.""" + # Cleanup expired tasks on access + self._cleanup_expired() + + task = create_task_state(metadata, task_id) + + if task.taskId in self._tasks: + raise ValueError(f"Task with ID {task.taskId} already exists") + + stored = StoredTask( + task=task, + expires_at=self._calculate_expiry(metadata.ttl), + ) + self._tasks[task.taskId] = stored + + # Return a copy to prevent external modification + return Task(**task.model_dump()) + + async def get_task(self, task_id: str) -> Task | None: + """Get a task by ID.""" + # Cleanup expired tasks on access + self._cleanup_expired() + + stored = self._tasks.get(task_id) + if stored is None: + return None + + # Return a copy to prevent external modification + return Task(**stored.task.model_dump()) + + async def update_task( + self, + task_id: str, + status: TaskStatus | None = None, + status_message: str | None = None, + ) -> Task: + """Update a task's status and/or message.""" + stored = self._tasks.get(task_id) + if stored is None: + raise ValueError(f"Task with ID {task_id} not found") + + # Per spec: Terminal states MUST NOT transition to any other status + if status is not None and status != stored.task.status and is_terminal(stored.task.status): + raise ValueError(f"Cannot transition from terminal status '{stored.task.status}'") + + status_changed = False + if status is not None and stored.task.status != status: + stored.task.status = status + status_changed = True + + if status_message is not None: + stored.task.statusMessage = status_message + + # Update lastUpdatedAt on any change + stored.task.lastUpdatedAt = datetime.now(timezone.utc) + + # If task is now terminal and has TTL, reset expiry timer + if status is not None and is_terminal(status) and stored.task.ttl is not None: + stored.expires_at = self._calculate_expiry(stored.task.ttl) + + # Notify waiters if status changed + if status_changed: + await self.notify_update(task_id) + + return Task(**stored.task.model_dump()) + + async def store_result(self, task_id: str, result: Result) -> None: + """Store the result for a task.""" + stored = self._tasks.get(task_id) + if stored is None: + raise ValueError(f"Task with ID {task_id} not found") + + stored.result = result + + async def get_result(self, task_id: str) -> Result | None: + """Get the stored result for a task.""" + stored = self._tasks.get(task_id) + if stored is None: + return None + + return stored.result + + async def list_tasks( + self, + cursor: str | None = None, + ) -> tuple[list[Task], str | None]: + """List tasks with pagination.""" + # Cleanup expired tasks on access + self._cleanup_expired() + + all_task_ids = list(self._tasks.keys()) + + start_index = 0 + if cursor is not None: + try: + cursor_index = all_task_ids.index(cursor) + start_index = cursor_index + 1 + except ValueError: + raise ValueError(f"Invalid cursor: {cursor}") + + page_task_ids = all_task_ids[start_index : start_index + self._page_size] + tasks = [Task(**self._tasks[tid].task.model_dump()) for tid in page_task_ids] + + # Determine next cursor + next_cursor = None + if start_index + self._page_size < len(all_task_ids) and page_task_ids: + next_cursor = page_task_ids[-1] + + return tasks, next_cursor + + async def delete_task(self, task_id: str) -> bool: + """Delete a task.""" + if task_id not in self._tasks: + return False + + del self._tasks[task_id] + return True + + async def wait_for_update(self, task_id: str) -> None: + """Wait until the task status changes.""" + if task_id not in self._tasks: + raise ValueError(f"Task with ID {task_id} not found") + + # Create a fresh event for waiting (anyio.Event can't be cleared) + self._update_events[task_id] = anyio.Event() + event = self._update_events[task_id] + await event.wait() + + async def notify_update(self, task_id: str) -> None: + """Signal that a task has been updated.""" + if task_id in self._update_events: + self._update_events[task_id].set() + + # --- Testing/debugging helpers --- + + def cleanup(self) -> None: + """Cleanup all tasks (useful for testing or graceful shutdown).""" + self._tasks.clear() + self._update_events.clear() + + def get_all_tasks(self) -> list[Task]: + """Get all tasks (useful for debugging). Returns copies to prevent modification.""" + self._cleanup_expired() + return [Task(**stored.task.model_dump()) for stored in self._tasks.values()] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py new file mode 100644 index 0000000..69b6609 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py @@ -0,0 +1,241 @@ +""" +TaskMessageQueue - FIFO queue for task-related messages. + +This implements the core message queue pattern from the MCP Tasks spec. +When a handler needs to send a request (like elicitation) during a task-augmented +request, the message is enqueued instead of sent directly. Messages are delivered +to the client only through the `tasks/result` endpoint. + +This pattern enables: +1. Decoupling request handling from message delivery +2. Proper bidirectional communication via the tasks/result stream +3. Automatic status management (working <-> input_required) +""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import Any, Literal + +import anyio + +from mcp.shared.experimental.tasks.resolver import Resolver +from mcp.types import JSONRPCNotification, JSONRPCRequest, RequestId + + +@dataclass +class QueuedMessage: + """ + A message queued for delivery via tasks/result. + + Messages are stored with their type and a resolver for requests + that expect responses. + """ + + type: Literal["request", "notification"] + """Whether this is a request (expects response) or notification (one-way).""" + + message: JSONRPCRequest | JSONRPCNotification + """The JSON-RPC message to send.""" + + timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) + """When the message was enqueued.""" + + resolver: Resolver[dict[str, Any]] | None = None + """Resolver to set when response arrives (only for requests).""" + + original_request_id: RequestId | None = None + """The original request ID used internally, for routing responses back.""" + + +class TaskMessageQueue(ABC): + """ + Abstract interface for task message queuing. + + This is a FIFO queue that stores messages to be delivered via `tasks/result`. + When a task-augmented handler calls elicit() or sends a notification, the + message is enqueued here instead of being sent directly to the client. + + The `tasks/result` handler then dequeues and sends these messages through + the transport, with `relatedRequestId` set to the tasks/result request ID + so responses are routed correctly. + + Implementations can use in-memory storage, Redis, etc. + """ + + @abstractmethod + async def enqueue(self, task_id: str, message: QueuedMessage) -> None: + """ + Add a message to the queue for a task. + + Args: + task_id: The task identifier + message: The message to enqueue + """ + + @abstractmethod + async def dequeue(self, task_id: str) -> QueuedMessage | None: + """ + Remove and return the next message from the queue. + + Args: + task_id: The task identifier + + Returns: + The next message, or None if queue is empty + """ + + @abstractmethod + async def peek(self, task_id: str) -> QueuedMessage | None: + """ + Return the next message without removing it. + + Args: + task_id: The task identifier + + Returns: + The next message, or None if queue is empty + """ + + @abstractmethod + async def is_empty(self, task_id: str) -> bool: + """ + Check if the queue is empty for a task. + + Args: + task_id: The task identifier + + Returns: + True if no messages are queued + """ + + @abstractmethod + async def clear(self, task_id: str) -> list[QueuedMessage]: + """ + Remove and return all messages from the queue. + + This is useful for cleanup when a task is cancelled or completed. + + Args: + task_id: The task identifier + + Returns: + All queued messages (may be empty) + """ + + @abstractmethod + async def wait_for_message(self, task_id: str) -> None: + """ + Wait until a message is available in the queue. + + This blocks until either: + 1. A message is enqueued for this task + 2. The wait is cancelled + + Args: + task_id: The task identifier + """ + + @abstractmethod + async def notify_message_available(self, task_id: str) -> None: + """ + Signal that a message is available for a task. + + This wakes up any coroutines waiting in wait_for_message(). + + Args: + task_id: The task identifier + """ + + +class InMemoryTaskMessageQueue(TaskMessageQueue): + """ + In-memory implementation of TaskMessageQueue. + + This is suitable for single-process servers. For distributed systems, + implement TaskMessageQueue with Redis, RabbitMQ, etc. + + Features: + - FIFO ordering per task + - Async wait for message availability + - Thread-safe for single-process async use + """ + + def __init__(self) -> None: + self._queues: dict[str, list[QueuedMessage]] = {} + self._events: dict[str, anyio.Event] = {} + + def _get_queue(self, task_id: str) -> list[QueuedMessage]: + """Get or create the queue for a task.""" + if task_id not in self._queues: + self._queues[task_id] = [] + return self._queues[task_id] + + async def enqueue(self, task_id: str, message: QueuedMessage) -> None: + """Add a message to the queue.""" + queue = self._get_queue(task_id) + queue.append(message) + # Signal that a message is available + await self.notify_message_available(task_id) + + async def dequeue(self, task_id: str) -> QueuedMessage | None: + """Remove and return the next message.""" + queue = self._get_queue(task_id) + if not queue: + return None + return queue.pop(0) + + async def peek(self, task_id: str) -> QueuedMessage | None: + """Return the next message without removing it.""" + queue = self._get_queue(task_id) + if not queue: + return None + return queue[0] + + async def is_empty(self, task_id: str) -> bool: + """Check if the queue is empty.""" + queue = self._get_queue(task_id) + return len(queue) == 0 + + async def clear(self, task_id: str) -> list[QueuedMessage]: + """Remove and return all messages.""" + queue = self._get_queue(task_id) + messages = list(queue) + queue.clear() + return messages + + async def wait_for_message(self, task_id: str) -> None: + """Wait until a message is available.""" + # Check if there are already messages + if not await self.is_empty(task_id): + return + + # Create a fresh event for waiting (anyio.Event can't be cleared) + self._events[task_id] = anyio.Event() + event = self._events[task_id] + + # Double-check after creating event (avoid race condition) + if not await self.is_empty(task_id): + return + + # Wait for a new message + await event.wait() + + async def notify_message_available(self, task_id: str) -> None: + """Signal that a message is available.""" + if task_id in self._events: + self._events[task_id].set() + + def cleanup(self, task_id: str | None = None) -> None: + """ + Clean up queues and events. + + Args: + task_id: If provided, clean up only this task. Otherwise clean up all. + """ + if task_id is not None: + self._queues.pop(task_id, None) + self._events.pop(task_id, None) + else: + self._queues.clear() + self._events.clear() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py new file mode 100644 index 0000000..39db2e6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py @@ -0,0 +1,45 @@ +""" +Shared polling utilities for task operations. + +This module provides generic polling logic that works for both client→server +and server→client task polling. + +WARNING: These APIs are experimental and may change without notice. +""" + +from collections.abc import AsyncIterator, Awaitable, Callable + +import anyio + +from mcp.shared.experimental.tasks.helpers import is_terminal +from mcp.types import GetTaskResult + + +async def poll_until_terminal( + get_task: Callable[[str], Awaitable[GetTaskResult]], + task_id: str, + default_interval_ms: int = 500, +) -> AsyncIterator[GetTaskResult]: + """ + Poll a task until it reaches terminal status. + + This is a generic utility that works for both client→server and server→client + polling. The caller provides the get_task function appropriate for their direction. + + Args: + get_task: Async function that takes task_id and returns GetTaskResult + task_id: The task to poll + default_interval_ms: Fallback poll interval if server doesn't specify + + Yields: + GetTaskResult for each poll + """ + while True: + status = await get_task(task_id) + yield status + + if is_terminal(status.status): + break + + interval_ms = status.pollInterval if status.pollInterval is not None else default_interval_ms + await anyio.sleep(interval_ms / 1000) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py new file mode 100644 index 0000000..f27425b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py @@ -0,0 +1,60 @@ +""" +Resolver - An anyio-compatible future-like object for async result passing. + +This provides a simple way to pass a result (or exception) from one coroutine +to another without depending on asyncio.Future. +""" + +from typing import Generic, TypeVar, cast + +import anyio + +T = TypeVar("T") + + +class Resolver(Generic[T]): + """ + A simple resolver for passing results between coroutines. + + Unlike asyncio.Future, this works with any anyio-compatible async backend. + + Usage: + resolver: Resolver[str] = Resolver() + + # In one coroutine: + resolver.set_result("hello") + + # In another coroutine: + result = await resolver.wait() # returns "hello" + """ + + def __init__(self) -> None: + self._event = anyio.Event() + self._value: T | None = None + self._exception: BaseException | None = None + + def set_result(self, value: T) -> None: + """Set the result value and wake up waiters.""" + if self._event.is_set(): + raise RuntimeError("Resolver already completed") + self._value = value + self._event.set() + + def set_exception(self, exc: BaseException) -> None: + """Set an exception and wake up waiters.""" + if self._event.is_set(): + raise RuntimeError("Resolver already completed") + self._exception = exc + self._event.set() + + async def wait(self) -> T: + """Wait for the result and return it, or raise the exception.""" + await self._event.wait() + if self._exception is not None: + raise self._exception + # If we reach here, set_result() was called, so _value is set + return cast(T, self._value) + + def done(self) -> bool: + """Return True if the resolver has been completed.""" + return self._event.is_set() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py new file mode 100644 index 0000000..71fb451 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py @@ -0,0 +1,156 @@ +""" +TaskStore - Abstract interface for task state storage. +""" + +from abc import ABC, abstractmethod + +from mcp.types import Result, Task, TaskMetadata, TaskStatus + + +class TaskStore(ABC): + """ + Abstract interface for task state storage. + + This is a pure storage interface - it doesn't manage execution. + Implementations can use in-memory storage, databases, Redis, etc. + + All methods are async to support various backends. + """ + + @abstractmethod + async def create_task( + self, + metadata: TaskMetadata, + task_id: str | None = None, + ) -> Task: + """ + Create a new task. + + Args: + metadata: Task metadata (ttl, etc.) + task_id: Optional task ID. If None, implementation should generate one. + + Returns: + The created Task with status="working" + + Raises: + ValueError: If task_id already exists + """ + + @abstractmethod + async def get_task(self, task_id: str) -> Task | None: + """ + Get a task by ID. + + Args: + task_id: The task identifier + + Returns: + The Task, or None if not found + """ + + @abstractmethod + async def update_task( + self, + task_id: str, + status: TaskStatus | None = None, + status_message: str | None = None, + ) -> Task: + """ + Update a task's status and/or message. + + Args: + task_id: The task identifier + status: New status (if changing) + status_message: New status message (if changing) + + Returns: + The updated Task + + Raises: + ValueError: If task not found + ValueError: If attempting to transition from a terminal status + (completed, failed, cancelled). Per spec, terminal states + MUST NOT transition to any other status. + """ + + @abstractmethod + async def store_result(self, task_id: str, result: Result) -> None: + """ + Store the result for a task. + + Args: + task_id: The task identifier + result: The result to store + + Raises: + ValueError: If task not found + """ + + @abstractmethod + async def get_result(self, task_id: str) -> Result | None: + """ + Get the stored result for a task. + + Args: + task_id: The task identifier + + Returns: + The stored Result, or None if not available + """ + + @abstractmethod + async def list_tasks( + self, + cursor: str | None = None, + ) -> tuple[list[Task], str | None]: + """ + List tasks with pagination. + + Args: + cursor: Optional cursor for pagination + + Returns: + Tuple of (tasks, next_cursor). next_cursor is None if no more pages. + """ + + @abstractmethod + async def delete_task(self, task_id: str) -> bool: + """ + Delete a task. + + Args: + task_id: The task identifier + + Returns: + True if deleted, False if not found + """ + + @abstractmethod + async def wait_for_update(self, task_id: str) -> None: + """ + Wait until the task status changes. + + This blocks until either: + 1. The task status changes + 2. The wait is cancelled + + Used by tasks/result to wait for task completion or status changes. + + Args: + task_id: The task identifier + + Raises: + ValueError: If task not found + """ + + @abstractmethod + async def notify_update(self, task_id: str) -> None: + """ + Signal that a task has been updated. + + This wakes up any coroutines waiting in wait_for_update(). + + Args: + task_id: The task identifier + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py new file mode 100644 index 0000000..06d404e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py @@ -0,0 +1,98 @@ +""" +In-memory transports +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from datetime import timedelta +from typing import Any + +import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +import mcp.types as types +from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT +from mcp.server import Server +from mcp.server.fastmcp import FastMCP +from mcp.shared.message import SessionMessage + +MessageStream = tuple[MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage]] + + +@asynccontextmanager +async def create_client_server_memory_streams() -> AsyncGenerator[tuple[MessageStream, MessageStream], None]: + """ + Creates a pair of bidirectional memory streams for client-server communication. + + Returns: + A tuple of (client_streams, server_streams) where each is a tuple of + (read_stream, write_stream) + """ + # Create streams for both directions + server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage | Exception](1) + client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage | Exception](1) + + client_streams = (server_to_client_receive, client_to_server_send) + server_streams = (client_to_server_receive, server_to_client_send) + + async with ( + server_to_client_receive, + client_to_server_send, + client_to_server_receive, + server_to_client_send, + ): + yield client_streams, server_streams + + +@asynccontextmanager +async def create_connected_server_and_client_session( + server: Server[Any] | FastMCP, + read_timeout_seconds: timedelta | None = None, + sampling_callback: SamplingFnT | None = None, + list_roots_callback: ListRootsFnT | None = None, + logging_callback: LoggingFnT | None = None, + message_handler: MessageHandlerFnT | None = None, + client_info: types.Implementation | None = None, + raise_exceptions: bool = False, + elicitation_callback: ElicitationFnT | None = None, +) -> AsyncGenerator[ClientSession, None]: + """Creates a ClientSession that is connected to a running MCP server.""" + + # TODO(Marcelo): we should have a proper `Client` that can use this "in-memory transport", + # and we should expose a method in the `FastMCP` so we don't access a private attribute. + if isinstance(server, FastMCP): # pragma: no cover + server = server._mcp_server # type: ignore[reportPrivateUsage] + + async with create_client_server_memory_streams() as (client_streams, server_streams): + client_read, client_write = client_streams + server_read, server_write = server_streams + + # Create a cancel scope for the server task + async with anyio.create_task_group() as tg: + tg.start_soon( + lambda: server.run( + server_read, + server_write, + server.create_initialization_options(), + raise_exceptions=raise_exceptions, + ) + ) + + try: + async with ClientSession( + read_stream=client_read, + write_stream=client_write, + read_timeout_seconds=read_timeout_seconds, + sampling_callback=sampling_callback, + list_roots_callback=list_roots_callback, + logging_callback=logging_callback, + message_handler=message_handler, + client_info=client_info, + elicitation_callback=elicitation_callback, + ) as client_session: + await client_session.initialize() + yield client_session + finally: # pragma: no cover + tg.cancel_scope.cancel() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py new file mode 100644 index 0000000..81503ea --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py @@ -0,0 +1,50 @@ +""" +Message wrapper with metadata support. + +This module defines a wrapper type that combines JSONRPCMessage with metadata +to support transport-specific features like resumability. +""" + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from mcp.types import JSONRPCMessage, RequestId + +ResumptionToken = str + +ResumptionTokenUpdateCallback = Callable[[ResumptionToken], Awaitable[None]] + +# Callback type for closing SSE streams without terminating +CloseSSEStreamCallback = Callable[[], Awaitable[None]] + + +@dataclass +class ClientMessageMetadata: + """Metadata specific to client messages.""" + + resumption_token: ResumptionToken | None = None + on_resumption_token_update: Callable[[ResumptionToken], Awaitable[None]] | None = None + + +@dataclass +class ServerMessageMetadata: + """Metadata specific to server messages.""" + + related_request_id: RequestId | None = None + # Request-specific context (e.g., headers, auth info) + request_context: object | None = None + # Callback to close SSE stream for the current request without terminating + close_sse_stream: CloseSSEStreamCallback | None = None + # Callback to close the standalone GET SSE stream (for unsolicited notifications) + close_standalone_sse_stream: CloseSSEStreamCallback | None = None + + +MessageMetadata = ClientMessageMetadata | ServerMessageMetadata | None + + +@dataclass +class SessionMessage: + """A message with specific metadata for transport-specific features.""" + + message: JSONRPCMessage + metadata: MessageMetadata = None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py new file mode 100644 index 0000000..e3f49da --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py @@ -0,0 +1,45 @@ +"""Utility functions for working with metadata in MCP types. + +These utilities are primarily intended for client-side usage to properly display +human-readable names in user interfaces in a spec compliant way. +""" + +from mcp.types import Implementation, Prompt, Resource, ResourceTemplate, Tool + + +def get_display_name(obj: Tool | Resource | Prompt | ResourceTemplate | Implementation) -> str: + """ + Get the display name for an MCP object with proper precedence. + + This is a client-side utility function designed to help MCP clients display + human-readable names in their user interfaces. When servers provide a 'title' + field, it should be preferred over the programmatic 'name' field for display. + + For tools: title > annotations.title > name + For other objects: title > name + + Example: + # In a client displaying available tools + tools = await session.list_tools() + for tool in tools.tools: + display_name = get_display_name(tool) + print(f"Available tool: {display_name}") + + Args: + obj: An MCP object with name and optional title fields + + Returns: + The display name to use for UI presentation + """ + if isinstance(obj, Tool): + # Tools have special precedence: title > annotations.title > name + if hasattr(obj, "title") and obj.title is not None: + return obj.title + if obj.annotations and hasattr(obj.annotations, "title") and obj.annotations.title is not None: + return obj.annotations.title + return obj.name + else: + # All other objects: title > name + if hasattr(obj, "title") and obj.title is not None: + return obj.title + return obj.name diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py new file mode 100644 index 0000000..a230c58 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py @@ -0,0 +1,58 @@ +from collections.abc import Generator +from contextlib import contextmanager +from dataclasses import dataclass, field +from typing import Generic + +from pydantic import BaseModel + +from mcp.shared.context import LifespanContextT, RequestContext +from mcp.shared.session import ( + BaseSession, + ReceiveNotificationT, + ReceiveRequestT, + SendNotificationT, + SendRequestT, + SendResultT, +) +from mcp.types import ProgressToken + + +class Progress(BaseModel): + progress: float + total: float | None + + +@dataclass +class ProgressContext(Generic[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT]): + session: BaseSession[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT] + progress_token: ProgressToken + total: float | None + current: float = field(default=0.0, init=False) + + async def progress(self, amount: float, message: str | None = None) -> None: + self.current += amount + + await self.session.send_progress_notification( + self.progress_token, self.current, total=self.total, message=message + ) + + +@contextmanager +def progress( + ctx: RequestContext[ + BaseSession[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT], + LifespanContextT, + ], + total: float | None = None, +) -> Generator[ + ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT], + None, +]: + if ctx.meta is None or ctx.meta.progressToken is None: # pragma: no cover + raise ValueError("No progress token provided") + + progress_ctx = ProgressContext(ctx.session, ctx.meta.progressToken, total) + try: + yield progress_ctx + finally: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py new file mode 100644 index 0000000..3179615 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py @@ -0,0 +1,63 @@ +""" +ResponseRouter - Protocol for pluggable response routing. + +This module defines a protocol for routing JSON-RPC responses to alternative +handlers before falling back to the default response stream mechanism. + +The primary use case is task-augmented requests: when a TaskSession enqueues +a request (like elicitation), the response needs to be routed back to the +waiting resolver instead of the normal response stream. + +Design: +- Protocol-based for testability and flexibility +- Returns bool to indicate if response was handled +- Supports both success responses and errors +""" + +from typing import Any, Protocol + +from mcp.types import ErrorData, RequestId + + +class ResponseRouter(Protocol): + """ + Protocol for routing responses to alternative handlers. + + Implementations check if they have a pending request for the given ID + and deliver the response/error to the appropriate handler. + + Example: + class TaskResultHandler(ResponseRouter): + def route_response(self, request_id, response): + resolver = self._pending_requests.pop(request_id, None) + if resolver: + resolver.set_result(response) + return True + return False + """ + + def route_response(self, request_id: RequestId, response: dict[str, Any]) -> bool: + """ + Try to route a response to a pending request handler. + + Args: + request_id: The JSON-RPC request ID from the response + response: The response result data + + Returns: + True if the response was handled, False otherwise + """ + ... # pragma: no cover + + def route_error(self, request_id: RequestId, error: ErrorData) -> bool: + """ + Try to route an error to a pending request handler. + + Args: + request_id: The JSON-RPC request ID from the error response + error: The error data + + Returns: + True if the error was handled, False otherwise + """ + ... # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py new file mode 100644 index 0000000..3033acd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py @@ -0,0 +1,550 @@ +import logging +from collections.abc import Callable +from contextlib import AsyncExitStack +from datetime import timedelta +from types import TracebackType +from typing import Any, Generic, Protocol, TypeVar + +import anyio +import httpx +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import BaseModel +from typing_extensions import Self + +from mcp.shared.exceptions import McpError +from mcp.shared.message import MessageMetadata, ServerMessageMetadata, SessionMessage +from mcp.shared.response_router import ResponseRouter +from mcp.types import ( + CONNECTION_CLOSED, + INVALID_PARAMS, + CancelledNotification, + ClientNotification, + ClientRequest, + ClientResult, + ErrorData, + JSONRPCError, + JSONRPCMessage, + JSONRPCNotification, + JSONRPCRequest, + JSONRPCResponse, + ProgressNotification, + RequestParams, + ServerNotification, + ServerRequest, + ServerResult, +) + +SendRequestT = TypeVar("SendRequestT", ClientRequest, ServerRequest) +SendResultT = TypeVar("SendResultT", ClientResult, ServerResult) +SendNotificationT = TypeVar("SendNotificationT", ClientNotification, ServerNotification) +ReceiveRequestT = TypeVar("ReceiveRequestT", ClientRequest, ServerRequest) +ReceiveResultT = TypeVar("ReceiveResultT", bound=BaseModel) +ReceiveNotificationT = TypeVar("ReceiveNotificationT", ClientNotification, ServerNotification) + +RequestId = str | int + + +class ProgressFnT(Protocol): + """Protocol for progress notification callbacks.""" + + async def __call__( + self, progress: float, total: float | None, message: str | None + ) -> None: ... # pragma: no branch + + +class RequestResponder(Generic[ReceiveRequestT, SendResultT]): + """Handles responding to MCP requests and manages request lifecycle. + + This class MUST be used as a context manager to ensure proper cleanup and + cancellation handling: + + Example: + with request_responder as resp: + await resp.respond(result) + + The context manager ensures: + 1. Proper cancellation scope setup and cleanup + 2. Request completion tracking + 3. Cleanup of in-flight requests + """ + + def __init__( + self, + request_id: RequestId, + request_meta: RequestParams.Meta | None, + request: ReceiveRequestT, + session: """BaseSession[ + SendRequestT, + SendNotificationT, + SendResultT, + ReceiveRequestT, + ReceiveNotificationT + ]""", + on_complete: Callable[["RequestResponder[ReceiveRequestT, SendResultT]"], Any], + message_metadata: MessageMetadata = None, + ) -> None: + self.request_id = request_id + self.request_meta = request_meta + self.request = request + self.message_metadata = message_metadata + self._session = session + self._completed = False + self._cancel_scope = anyio.CancelScope() + self._on_complete = on_complete + self._entered = False # Track if we're in a context manager + + def __enter__(self) -> "RequestResponder[ReceiveRequestT, SendResultT]": + """Enter the context manager, enabling request cancellation tracking.""" + self._entered = True + self._cancel_scope = anyio.CancelScope() + self._cancel_scope.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + """Exit the context manager, performing cleanup and notifying completion.""" + try: + if self._completed: # pragma: no branch + self._on_complete(self) + finally: + self._entered = False + if not self._cancel_scope: # pragma: no cover + raise RuntimeError("No active cancel scope") + self._cancel_scope.__exit__(exc_type, exc_val, exc_tb) + + async def respond(self, response: SendResultT | ErrorData) -> None: + """Send a response for this request. + + Must be called within a context manager block. + Raises: + RuntimeError: If not used within a context manager + AssertionError: If request was already responded to + """ + if not self._entered: # pragma: no cover + raise RuntimeError("RequestResponder must be used as a context manager") + assert not self._completed, "Request already responded to" + + if not self.cancelled: # pragma: no branch + self._completed = True + + await self._session._send_response( # type: ignore[reportPrivateUsage] + request_id=self.request_id, response=response + ) + + async def cancel(self) -> None: + """Cancel this request and mark it as completed.""" + if not self._entered: # pragma: no cover + raise RuntimeError("RequestResponder must be used as a context manager") + if not self._cancel_scope: # pragma: no cover + raise RuntimeError("No active cancel scope") + + self._cancel_scope.cancel() + self._completed = True # Mark as completed so it's removed from in_flight + # Send an error response to indicate cancellation + await self._session._send_response( # type: ignore[reportPrivateUsage] + request_id=self.request_id, + response=ErrorData(code=0, message="Request cancelled", data=None), + ) + + @property + def in_flight(self) -> bool: # pragma: no cover + return not self._completed and not self.cancelled + + @property + def cancelled(self) -> bool: # pragma: no cover + return self._cancel_scope.cancel_called + + +class BaseSession( + Generic[ + SendRequestT, + SendNotificationT, + SendResultT, + ReceiveRequestT, + ReceiveNotificationT, + ], +): + """ + Implements an MCP "session" on top of read/write streams, including features + like request/response linking, notifications, and progress. + + This class is an async context manager that automatically starts processing + messages when entered. + """ + + _response_streams: dict[RequestId, MemoryObjectSendStream[JSONRPCResponse | JSONRPCError]] + _request_id: int + _in_flight: dict[RequestId, RequestResponder[ReceiveRequestT, SendResultT]] + _progress_callbacks: dict[RequestId, ProgressFnT] + _response_routers: list["ResponseRouter"] + + def __init__( + self, + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception], + write_stream: MemoryObjectSendStream[SessionMessage], + receive_request_type: type[ReceiveRequestT], + receive_notification_type: type[ReceiveNotificationT], + # If none, reading will never time out + read_timeout_seconds: timedelta | None = None, + ) -> None: + self._read_stream = read_stream + self._write_stream = write_stream + self._response_streams = {} + self._request_id = 0 + self._receive_request_type = receive_request_type + self._receive_notification_type = receive_notification_type + self._session_read_timeout_seconds = read_timeout_seconds + self._in_flight = {} + self._progress_callbacks = {} + self._response_routers = [] + self._exit_stack = AsyncExitStack() + + def add_response_router(self, router: ResponseRouter) -> None: + """ + Register a response router to handle responses for non-standard requests. + + Response routers are checked in order before falling back to the default + response stream mechanism. This is used by TaskResultHandler to route + responses for queued task requests back to their resolvers. + + WARNING: This is an experimental API that may change without notice. + + Args: + router: A ResponseRouter implementation + """ + self._response_routers.append(router) + + async def __aenter__(self) -> Self: + self._task_group = anyio.create_task_group() + await self._task_group.__aenter__() + self._task_group.start_soon(self._receive_loop) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + await self._exit_stack.aclose() + # Using BaseSession as a context manager should not block on exit (this + # would be very surprising behavior), so make sure to cancel the tasks + # in the task group. + self._task_group.cancel_scope.cancel() + return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) + + async def send_request( + self, + request: SendRequestT, + result_type: type[ReceiveResultT], + request_read_timeout_seconds: timedelta | None = None, + metadata: MessageMetadata = None, + progress_callback: ProgressFnT | None = None, + ) -> ReceiveResultT: + """ + Sends a request and wait for a response. Raises an McpError if the + response contains an error. If a request read timeout is provided, it + will take precedence over the session read timeout. + + Do not use this method to emit notifications! Use send_notification() + instead. + """ + request_id = self._request_id + self._request_id = request_id + 1 + + response_stream, response_stream_reader = anyio.create_memory_object_stream[JSONRPCResponse | JSONRPCError](1) + self._response_streams[request_id] = response_stream + + # Set up progress token if progress callback is provided + request_data = request.model_dump(by_alias=True, mode="json", exclude_none=True) + if progress_callback is not None: # pragma: no cover + # Use request_id as progress token + if "params" not in request_data: + request_data["params"] = {} + if "_meta" not in request_data["params"]: # pragma: no branch + request_data["params"]["_meta"] = {} + request_data["params"]["_meta"]["progressToken"] = request_id + # Store the callback for this request + self._progress_callbacks[request_id] = progress_callback + + try: + jsonrpc_request = JSONRPCRequest( + jsonrpc="2.0", + id=request_id, + **request_data, + ) + + await self._write_stream.send(SessionMessage(message=JSONRPCMessage(jsonrpc_request), metadata=metadata)) + + # request read timeout takes precedence over session read timeout + timeout = None + if request_read_timeout_seconds is not None: # pragma: no cover + timeout = request_read_timeout_seconds.total_seconds() + elif self._session_read_timeout_seconds is not None: # pragma: no cover + timeout = self._session_read_timeout_seconds.total_seconds() + + try: + with anyio.fail_after(timeout): + response_or_error = await response_stream_reader.receive() + except TimeoutError: + raise McpError( + ErrorData( + code=httpx.codes.REQUEST_TIMEOUT, + message=( + f"Timed out while waiting for response to " + f"{request.__class__.__name__}. Waited " + f"{timeout} seconds." + ), + ) + ) + + if isinstance(response_or_error, JSONRPCError): + raise McpError(response_or_error.error) + else: + return result_type.model_validate(response_or_error.result) + + finally: + self._response_streams.pop(request_id, None) + self._progress_callbacks.pop(request_id, None) + await response_stream.aclose() + await response_stream_reader.aclose() + + async def send_notification( + self, + notification: SendNotificationT, + related_request_id: RequestId | None = None, + ) -> None: + """ + Emits a notification, which is a one-way message that does not expect + a response. + """ + # Some transport implementations may need to set the related_request_id + # to attribute to the notifications to the request that triggered them. + jsonrpc_notification = JSONRPCNotification( + jsonrpc="2.0", + **notification.model_dump(by_alias=True, mode="json", exclude_none=True), + ) + session_message = SessionMessage( # pragma: no cover + message=JSONRPCMessage(jsonrpc_notification), + metadata=ServerMessageMetadata(related_request_id=related_request_id) if related_request_id else None, + ) + await self._write_stream.send(session_message) + + async def _send_response(self, request_id: RequestId, response: SendResultT | ErrorData) -> None: + if isinstance(response, ErrorData): + jsonrpc_error = JSONRPCError(jsonrpc="2.0", id=request_id, error=response) + session_message = SessionMessage(message=JSONRPCMessage(jsonrpc_error)) + await self._write_stream.send(session_message) + else: + jsonrpc_response = JSONRPCResponse( + jsonrpc="2.0", + id=request_id, + result=response.model_dump(by_alias=True, mode="json", exclude_none=True), + ) + session_message = SessionMessage(message=JSONRPCMessage(jsonrpc_response)) + await self._write_stream.send(session_message) + + async def _receive_loop(self) -> None: + async with ( + self._read_stream, + self._write_stream, + ): + try: + async for message in self._read_stream: + if isinstance(message, Exception): # pragma: no cover + await self._handle_incoming(message) + elif isinstance(message.message.root, JSONRPCRequest): + try: + validated_request = self._receive_request_type.model_validate( + message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True) + ) + responder = RequestResponder( + request_id=message.message.root.id, + request_meta=validated_request.root.params.meta + if validated_request.root.params + else None, + request=validated_request, + session=self, + on_complete=lambda r: self._in_flight.pop(r.request_id, None), + message_metadata=message.metadata, + ) + self._in_flight[responder.request_id] = responder + await self._received_request(responder) + + if not responder._completed: # type: ignore[reportPrivateUsage] + await self._handle_incoming(responder) + except Exception as e: + # For request validation errors, send a proper JSON-RPC error + # response instead of crashing the server + logging.warning(f"Failed to validate request: {e}") + logging.debug(f"Message that failed validation: {message.message.root}") + error_response = JSONRPCError( + jsonrpc="2.0", + id=message.message.root.id, + error=ErrorData( + code=INVALID_PARAMS, + message="Invalid request parameters", + data="", + ), + ) + session_message = SessionMessage(message=JSONRPCMessage(error_response)) + await self._write_stream.send(session_message) + + elif isinstance(message.message.root, JSONRPCNotification): + try: + notification = self._receive_notification_type.model_validate( + message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True) + ) + # Handle cancellation notifications + if isinstance(notification.root, CancelledNotification): + cancelled_id = notification.root.params.requestId + if cancelled_id in self._in_flight: # pragma: no branch + await self._in_flight[cancelled_id].cancel() + else: + # Handle progress notifications callback + if isinstance(notification.root, ProgressNotification): # pragma: no cover + progress_token = notification.root.params.progressToken + # If there is a progress callback for this token, + # call it with the progress information + if progress_token in self._progress_callbacks: + callback = self._progress_callbacks[progress_token] + try: + await callback( + notification.root.params.progress, + notification.root.params.total, + notification.root.params.message, + ) + except Exception as e: + logging.error( + "Progress callback raised an exception: %s", + e, + ) + await self._received_notification(notification) + await self._handle_incoming(notification) + except Exception as e: # pragma: no cover + # For other validation errors, log and continue + logging.warning( + f"Failed to validate notification: {e}. Message was: {message.message.root}" + ) + else: # Response or error + await self._handle_response(message) + + except anyio.ClosedResourceError: + # This is expected when the client disconnects abruptly. + # Without this handler, the exception would propagate up and + # crash the server's task group. + logging.debug("Read stream closed by client") # pragma: no cover + except Exception as e: # pragma: no cover + # Other exceptions are not expected and should be logged. We purposefully + # catch all exceptions here to avoid crashing the server. + logging.exception(f"Unhandled exception in receive loop: {e}") + finally: + # after the read stream is closed, we need to send errors + # to any pending requests + for id, stream in self._response_streams.items(): + error = ErrorData(code=CONNECTION_CLOSED, message="Connection closed") + try: + await stream.send(JSONRPCError(jsonrpc="2.0", id=id, error=error)) + await stream.aclose() + except Exception: # pragma: no cover + # Stream might already be closed + pass + self._response_streams.clear() + + def _normalize_request_id(self, response_id: RequestId) -> RequestId: + """ + Normalize a response ID to match how request IDs are stored. + + Since the client always sends integer IDs, we normalize string IDs + to integers when possible. This matches the TypeScript SDK approach: + https://github.com/modelcontextprotocol/typescript-sdk/blob/a606fb17909ea454e83aab14c73f14ea45c04448/src/shared/protocol.ts#L861 + + Args: + response_id: The response ID from the incoming message. + + Returns: + The normalized ID (int if possible, otherwise original value). + """ + if isinstance(response_id, str): + try: + return int(response_id) + except ValueError: + logging.warning(f"Response ID {response_id!r} cannot be normalized to match pending requests") + return response_id + + async def _handle_response(self, message: SessionMessage) -> None: + """ + Handle an incoming response or error message. + + Checks response routers first (e.g., for task-related responses), + then falls back to the normal response stream mechanism. + """ + root = message.message.root + + # This check is always true at runtime: the caller (_receive_loop) only invokes + # this method in the else branch after checking for JSONRPCRequest and + # JSONRPCNotification. However, the type checker can't infer this from the + # method signature, so we need this guard for type narrowing. + if not isinstance(root, JSONRPCResponse | JSONRPCError): + return # pragma: no cover + + # Normalize response ID to handle type mismatches (e.g., "0" vs 0) + response_id = self._normalize_request_id(root.id) + + # First, check response routers (e.g., TaskResultHandler) + if isinstance(root, JSONRPCError): + # Route error to routers + for router in self._response_routers: + if router.route_error(response_id, root.error): + return # Handled + else: + # Route success response to routers + response_data: dict[str, Any] = root.result or {} + for router in self._response_routers: + if router.route_response(response_id, response_data): + return # Handled + + # Fall back to normal response streams + stream = self._response_streams.pop(response_id, None) + if stream: # pragma: no cover + await stream.send(root) + else: # pragma: no cover + await self._handle_incoming(RuntimeError(f"Received response with an unknown request ID: {message}")) + + async def _received_request(self, responder: RequestResponder[ReceiveRequestT, SendResultT]) -> None: + """ + Can be overridden by subclasses to handle a request without needing to + listen on the message stream. + + If the request is responded to within this method, it will not be + forwarded on to the message stream. + """ + + async def _received_notification(self, notification: ReceiveNotificationT) -> None: + """ + Can be overridden by subclasses to handle a notification without needing + to listen on the message stream. + """ + + async def send_progress_notification( + self, + progress_token: str | int, + progress: float, + total: float | None = None, + message: str | None = None, + ) -> None: + """ + Sends a progress notification for a request that is currently being + processed. + """ + + async def _handle_incoming( + self, + req: RequestResponder[ReceiveRequestT, SendResultT] | ReceiveNotificationT | Exception, + ) -> None: + """A generic handler for incoming messages. Overwritten by subclasses.""" + pass # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py new file mode 100644 index 0000000..f35efa5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py @@ -0,0 +1,129 @@ +"""Tool name validation utilities according to SEP-986. + +Tool names SHOULD be between 1 and 128 characters in length (inclusive). +Tool names are case-sensitive. +Allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), +digits (0-9), underscore (_), dash (-), and dot (.). +Tool names SHOULD NOT contain spaces, commas, or other special characters. + +See: https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names +""" + +from __future__ import annotations + +import logging +import re +from dataclasses import dataclass, field + +logger = logging.getLogger(__name__) + +# Regular expression for valid tool names according to SEP-986 specification +TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}$") + +# SEP reference URL for warning messages +SEP_986_URL = "https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names" + + +@dataclass +class ToolNameValidationResult: + """Result of tool name validation. + + Attributes: + is_valid: Whether the tool name conforms to SEP-986 requirements. + warnings: List of warning messages for non-conforming aspects. + """ + + is_valid: bool + warnings: list[str] = field(default_factory=lambda: []) + + +def validate_tool_name(name: str) -> ToolNameValidationResult: + """Validate a tool name according to the SEP-986 specification. + + Args: + name: The tool name to validate. + + Returns: + ToolNameValidationResult containing validation status and any warnings. + """ + warnings: list[str] = [] + + # Check for empty name + if not name: + return ToolNameValidationResult( + is_valid=False, + warnings=["Tool name cannot be empty"], + ) + + # Check length + if len(name) > 128: + return ToolNameValidationResult( + is_valid=False, + warnings=[f"Tool name exceeds maximum length of 128 characters (current: {len(name)})"], + ) + + # Check for problematic patterns (warnings, not validation failures) + if " " in name: + warnings.append("Tool name contains spaces, which may cause parsing issues") + + if "," in name: + warnings.append("Tool name contains commas, which may cause parsing issues") + + # Check for potentially confusing leading/trailing characters + if name.startswith("-") or name.endswith("-"): + warnings.append("Tool name starts or ends with a dash, which may cause parsing issues in some contexts") + + if name.startswith(".") or name.endswith("."): + warnings.append("Tool name starts or ends with a dot, which may cause parsing issues in some contexts") + + # Check for invalid characters + if not TOOL_NAME_REGEX.match(name): + # Find all invalid characters (unique, preserving order) + invalid_chars: list[str] = [] + seen: set[str] = set() + for char in name: + if not re.match(r"[A-Za-z0-9._-]", char) and char not in seen: + invalid_chars.append(char) + seen.add(char) + + warnings.append(f"Tool name contains invalid characters: {', '.join(repr(c) for c in invalid_chars)}") + warnings.append("Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)") + + return ToolNameValidationResult(is_valid=False, warnings=warnings) + + return ToolNameValidationResult(is_valid=True, warnings=warnings) + + +def issue_tool_name_warning(name: str, warnings: list[str]) -> None: + """Log warnings for non-conforming tool names. + + Args: + name: The tool name that triggered the warnings. + warnings: List of warning messages to log. + """ + if not warnings: + return + + logger.warning(f'Tool name validation warning for "{name}":') + for warning in warnings: + logger.warning(f" - {warning}") + logger.warning("Tool registration will proceed, but this may cause compatibility issues.") + logger.warning("Consider updating the tool name to conform to the MCP tool naming standard.") + logger.warning(f"See SEP-986 ({SEP_986_URL}) for more details.") + + +def validate_and_warn_tool_name(name: str) -> bool: + """Validate a tool name and issue warnings for non-conforming names. + + This is the primary entry point for tool name validation. It validates + the name and logs any warnings via the logging module. + + Args: + name: The tool name to validate. + + Returns: + True if the name is valid, False otherwise. + """ + result = validate_tool_name(name) + issue_tool_name_warning(name, result.warnings) + return result.is_valid diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py new file mode 100644 index 0000000..d2a1e46 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py @@ -0,0 +1,3 @@ +from mcp.types import LATEST_PROTOCOL_VERSION + +SUPPORTED_PROTOCOL_VERSIONS: list[str] = ["2024-11-05", "2025-03-26", "2025-06-18", LATEST_PROTOCOL_VERSION] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py new file mode 100644 index 0000000..654c006 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/mcp/types.py @@ -0,0 +1,1999 @@ +from collections.abc import Callable +from datetime import datetime +from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar + +from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel +from pydantic.networks import AnyUrl, UrlConstraints +from typing_extensions import deprecated + +""" +Model Context Protocol bindings for Python + +These bindings were generated from https://github.com/modelcontextprotocol/specification, +using Claude, with a prompt something like the following: + +Generate idiomatic Python bindings for this schema for MCP, or the "Model Context +Protocol." The schema is defined in TypeScript, but there's also a JSON Schema version +for reference. + +* For the bindings, let's use Pydantic V2 models. +* Each model should allow extra fields everywhere, by specifying `model_config = + ConfigDict(extra='allow')`. Do this in every case, instead of a custom base class. +* Union types should be represented with a Pydantic `RootModel`. +* Define additional model classes instead of using dictionaries. Do this even if they're + not separate types in the schema. +""" + +LATEST_PROTOCOL_VERSION = "2025-11-25" + +""" +The default negotiated version of the Model Context Protocol when no version is specified. +We need this to satisfy the MCP specification, which requires the server to assume a +specific version if none is provided by the client. See section "Protocol Version Header" at +https://modelcontextprotocol.io/specification +""" +DEFAULT_NEGOTIATED_VERSION = "2025-03-26" + +ProgressToken = str | int +Cursor = str +Role = Literal["user", "assistant"] +RequestId = Annotated[int, Field(strict=True)] | str +AnyFunction: TypeAlias = Callable[..., Any] + +TaskExecutionMode = Literal["forbidden", "optional", "required"] +TASK_FORBIDDEN: Final[Literal["forbidden"]] = "forbidden" +TASK_OPTIONAL: Final[Literal["optional"]] = "optional" +TASK_REQUIRED: Final[Literal["required"]] = "required" + + +class TaskMetadata(BaseModel): + """ + Metadata for augmenting a request with task execution. + Include this in the `task` field of the request parameters. + """ + + model_config = ConfigDict(extra="allow") + + ttl: Annotated[int, Field(strict=True)] | None = None + """Requested duration in milliseconds to retain task from creation.""" + + +class RequestParams(BaseModel): + class Meta(BaseModel): + progressToken: ProgressToken | None = None + """ + If specified, the caller requests out-of-band progress notifications for + this request (as represented by notifications/progress). The value of this + parameter is an opaque token that will be attached to any subsequent + notifications. The receiver is not obligated to provide these notifications. + """ + + model_config = ConfigDict(extra="allow") + + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + meta: Meta | None = Field(alias="_meta", default=None) + + +class PaginatedRequestParams(RequestParams): + cursor: Cursor | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class NotificationParams(BaseModel): + class Meta(BaseModel): + model_config = ConfigDict(extra="allow") + + meta: Meta | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + + +RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams | dict[str, Any] | None) +NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams | dict[str, Any] | None) +MethodT = TypeVar("MethodT", bound=str) + + +class Request(BaseModel, Generic[RequestParamsT, MethodT]): + """Base class for JSON-RPC requests.""" + + method: MethodT + params: RequestParamsT + model_config = ConfigDict(extra="allow") + + +class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[MethodT]): + """Base class for paginated requests, + matching the schema's PaginatedRequest interface.""" + + params: PaginatedRequestParams | None = None + + +class Notification(BaseModel, Generic[NotificationParamsT, MethodT]): + """Base class for JSON-RPC notifications.""" + + method: MethodT + params: NotificationParamsT + model_config = ConfigDict(extra="allow") + + +class Result(BaseModel): + """Base class for JSON-RPC results.""" + + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class PaginatedResult(Result): + nextCursor: Cursor | None = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +class JSONRPCRequest(Request[dict[str, Any] | None, str]): + """A request that expects a response.""" + + jsonrpc: Literal["2.0"] + id: RequestId + method: str + params: dict[str, Any] | None = None + + +class JSONRPCNotification(Notification[dict[str, Any] | None, str]): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + params: dict[str, Any] | None = None + + +class JSONRPCResponse(BaseModel): + """A successful (non-error) response to a request.""" + + jsonrpc: Literal["2.0"] + id: RequestId + result: dict[str, Any] + model_config = ConfigDict(extra="allow") + + +# MCP-specific error codes in the range [-32000, -32099] +URL_ELICITATION_REQUIRED = -32042 +"""Error code indicating that a URL mode elicitation is required before the request can be processed.""" + +# SDK error codes +CONNECTION_CLOSED = -32000 +# REQUEST_TIMEOUT = -32001 # the typescript sdk uses this + +# Standard JSON-RPC error codes +PARSE_ERROR = -32700 +INVALID_REQUEST = -32600 +METHOD_NOT_FOUND = -32601 +INVALID_PARAMS = -32602 +INTERNAL_ERROR = -32603 + + +class ErrorData(BaseModel): + """Error information for JSON-RPC error responses.""" + + code: int + """The error type that occurred.""" + + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single + sentence. + """ + + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the + sender (e.g. detailed error information, nested errors etc.). + """ + + model_config = ConfigDict(extra="allow") + + +class JSONRPCError(BaseModel): + """A response to a request that indicates an error occurred.""" + + jsonrpc: Literal["2.0"] + id: str | int + error: ErrorData + model_config = ConfigDict(extra="allow") + + +class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError]): + pass + + +class EmptyResult(Result): + """A response that indicates success but carries no data.""" + + +class BaseMetadata(BaseModel): + """Base class for entities with name and optional title fields.""" + + name: str + """The programmatic name of the entity.""" + + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class Icon(BaseModel): + """An icon for display in user interfaces.""" + + src: str + """URL or data URI for the icon.""" + + mimeType: str | None = None + """Optional MIME type for the icon.""" + + sizes: list[str] | None = None + """Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"]).""" + + model_config = ConfigDict(extra="allow") + + +class Implementation(BaseMetadata): + """Describes the name and version of an MCP implementation.""" + + version: str + + websiteUrl: str | None = None + """An optional URL of the website for this implementation.""" + + icons: list[Icon] | None = None + """An optional list of icons for this implementation.""" + + model_config = ConfigDict(extra="allow") + + +class RootsCapability(BaseModel): + """Capability for root operations.""" + + listChanged: bool | None = None + """Whether the client supports notifications for changes to the roots list.""" + model_config = ConfigDict(extra="allow") + + +class SamplingContextCapability(BaseModel): + """ + Capability for context inclusion during sampling. + + Indicates support for non-'none' values in the includeContext parameter. + SOFT-DEPRECATED: New implementations should use tools parameter instead. + """ + + model_config = ConfigDict(extra="allow") + + +class SamplingToolsCapability(BaseModel): + """ + Capability indicating support for tool calling during sampling. + + When present in ClientCapabilities.sampling, indicates that the client + supports the tools and toolChoice parameters in sampling requests. + """ + + model_config = ConfigDict(extra="allow") + + +class FormElicitationCapability(BaseModel): + """Capability for form mode elicitation.""" + + model_config = ConfigDict(extra="allow") + + +class UrlElicitationCapability(BaseModel): + """Capability for URL mode elicitation.""" + + model_config = ConfigDict(extra="allow") + + +class ElicitationCapability(BaseModel): + """Capability for elicitation operations. + + Clients must support at least one mode (form or url). + """ + + form: FormElicitationCapability | None = None + """Present if the client supports form mode elicitation.""" + + url: UrlElicitationCapability | None = None + """Present if the client supports URL mode elicitation.""" + + model_config = ConfigDict(extra="allow") + + +class SamplingCapability(BaseModel): + """ + Sampling capability structure, allowing fine-grained capability advertisement. + """ + + context: SamplingContextCapability | None = None + """ + Present if the client supports non-'none' values for includeContext parameter. + SOFT-DEPRECATED: New implementations should use tools parameter instead. + """ + tools: SamplingToolsCapability | None = None + """ + Present if the client supports tools and toolChoice parameters in sampling requests. + Presence indicates full tool calling support during sampling. + """ + model_config = ConfigDict(extra="allow") + + +class TasksListCapability(BaseModel): + """Capability for tasks listing operations.""" + + model_config = ConfigDict(extra="allow") + + +class TasksCancelCapability(BaseModel): + """Capability for tasks cancel operations.""" + + model_config = ConfigDict(extra="allow") + + +class TasksCreateMessageCapability(BaseModel): + """Capability for tasks create messages.""" + + model_config = ConfigDict(extra="allow") + + +class TasksSamplingCapability(BaseModel): + """Capability for tasks sampling operations.""" + + model_config = ConfigDict(extra="allow") + + createMessage: TasksCreateMessageCapability | None = None + + +class TasksCreateElicitationCapability(BaseModel): + """Capability for tasks create elicitation operations.""" + + model_config = ConfigDict(extra="allow") + + +class TasksElicitationCapability(BaseModel): + """Capability for tasks elicitation operations.""" + + model_config = ConfigDict(extra="allow") + + create: TasksCreateElicitationCapability | None = None + + +class ClientTasksRequestsCapability(BaseModel): + """Capability for tasks requests operations.""" + + model_config = ConfigDict(extra="allow") + + sampling: TasksSamplingCapability | None = None + + elicitation: TasksElicitationCapability | None = None + + +class ClientTasksCapability(BaseModel): + """Capability for client tasks operations.""" + + model_config = ConfigDict(extra="allow") + + list: TasksListCapability | None = None + """Whether this client supports tasks/list.""" + + cancel: TasksCancelCapability | None = None + """Whether this client supports tasks/cancel.""" + + requests: ClientTasksRequestsCapability | None = None + """Specifies which request types can be augmented with tasks.""" + + +class ClientCapabilities(BaseModel): + """Capabilities a client may support.""" + + experimental: dict[str, dict[str, Any]] | None = None + """Experimental, non-standard capabilities that the client supports.""" + sampling: SamplingCapability | None = None + """ + Present if the client supports sampling from an LLM. + Can contain fine-grained capabilities like context and tools support. + """ + elicitation: ElicitationCapability | None = None + """Present if the client supports elicitation from the user.""" + roots: RootsCapability | None = None + """Present if the client supports listing roots.""" + tasks: ClientTasksCapability | None = None + """Present if the client supports task-augmented requests.""" + + model_config = ConfigDict(extra="allow") + + +class PromptsCapability(BaseModel): + """Capability for prompts operations.""" + + listChanged: bool | None = None + """Whether this server supports notifications for changes to the prompt list.""" + model_config = ConfigDict(extra="allow") + + +class ResourcesCapability(BaseModel): + """Capability for resources operations.""" + + subscribe: bool | None = None + """Whether this server supports subscribing to resource updates.""" + listChanged: bool | None = None + """Whether this server supports notifications for changes to the resource list.""" + model_config = ConfigDict(extra="allow") + + +class ToolsCapability(BaseModel): + """Capability for tools operations.""" + + listChanged: bool | None = None + """Whether this server supports notifications for changes to the tool list.""" + model_config = ConfigDict(extra="allow") + + +class LoggingCapability(BaseModel): + """Capability for logging operations.""" + + model_config = ConfigDict(extra="allow") + + +class CompletionsCapability(BaseModel): + """Capability for completions operations.""" + + model_config = ConfigDict(extra="allow") + + +class TasksCallCapability(BaseModel): + """Capability for tasks call operations.""" + + model_config = ConfigDict(extra="allow") + + +class TasksToolsCapability(BaseModel): + """Capability for tasks tools operations.""" + + model_config = ConfigDict(extra="allow") + call: TasksCallCapability | None = None + + +class ServerTasksRequestsCapability(BaseModel): + """Capability for tasks requests operations.""" + + model_config = ConfigDict(extra="allow") + + tools: TasksToolsCapability | None = None + + +class ServerTasksCapability(BaseModel): + """Capability for server tasks operations.""" + + model_config = ConfigDict(extra="allow") + + list: TasksListCapability | None = None + cancel: TasksCancelCapability | None = None + requests: ServerTasksRequestsCapability | None = None + + +class ServerCapabilities(BaseModel): + """Capabilities that a server may support.""" + + experimental: dict[str, dict[str, Any]] | None = None + """Experimental, non-standard capabilities that the server supports.""" + logging: LoggingCapability | None = None + """Present if the server supports sending log messages to the client.""" + prompts: PromptsCapability | None = None + """Present if the server offers any prompt templates.""" + resources: ResourcesCapability | None = None + """Present if the server offers any resources to read.""" + tools: ToolsCapability | None = None + """Present if the server offers any tools to call.""" + completions: CompletionsCapability | None = None + """Present if the server offers autocompletion suggestions for prompts and resources.""" + tasks: ServerTasksCapability | None = None + """Present if the server supports task-augmented requests.""" + model_config = ConfigDict(extra="allow") + + +TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"] + +# Task status constants +TASK_STATUS_WORKING: Final[Literal["working"]] = "working" +TASK_STATUS_INPUT_REQUIRED: Final[Literal["input_required"]] = "input_required" +TASK_STATUS_COMPLETED: Final[Literal["completed"]] = "completed" +TASK_STATUS_FAILED: Final[Literal["failed"]] = "failed" +TASK_STATUS_CANCELLED: Final[Literal["cancelled"]] = "cancelled" + + +class RelatedTaskMetadata(BaseModel): + """ + Metadata for associating messages with a task. + + Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + """ + + model_config = ConfigDict(extra="allow") + taskId: str + """The task identifier this message is associated with.""" + + +class Task(BaseModel): + """Data associated with a task.""" + + model_config = ConfigDict(extra="allow") + + taskId: str + """The task identifier.""" + + status: TaskStatus + """Current task state.""" + + statusMessage: str | None = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + + createdAt: datetime # Pydantic will enforce ISO 8601 and re-serialize as a string later + """ISO 8601 timestamp when the task was created.""" + + lastUpdatedAt: datetime + """ISO 8601 timestamp when the task was last updated.""" + + ttl: Annotated[int, Field(strict=True)] | None + """Actual retention duration from creation in milliseconds, null for unlimited.""" + + pollInterval: Annotated[int, Field(strict=True)] | None = None + """Suggested polling interval in milliseconds.""" + + +class CreateTaskResult(Result): + """A response to a task-augmented request.""" + + task: Task + + +class GetTaskRequestParams(RequestParams): + model_config = ConfigDict(extra="allow") + taskId: str + """The task identifier to query.""" + + +class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]): + """A request to retrieve the state of a task.""" + + method: Literal["tasks/get"] = "tasks/get" + + params: GetTaskRequestParams + + +class GetTaskResult(Result, Task): + """The response to a tasks/get request.""" + + +class GetTaskPayloadRequestParams(RequestParams): + model_config = ConfigDict(extra="allow") + + taskId: str + """The task identifier to retrieve results for.""" + + +class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]): + """A request to retrieve the result of a completed task.""" + + method: Literal["tasks/result"] = "tasks/result" + params: GetTaskPayloadRequestParams + + +class GetTaskPayloadResult(Result): + """ + The response to a tasks/result request. + The structure matches the result type of the original request. + For example, a tools/call task would return the CallToolResult structure. + """ + + +class CancelTaskRequestParams(RequestParams): + model_config = ConfigDict(extra="allow") + + taskId: str + """The task identifier to cancel.""" + + +class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]): + """A request to cancel a task.""" + + method: Literal["tasks/cancel"] = "tasks/cancel" + params: CancelTaskRequestParams + + +class CancelTaskResult(Result, Task): + """The response to a tasks/cancel request.""" + + +class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]): + """A request to retrieve a list of tasks.""" + + method: Literal["tasks/list"] = "tasks/list" + + +class ListTasksResult(PaginatedResult): + """The response to a tasks/list request.""" + + tasks: list[Task] + + +class TaskStatusNotificationParams(NotificationParams, Task): + """Parameters for a `notifications/tasks/status` notification.""" + + +class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]): + """ + An optional notification from the receiver to the requestor, informing them that a task's status has changed. + Receivers are not required to send these notifications + """ + + method: Literal["notifications/tasks/status"] = "notifications/tasks/status" + params: TaskStatusNotificationParams + + +class InitializeRequestParams(RequestParams): + """Parameters for the initialize request.""" + + protocolVersion: str | int + """The latest version of the Model Context Protocol that the client supports.""" + capabilities: ClientCapabilities + clientInfo: Implementation + model_config = ConfigDict(extra="allow") + + +class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]): + """ + This request is sent from the client to the server when it first connects, asking it + to begin initialization. + """ + + method: Literal["initialize"] = "initialize" + params: InitializeRequestParams + + +class InitializeResult(Result): + """After receiving an initialize request from the client, the server sends this.""" + + protocolVersion: str | int + """The version of the Model Context Protocol that the server wants to use.""" + capabilities: ServerCapabilities + serverInfo: Implementation + instructions: str | None = None + """Instructions describing how to use the server and its features.""" + + +class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): + """ + This notification is sent from the client to the server after initialization has + finished. + """ + + method: Literal["notifications/initialized"] = "notifications/initialized" + params: NotificationParams | None = None + + +class PingRequest(Request[RequestParams | None, Literal["ping"]]): + """ + A ping, issued by either the server or the client, to check that the other party is + still alive. + """ + + method: Literal["ping"] = "ping" + params: RequestParams | None = None + + +class ProgressNotificationParams(NotificationParams): + """Parameters for progress notifications.""" + + progressToken: ProgressToken + """ + The progress token which was given in the initial request, used to associate this + notification with the request that is proceeding. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the + total is unknown. + """ + total: float | None = None + """Total number of items to process (or total progress required), if known.""" + message: str | None = None + """ + Message related to progress. This should provide relevant human readable + progress information. + """ + model_config = ConfigDict(extra="allow") + + +class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]): + """ + An out-of-band notification used to inform the receiver of a progress update for a + long-running request. + """ + + method: Literal["notifications/progress"] = "notifications/progress" + params: ProgressNotificationParams + + +class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]): + """Sent from the client to request a list of resources the server has.""" + + method: Literal["resources/list"] = "resources/list" + + +class Annotations(BaseModel): + audience: list[Role] | None = None + priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None + model_config = ConfigDict(extra="allow") + + +class Resource(BaseMetadata): + """A known resource that the server is capable of reading.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """The URI of this resource.""" + description: str | None = None + """A description of what this resource represents.""" + mimeType: str | None = None + """The MIME type of this resource, if known.""" + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding + or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + icons: list[Icon] | None = None + """An optional list of icons for this resource.""" + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ResourceTemplate(BaseMetadata): + """A template description for resources available on the server.""" + + uriTemplate: str + """ + A URI template (according to RFC 6570) that can be used to construct resource + URIs. + """ + description: str | None = None + """A human-readable description of what this template is for.""" + mimeType: str | None = None + """ + The MIME type for all resources that match this template. This should only be + included if all resources matching this template have the same type. + """ + icons: list[Icon] | None = None + """An optional list of icons for this resource template.""" + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ListResourcesResult(PaginatedResult): + """The server's response to a resources/list request from the client.""" + + resources: list[Resource] + + +class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates/list"]]): + """Sent from the client to request a list of resource templates the server has.""" + + method: Literal["resources/templates/list"] = "resources/templates/list" + + +class ListResourceTemplatesResult(PaginatedResult): + """The server's response to a resources/templates/list request from the client.""" + + resourceTemplates: list[ResourceTemplate] + + +class ReadResourceRequestParams(RequestParams): + """Parameters for reading a resource.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """ + The URI of the resource to read. The URI can use any protocol; it is up to the + server how to interpret it. + """ + model_config = ConfigDict(extra="allow") + + +class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/read"]]): + """Sent from the client to the server, to read a specific resource URI.""" + + method: Literal["resources/read"] = "resources/read" + params: ReadResourceRequestParams + + +class ResourceContents(BaseModel): + """The contents of a specific resource or sub-resource.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """The URI of this resource.""" + mimeType: str | None = None + """The MIME type of this resource, if known.""" + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class TextResourceContents(ResourceContents): + """Text contents of a resource.""" + + text: str + """ + The text of the item. This must only be set if the item can actually be represented + as text (not binary data). + """ + + +class BlobResourceContents(ResourceContents): + """Binary contents of a resource.""" + + blob: str + """A base64-encoded string representing the binary data of the item.""" + + +class ReadResourceResult(Result): + """The server's response to a resources/read request from the client.""" + + contents: list[TextResourceContents | BlobResourceContents] + + +class ResourceListChangedNotification( + Notification[NotificationParams | None, Literal["notifications/resources/list_changed"]] +): + """ + An optional notification from the server to the client, informing it that the list + of resources it can read from has changed. + """ + + method: Literal["notifications/resources/list_changed"] = "notifications/resources/list_changed" + params: NotificationParams | None = None + + +class SubscribeRequestParams(RequestParams): + """Parameters for subscribing to a resource.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to + the server how to interpret it. + """ + model_config = ConfigDict(extra="allow") + + +class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscribe"]]): + """ + Sent from the client to request resources/updated notifications from the server + whenever a particular resource changes. + """ + + method: Literal["resources/subscribe"] = "resources/subscribe" + params: SubscribeRequestParams + + +class UnsubscribeRequestParams(RequestParams): + """Parameters for unsubscribing from a resource.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """The URI of the resource to unsubscribe from.""" + model_config = ConfigDict(extra="allow") + + +class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): + """ + Sent from the client to request cancellation of resources/updated notifications from + the server. + """ + + method: Literal["resources/unsubscribe"] = "resources/unsubscribe" + params: UnsubscribeRequestParams + + +class ResourceUpdatedNotificationParams(NotificationParams): + """Parameters for resource update notifications.""" + + uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] + """ + The URI of the resource that has been updated. This might be a sub-resource of the + one that the client actually subscribed to. + """ + model_config = ConfigDict(extra="allow") + + +class ResourceUpdatedNotification( + Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] +): + """ + A notification from the server to the client, informing it that a resource has + changed and may need to be read again. + """ + + method: Literal["notifications/resources/updated"] = "notifications/resources/updated" + params: ResourceUpdatedNotificationParams + + +class ListPromptsRequest(PaginatedRequest[Literal["prompts/list"]]): + """Sent from the client to request a list of prompts and prompt templates.""" + + method: Literal["prompts/list"] = "prompts/list" + + +class PromptArgument(BaseModel): + """An argument for a prompt template.""" + + name: str + """The name of the argument.""" + description: str | None = None + """A human-readable description of the argument.""" + required: bool | None = None + """Whether this argument must be provided.""" + model_config = ConfigDict(extra="allow") + + +class Prompt(BaseMetadata): + """A prompt or prompt template that the server offers.""" + + description: str | None = None + """An optional description of what this prompt provides.""" + arguments: list[PromptArgument] | None = None + """A list of arguments to use for templating the prompt.""" + icons: list[Icon] | None = None + """An optional list of icons for this prompt.""" + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ListPromptsResult(PaginatedResult): + """The server's response to a prompts/list request from the client.""" + + prompts: list[Prompt] + + +class GetPromptRequestParams(RequestParams): + """Parameters for getting a prompt.""" + + name: str + """The name of the prompt or prompt template.""" + arguments: dict[str, str] | None = None + """Arguments to use for templating the prompt.""" + model_config = ConfigDict(extra="allow") + + +class GetPromptRequest(Request[GetPromptRequestParams, Literal["prompts/get"]]): + """Used by the client to get a prompt provided by the server.""" + + method: Literal["prompts/get"] = "prompts/get" + params: GetPromptRequestParams + + +class TextContent(BaseModel): + """Text content for a message.""" + + type: Literal["text"] + text: str + """The text content of the message.""" + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ImageContent(BaseModel): + """Image content for a message.""" + + type: Literal["image"] + data: str + """The base64-encoded image data.""" + mimeType: str + """ + The MIME type of the image. Different providers may support different + image types. + """ + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class AudioContent(BaseModel): + """Audio content for a message.""" + + type: Literal["audio"] + data: str + """The base64-encoded audio data.""" + mimeType: str + """ + The MIME type of the audio. Different providers may support different + audio types. + """ + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ToolUseContent(BaseModel): + """ + Content representing an assistant's request to invoke a tool. + + This content type appears in assistant messages when the LLM wants to call a tool + during sampling. The server should execute the tool and return a ToolResultContent + in the next user message. + """ + + type: Literal["tool_use"] + """Discriminator for tool use content.""" + + name: str + """The name of the tool to invoke. Must match a tool name from the request's tools array.""" + + id: str + """Unique identifier for this tool call, used to correlate with ToolResultContent.""" + + input: dict[str, Any] + """Arguments to pass to the tool. Must conform to the tool's inputSchema.""" + + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ToolResultContent(BaseModel): + """ + Content representing the result of a tool execution. + + This content type appears in user messages as a response to a ToolUseContent + from the assistant. It contains the output of executing the requested tool. + """ + + type: Literal["tool_result"] + """Discriminator for tool result content.""" + + toolUseId: str + """The unique identifier that corresponds to the tool call's id field.""" + + content: list["ContentBlock"] = [] + """ + A list of content objects representing the tool result. + Defaults to empty list if not provided. + """ + + structuredContent: dict[str, Any] | None = None + """ + Optional structured tool output that matches the tool's outputSchema (if defined). + """ + + isError: bool | None = None + """Whether the tool execution resulted in an error.""" + + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent +"""Content block types allowed in sampling messages.""" + +SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent +"""Basic content types for sampling responses (without tool use). +Used for backwards-compatible CreateMessageResult when tools are not used.""" + + +class SamplingMessage(BaseModel): + """Describes a message issued to or received from an LLM API.""" + + role: Role + content: SamplingMessageContentBlock | list[SamplingMessageContentBlock] + """ + Message content. Can be a single content block or an array of content blocks + for multi-modal messages and tool interactions. + """ + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + @property + def content_as_list(self) -> list[SamplingMessageContentBlock]: + """Returns the content as a list of content blocks, regardless of whether + it was originally a single block or a list.""" + return self.content if isinstance(self.content, list) else [self.content] + + +class EmbeddedResource(BaseModel): + """ + The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + type: Literal["resource"] + resource: TextResourceContents | BlobResourceContents + annotations: Annotations | None = None + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ResourceLink(Resource): + """ + A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + type: Literal["resource_link"] + + +ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource +"""A content block that can be used in prompts and tool results.""" + +Content: TypeAlias = ContentBlock +# """DEPRECATED: Content is deprecated, you should use ContentBlock directly.""" + + +class PromptMessage(BaseModel): + """Describes a message returned as part of a prompt.""" + + role: Role + content: ContentBlock + model_config = ConfigDict(extra="allow") + + +class GetPromptResult(Result): + """The server's response to a prompts/get request from the client.""" + + description: str | None = None + """An optional description for the prompt.""" + messages: list[PromptMessage] + + +class PromptListChangedNotification( + Notification[NotificationParams | None, Literal["notifications/prompts/list_changed"]] +): + """ + An optional notification from the server to the client, informing it that the list + of prompts it offers has changed. + """ + + method: Literal["notifications/prompts/list_changed"] = "notifications/prompts/list_changed" + params: NotificationParams | None = None + + +class ListToolsRequest(PaginatedRequest[Literal["tools/list"]]): + """Sent from the client to request a list of tools the server has.""" + + method: Literal["tools/list"] = "tools/list" + + +class ToolAnnotations(BaseModel): + """ + Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + title: str | None = None + """A human-readable title for the tool.""" + + readOnlyHint: bool | None = None + """ + If true, the tool does not modify its environment. + Default: false + """ + + destructiveHint: bool | None = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + (This property is meaningful only when `readOnlyHint == false`) + Default: true + """ + + idempotentHint: bool | None = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on the its environment. + (This property is meaningful only when `readOnlyHint == false`) + Default: false + """ + + openWorldHint: bool | None = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + Default: true + """ + + model_config = ConfigDict(extra="allow") + + +class ToolExecution(BaseModel): + """Execution-related properties for a tool.""" + + model_config = ConfigDict(extra="allow") + + taskSupport: TaskExecutionMode | None = None + """ + Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" + """ + + +class Tool(BaseMetadata): + """Definition for a tool the client can call.""" + + description: str | None = None + """A human-readable description of the tool.""" + inputSchema: dict[str, Any] + """A JSON Schema object defining the expected parameters for the tool.""" + outputSchema: dict[str, Any] | None = None + """ + An optional JSON Schema object defining the structure of the tool's output + returned in the structuredContent field of a CallToolResult. + """ + icons: list[Icon] | None = None + """An optional list of icons for this tool.""" + annotations: ToolAnnotations | None = None + """Optional additional tool information.""" + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + + execution: ToolExecution | None = None + + model_config = ConfigDict(extra="allow") + + +class ListToolsResult(PaginatedResult): + """The server's response to a tools/list request from the client.""" + + tools: list[Tool] + + +class CallToolRequestParams(RequestParams): + """Parameters for calling a tool.""" + + name: str + arguments: dict[str, Any] | None = None + model_config = ConfigDict(extra="allow") + + +class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): + """Used by the client to invoke a tool provided by the server.""" + + method: Literal["tools/call"] = "tools/call" + params: CallToolRequestParams + + +class CallToolResult(Result): + """The server's response to a tool call.""" + + content: list[ContentBlock] + structuredContent: dict[str, Any] | None = None + """An optional JSON object that represents the structured result of the tool call.""" + isError: bool = False + + +class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): + """ + An optional notification from the server to the client, informing it that the list + of tools it offers has changed. + """ + + method: Literal["notifications/tools/list_changed"] = "notifications/tools/list_changed" + params: NotificationParams | None = None + + +LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"] + + +class SetLevelRequestParams(RequestParams): + """Parameters for setting the logging level.""" + + level: LoggingLevel + """The level of logging that the client wants to receive from the server.""" + model_config = ConfigDict(extra="allow") + + +class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]): + """A request from the client to the server, to enable or adjust logging.""" + + method: Literal["logging/setLevel"] = "logging/setLevel" + params: SetLevelRequestParams + + +class LoggingMessageNotificationParams(NotificationParams): + """Parameters for logging message notifications.""" + + level: LoggingLevel + """The severity of this log message.""" + logger: str | None = None + """An optional name of the logger issuing this message.""" + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable + type is allowed here. + """ + model_config = ConfigDict(extra="allow") + + +class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): + """Notification of a log message passed from server to client.""" + + method: Literal["notifications/message"] = "notifications/message" + params: LoggingMessageNotificationParams + + +IncludeContext = Literal["none", "thisServer", "allServers"] + + +class ModelHint(BaseModel): + """Hints to use for model selection.""" + + name: str | None = None + """A hint for a model name.""" + + model_config = ConfigDict(extra="allow") + + +class ModelPreferences(BaseModel): + """ + The server's preferences for model selection, requested by the client during + sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + + costPriority: float | None = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + + speedPriority: float | None = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + intelligencePriority: float | None = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + + model_config = ConfigDict(extra="allow") + + +class ToolChoice(BaseModel): + """ + Controls tool usage behavior during sampling. + + Allows the server to specify whether and how the LLM should use tools + in its response. + """ + + mode: Literal["auto", "required", "none"] | None = None + """ + Controls when tools are used: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model should not use tools + """ + + model_config = ConfigDict(extra="allow") + + +class CreateMessageRequestParams(RequestParams): + """Parameters for creating a message.""" + + messages: list[SamplingMessage] + modelPreferences: ModelPreferences | None = None + """ + The server's preferences for which model to select. The client MAY ignore + these preferences. + """ + systemPrompt: str | None = None + """An optional system prompt the server wants to use for sampling.""" + includeContext: IncludeContext | None = None + """ + A request to include context from one or more MCP servers (including the caller), to + be attached to the prompt. + """ + temperature: float | None = None + maxTokens: int + """The maximum number of tokens to sample, as requested by the server.""" + stopSequences: list[str] | None = None + metadata: dict[str, Any] | None = None + """Optional metadata to pass through to the LLM provider.""" + tools: list["Tool"] | None = None + """ + Tool definitions for the LLM to use during sampling. + Requires clientCapabilities.sampling.tools to be present. + """ + toolChoice: ToolChoice | None = None + """ + Controls tool usage behavior. + Requires clientCapabilities.sampling.tools and the tools parameter to be present. + """ + model_config = ConfigDict(extra="allow") + + +class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): + """A request from the server to sample an LLM via the client.""" + + method: Literal["sampling/createMessage"] = "sampling/createMessage" + params: CreateMessageRequestParams + + +StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str + + +class CreateMessageResult(Result): + """The client's response to a sampling/create_message request from the server. + + This is the backwards-compatible version that returns single content (no arrays). + Used when the request does not include tools. + """ + + role: Role + """The role of the message sender (typically 'assistant' for LLM responses).""" + content: SamplingContent + """Response content. Single content block (text, image, or audio).""" + model: str + """The name of the model that generated the message.""" + stopReason: StopReason | None = None + """The reason why sampling stopped, if known.""" + + +class CreateMessageResultWithTools(Result): + """The client's response to a sampling/create_message request when tools were provided. + + This version supports array content for tool use flows. + """ + + role: Role + """The role of the message sender (typically 'assistant' for LLM responses).""" + content: SamplingMessageContentBlock | list[SamplingMessageContentBlock] + """ + Response content. May be a single content block or an array. + May include ToolUseContent if stopReason is 'toolUse'. + """ + model: str + """The name of the model that generated the message.""" + stopReason: StopReason | None = None + """ + The reason why sampling stopped, if known. + 'toolUse' indicates the model wants to use a tool. + """ + + @property + def content_as_list(self) -> list[SamplingMessageContentBlock]: + """Returns the content as a list of content blocks, regardless of whether + it was originally a single block or a list.""" + return self.content if isinstance(self.content, list) else [self.content] + + +class ResourceTemplateReference(BaseModel): + """A reference to a resource or resource template definition.""" + + type: Literal["ref/resource"] + uri: str + """The URI or URI template of the resource.""" + model_config = ConfigDict(extra="allow") + + +@deprecated("`ResourceReference` is deprecated, you should use `ResourceTemplateReference`.") +class ResourceReference(ResourceTemplateReference): + pass + + +class PromptReference(BaseModel): + """Identifies a prompt.""" + + type: Literal["ref/prompt"] + name: str + """The name of the prompt or prompt template""" + model_config = ConfigDict(extra="allow") + + +class CompletionArgument(BaseModel): + """The argument's information for completion requests.""" + + name: str + """The name of the argument""" + value: str + """The value of the argument to use for completion matching.""" + model_config = ConfigDict(extra="allow") + + +class CompletionContext(BaseModel): + """Additional, optional context for completions.""" + + arguments: dict[str, str] | None = None + """Previously-resolved variables in a URI template or prompt.""" + model_config = ConfigDict(extra="allow") + + +class CompleteRequestParams(RequestParams): + """Parameters for completion requests.""" + + ref: ResourceTemplateReference | PromptReference + argument: CompletionArgument + context: CompletionContext | None = None + """Additional, optional context for completions""" + model_config = ConfigDict(extra="allow") + + +class CompleteRequest(Request[CompleteRequestParams, Literal["completion/complete"]]): + """A request from the client to the server, to ask for completion options.""" + + method: Literal["completion/complete"] = "completion/complete" + params: CompleteRequestParams + + +class Completion(BaseModel): + """Completion information.""" + + values: list[str] + """An array of completion values. Must not exceed 100 items.""" + total: int | None = None + """ + The total number of completion options available. This can exceed the number of + values actually sent in the response. + """ + hasMore: bool | None = None + """ + Indicates whether there are additional completion options beyond those provided in + the current response, even if the exact total is unknown. + """ + model_config = ConfigDict(extra="allow") + + +class CompleteResult(Result): + """The server's response to a completion/complete request""" + + completion: Completion + + +class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): + """ + Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + method: Literal["roots/list"] = "roots/list" + params: RequestParams | None = None + + +class Root(BaseModel): + """Represents a root directory or file that the server can operate on.""" + + uri: FileUrl + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + meta: dict[str, Any] | None = Field(alias="_meta", default=None) + """ + See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + for notes on _meta usage. + """ + model_config = ConfigDict(extra="allow") + + +class ListRootsResult(Result): + """ + The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + roots: list[Root] + + +class RootsListChangedNotification( + Notification[NotificationParams | None, Literal["notifications/roots/list_changed"]] +): + """ + A notification from the client to the server, informing it that the list of + roots has changed. + + This notification should be sent whenever the client adds, removes, or + modifies any root. The server should then request an updated list of roots + using the ListRootsRequest. + """ + + method: Literal["notifications/roots/list_changed"] = "notifications/roots/list_changed" + params: NotificationParams | None = None + + +class CancelledNotificationParams(NotificationParams): + """Parameters for cancellation notifications.""" + + requestId: RequestId | None = None + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). + """ + reason: str | None = None + """An optional string describing the reason for the cancellation.""" + + model_config = ConfigDict(extra="allow") + + +class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): + """ + This notification can be sent by either side to indicate that it is canceling a + previously-issued request. + """ + + method: Literal["notifications/cancelled"] = "notifications/cancelled" + params: CancelledNotificationParams + + +class ElicitCompleteNotificationParams(NotificationParams): + """Parameters for elicitation completion notifications.""" + + elicitationId: str + """The unique identifier of the elicitation that was completed.""" + + model_config = ConfigDict(extra="allow") + + +class ElicitCompleteNotification( + Notification[ElicitCompleteNotificationParams, Literal["notifications/elicitation/complete"]] +): + """ + A notification from the server to the client, informing it that a URL mode + elicitation has been completed. + + Clients MAY use the notification to automatically retry requests that received a + URLElicitationRequiredError, update the user interface, or otherwise continue + an interaction. However, because delivery of the notification is not guaranteed, + clients must not wait indefinitely for a notification from the server. + """ + + method: Literal["notifications/elicitation/complete"] = "notifications/elicitation/complete" + params: ElicitCompleteNotificationParams + + +ClientRequestType: TypeAlias = ( + PingRequest + | InitializeRequest + | CompleteRequest + | SetLevelRequest + | GetPromptRequest + | ListPromptsRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | CallToolRequest + | ListToolsRequest + | GetTaskRequest + | GetTaskPayloadRequest + | ListTasksRequest + | CancelTaskRequest +) + + +class ClientRequest(RootModel[ClientRequestType]): + pass + + +ClientNotificationType: TypeAlias = ( + CancelledNotification + | ProgressNotification + | InitializedNotification + | RootsListChangedNotification + | TaskStatusNotification +) + + +class ClientNotification(RootModel[ClientNotificationType]): + pass + + +# Type for elicitation schema - a JSON Schema dict +ElicitRequestedSchema: TypeAlias = dict[str, Any] +"""Schema for elicitation requests.""" + + +class ElicitRequestFormParams(RequestParams): + """Parameters for form mode elicitation requests. + + Form mode collects non-sensitive information from the user via an in-band form + rendered by the client. + """ + + mode: Literal["form"] = "form" + """The elicitation mode (always "form" for this type).""" + + message: str + """The message to present to the user describing what information is being requested.""" + + requestedSchema: ElicitRequestedSchema + """ + A restricted subset of JSON Schema defining the structure of expected response. + Only top-level properties are allowed, without nesting. + """ + + model_config = ConfigDict(extra="allow") + + +class ElicitRequestURLParams(RequestParams): + """Parameters for URL mode elicitation requests. + + URL mode directs users to external URLs for sensitive out-of-band interactions + like OAuth flows, credential collection, or payment processing. + """ + + mode: Literal["url"] = "url" + """The elicitation mode (always "url" for this type).""" + + message: str + """The message to present to the user explaining why the interaction is needed.""" + + url: str + """The URL that the user should navigate to.""" + + elicitationId: str + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ + + model_config = ConfigDict(extra="allow") + + +# Union type for elicitation request parameters +ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams +"""Parameters for elicitation requests - either form or URL mode.""" + + +class ElicitRequest(Request[ElicitRequestParams, Literal["elicitation/create"]]): + """A request from the server to elicit information from the client.""" + + method: Literal["elicitation/create"] = "elicitation/create" + params: ElicitRequestParams + + +class ElicitResult(Result): + """The client's response to an elicitation request.""" + + action: Literal["accept", "decline", "cancel"] + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action (or consented to URL navigation) + - "decline": User explicitly declined the action + - "cancel": User dismissed without making an explicit choice + """ + + content: dict[str, str | int | float | bool | list[str] | None] | None = None + """ + The submitted form data, only present when action is "accept" in form mode. + Contains values matching the requested schema. Values can be strings, integers, + booleans, or arrays of strings. + For URL mode, this field is omitted. + """ + + +class ElicitationRequiredErrorData(BaseModel): + """Error data for URLElicitationRequiredError. + + Servers return this when a request cannot be processed until one or more + URL mode elicitations are completed. + """ + + elicitations: list[ElicitRequestURLParams] + """List of URL mode elicitations that must be completed.""" + + model_config = ConfigDict(extra="allow") + + +ClientResultType: TypeAlias = ( + EmptyResult + | CreateMessageResult + | CreateMessageResultWithTools + | ListRootsResult + | ElicitResult + | GetTaskResult + | GetTaskPayloadResult + | ListTasksResult + | CancelTaskResult + | CreateTaskResult +) + + +class ClientResult(RootModel[ClientResultType]): + pass + + +ServerRequestType: TypeAlias = ( + PingRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest + | GetTaskRequest + | GetTaskPayloadRequest + | ListTasksRequest + | CancelTaskRequest +) + + +class ServerRequest(RootModel[ServerRequestType]): + pass + + +ServerNotificationType: TypeAlias = ( + CancelledNotification + | ProgressNotification + | LoggingMessageNotification + | ResourceUpdatedNotification + | ResourceListChangedNotification + | ToolListChangedNotification + | PromptListChangedNotification + | ElicitCompleteNotification + | TaskStatusNotification +) + + +class ServerNotification(RootModel[ServerNotificationType]): + pass + + +ServerResultType: TypeAlias = ( + EmptyResult + | InitializeResult + | CompleteResult + | GetPromptResult + | ListPromptsResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | CallToolResult + | ListToolsResult + | GetTaskResult + | GetTaskPayloadResult + | ListTasksResult + | CancelTaskResult + | CreateTaskResult +) + + +class ServerResult(RootModel[ServerResultType]): + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py new file mode 100644 index 0000000..53c08d2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/__init__.py @@ -0,0 +1,38 @@ +# Copyright 2012, Andrew Dunham +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This only works if using a file system, other loaders not implemented. + +import importlib.util +import sys +import warnings +from pathlib import Path + +for p in sys.path: + file_path = Path(p, "multipart.py") + try: + if file_path.is_file(): + spec = importlib.util.spec_from_file_location("multipart", file_path) + assert spec is not None, f"{file_path} found but not loadable!" + module = importlib.util.module_from_spec(spec) + sys.modules["multipart"] = module + assert spec.loader is not None, f"{file_path} must be loadable!" + spec.loader.exec_module(module) + break + except PermissionError: + pass +else: + warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2) + from python_multipart import * + from python_multipart import __all__, __version__ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py new file mode 100644 index 0000000..31acdfb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/decoders.py @@ -0,0 +1 @@ +from python_multipart.decoders import * diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py new file mode 100644 index 0000000..36815d1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py @@ -0,0 +1 @@ +from python_multipart.exceptions import * diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py new file mode 100644 index 0000000..7bf567d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/multipart/multipart.py @@ -0,0 +1 @@ +from python_multipart.multipart import * diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA new file mode 100644 index 0000000..b94a88b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA @@ -0,0 +1,244 @@ +Metadata-Version: 2.4 +Name: pycparser +Version: 3.0 +Summary: C parser in Python +Author-email: Eli Bendersky +Maintainer-email: Eli Bendersky +License-Expression: BSD-3-Clause +Project-URL: Homepage, https://github.com/eliben/pycparser +Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: license-file + +=============== +pycparser v3.00 +=============== + + +.. image:: https://github.com/eliben/pycparser/workflows/pycparser-tests/badge.svg + :align: center + :target: https://github.com/eliben/pycparser/actions + +---- + +.. contents:: + :backlinks: none + +.. sectnum:: + +Introduction +============ + +What is pycparser? +------------------ + +**pycparser** is a parser for the C language, written in pure Python. It is a +module designed to be easily integrated into applications that need to parse +C source code. + +What is it good for? +-------------------- + +Anything that needs C code to be parsed. The following are some uses for +**pycparser**, taken from real user reports: + +* C code obfuscator +* Front-end for various specialized C compilers +* Static code checker +* Automatic unit-test discovery +* Adding specialized extensions to the C language + +One of the most popular uses of **pycparser** is in the `cffi +`_ library, which uses it to parse the +declarations of C functions and types in order to auto-generate FFIs. + +**pycparser** is unique in the sense that it's written in pure Python - a very +high level language that's easy to experiment with and tweak. To people familiar +with Lex and Yacc, **pycparser**'s code will be simple to understand. It also +has no external dependencies (except for a Python interpreter), making it very +simple to install and deploy. + +Which version of C does pycparser support? +------------------------------------------ + +**pycparser** aims to support the full C99 language (according to the standard +ISO/IEC 9899). Some features from C11 are also supported, and patches to support +more are welcome. + +**pycparser** supports very few GCC extensions, but it's fairly easy to set +things up so that it parses code with a lot of GCC-isms successfully. See the +`FAQ `_ for more details. + +What grammar does pycparser follow? +----------------------------------- + +**pycparser** very closely follows the C grammar provided in Annex A of the C99 +standard (ISO/IEC 9899). + +How is pycparser licensed? +-------------------------- + +`BSD license `_. + +Contact details +--------------- + +For reporting problems with **pycparser** or submitting feature requests, please +open an `issue `_, or submit a +pull request. + + +Installing +========== + +Prerequisites +------------- + +**pycparser** is being tested with modern versions of Python on +Linux, macOS and Windows. See `the CI dashboard `__ +for details. + +**pycparser** has no external dependencies. + +Installation process +-------------------- + +The recommended way to install **pycparser** is with ``pip``:: + + > pip install pycparser + +Using +===== + +Interaction with the C preprocessor +----------------------------------- + +In order to be compilable, C code must be preprocessed by the C preprocessor - +``cpp``. A compatible ``cpp`` handles preprocessing directives like ``#include`` and +``#define``, removes comments, and performs other minor tasks that prepare the C +code for compilation. + +For all but the most trivial snippets of C code **pycparser**, like a C +compiler, must receive preprocessed C code in order to function correctly. If +you import the top-level ``parse_file`` function from the **pycparser** package, +it will interact with ``cpp`` for you, as long as it's in your PATH, or you +provide a path to it. + +Note also that you can use ``gcc -E`` or ``clang -E`` instead of ``cpp``. See +the ``using_gcc_E_libc.py`` example for more details. Windows users can download +and install a binary build of Clang for Windows `from this website +`_. + +What about the standard C library headers? +------------------------------------------ + +C code almost always ``#include``\s various header files from the standard C +library, like ``stdio.h``. While (with some effort) **pycparser** can be made to +parse the standard headers from any C compiler, it's much simpler to use the +provided "fake" standard includes for C11 in ``utils/fake_libc_include``. These +are standard C header files that contain only the bare necessities to allow +valid parsing of the files that use them. As a bonus, since they're minimal, it +can significantly improve the performance of parsing large C files. + +The key point to understand here is that **pycparser** doesn't really care about +the semantics of types. It only needs to know whether some token encountered in +the source is a previously defined type. This is essential in order to be able +to parse C correctly. + +See `this blog post +`_ +for more details. + +Note that the fake headers are not included in the ``pip`` package nor installed +via the package build (`#224 `_). + +Basic usage +----------- + +Take a look at the |examples|_ directory of the distribution for a few examples +of using **pycparser**. These should be enough to get you started. Please note +that most realistic C code samples would require running the C preprocessor +before passing the code to **pycparser**; see the previous sections for more +details. + +.. |examples| replace:: ``examples`` +.. _examples: examples + + +Advanced usage +-------------- + +The public interface of **pycparser** is well documented with comments in +``pycparser/c_parser.py``. For a detailed overview of the various AST nodes +created by the parser, see ``pycparser/_c_ast.cfg``. + +There's also a `FAQ available here `_. +In any case, you can always drop me an `email `_ for help. + + +Modifying +========= + +There are a few points to keep in mind when modifying **pycparser**: + +* The code for **pycparser**'s AST nodes is automatically generated from a + configuration file - ``_c_ast.cfg``, by ``_ast_gen.py``. If you modify the AST + configuration, make sure to re-generate the code. This can be done by running + the ``_ast_gen.py`` script (from the repository root or the + ``pycparser`` directory). +* Read the docstring in the constructor of the ``CParser`` class for details + on configuration and compatibility arguments. + + +Package contents +================ + +Once you unzip the ``pycparser`` package, you'll see the following files and +directories: + +README.rst: + This README file. + +LICENSE: + The pycparser license + +setup.py: + Legacy installation script (build metadata lives in ``pyproject.toml``). + +pyproject.toml: + Package metadata and build configuration. + +examples/: + A directory with some examples of using **pycparser** + +pycparser/: + The **pycparser** module source code. + +tests/: + Unit tests. + +utils/fake_libc_include: + Minimal standard C library include files that should allow to parse any C code. + Note that these headers now include C11 code, so they may not work when the + preprocessor is configured to an earlier C standard (like ``-std=c99``). + +utils/internal/: + Internal utilities for my own use. You probably don't need them. + + +Contributors +============ + +Some people have contributed to **pycparser** by opening issues on bugs they've +found and/or submitting patches. The list of contributors is in the CONTRIBUTORS +file in the source distribution. After **pycparser** moved to Github I stopped +updating this list because Github does a much better job at tracking +contributions. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD new file mode 100644 index 0000000..b3383a3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD @@ -0,0 +1,13 @@ +pycparser/__init__.py,sha256=phViRyAuUmgqE4kNmaCqpm5WVEBIvzUSFapBv4XX3xo,2829 +pycparser/_ast_gen.py,sha256=ExH5Ym4pk7dQPEIkQr9RJim5feztdBQwSBPvpvE-5BM,11292 +pycparser/_c_ast.cfg,sha256=ld5ezE9yzIJFIVAUfw7ezJSlMi4nXKNCzfmqjOyQTNo,4255 +pycparser/ast_transforms.py,sha256=XwMsarc5aDddNWgIiKm4-jOWMRYib96yNQUo0_u28WA,5899 +pycparser/c_ast.py,sha256=uwkcZWHfXDQIw6WDvCL17iWM_-0R-URDqEMmPjXLOAc,32954 +pycparser/c_generator.py,sha256=RVKJPguv2CvovHHSfQkqimckUr9wGU9PofxCGj251QA,20661 +pycparser/c_lexer.py,sha256=B1VoqbYhPWkOJJWCem4OY4zj0IxrPktBZZFe2Y87kUg,25155 +pycparser/c_parser.py,sha256=3FBKGLjjlC3v8afwD_cnMR67ImoYIy73a4WKQR6hX7g,89798 +pycparser-3.0.dist-info/licenses/LICENSE,sha256=DIRjmTaep23de1xE_m0WSXQV_PAV9cu1CMJL-YuBxbE,1543 +pycparser-3.0.dist-info/METADATA,sha256=9UemCwq1TMLyQiE9H4eWCtgN991d_rmNF6RE1Iv7a5M,8229 +pycparser-3.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92 +pycparser-3.0.dist-info/top_level.txt,sha256=c-lPcS74L_8KoH7IE6PQF5ofyirRQNV4VhkbSFIPeWM,10 +pycparser-3.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL new file mode 100644 index 0000000..fbbd86c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.10.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..bee14a4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE @@ -0,0 +1,27 @@ +pycparser -- A C parser in Python + +Copyright (c) 2008-2022, Eli Bendersky +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt new file mode 100644 index 0000000..dc1c9e1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pycparser diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py new file mode 100644 index 0000000..0606c03 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py @@ -0,0 +1,99 @@ +# ----------------------------------------------------------------- +# pycparser: __init__.py +# +# This package file exports some convenience functions for +# interacting with pycparser +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ----------------------------------------------------------------- +__all__ = ["c_lexer", "c_parser", "c_ast"] +__version__ = "3.00" + +import io +from subprocess import check_output + +from . import c_parser + +CParser = c_parser.CParser + + +def preprocess_file(filename, cpp_path="cpp", cpp_args=""): + """Preprocess a file using cpp. + + filename: + Name of the file you want to preprocess. + + cpp_path: + cpp_args: + Refer to the documentation of parse_file for the meaning of these + arguments. + + When successful, returns the preprocessed file's contents. + Errors from cpp will be printed out. + """ + path_list = [cpp_path] + if isinstance(cpp_args, list): + path_list += cpp_args + elif cpp_args != "": + path_list += [cpp_args] + path_list += [filename] + + try: + # Note the use of universal_newlines to treat all newlines + # as \n for Python's purpose + text = check_output(path_list, universal_newlines=True) + except OSError as e: + raise RuntimeError( + "Unable to invoke 'cpp'. " + + "Make sure its path was passed correctly\n" + + f"Original error: {e}" + ) + + return text + + +def parse_file( + filename, use_cpp=False, cpp_path="cpp", cpp_args="", parser=None, encoding=None +): + """Parse a C file using pycparser. + + filename: + Name of the file you want to parse. + + use_cpp: + Set to True if you want to execute the C pre-processor + on the file prior to parsing it. + + cpp_path: + If use_cpp is True, this is the path to 'cpp' on your + system. If no path is provided, it attempts to just + execute 'cpp', so it must be in your PATH. + + cpp_args: + If use_cpp is True, set this to the command line arguments strings + to cpp. Be careful with quotes - it's best to pass a raw string + (r'') here. For example: + r'-I../utils/fake_libc_include' + If several arguments are required, pass a list of strings. + + encoding: + Encoding to use for the file to parse + + parser: + Optional parser object to be used instead of the default CParser + + When successful, an AST is returned. ParseError can be + thrown if the file doesn't parse successfully. + + Errors from cpp will be printed out. + """ + if use_cpp: + text = preprocess_file(filename, cpp_path, cpp_args) + else: + with io.open(filename, encoding=encoding) as f: + text = f.read() + + if parser is None: + parser = CParser() + return parser.parse(text, filename) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py new file mode 100644 index 0000000..6df0b6a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py @@ -0,0 +1,355 @@ +# ----------------------------------------------------------------- +# _ast_gen.py +# +# Generates the AST Node classes from a specification given in +# a configuration file. This module can also be run as a script to +# regenerate c_ast.py from _c_ast.cfg (from the repo root or the +# pycparser/ directory). Use 'make check' to reformat the generated +# file after running this script. +# +# The design of this module was inspired by astgen.py from the +# Python 2.5 code-base. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ----------------------------------------------------------------- +from string import Template +import os +from typing import IO + + +class ASTCodeGenerator: + def __init__(self, cfg_filename="_c_ast.cfg"): + """Initialize the code generator from a configuration + file. + """ + self.cfg_filename = cfg_filename + self.node_cfg = [ + NodeCfg(name, contents) + for (name, contents) in self.parse_cfgfile(cfg_filename) + ] + + def generate(self, file: IO[str]) -> None: + """Generates the code into file, an open file buffer.""" + src = Template(_PROLOGUE_COMMENT).substitute(cfg_filename=self.cfg_filename) + + src += _PROLOGUE_CODE + for node_cfg in self.node_cfg: + src += node_cfg.generate_source() + "\n\n" + + file.write(src) + + def parse_cfgfile(self, filename): + """Parse the configuration file and yield pairs of + (name, contents) for each node. + """ + with open(filename, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + colon_i = line.find(":") + lbracket_i = line.find("[") + rbracket_i = line.find("]") + if colon_i < 1 or lbracket_i <= colon_i or rbracket_i <= lbracket_i: + raise RuntimeError(f"Invalid line in {filename}:\n{line}\n") + + name = line[:colon_i] + val = line[lbracket_i + 1 : rbracket_i] + vallist = [v.strip() for v in val.split(",")] if val else [] + yield name, vallist + + +class NodeCfg: + """Node configuration. + + name: node name + contents: a list of contents - attributes and child nodes + See comment at the top of the configuration file for details. + """ + + def __init__(self, name, contents): + self.name = name + self.all_entries = [] + self.attr = [] + self.child = [] + self.seq_child = [] + + for entry in contents: + clean_entry = entry.rstrip("*") + self.all_entries.append(clean_entry) + + if entry.endswith("**"): + self.seq_child.append(clean_entry) + elif entry.endswith("*"): + self.child.append(clean_entry) + else: + self.attr.append(entry) + + def generate_source(self): + src = self._gen_init() + src += "\n" + self._gen_children() + src += "\n" + self._gen_iter() + src += "\n" + self._gen_attr_names() + return src + + def _gen_init(self): + src = f"class {self.name}(Node):\n" + + if self.all_entries: + args = ", ".join(self.all_entries) + slots = ", ".join(f"'{e}'" for e in self.all_entries) + slots += ", 'coord', '__weakref__'" + arglist = f"(self, {args}, coord=None)" + else: + slots = "'coord', '__weakref__'" + arglist = "(self, coord=None)" + + src += f" __slots__ = ({slots})\n" + src += f" def __init__{arglist}:\n" + + for name in self.all_entries + ["coord"]: + src += f" self.{name} = {name}\n" + + return src + + def _gen_children(self): + src = " def children(self):\n" + + if self.all_entries: + src += " nodelist = []\n" + + for child in self.child: + src += f" if self.{child} is not None:\n" + src += f' nodelist.append(("{child}", self.{child}))\n' + + for seq_child in self.seq_child: + src += f" for i, child in enumerate(self.{seq_child} or []):\n" + src += f' nodelist.append((f"{seq_child}[{{i}}]", child))\n' + + src += " return tuple(nodelist)\n" + else: + src += " return ()\n" + + return src + + def _gen_iter(self): + src = " def __iter__(self):\n" + + if self.all_entries: + for child in self.child: + src += f" if self.{child} is not None:\n" + src += f" yield self.{child}\n" + + for seq_child in self.seq_child: + src += f" for child in (self.{seq_child} or []):\n" + src += " yield child\n" + + if not (self.child or self.seq_child): + # Empty generator + src += " return\n" + " yield\n" + else: + # Empty generator + src += " return\n" + " yield\n" + + return src + + def _gen_attr_names(self): + src = " attr_names = (" + "".join(f"{nm!r}, " for nm in self.attr) + ")" + return src + + +_PROLOGUE_COMMENT = r"""#----------------------------------------------------------------- +# ** ATTENTION ** +# This code was automatically generated from _c_ast.cfg +# +# Do not modify it directly. Modify the configuration file and +# run the generator again. +# ** ** *** ** ** +# +# pycparser: c_ast.py +# +# AST Node classes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +""" +_PROLOGUE_CODE = r''' +import sys +from typing import Any, ClassVar, IO, Optional + +def _repr(obj): + """ + Get the representation of an object, with dedicated pprint-like format for lists. + """ + if isinstance(obj, list): + return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]' + else: + return repr(obj) + +class Node: + __slots__ = () + """ Abstract base class for AST nodes. + """ + attr_names: ClassVar[tuple[str, ...]] = () + coord: Optional[Any] + def __repr__(self): + """ Generates a python representation of the current node + """ + result = self.__class__.__name__ + '(' + + indent = '' + separator = '' + for name in self.__slots__[:-2]: + result += separator + result += indent + result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__))))) + + separator = ',' + indent = '\n ' + (' ' * len(self.__class__.__name__)) + + result += indent + ')' + + return result + + def children(self): + """ A sequence of all children that are Nodes + """ + pass + + def show( + self, + buf: IO[str] = sys.stdout, + offset: int = 0, + attrnames: bool = False, + showemptyattrs: bool = True, + nodenames: bool = False, + showcoord: bool = False, + _my_node_name: Optional[str] = None, + ): + """ Pretty print the Node and all its attributes and + children (recursively) to a buffer. + + buf: + Open IO buffer into which the Node is printed. + + offset: + Initial offset (amount of leading spaces) + + attrnames: + True if you want to see the attribute names in + name=value pairs. False to only see the values. + + showemptyattrs: + False if you want to suppress printing empty attributes. + + nodenames: + True if you want to see the actual node names + within their parents. + + showcoord: + Do you want the coordinates of each Node to be + displayed. + """ + lead = ' ' * offset + if nodenames and _my_node_name is not None: + buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ') + else: + buf.write(lead + self.__class__.__name__+ ': ') + + if self.attr_names: + def is_empty(v): + v is None or (hasattr(v, '__len__') and len(v) == 0) + nvlist = [(n, getattr(self,n)) for n in self.attr_names \ + if showemptyattrs or not is_empty(getattr(self,n))] + if attrnames: + attrstr = ', '.join(f'{name}={value}' for name, value in nvlist) + else: + attrstr = ', '.join(f'{value}' for _, value in nvlist) + buf.write(attrstr) + + if showcoord: + buf.write(f' (at {self.coord})') + buf.write('\n') + + for (child_name, child) in self.children(): + child.show( + buf, + offset=offset + 2, + attrnames=attrnames, + showemptyattrs=showemptyattrs, + nodenames=nodenames, + showcoord=showcoord, + _my_node_name=child_name) + + +class NodeVisitor: + """ A base NodeVisitor class for visiting c_ast nodes. + Subclass it and define your own visit_XXX methods, where + XXX is the class name you want to visit with these + methods. + + For example: + + class ConstantVisitor(NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + Creates a list of values of all the constant nodes + encountered below the given node. To use it: + + cv = ConstantVisitor() + cv.visit(node) + + Notes: + + * generic_visit() will be called for AST nodes for which + no visit_XXX method was defined. + * The children of nodes for which a visit_XXX was + defined will not be visited - if you need this, call + generic_visit() on the node. + You can use: + NodeVisitor.generic_visit(self, node) + * Modeled after Python's own AST visiting facilities + (the ast module of Python 3.0) + """ + + _method_cache = None + + def visit(self, node: Node): + """ Visit a node. + """ + + if self._method_cache is None: + self._method_cache = {} + + visitor = self._method_cache.get(node.__class__.__name__, None) + if visitor is None: + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + self._method_cache[node.__class__.__name__] = visitor + + return visitor(node) + + def generic_visit(self, node: Node): + """ Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + for _, c in node.children(): + self.visit(c) + +''' + + +if __name__ == "__main__": + base_dir = os.path.dirname(os.path.abspath(__file__)) + cfg_path = os.path.join(base_dir, "_c_ast.cfg") + out_path = os.path.join(base_dir, "c_ast.py") + ast_gen = ASTCodeGenerator(cfg_path) + with open(out_path, "w") as out: + ast_gen.generate(out) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg new file mode 100644 index 0000000..0626533 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg @@ -0,0 +1,195 @@ +#----------------------------------------------------------------- +# pycparser: _c_ast.cfg +# +# Defines the AST Node classes used in pycparser. +# +# Each entry is a Node sub-class name, listing the attributes +# and child nodes of the class: +# * - a child node +# ** - a sequence of child nodes +# - an attribute +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +# ArrayDecl is a nested declaration of an array with the given type. +# dim: the dimension (for example, constant 42) +# dim_quals: list of dimension qualifiers, to support C99's allowing 'const' +# and 'static' within the array dimension in function declarations. +ArrayDecl: [type*, dim*, dim_quals] + +ArrayRef: [name*, subscript*] + +# op: =, +=, /= etc. +# +Assignment: [op, lvalue*, rvalue*] + +Alignas: [alignment*] + +BinaryOp: [op, left*, right*] + +Break: [] + +Case: [expr*, stmts**] + +Cast: [to_type*, expr*] + +# Compound statement in C99 is a list of block items (declarations or +# statements). +# +Compound: [block_items**] + +# Compound literal (anonymous aggregate) for C99. +# (type-name) {initializer_list} +# type: the typename +# init: InitList for the initializer list +# +CompoundLiteral: [type*, init*] + +# type: int, char, float, string, etc. +# +Constant: [type, value] + +Continue: [] + +# name: the variable being declared +# quals: list of qualifiers (const, volatile) +# funcspec: list function specifiers (i.e. inline in C99) +# storage: list of storage specifiers (extern, register, etc.) +# type: declaration type (probably nested with all the modifiers) +# init: initialization value, or None +# bitsize: bit field size, or None +# +Decl: [name, quals, align, storage, funcspec, type*, init*, bitsize*] + +DeclList: [decls**] + +Default: [stmts**] + +DoWhile: [cond*, stmt*] + +# Represents the ellipsis (...) parameter in a function +# declaration +# +EllipsisParam: [] + +# An empty statement (a semicolon ';' on its own) +# +EmptyStatement: [] + +# Enumeration type specifier +# name: an optional ID +# values: an EnumeratorList +# +Enum: [name, values*] + +# A name/value pair for enumeration values +# +Enumerator: [name, value*] + +# A list of enumerators +# +EnumeratorList: [enumerators**] + +# A list of expressions separated by the comma operator. +# +ExprList: [exprs**] + +# This is the top of the AST, representing a single C file (a +# translation unit in K&R jargon). It contains a list of +# "external-declaration"s, which is either declarations (Decl), +# Typedef or function definitions (FuncDef). +# +FileAST: [ext**] + +# for (init; cond; next) stmt +# +For: [init*, cond*, next*, stmt*] + +# name: Id +# args: ExprList +# +FuncCall: [name*, args*] + +# type (args) +# +FuncDecl: [args*, type*] + +# Function definition: a declarator for the function name and +# a body, which is a compound statement. +# There's an optional list of parameter declarations for old +# K&R-style definitions +# +FuncDef: [decl*, param_decls**, body*] + +Goto: [name] + +ID: [name] + +# Holder for types that are a simple identifier (e.g. the built +# ins void, char etc. and typedef-defined types) +# +IdentifierType: [names] + +If: [cond*, iftrue*, iffalse*] + +# An initialization list used for compound literals. +# +InitList: [exprs**] + +Label: [name, stmt*] + +# A named initializer for C99. +# The name of a NamedInitializer is a sequence of Nodes, because +# names can be hierarchical and contain constant expressions. +# +NamedInitializer: [name**, expr*] + +# a list of comma separated function parameter declarations +# +ParamList: [params**] + +PtrDecl: [quals, type*] + +Return: [expr*] + +StaticAssert: [cond*, message*] + +# name: struct tag name +# decls: declaration of members +# +Struct: [name, decls**] + +# type: . or -> +# name.field or name->field +# +StructRef: [name*, type, field*] + +Switch: [cond*, stmt*] + +# cond ? iftrue : iffalse +# +TernaryOp: [cond*, iftrue*, iffalse*] + +# A base type declaration +# +TypeDecl: [declname, quals, align, type*] + +# A typedef declaration. +# Very similar to Decl, but without some attributes +# +Typedef: [name, quals, storage, type*] + +Typename: [name, quals, align, type*] + +UnaryOp: [op, expr*] + +# name: union tag name +# decls: declaration of members +# +Union: [name, decls**] + +While: [cond*, stmt*] + +Pragma: [string] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py new file mode 100644 index 0000000..1051737 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py @@ -0,0 +1,174 @@ +# ------------------------------------------------------------------------------ +# pycparser: ast_transforms.py +# +# Some utilities used by the parser to create a friendlier AST. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ------------------------------------------------------------------------------ + +from typing import Any, List, Tuple, cast + +from . import c_ast + + +def fix_switch_cases(switch_node: c_ast.Switch) -> c_ast.Switch: + """The 'case' statements in a 'switch' come out of parsing with one + child node, so subsequent statements are just tucked to the parent + Compound. Additionally, consecutive (fall-through) case statements + come out messy. This is a peculiarity of the C grammar. The following: + + switch (myvar) { + case 10: + k = 10; + p = k + 1; + return 10; + case 20: + case 30: + return 20; + default: + break; + } + + Creates this tree (pseudo-dump): + + Switch + ID: myvar + Compound: + Case 10: + k = 10 + p = k + 1 + return 10 + Case 20: + Case 30: + return 20 + Default: + break + + The goal of this transform is to fix this mess, turning it into the + following: + + Switch + ID: myvar + Compound: + Case 10: + k = 10 + p = k + 1 + return 10 + Case 20: + Case 30: + return 20 + Default: + break + + A fixed AST node is returned. The argument may be modified. + """ + assert isinstance(switch_node, c_ast.Switch) + if not isinstance(switch_node.stmt, c_ast.Compound): + return switch_node + + # The new Compound child for the Switch, which will collect children in the + # correct order + new_compound = c_ast.Compound([], switch_node.stmt.coord) + + # The last Case/Default node + last_case: c_ast.Case | c_ast.Default | None = None + + # Goes over the children of the Compound below the Switch, adding them + # either directly below new_compound or below the last Case as appropriate + # (for `switch(cond) {}`, block_items would have been None) + for child in switch_node.stmt.block_items or []: + if isinstance(child, (c_ast.Case, c_ast.Default)): + # If it's a Case/Default: + # 1. Add it to the Compound and mark as "last case" + # 2. If its immediate child is also a Case or Default, promote it + # to a sibling. + new_compound.block_items.append(child) + _extract_nested_case(child, new_compound.block_items) + last_case = new_compound.block_items[-1] + else: + # Other statements are added as children to the last case, if it + # exists. + if last_case is None: + new_compound.block_items.append(child) + else: + last_case.stmts.append(child) + + switch_node.stmt = new_compound + return switch_node + + +def _extract_nested_case( + case_node: c_ast.Case | c_ast.Default, stmts_list: List[c_ast.Node] +) -> None: + """Recursively extract consecutive Case statements that are made nested + by the parser and add them to the stmts_list. + """ + if isinstance(case_node.stmts[0], (c_ast.Case, c_ast.Default)): + nested = case_node.stmts.pop() + stmts_list.append(nested) + _extract_nested_case(cast(Any, nested), stmts_list) + + +def fix_atomic_specifiers( + decl: c_ast.Decl | c_ast.Typedef, +) -> c_ast.Decl | c_ast.Typedef: + """Atomic specifiers like _Atomic(type) are unusually structured, + conferring a qualifier upon the contained type. + + This function fixes a decl with atomic specifiers to have a sane AST + structure, by removing spurious Typename->TypeDecl pairs and attaching + the _Atomic qualifier in the right place. + """ + # There can be multiple levels of _Atomic in a decl; fix them until a + # fixed point is reached. + while True: + decl, found = _fix_atomic_specifiers_once(decl) + if not found: + break + + # Make sure to add an _Atomic qual on the topmost decl if needed. Also + # restore the declname on the innermost TypeDecl (it gets placed in the + # wrong place during construction). + typ: Any = decl + while not isinstance(typ, c_ast.TypeDecl): + try: + typ = typ.type + except AttributeError: + return decl + if "_Atomic" in typ.quals and "_Atomic" not in decl.quals: + decl.quals.append("_Atomic") + if typ.declname is None: + typ.declname = decl.name + + return decl + + +def _fix_atomic_specifiers_once( + decl: c_ast.Decl | c_ast.Typedef, +) -> Tuple[c_ast.Decl | c_ast.Typedef, bool]: + """Performs one 'fix' round of atomic specifiers. + Returns (modified_decl, found) where found is True iff a fix was made. + """ + parent: Any = decl + grandparent: Any = None + node: Any = decl.type + while node is not None: + if isinstance(node, c_ast.Typename) and "_Atomic" in node.quals: + break + try: + grandparent = parent + parent = node + node = node.type + except AttributeError: + # If we've reached a node without a `type` field, it means we won't + # find what we're looking for at this point; give up the search + # and return the original decl unmodified. + return decl, False + + assert isinstance(parent, c_ast.TypeDecl) + assert grandparent is not None + cast(Any, grandparent).type = node.type + if "_Atomic" not in node.type.quals: + node.type.quals.append("_Atomic") + return decl, True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py new file mode 100644 index 0000000..b6f42af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py @@ -0,0 +1,1341 @@ +# ----------------------------------------------------------------- +# ** ATTENTION ** +# This code was automatically generated from _c_ast.cfg +# +# Do not modify it directly. Modify the configuration file and +# run the generator again. +# ** ** *** ** ** +# +# pycparser: c_ast.py +# +# AST Node classes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ----------------------------------------------------------------- + + +import sys +from typing import Any, ClassVar, IO, Optional + + +def _repr(obj): + """ + Get the representation of an object, with dedicated pprint-like format for lists. + """ + if isinstance(obj, list): + return "[" + (",\n ".join((_repr(e).replace("\n", "\n ") for e in obj))) + "\n]" + else: + return repr(obj) + + +class Node: + __slots__ = () + """ Abstract base class for AST nodes. + """ + attr_names: ClassVar[tuple[str, ...]] = () + coord: Optional[Any] + + def __repr__(self): + """Generates a python representation of the current node""" + result = self.__class__.__name__ + "(" + + indent = "" + separator = "" + for name in self.__slots__[:-2]: + result += separator + result += indent + result += ( + name + + "=" + + ( + _repr(getattr(self, name)).replace( + "\n", + "\n " + (" " * (len(name) + len(self.__class__.__name__))), + ) + ) + ) + + separator = "," + indent = "\n " + (" " * len(self.__class__.__name__)) + + result += indent + ")" + + return result + + def children(self): + """A sequence of all children that are Nodes""" + pass + + def show( + self, + buf: IO[str] = sys.stdout, + offset: int = 0, + attrnames: bool = False, + showemptyattrs: bool = True, + nodenames: bool = False, + showcoord: bool = False, + _my_node_name: Optional[str] = None, + ): + """Pretty print the Node and all its attributes and + children (recursively) to a buffer. + + buf: + Open IO buffer into which the Node is printed. + + offset: + Initial offset (amount of leading spaces) + + attrnames: + True if you want to see the attribute names in + name=value pairs. False to only see the values. + + showemptyattrs: + False if you want to suppress printing empty attributes. + + nodenames: + True if you want to see the actual node names + within their parents. + + showcoord: + Do you want the coordinates of each Node to be + displayed. + """ + lead = " " * offset + if nodenames and _my_node_name is not None: + buf.write(lead + self.__class__.__name__ + " <" + _my_node_name + ">: ") + else: + buf.write(lead + self.__class__.__name__ + ": ") + + if self.attr_names: + + def is_empty(v): + v is None or (hasattr(v, "__len__") and len(v) == 0) + + nvlist = [ + (n, getattr(self, n)) + for n in self.attr_names + if showemptyattrs or not is_empty(getattr(self, n)) + ] + if attrnames: + attrstr = ", ".join(f"{name}={value}" for name, value in nvlist) + else: + attrstr = ", ".join(f"{value}" for _, value in nvlist) + buf.write(attrstr) + + if showcoord: + buf.write(f" (at {self.coord})") + buf.write("\n") + + for child_name, child in self.children(): + child.show( + buf, + offset=offset + 2, + attrnames=attrnames, + showemptyattrs=showemptyattrs, + nodenames=nodenames, + showcoord=showcoord, + _my_node_name=child_name, + ) + + +class NodeVisitor: + """A base NodeVisitor class for visiting c_ast nodes. + Subclass it and define your own visit_XXX methods, where + XXX is the class name you want to visit with these + methods. + + For example: + + class ConstantVisitor(NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + Creates a list of values of all the constant nodes + encountered below the given node. To use it: + + cv = ConstantVisitor() + cv.visit(node) + + Notes: + + * generic_visit() will be called for AST nodes for which + no visit_XXX method was defined. + * The children of nodes for which a visit_XXX was + defined will not be visited - if you need this, call + generic_visit() on the node. + You can use: + NodeVisitor.generic_visit(self, node) + * Modeled after Python's own AST visiting facilities + (the ast module of Python 3.0) + """ + + _method_cache = None + + def visit(self, node: Node): + """Visit a node.""" + + if self._method_cache is None: + self._method_cache = {} + + visitor = self._method_cache.get(node.__class__.__name__, None) + if visitor is None: + method = "visit_" + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + self._method_cache[node.__class__.__name__] = visitor + + return visitor(node) + + def generic_visit(self, node: Node): + """Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + for _, c in node.children(): + self.visit(c) + + +class ArrayDecl(Node): + __slots__ = ("type", "dim", "dim_quals", "coord", "__weakref__") + + def __init__(self, type, dim, dim_quals, coord=None): + self.type = type + self.dim = dim + self.dim_quals = dim_quals + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + if self.dim is not None: + nodelist.append(("dim", self.dim)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.dim is not None: + yield self.dim + + attr_names = ("dim_quals",) + + +class ArrayRef(Node): + __slots__ = ("name", "subscript", "coord", "__weakref__") + + def __init__(self, name, subscript, coord=None): + self.name = name + self.subscript = subscript + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: + nodelist.append(("name", self.name)) + if self.subscript is not None: + nodelist.append(("subscript", self.subscript)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.subscript is not None: + yield self.subscript + + attr_names = () + + +class Assignment(Node): + __slots__ = ("op", "lvalue", "rvalue", "coord", "__weakref__") + + def __init__(self, op, lvalue, rvalue, coord=None): + self.op = op + self.lvalue = lvalue + self.rvalue = rvalue + self.coord = coord + + def children(self): + nodelist = [] + if self.lvalue is not None: + nodelist.append(("lvalue", self.lvalue)) + if self.rvalue is not None: + nodelist.append(("rvalue", self.rvalue)) + return tuple(nodelist) + + def __iter__(self): + if self.lvalue is not None: + yield self.lvalue + if self.rvalue is not None: + yield self.rvalue + + attr_names = ("op",) + + +class Alignas(Node): + __slots__ = ("alignment", "coord", "__weakref__") + + def __init__(self, alignment, coord=None): + self.alignment = alignment + self.coord = coord + + def children(self): + nodelist = [] + if self.alignment is not None: + nodelist.append(("alignment", self.alignment)) + return tuple(nodelist) + + def __iter__(self): + if self.alignment is not None: + yield self.alignment + + attr_names = () + + +class BinaryOp(Node): + __slots__ = ("op", "left", "right", "coord", "__weakref__") + + def __init__(self, op, left, right, coord=None): + self.op = op + self.left = left + self.right = right + self.coord = coord + + def children(self): + nodelist = [] + if self.left is not None: + nodelist.append(("left", self.left)) + if self.right is not None: + nodelist.append(("right", self.right)) + return tuple(nodelist) + + def __iter__(self): + if self.left is not None: + yield self.left + if self.right is not None: + yield self.right + + attr_names = ("op",) + + +class Break(Node): + __slots__ = ("coord", "__weakref__") + + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + + +class Case(Node): + __slots__ = ("expr", "stmts", "coord", "__weakref__") + + def __init__(self, expr, stmts, coord=None): + self.expr = expr + self.stmts = stmts + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: + nodelist.append(("expr", self.expr)) + for i, child in enumerate(self.stmts or []): + nodelist.append((f"stmts[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + for child in self.stmts or []: + yield child + + attr_names = () + + +class Cast(Node): + __slots__ = ("to_type", "expr", "coord", "__weakref__") + + def __init__(self, to_type, expr, coord=None): + self.to_type = to_type + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.to_type is not None: + nodelist.append(("to_type", self.to_type)) + if self.expr is not None: + nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.to_type is not None: + yield self.to_type + if self.expr is not None: + yield self.expr + + attr_names = () + + +class Compound(Node): + __slots__ = ("block_items", "coord", "__weakref__") + + def __init__(self, block_items, coord=None): + self.block_items = block_items + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.block_items or []): + nodelist.append((f"block_items[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.block_items or []: + yield child + + attr_names = () + + +class CompoundLiteral(Node): + __slots__ = ("type", "init", "coord", "__weakref__") + + def __init__(self, type, init, coord=None): + self.type = type + self.init = init + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + if self.init is not None: + nodelist.append(("init", self.init)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.init is not None: + yield self.init + + attr_names = () + + +class Constant(Node): + __slots__ = ("type", "value", "coord", "__weakref__") + + def __init__(self, type, value, coord=None): + self.type = type + self.value = value + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ( + "type", + "value", + ) + + +class Continue(Node): + __slots__ = ("coord", "__weakref__") + + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + + +class Decl(Node): + __slots__ = ( + "name", + "quals", + "align", + "storage", + "funcspec", + "type", + "init", + "bitsize", + "coord", + "__weakref__", + ) + + def __init__( + self, name, quals, align, storage, funcspec, type, init, bitsize, coord=None + ): + self.name = name + self.quals = quals + self.align = align + self.storage = storage + self.funcspec = funcspec + self.type = type + self.init = init + self.bitsize = bitsize + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + if self.init is not None: + nodelist.append(("init", self.init)) + if self.bitsize is not None: + nodelist.append(("bitsize", self.bitsize)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.init is not None: + yield self.init + if self.bitsize is not None: + yield self.bitsize + + attr_names = ( + "name", + "quals", + "align", + "storage", + "funcspec", + ) + + +class DeclList(Node): + __slots__ = ("decls", "coord", "__weakref__") + + def __init__(self, decls, coord=None): + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append((f"decls[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.decls or []: + yield child + + attr_names = () + + +class Default(Node): + __slots__ = ("stmts", "coord", "__weakref__") + + def __init__(self, stmts, coord=None): + self.stmts = stmts + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.stmts or []): + nodelist.append((f"stmts[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.stmts or []: + yield child + + attr_names = () + + +class DoWhile(Node): + __slots__ = ("cond", "stmt", "coord", "__weakref__") + + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.stmt is not None: + nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + + +class EllipsisParam(Node): + __slots__ = ("coord", "__weakref__") + + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + + +class EmptyStatement(Node): + __slots__ = ("coord", "__weakref__") + + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + + +class Enum(Node): + __slots__ = ("name", "values", "coord", "__weakref__") + + def __init__(self, name, values, coord=None): + self.name = name + self.values = values + self.coord = coord + + def children(self): + nodelist = [] + if self.values is not None: + nodelist.append(("values", self.values)) + return tuple(nodelist) + + def __iter__(self): + if self.values is not None: + yield self.values + + attr_names = ("name",) + + +class Enumerator(Node): + __slots__ = ("name", "value", "coord", "__weakref__") + + def __init__(self, name, value, coord=None): + self.name = name + self.value = value + self.coord = coord + + def children(self): + nodelist = [] + if self.value is not None: + nodelist.append(("value", self.value)) + return tuple(nodelist) + + def __iter__(self): + if self.value is not None: + yield self.value + + attr_names = ("name",) + + +class EnumeratorList(Node): + __slots__ = ("enumerators", "coord", "__weakref__") + + def __init__(self, enumerators, coord=None): + self.enumerators = enumerators + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.enumerators or []): + nodelist.append((f"enumerators[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.enumerators or []: + yield child + + attr_names = () + + +class ExprList(Node): + __slots__ = ("exprs", "coord", "__weakref__") + + def __init__(self, exprs, coord=None): + self.exprs = exprs + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.exprs or []): + nodelist.append((f"exprs[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.exprs or []: + yield child + + attr_names = () + + +class FileAST(Node): + __slots__ = ("ext", "coord", "__weakref__") + + def __init__(self, ext, coord=None): + self.ext = ext + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.ext or []): + nodelist.append((f"ext[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.ext or []: + yield child + + attr_names = () + + +class For(Node): + __slots__ = ("init", "cond", "next", "stmt", "coord", "__weakref__") + + def __init__(self, init, cond, next, stmt, coord=None): + self.init = init + self.cond = cond + self.next = next + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.init is not None: + nodelist.append(("init", self.init)) + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.next is not None: + nodelist.append(("next", self.next)) + if self.stmt is not None: + nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.init is not None: + yield self.init + if self.cond is not None: + yield self.cond + if self.next is not None: + yield self.next + if self.stmt is not None: + yield self.stmt + + attr_names = () + + +class FuncCall(Node): + __slots__ = ("name", "args", "coord", "__weakref__") + + def __init__(self, name, args, coord=None): + self.name = name + self.args = args + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: + nodelist.append(("name", self.name)) + if self.args is not None: + nodelist.append(("args", self.args)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.args is not None: + yield self.args + + attr_names = () + + +class FuncDecl(Node): + __slots__ = ("args", "type", "coord", "__weakref__") + + def __init__(self, args, type, coord=None): + self.args = args + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.args is not None: + nodelist.append(("args", self.args)) + if self.type is not None: + nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.args is not None: + yield self.args + if self.type is not None: + yield self.type + + attr_names = () + + +class FuncDef(Node): + __slots__ = ("decl", "param_decls", "body", "coord", "__weakref__") + + def __init__(self, decl, param_decls, body, coord=None): + self.decl = decl + self.param_decls = param_decls + self.body = body + self.coord = coord + + def children(self): + nodelist = [] + if self.decl is not None: + nodelist.append(("decl", self.decl)) + if self.body is not None: + nodelist.append(("body", self.body)) + for i, child in enumerate(self.param_decls or []): + nodelist.append((f"param_decls[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + if self.decl is not None: + yield self.decl + if self.body is not None: + yield self.body + for child in self.param_decls or []: + yield child + + attr_names = () + + +class Goto(Node): + __slots__ = ("name", "coord", "__weakref__") + + def __init__(self, name, coord=None): + self.name = name + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ("name",) + + +class ID(Node): + __slots__ = ("name", "coord", "__weakref__") + + def __init__(self, name, coord=None): + self.name = name + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ("name",) + + +class IdentifierType(Node): + __slots__ = ("names", "coord", "__weakref__") + + def __init__(self, names, coord=None): + self.names = names + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ("names",) + + +class If(Node): + __slots__ = ("cond", "iftrue", "iffalse", "coord", "__weakref__") + + def __init__(self, cond, iftrue, iffalse, coord=None): + self.cond = cond + self.iftrue = iftrue + self.iffalse = iffalse + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.iftrue is not None: + nodelist.append(("iftrue", self.iftrue)) + if self.iffalse is not None: + nodelist.append(("iffalse", self.iffalse)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.iftrue is not None: + yield self.iftrue + if self.iffalse is not None: + yield self.iffalse + + attr_names = () + + +class InitList(Node): + __slots__ = ("exprs", "coord", "__weakref__") + + def __init__(self, exprs, coord=None): + self.exprs = exprs + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.exprs or []): + nodelist.append((f"exprs[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.exprs or []: + yield child + + attr_names = () + + +class Label(Node): + __slots__ = ("name", "stmt", "coord", "__weakref__") + + def __init__(self, name, stmt, coord=None): + self.name = name + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.stmt is not None: + nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.stmt is not None: + yield self.stmt + + attr_names = ("name",) + + +class NamedInitializer(Node): + __slots__ = ("name", "expr", "coord", "__weakref__") + + def __init__(self, name, expr, coord=None): + self.name = name + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: + nodelist.append(("expr", self.expr)) + for i, child in enumerate(self.name or []): + nodelist.append((f"name[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + for child in self.name or []: + yield child + + attr_names = () + + +class ParamList(Node): + __slots__ = ("params", "coord", "__weakref__") + + def __init__(self, params, coord=None): + self.params = params + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.params or []): + nodelist.append((f"params[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.params or []: + yield child + + attr_names = () + + +class PtrDecl(Node): + __slots__ = ("quals", "type", "coord", "__weakref__") + + def __init__(self, quals, type, coord=None): + self.quals = quals + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ("quals",) + + +class Return(Node): + __slots__ = ("expr", "coord", "__weakref__") + + def __init__(self, expr, coord=None): + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: + nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + + attr_names = () + + +class StaticAssert(Node): + __slots__ = ("cond", "message", "coord", "__weakref__") + + def __init__(self, cond, message, coord=None): + self.cond = cond + self.message = message + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.message is not None: + nodelist.append(("message", self.message)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.message is not None: + yield self.message + + attr_names = () + + +class Struct(Node): + __slots__ = ("name", "decls", "coord", "__weakref__") + + def __init__(self, name, decls, coord=None): + self.name = name + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append((f"decls[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.decls or []: + yield child + + attr_names = ("name",) + + +class StructRef(Node): + __slots__ = ("name", "type", "field", "coord", "__weakref__") + + def __init__(self, name, type, field, coord=None): + self.name = name + self.type = type + self.field = field + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: + nodelist.append(("name", self.name)) + if self.field is not None: + nodelist.append(("field", self.field)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.field is not None: + yield self.field + + attr_names = ("type",) + + +class Switch(Node): + __slots__ = ("cond", "stmt", "coord", "__weakref__") + + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.stmt is not None: + nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + + +class TernaryOp(Node): + __slots__ = ("cond", "iftrue", "iffalse", "coord", "__weakref__") + + def __init__(self, cond, iftrue, iffalse, coord=None): + self.cond = cond + self.iftrue = iftrue + self.iffalse = iffalse + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.iftrue is not None: + nodelist.append(("iftrue", self.iftrue)) + if self.iffalse is not None: + nodelist.append(("iffalse", self.iffalse)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.iftrue is not None: + yield self.iftrue + if self.iffalse is not None: + yield self.iffalse + + attr_names = () + + +class TypeDecl(Node): + __slots__ = ("declname", "quals", "align", "type", "coord", "__weakref__") + + def __init__(self, declname, quals, align, type, coord=None): + self.declname = declname + self.quals = quals + self.align = align + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ( + "declname", + "quals", + "align", + ) + + +class Typedef(Node): + __slots__ = ("name", "quals", "storage", "type", "coord", "__weakref__") + + def __init__(self, name, quals, storage, type, coord=None): + self.name = name + self.quals = quals + self.storage = storage + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ( + "name", + "quals", + "storage", + ) + + +class Typename(Node): + __slots__ = ("name", "quals", "align", "type", "coord", "__weakref__") + + def __init__(self, name, quals, align, type, coord=None): + self.name = name + self.quals = quals + self.align = align + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: + nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ( + "name", + "quals", + "align", + ) + + +class UnaryOp(Node): + __slots__ = ("op", "expr", "coord", "__weakref__") + + def __init__(self, op, expr, coord=None): + self.op = op + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: + nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + + attr_names = ("op",) + + +class Union(Node): + __slots__ = ("name", "decls", "coord", "__weakref__") + + def __init__(self, name, decls, coord=None): + self.name = name + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append((f"decls[{i}]", child)) + return tuple(nodelist) + + def __iter__(self): + for child in self.decls or []: + yield child + + attr_names = ("name",) + + +class While(Node): + __slots__ = ("cond", "stmt", "coord", "__weakref__") + + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: + nodelist.append(("cond", self.cond)) + if self.stmt is not None: + nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + + +class Pragma(Node): + __slots__ = ("string", "coord", "__weakref__") + + def __init__(self, string, coord=None): + self.string = string + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ("string",) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py new file mode 100644 index 0000000..424e00e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py @@ -0,0 +1,573 @@ +# ------------------------------------------------------------------------------ +# pycparser: c_generator.py +# +# C code generator from pycparser AST nodes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ------------------------------------------------------------------------------ +from typing import Callable, List, Optional + +from . import c_ast + + +class CGenerator: + """Uses the same visitor pattern as c_ast.NodeVisitor, but modified to + return a value from each visit method, using string accumulation in + generic_visit. + """ + + indent_level: int + reduce_parentheses: bool + + def __init__(self, reduce_parentheses: bool = False) -> None: + """Constructs C-code generator + + reduce_parentheses: + if True, eliminates needless parentheses on binary operators + """ + # Statements start with indentation of self.indent_level spaces, using + # the _make_indent method. + self.indent_level = 0 + self.reduce_parentheses = reduce_parentheses + + def _make_indent(self) -> str: + return " " * self.indent_level + + def visit(self, node: c_ast.Node) -> str: + method = "visit_" + node.__class__.__name__ + return getattr(self, method, self.generic_visit)(node) + + def generic_visit(self, node: Optional[c_ast.Node]) -> str: + if node is None: + return "" + else: + return "".join(self.visit(c) for c_name, c in node.children()) + + def visit_Constant(self, n: c_ast.Constant) -> str: + return n.value + + def visit_ID(self, n: c_ast.ID) -> str: + return n.name + + def visit_Pragma(self, n: c_ast.Pragma) -> str: + ret = "#pragma" + if n.string: + ret += " " + n.string + return ret + + def visit_ArrayRef(self, n: c_ast.ArrayRef) -> str: + arrref = self._parenthesize_unless_simple(n.name) + return arrref + "[" + self.visit(n.subscript) + "]" + + def visit_StructRef(self, n: c_ast.StructRef) -> str: + sref = self._parenthesize_unless_simple(n.name) + return sref + n.type + self.visit(n.field) + + def visit_FuncCall(self, n: c_ast.FuncCall) -> str: + fref = self._parenthesize_unless_simple(n.name) + args = self.visit(n.args) if n.args is not None else "" + return fref + "(" + args + ")" + + def visit_UnaryOp(self, n: c_ast.UnaryOp) -> str: + match n.op: + case "sizeof": + # Always parenthesize the argument of sizeof since it can be + # a name. + return f"sizeof({self.visit(n.expr)})" + case "p++": + operand = self._parenthesize_unless_simple(n.expr) + return f"{operand}++" + case "p--": + operand = self._parenthesize_unless_simple(n.expr) + return f"{operand}--" + case _: + operand = self._parenthesize_unless_simple(n.expr) + return f"{n.op}{operand}" + + # Precedence map of binary operators: + precedence_map = { + # Should be in sync with c_parser.CParser.precedence + # Higher numbers are stronger binding + "||": 0, # weakest binding + "&&": 1, + "|": 2, + "^": 3, + "&": 4, + "==": 5, + "!=": 5, + ">": 6, + ">=": 6, + "<": 6, + "<=": 6, + ">>": 7, + "<<": 7, + "+": 8, + "-": 8, + "*": 9, + "/": 9, + "%": 9, # strongest binding + } + + def visit_BinaryOp(self, n: c_ast.BinaryOp) -> str: + # Note: all binary operators are left-to-right associative + # + # If `n.left.op` has a stronger or equally binding precedence in + # comparison to `n.op`, no parenthesis are needed for the left: + # e.g., `(a*b) + c` is equivalent to `a*b + c`, as well as + # `(a+b) - c` is equivalent to `a+b - c` (same precedence). + # If the left operator is weaker binding than the current, then + # parentheses are necessary: + # e.g., `(a+b) * c` is NOT equivalent to `a+b * c`. + lval_str = self._parenthesize_if( + n.left, + lambda d: not ( + self._is_simple_node(d) + or self.reduce_parentheses + and isinstance(d, c_ast.BinaryOp) + and self.precedence_map[d.op] >= self.precedence_map[n.op] + ), + ) + # If `n.right.op` has a stronger -but not equal- binding precedence, + # parenthesis can be omitted on the right: + # e.g., `a + (b*c)` is equivalent to `a + b*c`. + # If the right operator is weaker or equally binding, then parentheses + # are necessary: + # e.g., `a * (b+c)` is NOT equivalent to `a * b+c` and + # `a - (b+c)` is NOT equivalent to `a - b+c` (same precedence). + rval_str = self._parenthesize_if( + n.right, + lambda d: not ( + self._is_simple_node(d) + or self.reduce_parentheses + and isinstance(d, c_ast.BinaryOp) + and self.precedence_map[d.op] > self.precedence_map[n.op] + ), + ) + return f"{lval_str} {n.op} {rval_str}" + + def visit_Assignment(self, n: c_ast.Assignment) -> str: + rval_str = self._parenthesize_if( + n.rvalue, lambda n: isinstance(n, c_ast.Assignment) + ) + return f"{self.visit(n.lvalue)} {n.op} {rval_str}" + + def visit_IdentifierType(self, n: c_ast.IdentifierType) -> str: + return " ".join(n.names) + + def _visit_expr(self, n: c_ast.Node) -> str: + match n: + case c_ast.InitList(): + return "{" + self.visit(n) + "}" + case c_ast.ExprList() | c_ast.Compound(): + return "(" + self.visit(n) + ")" + case _: + return self.visit(n) + + def visit_Decl(self, n: c_ast.Decl, no_type: bool = False) -> str: + # no_type is used when a Decl is part of a DeclList, where the type is + # explicitly only for the first declaration in a list. + # + s = n.name if no_type else self._generate_decl(n) + if n.bitsize: + s += " : " + self.visit(n.bitsize) + if n.init: + s += " = " + self._visit_expr(n.init) + return s + + def visit_DeclList(self, n: c_ast.DeclList) -> str: + s = self.visit(n.decls[0]) + if len(n.decls) > 1: + s += ", " + ", ".join( + self.visit_Decl(decl, no_type=True) for decl in n.decls[1:] + ) + return s + + def visit_Typedef(self, n: c_ast.Typedef) -> str: + s = "" + if n.storage: + s += " ".join(n.storage) + " " + s += self._generate_type(n.type) + return s + + def visit_Cast(self, n: c_ast.Cast) -> str: + s = "(" + self._generate_type(n.to_type, emit_declname=False) + ")" + return s + " " + self._parenthesize_unless_simple(n.expr) + + def visit_ExprList(self, n: c_ast.ExprList) -> str: + visited_subexprs = [] + for expr in n.exprs: + visited_subexprs.append(self._visit_expr(expr)) + return ", ".join(visited_subexprs) + + def visit_InitList(self, n: c_ast.InitList) -> str: + visited_subexprs = [] + for expr in n.exprs: + visited_subexprs.append(self._visit_expr(expr)) + return ", ".join(visited_subexprs) + + def visit_Enum(self, n: c_ast.Enum) -> str: + return self._generate_struct_union_enum(n, name="enum") + + def visit_Alignas(self, n: c_ast.Alignas) -> str: + return "_Alignas({})".format(self.visit(n.alignment)) + + def visit_Enumerator(self, n: c_ast.Enumerator) -> str: + if not n.value: + return "{indent}{name},\n".format( + indent=self._make_indent(), + name=n.name, + ) + else: + return "{indent}{name} = {value},\n".format( + indent=self._make_indent(), + name=n.name, + value=self.visit(n.value), + ) + + def visit_FuncDef(self, n: c_ast.FuncDef) -> str: + decl = self.visit(n.decl) + self.indent_level = 0 + body = self.visit(n.body) + if n.param_decls: + knrdecls = ";\n".join(self.visit(p) for p in n.param_decls) + return decl + "\n" + knrdecls + ";\n" + body + "\n" + else: + return decl + "\n" + body + "\n" + + def visit_FileAST(self, n: c_ast.FileAST) -> str: + s = "" + for ext in n.ext: + match ext: + case c_ast.FuncDef(): + s += self.visit(ext) + case c_ast.Pragma(): + s += self.visit(ext) + "\n" + case _: + s += self.visit(ext) + ";\n" + return s + + def visit_Compound(self, n: c_ast.Compound) -> str: + s = self._make_indent() + "{\n" + self.indent_level += 2 + if n.block_items: + s += "".join(self._generate_stmt(stmt) for stmt in n.block_items) + self.indent_level -= 2 + s += self._make_indent() + "}\n" + return s + + def visit_CompoundLiteral(self, n: c_ast.CompoundLiteral) -> str: + return "(" + self.visit(n.type) + "){" + self.visit(n.init) + "}" + + def visit_EmptyStatement(self, n: c_ast.EmptyStatement) -> str: + return ";" + + def visit_ParamList(self, n: c_ast.ParamList) -> str: + return ", ".join(self.visit(param) for param in n.params) + + def visit_Return(self, n: c_ast.Return) -> str: + s = "return" + if n.expr: + s += " " + self.visit(n.expr) + return s + ";" + + def visit_Break(self, n: c_ast.Break) -> str: + return "break;" + + def visit_Continue(self, n: c_ast.Continue) -> str: + return "continue;" + + def visit_TernaryOp(self, n: c_ast.TernaryOp) -> str: + s = "(" + self._visit_expr(n.cond) + ") ? " + s += "(" + self._visit_expr(n.iftrue) + ") : " + s += "(" + self._visit_expr(n.iffalse) + ")" + return s + + def visit_If(self, n: c_ast.If) -> str: + s = "if (" + if n.cond: + s += self.visit(n.cond) + s += ")\n" + s += self._generate_stmt(n.iftrue, add_indent=True) + if n.iffalse: + s += self._make_indent() + "else\n" + s += self._generate_stmt(n.iffalse, add_indent=True) + return s + + def visit_For(self, n: c_ast.For) -> str: + s = "for (" + if n.init: + s += self.visit(n.init) + s += ";" + if n.cond: + s += " " + self.visit(n.cond) + s += ";" + if n.next: + s += " " + self.visit(n.next) + s += ")\n" + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_While(self, n: c_ast.While) -> str: + s = "while (" + if n.cond: + s += self.visit(n.cond) + s += ")\n" + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_DoWhile(self, n: c_ast.DoWhile) -> str: + s = "do\n" + s += self._generate_stmt(n.stmt, add_indent=True) + s += self._make_indent() + "while (" + if n.cond: + s += self.visit(n.cond) + s += ");" + return s + + def visit_StaticAssert(self, n: c_ast.StaticAssert) -> str: + s = "_Static_assert(" + s += self.visit(n.cond) + if n.message: + s += "," + s += self.visit(n.message) + s += ")" + return s + + def visit_Switch(self, n: c_ast.Switch) -> str: + s = "switch (" + self.visit(n.cond) + ")\n" + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_Case(self, n: c_ast.Case) -> str: + s = "case " + self.visit(n.expr) + ":\n" + for stmt in n.stmts: + s += self._generate_stmt(stmt, add_indent=True) + return s + + def visit_Default(self, n: c_ast.Default) -> str: + s = "default:\n" + for stmt in n.stmts: + s += self._generate_stmt(stmt, add_indent=True) + return s + + def visit_Label(self, n: c_ast.Label) -> str: + return n.name + ":\n" + self._generate_stmt(n.stmt) + + def visit_Goto(self, n: c_ast.Goto) -> str: + return "goto " + n.name + ";" + + def visit_EllipsisParam(self, n: c_ast.EllipsisParam) -> str: + return "..." + + def visit_Struct(self, n: c_ast.Struct) -> str: + return self._generate_struct_union_enum(n, "struct") + + def visit_Typename(self, n: c_ast.Typename) -> str: + return self._generate_type(n.type) + + def visit_Union(self, n: c_ast.Union) -> str: + return self._generate_struct_union_enum(n, "union") + + def visit_NamedInitializer(self, n: c_ast.NamedInitializer) -> str: + s = "" + for name in n.name: + if isinstance(name, c_ast.ID): + s += "." + name.name + else: + s += "[" + self.visit(name) + "]" + s += " = " + self._visit_expr(n.expr) + return s + + def visit_FuncDecl(self, n: c_ast.FuncDecl) -> str: + return self._generate_type(n) + + def visit_ArrayDecl(self, n: c_ast.ArrayDecl) -> str: + return self._generate_type(n, emit_declname=False) + + def visit_TypeDecl(self, n: c_ast.TypeDecl) -> str: + return self._generate_type(n, emit_declname=False) + + def visit_PtrDecl(self, n: c_ast.PtrDecl) -> str: + return self._generate_type(n, emit_declname=False) + + def _generate_struct_union_enum( + self, n: c_ast.Struct | c_ast.Union | c_ast.Enum, name: str + ) -> str: + """Generates code for structs, unions, and enums. name should be + 'struct', 'union', or 'enum'. + """ + if name in ("struct", "union"): + assert isinstance(n, (c_ast.Struct, c_ast.Union)) + members = n.decls + body_function = self._generate_struct_union_body + else: + assert name == "enum" + assert isinstance(n, c_ast.Enum) + members = None if n.values is None else n.values.enumerators + body_function = self._generate_enum_body + s = name + " " + (n.name or "") + if members is not None: + # None means no members + # Empty sequence means an empty list of members + s += "\n" + s += self._make_indent() + self.indent_level += 2 + s += "{\n" + s += body_function(members) + self.indent_level -= 2 + s += self._make_indent() + "}" + return s + + def _generate_struct_union_body(self, members: List[c_ast.Node]) -> str: + return "".join(self._generate_stmt(decl) for decl in members) + + def _generate_enum_body(self, members: List[c_ast.Enumerator]) -> str: + # `[:-2] + '\n'` removes the final `,` from the enumerator list + return "".join(self.visit(value) for value in members)[:-2] + "\n" + + def _generate_stmt(self, n: c_ast.Node, add_indent: bool = False) -> str: + """Generation from a statement node. This method exists as a wrapper + for individual visit_* methods to handle different treatment of + some statements in this context. + """ + if add_indent: + self.indent_level += 2 + indent = self._make_indent() + if add_indent: + self.indent_level -= 2 + + match n: + case ( + c_ast.Decl() + | c_ast.Assignment() + | c_ast.Cast() + | c_ast.UnaryOp() + | c_ast.BinaryOp() + | c_ast.TernaryOp() + | c_ast.FuncCall() + | c_ast.ArrayRef() + | c_ast.StructRef() + | c_ast.Constant() + | c_ast.ID() + | c_ast.Typedef() + | c_ast.ExprList() + ): + # These can also appear in an expression context so no semicolon + # is added to them automatically + # + return indent + self.visit(n) + ";\n" + case c_ast.Compound(): + # No extra indentation required before the opening brace of a + # compound - because it consists of multiple lines it has to + # compute its own indentation. + # + return self.visit(n) + case c_ast.If(): + return indent + self.visit(n) + case _: + return indent + self.visit(n) + "\n" + + def _generate_decl(self, n: c_ast.Decl) -> str: + """Generation from a Decl node.""" + s = "" + if n.funcspec: + s = " ".join(n.funcspec) + " " + if n.storage: + s += " ".join(n.storage) + " " + if n.align: + s += self.visit(n.align[0]) + " " + s += self._generate_type(n.type) + return s + + def _generate_type( + self, + n: c_ast.Node, + modifiers: List[c_ast.Node] = [], + emit_declname: bool = True, + ) -> str: + """Recursive generation from a type node. n is the type node. + modifiers collects the PtrDecl, ArrayDecl and FuncDecl modifiers + encountered on the way down to a TypeDecl, to allow proper + generation from it. + """ + # ~ print(n, modifiers) + match n: + case c_ast.TypeDecl(): + s = "" + if n.quals: + s += " ".join(n.quals) + " " + s += self.visit(n.type) + + nstr = n.declname if n.declname and emit_declname else "" + # Resolve modifiers. + # Wrap in parens to distinguish pointer to array and pointer to + # function syntax. + # + for i, modifier in enumerate(modifiers): + match modifier: + case c_ast.ArrayDecl(): + if i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl): + nstr = "(" + nstr + ")" + nstr += "[" + if modifier.dim_quals: + nstr += " ".join(modifier.dim_quals) + " " + if modifier.dim is not None: + nstr += self.visit(modifier.dim) + nstr += "]" + case c_ast.FuncDecl(): + if i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl): + nstr = "(" + nstr + ")" + args = ( + self.visit(modifier.args) + if modifier.args is not None + else "" + ) + nstr += "(" + args + ")" + case c_ast.PtrDecl(): + if modifier.quals: + quals = " ".join(modifier.quals) + suffix = f" {nstr}" if nstr else "" + nstr = f"* {quals}{suffix}" + else: + nstr = "*" + nstr + if nstr: + s += " " + nstr + return s + case c_ast.Decl(): + return self._generate_decl(n.type) + case c_ast.Typename(): + return self._generate_type(n.type, emit_declname=emit_declname) + case c_ast.IdentifierType(): + return " ".join(n.names) + " " + case c_ast.ArrayDecl() | c_ast.PtrDecl() | c_ast.FuncDecl(): + return self._generate_type( + n.type, modifiers + [n], emit_declname=emit_declname + ) + case _: + return self.visit(n) + + def _parenthesize_if( + self, n: c_ast.Node, condition: Callable[[c_ast.Node], bool] + ) -> str: + """Visits 'n' and returns its string representation, parenthesized + if the condition function applied to the node returns True. + """ + s = self._visit_expr(n) + if condition(n): + return "(" + s + ")" + else: + return s + + def _parenthesize_unless_simple(self, n: c_ast.Node) -> str: + """Common use case for _parenthesize_if""" + return self._parenthesize_if(n, lambda d: not self._is_simple_node(d)) + + def _is_simple_node(self, n: c_ast.Node) -> bool: + """Returns True for nodes that are "simple" - i.e. nodes that always + have higher precedence than operators. + """ + return isinstance( + n, + (c_ast.Constant, c_ast.ID, c_ast.ArrayRef, c_ast.StructRef, c_ast.FuncCall), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py new file mode 100644 index 0000000..ef59d69 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py @@ -0,0 +1,706 @@ +# ------------------------------------------------------------------------------ +# pycparser: c_lexer.py +# +# CLexer class: lexer for the C language +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ------------------------------------------------------------------------------ +import re +from dataclasses import dataclass +from enum import Enum +from typing import Callable, Dict, List, Optional, Tuple + + +@dataclass(slots=True) +class _Token: + type: str + value: str + lineno: int + column: int + + +class CLexer: + """A standalone lexer for C. + + Parameters for construction: + error_func: + Called with (msg, line, column) on lexing errors. + on_lbrace_func: + Called when an LBRACE token is produced (used for scope tracking). + on_rbrace_func: + Called when an RBRACE token is produced (used for scope tracking). + type_lookup_func: + Called with an identifier name; expected to return True if it is + a typedef name and should be tokenized as TYPEID. + + Call input(text) to initialize lexing, and then keep calling token() to + get the next token, until it returns None (at end of input). + """ + + def __init__( + self, + error_func: Callable[[str, int, int], None], + on_lbrace_func: Callable[[], None], + on_rbrace_func: Callable[[], None], + type_lookup_func: Callable[[str], bool], + ) -> None: + self.error_func = error_func + self.on_lbrace_func = on_lbrace_func + self.on_rbrace_func = on_rbrace_func + self.type_lookup_func = type_lookup_func + self._init_state() + + def input(self, text: str, filename: str = "") -> None: + """Initialize the lexer to the given input text. + + filename is an optional name identifying the file from which the input + comes. The lexer can modify it if #line directives are encountered. + """ + self._init_state() + self._lexdata = text + self._filename = filename + + def _init_state(self) -> None: + self._lexdata = "" + self._filename = "" + self._pos = 0 + self._line_start = 0 + self._pending_tok: Optional[_Token] = None + self._lineno = 1 + + @property + def filename(self) -> str: + return self._filename + + def token(self) -> Optional[_Token]: + # Lexing strategy overview: + # + # - We maintain a current position (self._pos), line number, and the + # byte offset of the current line start. The lexer is a simple loop + # that skips whitespace/newlines and emits one token per call. + # - A small amount of logic is handled manually before regex matching: + # + # * Preprocessor-style directives: if we see '#', we check whether + # it's a #line or #pragma directive and consume it inline. #line + # updates lineno/filename and produces no tokens. #pragma can yield + # both PPPRAGMA and PPPRAGMASTR, but token() returns a single token, + # so we stash the PPPRAGMASTR as _pending_tok to return on the next + # token() call. Otherwise we return PPHASH. + # * Newlines update lineno/line-start tracking so tokens can record + # accurate columns. + # + # - The bulk of tokens are recognized in _match_token: + # + # * _regex_rules: regex patterns for identifiers, literals, and other + # complex tokens (including error-producing patterns). The lexer + # uses a combined _regex_master to scan options at the same time. + # * _fixed_tokens: exact string matches for operators and punctuation, + # resolved by longest match. + # + # - Error patterns call the error callback and advance minimally, which + # keeps lexing resilient while reporting useful diagnostics. + text = self._lexdata + n = len(text) + + if self._pending_tok is not None: + tok = self._pending_tok + self._pending_tok = None + return tok + + while self._pos < n: + match text[self._pos]: + case " " | "\t": + self._pos += 1 + case "\n": + self._lineno += 1 + self._pos += 1 + self._line_start = self._pos + case "#": + if _line_pattern.match(text, self._pos + 1): + self._pos += 1 + self._handle_ppline() + continue + if _pragma_pattern.match(text, self._pos + 1): + self._pos += 1 + toks = self._handle_pppragma() + if len(toks) > 1: + self._pending_tok = toks[1] + if len(toks) > 0: + return toks[0] + continue + tok = self._make_token("PPHASH", "#", self._pos) + self._pos += 1 + return tok + case _: + if tok := self._match_token(): + return tok + else: + continue + + def _match_token(self) -> Optional[_Token]: + """Match one token at the current position. + + Returns a Token on success, or None if no token could be matched and + an error was reported. This method always advances _pos by the matched + length, or by 1 on error/no-match. + """ + text = self._lexdata + pos = self._pos + # We pick the longest match between: + # - the master regex (identifiers, literals, error patterns, etc.) + # - fixed operator/punctuator literals from the bucket for text[pos] + # + # The longest match is required to ensure we properly lex something + # like ".123" (a floating-point constant) as a single entity (with + # FLOAT_CONST), rather than a PERIOD followed by a number. + # + # The fixed-literal buckets are already length-sorted, so within that + # bucket we can take the first match. However, we still compare its + # length to the regex match because the regex may have matched a longer + # token that should take precedence. + best = None + + if m := _regex_master.match(text, pos): + tok_type = m.lastgroup + # All master-regex alternatives are named; lastgroup shouldn't be None. + assert tok_type is not None + value = m.group(tok_type) + length = len(value) + action, msg = _regex_actions[tok_type] + best = (length, tok_type, value, action, msg) + + if bucket := _fixed_tokens_by_first.get(text[pos]): + for entry in bucket: + if text.startswith(entry.literal, pos): + length = len(entry.literal) + if best is None or length > best[0]: + best = ( + length, + entry.tok_type, + entry.literal, + _RegexAction.TOKEN, + None, + ) + break + + if best is None: + msg = f"Illegal character {repr(text[pos])}" + self._error(msg, pos) + self._pos += 1 + return None + + length, tok_type, value, action, msg = best + if action == _RegexAction.ERROR: + if tok_type == "BAD_CHAR_CONST": + msg = f"Invalid char constant {value}" + # All other ERROR rules provide a message. + assert msg is not None + self._error(msg, pos) + self._pos += max(1, length) + return None + + if action == _RegexAction.ID: + tok_type = _keyword_map.get(value, "ID") + if tok_type == "ID" and self.type_lookup_func(value): + tok_type = "TYPEID" + + tok = self._make_token(tok_type, value, pos) + self._pos += length + + if tok.type == "LBRACE": + self.on_lbrace_func() + elif tok.type == "RBRACE": + self.on_rbrace_func() + + return tok + + def _make_token(self, tok_type: str, value: str, pos: int) -> _Token: + """Create a Token at an absolute input position. + + Expects tok_type/value and the absolute byte offset pos in the current + input. Does not advance lexer state; callers manage _pos themselves. + Returns a Token with lineno/column computed from current line tracking. + """ + column = pos - self._line_start + 1 + tok = _Token(tok_type, value, self._lineno, column) + return tok + + def _error(self, msg: str, pos: int) -> None: + column = pos - self._line_start + 1 + self.error_func(msg, self._lineno, column) + + def _handle_ppline(self) -> None: + # Since #line directives aren't supposed to return tokens but should + # only affect the lexer's state (update line/filename for coords), this + # method does a bit of parsing on its own. It doesn't return anything, + # but its side effect is to update self._pos past the directive, and + # potentially update self._lineno and self._filename, based on the + # directive's contents. + # + # Accepted #line forms from preprocessors: + # - "#line 66 \"kwas\\df.h\"" + # - "# 9" + # - "#line 10 \"include/me.h\" 1 2 3" (extra numeric flags) + # - "# 1 \"file.h\" 3" + # Errors we must report: + # - "#line \"file.h\"" (filename before line number) + # - "#line df" (garbage instead of number/string) + # + # We scan the directive line once (after an optional 'line' keyword), + # validating the order: NUMBER, optional STRING, then any NUMBERs. + # The NUMBERs tail is only accepted if a filename STRING was present. + text = self._lexdata + n = len(text) + line_end = text.find("\n", self._pos) + if line_end == -1: + line_end = n + line = text[self._pos : line_end] + pos = 0 + line_len = len(line) + + def skip_ws() -> None: + nonlocal pos + while pos < line_len and line[pos] in " \t": + pos += 1 + + skip_ws() + if line.startswith("line", pos): + pos += 4 + + def success(pp_line: Optional[str], pp_filename: Optional[str]) -> None: + if pp_line is None: + self._error("line number missing in #line", self._pos + line_len) + else: + self._lineno = int(pp_line) + if pp_filename is not None: + self._filename = pp_filename + self._pos = line_end + 1 + self._line_start = self._pos + + def fail(msg: str, offset: int) -> None: + self._error(msg, self._pos + offset) + self._pos = line_end + 1 + self._line_start = self._pos + + skip_ws() + if pos >= line_len: + success(None, None) + return + if line[pos] == '"': + fail("filename before line number in #line", pos) + return + + m = re.match(_decimal_constant, line[pos:]) + if not m: + fail("invalid #line directive", pos) + return + + pp_line = m.group(0) + pos += len(pp_line) + skip_ws() + if pos >= line_len: + success(pp_line, None) + return + + if line[pos] != '"': + fail("invalid #line directive", pos) + return + + m = re.match(_string_literal, line[pos:]) + if not m: + fail("invalid #line directive", pos) + return + + pp_filename = m.group(0).lstrip('"').rstrip('"') + pos += len(m.group(0)) + + # Consume arbitrary sequence of numeric flags after the directive + while True: + skip_ws() + if pos >= line_len: + break + m = re.match(_decimal_constant, line[pos:]) + if not m: + fail("invalid #line directive", pos) + return + pos += len(m.group(0)) + + success(pp_line, pp_filename) + + def _handle_pppragma(self) -> List[_Token]: + # Parse a full #pragma line; returns a list of tokens with 1 or 2 + # tokens - PPPRAGMA and an optional PPPRAGMASTR. If an empty list is + # returned, it means an error occurred, or we're at the end of input. + # + # Examples: + # - "#pragma" -> PPPRAGMA only + # - "#pragma once" -> PPPRAGMA, PPPRAGMASTR("once") + # - "# pragma omp parallel private(th_id)" -> PPPRAGMA, PPPRAGMASTR("omp parallel private(th_id)") + # - "#\tpragma {pack: 2, smack: 3}" -> PPPRAGMA, PPPRAGMASTR("{pack: 2, smack: 3}") + text = self._lexdata + n = len(text) + pos = self._pos + + while pos < n and text[pos] in " \t": + pos += 1 + if pos >= n: + self._pos = pos + return [] + + if not text.startswith("pragma", pos): + self._error("invalid #pragma directive", pos) + self._pos = pos + 1 + return [] + + pragma_pos = pos + pos += len("pragma") + toks = [self._make_token("PPPRAGMA", "pragma", pragma_pos)] + + while pos < n and text[pos] in " \t": + pos += 1 + + start = pos + while pos < n and text[pos] != "\n": + pos += 1 + if pos > start: + toks.append(self._make_token("PPPRAGMASTR", text[start:pos], start)) + if pos < n and text[pos] == "\n": + self._lineno += 1 + pos += 1 + self._line_start = pos + self._pos = pos + return toks + + +## +## Reserved keywords +## +_keywords: Tuple[str, ...] = ( + "AUTO", + "BREAK", + "CASE", + "CHAR", + "CONST", + "CONTINUE", + "DEFAULT", + "DO", + "DOUBLE", + "ELSE", + "ENUM", + "EXTERN", + "FLOAT", + "FOR", + "GOTO", + "IF", + "INLINE", + "INT", + "LONG", + "REGISTER", + "OFFSETOF", + "RESTRICT", + "RETURN", + "SHORT", + "SIGNED", + "SIZEOF", + "STATIC", + "STRUCT", + "SWITCH", + "TYPEDEF", + "UNION", + "UNSIGNED", + "VOID", + "VOLATILE", + "WHILE", + "__INT128", + "_BOOL", + "_COMPLEX", + "_NORETURN", + "_THREAD_LOCAL", + "_STATIC_ASSERT", + "_ATOMIC", + "_ALIGNOF", + "_ALIGNAS", + "_PRAGMA", +) + +_keyword_map: Dict[str, str] = {} + +for keyword in _keywords: + # Keywords from new C standard are mixed-case, like _Bool, _Alignas, etc. + if keyword.startswith("_") and len(keyword) > 1 and keyword[1].isalpha(): + _keyword_map[keyword[:2].upper() + keyword[2:].lower()] = keyword + else: + _keyword_map[keyword.lower()] = keyword + +## +## Regexes for use in tokens +## + +# valid C identifiers (K&R2: A.2.3), plus '$' (supported by some compilers) +_identifier = r"[a-zA-Z_$][0-9a-zA-Z_$]*" + +_hex_prefix = "0[xX]" +_hex_digits = "[0-9a-fA-F]+" +_bin_prefix = "0[bB]" +_bin_digits = "[01]+" + +# integer constants (K&R2: A.2.5.1) +_integer_suffix_opt = ( + r"(([uU]ll)|([uU]LL)|(ll[uU]?)|(LL[uU]?)|([uU][lL])|([lL][uU]?)|[uU])?" +) +_decimal_constant = ( + "(0" + _integer_suffix_opt + ")|([1-9][0-9]*" + _integer_suffix_opt + ")" +) +_octal_constant = "0[0-7]*" + _integer_suffix_opt +_hex_constant = _hex_prefix + _hex_digits + _integer_suffix_opt +_bin_constant = _bin_prefix + _bin_digits + _integer_suffix_opt + +_bad_octal_constant = "0[0-7]*[89]" + +# comments are not supported +_unsupported_c_style_comment = r"\/\*" +_unsupported_cxx_style_comment = r"\/\/" + +# character constants (K&R2: A.2.5.2) +# Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line +# directives with Windows paths as filenames (..\..\dir\file) +# For the same reason, decimal_escape allows all digit sequences. We want to +# parse all correct code, even if it means to sometimes parse incorrect +# code. +# +# The original regexes were taken verbatim from the C syntax definition, +# and were later modified to avoid worst-case exponential running time. +# +# simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" +# decimal_escape = r"""(\d+)""" +# hex_escape = r"""(x[0-9a-fA-F]+)""" +# bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" +# +# The following modifications were made to avoid the ambiguity that allowed +# backtracking: (https://github.com/eliben/pycparser/issues/61) +# +# - \x was removed from simple_escape, unless it was not followed by a hex +# digit, to avoid ambiguity with hex_escape. +# - hex_escape allows one or more hex characters, but requires that the next +# character(if any) is not hex +# - decimal_escape allows one or more decimal characters, but requires that the +# next character(if any) is not a decimal +# - bad_escape does not allow any decimals (8-9), to avoid conflicting with the +# permissive decimal_escape. +# +# Without this change, python's `re` module would recursively try parsing each +# ambiguous escape sequence in multiple ways. e.g. `\123` could be parsed as +# `\1`+`23`, `\12`+`3`, and `\123`. + +_simple_escape = r"""([a-wyzA-Z._~!=&\^\-\\?'"]|x(?![0-9a-fA-F]))""" +_decimal_escape = r"""(\d+)(?!\d)""" +_hex_escape = r"""(x[0-9a-fA-F]+)(?![0-9a-fA-F])""" +_bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-9])""" + +_escape_sequence = ( + r"""(\\(""" + _simple_escape + "|" + _decimal_escape + "|" + _hex_escape + "))" +) + +# This complicated regex with lookahead might be slow for strings, so because +# all of the valid escapes (including \x) allowed +# 0 or more non-escaped characters after the first character, +# simple_escape+decimal_escape+hex_escape got simplified to + +_escape_sequence_start_in_string = r"""(\\[0-9a-zA-Z._~!=&\^\-\\?'"])""" + +_cconst_char = r"""([^'\\\n]|""" + _escape_sequence + ")" +_char_const = "'" + _cconst_char + "'" +_wchar_const = "L" + _char_const +_u8char_const = "u8" + _char_const +_u16char_const = "u" + _char_const +_u32char_const = "U" + _char_const +_multicharacter_constant = "'" + _cconst_char + "{2,4}'" +_unmatched_quote = "('" + _cconst_char + "*\\n)|('" + _cconst_char + "*$)" +_bad_char_const = ( + r"""('""" + _cconst_char + """[^'\n]+')|('')|('""" + _bad_escape + r"""[^'\n]*')""" +) + +# string literals (K&R2: A.2.6) +_string_char = r"""([^"\\\n]|""" + _escape_sequence_start_in_string + ")" +_string_literal = '"' + _string_char + '*"' +_wstring_literal = "L" + _string_literal +_u8string_literal = "u8" + _string_literal +_u16string_literal = "u" + _string_literal +_u32string_literal = "U" + _string_literal +_bad_string_literal = '"' + _string_char + "*" + _bad_escape + _string_char + '*"' + +# floating constants (K&R2: A.2.5.3) +_exponent_part = r"""([eE][-+]?[0-9]+)""" +_fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" +_floating_constant = ( + "((((" + + _fractional_constant + + ")" + + _exponent_part + + "?)|([0-9]+" + + _exponent_part + + "))[FfLl]?)" +) +_binary_exponent_part = r"""([pP][+-]?[0-9]+)""" +_hex_fractional_constant = ( + "(((" + _hex_digits + r""")?\.""" + _hex_digits + ")|(" + _hex_digits + r"""\.))""" +) +_hex_floating_constant = ( + "(" + + _hex_prefix + + "(" + + _hex_digits + + "|" + + _hex_fractional_constant + + ")" + + _binary_exponent_part + + "[FfLl]?)" +) + + +class _RegexAction(Enum): + TOKEN = 0 + ID = 1 + ERROR = 2 + + +@dataclass(frozen=True) +class _RegexRule: + # tok_type: name of the token emitted for a match + # regex_pattern: the raw regex (no anchors) to match at the current position + # action: TOKEN for normal tokens, ID for identifiers, ERROR to report + # error_message: message used for ERROR entries + tok_type: str + regex_pattern: str + action: _RegexAction + error_message: Optional[str] + + +_regex_rules: List[_RegexRule] = [ + _RegexRule( + "UNSUPPORTED_C_STYLE_COMMENT", + _unsupported_c_style_comment, + _RegexAction.ERROR, + "Comments are not supported, see https://github.com/eliben/pycparser#3using.", + ), + _RegexRule( + "UNSUPPORTED_CXX_STYLE_COMMENT", + _unsupported_cxx_style_comment, + _RegexAction.ERROR, + "Comments are not supported, see https://github.com/eliben/pycparser#3using.", + ), + _RegexRule( + "BAD_STRING_LITERAL", + _bad_string_literal, + _RegexAction.ERROR, + "String contains invalid escape code", + ), + _RegexRule("WSTRING_LITERAL", _wstring_literal, _RegexAction.TOKEN, None), + _RegexRule("U8STRING_LITERAL", _u8string_literal, _RegexAction.TOKEN, None), + _RegexRule("U16STRING_LITERAL", _u16string_literal, _RegexAction.TOKEN, None), + _RegexRule("U32STRING_LITERAL", _u32string_literal, _RegexAction.TOKEN, None), + _RegexRule("STRING_LITERAL", _string_literal, _RegexAction.TOKEN, None), + _RegexRule("HEX_FLOAT_CONST", _hex_floating_constant, _RegexAction.TOKEN, None), + _RegexRule("FLOAT_CONST", _floating_constant, _RegexAction.TOKEN, None), + _RegexRule("INT_CONST_HEX", _hex_constant, _RegexAction.TOKEN, None), + _RegexRule("INT_CONST_BIN", _bin_constant, _RegexAction.TOKEN, None), + _RegexRule( + "BAD_CONST_OCT", + _bad_octal_constant, + _RegexAction.ERROR, + "Invalid octal constant", + ), + _RegexRule("INT_CONST_OCT", _octal_constant, _RegexAction.TOKEN, None), + _RegexRule("INT_CONST_DEC", _decimal_constant, _RegexAction.TOKEN, None), + _RegexRule("INT_CONST_CHAR", _multicharacter_constant, _RegexAction.TOKEN, None), + _RegexRule("CHAR_CONST", _char_const, _RegexAction.TOKEN, None), + _RegexRule("WCHAR_CONST", _wchar_const, _RegexAction.TOKEN, None), + _RegexRule("U8CHAR_CONST", _u8char_const, _RegexAction.TOKEN, None), + _RegexRule("U16CHAR_CONST", _u16char_const, _RegexAction.TOKEN, None), + _RegexRule("U32CHAR_CONST", _u32char_const, _RegexAction.TOKEN, None), + _RegexRule("UNMATCHED_QUOTE", _unmatched_quote, _RegexAction.ERROR, "Unmatched '"), + _RegexRule("BAD_CHAR_CONST", _bad_char_const, _RegexAction.ERROR, None), + _RegexRule("ID", _identifier, _RegexAction.ID, None), +] + +_regex_actions: Dict[str, Tuple[_RegexAction, Optional[str]]] = {} +_regex_pattern_parts: List[str] = [] +for _rule in _regex_rules: + _regex_actions[_rule.tok_type] = (_rule.action, _rule.error_message) + _regex_pattern_parts.append(f"(?P<{_rule.tok_type}>{_rule.regex_pattern})") +# The master regex is a single alternation of all token patterns, each wrapped +# in a named group. We match once at the current position and then use +# `lastgroup` to recover which token kind fired; this avoids iterating over all +# regexes on every character while keeping the same token-level semantics. +_regex_master: re.Pattern[str] = re.compile("|".join(_regex_pattern_parts)) + + +@dataclass(frozen=True) +class _FixedToken: + tok_type: str + literal: str + + +_fixed_tokens: List[_FixedToken] = [ + _FixedToken("ELLIPSIS", "..."), + _FixedToken("LSHIFTEQUAL", "<<="), + _FixedToken("RSHIFTEQUAL", ">>="), + _FixedToken("PLUSPLUS", "++"), + _FixedToken("MINUSMINUS", "--"), + _FixedToken("ARROW", "->"), + _FixedToken("LAND", "&&"), + _FixedToken("LOR", "||"), + _FixedToken("LSHIFT", "<<"), + _FixedToken("RSHIFT", ">>"), + _FixedToken("LE", "<="), + _FixedToken("GE", ">="), + _FixedToken("EQ", "=="), + _FixedToken("NE", "!="), + _FixedToken("TIMESEQUAL", "*="), + _FixedToken("DIVEQUAL", "/="), + _FixedToken("MODEQUAL", "%="), + _FixedToken("PLUSEQUAL", "+="), + _FixedToken("MINUSEQUAL", "-="), + _FixedToken("ANDEQUAL", "&="), + _FixedToken("OREQUAL", "|="), + _FixedToken("XOREQUAL", "^="), + _FixedToken("EQUALS", "="), + _FixedToken("PLUS", "+"), + _FixedToken("MINUS", "-"), + _FixedToken("TIMES", "*"), + _FixedToken("DIVIDE", "/"), + _FixedToken("MOD", "%"), + _FixedToken("OR", "|"), + _FixedToken("AND", "&"), + _FixedToken("NOT", "~"), + _FixedToken("XOR", "^"), + _FixedToken("LNOT", "!"), + _FixedToken("LT", "<"), + _FixedToken("GT", ">"), + _FixedToken("CONDOP", "?"), + _FixedToken("LPAREN", "("), + _FixedToken("RPAREN", ")"), + _FixedToken("LBRACKET", "["), + _FixedToken("RBRACKET", "]"), + _FixedToken("LBRACE", "{"), + _FixedToken("RBRACE", "}"), + _FixedToken("COMMA", ","), + _FixedToken("PERIOD", "."), + _FixedToken("SEMI", ";"), + _FixedToken("COLON", ":"), +] + +# To avoid scanning all fixed tokens on every character, we bucket them by the +# first character. When matching at position i, we only look at the bucket for +# text[i], and we pre-sort that bucket by token length so the first match is +# also the longest. This preserves longest-match semantics (e.g. '>>=' before +# '>>' before '>') while reducing the number of comparisons. +_fixed_tokens_by_first: Dict[str, List[_FixedToken]] = {} +for _entry in _fixed_tokens: + _fixed_tokens_by_first.setdefault(_entry.literal[0], []).append(_entry) +for _bucket in _fixed_tokens_by_first.values(): + _bucket.sort(key=lambda item: len(item.literal), reverse=True) + +_line_pattern: re.Pattern[str] = re.compile(r"([ \t]*line\W)|([ \t]*\d+)") +_pragma_pattern: re.Pattern[str] = re.compile(r"[ \t]*pragma\W") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py new file mode 100644 index 0000000..f980672 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py @@ -0,0 +1,2376 @@ +# ------------------------------------------------------------------------------ +# pycparser: c_parser.py +# +# Recursive-descent parser for the C language. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +# ------------------------------------------------------------------------------ +from dataclasses import dataclass +from typing import ( + Any, + Dict, + List, + Literal, + NoReturn, + Optional, + Tuple, + TypedDict, + cast, +) + +from . import c_ast +from .c_lexer import CLexer, _Token +from .ast_transforms import fix_switch_cases, fix_atomic_specifiers + + +@dataclass +class Coord: + """Coordinates of a syntactic element. Consists of: + - File name + - Line number + - Column number + """ + + file: str + line: int + column: Optional[int] = None + + def __str__(self) -> str: + text = f"{self.file}:{self.line}" + if self.column: + text += f":{self.column}" + return text + + +class ParseError(Exception): + pass + + +class CParser: + """Recursive-descent C parser. + + Usage: + parser = CParser() + ast = parser.parse(text, filename) + + The `lexer` parameter lets you inject a lexer class (defaults to CLexer). + The parameters after `lexer` are accepted for backward compatibility with + the old PLY-based parser and are otherwise unused. + """ + + def __init__( + self, + lex_optimize: bool = True, + lexer: type[CLexer] = CLexer, + lextab: str = "pycparser.lextab", + yacc_optimize: bool = True, + yacctab: str = "pycparser.yacctab", + yacc_debug: bool = False, + taboutputdir: str = "", + ) -> None: + self.clex: CLexer = lexer( + error_func=self._lex_error_func, + on_lbrace_func=self._lex_on_lbrace_func, + on_rbrace_func=self._lex_on_rbrace_func, + type_lookup_func=self._lex_type_lookup_func, + ) + + # Stack of scopes for keeping track of symbols. _scope_stack[-1] is + # the current (topmost) scope. Each scope is a dictionary that + # specifies whether a name is a type. If _scope_stack[n][name] is + # True, 'name' is currently a type in the scope. If it's False, + # 'name' is used in the scope but not as a type (for instance, if we + # saw: int name; + # If 'name' is not a key in _scope_stack[n] then 'name' was not defined + # in this scope at all. + self._scope_stack: List[Dict[str, bool]] = [dict()] + self._tokens: _TokenStream = _TokenStream(self.clex) + + def parse( + self, text: str, filename: str = "", debug: bool = False + ) -> c_ast.FileAST: + """Parses C code and returns an AST. + + text: + A string containing the C source code + + filename: + Name of the file being parsed (for meaningful + error messages) + + debug: + Deprecated debug flag (unused); for backwards compatibility. + """ + self._scope_stack = [dict()] + self.clex.input(text, filename) + self._tokens = _TokenStream(self.clex) + + ast = self._parse_translation_unit_or_empty() + tok = self._peek() + if tok is not None: + self._parse_error(f"before: {tok.value}", self._tok_coord(tok)) + return ast + + # ------------------------------------------------------------------ + # Scope and declaration helpers + # ------------------------------------------------------------------ + def _coord(self, lineno: int, column: Optional[int] = None) -> Coord: + return Coord(file=self.clex.filename, line=lineno, column=column) + + def _parse_error(self, msg: str, coord: Coord | str | None) -> NoReturn: + raise ParseError(f"{coord}: {msg}") + + def _push_scope(self) -> None: + self._scope_stack.append(dict()) + + def _pop_scope(self) -> None: + assert len(self._scope_stack) > 1 + self._scope_stack.pop() + + def _add_typedef_name(self, name: str, coord: Optional[Coord]) -> None: + """Add a new typedef name (ie a TYPEID) to the current scope""" + if not self._scope_stack[-1].get(name, True): + self._parse_error( + f"Typedef {name!r} previously declared as non-typedef in this scope", + coord, + ) + self._scope_stack[-1][name] = True + + def _add_identifier(self, name: str, coord: Optional[Coord]) -> None: + """Add a new object, function, or enum member name (ie an ID) to the + current scope + """ + if self._scope_stack[-1].get(name, False): + self._parse_error( + f"Non-typedef {name!r} previously declared as typedef in this scope", + coord, + ) + self._scope_stack[-1][name] = False + + def _is_type_in_scope(self, name: str) -> bool: + """Is *name* a typedef-name in the current scope?""" + for scope in reversed(self._scope_stack): + # If name is an identifier in this scope it shadows typedefs in + # higher scopes. + in_scope = scope.get(name) + if in_scope is not None: + return in_scope + return False + + def _lex_error_func(self, msg: str, line: int, column: int) -> None: + self._parse_error(msg, self._coord(line, column)) + + def _lex_on_lbrace_func(self) -> None: + self._push_scope() + + def _lex_on_rbrace_func(self) -> None: + self._pop_scope() + + def _lex_type_lookup_func(self, name: str) -> bool: + """Looks up types that were previously defined with + typedef. + Passed to the lexer for recognizing identifiers that + are types. + """ + return self._is_type_in_scope(name) + + # To understand what's going on here, read sections A.8.5 and + # A.8.6 of K&R2 very carefully. + # + # A C type consists of a basic type declaration, with a list + # of modifiers. For example: + # + # int *c[5]; + # + # The basic declaration here is 'int c', and the pointer and + # the array are the modifiers. + # + # Basic declarations are represented by TypeDecl (from module c_ast) and the + # modifiers are FuncDecl, PtrDecl and ArrayDecl. + # + # The standard states that whenever a new modifier is parsed, it should be + # added to the end of the list of modifiers. For example: + # + # K&R2 A.8.6.2: Array Declarators + # + # In a declaration T D where D has the form + # D1 [constant-expression-opt] + # and the type of the identifier in the declaration T D1 is + # "type-modifier T", the type of the + # identifier of D is "type-modifier array of T" + # + # This is what this method does. The declarator it receives + # can be a list of declarators ending with TypeDecl. It + # tacks the modifier to the end of this list, just before + # the TypeDecl. + # + # Additionally, the modifier may be a list itself. This is + # useful for pointers, that can come as a chain from the rule + # p_pointer. In this case, the whole modifier list is spliced + # into the new location. + def _type_modify_decl(self, decl: Any, modifier: Any) -> c_ast.Node: + """Tacks a type modifier on a declarator, and returns + the modified declarator. + + Note: the declarator and modifier may be modified + """ + modifier_head = modifier + modifier_tail = modifier + + # The modifier may be a nested list. Reach its tail. + while modifier_tail.type: + modifier_tail = modifier_tail.type + + # If the decl is a basic type, just tack the modifier onto it. + if isinstance(decl, c_ast.TypeDecl): + modifier_tail.type = decl + return modifier + else: + # Otherwise, the decl is a list of modifiers. Reach + # its tail and splice the modifier onto the tail, + # pointing to the underlying basic type. + decl_tail = decl + while not isinstance(decl_tail.type, c_ast.TypeDecl): + decl_tail = decl_tail.type + + modifier_tail.type = decl_tail.type + decl_tail.type = modifier_head + return decl + + # Due to the order in which declarators are constructed, + # they have to be fixed in order to look like a normal AST. + # + # When a declaration arrives from syntax construction, it has + # these problems: + # * The innermost TypeDecl has no type (because the basic + # type is only known at the uppermost declaration level) + # * The declaration has no variable name, since that is saved + # in the innermost TypeDecl + # * The typename of the declaration is a list of type + # specifiers, and not a node. Here, basic identifier types + # should be separated from more complex types like enums + # and structs. + # + # This method fixes these problems. + def _fix_decl_name_type( + self, + decl: c_ast.Decl | c_ast.Typedef | c_ast.Typename, + typename: List[Any], + ) -> c_ast.Decl | c_ast.Typedef | c_ast.Typename: + """Fixes a declaration. Modifies decl.""" + # Reach the underlying basic type + typ = decl + while not isinstance(typ, c_ast.TypeDecl): + typ = typ.type + + decl.name = typ.declname + typ.quals = decl.quals[:] + + # The typename is a list of types. If any type in this + # list isn't an IdentifierType, it must be the only + # type in the list (it's illegal to declare "int enum ..") + # If all the types are basic, they're collected in the + # IdentifierType holder. + for tn in typename: + if not isinstance(tn, c_ast.IdentifierType): + if len(typename) > 1: + self._parse_error("Invalid multiple types specified", tn.coord) + else: + typ.type = tn + return decl + + if not typename: + # Functions default to returning int + if not isinstance(decl.type, c_ast.FuncDecl): + self._parse_error("Missing type in declaration", decl.coord) + typ.type = c_ast.IdentifierType(["int"], coord=decl.coord) + else: + # At this point, we know that typename is a list of IdentifierType + # nodes. Concatenate all the names into a single list. + typ.type = c_ast.IdentifierType( + [name for id in typename for name in id.names], coord=typename[0].coord + ) + return decl + + def _add_declaration_specifier( + self, + declspec: Optional["_DeclSpec"], + newspec: Any, + kind: "_DeclSpecKind", + append: bool = False, + ) -> "_DeclSpec": + """See _DeclSpec for the specifier dictionary layout.""" + if declspec is None: + spec: _DeclSpec = dict( + qual=[], storage=[], type=[], function=[], alignment=[] + ) + else: + spec = declspec + + if append: + spec[kind].append(newspec) + else: + spec[kind].insert(0, newspec) + + return spec + + def _build_declarations( + self, + spec: "_DeclSpec", + decls: List["_DeclInfo"], + typedef_namespace: bool = False, + ) -> List[c_ast.Node]: + """Builds a list of declarations all sharing the given specifiers. + If typedef_namespace is true, each declared name is added + to the "typedef namespace", which also includes objects, + functions, and enum constants. + """ + is_typedef = "typedef" in spec["storage"] + declarations = [] + + # Bit-fields are allowed to be unnamed. + if decls[0].get("bitsize") is None: + # When redeclaring typedef names as identifiers in inner scopes, a + # problem can occur where the identifier gets grouped into + # spec['type'], leaving decl as None. This can only occur for the + # first declarator. + if decls[0]["decl"] is None: + if ( + len(spec["type"]) < 2 + or len(spec["type"][-1].names) != 1 + or not self._is_type_in_scope(spec["type"][-1].names[0]) + ): + coord = "?" + for t in spec["type"]: + if hasattr(t, "coord"): + coord = t.coord + break + self._parse_error("Invalid declaration", coord) + + # Make this look as if it came from "direct_declarator:ID" + decls[0]["decl"] = c_ast.TypeDecl( + declname=spec["type"][-1].names[0], + type=None, + quals=None, + align=spec["alignment"], + coord=spec["type"][-1].coord, + ) + # Remove the "new" type's name from the end of spec['type'] + del spec["type"][-1] + # A similar problem can occur where the declaration ends up + # looking like an abstract declarator. Give it a name if this is + # the case. + elif not isinstance( + decls[0]["decl"], + (c_ast.Enum, c_ast.Struct, c_ast.Union, c_ast.IdentifierType), + ): + decls_0_tail = cast(Any, decls[0]["decl"]) + while not isinstance(decls_0_tail, c_ast.TypeDecl): + decls_0_tail = decls_0_tail.type + if decls_0_tail.declname is None: + decls_0_tail.declname = spec["type"][-1].names[0] + del spec["type"][-1] + + for decl in decls: + assert decl["decl"] is not None + if is_typedef: + declaration = c_ast.Typedef( + name=None, + quals=spec["qual"], + storage=spec["storage"], + type=decl["decl"], + coord=decl["decl"].coord, + ) + else: + declaration = c_ast.Decl( + name=None, + quals=spec["qual"], + align=spec["alignment"], + storage=spec["storage"], + funcspec=spec["function"], + type=decl["decl"], + init=decl.get("init"), + bitsize=decl.get("bitsize"), + coord=decl["decl"].coord, + ) + + if isinstance( + declaration.type, + (c_ast.Enum, c_ast.Struct, c_ast.Union, c_ast.IdentifierType), + ): + fixed_decl = declaration + else: + fixed_decl = self._fix_decl_name_type(declaration, spec["type"]) + + # Add the type name defined by typedef to a + # symbol table (for usage in the lexer) + if typedef_namespace: + if is_typedef: + self._add_typedef_name(fixed_decl.name, fixed_decl.coord) + else: + self._add_identifier(fixed_decl.name, fixed_decl.coord) + + fixed_decl = fix_atomic_specifiers( + cast(c_ast.Decl | c_ast.Typedef, fixed_decl) + ) + declarations.append(fixed_decl) + + return declarations + + def _build_function_definition( + self, + spec: "_DeclSpec", + decl: c_ast.Node, + param_decls: Optional[List[c_ast.Node]], + body: c_ast.Node, + ) -> c_ast.Node: + """Builds a function definition.""" + if "typedef" in spec["storage"]: + self._parse_error("Invalid typedef", decl.coord) + + declaration = self._build_declarations( + spec=spec, + decls=[dict(decl=decl, init=None, bitsize=None)], + typedef_namespace=True, + )[0] + + return c_ast.FuncDef( + decl=declaration, param_decls=param_decls, body=body, coord=decl.coord + ) + + def _select_struct_union_class(self, token: str) -> type: + """Given a token (either STRUCT or UNION), selects the + appropriate AST class. + """ + if token == "struct": + return c_ast.Struct + else: + return c_ast.Union + + # ------------------------------------------------------------------ + # Token helpers + # ------------------------------------------------------------------ + def _peek(self, k: int = 1) -> Optional[_Token]: + """Return the k-th next token without consuming it (1-based).""" + return self._tokens.peek(k) + + def _peek_type(self, k: int = 1) -> Optional[str]: + """Return the type of the k-th next token, or None if absent (1-based).""" + tok = self._peek(k) + return tok.type if tok is not None else None + + def _advance(self) -> _Token: + tok = self._tokens.next() + if tok is None: + self._parse_error("At end of input", self.clex.filename) + else: + return tok + + def _accept(self, token_type: str) -> Optional[_Token]: + """Conditionally consume next token, only if it's of token_type. + + If it is of the expected type, consume and return it. + Otherwise, leaves the token intact and returns None. + """ + tok = self._peek() + if tok is not None and tok.type == token_type: + return self._advance() + return None + + def _expect(self, token_type: str) -> _Token: + tok = self._advance() + if tok.type != token_type: + self._parse_error(f"before: {tok.value}", self._tok_coord(tok)) + return tok + + def _mark(self) -> int: + return self._tokens.mark() + + def _reset(self, mark: int) -> None: + self._tokens.reset(mark) + + def _tok_coord(self, tok: _Token) -> Coord: + return self._coord(tok.lineno, tok.column) + + def _starts_declaration(self, tok: Optional[_Token] = None) -> bool: + tok = tok or self._peek() + if tok is None: + return False + return tok.type in _DECL_START + + def _starts_expression(self, tok: Optional[_Token] = None) -> bool: + tok = tok or self._peek() + if tok is None: + return False + return tok.type in _STARTS_EXPRESSION + + def _starts_statement(self) -> bool: + tok_type = self._peek_type() + if tok_type is None: + return False + if tok_type in _STARTS_STATEMENT: + return True + return self._starts_expression() + + def _starts_declarator(self, id_only: bool = False) -> bool: + tok_type = self._peek_type() + if tok_type is None: + return False + if tok_type in {"TIMES", "LPAREN"}: + return True + if id_only: + return tok_type == "ID" + return tok_type in {"ID", "TYPEID"} + + def _peek_declarator_name_info(self) -> Tuple[Optional[str], bool]: + mark = self._mark() + tok_type, saw_paren = self._scan_declarator_name_info() + self._reset(mark) + return tok_type, saw_paren + + def _parse_any_declarator( + self, allow_abstract: bool = False, typeid_paren_as_abstract: bool = False + ) -> Tuple[Optional[c_ast.Node], bool]: + # C declarators are ambiguous without lookahead. For example: + # int foo(int (aa)); -> aa is a name (ID) + # typedef char TT; + # int bar(int (TT)); -> TT is a type (TYPEID) in parens + name_type, saw_paren = self._peek_declarator_name_info() + if name_type is None or ( + typeid_paren_as_abstract and name_type == "TYPEID" and saw_paren + ): + if not allow_abstract: + tok = self._peek() + coord = self._tok_coord(tok) if tok is not None else self.clex.filename + self._parse_error("Invalid declarator", coord) + decl = self._parse_abstract_declarator_opt() + return decl, False + + if name_type == "TYPEID": + if typeid_paren_as_abstract: + decl = self._parse_typeid_noparen_declarator() + else: + decl = self._parse_typeid_declarator() + else: + decl = self._parse_id_declarator() + return decl, True + + def _scan_declarator_name_info(self) -> Tuple[Optional[str], bool]: + saw_paren = False + while self._accept("TIMES"): + while self._peek_type() in _TYPE_QUALIFIER: + self._advance() + + tok = self._peek() + if tok is None: + return None, saw_paren + if tok.type in {"ID", "TYPEID"}: + self._advance() + return tok.type, saw_paren + if tok.type == "LPAREN": + saw_paren = True + self._advance() + tok_type, nested_paren = self._scan_declarator_name_info() + if nested_paren: + saw_paren = True + depth = 1 + while True: + tok = self._peek() + if tok is None: + return None, saw_paren + if tok.type == "LPAREN": + depth += 1 + elif tok.type == "RPAREN": + depth -= 1 + self._advance() + if depth == 0: + break + continue + self._advance() + return tok_type, saw_paren + return None, saw_paren + + def _starts_direct_abstract_declarator(self) -> bool: + return self._peek_type() in {"LPAREN", "LBRACKET"} + + def _is_assignment_op(self) -> bool: + tok = self._peek() + return tok is not None and tok.type in _ASSIGNMENT_OPS + + def _try_parse_paren_type_name( + self, + ) -> Optional[Tuple[c_ast.Typename, int, _Token]]: + """Parse and return a parenthesized type name if present. + + Returns (typ, mark, lparen_tok) when the next tokens look like + '(' type_name ')', where typ is the parsed type name, mark is the + token-stream position before parsing, and lparen_tok is the LPAREN + token. Returns None if no parenthesized type name is present. + """ + mark = self._mark() + lparen_tok = self._accept("LPAREN") + if lparen_tok is None: + return None + if not self._starts_declaration(): + self._reset(mark) + return None + typ = self._parse_type_name() + if self._accept("RPAREN") is None: + self._reset(mark) + return None + return typ, mark, lparen_tok + + # ------------------------------------------------------------------ + # Top-level + # ------------------------------------------------------------------ + # BNF: translation_unit_or_empty : translation_unit | empty + def _parse_translation_unit_or_empty(self) -> c_ast.FileAST: + if self._peek() is None: + return c_ast.FileAST([]) + return c_ast.FileAST(self._parse_translation_unit()) + + # BNF: translation_unit : external_declaration+ + def _parse_translation_unit(self) -> List[c_ast.Node]: + ext = [] + while self._peek() is not None: + ext.extend(self._parse_external_declaration()) + return ext + + # BNF: external_declaration : function_definition + # | declaration + # | pp_directive + # | pppragma_directive + # | static_assert + # | ';' + def _parse_external_declaration(self) -> List[c_ast.Node]: + tok = self._peek() + if tok is None: + return [] + if tok.type == "PPHASH": + self._parse_pp_directive() + return [] + if tok.type in {"PPPRAGMA", "_PRAGMA"}: + return [self._parse_pppragma_directive()] + if self._accept("SEMI"): + return [] + if tok.type == "_STATIC_ASSERT": + return self._parse_static_assert() + + if not self._starts_declaration(tok): + # Special handling for old-style function definitions that have an + # implicit return type, e.g. + # + # foo() { + # return 5; + # } + # + # These get an implicit 'int' return type. + decl = self._parse_id_declarator() + param_decls = None + if self._peek_type() != "LBRACE": + self._parse_error("Invalid function definition", decl.coord) + spec: _DeclSpec = dict( + qual=[], + alignment=[], + storage=[], + type=[c_ast.IdentifierType(["int"], coord=decl.coord)], + function=[], + ) + func = self._build_function_definition( + spec=spec, + decl=decl, + param_decls=param_decls, + body=self._parse_compound_statement(), + ) + return [func] + + # From here on, parsing a standard declatation/definition. + spec, saw_type, spec_coord = self._parse_declaration_specifiers( + allow_no_type=True + ) + + name_type, _ = self._peek_declarator_name_info() + if name_type != "ID": + decls = self._parse_decl_body_with_spec(spec, saw_type) + self._expect("SEMI") + return decls + + decl = self._parse_id_declarator() + + if self._peek_type() == "LBRACE" or self._starts_declaration(): + param_decls = None + if self._starts_declaration(): + param_decls = self._parse_declaration_list() + if self._peek_type() != "LBRACE": + self._parse_error("Invalid function definition", decl.coord) + if not spec["type"]: + spec["type"] = [c_ast.IdentifierType(["int"], coord=spec_coord)] + func = self._build_function_definition( + spec=spec, + decl=decl, + param_decls=param_decls, + body=self._parse_compound_statement(), + ) + return [func] + + decl_dict: "_DeclInfo" = dict(decl=decl, init=None, bitsize=None) + if self._accept("EQUALS"): + decl_dict["init"] = self._parse_initializer() + decls = self._parse_init_declarator_list(first=decl_dict) + decls = self._build_declarations(spec=spec, decls=decls, typedef_namespace=True) + self._expect("SEMI") + return decls + + # ------------------------------------------------------------------ + # Declarations + # + # Declarations always come as lists (because they can be several in one + # line). When returning parsed declarations, a list is always returned - + # even if it contains a single element. + # ------------------------------------------------------------------ + def _parse_declaration(self) -> List[c_ast.Node]: + decls = self._parse_decl_body() + self._expect("SEMI") + return decls + + # BNF: decl_body : declaration_specifiers decl_body_with_spec + def _parse_decl_body(self) -> List[c_ast.Node]: + spec, saw_type, _ = self._parse_declaration_specifiers(allow_no_type=True) + return self._parse_decl_body_with_spec(spec, saw_type) + + # BNF: decl_body_with_spec : init_declarator_list + # | struct_or_union_or_enum_only + def _parse_decl_body_with_spec( + self, spec: "_DeclSpec", saw_type: bool + ) -> List[c_ast.Node]: + decls = None + if saw_type: + if self._starts_declarator(): + decls = self._parse_init_declarator_list() + else: + if self._starts_declarator(id_only=True): + decls = self._parse_init_declarator_list(id_only=True) + + if decls is None: + ty = spec["type"] + s_u_or_e = (c_ast.Struct, c_ast.Union, c_ast.Enum) + if len(ty) == 1 and isinstance(ty[0], s_u_or_e): + decls = [ + c_ast.Decl( + name=None, + quals=spec["qual"], + align=spec["alignment"], + storage=spec["storage"], + funcspec=spec["function"], + type=ty[0], + init=None, + bitsize=None, + coord=ty[0].coord, + ) + ] + else: + decls = self._build_declarations( + spec=spec, + decls=[dict(decl=None, init=None, bitsize=None)], + typedef_namespace=True, + ) + else: + decls = self._build_declarations( + spec=spec, decls=decls, typedef_namespace=True + ) + + return decls + + # BNF: declaration_list : declaration+ + def _parse_declaration_list(self) -> List[c_ast.Node]: + decls = [] + while self._starts_declaration(): + decls.extend(self._parse_declaration()) + return decls + + # BNF: declaration_specifiers : (storage_class_specifier + # | type_specifier + # | type_qualifier + # | function_specifier + # | alignment_specifier)+ + def _parse_declaration_specifiers( + self, allow_no_type: bool = False + ) -> Tuple["_DeclSpec", bool, Optional[Coord]]: + """Parse declaration-specifier sequence. + + allow_no_type: + If True, allow a missing type specifier without error. + + Returns: + (spec, saw_type, first_coord) where spec is a dict with + qual/storage/type/function/alignment entries, saw_type is True + if a type specifier was consumed, and first_coord is the coord + of the first specifier token (used for diagnostics). + """ + spec = None + saw_type = False + first_coord = None + + while True: + tok = self._peek() + if tok is None: + break + + if tok.type == "_ALIGNAS": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_alignment_specifier(), "alignment", append=True + ) + continue + + if tok.type == "_ATOMIC" and self._peek_type(2) == "LPAREN": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_atomic_specifier(), "type", append=True + ) + saw_type = True + continue + + if tok.type in _TYPE_QUALIFIER: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._advance().value, "qual", append=True + ) + continue + + if tok.type in _STORAGE_CLASS: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._advance().value, "storage", append=True + ) + continue + + if tok.type in _FUNCTION_SPEC: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._advance().value, "function", append=True + ) + continue + + if tok.type in _TYPE_SPEC_SIMPLE: + if first_coord is None: + first_coord = self._tok_coord(tok) + tok = self._advance() + spec = self._add_declaration_specifier( + spec, + c_ast.IdentifierType([tok.value], coord=self._tok_coord(tok)), + "type", + append=True, + ) + saw_type = True + continue + + if tok.type == "TYPEID": + if saw_type: + break + if first_coord is None: + first_coord = self._tok_coord(tok) + tok = self._advance() + spec = self._add_declaration_specifier( + spec, + c_ast.IdentifierType([tok.value], coord=self._tok_coord(tok)), + "type", + append=True, + ) + saw_type = True + continue + + if tok.type in {"STRUCT", "UNION"}: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_struct_or_union_specifier(), "type", append=True + ) + saw_type = True + continue + + if tok.type == "ENUM": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_enum_specifier(), "type", append=True + ) + saw_type = True + continue + + break + + if spec is None: + self._parse_error("Invalid declaration", self.clex.filename) + + if not saw_type and not allow_no_type: + self._parse_error("Missing type in declaration", first_coord) + + return spec, saw_type, first_coord + + # BNF: specifier_qualifier_list : (type_specifier + # | type_qualifier + # | alignment_specifier)+ + def _parse_specifier_qualifier_list(self) -> "_DeclSpec": + spec = None + saw_type = False + saw_alignment = False + first_coord = None + + while True: + tok = self._peek() + if tok is None: + break + + if tok.type == "_ALIGNAS": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_alignment_specifier(), "alignment", append=True + ) + saw_alignment = True + continue + + if tok.type == "_ATOMIC" and self._peek_type(2) == "LPAREN": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_atomic_specifier(), "type", append=True + ) + saw_type = True + continue + + if tok.type in _TYPE_QUALIFIER: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._advance().value, "qual", append=True + ) + continue + + if tok.type in _TYPE_SPEC_SIMPLE: + if first_coord is None: + first_coord = self._tok_coord(tok) + tok = self._advance() + spec = self._add_declaration_specifier( + spec, + c_ast.IdentifierType([tok.value], coord=self._tok_coord(tok)), + "type", + append=True, + ) + saw_type = True + continue + + if tok.type == "TYPEID": + if saw_type: + break + if first_coord is None: + first_coord = self._tok_coord(tok) + tok = self._advance() + spec = self._add_declaration_specifier( + spec, + c_ast.IdentifierType([tok.value], coord=self._tok_coord(tok)), + "type", + append=True, + ) + saw_type = True + continue + + if tok.type in {"STRUCT", "UNION"}: + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_struct_or_union_specifier(), "type", append=True + ) + saw_type = True + continue + + if tok.type == "ENUM": + if first_coord is None: + first_coord = self._tok_coord(tok) + spec = self._add_declaration_specifier( + spec, self._parse_enum_specifier(), "type", append=True + ) + saw_type = True + continue + + break + + if spec is None: + self._parse_error("Invalid specifier list", self.clex.filename) + + if not saw_type and not saw_alignment: + self._parse_error("Missing type in declaration", first_coord) + + if spec.get("storage") is None: + spec["storage"] = [] + if spec.get("function") is None: + spec["function"] = [] + + return spec + + # BNF: type_qualifier_list : type_qualifier+ + def _parse_type_qualifier_list(self) -> List[str]: + quals = [] + while self._peek_type() in _TYPE_QUALIFIER: + quals.append(self._advance().value) + return quals + + # BNF: alignment_specifier : _ALIGNAS '(' type_name | constant_expression ')' + def _parse_alignment_specifier(self) -> c_ast.Node: + tok = self._expect("_ALIGNAS") + self._expect("LPAREN") + + if self._starts_declaration(): + typ = self._parse_type_name() + self._expect("RPAREN") + return c_ast.Alignas(typ, self._tok_coord(tok)) + + expr = self._parse_constant_expression() + self._expect("RPAREN") + return c_ast.Alignas(expr, self._tok_coord(tok)) + + # BNF: atomic_specifier : _ATOMIC '(' type_name ')' + def _parse_atomic_specifier(self) -> c_ast.Node: + self._expect("_ATOMIC") + self._expect("LPAREN") + typ = self._parse_type_name() + self._expect("RPAREN") + typ.quals.append("_Atomic") + return typ + + # BNF: init_declarator_list : init_declarator (',' init_declarator)* + def _parse_init_declarator_list( + self, first: Optional["_DeclInfo"] = None, id_only: bool = False + ) -> List["_DeclInfo"]: + decls = ( + [first] + if first is not None + else [self._parse_init_declarator(id_only=id_only)] + ) + + while self._accept("COMMA"): + decls.append(self._parse_init_declarator(id_only=id_only)) + return decls + + # BNF: init_declarator : declarator ('=' initializer)? + def _parse_init_declarator(self, id_only: bool = False) -> "_DeclInfo": + decl = self._parse_id_declarator() if id_only else self._parse_declarator() + init = None + if self._accept("EQUALS"): + init = self._parse_initializer() + return dict(decl=decl, init=init, bitsize=None) + + # ------------------------------------------------------------------ + # Structs/unions/enums + # ------------------------------------------------------------------ + # BNF: struct_or_union_specifier : struct_or_union ID? '{' struct_declaration_list? '}' + # | struct_or_union ID + def _parse_struct_or_union_specifier(self) -> c_ast.Node: + tok = self._advance() + klass = self._select_struct_union_class(tok.value) + + if self._peek_type() in {"ID", "TYPEID"}: + name_tok = self._advance() + if self._peek_type() == "LBRACE": + self._advance() + if self._accept("RBRACE"): + return klass( + name=name_tok.value, decls=[], coord=self._tok_coord(name_tok) + ) + decls = self._parse_struct_declaration_list() + self._expect("RBRACE") + return klass( + name=name_tok.value, decls=decls, coord=self._tok_coord(name_tok) + ) + + return klass( + name=name_tok.value, decls=None, coord=self._tok_coord(name_tok) + ) + + if self._peek_type() == "LBRACE": + brace_tok = self._advance() + if self._accept("RBRACE"): + return klass(name=None, decls=[], coord=self._tok_coord(brace_tok)) + decls = self._parse_struct_declaration_list() + self._expect("RBRACE") + return klass(name=None, decls=decls, coord=self._tok_coord(brace_tok)) + + self._parse_error("Invalid struct/union declaration", self._tok_coord(tok)) + + # BNF: struct_declaration_list : struct_declaration+ + def _parse_struct_declaration_list(self) -> List[c_ast.Node]: + decls = [] + while self._peek_type() not in {None, "RBRACE"}: + items = self._parse_struct_declaration() + if items is None: + continue + decls.extend(items) + return decls + + # BNF: struct_declaration : specifier_qualifier_list struct_declarator_list? ';' + # | static_assert + # | pppragma_directive + def _parse_struct_declaration(self) -> Optional[List[c_ast.Node]]: + if self._peek_type() == "SEMI": + self._advance() + return None + if self._peek_type() in {"PPPRAGMA", "_PRAGMA"}: + return [self._parse_pppragma_directive()] + + spec = self._parse_specifier_qualifier_list() + assert "typedef" not in spec.get("storage", []) + + decls = None + if self._starts_declarator() or self._peek_type() == "COLON": + decls = self._parse_struct_declarator_list() + if decls is not None: + self._expect("SEMI") + return self._build_declarations(spec=spec, decls=decls) + + if len(spec["type"]) == 1: + node = spec["type"][0] + if isinstance(node, c_ast.Node): + decl_type = node + else: + decl_type = c_ast.IdentifierType(node) + self._expect("SEMI") + return self._build_declarations( + spec=spec, decls=[dict(decl=decl_type, init=None, bitsize=None)] + ) + + self._expect("SEMI") + return self._build_declarations( + spec=spec, decls=[dict(decl=None, init=None, bitsize=None)] + ) + + # BNF: struct_declarator_list : struct_declarator (',' struct_declarator)* + def _parse_struct_declarator_list(self) -> List["_DeclInfo"]: + decls = [self._parse_struct_declarator()] + while self._accept("COMMA"): + decls.append(self._parse_struct_declarator()) + return decls + + # BNF: struct_declarator : declarator? ':' constant_expression + # | declarator (':' constant_expression)? + def _parse_struct_declarator(self) -> "_DeclInfo": + if self._accept("COLON"): + bitsize = self._parse_constant_expression() + return { + "decl": c_ast.TypeDecl(None, None, None, None), + "init": None, + "bitsize": bitsize, + } + + decl = self._parse_declarator() + if self._accept("COLON"): + bitsize = self._parse_constant_expression() + return {"decl": decl, "init": None, "bitsize": bitsize} + + return {"decl": decl, "init": None, "bitsize": None} + + # BNF: enum_specifier : ENUM ID? '{' enumerator_list? '}' + # | ENUM ID + def _parse_enum_specifier(self) -> c_ast.Node: + tok = self._expect("ENUM") + if self._peek_type() in {"ID", "TYPEID"}: + name_tok = self._advance() + if self._peek_type() == "LBRACE": + self._advance() + enums = self._parse_enumerator_list() + self._expect("RBRACE") + return c_ast.Enum(name_tok.value, enums, self._tok_coord(tok)) + return c_ast.Enum(name_tok.value, None, self._tok_coord(tok)) + + self._expect("LBRACE") + enums = self._parse_enumerator_list() + self._expect("RBRACE") + return c_ast.Enum(None, enums, self._tok_coord(tok)) + + # BNF: enumerator_list : enumerator (',' enumerator)* ','? + def _parse_enumerator_list(self) -> c_ast.Node: + enum = self._parse_enumerator() + enum_list = c_ast.EnumeratorList([enum], enum.coord) + while self._accept("COMMA"): + if self._peek_type() == "RBRACE": + break + enum = self._parse_enumerator() + enum_list.enumerators.append(enum) + return enum_list + + # BNF: enumerator : ID ('=' constant_expression)? + def _parse_enumerator(self) -> c_ast.Node: + name_tok = self._expect("ID") + if self._accept("EQUALS"): + value = self._parse_constant_expression() + else: + value = None + enum = c_ast.Enumerator(name_tok.value, value, self._tok_coord(name_tok)) + self._add_identifier(enum.name, enum.coord) + return enum + + # ------------------------------------------------------------------ + # Declarators + # ------------------------------------------------------------------ + # BNF: declarator : pointer? direct_declarator + def _parse_declarator(self) -> c_ast.Node: + decl, _ = self._parse_any_declarator( + allow_abstract=False, typeid_paren_as_abstract=False + ) + assert decl is not None + return decl + + # BNF: id_declarator : declarator with ID name + def _parse_id_declarator(self) -> c_ast.Node: + return self._parse_declarator_kind(kind="id", allow_paren=True) + + # BNF: typeid_declarator : declarator with TYPEID name + def _parse_typeid_declarator(self) -> c_ast.Node: + return self._parse_declarator_kind(kind="typeid", allow_paren=True) + + # BNF: typeid_noparen_declarator : declarator without parenthesized name + def _parse_typeid_noparen_declarator(self) -> c_ast.Node: + return self._parse_declarator_kind(kind="typeid", allow_paren=False) + + # BNF: declarator_kind : pointer? direct_declarator(kind) + def _parse_declarator_kind(self, kind: str, allow_paren: bool) -> c_ast.Node: + ptr = None + if self._peek_type() == "TIMES": + ptr = self._parse_pointer() + direct = self._parse_direct_declarator(kind, allow_paren=allow_paren) + if ptr is not None: + return self._type_modify_decl(direct, ptr) + return direct + + # BNF: direct_declarator : ID | TYPEID | '(' declarator ')' + # | direct_declarator '[' ... ']' + # | direct_declarator '(' ... ')' + def _parse_direct_declarator( + self, kind: str, allow_paren: bool = True + ) -> c_ast.Node: + if allow_paren and self._accept("LPAREN"): + decl = self._parse_declarator_kind(kind, allow_paren=True) + self._expect("RPAREN") + else: + if kind == "id": + name_tok = self._expect("ID") + else: + name_tok = self._expect("TYPEID") + decl = c_ast.TypeDecl( + declname=name_tok.value, + type=None, + quals=None, + align=None, + coord=self._tok_coord(name_tok), + ) + + return self._parse_decl_suffixes(decl) + + def _parse_decl_suffixes(self, decl: c_ast.Node) -> c_ast.Node: + """Parse a chain of array/function suffixes and attach them to decl.""" + while True: + if self._peek_type() == "LBRACKET": + decl = self._type_modify_decl(decl, self._parse_array_decl(decl)) + continue + if self._peek_type() == "LPAREN": + func = self._parse_function_decl(decl) + decl = self._type_modify_decl(decl, func) + continue + break + return decl + + # BNF: array_decl : '[' array_specifiers? assignment_expression? ']' + def _parse_array_decl(self, base_decl: c_ast.Node) -> c_ast.Node: + return self._parse_array_decl_common(base_type=None, coord=base_decl.coord) + + def _parse_array_decl_common( + self, base_type: Optional[c_ast.Node], coord: Optional[Coord] = None + ) -> c_ast.Node: + """Parse an array declarator suffix and return an ArrayDecl node. + + base_type: + Base declarator node to attach (None for direct-declarator parsing, + TypeDecl for abstract declarators). + + coord: + Coordinate to use for the ArrayDecl. If None, uses the '[' token. + """ + lbrack_tok = self._expect("LBRACKET") + if coord is None: + coord = self._tok_coord(lbrack_tok) + + def make_array_decl(dim, dim_quals): + return c_ast.ArrayDecl( + type=base_type, dim=dim, dim_quals=dim_quals, coord=coord + ) + + if self._accept("STATIC"): + dim_quals = ["static"] + (self._parse_type_qualifier_list() or []) + dim = self._parse_assignment_expression() + self._expect("RBRACKET") + return make_array_decl(dim, dim_quals) + + if self._peek_type() in _TYPE_QUALIFIER: + dim_quals = self._parse_type_qualifier_list() or [] + if self._accept("STATIC"): + dim_quals = dim_quals + ["static"] + dim = self._parse_assignment_expression() + self._expect("RBRACKET") + return make_array_decl(dim, dim_quals) + times_tok = self._accept("TIMES") + if times_tok: + self._expect("RBRACKET") + dim = c_ast.ID(times_tok.value, self._tok_coord(times_tok)) + return make_array_decl(dim, dim_quals) + dim = None + if self._starts_expression(): + dim = self._parse_assignment_expression() + self._expect("RBRACKET") + return make_array_decl(dim, dim_quals) + + times_tok = self._accept("TIMES") + if times_tok: + self._expect("RBRACKET") + dim = c_ast.ID(times_tok.value, self._tok_coord(times_tok)) + return make_array_decl(dim, []) + + dim = None + if self._starts_expression(): + dim = self._parse_assignment_expression() + self._expect("RBRACKET") + return make_array_decl(dim, []) + + # BNF: function_decl : '(' parameter_type_list_opt | identifier_list_opt ')' + def _parse_function_decl(self, base_decl: c_ast.Node) -> c_ast.Node: + self._expect("LPAREN") + if self._accept("RPAREN"): + args = None + else: + args = ( + self._parse_parameter_type_list() + if self._starts_declaration() + else self._parse_identifier_list_opt() + ) + self._expect("RPAREN") + + func = c_ast.FuncDecl(args=args, type=None, coord=base_decl.coord) + + if self._peek_type() == "LBRACE": + if func.args is not None: + for param in func.args.params: + if isinstance(param, c_ast.EllipsisParam): + break + name = getattr(param, "name", None) + if name: + self._add_identifier(name, param.coord) + + return func + + # BNF: pointer : '*' type_qualifier_list? pointer? + def _parse_pointer(self) -> Optional[c_ast.Node]: + stars = [] + times_tok = self._accept("TIMES") + while times_tok: + quals = self._parse_type_qualifier_list() or [] + stars.append((quals, self._tok_coord(times_tok))) + times_tok = self._accept("TIMES") + + if not stars: + return None + + ptr = None + for quals, coord in stars: + ptr = c_ast.PtrDecl(quals=quals, type=ptr, coord=coord) + return ptr + + # BNF: parameter_type_list : parameter_list (',' ELLIPSIS)? + def _parse_parameter_type_list(self) -> c_ast.ParamList: + params = self._parse_parameter_list() + if self._peek_type() == "COMMA" and self._peek_type(2) == "ELLIPSIS": + self._advance() + ell_tok = self._advance() + params.params.append(c_ast.EllipsisParam(self._tok_coord(ell_tok))) + return params + + # BNF: parameter_list : parameter_declaration (',' parameter_declaration)* + def _parse_parameter_list(self) -> c_ast.ParamList: + first = self._parse_parameter_declaration() + params = c_ast.ParamList([first], first.coord) + while self._peek_type() == "COMMA" and self._peek_type(2) != "ELLIPSIS": + self._advance() + params.params.append(self._parse_parameter_declaration()) + return params + + # BNF: parameter_declaration : declaration_specifiers declarator? + # | declaration_specifiers abstract_declarator_opt + def _parse_parameter_declaration(self) -> c_ast.Node: + spec, _, spec_coord = self._parse_declaration_specifiers(allow_no_type=True) + + if not spec["type"]: + spec["type"] = [c_ast.IdentifierType(["int"], coord=spec_coord)] + + if self._starts_declarator(): + decl, is_named = self._parse_any_declarator( + allow_abstract=True, typeid_paren_as_abstract=True + ) + if is_named: + return self._build_declarations( + spec=spec, decls=[dict(decl=decl, init=None, bitsize=None)] + )[0] + return self._build_parameter_declaration(spec, decl, spec_coord) + + decl = self._parse_abstract_declarator_opt() + return self._build_parameter_declaration(spec, decl, spec_coord) + + def _build_parameter_declaration( + self, spec: "_DeclSpec", decl: Optional[c_ast.Node], spec_coord: Optional[Coord] + ) -> c_ast.Node: + if ( + len(spec["type"]) > 1 + and len(spec["type"][-1].names) == 1 + and self._is_type_in_scope(spec["type"][-1].names[0]) + ): + return self._build_declarations( + spec=spec, decls=[dict(decl=decl, init=None, bitsize=None)] + )[0] + + decl = c_ast.Typename( + name="", + quals=spec["qual"], + align=None, + type=decl or c_ast.TypeDecl(None, None, None, None), + coord=spec_coord, + ) + return self._fix_decl_name_type(decl, spec["type"]) + + # BNF: identifier_list_opt : identifier_list | empty + def _parse_identifier_list_opt(self) -> Optional[c_ast.Node]: + if self._peek_type() == "RPAREN": + return None + return self._parse_identifier_list() + + # BNF: identifier_list : identifier (',' identifier)* + def _parse_identifier_list(self) -> c_ast.Node: + first = self._parse_identifier() + params = c_ast.ParamList([first], first.coord) + while self._accept("COMMA"): + params.params.append(self._parse_identifier()) + return params + + # ------------------------------------------------------------------ + # Abstract declarators + # ------------------------------------------------------------------ + # BNF: type_name : specifier_qualifier_list abstract_declarator_opt + def _parse_type_name(self) -> c_ast.Typename: + spec = self._parse_specifier_qualifier_list() + decl = self._parse_abstract_declarator_opt() + + coord = None + if decl is not None: + coord = decl.coord + elif spec["type"]: + coord = spec["type"][0].coord + + typename = c_ast.Typename( + name="", + quals=spec["qual"][:], + align=None, + type=decl or c_ast.TypeDecl(None, None, None, None), + coord=coord, + ) + return cast(c_ast.Typename, self._fix_decl_name_type(typename, spec["type"])) + + # BNF: abstract_declarator_opt : pointer? direct_abstract_declarator? + def _parse_abstract_declarator_opt(self) -> Optional[c_ast.Node]: + if self._peek_type() == "TIMES": + ptr = self._parse_pointer() + if self._starts_direct_abstract_declarator(): + decl = self._parse_direct_abstract_declarator() + else: + decl = c_ast.TypeDecl(None, None, None, None) + assert ptr is not None + return self._type_modify_decl(decl, ptr) + + if self._starts_direct_abstract_declarator(): + return self._parse_direct_abstract_declarator() + + return None + + # BNF: direct_abstract_declarator : '(' parameter_type_list_opt ')' + # | '(' abstract_declarator ')' + # | '[' ... ']' + def _parse_direct_abstract_declarator(self) -> c_ast.Node: + lparen_tok = self._accept("LPAREN") + if lparen_tok: + if self._starts_declaration() or self._peek_type() == "RPAREN": + params = self._parse_parameter_type_list_opt() + self._expect("RPAREN") + decl = c_ast.FuncDecl( + args=params, + type=c_ast.TypeDecl(None, None, None, None), + coord=self._tok_coord(lparen_tok), + ) + else: + decl = self._parse_abstract_declarator_opt() + self._expect("RPAREN") + assert decl is not None + elif self._peek_type() == "LBRACKET": + decl = self._parse_abstract_array_base() + else: + self._parse_error("Invalid abstract declarator", self.clex.filename) + + return self._parse_decl_suffixes(decl) + + # BNF: parameter_type_list_opt : parameter_type_list | empty + def _parse_parameter_type_list_opt(self) -> Optional[c_ast.ParamList]: + if self._peek_type() == "RPAREN": + return None + return self._parse_parameter_type_list() + + # BNF: abstract_array_base : '[' array_specifiers? assignment_expression? ']' + def _parse_abstract_array_base(self) -> c_ast.Node: + return self._parse_array_decl_common( + base_type=c_ast.TypeDecl(None, None, None, None), coord=None + ) + + # ------------------------------------------------------------------ + # Statements + # ------------------------------------------------------------------ + # BNF: statement : labeled_statement | compound_statement + # | selection_statement | iteration_statement + # | jump_statement | expression_statement + # | static_assert | pppragma_directive + def _parse_statement(self) -> c_ast.Node | List[c_ast.Node]: + tok_type = self._peek_type() + match tok_type: + case "CASE" | "DEFAULT": + return self._parse_labeled_statement() + case "ID" if self._peek_type(2) == "COLON": + return self._parse_labeled_statement() + case "LBRACE": + return self._parse_compound_statement() + case "IF" | "SWITCH": + return self._parse_selection_statement() + case "WHILE" | "DO" | "FOR": + return self._parse_iteration_statement() + case "GOTO" | "BREAK" | "CONTINUE" | "RETURN": + return self._parse_jump_statement() + case "PPPRAGMA" | "_PRAGMA": + return self._parse_pppragma_directive() + case "_STATIC_ASSERT": + return self._parse_static_assert() + case _: + return self._parse_expression_statement() + + # BNF: pragmacomp_or_statement : pppragma_directive* statement + def _parse_pragmacomp_or_statement(self) -> c_ast.Node | List[c_ast.Node]: + if self._peek_type() in {"PPPRAGMA", "_PRAGMA"}: + pragmas = self._parse_pppragma_directive_list() + stmt = self._parse_statement() + return c_ast.Compound(block_items=pragmas + [stmt], coord=pragmas[0].coord) + return self._parse_statement() + + # BNF: block_item : declaration | statement + def _parse_block_item(self) -> c_ast.Node | List[c_ast.Node]: + if self._starts_declaration(): + return self._parse_declaration() + return self._parse_statement() + + # BNF: block_item_list : block_item+ + def _parse_block_item_list(self) -> List[c_ast.Node]: + items = [] + while self._peek_type() not in {"RBRACE", None}: + item = self._parse_block_item() + if isinstance(item, list): + if item == [None]: + continue + items.extend(item) + else: + items.append(item) + return items + + # BNF: compound_statement : '{' block_item_list? '}' + def _parse_compound_statement(self) -> c_ast.Node: + lbrace_tok = self._expect("LBRACE") + if self._accept("RBRACE"): + return c_ast.Compound(block_items=None, coord=self._tok_coord(lbrace_tok)) + block_items = self._parse_block_item_list() + self._expect("RBRACE") + return c_ast.Compound( + block_items=block_items, coord=self._tok_coord(lbrace_tok) + ) + + # BNF: labeled_statement : ID ':' statement + # | CASE constant_expression ':' statement + # | DEFAULT ':' statement + def _parse_labeled_statement(self) -> c_ast.Node: + tok_type = self._peek_type() + match tok_type: + case "ID": + name_tok = self._advance() + self._expect("COLON") + if self._starts_statement(): + stmt = self._parse_pragmacomp_or_statement() + else: + stmt = c_ast.EmptyStatement(self._tok_coord(name_tok)) + return c_ast.Label(name_tok.value, stmt, self._tok_coord(name_tok)) + case "CASE": + case_tok = self._advance() + expr = self._parse_constant_expression() + self._expect("COLON") + if self._starts_statement(): + stmt = self._parse_pragmacomp_or_statement() + else: + stmt = c_ast.EmptyStatement(self._tok_coord(case_tok)) + return c_ast.Case(expr, [stmt], self._tok_coord(case_tok)) + case "DEFAULT": + def_tok = self._advance() + self._expect("COLON") + if self._starts_statement(): + stmt = self._parse_pragmacomp_or_statement() + else: + stmt = c_ast.EmptyStatement(self._tok_coord(def_tok)) + return c_ast.Default([stmt], self._tok_coord(def_tok)) + case _: + self._parse_error("Invalid labeled statement", self.clex.filename) + + # BNF: selection_statement : IF '(' expression ')' statement (ELSE statement)? + # | SWITCH '(' expression ')' statement + def _parse_selection_statement(self) -> c_ast.Node: + tok = self._advance() + match tok.type: + case "IF": + self._expect("LPAREN") + cond = self._parse_expression() + self._expect("RPAREN") + then_stmt = self._parse_pragmacomp_or_statement() + if self._accept("ELSE"): + else_stmt = self._parse_pragmacomp_or_statement() + return c_ast.If(cond, then_stmt, else_stmt, self._tok_coord(tok)) + return c_ast.If(cond, then_stmt, None, self._tok_coord(tok)) + case "SWITCH": + self._expect("LPAREN") + expr = self._parse_expression() + self._expect("RPAREN") + stmt = self._parse_pragmacomp_or_statement() + return fix_switch_cases(c_ast.Switch(expr, stmt, self._tok_coord(tok))) + case _: + self._parse_error("Invalid selection statement", self._tok_coord(tok)) + + # BNF: iteration_statement : WHILE '(' expression ')' statement + # | DO statement WHILE '(' expression ')' ';' + # | FOR '(' (declaration | expression_opt) ';' + # expression_opt ';' expression_opt ')' statement + def _parse_iteration_statement(self) -> c_ast.Node: + tok = self._advance() + match tok.type: + case "WHILE": + self._expect("LPAREN") + cond = self._parse_expression() + self._expect("RPAREN") + stmt = self._parse_pragmacomp_or_statement() + return c_ast.While(cond, stmt, self._tok_coord(tok)) + case "DO": + stmt = self._parse_pragmacomp_or_statement() + self._expect("WHILE") + self._expect("LPAREN") + cond = self._parse_expression() + self._expect("RPAREN") + self._expect("SEMI") + return c_ast.DoWhile(cond, stmt, self._tok_coord(tok)) + case "FOR": + self._expect("LPAREN") + if self._starts_declaration(): + decls = self._parse_declaration() + init = c_ast.DeclList(decls, self._tok_coord(tok)) + cond = self._parse_expression_opt() + self._expect("SEMI") + next_expr = self._parse_expression_opt() + self._expect("RPAREN") + stmt = self._parse_pragmacomp_or_statement() + return c_ast.For(init, cond, next_expr, stmt, self._tok_coord(tok)) + + init = self._parse_expression_opt() + self._expect("SEMI") + cond = self._parse_expression_opt() + self._expect("SEMI") + next_expr = self._parse_expression_opt() + self._expect("RPAREN") + stmt = self._parse_pragmacomp_or_statement() + return c_ast.For(init, cond, next_expr, stmt, self._tok_coord(tok)) + case _: + self._parse_error("Invalid iteration statement", self._tok_coord(tok)) + + # BNF: jump_statement : GOTO ID ';' | BREAK ';' | CONTINUE ';' + # | RETURN expression? ';' + def _parse_jump_statement(self) -> c_ast.Node: + tok = self._advance() + match tok.type: + case "GOTO": + name_tok = self._expect("ID") + self._expect("SEMI") + return c_ast.Goto(name_tok.value, self._tok_coord(tok)) + case "BREAK": + self._expect("SEMI") + return c_ast.Break(self._tok_coord(tok)) + case "CONTINUE": + self._expect("SEMI") + return c_ast.Continue(self._tok_coord(tok)) + case "RETURN": + if self._accept("SEMI"): + return c_ast.Return(None, self._tok_coord(tok)) + expr = self._parse_expression() + self._expect("SEMI") + return c_ast.Return(expr, self._tok_coord(tok)) + case _: + self._parse_error("Invalid jump statement", self._tok_coord(tok)) + + # BNF: expression_statement : expression_opt ';' + def _parse_expression_statement(self) -> c_ast.Node: + expr = self._parse_expression_opt() + semi_tok = self._expect("SEMI") + if expr is None: + return c_ast.EmptyStatement(self._tok_coord(semi_tok)) + return expr + + # ------------------------------------------------------------------ + # Expressions + # ------------------------------------------------------------------ + # BNF: expression_opt : expression | empty + def _parse_expression_opt(self) -> Optional[c_ast.Node]: + if self._starts_expression(): + return self._parse_expression() + return None + + # BNF: expression : assignment_expression (',' assignment_expression)* + def _parse_expression(self) -> c_ast.Node: + expr = self._parse_assignment_expression() + if not self._accept("COMMA"): + return expr + exprs = [expr, self._parse_assignment_expression()] + while self._accept("COMMA"): + exprs.append(self._parse_assignment_expression()) + return c_ast.ExprList(exprs, expr.coord) + + # BNF: assignment_expression : conditional_expression + # | unary_expression assignment_op assignment_expression + def _parse_assignment_expression(self) -> c_ast.Node: + if self._peek_type() == "LPAREN" and self._peek_type(2) == "LBRACE": + self._advance() + comp = self._parse_compound_statement() + self._expect("RPAREN") + return comp + + expr = self._parse_conditional_expression() + if self._is_assignment_op(): + op = self._advance().value + rhs = self._parse_assignment_expression() + return c_ast.Assignment(op, expr, rhs, expr.coord) + return expr + + # BNF: conditional_expression : binary_expression + # | binary_expression '?' expression ':' conditional_expression + def _parse_conditional_expression(self) -> c_ast.Node: + expr = self._parse_binary_expression() + if self._accept("CONDOP"): + iftrue = self._parse_expression() + self._expect("COLON") + iffalse = self._parse_conditional_expression() + return c_ast.TernaryOp(expr, iftrue, iffalse, expr.coord) + return expr + + # BNF: binary_expression : cast_expression (binary_op cast_expression)* + def _parse_binary_expression( + self, min_prec: int = 0, lhs: Optional[c_ast.Node] = None + ) -> c_ast.Node: + if lhs is None: + lhs = self._parse_cast_expression() + + while True: + tok = self._peek() + if tok is None or tok.type not in _BINARY_PRECEDENCE: + break + prec = _BINARY_PRECEDENCE[tok.type] + if prec < min_prec: + break + + op = tok.value + self._advance() + rhs = self._parse_cast_expression() + + while True: + next_tok = self._peek() + if next_tok is None or next_tok.type not in _BINARY_PRECEDENCE: + break + next_prec = _BINARY_PRECEDENCE[next_tok.type] + if next_prec > prec: + rhs = self._parse_binary_expression(next_prec, rhs) + else: + break + + lhs = c_ast.BinaryOp(op, lhs, rhs, lhs.coord) + + return lhs + + # BNF: cast_expression : '(' type_name ')' cast_expression + # | unary_expression + def _parse_cast_expression(self) -> c_ast.Node: + result = self._try_parse_paren_type_name() + if result is not None: + typ, mark, lparen_tok = result + if self._peek_type() == "LBRACE": + # (type){...} is a compound literal, not a cast. Examples: + # (int){1} -> compound literal, handled in postfix + # (int) x -> cast, handled below + self._reset(mark) + else: + expr = self._parse_cast_expression() + return c_ast.Cast(typ, expr, self._tok_coord(lparen_tok)) + return self._parse_unary_expression() + + # BNF: unary_expression : postfix_expression + # | '++' unary_expression + # | '--' unary_expression + # | unary_op cast_expression + # | 'sizeof' unary_expression + # | 'sizeof' '(' type_name ')' + # | '_Alignof' '(' type_name ')' + def _parse_unary_expression(self) -> c_ast.Node: + tok_type = self._peek_type() + if tok_type in {"PLUSPLUS", "MINUSMINUS"}: + tok = self._advance() + expr = self._parse_unary_expression() + return c_ast.UnaryOp(tok.value, expr, expr.coord) + + if tok_type in {"AND", "TIMES", "PLUS", "MINUS", "NOT", "LNOT"}: + tok = self._advance() + expr = self._parse_cast_expression() + return c_ast.UnaryOp(tok.value, expr, expr.coord) + + if tok_type == "SIZEOF": + tok = self._advance() + result = self._try_parse_paren_type_name() + if result is not None: + typ, _, _ = result + return c_ast.UnaryOp(tok.value, typ, self._tok_coord(tok)) + expr = self._parse_unary_expression() + return c_ast.UnaryOp(tok.value, expr, self._tok_coord(tok)) + + if tok_type == "_ALIGNOF": + tok = self._advance() + self._expect("LPAREN") + typ = self._parse_type_name() + self._expect("RPAREN") + return c_ast.UnaryOp(tok.value, typ, self._tok_coord(tok)) + + return self._parse_postfix_expression() + + # BNF: postfix_expression : primary_expression postfix_suffix* + # | '(' type_name ')' '{' initializer_list ','? '}' + def _parse_postfix_expression(self) -> c_ast.Node: + result = self._try_parse_paren_type_name() + if result is not None: + typ, mark, _ = result + # Disambiguate between casts and compound literals: + # (int) x -> cast + # (int) {1} -> compound literal + if self._accept("LBRACE"): + init = self._parse_initializer_list() + self._accept("COMMA") + self._expect("RBRACE") + return c_ast.CompoundLiteral(typ, init) + else: + self._reset(mark) + + expr = self._parse_primary_expression() + while True: + if self._accept("LBRACKET"): + sub = self._parse_expression() + self._expect("RBRACKET") + expr = c_ast.ArrayRef(expr, sub, expr.coord) + continue + if self._accept("LPAREN"): + if self._peek_type() == "RPAREN": + self._advance() + args = None + else: + args = self._parse_argument_expression_list() + self._expect("RPAREN") + expr = c_ast.FuncCall(expr, args, expr.coord) + continue + if self._peek_type() in {"PERIOD", "ARROW"}: + op_tok = self._advance() + name_tok = self._advance() + if name_tok.type not in {"ID", "TYPEID"}: + self._parse_error( + "Invalid struct reference", self._tok_coord(name_tok) + ) + field = c_ast.ID(name_tok.value, self._tok_coord(name_tok)) + expr = c_ast.StructRef(expr, op_tok.value, field, expr.coord) + continue + if self._peek_type() in {"PLUSPLUS", "MINUSMINUS"}: + tok = self._advance() + expr = c_ast.UnaryOp("p" + tok.value, expr, expr.coord) + continue + break + return expr + + # BNF: primary_expression : ID | constant | string_literal + # | '(' expression ')' | offsetof + def _parse_primary_expression(self) -> c_ast.Node: + tok_type = self._peek_type() + if tok_type == "ID": + return self._parse_identifier() + if ( + tok_type in _INT_CONST + or tok_type in _FLOAT_CONST + or tok_type in _CHAR_CONST + ): + return self._parse_constant() + if tok_type in _STRING_LITERAL: + return self._parse_unified_string_literal() + if tok_type in _WSTR_LITERAL: + return self._parse_unified_wstring_literal() + if tok_type == "LPAREN": + self._advance() + expr = self._parse_expression() + self._expect("RPAREN") + return expr + if tok_type == "OFFSETOF": + off_tok = self._advance() + self._expect("LPAREN") + typ = self._parse_type_name() + self._expect("COMMA") + designator = self._parse_offsetof_member_designator() + self._expect("RPAREN") + coord = self._tok_coord(off_tok) + return c_ast.FuncCall( + c_ast.ID(off_tok.value, coord), + c_ast.ExprList([typ, designator], coord), + coord, + ) + + self._parse_error("Invalid expression", self.clex.filename) + + # BNF: offsetof_member_designator : identifier_or_typeid + # ('.' identifier_or_typeid | '[' expression ']')* + def _parse_offsetof_member_designator(self) -> c_ast.Node: + node = self._parse_identifier_or_typeid() + while True: + if self._accept("PERIOD"): + field = self._parse_identifier_or_typeid() + node = c_ast.StructRef(node, ".", field, node.coord) + continue + if self._accept("LBRACKET"): + expr = self._parse_expression() + self._expect("RBRACKET") + node = c_ast.ArrayRef(node, expr, node.coord) + continue + break + return node + + # BNF: argument_expression_list : assignment_expression (',' assignment_expression)* + def _parse_argument_expression_list(self) -> c_ast.Node: + expr = self._parse_assignment_expression() + exprs = [expr] + while self._accept("COMMA"): + exprs.append(self._parse_assignment_expression()) + return c_ast.ExprList(exprs, expr.coord) + + # BNF: constant_expression : conditional_expression + def _parse_constant_expression(self) -> c_ast.Node: + return self._parse_conditional_expression() + + # ------------------------------------------------------------------ + # Terminals + # ------------------------------------------------------------------ + # BNF: identifier : ID + def _parse_identifier(self) -> c_ast.Node: + tok = self._expect("ID") + return c_ast.ID(tok.value, self._tok_coord(tok)) + + # BNF: identifier_or_typeid : ID | TYPEID + def _parse_identifier_or_typeid(self) -> c_ast.Node: + tok = self._advance() + if tok.type not in {"ID", "TYPEID"}: + self._parse_error("Expected identifier", self._tok_coord(tok)) + return c_ast.ID(tok.value, self._tok_coord(tok)) + + # BNF: constant : INT_CONST | FLOAT_CONST | CHAR_CONST + def _parse_constant(self) -> c_ast.Node: + tok = self._advance() + if tok.type in _INT_CONST: + u_count = 0 + l_count = 0 + for ch in tok.value[-3:]: + if ch in ("l", "L"): + l_count += 1 + elif ch in ("u", "U"): + u_count += 1 + if u_count > 1: + raise ValueError("Constant cannot have more than one u/U suffix.") + if l_count > 2: + raise ValueError("Constant cannot have more than two l/L suffix.") + prefix = "unsigned " * u_count + "long " * l_count + return c_ast.Constant(prefix + "int", tok.value, self._tok_coord(tok)) + + if tok.type in _FLOAT_CONST: + if tok.value[-1] in ("f", "F"): + t = "float" + elif tok.value[-1] in ("l", "L"): + t = "long double" + else: + t = "double" + return c_ast.Constant(t, tok.value, self._tok_coord(tok)) + + if tok.type in _CHAR_CONST: + return c_ast.Constant("char", tok.value, self._tok_coord(tok)) + + self._parse_error("Invalid constant", self._tok_coord(tok)) + + # BNF: unified_string_literal : STRING_LITERAL+ + def _parse_unified_string_literal(self) -> c_ast.Node: + tok = self._expect("STRING_LITERAL") + node = c_ast.Constant("string", tok.value, self._tok_coord(tok)) + while self._peek_type() == "STRING_LITERAL": + tok2 = self._advance() + node.value = node.value[:-1] + tok2.value[1:] + return node + + # BNF: unified_wstring_literal : WSTRING_LITERAL+ + def _parse_unified_wstring_literal(self) -> c_ast.Node: + tok = self._advance() + if tok.type not in _WSTR_LITERAL: + self._parse_error("Invalid string literal", self._tok_coord(tok)) + node = c_ast.Constant("string", tok.value, self._tok_coord(tok)) + while self._peek_type() in _WSTR_LITERAL: + tok2 = self._advance() + node.value = node.value.rstrip()[:-1] + tok2.value[2:] + return node + + # ------------------------------------------------------------------ + # Initializers + # ------------------------------------------------------------------ + # BNF: initializer : assignment_expression + # | '{' initializer_list ','? '}' + # | '{' '}' + def _parse_initializer(self) -> c_ast.Node: + lbrace_tok = self._accept("LBRACE") + if lbrace_tok: + if self._accept("RBRACE"): + return c_ast.InitList([], self._tok_coord(lbrace_tok)) + init_list = self._parse_initializer_list() + self._accept("COMMA") + self._expect("RBRACE") + return init_list + + return self._parse_assignment_expression() + + # BNF: initializer_list : initializer_item (',' initializer_item)* ','? + def _parse_initializer_list(self) -> c_ast.Node: + items = [self._parse_initializer_item()] + while self._accept("COMMA"): + if self._peek_type() == "RBRACE": + break + items.append(self._parse_initializer_item()) + return c_ast.InitList(items, items[0].coord) + + # BNF: initializer_item : designation? initializer + def _parse_initializer_item(self) -> c_ast.Node: + designation = None + if self._peek_type() in {"LBRACKET", "PERIOD"}: + designation = self._parse_designation() + init = self._parse_initializer() + if designation is not None: + return c_ast.NamedInitializer(designation, init) + return init + + # BNF: designation : designator_list '=' + def _parse_designation(self) -> List[c_ast.Node]: + designators = self._parse_designator_list() + self._expect("EQUALS") + return designators + + # BNF: designator_list : designator+ + def _parse_designator_list(self) -> List[c_ast.Node]: + designators = [] + while self._peek_type() in {"LBRACKET", "PERIOD"}: + designators.append(self._parse_designator()) + return designators + + # BNF: designator : '[' constant_expression ']' + # | '.' identifier_or_typeid + def _parse_designator(self) -> c_ast.Node: + if self._accept("LBRACKET"): + expr = self._parse_constant_expression() + self._expect("RBRACKET") + return expr + if self._accept("PERIOD"): + return self._parse_identifier_or_typeid() + self._parse_error("Invalid designator", self.clex.filename) + + # ------------------------------------------------------------------ + # Preprocessor-like directives + # ------------------------------------------------------------------ + # BNF: pp_directive : '#' ... (unsupported) + def _parse_pp_directive(self) -> NoReturn: + tok = self._expect("PPHASH") + self._parse_error("Directives not supported yet", self._tok_coord(tok)) + + # BNF: pppragma_directive : PPPRAGMA PPPRAGMASTR? + # | _PRAGMA '(' string_literal ')' + def _parse_pppragma_directive(self) -> c_ast.Node: + if self._peek_type() == "PPPRAGMA": + tok = self._advance() + if self._peek_type() == "PPPRAGMASTR": + str_tok = self._advance() + return c_ast.Pragma(str_tok.value, self._tok_coord(str_tok)) + return c_ast.Pragma("", self._tok_coord(tok)) + + if self._peek_type() == "_PRAGMA": + tok = self._advance() + lparen = self._expect("LPAREN") + literal = self._parse_unified_string_literal() + self._expect("RPAREN") + return c_ast.Pragma(literal, self._tok_coord(lparen)) + + self._parse_error("Invalid pragma", self.clex.filename) + + # BNF: pppragma_directive_list : pppragma_directive+ + def _parse_pppragma_directive_list(self) -> List[c_ast.Node]: + pragmas = [] + while self._peek_type() in {"PPPRAGMA", "_PRAGMA"}: + pragmas.append(self._parse_pppragma_directive()) + return pragmas + + # BNF: static_assert : _STATIC_ASSERT '(' constant_expression (',' string_literal)? ')' + def _parse_static_assert(self) -> List[c_ast.Node]: + tok = self._expect("_STATIC_ASSERT") + self._expect("LPAREN") + cond = self._parse_constant_expression() + msg = None + if self._accept("COMMA"): + msg = self._parse_unified_string_literal() + self._expect("RPAREN") + return [c_ast.StaticAssert(cond, msg, self._tok_coord(tok))] + + +_ASSIGNMENT_OPS = { + "EQUALS", + "XOREQUAL", + "TIMESEQUAL", + "DIVEQUAL", + "MODEQUAL", + "PLUSEQUAL", + "MINUSEQUAL", + "LSHIFTEQUAL", + "RSHIFTEQUAL", + "ANDEQUAL", + "OREQUAL", +} + +# Precedence of operators (lower number = weather binding) +# If this changes, c_generator.CGenerator.precedence_map needs to change as +# well +_BINARY_PRECEDENCE = { + "LOR": 0, + "LAND": 1, + "OR": 2, + "XOR": 3, + "AND": 4, + "EQ": 5, + "NE": 5, + "GT": 6, + "GE": 6, + "LT": 6, + "LE": 6, + "RSHIFT": 7, + "LSHIFT": 7, + "PLUS": 8, + "MINUS": 8, + "TIMES": 9, + "DIVIDE": 9, + "MOD": 9, +} + +_STORAGE_CLASS = {"AUTO", "REGISTER", "STATIC", "EXTERN", "TYPEDEF", "_THREAD_LOCAL"} + +_FUNCTION_SPEC = {"INLINE", "_NORETURN"} + +_TYPE_QUALIFIER = {"CONST", "RESTRICT", "VOLATILE", "_ATOMIC"} + +_TYPE_SPEC_SIMPLE = { + "VOID", + "_BOOL", + "CHAR", + "SHORT", + "INT", + "LONG", + "FLOAT", + "DOUBLE", + "_COMPLEX", + "SIGNED", + "UNSIGNED", + "__INT128", +} + +_DECL_START = ( + _STORAGE_CLASS + | _FUNCTION_SPEC + | _TYPE_QUALIFIER + | _TYPE_SPEC_SIMPLE + | {"TYPEID", "STRUCT", "UNION", "ENUM", "_ALIGNAS", "_ATOMIC"} +) + +_EXPR_START = { + "ID", + "LPAREN", + "PLUSPLUS", + "MINUSMINUS", + "PLUS", + "MINUS", + "TIMES", + "AND", + "NOT", + "LNOT", + "SIZEOF", + "_ALIGNOF", + "OFFSETOF", +} + +_INT_CONST = { + "INT_CONST_DEC", + "INT_CONST_OCT", + "INT_CONST_HEX", + "INT_CONST_BIN", + "INT_CONST_CHAR", +} + +_FLOAT_CONST = {"FLOAT_CONST", "HEX_FLOAT_CONST"} + +_CHAR_CONST = { + "CHAR_CONST", + "WCHAR_CONST", + "U8CHAR_CONST", + "U16CHAR_CONST", + "U32CHAR_CONST", +} + +_STRING_LITERAL = {"STRING_LITERAL"} + +_WSTR_LITERAL = { + "WSTRING_LITERAL", + "U8STRING_LITERAL", + "U16STRING_LITERAL", + "U32STRING_LITERAL", +} + +_STARTS_EXPRESSION = ( + _EXPR_START + | _INT_CONST + | _FLOAT_CONST + | _CHAR_CONST + | _STRING_LITERAL + | _WSTR_LITERAL +) + +_STARTS_STATEMENT = { + "LBRACE", + "IF", + "SWITCH", + "WHILE", + "DO", + "FOR", + "GOTO", + "BREAK", + "CONTINUE", + "RETURN", + "CASE", + "DEFAULT", + "PPPRAGMA", + "_PRAGMA", + "_STATIC_ASSERT", + "SEMI", +} + + +class _TokenStream: + """Wraps a lexer to provide convenient, buffered access to the underlying + token stream. The lexer is expected to be initialized with the input + string already. + """ + + def __init__(self, lexer: CLexer) -> None: + self._lexer = lexer + self._buffer: List[Optional[_Token]] = [] + self._index = 0 + + def peek(self, k: int = 1) -> Optional[_Token]: + """Peek at the k-th next token in the stream, without consuming it. + + Examples: + k=1 returns the immediate next token. + k=2 returns the token after that. + """ + if k <= 0: + return None + self._fill(k) + return self._buffer[self._index + k - 1] + + def next(self) -> Optional[_Token]: + """Consume a single token and return it.""" + self._fill(1) + tok = self._buffer[self._index] + self._index += 1 + return tok + + # The 'mark' and 'reset' methods are useful for speculative parsing with + # backtracking; when the parser needs to examine a sequence of tokens + # and potentially decide to try a different path on the same sequence, it + # can call 'mark' to obtain the current token position, and if the first + # path fails restore the position with `reset(pos)`. + def mark(self) -> int: + return self._index + + def reset(self, mark: int) -> None: + self._index = mark + + def _fill(self, n: int) -> None: + while len(self._buffer) < self._index + n: + tok = self._lexer.token() + self._buffer.append(tok) + if tok is None: + break + + +# Declaration specifiers are represented by a dictionary with entries: +# - qual: a list of type qualifiers +# - storage: a list of storage class specifiers +# - type: a list of type specifiers +# - function: a list of function specifiers +# - alignment: a list of alignment specifiers +class _DeclSpec(TypedDict): + qual: List[Any] + storage: List[Any] + type: List[Any] + function: List[Any] + alignment: List[Any] + + +_DeclSpecKind = Literal["qual", "storage", "type", "function", "alignment"] + + +class _DeclInfo(TypedDict): + # Declarator payloads used by declaration/initializer parsing: + # - decl: the declarator node (may be None for abstract/implicit cases) + # - init: optional initializer expression + # - bitsize: optional bit-field width expression (for struct declarators) + decl: Optional[c_ast.Node] + init: Optional[c_ast.Node] + bitsize: Optional[c_ast.Node] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA new file mode 100644 index 0000000..10d0af0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA @@ -0,0 +1,1307 @@ +Metadata-Version: 2.5 +Name: pydantic +Version: 2.13.5 +Summary: Data validation using Python type hints +Project-URL: Homepage, https://github.com/pydantic/pydantic +Project-URL: Documentation, https://docs.pydantic.dev +Project-URL: Funding, https://github.com/sponsors/samuelcolvin +Project-URL: Source, https://github.com/pydantic/pydantic +Project-URL: Changelog, https://docs.pydantic.dev/latest/changelog/ +Author-email: Samuel Colvin , Eric Jolibois , Hasan Ramezani , Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Terrence Dorsey , David Montague , Serge Matveenko , Marcelo Trylesinski , Sydney Runkle , David Hewitt , Alex Hall , Victorien Plot +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: Hypothesis +Classifier: Framework :: Pydantic +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.9 +Requires-Dist: annotated-types>=0.6.0 +Requires-Dist: pydantic-core==2.46.5 +Requires-Dist: typing-extensions>=4.14.1 +Requires-Dist: typing-inspection>=0.4.2 +Provides-Extra: email +Requires-Dist: email-validator>=2.0.0; extra == 'email' +Provides-Extra: timezone +Requires-Dist: tzdata; (python_version >= '3.9' and platform_system == 'Windows') and extra == 'timezone' +Description-Content-Type: text/markdown + +# Pydantic Validation + +[![CI](https://img.shields.io/github/actions/workflow/status/pydantic/pydantic/ci.yml?branch=main&logo=github&label=CI)](https://github.com/pydantic/pydantic/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic) +[![pypi](https://img.shields.io/pypi/v/pydantic.svg)](https://pypi.python.org/pypi/pydantic) +[![CondaForge](https://img.shields.io/conda/v/conda-forge/pydantic.svg)](https://anaconda.org/conda-forge/pydantic) +[![downloads](https://static.pepy.tech/badge/pydantic/month)](https://pepy.tech/project/pydantic) +[![versions](https://img.shields.io/pypi/pyversions/pydantic.svg)](https://github.com/pydantic/pydantic) +[![license](https://img.shields.io/github/license/pydantic/pydantic.svg)](https://github.com/pydantic/pydantic/blob/main/LICENSE) +[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://docs.pydantic.dev/latest/contributing/#badges) +[![llms.txt](https://img.shields.io/badge/llms.txt-green)](https://docs.pydantic.dev/latest/llms.txt) + +Data validation using Python type hints. + +Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. +Define how data should be in pure, canonical Python 3.9+; validate it with Pydantic. + +## Pydantic Logfire :fire: + +We've launched Pydantic Logfire to help you monitor your applications. +[Learn more](https://pydantic.dev/logfire/?utm_source=pydantic_validation) + +## Pydantic V1.10 vs. V2 + +Pydantic V2 is a ground-up rewrite that offers many new features, performance improvements, and some breaking changes compared to Pydantic V1. + +If you're using Pydantic V1 you may want to look at the +[pydantic V1.10 Documentation](https://docs.pydantic.dev/) or, +[`1.10.X-fixes` git branch](https://github.com/pydantic/pydantic/tree/1.10.X-fixes). Pydantic V2 also ships with the latest version of Pydantic V1 built in so that you can incrementally upgrade your code base and projects: `from pydantic import v1 as pydantic_v1`. + +## Help + +See [documentation](https://docs.pydantic.dev/) for more details. + +## Installation + +Install using `pip install -U pydantic` or `conda install pydantic -c conda-forge`. +For more installation options to make Pydantic even faster, +see the [Install](https://docs.pydantic.dev/install/) section in the documentation. + +## A Simple Example + +```python +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + +class User(BaseModel): + id: int + name: str = 'John Doe' + signup_ts: Optional[datetime] = None + friends: list[int] = [] + +external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']} +user = User(**external_data) +print(user) +#> User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] +print(user.id) +#> 123 +``` + +## Contributing + +For guidance on setting up a development environment and how to make a +contribution to Pydantic, see +[Contributing to Pydantic](https://docs.pydantic.dev/contributing/). + +## Reporting a Security Vulnerability + +See our [security policy](https://github.com/pydantic/pydantic/security/policy). + +## Changelog + + + + + +## v2.13.5 (2026-08-28) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.5) + +### What's Changed + +#### Fixes + +* Allow reuse of validators when plugins are set by [@Viicos](https://github.com/Viicos) in [#13535](https://github.com/pydantic/pydantic/pull/13535) +* Fix missing GC traversal on some `pydantic-core` struct fields by [@Viicos](https://github.com/Viicos) in [#13624](https://github.com/pydantic/pydantic/pull/13624) +* Fix missing GC traversal in `pydantic-core` for `GeneralFieldsSerializer` by [@Viicos](https://github.com/Viicos) in [#13629](https://github.com/pydantic/pydantic/pull/13629) +* Count validated model fields once in smart unions by [@tamird](https://github.com/tamird) in [#13731](https://github.com/pydantic/pydantic/pull/13731) + +## v2.13.4 (2026-05-06) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.4) + +### What's Changed + +#### Packaging + +* Bump libc from 0.2.155 to 0.2.185 by [@Viicos](https://github.com/Viicos) in [#13109](https://github.com/pydantic/pydantic/pull/13109) +* Adapt `pydantic-core` linker flags on macOS by [@washingtoneg](https://github.com/washingtoneg) and [@Viicos](https://github.com/Viicos) in [#13147](https://github.com/pydantic/pydantic/pull/13147) + +#### Fixes + +* Preserve `RootModel` core metadata by [@Viicos](https://github.com/Viicos) in [#13129](https://github.com/pydantic/pydantic/pull/13129) + +## v2.13.3 (2026-04-20) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.3) + +### What's Changed + +#### Fixes + +* Handle `AttributeError` subclasses with `from_attributes` by [@Viicos](https://github.com/Viicos) in [#13096](https://github.com/pydantic/pydantic/pull/13096) + +## v2.13.2 (2026-04-17) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.2) + +### What's Changed + +#### Fixes + +* Fix `ValidationInfo.field_name` missing with `model_validate_json()` by [@Viicos](https://github.com/Viicos) in [#13084](https://github.com/pydantic/pydantic/pull/13084) + +## v2.13.1 (2026-04-15) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.1) + +### What's Changed + +#### Fixes + +* Fix `ValidationInfo.data` missing with `model_validate_json()` by [@davidhewitt](https://github.com/davidhewitt) in [#13079](https://github.com/pydantic/pydantic/pull/13079) + +## v2.13.0 (2026-04-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.0) + +The highlights of the v2.13 release are available in the [blog post](https://pydantic.dev/articles/pydantic-v2-13-release). +Several minor changes (considered non-breaking changes according to our [versioning policy](https://pydantic.dev/docs/validation/2.13/get-started/version-policy/#pydantic-v2)) +are also included in this release. Make sure to look into them before upgrading. + +This release contains the updated `pydantic.v1` namespace, matching version 1.10.26 which includes support for Python 3.14. + +### What's Changed + +See the beta releases for all changes sinces 2.12. + +#### New Features + +* Allow default factories of private attributes to take validated model data by [@Viicos](https://github.com/Viicos) in [#13013](https://github.com/pydantic/pydantic/pull/13013) + +#### Changes + +* Warn when serializing fixed length tuples with too few items by [@arvindsaripalli](https://github.com/arvindsaripalli) in [#13016](https://github.com/pydantic/pydantic/pull/13016) + +#### Fixes + +* Change type of `Any` when synthesizing `_build_sources` for `BaseSettings.__init__()` signature in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#13049](https://github.com/pydantic/pydantic/pull/13049) +* Fix model equality when using runtime `extra` configuration by [@Viicos](https://github.com/Viicos) in [#13062](https://github.com/pydantic/pydantic/pull/13062) + +#### Packaging + +* Add zizmor for GitHub Actions workflow linting by [@Viicos](https://github.com/Viicos) in [#13039](https://github.com/pydantic/pydantic/pull/13039) +* Update jiter to v0.14.0 to fix a segmentation fault on musl Linux by [@Viicos](https://github.com/Viicos) in [#13064](https://github.com/pydantic/pydantic/pull/13064) + +### New Contributors + +* [@arvindsaripalli](https://github.com/arvindsaripalli) made their first contribution in [#13016](https://github.com/pydantic/pydantic/pull/13016) + +## v2.13.0b3 (2026-03-31) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.0b3) + +### What's Changed + +#### New Features + +* Add `ascii_only` option to `StringConstraints` by [@ai-man-codes](https://github.com/ai-man-codes) in [#12907](https://github.com/pydantic/pydantic/pull/12907) +* Support `exclude_if` in computed fields by [@andresliszt](https://github.com/andresliszt) in [#12748](https://github.com/pydantic/pydantic/pull/12748) +* Push down constraints in unions involving `MISSING` sentinel by [@Viicos](https://github.com/Viicos) in [#12908](https://github.com/pydantic/pydantic/pull/12908) + +#### Changes + +* Track extra fields set after init in `model_fields_set` by [@navalprakhar](https://github.com/navalprakhar) in [#12817](https://github.com/pydantic/pydantic/pull/12817) +* Do not include annotations that are not part of named tuple fields by [@galuszkak](https://github.com/galuszkak) in [#12951](https://github.com/pydantic/pydantic/pull/12951) +* No longer fall back to trying all union members when the variant selected by discriminator fails to serialize by [@navalprakhar](https://github.com/navalprakhar) in [#12825](https://github.com/pydantic/pydantic/pull/12825) + +#### Fixes + +* Support discriminator metadata outside union type alias by [@Viicos](https://github.com/Viicos) in [#12785](https://github.com/pydantic/pydantic/pull/12785) +* Respect `extras_schema` when only `extra_fields_behavior` is set on the config in JSON Schema generation for typed dictionaries by [@Viicos](https://github.com/Viicos) in [#12810](https://github.com/pydantic/pydantic/pull/12810) +* Ensure `__pydantic_private__` is set in `model_construct()` with user-defined `model_post_init()` by [@nightcityblade](https://github.com/nightcityblade) in [#12816](https://github.com/pydantic/pydantic/pull/12816) +* Handle all schema generation errors in `InstanceOf` by [@Viicos](https://github.com/Viicos) in [#12705](https://github.com/pydantic/pydantic/pull/12705) +* Allow dynamic models created with `create_model()` to be used as annotations in the Mypy plugin by [@Br1an67](https://github.com/Br1an67) in [#12879](https://github.com/pydantic/pydantic/pull/12879) +* Check for `PlaceholderNode` in Mypy plugin by [@Viicos](https://github.com/Viicos) in [#12929](https://github.com/pydantic/pydantic/pull/12929) +* Try other branches in smart union in case of omit errors by [@mikeedjones](https://github.com/mikeedjones) in [#12758](https://github.com/pydantic/pydantic/pull/12758) +* Patch unset attributes with `MISSING` during model serialization with `exclude_unset` by [@davidhewitt](https://github.com/davidhewitt) in [#12905](https://github.com/pydantic/pydantic/pull/12905) +* Ensure custom `__init__()` is called when using `model_validate_strings()` by [@siewcapital](https://github.com/siewcapital) in [#12897](https://github.com/pydantic/pydantic/pull/12897) + +#### Packaging + +* Add riscv64 build target for manylinux by [@boosterl](https://github.com/boosterl) in [#12723](https://github.com/pydantic/pydantic/pull/12723) + +### New Contributors + +* [@kelsonbrito50](https://github.com/kelsonbrito50) made their first contribution in [#12860](https://github.com/pydantic/pydantic/pull/12860) +* [@boosterl](https://github.com/boosterl) made their first contribution in [#12723](https://github.com/pydantic/pydantic/pull/12723) +* [@adityagiri3600](https://github.com/adityagiri3600) made their first contribution in [#12868](https://github.com/pydantic/pydantic/pull/12868) +* [@navalprakhar](https://github.com/navalprakhar) made their first contribution in [#12817](https://github.com/pydantic/pydantic/pull/12817) +* [@Br1an67](https://github.com/Br1an67) made their first contribution in [#12879](https://github.com/pydantic/pydantic/pull/12879) +* [@rmorshea](https://github.com/rmorshea) made their first contribution in [#12910](https://github.com/pydantic/pydantic/pull/12910) +* [@N3XT3R1337](https://github.com/N3XT3R1337) made their first contribution in [#12922](https://github.com/pydantic/pydantic/pull/12922) +* [@ai-man-codes](https://github.com/ai-man-codes) made their first contribution in [#12907](https://github.com/pydantic/pydantic/pull/12907) +* [@Yume05-dev](https://github.com/Yume05-dev) made their first contribution in [#12953](https://github.com/pydantic/pydantic/pull/12953) +* [@galuszkak](https://github.com/galuszkak) made their first contribution in [#12951](https://github.com/pydantic/pydantic/pull/12951) +* [@siewcapital](https://github.com/siewcapital) made their first contribution in [#12897](https://github.com/pydantic/pydantic/pull/12897) + +## v2.13.0b2 (2026-02-24) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.0b2) + +### What's Changed + +#### Fixes + +* Fix backported V1 namespace by [@Viicos](https://github.com/Viicos) in [#12855](https://github.com/pydantic/pydantic/pull/12855) +* Allow any type form to be used in `validate_as()` by [@bledden](https://github.com/bledden) in [#12846](https://github.com/pydantic/pydantic/pull/12846) +* Fix walrus operator precedence in `UrlConstraints.__get_pydantic_core_schema__()` by [@bysiber](https://github.com/bysiber) in [#12826](https://github.com/pydantic/pydantic/pull/12826) + +### New Contributors + +* [@bledden](https://github.com/bledden) made their first contribution in [#12846](https://github.com/pydantic/pydantic/pull/12846) +* [@bysiber](https://github.com/bysiber) made their first contribution in [#12827](https://github.com/pydantic/pydantic/pull/12827) + +## v2.13.0b1 (2026-02-23) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.13.0b1) + +This is the first beta release of the 2.13 version, mainly providing bug fixes and performance improvements +for validation and serialization. + +Notable changes include: + +* Add a new `polymorphic_serialization` option, solving issues with `serialize_as_any` introduced in 2.12. +* Latest V1.10.26 release under the `pydantic.v1` namespace. This version includes support for Python 3.14. +* The [`pydantic-core`](https://github.com/pydantic/pydantic-core/) repository was merged inside the main `pydantic` one. + +### What's Changed + +#### New Features + +* Add `polymorphic_serialization` option by [@davidhewitt](https://github.com/davidhewitt) in [#12518](https://github.com/pydantic/pydantic/pull/12518) +* Support Root models with `Literal` root types as discriminator field types by [@YassinNouh21](https://github.com/YassinNouh21) in [#12680](https://github.com/pydantic/pydantic/pull/12680) + +#### Changes + +* Migrate `pydantic-core` CI by [@Viicos](https://github.com/Viicos) in [#12752](https://github.com/pydantic/pydantic/pull/12752) +* Import `pydantic-core` into pydantic by [@davidhewitt](https://github.com/davidhewitt) in [#12481](https://github.com/pydantic/pydantic/pull/12481) +* Backport V1 changes up to v1.10.26 by [@Viicos](https://github.com/Viicos) in [#12663](https://github.com/pydantic/pydantic/pull/12663) +* Use the `complex()` constructor unconditionally when validating `complex` Python data by [@tanmaymunjal](https://github.com/tanmaymunjal) in [#12498](https://github.com/pydantic/pydantic/pull/12498) +* Add support for three-tuple input for `Decimal` by [@tanmaymunjal](https://github.com/tanmaymunjal) in [#12500](https://github.com/pydantic/pydantic/pull/12500) +* Align `@field_serializer` logic with `@field_validator` by [@Viicos](https://github.com/Viicos) in [#12577](https://github.com/pydantic/pydantic/pull/12577) +* Make `PydanticUserError` a `RuntimeError` instead of a `TypeError` by [@poliakovva](https://github.com/poliakovva) in [#12579](https://github.com/pydantic/pydantic/pull/12579) +* Remove redundant serialization attempts in nested unions by [@davidhewitt](https://github.com/davidhewitt) in [#12604](https://github.com/pydantic/pydantic/pull/12604) +* Copy `root` value when making root model shallow copies by [@YassinNouh21](https://github.com/YassinNouh21) in [#12679](https://github.com/pydantic/pydantic/pull/12679) +* Ensure deterministic JSON schema defaults by sorting sets by [@drshvik](https://github.com/drshvik) in [#12760](https://github.com/pydantic/pydantic/pull/12760) + +#### Performance + +* Refactor `DecoratorInfos.build()` implementation by [@Viicos](https://github.com/Viicos) in [#12536](https://github.com/pydantic/pydantic/pull/12536) +* Cache compiled regex in `pydantic-core` by [@Viicos](https://github.com/Viicos) in [#12549](https://github.com/pydantic/pydantic/pull/12549) +* Optimize creation of `Literal` validators by [@davidhewitt](https://github.com/davidhewitt) in [#12569](https://github.com/pydantic/pydantic/pull/12569) +* Optimize implementation of `LookupKey` by [@davidhewitt](https://github.com/davidhewitt) in [#12571](https://github.com/pydantic/pydantic/pull/12571) +* Use python strings for field names by [@davidhewitt](https://github.com/davidhewitt) in [#12631](https://github.com/pydantic/pydantic/pull/12631) +* Optimize datetime formatting code by [@davidhewitt](https://github.com/davidhewitt) in [#12626](https://github.com/pydantic/pydantic/pull/12626) +* Validate JSON model data by iteration by [@davidhewitt](https://github.com/davidhewitt) in [#12550](https://github.com/pydantic/pydantic/pull/12550) +* Optimize annotations evaluation of Pydantic models by [@Viicos](https://github.com/Viicos) in [#12681](https://github.com/pydantic/pydantic/pull/12681) +* Optimize `FieldInfo._copy()` by [@Viicos](https://github.com/Viicos) in [#12727](https://github.com/pydantic/pydantic/pull/12727) + +#### Fixes + +* Fix `FieldInfo` rebuilding when parameterizing generic models with an `Annotated` type by [@Viicos](https://github.com/Viicos) in [#12463](https://github.com/pydantic/pydantic/pull/12463) +* Fix nested model schema deduplication in JSON schema generation by [@marwan-alloreview](https://github.com/marwan-alloreview) in [#12494](https://github.com/pydantic/pydantic/pull/12494) +* Fix `InitVar` being ignored when using with the `pydantic.Field()` function by [@Viicos](https://github.com/Viicos) in [#12495](https://github.com/pydantic/pydantic/pull/12495) +* Fix support for enums with `NamedTuple` as values by [@Viicos](https://github.com/Viicos) in [#12506](https://github.com/pydantic/pydantic/pull/12506) +* Do not delete mock validator/serializer in `rebuild_dataclass()` by [@Viicos](https://github.com/Viicos) in [#12513](https://github.com/pydantic/pydantic/pull/12513) +* Require test suite to pass with free threading, switch back to global generic types cache by [@davidhewitt](https://github.com/davidhewitt) in [#12537](https://github.com/pydantic/pydantic/pull/12537) +* Refactor `__pydantic_extra__` annotation handling by [@Viicos](https://github.com/Viicos) in [#12563](https://github.com/pydantic/pydantic/pull/12563) +* Do not add claim of UUID "safety" provision by [@davidhewitt](https://github.com/davidhewitt) in [#12567](https://github.com/pydantic/pydantic/pull/12567) +* Use Python hash to perform lookup in tagged union serializer by [@davidhewitt](https://github.com/davidhewitt) in [#12594](https://github.com/pydantic/pydantic/pull/12594) +* Do not emit serialization warning `MISSING` sentinel is present in a nested model by [@Viicos](https://github.com/Viicos) in [#12635](https://github.com/pydantic/pydantic/pull/12635) +* Do not eagerly evaluate annotations in signature logic by [@Viicos](https://github.com/Viicos) in [#12660](https://github.com/pydantic/pydantic/pull/12660) +* Fix serialization of typed dict unions when `exclude_none` is set by [@davidhewitt](https://github.com/davidhewitt) in [#12677](https://github.com/pydantic/pydantic/pull/12677) +* Do not reuse prebuilt serializers/validators on rebuilds by [@lmmx](https://github.com/lmmx) in [#12689](https://github.com/pydantic/pydantic/pull/12689) +* Fix type annotation of `field_definitions` in `create_model()` by [@lehmann-hqs](https://github.com/lehmann-hqs) in [#12734](https://github.com/pydantic/pydantic/pull/12734) +* Fix incorrect dataclass constructor signature when overriding class `kw_only` with `Field()` by [@jfadia](https://github.com/jfadia) in [#12741](https://github.com/pydantic/pydantic/pull/12741) +* Use `typing.Union` when replacing types under Python 3.14 by [@Viicos](https://github.com/Viicos) in [#12733](https://github.com/pydantic/pydantic/pull/12733) +* Improve ImportString error when internal imports fail by [@tsembp](https://github.com/tsembp) in [#12740](https://github.com/pydantic/pydantic/pull/12740) +* Fix serializing complex numbers with negative zero imaginary part by [@lhnwrk](https://github.com/lhnwrk) in [#12770](https://github.com/pydantic/pydantic/pull/12770) +* Preserve custom docstrings on stdlib dataclasses in JSON schema by [@nightcityblade](https://github.com/nightcityblade) in [#12815](https://github.com/pydantic/pydantic/pull/12815) + +#### Packaging + +* Bump Rust url dependency from 2.5.4 to 2.5.7 in `pydantic-core` by [@dependabot](https://github.com/dependabot)[bot] in [#12508](https://github.com/pydantic/pydantic/pull/12508) +* Bump Rust minimum version to 1.88, use edition 2024 by [@davidhewitt](https://github.com/davidhewitt) and [@Viicos](https://github.com/Viicos) in [#12551](https://github.com/pydantic/pydantic/pull/12551) and [#12752](https://github.com/pydantic/pydantic/pull/12752) +* Bump PyO3 to 0.28, jiter to 0.13 by [@davidhewitt](https://github.com/davidhewitt) in [#12767](https://github.com/pydantic/pydantic/pull/12767) + +### New Contributors + +* [@marwan-alloreview](https://github.com/marwan-alloreview) made their first contribution in [#12494](https://github.com/pydantic/pydantic/pull/12494) +* [@tanmaymunjal](https://github.com/tanmaymunjal) made their first contribution in [#12498](https://github.com/pydantic/pydantic/pull/12498) +* [@poliakovva](https://github.com/poliakovva) made their first contribution in [#12579](https://github.com/pydantic/pydantic/pull/12579) +* [@lehmann-hqs](https://github.com/lehmann-hqs) made their first contribution in [#12734](https://github.com/pydantic/pydantic/pull/12734) +* [@jfadia](https://github.com/jfadia) made their first contribution in [#12741](https://github.com/pydantic/pydantic/pull/12741) +* [@tsembp](https://github.com/tsembp) made their first contribution in [#12740](https://github.com/pydantic/pydantic/pull/12740) +* [@drshvik](https://github.com/drshvik) made their first contribution in [#12760](https://github.com/pydantic/pydantic/pull/12760) +* [@lhnwrk](https://github.com/lhnwrk) made their first contribution in [#12770](https://github.com/pydantic/pydantic/pull/12770) +* [@nightcityblade](https://github.com/nightcityblade) made their first contribution in [#12815](https://github.com/pydantic/pydantic/pull/12811) + +## v2.12.5 (2025-11-26) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.5) + +This is the fifth 2.12 patch release, addressing an issue with the `MISSING` sentinel and providing several documentation improvements. + +The next 2.13 minor release will be published in a couple weeks, and will include a new *polymorphic serialization* feature addressing +the remaining unexpected changes to the *serialize as any* behavior. + +* Fix pickle error when using `model_construct()` on a model with `MISSING` as a default value by [@ornariece](https://github.com/ornariece) in [#12522](https://github.com/pydantic/pydantic/pull/12522). +* Several updates to the documentation by [@Viicos](https://github.com/Viicos). + +## v2.12.4 (2025-11-05) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.4) + +This is the fourth 2.12 patch release, fixing more regressions, and reverting a change in the `build()` method +of the [`AnyUrl` and Dsn types](https://docs.pydantic.dev/latest/api/networks/). + +This patch release also fixes an issue with the serialization of IP address types, when `serialize_as_any` is used. The next patch release +will try to address the remaining issues with *serialize as any* behavior by introducing a new *polymorphic serialization* feature, that +should be used in most cases in place of *serialize as any*. + +* Fix issue with forward references in parent `TypedDict` classes by [@Viicos](https://github.com/Viicos) in [#12427](https://github.com/pydantic/pydantic/pull/12427). + + This issue is only relevant on Python 3.14 and greater. + +* Exclude fields with `exclude_if` from JSON Schema required fields by [@Viicos](https://github.com/Viicos) in [#12430](https://github.com/pydantic/pydantic/pull/12430) +* Revert URL percent-encoding of credentials in the `build()` method + of the [`AnyUrl` and Dsn types](https://docs.pydantic.dev/latest/api/networks/) by [@davidhewitt](https://github.com/davidhewitt) in + [pydantic-core#1833](https://github.com/pydantic/pydantic-core/pull/1833). + + This was initially considered as a bugfix, but caused regressions and as such was fully reverted. The next release will include + an opt-in option to percent-encode components of the URL. + +* Add type inference for IP address types by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1868](https://github.com/pydantic/pydantic-core/pull/1868). + + The 2.12 changes to the `serialize_as_any` behavior made it so that IP address types could not properly serialize to JSON. + +* Avoid getting default values from defaultdict by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1853](https://github.com/pydantic/pydantic-core/pull/1853). + + This fixes a subtle regression in the validation behavior of the [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) + type. + +* Fix issue with field serializers on nested typed dictionaries by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1879](https://github.com/pydantic/pydantic-core/pull/1879). +* Add more `pydantic-core` builds for the three-threaded version of Python 3.14 by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1864](https://github.com/pydantic/pydantic-core/pull/1864). + +## v2.12.3 (2025-10-17) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.3) + +### What's Changed + +This is the third 2.12 patch release, fixing issues related to the `FieldInfo` class, and reverting a change to the supported +[*after* model validator](https://docs.pydantic.dev/latest/concepts/validators/#model-validators) function signatures. + +* Raise a warning when an invalid after model validator function signature is raised by [@Viicos](https://github.com/Viicos) in [#12414](https://github.com/pydantic/pydantic/pull/12414). + Starting in 2.12.0, using class methods for *after* model validators raised an error, but the error wasn't raised concistently. We decided + to emit a deprecation warning instead. +* Add [`FieldInfo.asdict()`](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.FieldInfo.asdict) method, improve documentation around `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#12411](https://github.com/pydantic/pydantic/pull/12411). + This also add back support for mutations on `FieldInfo` classes, that are reused as `Annotated` metadata. **However**, note that this is still + *not* a supported pattern. Instead, please refer to the [added example](https://docs.pydantic.dev/latest/examples/dynamic_models/) in the documentation. + +The [blog post](https://pydantic.dev/articles/pydantic-v2-12-release#changes) section on changes was also updated to document the changes related to `serialize_as_any`. + +## v2.12.2 (2025-10-14) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.2) + +### What's Changed + +#### Fixes + +* Release a new `pydantic-core` version, as a corrupted CPython 3.10 `manylinux2014_aarch64` wheel got uploaded ([pydantic-core#1843](https://github.com/pydantic/pydantic-core/pull/1843)). +* Fix issue with recursive generic models with a parent model class by [@Viicos](https://github.com/Viicos) in [#12398](https://github.com/pydantic/pydantic/pull/12398) + +## v2.12.1 (2025-10-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.1) + +### What's Changed + +This is the first 2.12 patch release, addressing most (but not all yet) regressions from the initial 2.12.0 release. + +#### Fixes + +* Do not evaluate annotations when inspecting validators and serializers by [@Viicos](https://github.com/Viicos) in [#12355](https://github.com/pydantic/pydantic/pull/12355) +* Make sure `None` is converted as `NoneType` in Python 3.14 by [@Viicos](https://github.com/Viicos) in [#12370](https://github.com/pydantic/pydantic/pull/12370) +* Backport V1 runtime warning when using Python 3.14 by [@Viicos](https://github.com/Viicos) in [#12367](https://github.com/pydantic/pydantic/pull/12367) +* Fix error message for invalid validator signatures by [@Viicos](https://github.com/Viicos) in [#12366](https://github.com/pydantic/pydantic/pull/12366) +* Populate field name in `ValidationInfo` for validation of default value by [@Viicos](https://github.com/Viicos) in [pydantic-core#1826](https://github.com/pydantic/pydantic-core/pull/1826) +* Encode credentials in `MultiHostUrl` builder by [@willswire](https://github.com/willswire) in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) +* Respect field serializers when using `serialize_as_any` serialization flag by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) +* Fix various `RootModel` serialization issues by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1836](https://github.com/pydantic/pydantic-core/pull/1836) + +### New Contributors + +* [@willswire](https://github.com/willswire) made their first contribution in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) + +## v2.12.0 (2025-10-07) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0) + +This is the final 2.12 release. It features the work of 20 external contributors and provides useful new features, along with initial Python 3.14 support. +Several minor changes (considered non-breaking changes according to our [versioning policy](https://docs.pydantic.dev/2.12/version-policy/#pydantic-v2)) +are also included in this release. Make sure to look into them before upgrading. + +**Note that Pydantic V1 is not compatible with Python 3.14 and greater**. + +### What's Changed + +See the beta releases for all changes sinces 2.11. + +#### New Features + +* Add `extra` parameter to the validate functions by [@anvilpete](https://github.com/anvilpete) in [#12233](https://github.com/pydantic/pydantic/pull/12233) +* Add `exclude_computed_fields` serialization option by [@Viicos](https://github.com/Viicos) in [#12334](https://github.com/pydantic/pydantic/pull/12334) +* Add `preverse_empty_path` URL options by [@Viicos](https://github.com/Viicos) in [#12336](https://github.com/pydantic/pydantic/pull/12336) +* Add `union_format` parameter to JSON Schema generation by [@Viicos](https://github.com/Viicos) in [#12147](https://github.com/pydantic/pydantic/pull/12147) +* Add `__qualname__` parameter for `create_model` by [@Atry](https://github.com/Atry) in [#12001](https://github.com/pydantic/pydantic/pull/12001) + +#### Fixes + +* Do not try to infer name from lambda definitions in pipelines API by [@Viicos](https://github.com/Viicos) in [#12289](https://github.com/pydantic/pydantic/pull/12289) +* Use proper namespace for functions in `TypeAdapter` by [@Viicos](https://github.com/Viicos) in [#12324](https://github.com/pydantic/pydantic/pull/12324) +* Use `Any` for context type annotation in `TypeAdapter` by [@inducer](https://github.com/inducer) in [#12279](https://github.com/pydantic/pydantic/pull/12279) +* Expose `FieldInfo` in `pydantic.fields.__all__` by [@Viicos](https://github.com/Viicos) in [#12339](https://github.com/pydantic/pydantic/pull/12339) +* Respect `validation_alias` in `@validate_call` by [@Viicos](https://github.com/Viicos) in [#12340](https://github.com/pydantic/pydantic/pull/12340) +* Use `Any` as context annotation in plugin API by [@Viicos](https://github.com/Viicos) in [#12341](https://github.com/pydantic/pydantic/pull/12341) +* Use proper `stacklevel` in warnings when possible by [@Viicos](https://github.com/Viicos) in [#12342](https://github.com/pydantic/pydantic/pull/12342) + +#### Packaging + +* Update V1 copy to v1.10.24 by [@Viicos](https://github.com/Viicos) in [#12338](https://github.com/pydantic/pydantic/pull/12338) + +### New Contributors + +* [@anvilpete](https://github.com/anvilpete) made their first contribution in [#12233](https://github.com/pydantic/pydantic/pull/12233) +* [@JonathanWindell](https://github.com/JonathanWindell) made their first contribution in [#12327](https://github.com/pydantic/pydantic/pull/12327) +* [@inducer](https://github.com/inducer) made their first contribution in [#12279](https://github.com/pydantic/pydantic/pull/12279) +* [@Atry](https://github.com/Atry) made their first contribution in [#12001](https://github.com/pydantic/pydantic/pull/12001) + +## v2.12.0b1 (2025-10-03) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0b1) + +This is the first beta release of the upcoming 2.12 release. + +### What's Changed + +#### New Features + +* Add support for `exclude_if` at the field level by [@andresliszt](https://github.com/andresliszt) in [#12141](https://github.com/pydantic/pydantic/pull/12141) +* Add `ValidateAs` annotation helper by [@Viicos](https://github.com/Viicos) in [#11942](https://github.com/pydantic/pydantic/pull/11942) +* Add configuration options for validation and JSON serialization of temporal types by [@ollz272](https://github.com/ollz272) in [#12068](https://github.com/pydantic/pydantic/pull/12068) +* Add support for PEP 728 by [@Viicos](https://github.com/Viicos) in [#12179](https://github.com/pydantic/pydantic/pull/12179) +* Add field name in serialization error by [@NicolasPllr1](https://github.com/NicolasPllr1) in [pydantic-core#1799](https://github.com/pydantic/pydantic-core/pull/1799) +* Add option to preserve empty URL paths by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1789](https://github.com/pydantic/pydantic-core/pull/1789) + +#### Changes + +* Raise error if an incompatible `pydantic-core` version is installed by [@Viicos](https://github.com/Viicos) in [#12196](https://github.com/pydantic/pydantic/pull/12196) +* Remove runtime warning for experimental features by [@Viicos](https://github.com/Viicos) in [#12265](https://github.com/pydantic/pydantic/pull/12265) +* Warn if registering virtual subclasses on Pydantic models by [@Viicos](https://github.com/Viicos) in [#11669](https://github.com/pydantic/pydantic/pull/11669) + +#### Fixes + +* Fix `__getattr__()` behavior on Pydantic models when a property raised an `AttributeError` and extra values are present by [@raspuchin](https://github.com/raspuchin) in [#12106](https://github.com/pydantic/pydantic/pull/12106) +* Add test to prevent regression with Pydantic models used as annotated metadata by [@Viicos](https://github.com/Viicos) in [#12133](https://github.com/pydantic/pydantic/pull/12133) +* Allow to use property setters on Pydantic dataclasses with `validate_assignment` set by [@Viicos](https://github.com/Viicos) in [#12173](https://github.com/pydantic/pydantic/pull/12173) +* Fix mypy v2 plugin for upcoming mypy release by [@cdce8p](https://github.com/cdce8p) in [#12209](https://github.com/pydantic/pydantic/pull/12209) +* Respect custom title in functions JSON Schema by [@Viicos](https://github.com/Viicos) in [#11892](https://github.com/pydantic/pydantic/pull/11892) +* Fix `ImportString` JSON serialization for objects with a `name` attribute by [@chr1sj0nes](https://github.com/chr1sj0nes) in [#12219](https://github.com/pydantic/pydantic/pull/12219) +* Do not error on fields overridden by methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#12290](https://github.com/pydantic/pydantic/pull/12290) + +#### Packaging + +* Bump `pydantic-core` to v2.40.1 by [@Viicos](https://github.com/Viicos) in [#12314](https://github.com/pydantic/pydantic/pull/12314) + +### New Contributors + +* [@raspuchin](https://github.com/raspuchin) made their first contribution in [#12106](https://github.com/pydantic/pydantic/pull/12106) +* [@chr1sj0nes](https://github.com/chr1sj0nes) made their first contribution in [#12219](https://github.com/pydantic/pydantic/pull/12219) + +## v2.12.0a1 (2025-07-26) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0a1) + +This is the first alpha release of the upcoming 2.12 release, which adds initial support for Python 3.14. + +### What's Changed + +#### New Features + +* Add `__pydantic_on_complete__()` hook that is called once model is fully ready to be used by [@DouweM](https://github.com/DouweM) in [#11762](https://github.com/pydantic/pydantic/pull/11762) +* Add initial support for Python 3.14 by [@Viicos](https://github.com/Viicos) in [#11991](https://github.com/pydantic/pydantic/pull/11991) +* Add regex patterns to JSON schema for `Decimal` type by [@Dima-Bulavenko](https://github.com/Dima-Bulavenko) in [#11987](https://github.com/pydantic/pydantic/pull/11987) +* Add support for `doc` attribute on dataclass fields by [@Viicos](https://github.com/Viicos) in [#12077](https://github.com/pydantic/pydantic/pull/12077) +* Add experimental `MISSING` sentinel by [@Viicos](https://github.com/Viicos) in [#11883](https://github.com/pydantic/pydantic/pull/11883) + +#### Changes + +* Allow config and bases to be specified together in `create_model()` by [@Viicos](https://github.com/Viicos) in [#11714](https://github.com/pydantic/pydantic/pull/11714) +* Move some field logic out of the `GenerateSchema` class by [@Viicos](https://github.com/Viicos) in [#11733](https://github.com/pydantic/pydantic/pull/11733) +* Always make use of `inspect.getsourcelines()` for docstring extraction on Python 3.13 and greater by [@Viicos](https://github.com/Viicos) in [#11829](https://github.com/pydantic/pydantic/pull/11829) +* Only support the latest Mypy version by [@Viicos](https://github.com/Viicos) in [#11832](https://github.com/pydantic/pydantic/pull/11832) +* Do not implicitly convert after model validators to class methods by [@Viicos](https://github.com/Viicos) in [#11957](https://github.com/pydantic/pydantic/pull/11957) +* Refactor `FieldInfo` creation implementation by [@Viicos](https://github.com/Viicos) in [#11898](https://github.com/pydantic/pydantic/pull/11898) +* Make `Secret` covariant by [@bluenote10](https://github.com/bluenote10) in [#12008](https://github.com/pydantic/pydantic/pull/12008) +* Emit warning when field-specific metadata is used in invalid contexts by [@Viicos](https://github.com/Viicos) in [#12028](https://github.com/pydantic/pydantic/pull/12028) + +#### Fixes + +* Properly fetch plain serializer function when serializing default value in JSON Schema by [@Viicos](https://github.com/Viicos) in [#11721](https://github.com/pydantic/pydantic/pull/11721) +* Remove generics cache workaround by [@Viicos](https://github.com/Viicos) in [#11755](https://github.com/pydantic/pydantic/pull/11755) +* Remove coercion of decimal constraints by [@Viicos](https://github.com/Viicos) in [#11772](https://github.com/pydantic/pydantic/pull/11772) +* Fix crash when expanding root type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11735](https://github.com/pydantic/pydantic/pull/11735) +* Only mark model as complete once all fields are complete by [@DouweM](https://github.com/DouweM) in [#11759](https://github.com/pydantic/pydantic/pull/11759) +* Do not provide `field_name` in validator core schemas by [@DouweM](https://github.com/DouweM) in [#11761](https://github.com/pydantic/pydantic/pull/11761) +* Fix issue with recursive generic models by [@Viicos](https://github.com/Viicos) in [#11775](https://github.com/pydantic/pydantic/pull/11775) +* Fix qualified name comparison of private attributes during namespace inspection by [@karta9821](https://github.com/karta9821) in [#11803](https://github.com/pydantic/pydantic/pull/11803) +* Make sure Pydantic dataclasses with slots and `validate_assignment` can be unpickled by [@Viicos](https://github.com/Viicos) in [#11769](https://github.com/pydantic/pydantic/pull/11769) +* Traverse `function-before` schemas during schema gathering by [@Viicos](https://github.com/Viicos) in [#11801](https://github.com/pydantic/pydantic/pull/11801) +* Fix check for stdlib dataclasses by [@Viicos](https://github.com/Viicos) in [#11822](https://github.com/pydantic/pydantic/pull/11822) +* Check if `FieldInfo` is complete after applying type variable map by [@Viicos](https://github.com/Viicos) in [#11855](https://github.com/pydantic/pydantic/pull/11855) +* Do not delete mock validator/serializer in `model_rebuild()` by [@Viicos](https://github.com/Viicos) in [#11890](https://github.com/pydantic/pydantic/pull/11890) +* Rebuild dataclass fields before schema generation by [@Viicos](https://github.com/Viicos) in [#11949](https://github.com/pydantic/pydantic/pull/11949) +* Always store the original field assignment on `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11946](https://github.com/pydantic/pydantic/pull/11946) +* Do not use deprecated methods as default field values by [@Viicos](https://github.com/Viicos) in [#11914](https://github.com/pydantic/pydantic/pull/11914) +* Allow callable discriminator to be applied on PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in [#11941](https://github.com/pydantic/pydantic/pull/11941) +* Suppress core schema generation warning when using `SkipValidation` by [@ygsh0816](https://github.com/ygsh0816) in [#12002](https://github.com/pydantic/pydantic/pull/12002) +* Do not emit typechecking error for invalid `Field()` default with `validate_default` set to `True` by [@Viicos](https://github.com/Viicos) in [#11988](https://github.com/pydantic/pydantic/pull/11988) +* Refactor logic to support Pydantic's `Field()` function in dataclasses by [@Viicos](https://github.com/Viicos) in [#12051](https://github.com/pydantic/pydantic/pull/12051) + +#### Packaging + +* Update project metadata to use PEP 639 by [@Viicos](https://github.com/Viicos) in [#11694](https://github.com/pydantic/pydantic/pull/11694) +* Bump `mkdocs-llmstxt` to v0.2.0 by [@Viicos](https://github.com/Viicos) in [#11725](https://github.com/pydantic/pydantic/pull/11725) +* Bump `pydantic-core` to v2.35.1 by [@Viicos](https://github.com/Viicos) in [#11963](https://github.com/pydantic/pydantic/pull/11963) +* Bump dawidd6/action-download-artifact from 10 to 11 by [@dependabot](https://github.com/dependabot)[bot] in [#12033](https://github.com/pydantic/pydantic/pull/12033) +* Bump astral-sh/setup-uv from 5 to 6 by [@dependabot](https://github.com/dependabot)[bot] in [#11826](https://github.com/pydantic/pydantic/pull/11826) +* Update mypy to 1.17.0 by [@Viicos](https://github.com/Viicos) in [#12076](https://github.com/pydantic/pydantic/pull/12076) + +### New Contributors + +* [@parth-paradkar](https://github.com/parth-paradkar) made their first contribution in [#11695](https://github.com/pydantic/pydantic/pull/11695) +* [@dqkqd](https://github.com/dqkqd) made their first contribution in [#11739](https://github.com/pydantic/pydantic/pull/11739) +* [@fhightower](https://github.com/fhightower) made their first contribution in [#11722](https://github.com/pydantic/pydantic/pull/11722) +* [@gbaian10](https://github.com/gbaian10) made their first contribution in [#11766](https://github.com/pydantic/pydantic/pull/11766) +* [@DouweM](https://github.com/DouweM) made their first contribution in [#11759](https://github.com/pydantic/pydantic/pull/11759) +* [@bowenliang123](https://github.com/bowenliang123) made their first contribution in [#11719](https://github.com/pydantic/pydantic/pull/11719) +* [@rawwar](https://github.com/rawwar) made their first contribution in [#11799](https://github.com/pydantic/pydantic/pull/11799) +* [@karta9821](https://github.com/karta9821) made their first contribution in [#11803](https://github.com/pydantic/pydantic/pull/11803) +* [@jinnovation](https://github.com/jinnovation) made their first contribution in [#11834](https://github.com/pydantic/pydantic/pull/11834) +* [@zmievsa](https://github.com/zmievsa) made their first contribution in [#11861](https://github.com/pydantic/pydantic/pull/11861) +* [@Otto-AA](https://github.com/Otto-AA) made their first contribution in [#11860](https://github.com/pydantic/pydantic/pull/11860) +* [@ygsh0816](https://github.com/ygsh0816) made their first contribution in [#12002](https://github.com/pydantic/pydantic/pull/12002) +* [@lukland](https://github.com/lukland) made their first contribution in [#12015](https://github.com/pydantic/pydantic/pull/12015) +* [@Dima-Bulavenko](https://github.com/Dima-Bulavenko) made their first contribution in [#11987](https://github.com/pydantic/pydantic/pull/11987) +* [@GSemikozov](https://github.com/GSemikozov) made their first contribution in [#12050](https://github.com/pydantic/pydantic/pull/12050) +* [@hannah-heywa](https://github.com/hannah-heywa) made their first contribution in [#12082](https://github.com/pydantic/pydantic/pull/12082) + +## v2.11.10 (2025-10-04) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.10) + +### What's Changed + +#### Fixes + +* Backport v1.10.24 changes by [@Viicos](https://github.com/Viicos) + +## v2.11.9 (2025-09-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.9) + +### What's Changed + +#### Fixes + +* Backport v1.10.23 changes by [@Viicos](https://github.com/Viicos) + +## v2.11.8 (2025-09-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.8) + +### What's Changed + +#### Fixes + +* Fix mypy plugin for mypy 1.18 by [@cdce8p](https://github.com/cdce8p) in [#12209](https://github.com/pydantic/pydantic/pull/12209) + +## v2.11.7 (2025-06-14) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.7) + +### What's Changed + +#### Fixes + +* Copy `FieldInfo` instance if necessary during `FieldInfo` build by [@Viicos](https://github.com/Viicos) in [#11898](https://github.com/pydantic/pydantic/pull/11898) + +## v2.11.6 (2025-06-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.6) + +### What's Changed + +#### Fixes + +* Rebuild dataclass fields before schema generation by [@Viicos](https://github.com/Viicos) in [#11949](https://github.com/pydantic/pydantic/pull/11949) +* Always store the original field assignment on `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11946](https://github.com/pydantic/pydantic/pull/11946) + +## v2.11.5 (2025-05-22) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.5) + +### What's Changed + +#### Fixes + +* Check if `FieldInfo` is complete after applying type variable map by [@Viicos](https://github.com/Viicos) in [#11855](https://github.com/pydantic/pydantic/pull/11855) +* Do not delete mock validator/serializer in `model_rebuild()` by [@Viicos](https://github.com/Viicos) in [#11890](https://github.com/pydantic/pydantic/pull/11890) +* Do not duplicate metadata on model rebuild by [@Viicos](https://github.com/Viicos) in [#11902](https://github.com/pydantic/pydantic/pull/11902) + +## v2.11.4 (2025-04-29) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.4) + +### What's Changed + +#### Changes + +* Allow config and bases to be specified together in `create_model()` by [@Viicos](https://github.com/Viicos) in [#11714](https://github.com/pydantic/pydantic/pull/11714). + This change was backported as it was previously possible (although not meant to be supported) + to provide `model_config` as a field, which would make it possible to provide both configuration + and bases. + +#### Fixes + +* Remove generics cache workaround by [@Viicos](https://github.com/Viicos) in [#11755](https://github.com/pydantic/pydantic/pull/11755) +* Remove coercion of decimal constraints by [@Viicos](https://github.com/Viicos) in [#11772](https://github.com/pydantic/pydantic/pull/11772) +* Fix crash when expanding root type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11735](https://github.com/pydantic/pydantic/pull/11735) +* Fix issue with recursive generic models by [@Viicos](https://github.com/Viicos) in [#11775](https://github.com/pydantic/pydantic/pull/11775) +* Traverse `function-before` schemas during schema gathering by [@Viicos](https://github.com/Viicos) in [#11801](https://github.com/pydantic/pydantic/pull/11801) + +#### Packaging + +* Bump `mkdocs-llmstxt` to v0.2.0 by [@Viicos](https://github.com/Viicos) in [#11725](https://github.com/pydantic/pydantic/pull/11725) + +## v2.11.3 (2025-04-08) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.3) + +### What's Changed + +#### Fixes + +* Preserve field description when rebuilding model fields by [@Viicos](https://github.com/Viicos) in [#11698](https://github.com/pydantic/pydantic/pull/11698) + +#### Packaging + +* Update V1 copy to v1.10.21 by [@Viicos](https://github.com/Viicos) in [#11706](https://github.com/pydantic/pydantic/pull/11706) + +## v2.11.2 (2025-04-03) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.2) + +### What's Changed + +#### Fixes + +* Bump `pydantic-core` to v2.33.1 by [@Viicos](https://github.com/Viicos) in [#11678](https://github.com/pydantic/pydantic/pull/11678) +* Make sure `__pydantic_private__` exists before setting private attributes by [@Viicos](https://github.com/Viicos) in [#11666](https://github.com/pydantic/pydantic/pull/11666) +* Do not override `FieldInfo._complete` when using field from parent class by [@Viicos](https://github.com/Viicos) in [#11668](https://github.com/pydantic/pydantic/pull/11668) +* Provide the available definitions when applying discriminated unions by [@Viicos](https://github.com/Viicos) in [#11670](https://github.com/pydantic/pydantic/pull/11670) +* Do not expand root type in the mypy plugin for variables by [@Viicos](https://github.com/Viicos) in [#11676](https://github.com/pydantic/pydantic/pull/11676) +* Mention the attribute name in model fields deprecation message by [@Viicos](https://github.com/Viicos) in [#11674](https://github.com/pydantic/pydantic/pull/11674) +* Properly validate parameterized mappings by [@Viicos](https://github.com/Viicos) in [#11658](https://github.com/pydantic/pydantic/pull/11658) + +## v2.11.1 (2025-03-28) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.1) + +### What's Changed + +#### Fixes + +* Do not override `'definitions-ref'` schemas containing serialization schemas or metadata by [@Viicos](https://github.com/Viicos) in [#11644](https://github.com/pydantic/pydantic/pull/11644) + +## v2.11.0 (2025-03-27) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0) + +### What's Changed + +Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). +See the [blog post](https://pydantic.dev/articles/pydantic-v2-11-release) for more details. + +#### New Features + +* Add `encoded_string()` method to the URL types by [@YassinNouh21](https://github.com/YassinNouh21) in [#11580](https://github.com/pydantic/pydantic/pull/11580) +* Add support for `defer_build` with `@validate_call` decorator by [@Viicos](https://github.com/Viicos) in [#11584](https://github.com/pydantic/pydantic/pull/11584) +* Allow `@with_config` decorator to be used with keyword arguments by [@Viicos](https://github.com/Viicos) in [#11608](https://github.com/pydantic/pydantic/pull/11608) +* Simplify customization of default value inclusion in JSON Schema generation by [@Viicos](https://github.com/Viicos) in [#11634](https://github.com/pydantic/pydantic/pull/11634) +* Add `generate_arguments_schema()` function by [@Viicos](https://github.com/Viicos) in [#11572](https://github.com/pydantic/pydantic/pull/11572) + +#### Fixes + +* Allow generic typed dictionaries to be used for unpacked variadic keyword parameters by [@Viicos](https://github.com/Viicos) in [#11571](https://github.com/pydantic/pydantic/pull/11571) +* Fix runtime error when computing model string representation involving cached properties and self-referenced models by [@Viicos](https://github.com/Viicos) in [#11579](https://github.com/pydantic/pydantic/pull/11579) +* Preserve other steps when using the ellipsis in the pipeline API by [@Viicos](https://github.com/Viicos) in [#11626](https://github.com/pydantic/pydantic/pull/11626) +* Fix deferred discriminator application logic by [@Viicos](https://github.com/Viicos) in [#11591](https://github.com/pydantic/pydantic/pull/11591) + +#### Packaging + +* Bump `pydantic-core` to v2.33.0 by [@Viicos](https://github.com/Viicos) in [#11631](https://github.com/pydantic/pydantic/pull/11631) + +### New Contributors + +* [@cmenon12](https://github.com/cmenon12) made their first contribution in [#11562](https://github.com/pydantic/pydantic/pull/11562) +* [@Jeukoh](https://github.com/Jeukoh) made their first contribution in [#11611](https://github.com/pydantic/pydantic/pull/11611) + +## v2.11.0b2 (2025-03-17) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0b2) + +### What's Changed + +#### New Features + +* Add experimental support for free threading by [@Viicos](https://github.com/Viicos) in [#11516](https://github.com/pydantic/pydantic/pull/11516) + +#### Fixes + +* Fix `NotRequired` qualifier not taken into account in stringified annotation by [@Viicos](https://github.com/Viicos) in [#11559](https://github.com/pydantic/pydantic/pull/11559) + +#### Packaging + +* Bump `pydantic-core` to v2.32.0 by [@Viicos](https://github.com/Viicos) in [#11567](https://github.com/pydantic/pydantic/pull/11567) + +### New Contributors + +* [@joren485](https://github.com/joren485) made their first contribution in [#11547](https://github.com/pydantic/pydantic/pull/11547) + +## v2.11.0b1 (2025-03-06) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0b1) + +### What's Changed + +#### New Features + +* Support unsubstituted type variables with both a default and a bound or constraints by [@FyZzyss](https://github.com/FyZzyss) in https://github.com/pydantic/pydantic/pull/10789 +* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11034 +* Raise a better error when a generic alias is used inside `type[]` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11088 +* Properly support PEP 695 generics syntax by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11189 +* Properly support type variable defaults by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11332 +* Add support for validating v6, v7, v8 UUIDs by [@astei](https://github.com/astei) in https://github.com/pydantic/pydantic/pull/11436 +* Improve alias configuration APIs by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11468 + +#### Changes + +* Rework `create_model` field definitions format by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11032 +* Raise a deprecation warning when a field is annotated as final with a default value by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11168 +* Deprecate accessing `model_fields` and `model_computed_fields` on instances by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11169 +* **Breaking Change:** Move core schema generation logic for path types inside the `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/10846 +* Remove Python 3.8 Support by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11258 +* Optimize calls to `get_type_ref` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10863 +* Disable `pydantic-core` core schema validation by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11271 + +#### Performance + +* Only evaluate `FieldInfo` annotations if required during schema building by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10769 +* Improve `__setattr__` performance of Pydantic models by caching setter functions by [@MarkusSintonen](https://github.com/MarkusSintonen) in https://github.com/pydantic/pydantic/pull/10868 +* Improve annotation application performance by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11186 +* Improve performance of `_typing_extra` module by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11255 +* Refactor and optimize schema cleaning logic by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11244 +* Create a single dictionary when creating a `CoreConfig` instance by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11384 +* Bump `pydantic-core` and thus use `SchemaValidator` and `SchemaSerializer` caching by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11402 +* Reuse cached core schemas for parametrized generic Pydantic models by [@MarkusSintonen](https://github.com/MarkusSintonen) in https://github.com/pydantic/pydantic/pull/11434 + +#### Fixes + +* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/10872 +* Use the correct frame when instantiating a parametrized `TypeAdapter` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10893 +* Infer final fields with a default value as class variables in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11121 +* Recursively unpack `Literal` values if using PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11114 +* Override `__subclasscheck__` on `ModelMetaclass` to avoid memory leak and performance issues by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11116 +* Remove unused `_extract_get_pydantic_json_schema()` parameter by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11155 +* Improve discriminated union error message for invalid union variants by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11161 +* Unpack PEP 695 type aliases if using the `Annotated` form by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11109 +* Add missing stacklevel in `deprecated_instance_property` warning by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11200 +* Copy `WithJsonSchema` schema to avoid sharing mutated data by [@thejcannon](https://github.com/thejcannon) in https://github.com/pydantic/pydantic/pull/11014 +* Do not cache parametrized models when in the process of parametrizing another model by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10704 +* Add discriminated union related metadata entries to the `CoreMetadata` definition by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11216 +* Consolidate schema definitions logic in the `_Definitions` class by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11208 +* Support initializing root model fields with values of the `root` type in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11212 +* Fix various issues with dataclasses and `use_attribute_docstrings` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11246 +* Only compute normalized decimal places if necessary in `decimal_places_validator` by [@misrasaurabh1](https://github.com/misrasaurabh1) in https://github.com/pydantic/pydantic/pull/11281 +* Add support for `validation_alias` in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11295 +* Fix JSON Schema reference collection with `"examples"` keys by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11305 +* Do not transform model serializer functions as class methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11298 +* Simplify `GenerateJsonSchema.literal_schema()` implementation by [@misrasaurabh1](https://github.com/misrasaurabh1) in https://github.com/pydantic/pydantic/pull/11321 +* Add additional allowed schemes for `ClickHouseDsn` by [@Maze21127](https://github.com/Maze21127) in https://github.com/pydantic/pydantic/pull/11319 +* Coerce decimal constraints to `Decimal` instances by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11350 +* Use the correct JSON Schema mode when handling function schemas by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11367 +* Improve exception message when encountering recursion errors during type evaluation by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11356 +* Always include `additionalProperties: True` for arbitrary dictionary schemas by [@austinyu](https://github.com/austinyu) in https://github.com/pydantic/pydantic/pull/11392 +* Expose `fallback` parameter in serialization methods by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11398 +* Fix path serialization behavior by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11416 +* Do not reuse validators and serializers during model rebuild by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11429 +* Collect model fields when rebuilding a model by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11388 +* Allow cached properties to be altered on frozen models by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11432 +* Fix tuple serialization for `Sequence` types by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11435 +* Fix: do not check for `__get_validators__` on classes where `__get_pydantic_core_schema__` is also defined by [@tlambert03](https://github.com/tlambert03) in https://github.com/pydantic/pydantic/pull/11444 +* Allow callable instances to be used as serializers by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11451 +* Improve error thrown when overriding field with a property by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11459 +* Fix JSON Schema generation with referenceable core schemas holding JSON metadata by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11475 +* Support strict specification on union member types by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11481 +* Implicitly set `validate_by_name` to `True` when `validate_by_alias` is `False` by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11503 +* Change type of `Any` when synthesizing `BaseSettings.__init__` signature in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11497 +* Support type variable defaults referencing other type variables by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11520 +* Fix `ValueError` on year zero by [@davidhewitt](https://github.com/davidhewitt) in https://github.com/pydantic/pydantic-core/pull/1583 +* `dataclass` `InitVar` shouldn't be required on serialization by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic-core/pull/1602 + +#### Packaging + +* Add a `check_pydantic_core_version()` function by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11324 +* Remove `greenlet` development dependency by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11351 +* Use the `typing-inspection` library by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11479 +* Bump `pydantic-core` to `v2.31.1` by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11526 + +## New Contributors + +* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in https://github.com/pydantic/pydantic/pull/10789 +* [@tamird](https://github.com/tamird) made their first contribution in https://github.com/pydantic/pydantic/pull/10948 +* [@felixxm](https://github.com/felixxm) made their first contribution in https://github.com/pydantic/pydantic/pull/11077 +* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in https://github.com/pydantic/pydantic/pull/11082 +* [@Kharianne](https://github.com/Kharianne) made their first contribution in https://github.com/pydantic/pydantic/pull/11111 +* [@mdaffad](https://github.com/mdaffad) made their first contribution in https://github.com/pydantic/pydantic/pull/11177 +* [@thejcannon](https://github.com/thejcannon) made their first contribution in https://github.com/pydantic/pydantic/pull/11014 +* [@thomasfrimannkoren](https://github.com/thomasfrimannkoren) made their first contribution in https://github.com/pydantic/pydantic/pull/11251 +* [@usernameMAI](https://github.com/usernameMAI) made their first contribution in https://github.com/pydantic/pydantic/pull/11275 +* [@ananiavito](https://github.com/ananiavito) made their first contribution in https://github.com/pydantic/pydantic/pull/11302 +* [@pawamoy](https://github.com/pawamoy) made their first contribution in https://github.com/pydantic/pydantic/pull/11311 +* [@Maze21127](https://github.com/Maze21127) made their first contribution in https://github.com/pydantic/pydantic/pull/11319 +* [@kauabh](https://github.com/kauabh) made their first contribution in https://github.com/pydantic/pydantic/pull/11369 +* [@jaceklaskowski](https://github.com/jaceklaskowski) made their first contribution in https://github.com/pydantic/pydantic/pull/11353 +* [@tmpbeing](https://github.com/tmpbeing) made their first contribution in https://github.com/pydantic/pydantic/pull/11375 +* [@petyosi](https://github.com/petyosi) made their first contribution in https://github.com/pydantic/pydantic/pull/11405 +* [@austinyu](https://github.com/austinyu) made their first contribution in https://github.com/pydantic/pydantic/pull/11392 +* [@mikeedjones](https://github.com/mikeedjones) made their first contribution in https://github.com/pydantic/pydantic/pull/11402 +* [@astei](https://github.com/astei) made their first contribution in https://github.com/pydantic/pydantic/pull/11436 +* [@dsayling](https://github.com/dsayling) made their first contribution in https://github.com/pydantic/pydantic/pull/11522 +* [@sobolevn](https://github.com/sobolevn) made their first contribution in https://github.com/pydantic/pydantic-core/pull/1645 + +## v2.11.0a2 (2025-02-10) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0a2) + +### What's Changed + +Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). +This is another early alpha release, meant to collect early feedback from users having issues with core schema builds. + +#### Performance + +* Create a single dictionary when creating a `CoreConfig` instance by [@sydney-runkle](https://github.com/sydney-runkle) in [#11384](https://github.com/pydantic/pydantic/pull/11384) + +#### Fixes + +* Use the correct JSON Schema mode when handling function schemas by [@Viicos](https://github.com/Viicos) in [#11367](https://github.com/pydantic/pydantic/pull/11367) +* Fix JSON Schema reference logic with `examples` keys by [@Viicos](https://github.com/Viicos) in [#11366](https://github.com/pydantic/pydantic/pull/11366) +* Improve exception message when encountering recursion errors during type evaluation by [@Viicos](https://github.com/Viicos) in [#11356](https://github.com/pydantic/pydantic/pull/11356) +* Always include `additionalProperties: True` for arbitrary dictionary schemas by [@austinyu](https://github.com/austinyu) in [#11392](https://github.com/pydantic/pydantic/pull/11392) +* Expose `fallback` parameter in serialization methods by [@Viicos](https://github.com/Viicos) in [#11398](https://github.com/pydantic/pydantic/pull/11398) +* Fix path serialization behavior by [@sydney-runkle](https://github.com/sydney-runkle) in [#11416](https://github.com/pydantic/pydantic/pull/11416) + +#### Packaging + +* Bump `ruff` from 0.9.2 to 0.9.5 by [@Viicos](https://github.com/Viicos) in [#11407](https://github.com/pydantic/pydantic/pull/11407) +* Bump `pydantic-core` to v2.29.0 by [@mikeedjones](https://github.com/mikeedjones) in [#11402](https://github.com/pydantic/pydantic/pull/11402) +* Use locally-built rust with symbols & pgo by [@davidhewitt](https://github.com/davidhewitt) in [#11403](https://github.com/pydantic/pydantic/pull/11403) + +### New Contributors + +* [@kauabh](https://github.com/kauabh) made their first contribution in [#11369](https://github.com/pydantic/pydantic/pull/11369) +* [@jaceklaskowski](https://github.com/jaceklaskowski) made their first contribution in [#11353](https://github.com/pydantic/pydantic/pull/11353) +* [@tmpbeing](https://github.com/tmpbeing) made their first contribution in [#11375](https://github.com/pydantic/pydantic/pull/11375) +* [@petyosi](https://github.com/petyosi) made their first contribution in [#11405](https://github.com/pydantic/pydantic/pull/11405) +* [@austinyu](https://github.com/austinyu) made their first contribution in [#11392](https://github.com/pydantic/pydantic/pull/11392) +* [@mikeedjones](https://github.com/mikeedjones) made their first contribution in [#11402](https://github.com/pydantic/pydantic/pull/11402) + +## v2.11.0a1 (2025-01-30) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0a1) + +### What's Changed + +Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). +This is an early alpha release, meant to collect early feedback from users having issues with core schema builds. + +#### New Features + +* Support unsubstituted type variables with both a default and a bound or constraints by [@FyZzyss](https://github.com/FyZzyss) in [#10789](https://github.com/pydantic/pydantic/pull/10789) +* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11034](https://github.com/pydantic/pydantic/pull/11034) +* Raise a better error when a generic alias is used inside `type[]` by [@Viicos](https://github.com/Viicos) in [#11088](https://github.com/pydantic/pydantic/pull/11088) +* Properly support PEP 695 generics syntax by [@Viicos](https://github.com/Viicos) in [#11189](https://github.com/pydantic/pydantic/pull/11189) +* Properly support type variable defaults by [@Viicos](https://github.com/Viicos) in [#11332](https://github.com/pydantic/pydantic/pull/11332) + +#### Changes + +* Rework `create_model` field definitions format by [@Viicos](https://github.com/Viicos) in [#11032](https://github.com/pydantic/pydantic/pull/11032) +* Raise a deprecation warning when a field is annotated as final with a default value by [@Viicos](https://github.com/Viicos) in [#11168](https://github.com/pydantic/pydantic/pull/11168) +* Deprecate accessing `model_fields` and `model_computed_fields` on instances by [@Viicos](https://github.com/Viicos) in [#11169](https://github.com/pydantic/pydantic/pull/11169) +* Move core schema generation logic for path types inside the `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in [#10846](https://github.com/pydantic/pydantic/pull/10846) +* Move `deque` schema gen to `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in [#11239](https://github.com/pydantic/pydantic/pull/11239) +* Move `Mapping` schema gen to `GenerateSchema` to complete removal of `prepare_annotations_for_known_type` workaround by [@sydney-runkle](https://github.com/sydney-runkle) in [#11247](https://github.com/pydantic/pydantic/pull/11247) +* Remove Python 3.8 Support by [@sydney-runkle](https://github.com/sydney-runkle) in [#11258](https://github.com/pydantic/pydantic/pull/11258) +* Disable `pydantic-core` core schema validation by [@sydney-runkle](https://github.com/sydney-runkle) in [#11271](https://github.com/pydantic/pydantic/pull/11271) + +#### Performance + +* Only evaluate `FieldInfo` annotations if required during schema building by [@Viicos](https://github.com/Viicos) in [#10769](https://github.com/pydantic/pydantic/pull/10769) +* Optimize calls to `get_type_ref` by [@Viicos](https://github.com/Viicos) in [#10863](https://github.com/pydantic/pydantic/pull/10863) +* Improve `__setattr__` performance of Pydantic models by caching setter functions by [@MarkusSintonen](https://github.com/MarkusSintonen) in [#10868](https://github.com/pydantic/pydantic/pull/10868) +* Improve annotation application performance by [@Viicos](https://github.com/Viicos) in [#11186](https://github.com/pydantic/pydantic/pull/11186) +* Improve performance of `_typing_extra` module by [@Viicos](https://github.com/Viicos) in [#11255](https://github.com/pydantic/pydantic/pull/11255) +* Refactor and optimize schema cleaning logic by [@Viicos](https://github.com/Viicos) and [@MarkusSintonen](https://github.com/MarkusSintonen) in [#11244](https://github.com/pydantic/pydantic/pull/11244) + +#### Fixes + +* Add validation tests for `_internal/_validators.py` by [@tkasuz](https://github.com/tkasuz) in [#10763](https://github.com/pydantic/pydantic/pull/10763) +* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in [#10872](https://github.com/pydantic/pydantic/pull/10872) +* Revert "ci: use locally built pydantic-core with debug symbols by [@sydney-runkle](https://github.com/sydney-runkle) in [#10942](https://github.com/pydantic/pydantic/pull/10942) +* Re-enable all FastAPI tests by [@tamird](https://github.com/tamird) in [#10948](https://github.com/pydantic/pydantic/pull/10948) +* Fix typo in HISTORY.md. by [@felixxm](https://github.com/felixxm) in [#11077](https://github.com/pydantic/pydantic/pull/11077) +* Infer final fields with a default value as class variables in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11121](https://github.com/pydantic/pydantic/pull/11121) +* Recursively unpack `Literal` values if using PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in [#11114](https://github.com/pydantic/pydantic/pull/11114) +* Override `__subclasscheck__` on `ModelMetaclass` to avoid memory leak and performance issues by [@Viicos](https://github.com/Viicos) in [#11116](https://github.com/pydantic/pydantic/pull/11116) +* Remove unused `_extract_get_pydantic_json_schema()` parameter by [@Viicos](https://github.com/Viicos) in [#11155](https://github.com/pydantic/pydantic/pull/11155) +* Add FastAPI and SQLModel to third-party tests by [@sydney-runkle](https://github.com/sydney-runkle) in [#11044](https://github.com/pydantic/pydantic/pull/11044) +* Fix conditional expressions syntax for third-party tests by [@Viicos](https://github.com/Viicos) in [#11162](https://github.com/pydantic/pydantic/pull/11162) +* Move FastAPI tests to third-party workflow by [@Viicos](https://github.com/Viicos) in [#11164](https://github.com/pydantic/pydantic/pull/11164) +* Improve discriminated union error message for invalid union variants by [@Viicos](https://github.com/Viicos) in [#11161](https://github.com/pydantic/pydantic/pull/11161) +* Unpack PEP 695 type aliases if using the `Annotated` form by [@Viicos](https://github.com/Viicos) in [#11109](https://github.com/pydantic/pydantic/pull/11109) +* Include `openapi-python-client` check in issue creation for third-party failures, use `main` branch by [@sydney-runkle](https://github.com/sydney-runkle) in [#11182](https://github.com/pydantic/pydantic/pull/11182) +* Add pandera third-party tests by [@Viicos](https://github.com/Viicos) in [#11193](https://github.com/pydantic/pydantic/pull/11193) +* Add ODMantic third-party tests by [@sydney-runkle](https://github.com/sydney-runkle) in [#11197](https://github.com/pydantic/pydantic/pull/11197) +* Add missing stacklevel in `deprecated_instance_property` warning by [@Viicos](https://github.com/Viicos) in [#11200](https://github.com/pydantic/pydantic/pull/11200) +* Copy `WithJsonSchema` schema to avoid sharing mutated data by [@thejcannon](https://github.com/thejcannon) in [#11014](https://github.com/pydantic/pydantic/pull/11014) +* Do not cache parametrized models when in the process of parametrizing another model by [@Viicos](https://github.com/Viicos) in [#10704](https://github.com/pydantic/pydantic/pull/10704) +* Re-enable Beanie third-party tests by [@Viicos](https://github.com/Viicos) in [#11214](https://github.com/pydantic/pydantic/pull/11214) +* Add discriminated union related metadata entries to the `CoreMetadata` definition by [@Viicos](https://github.com/Viicos) in [#11216](https://github.com/pydantic/pydantic/pull/11216) +* Consolidate schema definitions logic in the `_Definitions` class by [@Viicos](https://github.com/Viicos) in [#11208](https://github.com/pydantic/pydantic/pull/11208) +* Support initializing root model fields with values of the `root` type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11212](https://github.com/pydantic/pydantic/pull/11212) +* Fix various issues with dataclasses and `use_attribute_docstrings` by [@Viicos](https://github.com/Viicos) in [#11246](https://github.com/pydantic/pydantic/pull/11246) +* Only compute normalized decimal places if necessary in `decimal_places_validator` by [@misrasaurabh1](https://github.com/misrasaurabh1) in [#11281](https://github.com/pydantic/pydantic/pull/11281) +* Fix two misplaced sentences in validation errors documentation by [@ananiavito](https://github.com/ananiavito) in [#11302](https://github.com/pydantic/pydantic/pull/11302) +* Fix mkdocstrings inventory example in documentation by [@pawamoy](https://github.com/pawamoy) in [#11311](https://github.com/pydantic/pydantic/pull/11311) +* Add support for `validation_alias` in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11295](https://github.com/pydantic/pydantic/pull/11295) +* Do not transform model serializer functions as class methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11298](https://github.com/pydantic/pydantic/pull/11298) +* Simplify `GenerateJsonSchema.literal_schema()` implementation by [@misrasaurabh1](https://github.com/misrasaurabh1) in [#11321](https://github.com/pydantic/pydantic/pull/11321) +* Add additional allowed schemes for `ClickHouseDsn` by [@Maze21127](https://github.com/Maze21127) in [#11319](https://github.com/pydantic/pydantic/pull/11319) +* Coerce decimal constraints to `Decimal` instances by [@Viicos](https://github.com/Viicos) in [#11350](https://github.com/pydantic/pydantic/pull/11350) +* Fix `ValueError` on year zero by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1583](https://github.com/pydantic/pydantic-core/pull/1583) + +#### Packaging + +* Bump dawidd6/action-download-artifact from 6 to 7 by [@dependabot](https://github.com/dependabot) in [#11018](https://github.com/pydantic/pydantic/pull/11018) +* Re-enable memray related tests on Python 3.12+ by [@Viicos](https://github.com/Viicos) in [#11191](https://github.com/pydantic/pydantic/pull/11191) +* Bump astral-sh/setup-uv to 5 by [@dependabot](https://github.com/dependabot) in [#11205](https://github.com/pydantic/pydantic/pull/11205) +* Bump `ruff` to v0.9.0 by [@sydney-runkle](https://github.com/sydney-runkle) in [#11254](https://github.com/pydantic/pydantic/pull/11254) +* Regular `uv.lock` deps update by [@sydney-runkle](https://github.com/sydney-runkle) in [#11333](https://github.com/pydantic/pydantic/pull/11333) +* Add a `check_pydantic_core_version()` function by [@Viicos](https://github.com/Viicos) in [#11324](https://github.com/pydantic/pydantic/pull/11324) +* Remove `greenlet` development dependency by [@Viicos](https://github.com/Viicos) in [#11351](https://github.com/pydantic/pydantic/pull/11351) +* Bump `pydantic-core` to v2.28.0 by [@Viicos](https://github.com/Viicos) in [#11364](https://github.com/pydantic/pydantic/pull/11364) + +### New Contributors + +* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in [#10789](https://github.com/pydantic/pydantic/pull/10789) +* [@tamird](https://github.com/tamird) made their first contribution in [#10948](https://github.com/pydantic/pydantic/pull/10948) +* [@felixxm](https://github.com/felixxm) made their first contribution in [#11077](https://github.com/pydantic/pydantic/pull/11077) +* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in [#11082](https://github.com/pydantic/pydantic/pull/11082) +* [@Kharianne](https://github.com/Kharianne) made their first contribution in [#11111](https://github.com/pydantic/pydantic/pull/11111) +* [@mdaffad](https://github.com/mdaffad) made their first contribution in [#11177](https://github.com/pydantic/pydantic/pull/11177) +* [@thejcannon](https://github.com/thejcannon) made their first contribution in [#11014](https://github.com/pydantic/pydantic/pull/11014) +* [@thomasfrimannkoren](https://github.com/thomasfrimannkoren) made their first contribution in [#11251](https://github.com/pydantic/pydantic/pull/11251) +* [@usernameMAI](https://github.com/usernameMAI) made their first contribution in [#11275](https://github.com/pydantic/pydantic/pull/11275) +* [@ananiavito](https://github.com/ananiavito) made their first contribution in [#11302](https://github.com/pydantic/pydantic/pull/11302) +* [@pawamoy](https://github.com/pawamoy) made their first contribution in [#11311](https://github.com/pydantic/pydantic/pull/11311) +* [@Maze21127](https://github.com/Maze21127) made their first contribution in [#11319](https://github.com/pydantic/pydantic/pull/11319) + +## v2.10.6 (2025-01-23) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.6) + +### What's Changed + +#### Fixes + +* Fix JSON Schema reference collection with `'examples'` keys by [@Viicos](https://github.com/Viicos) in [#11325](https://github.com/pydantic/pydantic/pull/11325) +* Fix url python serialization by [@sydney-runkle](https://github.com/sydney-runkle) in [#11331](https://github.com/pydantic/pydantic/pull/11331) + +## v2.10.5 (2025-01-08) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.5) + +### What's Changed + +#### Fixes + +* Remove custom MRO implementation of Pydantic models by [@Viicos](https://github.com/Viicos) in [#11184](https://github.com/pydantic/pydantic/pull/11184) +* Fix URL serialization for unions by [@sydney-runkle](https://github.com/sydney-runkle) in [#11233](https://github.com/pydantic/pydantic/pull/11233) + +## v2.10.4 (2024-12-18) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.4) + +### What's Changed + +#### Fixes + +* Fix for comparison of `AnyUrl` objects by [@alexprabhat99](https://github.com/alexprabhat99) in [#11082](https://github.com/pydantic/pydantic/pull/11082) +* Properly fetch PEP 695 type params for functions, do not fetch annotations from signature by [@Viicos](https://github.com/Viicos) in [#11093](https://github.com/pydantic/pydantic/pull/11093) +* Include JSON Schema input core schema in function schemas by [@Viicos](https://github.com/Viicos) in [#11085](https://github.com/pydantic/pydantic/pull/11085) +* Add `len` to `_BaseUrl` to avoid TypeError by [@Kharianne](https://github.com/Kharianne) in [#11111](https://github.com/pydantic/pydantic/pull/11111) +* Make sure the type reference is removed from the seen references by [@Viicos](https://github.com/Viicos) in [#11143](https://github.com/pydantic/pydantic/pull/11143) + +### New Contributors + +* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in [#10789](https://github.com/pydantic/pydantic/pull/10789) +* [@tamird](https://github.com/tamird) made their first contribution in [#10948](https://github.com/pydantic/pydantic/pull/10948) +* [@felixxm](https://github.com/felixxm) made their first contribution in [#11077](https://github.com/pydantic/pydantic/pull/11077) +* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in [#11082](https://github.com/pydantic/pydantic/pull/11082) +* [@Kharianne](https://github.com/Kharianne) made their first contribution in [#11111](https://github.com/pydantic/pydantic/pull/11111) + +#### Packaging + +* Bump `pydantic-core` to v2.27.2 by [@davidhewitt](https://github.com/davidhewitt) in [#11138](https://github.com/pydantic/pydantic/pull/11138) + +## v2.10.3 (2024-12-03) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.3) + +### What's Changed + +#### Fixes + +* Set fields when `defer_build` is set on Pydantic dataclasses by [@Viicos](https://github.com/Viicos) in [#10984](https://github.com/pydantic/pydantic/pull/10984) +* Do not resolve the JSON Schema reference for `dict` core schema keys by [@Viicos](https://github.com/Viicos) in [#10989](https://github.com/pydantic/pydantic/pull/10989) +* Use the globals of the function when evaluating the return type for `PlainSerializer` and `WrapSerializer` functions by [@Viicos](https://github.com/Viicos) in [#11008](https://github.com/pydantic/pydantic/pull/11008) +* Fix host required enforcement for urls to be compatible with v2.9 behavior by [@sydney-runkle](https://github.com/sydney-runkle) in [#11027](https://github.com/pydantic/pydantic/pull/11027) +* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11034](https://github.com/pydantic/pydantic/pull/11034) +* Fix url json schema in `serialization` mode by [@sydney-runkle](https://github.com/sydney-runkle) in [#11035](https://github.com/pydantic/pydantic/pull/11035) + +## v2.10.2 (2024-11-25) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.2) + +### What's Changed + +#### Fixes + +* Only evaluate FieldInfo annotations if required during schema building by [@Viicos](https://github.com/Viicos) in [#10769](https://github.com/pydantic/pydantic/pull/10769) +* Do not evaluate annotations for private fields by [@Viicos](https://github.com/Viicos) in [#10962](https://github.com/pydantic/pydantic/pull/10962) +* Support serialization as any for `Secret` types and `Url` types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10947](https://github.com/pydantic/pydantic/pull/10947) +* Fix type hint of `Field.default` to be compatible with Python 3.8 and 3.9 by [@Viicos](https://github.com/Viicos) in [#10972](https://github.com/pydantic/pydantic/pull/10972) +* Add hashing support for URL types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10975](https://github.com/pydantic/pydantic/pull/10975) +* Hide `BaseModel.__replace__` definition from type checkers by [@Viicos](https://github.com/Viicos) in [#10979](https://github.com/pydantic/pydantic/pull/10979) + +## v2.10.1 (2024-11-21) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.1) + +### What's Changed + +#### Fixes + +* Use the correct frame when instantiating a parametrized `TypeAdapter` by [@Viicos](https://github.com/Viicos) in [#10893](https://github.com/pydantic/pydantic/pull/10893) +* Relax check for validated data in `default_factory` utils by [@sydney-runkle](https://github.com/sydney-runkle) in [#10909](https://github.com/pydantic/pydantic/pull/10909) +* Fix type checking issue with `model_fields` and `model_computed_fields` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10911](https://github.com/pydantic/pydantic/pull/10911) +* Use the parent configuration during schema generation for stdlib `dataclass`es by [@sydney-runkle](https://github.com/sydney-runkle) in [#10928](https://github.com/pydantic/pydantic/pull/10928) +* Use the `globals` of the function when evaluating the return type of serializers and `computed_field`s by [@Viicos](https://github.com/Viicos) in [#10929](https://github.com/pydantic/pydantic/pull/10929) +* Fix URL constraint application by [@sydney-runkle](https://github.com/sydney-runkle) in [#10922](https://github.com/pydantic/pydantic/pull/10922) +* Fix URL equality with different validation methods by [@sydney-runkle](https://github.com/sydney-runkle) in [#10934](https://github.com/pydantic/pydantic/pull/10934) +* Fix JSON schema title when specified as `''` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10936](https://github.com/pydantic/pydantic/pull/10936) +* Fix `python` mode serialization for `complex` inference by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic-core#1549](https://github.com/pydantic/pydantic-core/pull/1549) + +#### Packaging + +* Bump `pydantic-core` version to `v2.27.1` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10938](https://github.com/pydantic/pydantic/pull/10938) + +### New Contributors + +## v2.10.0 (2024-11-20) + +The code released in v2.10.0 is practically identical to that of v2.10.0b2. + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0) + +See the [v2.10 release blog post](https://pydantic.dev/articles/pydantic-v2-10-release) for the highlights! + +### What's Changed + +#### New Features + +* Support `fractions.Fraction` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10318](https://github.com/pydantic/pydantic/pull/10318) +* Support `Hashable` for json validation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10324](https://github.com/pydantic/pydantic/pull/10324) +* Add a `SocketPath` type for `linux` systems by [@theunkn0wn1](https://github.com/theunkn0wn1) in [#10378](https://github.com/pydantic/pydantic/pull/10378) +* Allow arbitrary refs in JSON schema `examples` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10417](https://github.com/pydantic/pydantic/pull/10417) +* Support `defer_build` for Pydantic dataclasses by [@Viicos](https://github.com/Viicos) in [#10313](https://github.com/pydantic/pydantic/pull/10313) +* Adding v1 / v2 incompatibility warning for nested v1 model by [@sydney-runkle](https://github.com/sydney-runkle) in [#10431](https://github.com/pydantic/pydantic/pull/10431) +* Add support for unpacked `TypedDict` to type hint variadic keyword arguments with `@validate_call` by [@Viicos](https://github.com/Viicos) in [#10416](https://github.com/pydantic/pydantic/pull/10416) +* Support compiled patterns in `protected_namespaces` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10522](https://github.com/pydantic/pydantic/pull/10522) +* Add support for `propertyNames` in JSON schema by [@FlorianSW](https://github.com/FlorianSW) in [#10478](https://github.com/pydantic/pydantic/pull/10478) +* Adding `__replace__` protocol for Python 3.13+ support by [@sydney-runkle](https://github.com/sydney-runkle) in [#10596](https://github.com/pydantic/pydantic/pull/10596) +* Expose public `sort` method for JSON schema generation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10595](https://github.com/pydantic/pydantic/pull/10595) +* Add runtime validation of `@validate_call` callable argument by [@kc0506](https://github.com/kc0506) in [#10627](https://github.com/pydantic/pydantic/pull/10627) +* Add `experimental_allow_partial` support by [@samuelcolvin](https://github.com/samuelcolvin) in [#10748](https://github.com/pydantic/pydantic/pull/10748) +* Support default factories taking validated data as an argument by [@Viicos](https://github.com/Viicos) in [#10678](https://github.com/pydantic/pydantic/pull/10678) +* Allow subclassing `ValidationError` and `PydanticCustomError` by [@Youssefares](https://github.com/Youssefares) in [pydantic/pydantic-core#1413](https://github.com/pydantic/pydantic-core/pull/1413) +* Add `trailing-strings` support to `experimental_allow_partial` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10825](https://github.com/pydantic/pydantic/pull/10825) +* Add `rebuild()` method for `TypeAdapter` and simplify `defer_build` patterns by [@sydney-runkle](https://github.com/sydney-runkle) in [#10537](https://github.com/pydantic/pydantic/pull/10537) +* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in [#10872](https://github.com/pydantic/pydantic/pull/10872) + +#### Changes + +* Don't allow customization of `SchemaGenerator` until interface is more stable by [@sydney-runkle](https://github.com/sydney-runkle) in [#10303](https://github.com/pydantic/pydantic/pull/10303) +* Cleanly `defer_build` on `TypeAdapters`, removing experimental flag by [@sydney-runkle](https://github.com/sydney-runkle) in [#10329](https://github.com/pydantic/pydantic/pull/10329) +* Fix `mro` of generic subclass by [@kc0506](https://github.com/kc0506) in [#10100](https://github.com/pydantic/pydantic/pull/10100) +* Strip whitespaces on JSON Schema title generation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10404](https://github.com/pydantic/pydantic/pull/10404) +* Use `b64decode` and `b64encode` for `Base64Bytes` type by [@sydney-runkle](https://github.com/sydney-runkle) in [#10486](https://github.com/pydantic/pydantic/pull/10486) +* Relax protected namespace config default by [@sydney-runkle](https://github.com/sydney-runkle) in [#10441](https://github.com/pydantic/pydantic/pull/10441) +* Revalidate parametrized generics if instance's origin is subclass of OG class by [@sydney-runkle](https://github.com/sydney-runkle) in [#10666](https://github.com/pydantic/pydantic/pull/10666) +* Warn if configuration is specified on the `@dataclass` decorator and with the `__pydantic_config__` attribute by [@sydney-runkle](https://github.com/sydney-runkle) in [#10406](https://github.com/pydantic/pydantic/pull/10406) +* Recommend against using `Ellipsis` (...) with `Field` by [@Viicos](https://github.com/Viicos) in [#10661](https://github.com/pydantic/pydantic/pull/10661) +* Migrate to subclassing instead of annotated approach for pydantic url types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10662](https://github.com/pydantic/pydantic/pull/10662) +* Change JSON schema generation of `Literal`s and `Enums` by [@Viicos](https://github.com/Viicos) in [#10692](https://github.com/pydantic/pydantic/pull/10692) +* Simplify unions involving `Any` or `Never` when replacing type variables by [@Viicos](https://github.com/Viicos) in [#10338](https://github.com/pydantic/pydantic/pull/10338) +* Do not require padding when decoding `base64` bytes by [@bschoenmaeckers](https://github.com/bschoenmaeckers) in [pydantic/pydantic-core#1448](https://github.com/pydantic/pydantic-core/pull/1448) +* Support dates all the way to 1BC by [@changhc](https://github.com/changhc) in [pydantic/speedate#77](https://github.com/pydantic/speedate/pull/77) + +#### Performance + +* Schema cleaning: skip unnecessary copies during schema walking by [@Viicos](https://github.com/Viicos) in [#10286](https://github.com/pydantic/pydantic/pull/10286) +* Refactor namespace logic for annotations evaluation by [@Viicos](https://github.com/Viicos) in [#10530](https://github.com/pydantic/pydantic/pull/10530) +* Improve email regexp on edge cases by [@AlekseyLobanov](https://github.com/AlekseyLobanov) in [#10601](https://github.com/pydantic/pydantic/pull/10601) +* `CoreMetadata` refactor with an emphasis on documentation, schema build time performance, and reducing complexity by [@sydney-runkle](https://github.com/sydney-runkle) in [#10675](https://github.com/pydantic/pydantic/pull/10675) + +#### Packaging + +* Bump `pydantic-core` to `v2.27.0` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10825](https://github.com/pydantic/pydantic/pull/10825) +* Replaced pdm with uv by [@frfahim](https://github.com/frfahim) in [#10727](https://github.com/pydantic/pydantic/pull/10727) + +#### Fixes + +* Remove guarding check on `computed_field` with `field_serializer` by [@nix010](https://github.com/nix010) in [#10390](https://github.com/pydantic/pydantic/pull/10390) +* Fix `Predicate` issue in `v2.9.0` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10321](https://github.com/pydantic/pydantic/pull/10321) +* Fixing `annotated-types` bound by [@sydney-runkle](https://github.com/sydney-runkle) in [#10327](https://github.com/pydantic/pydantic/pull/10327) +* Turn `tzdata` install requirement into optional `timezone` dependency by [@jakob-keller](https://github.com/jakob-keller) in [#10331](https://github.com/pydantic/pydantic/pull/10331) +* Use correct types namespace when building `namedtuple` core schemas by [@Viicos](https://github.com/Viicos) in [#10337](https://github.com/pydantic/pydantic/pull/10337) +* Fix evaluation of stringified annotations during namespace inspection by [@Viicos](https://github.com/Viicos) in [#10347](https://github.com/pydantic/pydantic/pull/10347) +* Fix `IncEx` type alias definition by [@Viicos](https://github.com/Viicos) in [#10339](https://github.com/pydantic/pydantic/pull/10339) +* Do not error when trying to evaluate annotations of private attributes by [@Viicos](https://github.com/Viicos) in [#10358](https://github.com/pydantic/pydantic/pull/10358) +* Fix nested type statement by [@kc0506](https://github.com/kc0506) in [#10369](https://github.com/pydantic/pydantic/pull/10369) +* Improve typing of `ModelMetaclass.mro` by [@Viicos](https://github.com/Viicos) in [#10372](https://github.com/pydantic/pydantic/pull/10372) +* Fix class access of deprecated `computed_field`s by [@Viicos](https://github.com/Viicos) in [#10391](https://github.com/pydantic/pydantic/pull/10391) +* Make sure `inspect.iscoroutinefunction` works on coroutines decorated with `@validate_call` by [@MovisLi](https://github.com/MovisLi) in [#10374](https://github.com/pydantic/pydantic/pull/10374) +* Fix `NameError` when using `validate_call` with PEP 695 on a class by [@kc0506](https://github.com/kc0506) in [#10380](https://github.com/pydantic/pydantic/pull/10380) +* Fix `ZoneInfo` with various invalid types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10408](https://github.com/pydantic/pydantic/pull/10408) +* Fix `PydanticUserError` on empty `model_config` with annotations by [@cdwilson](https://github.com/cdwilson) in [#10412](https://github.com/pydantic/pydantic/pull/10412) +* Fix variance issue in `_IncEx` type alias, only allow `True` by [@Viicos](https://github.com/Viicos) in [#10414](https://github.com/pydantic/pydantic/pull/10414) +* Fix serialization schema generation when using `PlainValidator` by [@Viicos](https://github.com/Viicos) in [#10427](https://github.com/pydantic/pydantic/pull/10427) +* Fix schema generation error when serialization schema holds references by [@Viicos](https://github.com/Viicos) in [#10444](https://github.com/pydantic/pydantic/pull/10444) +* Inline references if possible when generating schema for `json_schema_input_type` by [@Viicos](https://github.com/Viicos) in [#10439](https://github.com/pydantic/pydantic/pull/10439) +* Fix recursive arguments in `Representation` by [@Viicos](https://github.com/Viicos) in [#10480](https://github.com/pydantic/pydantic/pull/10480) +* Fix representation for builtin function types by [@kschwab](https://github.com/kschwab) in [#10479](https://github.com/pydantic/pydantic/pull/10479) +* Add python validators for decimal constraints (`max_digits` and `decimal_places`) by [@sydney-runkle](https://github.com/sydney-runkle) in [#10506](https://github.com/pydantic/pydantic/pull/10506) +* Only fetch `__pydantic_core_schema__` from the current class during schema generation by [@Viicos](https://github.com/Viicos) in [#10518](https://github.com/pydantic/pydantic/pull/10518) +* Fix `stacklevel` on deprecation warnings for `BaseModel` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10520](https://github.com/pydantic/pydantic/pull/10520) +* Fix warning `stacklevel` in `BaseModel.__init__` by [@Viicos](https://github.com/Viicos) in [#10526](https://github.com/pydantic/pydantic/pull/10526) +* Improve error handling for in-evaluable refs for discriminator application by [@sydney-runkle](https://github.com/sydney-runkle) in [#10440](https://github.com/pydantic/pydantic/pull/10440) +* Change the signature of `ConfigWrapper.core_config` to take the title directly by [@Viicos](https://github.com/Viicos) in [#10562](https://github.com/pydantic/pydantic/pull/10562) +* Do not use the previous config from the stack for dataclasses without config by [@Viicos](https://github.com/Viicos) in [#10576](https://github.com/pydantic/pydantic/pull/10576) +* Fix serialization for IP types with `mode='python'` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10594](https://github.com/pydantic/pydantic/pull/10594) +* Support constraint application for `Base64Etc` types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10584](https://github.com/pydantic/pydantic/pull/10584) +* Fix `validate_call` ignoring `Field` in `Annotated` by [@kc0506](https://github.com/kc0506) in [#10610](https://github.com/pydantic/pydantic/pull/10610) +* Raise an error when `Self` is invalid by [@kc0506](https://github.com/kc0506) in [#10609](https://github.com/pydantic/pydantic/pull/10609) +* Using `core_schema.InvalidSchema` instead of metadata injection + checks by [@sydney-runkle](https://github.com/sydney-runkle) in [#10523](https://github.com/pydantic/pydantic/pull/10523) +* Tweak type alias logic by [@kc0506](https://github.com/kc0506) in [#10643](https://github.com/pydantic/pydantic/pull/10643) +* Support usage of `type` with `typing.Self` and type aliases by [@kc0506](https://github.com/kc0506) in [#10621](https://github.com/pydantic/pydantic/pull/10621) +* Use overloads for `Field` and `PrivateAttr` functions by [@Viicos](https://github.com/Viicos) in [#10651](https://github.com/pydantic/pydantic/pull/10651) +* Clean up the `mypy` plugin implementation by [@Viicos](https://github.com/Viicos) in [#10669](https://github.com/pydantic/pydantic/pull/10669) +* Properly check for `typing_extensions` variant of `TypeAliasType` by [@Daraan](https://github.com/Daraan) in [#10713](https://github.com/pydantic/pydantic/pull/10713) +* Allow any mapping in `BaseModel.model_copy()` by [@Viicos](https://github.com/Viicos) in [#10751](https://github.com/pydantic/pydantic/pull/10751) +* Fix `isinstance` behavior for urls by [@sydney-runkle](https://github.com/sydney-runkle) in [#10766](https://github.com/pydantic/pydantic/pull/10766) +* Ensure `cached_property` can be set on Pydantic models by [@Viicos](https://github.com/Viicos) in [#10774](https://github.com/pydantic/pydantic/pull/10774) +* Fix equality checks for primitives in literals by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1459](https://github.com/pydantic/pydantic-core/pull/1459) +* Properly enforce `host_required` for URLs by [@Viicos](https://github.com/Viicos) in [pydantic/pydantic-core#1488](https://github.com/pydantic/pydantic-core/pull/1488) +* Fix when `coerce_numbers_to_str` enabled and string has invalid Unicode character by [@andrey-berenda](https://github.com/andrey-berenda) in [pydantic/pydantic-core#1515](https://github.com/pydantic/pydantic-core/pull/1515) +* Fix serializing `complex` values in `Enum`s by [@changhc](https://github.com/changhc) in [pydantic/pydantic-core#1524](https://github.com/pydantic/pydantic-core/pull/1524) +* Refactor `_typing_extra` module by [@Viicos](https://github.com/Viicos) in [#10725](https://github.com/pydantic/pydantic/pull/10725) +* Support intuitive equality for urls by [@sydney-runkle](https://github.com/sydney-runkle) in [#10798](https://github.com/pydantic/pydantic/pull/10798) +* Add `bytearray` to `TypeAdapter.validate_json` signature by [@samuelcolvin](https://github.com/samuelcolvin) in [#10802](https://github.com/pydantic/pydantic/pull/10802) +* Ensure class access of method descriptors is performed when used as a default with `Field` by [@Viicos](https://github.com/Viicos) in [#10816](https://github.com/pydantic/pydantic/pull/10816) +* Fix circular import with `validate_call` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10807](https://github.com/pydantic/pydantic/pull/10807) +* Fix error when using type aliases referencing other type aliases by [@Viicos](https://github.com/Viicos) in [#10809](https://github.com/pydantic/pydantic/pull/10809) +* Fix `IncEx` type alias to be compatible with mypy by [@Viicos](https://github.com/Viicos) in [#10813](https://github.com/pydantic/pydantic/pull/10813) +* Make `__signature__` a lazy property, do not deepcopy defaults by [@Viicos](https://github.com/Viicos) in [#10818](https://github.com/pydantic/pydantic/pull/10818) +* Make `__signature__` lazy for dataclasses, too by [@sydney-runkle](https://github.com/sydney-runkle) in [#10832](https://github.com/pydantic/pydantic/pull/10832) +* Subclass all single host url classes from `AnyUrl` to preserve behavior from v2.9 by [@sydney-runkle](https://github.com/sydney-runkle) in [#10856](https://github.com/pydantic/pydantic/pull/10856) + +### New Contributors + +* [@jakob-keller](https://github.com/jakob-keller) made their first contribution in [#10331](https://github.com/pydantic/pydantic/pull/10331) +* [@MovisLi](https://github.com/MovisLi) made their first contribution in [#10374](https://github.com/pydantic/pydantic/pull/10374) +* [@joaopalmeiro](https://github.com/joaopalmeiro) made their first contribution in [#10405](https://github.com/pydantic/pydantic/pull/10405) +* [@theunkn0wn1](https://github.com/theunkn0wn1) made their first contribution in [#10378](https://github.com/pydantic/pydantic/pull/10378) +* [@cdwilson](https://github.com/cdwilson) made their first contribution in [#10412](https://github.com/pydantic/pydantic/pull/10412) +* [@dlax](https://github.com/dlax) made their first contribution in [#10421](https://github.com/pydantic/pydantic/pull/10421) +* [@kschwab](https://github.com/kschwab) made their first contribution in [#10479](https://github.com/pydantic/pydantic/pull/10479) +* [@santibreo](https://github.com/santibreo) made their first contribution in [#10453](https://github.com/pydantic/pydantic/pull/10453) +* [@FlorianSW](https://github.com/FlorianSW) made their first contribution in [#10478](https://github.com/pydantic/pydantic/pull/10478) +* [@tkasuz](https://github.com/tkasuz) made their first contribution in [#10555](https://github.com/pydantic/pydantic/pull/10555) +* [@AlekseyLobanov](https://github.com/AlekseyLobanov) made their first contribution in [#10601](https://github.com/pydantic/pydantic/pull/10601) +* [@NiclasvanEyk](https://github.com/NiclasvanEyk) made their first contribution in [#10667](https://github.com/pydantic/pydantic/pull/10667) +* [@mschoettle](https://github.com/mschoettle) made their first contribution in [#10677](https://github.com/pydantic/pydantic/pull/10677) +* [@Daraan](https://github.com/Daraan) made their first contribution in [#10713](https://github.com/pydantic/pydantic/pull/10713) +* [@k4nar](https://github.com/k4nar) made their first contribution in [#10736](https://github.com/pydantic/pydantic/pull/10736) +* [@UriyaHarpeness](https://github.com/UriyaHarpeness) made their first contribution in [#10740](https://github.com/pydantic/pydantic/pull/10740) +* [@frfahim](https://github.com/frfahim) made their first contribution in [#10727](https://github.com/pydantic/pydantic/pull/10727) + +## v2.10.0b2 (2024-11-13) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0b2) for details. + +## v2.10.0b1 (2024-11-06) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0b1) for details. + + +... see [here](https://docs.pydantic.dev/changelog/#v0322-2019-08-17) for earlier changes. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD new file mode 100644 index 0000000..51852ce --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD @@ -0,0 +1,111 @@ +pydantic/__init__.py,sha256=5iEnJ4wHv1OEzdKQPzaKaZKfO4pSQAC65ODrYI6_S8Y,15812 +pydantic/_migration.py,sha256=VF73LRCUz3Irb5xVt13jb3NAcXVnEF6T1-J0OLfeZ5A,12160 +pydantic/alias_generators.py,sha256=KM1n3u4JfLSBl1UuYg3hoYHzXJD-yvgrnq8u1ccwh_A,2124 +pydantic/aliases.py,sha256=vhCHyoSWnX-EJ-wWb5qj4xyRssgGWnTQfzQp4GSZ9ug,4937 +pydantic/annotated_handlers.py,sha256=WfyFSqwoEIFXBh7T73PycKloI1DiX45GWi0-JOsCR4Y,4407 +pydantic/class_validators.py,sha256=i_V3j-PYdGLSLmj_IJZekTRjunO8SIVz8LMlquPyP7E,148 +pydantic/color.py,sha256=AzqGfVQHF92_ZctDcue0DM4yTp2P6tekkwRINTWrLIo,21481 +pydantic/config.py,sha256=o1P67FMWIQG-_RfGtKislkHwo4pXm_6jfsR41P88v78,44533 +pydantic/dataclasses.py,sha256=4X9We0jj1KLwBtvYSkAFXNon46zrpBmRZANf4LfwbXg,18963 +pydantic/datetime_parse.py,sha256=QC-WgMxMr_wQ_mNXUS7AVf-2hLEhvvsPY1PQyhSGOdk,150 +pydantic/decorator.py,sha256=YX-jUApu5AKaVWKPoaV-n-4l7UbS69GEt9Ra3hszmKI,145 +pydantic/env_settings.py,sha256=6IHeeWEqlUPRUv3V-AXiF_W91fg2Jw_M3O0l34J_eyA,148 +pydantic/error_wrappers.py,sha256=RK6mqATc9yMD-KBD9IJS9HpKCprWHd8wo84Bnm-3fR8,150 +pydantic/errors.py,sha256=DrECPCWhSYrQ8Ba4O8hKzIAM2i9GBHTXWALzaFDpLf4,6013 +pydantic/fields.py,sha256=a8ZhJfI8FD6TQDD7-cWMm1ZXlQva0wMdT2Ey5VvVe-M,82023 +pydantic/functional_serializers.py,sha256=zwRAjZusORtEbtxpVU20kg8FpemyZz4Fq6wJk1mpwYQ,18117 +pydantic/functional_validators.py,sha256=7p-4jvP__9jZyvcdXiide5pAa_JlM7fJ1BgkqbjxSWM,31724 +pydantic/generics.py,sha256=0ZqZ9O9annIj_3mGBRqps4htey3b5lV1-d2tUxPMMnA,144 +pydantic/json.py,sha256=ZH8RkI7h4Bz-zp8OdTAxbJUoVvcoU-jhMdRZ0B-k0xc,140 +pydantic/json_schema.py,sha256=da3hQ9vQPLEhPsrDnUlijfQ1fuCCViGVnUzOrAZLgDs,125955 +pydantic/main.py,sha256=NbhCz-ku8wDgYLQMBi75Ov7hywdc8LewA3oUNIZ8JVQ,85334 +pydantic/mypy.py,sha256=ypnhh7hn-L-mk6sW4CcrdV2C5lyMzcM6FTBj21GufsE,61074 +pydantic/networks.py,sha256=gCB96gt0G7tiVDhVnJfpKr1ARL5qkH-SPZkuNkmG2O4,42102 +pydantic/parse.py,sha256=wkd82dgtvWtD895U_I6E1htqMlGhBSYEV39cuBSeo3A,141 +pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/root_model.py,sha256=cOMoeWdp536KF85uRIcW-oz1O7d5GRCpx9HTS4-1uf8,6394 +pydantic/schema.py,sha256=Vqqjvq_LnapVknebUd3Bp_J1p2gXZZnZRgL48bVEG7o,142 +pydantic/tools.py,sha256=iHQpd8SJ5DCTtPV5atAV06T89bjSaMFeZZ2LX9lasZY,141 +pydantic/type_adapter.py,sha256=T05g8WQczBsVU_35RdKxgjIi7Y7LIip67pF3NI7X4GE,36123 +pydantic/types.py,sha256=fpYcGnAncK4QjaFm3jZtqYwxJYzB7rJJPfAl89emzyQ,105961 +pydantic/typing.py,sha256=P7feA35MwTcLsR1uL7db0S-oydBxobmXa55YDoBgajQ,138 +pydantic/utils.py,sha256=15nR2QpqTBFlQV4TNtTItMyTJx_fbyV-gPmIEY1Gooc,141 +pydantic/validate_call_decorator.py,sha256=VLAi4hoFpjC-1eL0HixYaaWaEFO6htGcDctgsqa5VII,4416 +pydantic/validators.py,sha256=pwbIJXVb1CV2mAE4w_EGfNj7DwzsKaWw_tTL6cviTus,146 +pydantic/version.py,sha256=uKOxh0LjiZkZ0KfLmlpPYRAY-_NpIX8ijxOLDNhW8m8,3985 +pydantic/warnings.py,sha256=3QyQo6lN35cO7OXDbFEXWcNaPlGCRaregVZA-G-lZwI,4822 +pydantic/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/_internal/_config.py,sha256=Rzys1Joffn4JczElcYDqsZLRgBgHn2lYWqDR55oASPA,14839 +pydantic/_internal/_core_metadata.py,sha256=Y_g2t3i7uluK-wXCZvzJfRFMPUM23aBYLfae4FzBPy0,5162 +pydantic/_internal/_core_utils.py,sha256=1jru4VbJ0x63R6dtVcuOI-dKQTC_d_lSnJWEBQzGNEQ,6487 +pydantic/_internal/_dataclasses.py,sha256=Zgqcm1WaJLBwTQQC5mGKNowjlTgX3mfX_J5e2vd24lM,13188 +pydantic/_internal/_decorators.py,sha256=RDEG_Jau5NiJcfO0xgdT7EOgsU1LgWIYlX7wN5rYtVs,33620 +pydantic/_internal/_decorators_v1.py,sha256=tfdfdpQKY4R2XCOwqHbZeoQMur6VNigRrfhudXBHx38,6185 +pydantic/_internal/_discriminated_union.py,sha256=JLx_MVLep7Mxl1zbpdNZjvHDcz-J3OEW6WcdV184dcM,26255 +pydantic/_internal/_docs_extraction.py,sha256=fyznSAHh5AzohnXZStV0HvH-nRbavNHPyg-knx-S_EE,4127 +pydantic/_internal/_fields.py,sha256=hXeb-zodGwTDvG9OK1um18P64b7cqHb0GOngP4jrgcY,31557 +pydantic/_internal/_forward_ref.py,sha256=5n3Y7-3AKLn8_FS3Yc7KutLiPUhyXmAtkEZOaFnonwM,611 +pydantic/_internal/_generate_schema.py,sha256=PjogUawIXmf8LuLNe9seJph7WLW4MJ7-GBsXwCsQC9Q,136348 +pydantic/_internal/_generics.py,sha256=CXjcInlvci8VejaWn1f39kv0AcfJL0R523qRLFZVD-s,23393 +pydantic/_internal/_git.py,sha256=IwPh3DPfa2Xq3rBuB9Nx8luR2A1i69QdeTfWWXIuCVg,809 +pydantic/_internal/_import_utils.py,sha256=TRhxD5OuY6CUosioBdBcJUs0om7IIONiZdYAV7zQ8jM,402 +pydantic/_internal/_internal_dataclass.py,sha256=_bedc1XbuuygRGiLZqkUkwwFpQaoR1hKLlR501nyySY,144 +pydantic/_internal/_known_annotated_metadata.py,sha256=PynQIFQ61__4Gcrzn0D5ENllg7jPq_cxoLTmuFQBY88,16805 +pydantic/_internal/_mock_val_ser.py,sha256=wmRRFSBvqfcLbI41PsFliB4u2AZ3mJpZeiERbD3xKTo,8885 +pydantic/_internal/_model_construction.py,sha256=JoKmY4JrDBu3nG_tCIrJgtJJE1uq6v29TvTz5ElHE5g,38928 +pydantic/_internal/_namespace_utils.py,sha256=hl3-TRAr82U2jTyPP3t-QqsvKLirxtkLfNfrN-fp0x8,12878 +pydantic/_internal/_repr.py,sha256=jQfnJuyDxQpSRNhG29II9PX8e4Nv2qWZrEw2lqih3UE,5172 +pydantic/_internal/_schema_gather.py,sha256=8nJ-uM6Y4z6xpasnGonEMubtNVX_mxeeRDFmd_qMVLA,9052 +pydantic/_internal/_schema_generation_shared.py,sha256=F_rbQbrkoomgxsskdHpP0jUJ7TCfe0BADAEkq6CJ4nM,4842 +pydantic/_internal/_serializers.py,sha256=YIWvSmAR5fnbGSWCOQduWt1yB4ZQY42eAruc-enrb6c,1491 +pydantic/_internal/_signature.py,sha256=i_b6wtluiVWZRh1ZY8UvB2UZziP1KjqSXZgC-HxwOT0,6808 +pydantic/_internal/_typing_extra.py,sha256=dDxqF46lzuqCoKLrAH_k95EDbayEeKb2lHjuTJ5OBoY,31574 +pydantic/_internal/_utils.py,sha256=gN48BsR-FDrJDibCmo69ttQg67WbuFrdy_1NQL3cvLI,15959 +pydantic/_internal/_validate_call.py,sha256=OD_BspHaL9FKzZ9XrndhiEuMnjF3SRIJUHtwv6yUffU,5366 +pydantic/_internal/_validators.py,sha256=7GTjXXWFMLib4dxQ-HeaiHlAZiR2B2G8byCYMGrmQ48,20563 +pydantic/deprecated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/deprecated/class_validators.py,sha256=n0jYQOcb5YQiw0b7YXyi7NPiYdV7ujWR4KyZumjTPok,10281 +pydantic/deprecated/config.py,sha256=k_lsVk57paxLJOcBueH07cu1OgEgWdVBxm6lfaC3CCU,2663 +pydantic/deprecated/copy_internals.py,sha256=Ghd-vkMd5EYCCgyCGtPKO58np9cEKBQC6qkBeIEFI2g,7618 +pydantic/deprecated/decorator.py,sha256=TBm6bJ7wJsNih_8Wq5IzDcwP32m9_vfxs96desLuk00,10845 +pydantic/deprecated/json.py,sha256=HlWCG35RRrxyzuTS6LTQiZBwRhmDZWmeqQH8rLW6wA8,4657 +pydantic/deprecated/parse.py,sha256=Gzd6b_g8zJXcuE7QRq5adhx_EMJahXfcpXCF0RgrqqI,2511 +pydantic/deprecated/tools.py,sha256=Nrm9oFRZWp8-jlfvPgJILEsywp4YzZD52XIGPDLxHcI,3330 +pydantic/experimental/__init__.py,sha256=QT7rKYdDsCiTJ9GEjmsQdWHScwpKrrNkGq6vqONP6RQ,104 +pydantic/experimental/arguments_schema.py,sha256=EFnjX_ulp-tPyUjQX5pmQtug1OFL_Acc8bcMbLd-fVY,1866 +pydantic/experimental/missing_sentinel.py,sha256=hQejgtF00wUuQMni9429evg-eXyIwpKvjsD8ofqfj-w,127 +pydantic/experimental/pipeline.py,sha256=auoW6l6g1FC41LciPmsI1M6ncf00Szde9B3C-yvb9mI,23956 +pydantic/plugin/__init__.py,sha256=a7Tw366U6K3kltCCNZY76nc9ss-7uGGQ40TXad9OypQ,7333 +pydantic/plugin/_loader.py,sha256=hAjgSljoIhGx3AVpIpuqw5SPttBNNeGBSTrqSMnNiJk,2213 +pydantic/plugin/_schema_validator.py,sha256=thbZz_-5sYPzxsC5uWK5GDfYcmmrsecC8Vw_A02mhwU,5815 +pydantic/v1/__init__.py,sha256=SxQPklgBs4XHJwE6BZ9qoewYoGiNyYUnmHzEFCZbfnI,2946 +pydantic/v1/_hypothesis_plugin.py,sha256=5ES5xWuw1FQAsymLezy8QgnVz0ZpVfU3jkmT74H27VQ,14847 +pydantic/v1/annotated_types.py,sha256=uk2NAAxqiNELKjiHhyhxKaIOh8F1lYW_LzrW3X7oZBc,3157 +pydantic/v1/class_validators.py,sha256=ULOaIUgYUDBsHL7EEVEarcM-UubKUggoN8hSbDonsFE,14672 +pydantic/v1/color.py,sha256=iZABLYp6OVoo2AFkP9Ipri_wSc6-Kklu8YuhSartd5g,16844 +pydantic/v1/config.py,sha256=a6P0Wer9x4cbwKW7Xv8poSUqM4WP-RLWwX6YMpYq9AA,6532 +pydantic/v1/dataclasses.py,sha256=784cqvInbwIPWr9usfpX3ch7z4t3J2tTK6N067_wk1o,18172 +pydantic/v1/datetime_parse.py,sha256=4Qy1kQpq3rNVZJeIHeSPDpuS2Bvhp1KPtzJG1xu-H00,7724 +pydantic/v1/decorator.py,sha256=zaaxxxoWPCm818D1bs0yhapRjXm32V8G0ZHWCdM1uXA,10339 +pydantic/v1/env_settings.py,sha256=A9VXwtRl02AY-jH0C0ouy5VNw3fi6F_pkzuHDjgAAOM,14105 +pydantic/v1/error_wrappers.py,sha256=6625Mfw9qkC2NwitB_JFAWe8B-Xv6zBU7rL9k28tfyo,5196 +pydantic/v1/errors.py,sha256=mIwPED5vGM5Q5v4C4Z1JPldTRH-omvEylH6ksMhOmPw,17726 +pydantic/v1/fields.py,sha256=VqWJCriUNiEyptXroDVJ501JpVA0en2VANcksqXL2b8,50649 +pydantic/v1/generics.py,sha256=YzyKTZN6x5Q1RGJ3WQ9jN-uwHJxL3W4qoZqwcZXqxWg,17829 +pydantic/v1/json.py,sha256=WQ5Hy_hIpfdR3YS8k6N2E6KMJzsdbBi_ldWOPJaV81M,3390 +pydantic/v1/main.py,sha256=vRB1TbpkzPN3P5ijJlc-cjNuO-HciNOpC4b8K3zZnfc,45697 +pydantic/v1/mypy.py,sha256=Cl8XRfCmIcVE3j5AEU52C8iDh8lcX__D3hz2jIWxMAs,38860 +pydantic/v1/networks.py,sha256=HYNtKAfOmOnKJpsDg1g6SIkj9WPhU_-i8l5e2JKBpG4,22124 +pydantic/v1/parse.py,sha256=BJtdqiZRtav9VRFCmOxoY-KImQmjPy-A_NoojiFUZxY,1821 +pydantic/v1/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/v1/schema.py,sha256=aqBuA--cq8gAVkim5BJPFASHzOZ8dFtmFX_fNGr6ip4,47801 +pydantic/v1/tools.py,sha256=1lDdXHk0jL5uP3u5RCYAvUAlGClgAO-45lkq9j7fyBA,2881 +pydantic/v1/types.py,sha256=Bzl-RcnitPBHnqwwj9iv7JjHuN1GpnWH24dKkF3l9e8,35455 +pydantic/v1/typing.py,sha256=ovwtLpEZCbnghZaHfSNJupzetzHNkLXjn_66kgTnIV4,20102 +pydantic/v1/utils.py,sha256=1PqOIlz6OVWwGds3HBKlw4Et6asFou0UUpAto7jFOCs,26014 +pydantic/v1/validators.py,sha256=lyUkn1MWhHxlCX5ZfEgFj_CAHojoiPcaQeMdEM9XviU,22187 +pydantic/v1/version.py,sha256=YpHWOQKtGoxfyikzGrcmXJVKUVYB9EBdoCR994QxSnE,1039 +pydantic-2.13.5.dist-info/METADATA,sha256=BoWDDRNke9bz8FUmBD0yqLKiIdhrU3cwU9bFK7F6nAc,110178 +pydantic-2.13.5.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87 +pydantic-2.13.5.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129 +pydantic-2.13.5.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL new file mode 100644 index 0000000..51abe1f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.32.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE new file mode 100644 index 0000000..488c626 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 to present Pydantic Services Inc. and individual contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py new file mode 100644 index 0000000..0121284 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py @@ -0,0 +1,456 @@ +from importlib import import_module +from typing import TYPE_CHECKING +from warnings import warn + +from ._migration import getattr_migration +from .version import VERSION, _ensure_pydantic_core_version + +_ensure_pydantic_core_version() +del _ensure_pydantic_core_version + +if TYPE_CHECKING: + # import of virtually everything is supported via `__getattr__` below, + # but we need them here for type checking and IDE support + import pydantic_core + from pydantic_core.core_schema import ( + FieldSerializationInfo, + SerializationInfo, + SerializerFunctionWrapHandler, + ValidationInfo, + ValidatorFunctionWrapHandler, + ) + + from . import dataclasses + from .aliases import AliasChoices, AliasGenerator, AliasPath + from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler + from .config import ConfigDict, with_config + from .errors import * + from .fields import Field, PrivateAttr, computed_field + from .functional_serializers import ( + PlainSerializer, + SerializeAsAny, + WrapSerializer, + field_serializer, + model_serializer, + ) + from .functional_validators import ( + AfterValidator, + BeforeValidator, + InstanceOf, + ModelWrapValidatorHandler, + PlainValidator, + SkipValidation, + ValidateAs, + WrapValidator, + field_validator, + model_validator, + ) + from .json_schema import WithJsonSchema + from .main import * + from .networks import * + from .type_adapter import TypeAdapter + from .types import * + from .validate_call_decorator import validate_call + from .warnings import ( + PydanticDeprecatedSince20, + PydanticDeprecatedSince26, + PydanticDeprecatedSince29, + PydanticDeprecatedSince210, + PydanticDeprecatedSince211, + PydanticDeprecatedSince212, + PydanticDeprecationWarning, + PydanticExperimentalWarning, + ) + + # this encourages pycharm to import `ValidationError` from here, not pydantic_core + ValidationError = pydantic_core.ValidationError + from .deprecated.class_validators import root_validator, validator + from .deprecated.config import BaseConfig, Extra + from .deprecated.tools import * + from .root_model import RootModel + +__version__ = VERSION +__all__ = ( + # dataclasses + 'dataclasses', + # functional validators + 'field_validator', + 'model_validator', + 'AfterValidator', + 'BeforeValidator', + 'PlainValidator', + 'WrapValidator', + 'SkipValidation', + 'ValidateAs', + 'InstanceOf', + 'ModelWrapValidatorHandler', + # JSON Schema + 'WithJsonSchema', + # deprecated V1 functional validators, these are imported via `__getattr__` below + 'root_validator', + 'validator', + # functional serializers + 'field_serializer', + 'model_serializer', + 'PlainSerializer', + 'SerializeAsAny', + 'WrapSerializer', + # config + 'ConfigDict', + 'with_config', + # deprecated V1 config, these are imported via `__getattr__` below + 'BaseConfig', + 'Extra', + # validate_call + 'validate_call', + # errors + 'PydanticErrorCodes', + 'PydanticUserError', + 'PydanticSchemaGenerationError', + 'PydanticImportError', + 'PydanticUndefinedAnnotation', + 'PydanticInvalidForJsonSchema', + 'PydanticForbiddenQualifier', + # fields + 'Field', + 'computed_field', + 'PrivateAttr', + # alias + 'AliasChoices', + 'AliasGenerator', + 'AliasPath', + # main + 'BaseModel', + 'create_model', + # network + 'AnyUrl', + 'AnyHttpUrl', + 'FileUrl', + 'HttpUrl', + 'FtpUrl', + 'WebsocketUrl', + 'AnyWebsocketUrl', + 'UrlConstraints', + 'EmailStr', + 'NameEmail', + 'IPvAnyAddress', + 'IPvAnyInterface', + 'IPvAnyNetwork', + 'PostgresDsn', + 'CockroachDsn', + 'AmqpDsn', + 'RedisDsn', + 'MongoDsn', + 'KafkaDsn', + 'NatsDsn', + 'MySQLDsn', + 'MariaDBDsn', + 'ClickHouseDsn', + 'SnowflakeDsn', + 'validate_email', + # root_model + 'RootModel', + # deprecated tools, these are imported via `__getattr__` below + 'parse_obj_as', + 'schema_of', + 'schema_json_of', + # types + 'Strict', + 'StrictStr', + 'conbytes', + 'conlist', + 'conset', + 'confrozenset', + 'constr', + 'StringConstraints', + 'ImportString', + 'conint', + 'PositiveInt', + 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', + 'confloat', + 'PositiveFloat', + 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', + 'FiniteFloat', + 'condecimal', + 'condate', + 'UUID1', + 'UUID3', + 'UUID4', + 'UUID5', + 'UUID6', + 'UUID7', + 'UUID8', + 'FilePath', + 'DirectoryPath', + 'NewPath', + 'Json', + 'Secret', + 'SecretStr', + 'SecretBytes', + 'SocketPath', + 'StrictBool', + 'StrictBytes', + 'StrictInt', + 'StrictFloat', + 'PaymentCardNumber', + 'ByteSize', + 'PastDate', + 'FutureDate', + 'PastDatetime', + 'FutureDatetime', + 'AwareDatetime', + 'NaiveDatetime', + 'AllowInfNan', + 'EncoderProtocol', + 'EncodedBytes', + 'EncodedStr', + 'Base64Encoder', + 'Base64Bytes', + 'Base64Str', + 'Base64UrlBytes', + 'Base64UrlStr', + 'GetPydanticSchema', + 'Tag', + 'Discriminator', + 'JsonValue', + 'FailFast', + # type_adapter + 'TypeAdapter', + # version + '__version__', + 'VERSION', + # warnings + 'PydanticDeprecatedSince20', + 'PydanticDeprecatedSince26', + 'PydanticDeprecatedSince29', + 'PydanticDeprecatedSince210', + 'PydanticDeprecatedSince211', + 'PydanticDeprecatedSince212', + 'PydanticDeprecationWarning', + 'PydanticExperimentalWarning', + # annotated handlers + 'GetCoreSchemaHandler', + 'GetJsonSchemaHandler', + # pydantic_core + 'ValidationError', + 'ValidationInfo', + 'SerializationInfo', + 'ValidatorFunctionWrapHandler', + 'FieldSerializationInfo', + 'SerializerFunctionWrapHandler', + 'OnErrorOmit', +) + +# A mapping of {: (package, )} defining dynamic imports +_dynamic_imports: 'dict[str, tuple[str, str]]' = { + 'dataclasses': (__spec__.parent, '__module__'), + # functional validators + 'field_validator': (__spec__.parent, '.functional_validators'), + 'model_validator': (__spec__.parent, '.functional_validators'), + 'AfterValidator': (__spec__.parent, '.functional_validators'), + 'BeforeValidator': (__spec__.parent, '.functional_validators'), + 'PlainValidator': (__spec__.parent, '.functional_validators'), + 'WrapValidator': (__spec__.parent, '.functional_validators'), + 'SkipValidation': (__spec__.parent, '.functional_validators'), + 'InstanceOf': (__spec__.parent, '.functional_validators'), + 'ValidateAs': (__spec__.parent, '.functional_validators'), + 'ModelWrapValidatorHandler': (__spec__.parent, '.functional_validators'), + # JSON Schema + 'WithJsonSchema': (__spec__.parent, '.json_schema'), + # functional serializers + 'field_serializer': (__spec__.parent, '.functional_serializers'), + 'model_serializer': (__spec__.parent, '.functional_serializers'), + 'PlainSerializer': (__spec__.parent, '.functional_serializers'), + 'SerializeAsAny': (__spec__.parent, '.functional_serializers'), + 'WrapSerializer': (__spec__.parent, '.functional_serializers'), + # config + 'ConfigDict': (__spec__.parent, '.config'), + 'with_config': (__spec__.parent, '.config'), + # validate call + 'validate_call': (__spec__.parent, '.validate_call_decorator'), + # errors + 'PydanticErrorCodes': (__spec__.parent, '.errors'), + 'PydanticUserError': (__spec__.parent, '.errors'), + 'PydanticSchemaGenerationError': (__spec__.parent, '.errors'), + 'PydanticImportError': (__spec__.parent, '.errors'), + 'PydanticUndefinedAnnotation': (__spec__.parent, '.errors'), + 'PydanticInvalidForJsonSchema': (__spec__.parent, '.errors'), + 'PydanticForbiddenQualifier': (__spec__.parent, '.errors'), + # fields + 'Field': (__spec__.parent, '.fields'), + 'computed_field': (__spec__.parent, '.fields'), + 'PrivateAttr': (__spec__.parent, '.fields'), + # alias + 'AliasChoices': (__spec__.parent, '.aliases'), + 'AliasGenerator': (__spec__.parent, '.aliases'), + 'AliasPath': (__spec__.parent, '.aliases'), + # main + 'BaseModel': (__spec__.parent, '.main'), + 'create_model': (__spec__.parent, '.main'), + # network + 'AnyUrl': (__spec__.parent, '.networks'), + 'AnyHttpUrl': (__spec__.parent, '.networks'), + 'FileUrl': (__spec__.parent, '.networks'), + 'HttpUrl': (__spec__.parent, '.networks'), + 'FtpUrl': (__spec__.parent, '.networks'), + 'WebsocketUrl': (__spec__.parent, '.networks'), + 'AnyWebsocketUrl': (__spec__.parent, '.networks'), + 'UrlConstraints': (__spec__.parent, '.networks'), + 'EmailStr': (__spec__.parent, '.networks'), + 'NameEmail': (__spec__.parent, '.networks'), + 'IPvAnyAddress': (__spec__.parent, '.networks'), + 'IPvAnyInterface': (__spec__.parent, '.networks'), + 'IPvAnyNetwork': (__spec__.parent, '.networks'), + 'PostgresDsn': (__spec__.parent, '.networks'), + 'CockroachDsn': (__spec__.parent, '.networks'), + 'AmqpDsn': (__spec__.parent, '.networks'), + 'RedisDsn': (__spec__.parent, '.networks'), + 'MongoDsn': (__spec__.parent, '.networks'), + 'KafkaDsn': (__spec__.parent, '.networks'), + 'NatsDsn': (__spec__.parent, '.networks'), + 'MySQLDsn': (__spec__.parent, '.networks'), + 'MariaDBDsn': (__spec__.parent, '.networks'), + 'ClickHouseDsn': (__spec__.parent, '.networks'), + 'SnowflakeDsn': (__spec__.parent, '.networks'), + 'validate_email': (__spec__.parent, '.networks'), + # root_model + 'RootModel': (__spec__.parent, '.root_model'), + # types + 'Strict': (__spec__.parent, '.types'), + 'StrictStr': (__spec__.parent, '.types'), + 'conbytes': (__spec__.parent, '.types'), + 'conlist': (__spec__.parent, '.types'), + 'conset': (__spec__.parent, '.types'), + 'confrozenset': (__spec__.parent, '.types'), + 'constr': (__spec__.parent, '.types'), + 'StringConstraints': (__spec__.parent, '.types'), + 'ImportString': (__spec__.parent, '.types'), + 'conint': (__spec__.parent, '.types'), + 'PositiveInt': (__spec__.parent, '.types'), + 'NegativeInt': (__spec__.parent, '.types'), + 'NonNegativeInt': (__spec__.parent, '.types'), + 'NonPositiveInt': (__spec__.parent, '.types'), + 'confloat': (__spec__.parent, '.types'), + 'PositiveFloat': (__spec__.parent, '.types'), + 'NegativeFloat': (__spec__.parent, '.types'), + 'NonNegativeFloat': (__spec__.parent, '.types'), + 'NonPositiveFloat': (__spec__.parent, '.types'), + 'FiniteFloat': (__spec__.parent, '.types'), + 'condecimal': (__spec__.parent, '.types'), + 'condate': (__spec__.parent, '.types'), + 'UUID1': (__spec__.parent, '.types'), + 'UUID3': (__spec__.parent, '.types'), + 'UUID4': (__spec__.parent, '.types'), + 'UUID5': (__spec__.parent, '.types'), + 'UUID6': (__spec__.parent, '.types'), + 'UUID7': (__spec__.parent, '.types'), + 'UUID8': (__spec__.parent, '.types'), + 'FilePath': (__spec__.parent, '.types'), + 'DirectoryPath': (__spec__.parent, '.types'), + 'NewPath': (__spec__.parent, '.types'), + 'Json': (__spec__.parent, '.types'), + 'Secret': (__spec__.parent, '.types'), + 'SecretStr': (__spec__.parent, '.types'), + 'SecretBytes': (__spec__.parent, '.types'), + 'StrictBool': (__spec__.parent, '.types'), + 'StrictBytes': (__spec__.parent, '.types'), + 'StrictInt': (__spec__.parent, '.types'), + 'StrictFloat': (__spec__.parent, '.types'), + 'PaymentCardNumber': (__spec__.parent, '.types'), + 'ByteSize': (__spec__.parent, '.types'), + 'PastDate': (__spec__.parent, '.types'), + 'SocketPath': (__spec__.parent, '.types'), + 'FutureDate': (__spec__.parent, '.types'), + 'PastDatetime': (__spec__.parent, '.types'), + 'FutureDatetime': (__spec__.parent, '.types'), + 'AwareDatetime': (__spec__.parent, '.types'), + 'NaiveDatetime': (__spec__.parent, '.types'), + 'AllowInfNan': (__spec__.parent, '.types'), + 'EncoderProtocol': (__spec__.parent, '.types'), + 'EncodedBytes': (__spec__.parent, '.types'), + 'EncodedStr': (__spec__.parent, '.types'), + 'Base64Encoder': (__spec__.parent, '.types'), + 'Base64Bytes': (__spec__.parent, '.types'), + 'Base64Str': (__spec__.parent, '.types'), + 'Base64UrlBytes': (__spec__.parent, '.types'), + 'Base64UrlStr': (__spec__.parent, '.types'), + 'GetPydanticSchema': (__spec__.parent, '.types'), + 'Tag': (__spec__.parent, '.types'), + 'Discriminator': (__spec__.parent, '.types'), + 'JsonValue': (__spec__.parent, '.types'), + 'OnErrorOmit': (__spec__.parent, '.types'), + 'FailFast': (__spec__.parent, '.types'), + # type_adapter + 'TypeAdapter': (__spec__.parent, '.type_adapter'), + # warnings + 'PydanticDeprecatedSince20': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince26': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince29': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince210': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince211': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince212': (__spec__.parent, '.warnings'), + 'PydanticDeprecationWarning': (__spec__.parent, '.warnings'), + 'PydanticExperimentalWarning': (__spec__.parent, '.warnings'), + # annotated handlers + 'GetCoreSchemaHandler': (__spec__.parent, '.annotated_handlers'), + 'GetJsonSchemaHandler': (__spec__.parent, '.annotated_handlers'), + # pydantic_core stuff + 'ValidationError': ('pydantic_core', '.'), + 'ValidationInfo': ('pydantic_core', '.core_schema'), + 'SerializationInfo': ('pydantic_core', '.core_schema'), + 'ValidatorFunctionWrapHandler': ('pydantic_core', '.core_schema'), + 'FieldSerializationInfo': ('pydantic_core', '.core_schema'), + 'SerializerFunctionWrapHandler': ('pydantic_core', '.core_schema'), + # deprecated, mostly not included in __all__ + 'root_validator': (__spec__.parent, '.deprecated.class_validators'), + 'validator': (__spec__.parent, '.deprecated.class_validators'), + 'BaseConfig': (__spec__.parent, '.deprecated.config'), + 'Extra': (__spec__.parent, '.deprecated.config'), + 'parse_obj_as': (__spec__.parent, '.deprecated.tools'), + 'schema_of': (__spec__.parent, '.deprecated.tools'), + 'schema_json_of': (__spec__.parent, '.deprecated.tools'), + # deprecated dynamic imports + 'FieldValidationInfo': ('pydantic_core', '.core_schema'), + 'GenerateSchema': (__spec__.parent, '._internal._generate_schema'), +} +_deprecated_dynamic_imports = {'FieldValidationInfo', 'GenerateSchema'} + +_getattr_migration = getattr_migration(__name__) + + +def __getattr__(attr_name: str) -> object: + if attr_name in _deprecated_dynamic_imports: + from pydantic.warnings import PydanticDeprecatedSince20 + + warn( + f'Importing {attr_name} from `pydantic` is deprecated. This feature is either no longer supported, or is not public.', + PydanticDeprecatedSince20, + stacklevel=2, + ) + + dynamic_attr = _dynamic_imports.get(attr_name) + if dynamic_attr is None: + return _getattr_migration(attr_name) + + package, module_name = dynamic_attr + + if module_name == '__module__': + result = import_module(f'.{attr_name}', package=package) + globals()[attr_name] = result + return result + else: + module = import_module(module_name, package=package) + result = getattr(module, attr_name) + g = globals() + for k, (_, v_module_name) in _dynamic_imports.items(): + if v_module_name == module_name and k not in _deprecated_dynamic_imports: + g[k] = getattr(module, k) + return result + + +def __dir__() -> list[str]: + return list(__all__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py new file mode 100644 index 0000000..73b5870 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py @@ -0,0 +1,386 @@ +from __future__ import annotations as _annotations + +import warnings +from contextlib import contextmanager +from re import Pattern +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Literal, + cast, +) + +from pydantic_core import core_schema +from typing_extensions import Self + +from ..aliases import AliasGenerator +from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable +from ..errors import PydanticUserError +from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210 + +if TYPE_CHECKING: + from .._internal._schema_generation_shared import GenerateSchema + from ..fields import ComputedFieldInfo, FieldInfo + +DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.' + + +class ConfigWrapper: + """Internal wrapper for Config which exposes ConfigDict items as attributes.""" + + __slots__ = ('config_dict',) + + config_dict: ConfigDict + + # all annotations are copied directly from ConfigDict, and should be kept up to date, a test will fail if they + # stop matching + title: str | None + str_to_lower: bool + str_to_upper: bool + str_strip_whitespace: bool + str_min_length: int + str_max_length: int | None + extra: ExtraValues | None + frozen: bool + populate_by_name: bool + use_enum_values: bool + validate_assignment: bool + arbitrary_types_allowed: bool + from_attributes: bool + # whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names + # to construct error `loc`s, default `True` + loc_by_alias: bool + alias_generator: Callable[[str], str] | AliasGenerator | None + model_title_generator: Callable[[type], str] | None + field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None + ignored_types: tuple[type, ...] + allow_inf_nan: bool + json_schema_extra: JsonDict | JsonSchemaExtraCallable | None + json_encoders: dict[type[object], JsonEncoder] | None + + # new in V2 + strict: bool + # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' + revalidate_instances: Literal['always', 'never', 'subclass-instances'] + ser_json_timedelta: Literal['iso8601', 'float'] + ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] + val_temporal_unit: Literal['seconds', 'milliseconds', 'infer'] + ser_json_bytes: Literal['utf8', 'base64', 'hex'] + val_json_bytes: Literal['utf8', 'base64', 'hex'] + ser_json_inf_nan: Literal['null', 'constants', 'strings'] + # whether to validate default values during validation, default False + validate_default: bool + validate_return: bool + protected_namespaces: tuple[str | Pattern[str], ...] + hide_input_in_errors: bool + defer_build: bool + plugin_settings: dict[str, object] | None + schema_generator: type[GenerateSchema] | None + json_schema_serialization_defaults_required: bool + json_schema_mode_override: Literal['validation', 'serialization', None] + coerce_numbers_to_str: bool + regex_engine: Literal['rust-regex', 'python-re'] + validation_error_cause: bool + use_attribute_docstrings: bool + cache_strings: bool | Literal['all', 'keys', 'none'] + validate_by_alias: bool + validate_by_name: bool + serialize_by_alias: bool + url_preserve_empty_path: bool + polymorphic_serialization: bool + + def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True): + if check: + self.config_dict = prepare_config(config) + else: + self.config_dict = cast(ConfigDict, config) + + @classmethod + def for_model( + cls, + bases: tuple[type[Any], ...], + namespace: dict[str, Any], + raw_annotations: dict[str, Any], + kwargs: dict[str, Any], + ) -> Self: + """Build a new `ConfigWrapper` instance for a `BaseModel`. + + The config wrapper built based on (in descending order of priority): + - options from `kwargs` + - options from the `namespace` + - options from the base classes (`bases`) + + Args: + bases: A tuple of base classes. + namespace: The namespace of the class being created. + raw_annotations: The (non-evaluated) annotations of the model. + kwargs: The kwargs passed to the class being created. + + Returns: + A `ConfigWrapper` instance for `BaseModel`. + """ + config_new = ConfigDict() + for base in bases: + config = getattr(base, 'model_config', None) + if config: + config_new.update(config.copy()) + + config_class_from_namespace = namespace.get('Config') + config_dict_from_namespace = namespace.get('model_config') + + if raw_annotations.get('model_config') and config_dict_from_namespace is None: + raise PydanticUserError( + '`model_config` cannot be used as a model field name. Use `model_config` for model configuration.', + code='model-config-invalid-field-name', + ) + + if config_class_from_namespace and config_dict_from_namespace: + raise PydanticUserError('"Config" and "model_config" cannot be used together', code='config-both') + + config_from_namespace = config_dict_from_namespace or prepare_config(config_class_from_namespace) + + config_new.update(config_from_namespace) + + for k in list(kwargs.keys()): + if k in config_keys: + config_new[k] = kwargs.pop(k) + + return cls(config_new) + + # we don't show `__getattr__` to type checkers so missing attributes cause errors + if not TYPE_CHECKING: # pragma: no branch + + def __getattr__(self, name: str) -> Any: + try: + return self.config_dict[name] + except KeyError: + try: + return config_defaults[name] + except KeyError: + raise AttributeError(f'Config has no attribute {name!r}') from None + + def core_config(self, title: str | None) -> core_schema.CoreConfig: + """Create a pydantic-core config. + + We don't use getattr here since we don't want to populate with defaults. + + Args: + title: The title to use if not set in config. + + Returns: + A `CoreConfig` object created from config. + """ + config = self.config_dict + + if config.get('schema_generator') is not None: + warnings.warn( + 'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.', + PydanticDeprecatedSince210, + stacklevel=2, + ) + + if (populate_by_name := config.get('populate_by_name')) is not None: + # We include this patch for backwards compatibility purposes, but this config setting will be deprecated in v3.0, and likely removed in v4.0. + # Thus, the above warning and this patch can be removed then as well. + if config.get('validate_by_name') is None: + config['validate_by_alias'] = True + config['validate_by_name'] = populate_by_name + + # We dynamically patch validate_by_name to be True if validate_by_alias is set to False + # and validate_by_name is not explicitly set. + if config.get('validate_by_alias') is False and config.get('validate_by_name') is None: + config['validate_by_name'] = True + + if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)): + raise PydanticUserError( + 'At least one of `validate_by_alias` or `validate_by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return core_schema.CoreConfig( + **{ # pyright: ignore[reportArgumentType] + k: v + for k, v in ( + ('title', config.get('title') or title or None), + ('extra_fields_behavior', config.get('extra')), + ('allow_inf_nan', config.get('allow_inf_nan')), + ('str_strip_whitespace', config.get('str_strip_whitespace')), + ('str_to_lower', config.get('str_to_lower')), + ('str_to_upper', config.get('str_to_upper')), + ('strict', config.get('strict')), + ('ser_json_timedelta', config.get('ser_json_timedelta')), + ('ser_json_temporal', config.get('ser_json_temporal')), + ('val_temporal_unit', config.get('val_temporal_unit')), + ('ser_json_bytes', config.get('ser_json_bytes')), + ('val_json_bytes', config.get('val_json_bytes')), + ('ser_json_inf_nan', config.get('ser_json_inf_nan')), + ('from_attributes', config.get('from_attributes')), + ('loc_by_alias', config.get('loc_by_alias')), + ('revalidate_instances', config.get('revalidate_instances')), + ('validate_default', config.get('validate_default')), + ('str_max_length', config.get('str_max_length')), + ('str_min_length', config.get('str_min_length')), + ('hide_input_in_errors', config.get('hide_input_in_errors')), + ('coerce_numbers_to_str', config.get('coerce_numbers_to_str')), + ('regex_engine', config.get('regex_engine')), + ('validation_error_cause', config.get('validation_error_cause')), + ('cache_strings', config.get('cache_strings')), + ('validate_by_alias', config.get('validate_by_alias')), + ('validate_by_name', config.get('validate_by_name')), + ('serialize_by_alias', config.get('serialize_by_alias')), + ('url_preserve_empty_path', config.get('url_preserve_empty_path')), + ('polymorphic_serialization', config.get('polymorphic_serialization')), + ) + if v is not None + } + ) + + def __repr__(self): + c = ', '.join(f'{k}={v!r}' for k, v in self.config_dict.items()) + return f'ConfigWrapper({c})' + + +class ConfigWrapperStack: + """A stack of `ConfigWrapper` instances.""" + + def __init__(self, config_wrapper: ConfigWrapper): + self._config_wrapper_stack: list[ConfigWrapper] = [config_wrapper] + + @property + def tail(self) -> ConfigWrapper: + return self._config_wrapper_stack[-1] + + @contextmanager + def push(self, config_wrapper: ConfigWrapper | ConfigDict | None): + if config_wrapper is None: + yield + return + + if not isinstance(config_wrapper, ConfigWrapper): + config_wrapper = ConfigWrapper(config_wrapper, check=False) + + self._config_wrapper_stack.append(config_wrapper) + try: + yield + finally: + self._config_wrapper_stack.pop() + + +config_defaults = ConfigDict( + title=None, + str_to_lower=False, + str_to_upper=False, + str_strip_whitespace=False, + str_min_length=0, + str_max_length=None, + # let the model / dataclass decide how to handle it + extra=None, + frozen=False, + populate_by_name=False, + use_enum_values=False, + validate_assignment=False, + arbitrary_types_allowed=False, + from_attributes=False, + loc_by_alias=True, + alias_generator=None, + model_title_generator=None, + field_title_generator=None, + ignored_types=(), + allow_inf_nan=True, + json_schema_extra=None, + strict=False, + revalidate_instances='never', + ser_json_timedelta='iso8601', + ser_json_temporal='iso8601', + val_temporal_unit='infer', + ser_json_bytes='utf8', + val_json_bytes='utf8', + ser_json_inf_nan='null', + validate_default=False, + validate_return=False, + protected_namespaces=('model_validate', 'model_dump'), + hide_input_in_errors=False, + json_encoders=None, + defer_build=False, + schema_generator=None, + plugin_settings=None, + json_schema_serialization_defaults_required=False, + json_schema_mode_override=None, + coerce_numbers_to_str=False, + regex_engine='rust-regex', + validation_error_cause=False, + use_attribute_docstrings=False, + cache_strings=True, + validate_by_alias=True, + validate_by_name=False, + serialize_by_alias=False, + url_preserve_empty_path=False, + polymorphic_serialization=False, +) + + +def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> ConfigDict: + """Create a `ConfigDict` instance from an existing dict, a class (e.g. old class-based config) or None. + + Args: + config: The input config. + + Returns: + A ConfigDict object created from config. + """ + if config is None: + return ConfigDict() + + if not isinstance(config, dict): + warnings.warn(DEPRECATION_MESSAGE, PydanticDeprecatedSince20, stacklevel=4) + config = {k: getattr(config, k) for k in dir(config) if not k.startswith('__')} + + config_dict = cast(ConfigDict, config) + check_deprecated(config_dict) + return config_dict + + +config_keys = set(ConfigDict.__annotations__.keys()) + + +V2_REMOVED_KEYS = { + 'allow_mutation', + 'error_msg_templates', + 'fields', + 'getter_dict', + 'smart_union', + 'underscore_attrs_are_private', + 'json_loads', + 'json_dumps', + 'copy_on_model_validation', + 'post_init_call', +} +V2_RENAMED_KEYS = { + 'allow_population_by_field_name': 'validate_by_name', + 'anystr_lower': 'str_to_lower', + 'anystr_strip_whitespace': 'str_strip_whitespace', + 'anystr_upper': 'str_to_upper', + 'keep_untouched': 'ignored_types', + 'max_anystr_length': 'str_max_length', + 'min_anystr_length': 'str_min_length', + 'orm_mode': 'from_attributes', + 'schema_extra': 'json_schema_extra', + 'validate_all': 'validate_default', +} + + +def check_deprecated(config_dict: ConfigDict) -> None: + """Check for deprecated config keys and warn the user. + + Args: + config_dict: The input config. + """ + deprecated_removed_keys = V2_REMOVED_KEYS & config_dict.keys() + deprecated_renamed_keys = V2_RENAMED_KEYS.keys() & config_dict.keys() + if deprecated_removed_keys or deprecated_renamed_keys: + renamings = {k: V2_RENAMED_KEYS[k] for k in sorted(deprecated_renamed_keys)} + renamed_bullets = [f'* {k!r} has been renamed to {v!r}' for k, v in renamings.items()] + removed_bullets = [f'* {k!r} has been removed' for k in sorted(deprecated_removed_keys)] + message = '\n'.join(['Valid config keys have changed in V2:'] + renamed_bullets + removed_bullets) + warnings.warn(message, UserWarning) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py new file mode 100644 index 0000000..9f2510c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py @@ -0,0 +1,97 @@ +from __future__ import annotations as _annotations + +from typing import TYPE_CHECKING, Any, TypedDict, cast +from warnings import warn + +if TYPE_CHECKING: + from ..config import JsonDict, JsonSchemaExtraCallable + from ._schema_generation_shared import ( + GetJsonSchemaFunction, + ) + + +class CoreMetadata(TypedDict, total=False): + """A `TypedDict` for holding the metadata dict of the schema. + + Attributes: + pydantic_js_functions: List of JSON schema functions that resolve refs during application. + pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application. + pydantic_js_prefer_positional_arguments: Whether JSON schema generator will + prefer positional over keyword arguments for an 'arguments' schema. + custom validation function. Only applies to before, plain, and wrap validators. + pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type. + pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable. + pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union. + pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union + when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time + of the annotation application. + + TODO: Perhaps we should move this structure to pydantic-core. At the moment, though, + it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API. + + TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during + the core schema generation process. It's inevitable that we need to store some json schema related information + on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related + issues is quite difficult when JSON schema information is disguised via dynamically defined functions. + """ + + pydantic_js_functions: list[GetJsonSchemaFunction] + pydantic_js_annotation_functions: list[GetJsonSchemaFunction] + pydantic_js_prefer_positional_arguments: bool + pydantic_js_updates: JsonDict + pydantic_js_extra: JsonDict | JsonSchemaExtraCallable + pydantic_internal_union_tag_key: str + pydantic_internal_union_discriminator: str + + +def update_core_metadata( + core_metadata: Any, + /, + *, + pydantic_js_functions: list[GetJsonSchemaFunction] | None = None, + pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None, + pydantic_js_updates: JsonDict | None = None, + pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None, +) -> None: + from ..json_schema import PydanticJsonSchemaWarning + + """Update CoreMetadata instance in place. When we make modifications in this function, they + take effect on the `core_metadata` reference passed in as the first (and only) positional argument. + + First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility. + We do this here, instead of before / after each call to this function so that this typing hack + can be easily removed if/when we move `CoreMetadata` to `pydantic-core`. + + For parameter descriptions, see `CoreMetadata` above. + """ + core_metadata = cast(CoreMetadata, core_metadata) + + if pydantic_js_functions: + core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions) + + if pydantic_js_annotation_functions: + core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions) + + if pydantic_js_updates: + if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None: + core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates} + else: + core_metadata['pydantic_js_updates'] = pydantic_js_updates + + if pydantic_js_extra is not None: + existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra') + if existing_pydantic_js_extra is None: + core_metadata['pydantic_js_extra'] = pydantic_js_extra + if isinstance(existing_pydantic_js_extra, dict): + if isinstance(pydantic_js_extra, dict): + core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra} + if callable(pydantic_js_extra): + warn( + 'Composing `dict` and `callable` type `json_schema_extra` is not supported.' + 'The `callable` type is being ignored.' + "If you'd like support for this behavior, please open an issue on pydantic.", + PydanticJsonSchemaWarning, + ) + if callable(existing_pydantic_js_extra): + # if ever there's a case of a callable, we'll just keep the last json schema extra spec + core_metadata['pydantic_js_extra'] = pydantic_js_extra diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py new file mode 100644 index 0000000..caa51e8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +import inspect +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Union + +from pydantic_core import CoreSchema, core_schema +from typing_extensions import TypeGuard, get_args, get_origin +from typing_inspection import typing_objects + +from . import _repr +from ._typing_extra import is_generic_alias + +if TYPE_CHECKING: + from rich.console import Console + +AnyFunctionSchema = Union[ + core_schema.AfterValidatorFunctionSchema, + core_schema.BeforeValidatorFunctionSchema, + core_schema.WrapValidatorFunctionSchema, + core_schema.PlainValidatorFunctionSchema, +] + + +FunctionSchemaWithInnerSchema = Union[ + core_schema.AfterValidatorFunctionSchema, + core_schema.BeforeValidatorFunctionSchema, + core_schema.WrapValidatorFunctionSchema, +] + +CoreSchemaField = Union[ + core_schema.ModelField, core_schema.DataclassField, core_schema.TypedDictField, core_schema.ComputedField +] +CoreSchemaOrField = Union[core_schema.CoreSchema, CoreSchemaField] + +_CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'} +_FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'} +_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'set', 'frozenset'} + + +def is_core_schema( + schema: CoreSchemaOrField, +) -> TypeGuard[CoreSchema]: + return schema['type'] not in _CORE_SCHEMA_FIELD_TYPES + + +def is_core_schema_field( + schema: CoreSchemaOrField, +) -> TypeGuard[CoreSchemaField]: + return schema['type'] in _CORE_SCHEMA_FIELD_TYPES + + +def is_function_with_inner_schema( + schema: CoreSchemaOrField, +) -> TypeGuard[FunctionSchemaWithInnerSchema]: + return schema['type'] in _FUNCTION_WITH_INNER_SCHEMA_TYPES + + +def is_list_like_schema_with_items_schema( + schema: CoreSchema, +) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]: + return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES + + +def get_type_ref(type_: Any, args_override: tuple[type[Any], ...] | None = None) -> str: + """Produces the ref to be used for this type by pydantic_core's core schemas. + + This `args_override` argument was added for the purpose of creating valid recursive references + when creating generic models without needing to create a concrete class. + """ + origin = get_origin(type_) or type_ + + args = get_args(type_) if is_generic_alias(type_) else (args_override or ()) + generic_metadata = getattr(type_, '__pydantic_generic_metadata__', None) + if generic_metadata: + origin = generic_metadata['origin'] or origin + args = generic_metadata['args'] or args + + module_name = getattr(origin, '__module__', '') + if typing_objects.is_typealiastype(origin): + type_ref = f'{module_name}.{origin.__name__}:{id(origin)}' + else: + try: + qualname = getattr(origin, '__qualname__', f'') + except Exception: + qualname = getattr(origin, '__qualname__', '') + type_ref = f'{module_name}.{qualname}:{id(origin)}' + + arg_refs: list[str] = [] + for arg in args: + if isinstance(arg, str): + # Handle string literals as a special case; we may be able to remove this special handling if we + # wrap them in a ForwardRef at some point. + arg_ref = f'{arg}:str-{id(arg)}' + else: + arg_ref = f'{_repr.display_as_type(arg)}:{id(arg)}' + arg_refs.append(arg_ref) + if arg_refs: + type_ref = f'{type_ref}[{",".join(arg_refs)}]' + return type_ref + + +def get_ref(s: core_schema.CoreSchema) -> None | str: + """Get the ref from the schema if it has one. + This exists just for type checking to work correctly. + """ + return s.get('ref', None) + + +def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover + """A utility function to remove irrelevant information from a core schema.""" + if isinstance(obj, Mapping): + new_dct = {} + for k, v in obj.items(): + if k == 'metadata' and strip_metadata: + new_metadata = {} + + for meta_k, meta_v in v.items(): + if meta_k in ('pydantic_js_functions', 'pydantic_js_annotation_functions'): + new_metadata['js_metadata'] = '' + else: + new_metadata[meta_k] = _clean_schema_for_pretty_print(meta_v, strip_metadata=strip_metadata) + + if list(new_metadata.keys()) == ['js_metadata']: + new_metadata = {''} + + new_dct[k] = new_metadata + # Remove some defaults: + elif k in ('custom_init', 'root_model') and not v: + continue + else: + new_dct[k] = _clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) + + return new_dct + elif isinstance(obj, Sequence) and not isinstance(obj, str): + return [_clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) for v in obj] + else: + return obj + + +def pretty_print_core_schema( + val: Any, + *, + console: Console | None = None, + max_depth: int | None = None, + strip_metadata: bool = True, +) -> None: # pragma: no cover + """Pretty-print a core schema using the `rich` library. + + Args: + val: The core schema to print, or a Pydantic model/dataclass/type adapter + (in which case the cached core schema is fetched and printed). + console: A rich console to use when printing. Defaults to the global rich console instance. + max_depth: The number of nesting levels which may be printed. + strip_metadata: Whether to strip metadata in the output. If `True` any known core metadata + attributes will be stripped (but custom attributes are kept). Defaults to `True`. + """ + # lazy import: + from rich.pretty import pprint + + # circ. imports: + from pydantic import BaseModel, TypeAdapter + from pydantic.dataclasses import is_pydantic_dataclass + + if (inspect.isclass(val) and issubclass(val, BaseModel)) or is_pydantic_dataclass(val): + val = val.__pydantic_core_schema__ + if isinstance(val, TypeAdapter): + val = val.core_schema + cleaned_schema = _clean_schema_for_pretty_print(val, strip_metadata=strip_metadata) + + pprint(cleaned_schema, console=console, max_depth=max_depth) + + +pps = pretty_print_core_schema diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py new file mode 100644 index 0000000..f00c8b7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py @@ -0,0 +1,315 @@ +"""Private logic for creating pydantic dataclasses.""" + +from __future__ import annotations as _annotations + +import copy +import dataclasses +import sys +import warnings +from collections.abc import Generator +from contextlib import contextmanager +from functools import partial +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast + +from pydantic_core import ( + ArgsKwargs, + SchemaSerializer, + SchemaValidator, + core_schema, +) +from typing_extensions import TypeAlias, TypeIs + +from ..errors import PydanticUndefinedAnnotation +from ..fields import FieldInfo +from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator +from ..warnings import PydanticDeprecatedSince20 +from . import _config, _decorators +from ._fields import collect_dataclass_fields +from ._generate_schema import GenerateSchema, InvalidSchemaError +from ._generics import get_standard_typevars_map +from ._mock_val_ser import set_dataclass_mocks +from ._namespace_utils import NsResolver +from ._signature import generate_pydantic_signature +from ._utils import LazyClassAttribute + +if TYPE_CHECKING: + from _typeshed import DataclassInstance as StandardDataclass + + from ..config import ConfigDict + + class PydanticDataclass(StandardDataclass, Protocol): + """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass. + + Attributes: + __pydantic_config__: Pydantic-specific configuration settings for the dataclass. + __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields. + __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer. + __pydantic_decorators__: Metadata containing the decorators defined on the dataclass. + __pydantic_fields__: Metadata about the fields defined on the dataclass. + __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass. + __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass. + """ + + __pydantic_config__: ClassVar[ConfigDict] + __pydantic_complete__: ClassVar[bool] + __pydantic_core_schema__: ClassVar[core_schema.CoreSchema] + __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] + __pydantic_fields__: ClassVar[dict[str, FieldInfo]] + __pydantic_serializer__: ClassVar[SchemaSerializer] + __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] + + @classmethod + def __pydantic_fields_complete__(cls) -> bool: ... + + +def set_dataclass_fields( + cls: type[StandardDataclass], + config_wrapper: _config.ConfigWrapper, + ns_resolver: NsResolver | None = None, +) -> None: + """Collect and set `cls.__pydantic_fields__`. + + Args: + cls: The class. + config_wrapper: The config wrapper instance. + ns_resolver: Namespace resolver to use when getting dataclass annotations. + """ + typevars_map = get_standard_typevars_map(cls) + fields = collect_dataclass_fields( + cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper + ) + + cls.__pydantic_fields__ = fields # type: ignore + + +def complete_dataclass( + cls: type[Any], + config_wrapper: _config.ConfigWrapper, + *, + raise_errors: bool = True, + ns_resolver: NsResolver | None = None, + _force_build: bool = False, +) -> bool: + """Finish building a pydantic dataclass. + + This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`. + + This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`. + + Args: + cls: The class. + config_wrapper: The config wrapper instance. + raise_errors: Whether to raise errors, defaults to `True`. + ns_resolver: The namespace resolver instance to use when collecting dataclass fields + and during schema building. + _force_build: Whether to force building the dataclass, no matter if + [`defer_build`][pydantic.config.ConfigDict.defer_build] is set. + + Returns: + `True` if building a pydantic dataclass is successfully completed, `False` otherwise. + + Raises: + PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations. + """ + original_init = cls.__init__ + + # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied, + # and so that the mock validator is used if building was deferred: + def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: + __tracebackhide__ = True + s = __dataclass_self__ + s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) + + __init__.__qualname__ = f'{cls.__qualname__}.__init__' + + cls.__init__ = __init__ # type: ignore + cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore + + set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) + + if not _force_build and config_wrapper.defer_build: + set_dataclass_mocks(cls) + return False + + if hasattr(cls, '__post_init_post_parse__'): + warnings.warn( + 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', + PydanticDeprecatedSince20, + ) + + typevars_map = get_standard_typevars_map(cls) + gen_schema = GenerateSchema( + config_wrapper, + ns_resolver=ns_resolver, + typevars_map=typevars_map, + ) + + # set __signature__ attr only for the class, but not for its instances + # (because instances can define `__call__`, and `inspect.signature` shouldn't + # use the `__signature__` attribute and instead generate from `__call__`). + cls.__signature__ = LazyClassAttribute( + '__signature__', + partial( + generate_pydantic_signature, + # It's important that we reference the `original_init` here, + # as it is the one synthesized by the stdlib `dataclass` module: + init=original_init, + fields=cls.__pydantic_fields__, # type: ignore + validate_by_name=config_wrapper.validate_by_name, + extra=config_wrapper.extra, + is_dataclass=True, + ), + ) + + try: + schema = gen_schema.generate_schema(cls) + except PydanticUndefinedAnnotation as e: + if raise_errors: + raise + set_dataclass_mocks(cls, f'`{e.name}`') + return False + + core_config = config_wrapper.core_config(title=cls.__name__) + + try: + schema = gen_schema.clean_schema(schema) + except InvalidSchemaError: + set_dataclass_mocks(cls) + return False + + # We are about to set all the remaining required properties expected for this cast; + # __pydantic_decorators__ and __pydantic_fields__ should already be set + cls = cast('type[PydanticDataclass]', cls) + + cls.__pydantic_core_schema__ = schema + cls.__pydantic_validator__ = create_schema_validator( + schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings + ) + cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) + cls.__pydantic_complete__ = True + return True + + +def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: + """Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass. + + Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses + of a dataclass that are themselves not dataclasses. + + Args: + cls: The class. + + Returns: + `True` if the class is a stdlib dataclass, `False` otherwise. + """ + return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__') + + +def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: + field_args: dict[str, Any] = {'default': pydantic_field} + + # Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple: + if sys.version_info >= (3, 14) and pydantic_field.description is not None: + field_args['doc'] = pydantic_field.description + + # Needed as the stdlib dataclass module processes kw_only in a specific way during class construction: + if sys.version_info >= (3, 10) and pydantic_field.kw_only is not None: + field_args['kw_only'] = pydantic_field.kw_only + + # Needed as the stdlib dataclass modules generates `__repr__()` during class construction: + if pydantic_field.repr is not True: + field_args['repr'] = pydantic_field.repr + + return dataclasses.field(**field_args) + + +DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] + + +@contextmanager +def patch_base_fields(cls: type[Any]) -> Generator[None]: + """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used. + + When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where + the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply + the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass, + the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the + dataclass construction. To do so, we temporarily patch the fields dictionary of the affected + bases. + + For instance, with the following example: + + ```python {test="skip" lint="skip"} + import dataclasses as stdlib_dc + + import pydantic + import pydantic.dataclasses as pydantic_dc + + @stdlib_dc.dataclass + class A: + a: int = pydantic.Field(repr=False) + + # Notice that the `repr` attribute of the dataclass field is `True`: + A.__dataclass_fields__['a'] + #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...) + + @pydantic_dc.dataclass + class B(A): + b: int = pydantic.Field(repr=False) + ``` + + When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes + and reuse them directly. When this context manager is active, `A` will be temporarily patched to be + equivalent to: + + ```python {test="skip" lint="skip"} + @stdlib_dc.dataclass + class A: + a: int = stdlib_dc.field(default=Field(repr=False), repr=False) + ``` + + !!! note + This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic + dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields + directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`). + + !!! note + This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions. + The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a + safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway. + """ + # A list of two-tuples, the first element being a reference to the + # dataclass fields dictionary, the second element being a mapping between + # the field names that were modified, and their original `Field`: + original_fields_list: list[tuple[DcFields, DcFields]] = [] + + for base in cls.__mro__[1:]: + dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) + dc_fields_with_pydantic_field_defaults = { + field_name: field + for field_name, field in dc_fields.items() + if isinstance(field.default, FieldInfo) + # Only do the patching if one of the affected attributes is set: + and (field.default.description is not None or field.default.kw_only or field.default.repr is not True) + } + if dc_fields_with_pydantic_field_defaults: + original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) + for field_name, field in dc_fields_with_pydantic_field_defaults.items(): + default = cast(FieldInfo, field.default) + # `dataclasses.Field` isn't documented as working with `copy.copy()`. + # It is a class with `__slots__`, so should work (and we hope for the best): + new_dc_field = copy.copy(field) + # For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant + # for the class under construction and handled in `as_dataclass_field()`. + if sys.version_info >= (3, 10) and default.kw_only: + new_dc_field.kw_only = True + if default.repr is not True: + new_dc_field.repr = default.repr + dc_fields[field_name] = new_dc_field + + try: + yield + finally: + for fields, original_fields in original_fields_list: + for field_name, original_field in original_fields.items(): + fields[field_name] = original_field diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py new file mode 100644 index 0000000..e24ad2c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py @@ -0,0 +1,873 @@ +"""Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators.""" + +from __future__ import annotations as _annotations + +import types +from collections import deque +from collections.abc import Iterable +from copy import copy +from dataclasses import dataclass, field +from functools import cached_property, partial, partialmethod +from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor +from itertools import islice +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Literal, TypeVar, Union + +from pydantic_core import PydanticUndefined, PydanticUndefinedType, core_schema +from typing_extensions import Self, TypeAlias, is_typeddict + +from ..errors import PydanticUserError +from ._core_utils import get_type_ref +from ._internal_dataclass import slots_true +from ._namespace_utils import GlobalsNamespace, MappingNamespace +from ._typing_extra import get_function_type_hints, signature_no_eval +from ._utils import can_be_positional + +if TYPE_CHECKING: + from ..fields import ComputedFieldInfo + from ..functional_validators import FieldValidatorModes + from ._config import ConfigWrapper + + +@dataclass(**slots_true) +class ValidatorDecoratorInfo: + """A container for data from `@validator` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@validator'. + fields: A tuple of field names the validator should be called on. + mode: The proposed validator mode. + each_item: For complex objects (sets, lists etc.) whether to validate individual + elements rather than the whole object. + always: Whether this method and other validators should be called even if the value is missing. + check_fields: Whether to check that the fields actually exist on the model. + """ + + decorator_repr: ClassVar[str] = '@validator' + + fields: tuple[str, ...] + mode: Literal['before', 'after'] + each_item: bool + always: bool + check_fields: bool | None + + +@dataclass(**slots_true) +class FieldValidatorDecoratorInfo: + """A container for data from `@field_validator` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@field_validator'. + fields: A tuple of field names the validator should be called on. + mode: The proposed validator mode. + check_fields: Whether to check that the fields actually exist on the model. + json_schema_input_type: The input type of the function. This is only used to generate + the appropriate JSON Schema (in validation mode) and can only specified + when `mode` is either `'before'`, `'plain'` or `'wrap'`. + """ + + decorator_repr: ClassVar[str] = '@field_validator' + + fields: tuple[str, ...] + mode: FieldValidatorModes + check_fields: bool | None + json_schema_input_type: Any + + +@dataclass(**slots_true) +class RootValidatorDecoratorInfo: + """A container for data from `@root_validator` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@root_validator'. + mode: The proposed validator mode. + """ + + decorator_repr: ClassVar[str] = '@root_validator' + mode: Literal['before', 'after'] + + +@dataclass(**slots_true) +class FieldSerializerDecoratorInfo: + """A container for data from `@field_serializer` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@field_serializer'. + fields: A tuple of field names the serializer should be called on. + mode: The proposed serializer mode. + return_type: The type of the serializer's return value. + when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`, + and `'json-unless-none'`. + check_fields: Whether to check that the fields actually exist on the model. + """ + + decorator_repr: ClassVar[str] = '@field_serializer' + fields: tuple[str, ...] + mode: Literal['plain', 'wrap'] + return_type: Any + when_used: core_schema.WhenUsed + check_fields: bool | None + + +@dataclass(**slots_true) +class ModelSerializerDecoratorInfo: + """A container for data from `@model_serializer` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@model_serializer'. + mode: The proposed serializer mode. + return_type: The type of the serializer's return value. + when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`, + and `'json-unless-none'`. + """ + + decorator_repr: ClassVar[str] = '@model_serializer' + mode: Literal['plain', 'wrap'] + return_type: Any + when_used: core_schema.WhenUsed + + +@dataclass(**slots_true) +class ModelValidatorDecoratorInfo: + """A container for data from `@model_validator` so that we can access it + while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@model_validator'. + mode: The proposed serializer mode. + """ + + decorator_repr: ClassVar[str] = '@model_validator' + mode: Literal['wrap', 'before', 'after'] + + +DecoratorInfo: TypeAlias = """Union[ + ValidatorDecoratorInfo, + FieldValidatorDecoratorInfo, + RootValidatorDecoratorInfo, + FieldSerializerDecoratorInfo, + ModelSerializerDecoratorInfo, + ModelValidatorDecoratorInfo, + ComputedFieldInfo, +]""" + +ReturnType = TypeVar('ReturnType') +DecoratedType: TypeAlias = ( + 'Union[classmethod[Any, Any, ReturnType], staticmethod[Any, ReturnType], Callable[..., ReturnType], property]' +) + + +@dataclass # can't use slots here since we set attributes on `__post_init__` +class PydanticDescriptorProxy(Generic[ReturnType]): + """Wrap a classmethod, staticmethod, property or unbound function + and act as a descriptor that allows us to detect decorated items + from the class' attributes. + + This class' __get__ returns the wrapped item's __get__ result, + which makes it transparent for classmethods and staticmethods. + + Attributes: + wrapped: The decorator that has to be wrapped. + decorator_info: The decorator info. + shim: A wrapper function to wrap V1 style function. + """ + + wrapped: DecoratedType[ReturnType] + decorator_info: DecoratorInfo + shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None + + def __post_init__(self): + for attr in 'setter', 'deleter': + if hasattr(self.wrapped, attr): + f = partial(self._call_wrapped_attr, name=attr) + setattr(self, attr, f) + + def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]: + self.wrapped = getattr(self.wrapped, name)(func) + if isinstance(self.wrapped, property): + # update ComputedFieldInfo.wrapped_property + from ..fields import ComputedFieldInfo + + if isinstance(self.decorator_info, ComputedFieldInfo): + self.decorator_info.wrapped_property = self.wrapped + return self + + def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]: + try: + return self.wrapped.__get__(obj, obj_type) # pyright: ignore[reportReturnType] + except AttributeError: + # not a descriptor, e.g. a partial object + return self.wrapped # type: ignore[return-value] + + def __set_name__(self, instance: Any, name: str) -> None: + if hasattr(self.wrapped, '__set_name__'): + self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess] + + def __getattr__(self, name: str, /) -> Any: + """Forward checks for __isabstractmethod__ and such.""" + return getattr(self.wrapped, name) + + +DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) + + +@dataclass(**slots_true) +class Decorator(Generic[DecoratorInfoType]): + """A generic container class to join together the decorator metadata + (metadata from decorator itself, which we have when the + decorator is called but not when we are building the core-schema) + and the bound function (which we have after the class itself is created). + + Attributes: + cls_ref: The class ref. + cls_var_name: The decorated function name. + func: The decorated function. + shim: A wrapper function to wrap V1 style function. + info: The decorator info. + """ + + cls_ref: str + cls_var_name: str + func: Callable[..., Any] + shim: Callable[[Any], Any] | None + info: DecoratorInfoType + + @staticmethod + def build( + cls_: Any, + *, + cls_var_name: str, + shim: Callable[[Any], Any] | None, + info: DecoratorInfoType, + ) -> Decorator[DecoratorInfoType]: + """Build a new decorator. + + Args: + cls_: The class. + cls_var_name: The decorated function name. + shim: A wrapper function to wrap V1 style function. + info: The decorator info. + + Returns: + The new decorator instance. + """ + func = get_attribute_from_bases(cls_, cls_var_name) + if shim is not None: + func = shim(func) + func = unwrap_wrapped_function(func, unwrap_partial=False) + if not callable(func): + # TODO most likely this branch can be removed when we drop support for Python 3.12: + # This branch will get hit for classmethod properties + attribute = get_attribute_from_base_dicts(cls_, cls_var_name) # prevents the binding call to `__get__` + if isinstance(attribute, PydanticDescriptorProxy): + func = unwrap_wrapped_function(attribute.wrapped) + return Decorator( + cls_ref=get_type_ref(cls_), + cls_var_name=cls_var_name, + func=func, + shim=shim, + info=info, + ) + + def bind_to_cls(self, cls: Any) -> Decorator[DecoratorInfoType]: + """Bind the decorator to a class. + + Args: + cls: the class. + + Returns: + The new decorator instance. + """ + return self.build( + cls, + cls_var_name=self.cls_var_name, + shim=self.shim, + info=copy(self.info), + ) + + +def get_bases(tp: type[Any]) -> tuple[type[Any], ...]: + """Get the base classes of a class or typeddict. + + Args: + tp: The type or class to get the bases. + + Returns: + The base classes. + """ + if is_typeddict(tp): + return tp.__orig_bases__ # type: ignore + try: + return tp.__bases__ + except AttributeError: + return () + + +def mro(tp: type[Any]) -> tuple[type[Any], ...]: + """Calculate the Method Resolution Order of bases using the C3 algorithm. + + See https://www.python.org/download/releases/2.3/mro/ + """ + # try to use the existing mro, for performance mainly + # but also because it helps verify the implementation below + if not is_typeddict(tp): + try: + return tp.__mro__ + except AttributeError: + # GenericAlias and some other cases + pass + + bases = get_bases(tp) + return (tp,) + mro_for_bases(bases) + + +def mro_for_bases(bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]: + def merge_seqs(seqs: list[deque[type[Any]]]) -> Iterable[type[Any]]: + while True: + non_empty = [seq for seq in seqs if seq] + if not non_empty: + # Nothing left to process, we're done. + return + candidate: type[Any] | None = None + for seq in non_empty: # Find merge candidates among seq heads. + candidate = seq[0] + not_head = [s for s in non_empty if candidate in islice(s, 1, None)] + if not_head: + # Reject the candidate. + candidate = None + else: + break + if not candidate: + raise TypeError('Inconsistent hierarchy, no C3 MRO is possible') + yield candidate + for seq in non_empty: + # Remove candidate. + if seq[0] == candidate: + seq.popleft() + + seqs = [deque(mro(base)) for base in bases] + [deque(bases)] + return tuple(merge_seqs(seqs)) + + +_sentinel = object() + + +def get_attribute_from_bases(tp: type[Any] | tuple[type[Any], ...], name: str) -> Any: + """Get the attribute from the next class in the MRO that has it, + aiming to simulate calling the method on the actual class. + + The reason for iterating over the mro instead of just getting + the attribute (which would do that for us) is to support TypedDict, + which lacks a real __mro__, but can have a virtual one constructed + from its bases (as done here). + + Args: + tp: The type or class to search for the attribute. If a tuple, this is treated as a set of base classes. + name: The name of the attribute to retrieve. + + Returns: + Any: The attribute value, if found. + + Raises: + AttributeError: If the attribute is not found in any class in the MRO. + """ + if isinstance(tp, tuple): + for base in mro_for_bases(tp): + attribute = base.__dict__.get(name, _sentinel) + if attribute is not _sentinel: + attribute_get = getattr(attribute, '__get__', None) + if attribute_get is not None: + return attribute_get(None, tp) + return attribute + raise AttributeError(f'{name} not found in {tp}') + else: + try: + return getattr(tp, name) + except AttributeError: + return get_attribute_from_bases(mro(tp), name) + + +def get_attribute_from_base_dicts(tp: type[Any], name: str) -> Any: + """Get an attribute out of the `__dict__` following the MRO. + This prevents the call to `__get__` on the descriptor, and allows + us to get the original function for classmethod properties. + + Args: + tp: The type or class to search for the attribute. + name: The name of the attribute to retrieve. + + Returns: + Any: The attribute value, if found. + + Raises: + KeyError: If the attribute is not found in any class's `__dict__` in the MRO. + """ + for base in reversed(mro(tp)): + if name in base.__dict__: + return base.__dict__[name] + return tp.__dict__[name] # raise the error + + +@dataclass(**slots_true) +class DecoratorInfos: + """Mapping of name in the class namespace to decorator info. + + note that the name in the class namespace is the function or attribute name + not the field name! + """ + + validators: dict[str, Decorator[ValidatorDecoratorInfo]] = field(default_factory=dict) + field_validators: dict[str, Decorator[FieldValidatorDecoratorInfo]] = field(default_factory=dict) + root_validators: dict[str, Decorator[RootValidatorDecoratorInfo]] = field(default_factory=dict) + field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]] = field(default_factory=dict) + model_serializers: dict[str, Decorator[ModelSerializerDecoratorInfo]] = field(default_factory=dict) + model_validators: dict[str, Decorator[ModelValidatorDecoratorInfo]] = field(default_factory=dict) + computed_fields: dict[str, Decorator[ComputedFieldInfo]] = field(default_factory=dict) + + @classmethod + def build( + cls, + typ: type[Any], + # Default to `True` for backwards compatibility: + replace_wrapped_methods: bool = True, + ) -> Self: + """Build a `DecoratorInfos` instance for the given model, dataclass or `TypedDict` type. + + Decorators from parent classes are included, including "bare" classes (e.g. if `typ` + is a Pydantic model, non Pydantic parent model classes are also taken into account). + The collection of the decorators happens by respecting the MRO. + + If one of the bases has an `__pydantic_decorators__` attribute set, it is assumed to be + a `DecoratorInfos` instance and is used as-is. The `__pydantic_decorators__` attribute + is *not* being set on the provided `typ`. + + Args: + typ: The model, dataclass or `TypedDict` type to use when building the `DecoratorInfos` instance. + replace_wrapped_methods: Whether to replace the decorator's wrapped methods on `typ`. + This is useful e.g. for field validators which are initially class methods. This should + only be set to `True` if `typ` is a Pydantic model or dataclass (otherwise this results + in mutations of classes Pydantic doesn't "own"). + """ + # reminder: dicts are ordered and replacement does not alter the order + res = cls() + # Iterate over the bases, without the actual `typ`. + # `1:-1` because we don't need to include `object`/`TypedDict`: + for base in reversed(mro(typ)[1:-1]): + existing: DecoratorInfos | None = base.__dict__.get('__pydantic_decorators__') + if existing is None: + existing, _ = _decorator_infos_for_class(base, collect_to_replace=False) + res.validators.update({k: v.bind_to_cls(typ) for k, v in existing.validators.items()}) + res.field_validators.update({k: v.bind_to_cls(typ) for k, v in existing.field_validators.items()}) + res.root_validators.update({k: v.bind_to_cls(typ) for k, v in existing.root_validators.items()}) + res.field_serializers.update({k: v.bind_to_cls(typ) for k, v in existing.field_serializers.items()}) + res.model_serializers.update({k: v.bind_to_cls(typ) for k, v in existing.model_serializers.items()}) + res.model_validators.update({k: v.bind_to_cls(typ) for k, v in existing.model_validators.items()}) + res.computed_fields.update({k: v.bind_to_cls(typ) for k, v in existing.computed_fields.items()}) + + decorator_infos, to_replace = _decorator_infos_for_class(typ, collect_to_replace=True) + + res.validators.update(decorator_infos.validators) + res.field_validators.update(decorator_infos.field_validators) + res.root_validators.update(decorator_infos.root_validators) + res.field_serializers.update(decorator_infos.field_serializers) + res.model_serializers.update(decorator_infos.model_serializers) + res.model_validators.update(decorator_infos.model_validators) + res.computed_fields.update(decorator_infos.computed_fields) + + if replace_wrapped_methods and to_replace: + for name, value in to_replace: + setattr(typ, name, value) + + res._validate() + return res + + def _validate(self) -> None: + seen: set[str] = set() + for field_ser in self.field_serializers.values(): + for f_name in field_ser.info.fields: + if f_name in seen: + raise PydanticUserError( + f'Multiple field serializer functions were defined for field {f_name!r}, this is not allowed.', + code='multiple-field-serializers', + ) + seen.add(f_name) + + def update_from_config(self, config_wrapper: ConfigWrapper) -> None: + """Update the decorator infos from the configuration of the class they are attached to.""" + for name, computed_field_dec in self.computed_fields.items(): + computed_field_dec.info._update_from_config(config_wrapper, name) + + +def _decorator_infos_for_class( + typ: type[Any], + *, + collect_to_replace: bool, +) -> tuple[DecoratorInfos, list[tuple[str, Any]]]: + """Collect a `DecoratorInfos` for class, without looking into bases.""" + res = DecoratorInfos() + to_replace: list[tuple[str, Any]] = [] + + for var_name, var_value in vars(typ).items(): + if isinstance(var_value, PydanticDescriptorProxy): + info = var_value.decorator_info + if isinstance(info, ValidatorDecoratorInfo): + res.validators[var_name] = Decorator.build(typ, cls_var_name=var_name, shim=var_value.shim, info=info) + elif isinstance(info, FieldValidatorDecoratorInfo): + res.field_validators[var_name] = Decorator.build( + typ, cls_var_name=var_name, shim=var_value.shim, info=info + ) + elif isinstance(info, RootValidatorDecoratorInfo): + res.root_validators[var_name] = Decorator.build( + typ, cls_var_name=var_name, shim=var_value.shim, info=info + ) + elif isinstance(info, FieldSerializerDecoratorInfo): + res.field_serializers[var_name] = Decorator.build( + typ, cls_var_name=var_name, shim=var_value.shim, info=info + ) + elif isinstance(info, ModelValidatorDecoratorInfo): + res.model_validators[var_name] = Decorator.build( + typ, cls_var_name=var_name, shim=var_value.shim, info=info + ) + elif isinstance(info, ModelSerializerDecoratorInfo): + res.model_serializers[var_name] = Decorator.build( + typ, cls_var_name=var_name, shim=var_value.shim, info=info + ) + else: + from ..fields import ComputedFieldInfo + + isinstance(var_value, ComputedFieldInfo) + res.computed_fields[var_name] = Decorator.build(typ, cls_var_name=var_name, shim=None, info=info) + if collect_to_replace: + to_replace.append((var_name, var_value.wrapped)) + + return res, to_replace + + +def inspect_validator( + validator: Callable[..., Any], *, mode: FieldValidatorModes, type: Literal['field', 'model'] +) -> bool: + """Look at a field or model validator function and determine whether it takes an info argument. + + An error is raised if the function has an invalid signature. + + Args: + validator: The validator function to inspect. + mode: The proposed validator mode. + type: The type of validator, either 'field' or 'model'. + + Returns: + Whether the validator takes an info argument. + """ + try: + sig = signature_no_eval(validator) + except (ValueError, TypeError): + # `inspect.signature` might not be able to infer a signature, e.g. with C objects. + # In this case, we assume no info argument is present: + return False + n_positional = count_positional_required_params(sig) + if mode == 'wrap': + if n_positional == 3: + return True + elif n_positional == 2: + return False + else: + assert mode in {'before', 'after', 'plain'}, f"invalid mode: {mode!r}, expected 'before', 'after' or 'plain" + if n_positional == 2: + return True + elif n_positional == 1: + return False + + raise PydanticUserError( + f'Unrecognized {type} validator function signature for {validator} with `mode={mode}`: {sig}', + code='validator-signature', + ) + + +def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> tuple[bool, bool]: + """Look at a field serializer function and determine if it is a field serializer, + and whether it takes an info argument. + + An error is raised if the function has an invalid signature. + + Args: + serializer: The serializer function to inspect. + mode: The serializer mode, either 'plain' or 'wrap'. + + Returns: + Tuple of (is_field_serializer, info_arg). + """ + try: + sig = signature_no_eval(serializer) + except (ValueError, TypeError): + # `inspect.signature` might not be able to infer a signature, e.g. with C objects. + # In this case, we assume no info argument is present and this is not a method: + return (False, False) + + first = next(iter(sig.parameters.values()), None) + is_field_serializer = first is not None and first.name == 'self' + + n_positional = count_positional_required_params(sig) + if is_field_serializer: + # -1 to correct for self parameter + info_arg = _serializer_info_arg(mode, n_positional - 1) + else: + info_arg = _serializer_info_arg(mode, n_positional) + + if info_arg is None: + raise PydanticUserError( + f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', + code='field-serializer-signature', + ) + + return is_field_serializer, info_arg + + +def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: + """Look at a serializer function used via `Annotated` and determine whether it takes an info argument. + + An error is raised if the function has an invalid signature. + + Args: + serializer: The serializer function to check. + mode: The serializer mode, either 'plain' or 'wrap'. + + Returns: + info_arg + """ + try: + sig = signature_no_eval(serializer) + except (ValueError, TypeError): + # `inspect.signature` might not be able to infer a signature, e.g. with C objects. + # In this case, we assume no info argument is present: + return False + info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) + if info_arg is None: + raise PydanticUserError( + f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', + code='field-serializer-signature', + ) + else: + return info_arg + + +def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: + """Look at a model serializer function and determine whether it takes an info argument. + + An error is raised if the function has an invalid signature. + + Args: + serializer: The serializer function to check. + mode: The serializer mode, either 'plain' or 'wrap'. + + Returns: + `info_arg` - whether the function expects an info argument. + """ + if isinstance(serializer, (staticmethod, classmethod)) or not is_instance_method_from_sig(serializer): + raise PydanticUserError( + '`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method' + ) + + sig = signature_no_eval(serializer) + info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) + if info_arg is None: + raise PydanticUserError( + f'Unrecognized model_serializer function signature for {serializer} with `mode={mode}`:{sig}', + code='model-serializer-signature', + ) + else: + return info_arg + + +def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None: + if mode == 'plain': + if n_positional == 1: + # (input_value: Any, /) -> Any + return False + elif n_positional == 2: + # (model: Any, input_value: Any, /) -> Any + return True + else: + assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'" + if n_positional == 2: + # (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any + return False + elif n_positional == 3: + # (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any + return True + + return None + + +AnyDecoratorCallable: TypeAlias = ( + 'Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any], Callable[..., Any]]' +) + + +def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: + """Whether the function is an instance method. + + It will consider a function as instance method if the first parameter of + function is `self`. + + Args: + function: The function to check. + + Returns: + `True` if the function is an instance method, `False` otherwise. + """ + sig = signature_no_eval(unwrap_wrapped_function(function)) + first = next(iter(sig.parameters.values()), None) + if first and first.name == 'self': + return True + return False + + +def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any: + """Apply the `@classmethod` decorator on the function. + + Args: + function: The function to apply the decorator on. + + Return: + The `@classmethod` decorator applied function. + """ + if not isinstance( + unwrap_wrapped_function(function, unwrap_class_static_method=False), classmethod + ) and _is_classmethod_from_sig(function): + return classmethod(function) # type: ignore[arg-type] + return function + + +def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool: + sig = signature_no_eval(unwrap_wrapped_function(function)) + first = next(iter(sig.parameters.values()), None) + if first and first.name == 'cls': + return True + return False + + +def unwrap_wrapped_function( + func: Any, + *, + unwrap_partial: bool = True, + unwrap_class_static_method: bool = True, +) -> Any: + """Recursively unwraps a wrapped function until the underlying function is reached. + This handles property, functools.partial, functools.partialmethod, staticmethod, and classmethod. + + Args: + func: The function to unwrap. + unwrap_partial: If True (default), unwrap partial and partialmethod decorators. + unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod + decorators. If False, only unwrap partial and partialmethod decorators. + + Returns: + The underlying function of the wrapped function. + """ + # Define the types we want to check against as a single tuple. + unwrap_types = ( + (property, cached_property) + + ((partial, partialmethod) if unwrap_partial else ()) + + ((staticmethod, classmethod) if unwrap_class_static_method else ()) + ) + + while isinstance(func, unwrap_types): + if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)): + func = func.__func__ + elif isinstance(func, (partial, partialmethod)): + func = func.func + elif isinstance(func, property): + func = func.fget # arbitrary choice, convenient for computed fields + else: + # Make coverage happy as it can only get here in the last possible case + assert isinstance(func, cached_property) + func = func.func # type: ignore + + return func + + +_function_like = ( + partial, + partialmethod, + types.FunctionType, + types.BuiltinFunctionType, + types.MethodType, + types.WrapperDescriptorType, + types.MethodWrapperType, + types.MemberDescriptorType, +) + + +def get_callable_return_type( + callable_obj: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, +) -> Any | PydanticUndefinedType: + """Get the callable return type. + + Args: + callable_obj: The callable to analyze. + globalns: The globals namespace to use during type annotation evaluation. + localns: The locals namespace to use during type annotation evaluation. + + Returns: + The function return type. + """ + if isinstance(callable_obj, type): + # types are callables, and we assume the return type + # is the type itself (e.g. `int()` results in an instance of `int`). + return callable_obj + + if not isinstance(callable_obj, _function_like): + call_func = getattr(type(callable_obj), '__call__', None) # noqa: B004 + if call_func is not None: + callable_obj = call_func + + hints = get_function_type_hints( + unwrap_wrapped_function(callable_obj), + include_keys={'return'}, + globalns=globalns, + localns=localns, + ) + return hints.get('return', PydanticUndefined) + + +def count_positional_required_params(sig: Signature) -> int: + """Get the number of positional (required) arguments of a signature. + + This function should only be used to inspect signatures of validation and serialization functions. + The first argument (the value being serialized or validated) is counted as a required argument + even if a default value exists. + + Returns: + The number of positional arguments of a signature. + """ + parameters = list(sig.parameters.values()) + return sum( + 1 + for param in parameters + if can_be_positional(param) + # First argument is the value being validated/serialized, and can have a default value + # (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg + # for instance) should be required, and thus without any default value. + and (param.default is Parameter.empty or param is parameters[0]) + ) + + +def ensure_property(f: Any) -> Any: + """Ensure that a function is a `property` or `cached_property`, or is a valid descriptor. + + Args: + f: The function to check. + + Returns: + The function, or a `property` or `cached_property` instance wrapping the function. + """ + if ismethoddescriptor(f) or isdatadescriptor(f): + return f + else: + return property(f) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py new file mode 100644 index 0000000..3427377 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py @@ -0,0 +1,174 @@ +"""Logic for V1 validators, e.g. `@validator` and `@root_validator`.""" + +from __future__ import annotations as _annotations + +from inspect import Parameter, signature +from typing import Any, Union, cast + +from pydantic_core import core_schema +from typing_extensions import Protocol + +from ..errors import PydanticUserError +from ._utils import can_be_positional + + +class V1OnlyValueValidator(Protocol): + """A simple validator, supported for V1 validators and V2 validators.""" + + def __call__(self, __value: Any) -> Any: ... + + +class V1ValidatorWithValues(Protocol): + """A validator with `values` argument, supported for V1 validators and V2 validators.""" + + def __call__(self, __value: Any, values: dict[str, Any]) -> Any: ... + + +class V1ValidatorWithValuesKwOnly(Protocol): + """A validator with keyword only `values` argument, supported for V1 validators and V2 validators.""" + + def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: ... + + +class V1ValidatorWithKwargs(Protocol): + """A validator with `kwargs` argument, supported for V1 validators and V2 validators.""" + + def __call__(self, __value: Any, **kwargs: Any) -> Any: ... + + +class V1ValidatorWithValuesAndKwargs(Protocol): + """A validator with `values` and `kwargs` arguments, supported for V1 validators and V2 validators.""" + + def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... + + +V1Validator = Union[ + V1ValidatorWithValues, V1ValidatorWithValuesKwOnly, V1ValidatorWithKwargs, V1ValidatorWithValuesAndKwargs +] + + +def can_be_keyword(param: Parameter) -> bool: + return param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) + + +def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithInfoValidatorFunction: + """Wrap a V1 style field validator for V2 compatibility. + + Args: + validator: The V1 style field validator. + + Returns: + A wrapped V2 style field validator. + + Raises: + PydanticUserError: If the signature is not supported or the parameters are + not available in Pydantic V2. + """ + sig = signature(validator) + + needs_values_kw = False + + for param_num, (param_name, parameter) in enumerate(sig.parameters.items()): + if can_be_keyword(parameter) and param_name in ('field', 'config'): + raise PydanticUserError( + 'The `field` and `config` parameters are not available in Pydantic V2, ' + 'please use the `info` parameter instead.', + code='validator-field-config-info', + ) + if parameter.kind is Parameter.VAR_KEYWORD: + needs_values_kw = True + elif can_be_keyword(parameter) and param_name == 'values': + needs_values_kw = True + elif can_be_positional(parameter) and param_num == 0: + # value + continue + elif parameter.default is Parameter.empty: # ignore params with defaults e.g. bound by functools.partial + raise PydanticUserError( + f'Unsupported signature for V1 style validator {validator}: {sig} is not supported.', + code='validator-v1-signature', + ) + + if needs_values_kw: + # (v, **kwargs), (v, values, **kwargs), (v, *, values, **kwargs) or (v, *, values) + val1 = cast(V1ValidatorWithValues, validator) + + def wrapper1(value: Any, info: core_schema.ValidationInfo) -> Any: + return val1(value, values=info.data) + + return wrapper1 + else: + val2 = cast(V1OnlyValueValidator, validator) + + def wrapper2(value: Any, _: core_schema.ValidationInfo) -> Any: + return val2(value) + + return wrapper2 + + +RootValidatorValues = dict[str, Any] +# technically tuple[model_dict, model_extra, fields_set] | tuple[dataclass_dict, init_vars] +RootValidatorFieldsTuple = tuple[Any, ...] + + +class V1RootValidatorFunction(Protocol): + """A simple root validator, supported for V1 validators and V2 validators.""" + + def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: ... + + +class V2CoreBeforeRootValidator(Protocol): + """V2 validator with mode='before'.""" + + def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: ... + + +class V2CoreAfterRootValidator(Protocol): + """V2 validator with mode='after'.""" + + def __call__( + self, __fields_tuple: RootValidatorFieldsTuple, __info: core_schema.ValidationInfo + ) -> RootValidatorFieldsTuple: ... + + +def make_v1_generic_root_validator( + validator: V1RootValidatorFunction, pre: bool +) -> V2CoreBeforeRootValidator | V2CoreAfterRootValidator: + """Wrap a V1 style root validator for V2 compatibility. + + Args: + validator: The V1 style field validator. + pre: Whether the validator is a pre validator. + + Returns: + A wrapped V2 style validator. + """ + if pre is True: + # mode='before' for pydantic-core + def _wrapper1(values: RootValidatorValues, _: core_schema.ValidationInfo) -> RootValidatorValues: + return validator(values) + + return _wrapper1 + + # mode='after' for pydantic-core + def _wrapper2(fields_tuple: RootValidatorFieldsTuple, _: core_schema.ValidationInfo) -> RootValidatorFieldsTuple: + if len(fields_tuple) == 2: + # dataclass, this is easy + values, init_vars = fields_tuple + values = validator(values) + return values, init_vars + else: + # ugly hack: to match v1 behaviour, we merge values and model_extra, then split them up based on fields + # afterwards + model_dict, model_extra, fields_set = fields_tuple + if model_extra: + fields = set(model_dict.keys()) + model_dict.update(model_extra) + model_dict_new = validator(model_dict) + for k in list(model_dict_new.keys()): + if k not in fields: + model_extra[k] = model_dict_new.pop(k) + else: + model_dict_new = validator(model_dict) + return model_dict_new, model_extra, fields_set + + return _wrapper2 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py new file mode 100644 index 0000000..fc52f30 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py @@ -0,0 +1,494 @@ +from __future__ import annotations as _annotations + +from collections.abc import Hashable, Sequence +from typing import TYPE_CHECKING, Any, cast + +from pydantic_core import CoreSchema, core_schema + +from ..errors import PydanticUserError +from . import _core_utils +from ._core_utils import ( + CoreSchemaField, +) + +if TYPE_CHECKING: + from ..types import Discriminator + from ._core_metadata import CoreMetadata + + +class MissingDefinitionForUnionRef(Exception): + """Raised when applying a discriminated union discriminator to a schema + requires a definition that is not yet defined + """ + + def __init__(self, ref: str) -> None: + self.ref = ref + super().__init__(f'Missing definition for ref {self.ref!r}') + + +def set_discriminator_in_metadata(schema: CoreSchema, discriminator: Any) -> None: + metadata = cast('CoreMetadata', schema.setdefault('metadata', {})) + metadata['pydantic_internal_union_discriminator'] = discriminator + + +def apply_discriminator( + schema: core_schema.CoreSchema, + discriminator: str | Discriminator, + definitions: dict[str, core_schema.CoreSchema] | None = None, +) -> core_schema.CoreSchema: + """Applies the discriminator and returns a new core schema. + + Args: + schema: The input schema. + discriminator: The name of the field which will serve as the discriminator. + definitions: A mapping of schema ref to schema. + + Returns: + The new core schema. + + Raises: + TypeError: + - If `discriminator` is used with invalid union variant. + - If `discriminator` is used with `Union` type with one variant. + - If `discriminator` value mapped to multiple choices. + MissingDefinitionForUnionRef: + If the definition for ref is missing. + PydanticUserError: + - If a model in union doesn't have a discriminator field. + - If discriminator field has a non-string alias. + - If discriminator fields have different aliases. + - If discriminator field not of type `Literal`. + """ + from ..types import Discriminator + + if isinstance(discriminator, Discriminator): + if isinstance(discriminator.discriminator, str): + discriminator = discriminator.discriminator + else: + return discriminator._convert_schema(schema) + + return _ApplyInferredDiscriminator(discriminator, definitions or {}).apply(schema) + + +class _ApplyInferredDiscriminator: + """This class is used to convert an input schema containing a union schema into one where that union is + replaced with a tagged-union, with all the associated debugging and performance benefits. + + This is done by: + * Validating that the input schema is compatible with the provided discriminator + * Introspecting the schema to determine which discriminator values should map to which union choices + * Handling various edge cases such as 'definitions', 'default', 'nullable' schemas, and more + + I have chosen to implement the conversion algorithm in this class, rather than a function, + to make it easier to maintain state while recursively walking the provided CoreSchema. + """ + + def __init__(self, discriminator: str, definitions: dict[str, core_schema.CoreSchema]): + # `discriminator` should be the name of the field which will serve as the discriminator. + # It must be the python name of the field, and *not* the field's alias. Note that as of now, + # all members of a discriminated union _must_ use a field with the same name as the discriminator. + # This may change if/when we expose a way to manually specify the TaggedUnionSchema's choices. + self.discriminator = discriminator + + # `definitions` should contain a mapping of schema ref to schema for all schemas which might + # be referenced by some choice + self.definitions = definitions + + # `_discriminator_alias` will hold the value, if present, of the alias for the discriminator + # + # Note: following the v1 implementation, we currently disallow the use of different aliases + # for different choices. This is not a limitation of pydantic_core, but if we try to handle + # this, the inference logic gets complicated very quickly, and could result in confusing + # debugging challenges for users making subtle mistakes. + # + # Rather than trying to do the most powerful inference possible, I think we should eventually + # expose a way to more-manually control the way the TaggedUnionSchema is constructed through + # the use of a new type which would be placed as an Annotation on the Union type. This would + # provide the full flexibility/power of pydantic_core's TaggedUnionSchema where necessary for + # more complex cases, without over-complicating the inference logic for the common cases. + self._discriminator_alias: str | None = None + + # `_should_be_nullable` indicates whether the converted union has `None` as an allowed value. + # If `None` is an acceptable value of the (possibly-wrapped) union, we ignore it while + # constructing the TaggedUnionSchema, but set the `_should_be_nullable` attribute to True. + # Once we have constructed the TaggedUnionSchema, if `_should_be_nullable` is True, we ensure + # that the final schema gets wrapped as a NullableSchema. This has the same semantics on the + # python side, but resolves the issue that `None` cannot correspond to any discriminator values. + self._should_be_nullable = False + + # `_is_nullable` is used to track if the final produced schema will definitely be nullable; + # we set it to True if the input schema is wrapped in a nullable schema that we know will be preserved + # as an indication that, even if None is discovered as one of the union choices, we will not need to wrap + # the final value in another nullable schema. + # + # This is more complicated than just checking for the final outermost schema having type 'nullable' thanks + # to the possible presence of other wrapper schemas such as DefinitionsSchema, WithDefaultSchema, etc. + self._is_nullable = False + + # `_choices_to_handle` serves as a stack of choices to add to the tagged union. Initially, choices + # from the union in the wrapped schema will be appended to this list, and the recursive choice-handling + # algorithm may add more choices to this stack as (nested) unions are encountered. + self._choices_to_handle: list[core_schema.CoreSchema] = [] + + # `_tagged_union_choices` is built during the call to `apply`, and will hold the choices to be included + # in the output TaggedUnionSchema that will replace the union from the input schema + self._tagged_union_choices: dict[Hashable, core_schema.CoreSchema] = {} + + # `_used` is changed to True after applying the discriminator to prevent accidental reuse + self._used = False + + def apply(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + """Return a new CoreSchema based on `schema` that uses a tagged-union with the discriminator provided + to this class. + + Args: + schema: The input schema. + + Returns: + The new core schema. + + Raises: + TypeError: + - If `discriminator` is used with invalid union variant. + - If `discriminator` is used with `Union` type with one variant. + - If `discriminator` value mapped to multiple choices. + ValueError: + If the definition for ref is missing. + PydanticUserError: + - If a model in union doesn't have a discriminator field. + - If discriminator field has a non-string alias. + - If discriminator fields have different aliases. + - If discriminator field not of type `Literal`. + """ + assert not self._used + schema = self._apply_to_root(schema) + if self._should_be_nullable and not self._is_nullable: + schema = core_schema.nullable_schema(schema) + self._used = True + return schema + + def _apply_to_root(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + """This method handles the outer-most stage of recursion over the input schema: + unwrapping nullable or definitions schemas, and calling the `_handle_choice` + method iteratively on the choices extracted (recursively) from the possibly-wrapped union. + """ + if schema['type'] == 'nullable': + self._is_nullable = True + wrapped = self._apply_to_root(schema['schema']) + nullable_wrapper = schema.copy() + nullable_wrapper['schema'] = wrapped + return nullable_wrapper + + if schema['type'] == 'definitions': + wrapped = self._apply_to_root(schema['schema']) + definitions_wrapper = schema.copy() + definitions_wrapper['schema'] = wrapped + return definitions_wrapper + + if schema['type'] == 'definition-ref': + schema_ref = schema['schema_ref'] + if schema_ref not in self.definitions: # pragma: no cover + raise MissingDefinitionForUnionRef(schema_ref) + + def_schema = self.definitions[schema_ref] + # If using a referenceable union as discriminated (e.g. `type Pet = Cat | Dog; field: Pet = Field(discriminator=...)`): + if def_schema['type'] == 'union': + schema = def_schema.copy() + schema.pop('ref') + + if schema['type'] != 'union': + # If the schema is not a union, it probably means it just had a single member and + # was flattened by pydantic_core. + # However, it still may make sense to apply the discriminator to this schema, + # as a way to get discriminated-union-style error messages, so we allow this here. + schema = core_schema.union_schema([schema]) + + # Reverse the choices list before extending the stack so that they get handled in the order they occur + choices_schemas = [v[0] if isinstance(v, tuple) else v for v in schema['choices'][::-1]] + self._choices_to_handle.extend(choices_schemas) + while self._choices_to_handle: + choice = self._choices_to_handle.pop() + self._handle_choice(choice) + + if self._discriminator_alias is not None and self._discriminator_alias != self.discriminator: + # * We need to annotate `discriminator` as a union here to handle both branches of this conditional + # * We need to annotate `discriminator` as list[list[str | int]] and not list[list[str]] due to the + # invariance of list, and because list[list[str | int]] is the type of the discriminator argument + # to tagged_union_schema below + # * See the docstring of pydantic_core.core_schema.tagged_union_schema for more details about how to + # interpret the value of the discriminator argument to tagged_union_schema. (The list[list[str]] here + # is the appropriate way to provide a list of fallback attributes to check for a discriminator value.) + discriminator: str | list[list[str | int]] = [[self.discriminator], [self._discriminator_alias]] + else: + discriminator = self.discriminator + return core_schema.tagged_union_schema( + choices=self._tagged_union_choices, + discriminator=discriminator, + custom_error_type=schema.get('custom_error_type'), + custom_error_message=schema.get('custom_error_message'), + custom_error_context=schema.get('custom_error_context'), + strict=False, + from_attributes=True, + ref=schema.get('ref'), + metadata=schema.get('metadata'), + serialization=schema.get('serialization'), + ) + + def _handle_choice(self, choice: core_schema.CoreSchema) -> None: + """This method handles the "middle" stage of recursion over the input schema. + Specifically, it is responsible for handling each choice of the outermost union + (and any "coalesced" choices obtained from inner unions). + + Here, "handling" entails: + * Coalescing nested unions and compatible tagged-unions + * Tracking the presence of 'none' and 'nullable' schemas occurring as choices + * Validating that each allowed discriminator value maps to a unique choice + * Updating the _tagged_union_choices mapping that will ultimately be used to build the TaggedUnionSchema. + """ + if choice['type'] == 'definition-ref': + if choice['schema_ref'] not in self.definitions: + raise MissingDefinitionForUnionRef(choice['schema_ref']) + + if choice['type'] == 'none': + self._should_be_nullable = True + elif choice['type'] == 'definitions': + self._handle_choice(choice['schema']) + elif choice['type'] == 'nullable': + self._should_be_nullable = True + self._handle_choice(choice['schema']) # unwrap the nullable schema + elif choice['type'] == 'union': + # Reverse the choices list before extending the stack so that they get handled in the order they occur + choices_schemas = [v[0] if isinstance(v, tuple) else v for v in choice['choices'][::-1]] + self._choices_to_handle.extend(choices_schemas) + elif choice['type'] not in { + 'model', + 'typed-dict', + 'tagged-union', + 'lax-or-strict', + 'dataclass', + 'dataclass-args', + 'definition-ref', + } and not _core_utils.is_function_with_inner_schema(choice): + # We should eventually handle 'definition-ref' as well + err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.' + if choice['type'] == 'list': + err_str += ( + ' If you are making use of a list of union types, make sure the discriminator is applied to the ' + 'union type and not the list (e.g. `list[Annotated[ | , Field(discriminator=...)]]`).' + ) + raise TypeError(err_str) + else: + if choice['type'] == 'tagged-union' and self._is_discriminator_shared(choice): + # In this case, this inner tagged-union is compatible with the outer tagged-union, + # and its choices can be coalesced into the outer TaggedUnionSchema. + subchoices = [x for x in choice['choices'].values() if not isinstance(x, (str, int))] + # Reverse the choices list before extending the stack so that they get handled in the order they occur + self._choices_to_handle.extend(subchoices[::-1]) + return + + inferred_discriminator_values = self._infer_discriminator_values_for_choice(choice, source_name=None) + self._set_unique_choice_for_values(choice, inferred_discriminator_values) + + def _is_discriminator_shared(self, choice: core_schema.TaggedUnionSchema) -> bool: + """This method returns a boolean indicating whether the discriminator for the `choice` + is the same as that being used for the outermost tagged union. This is used to + determine whether this TaggedUnionSchema choice should be "coalesced" into the top level, + or whether it should be treated as a separate (nested) choice. + """ + inner_discriminator = choice['discriminator'] + return inner_discriminator == self.discriminator or ( + isinstance(inner_discriminator, list) + and (self.discriminator in inner_discriminator or [self.discriminator] in inner_discriminator) + ) + + def _infer_discriminator_values_for_choice( # noqa C901 + self, choice: core_schema.CoreSchema, source_name: str | None + ) -> list[str | int]: + """This function recurses over `choice`, extracting all discriminator values that should map to this choice. + + `model_name` is accepted for the purpose of producing useful error messages. + """ + if choice['type'] == 'definitions': + return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name) + + elif _core_utils.is_function_with_inner_schema(choice): + return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name) + + elif choice['type'] == 'lax-or-strict': + return sorted( + set( + self._infer_discriminator_values_for_choice(choice['lax_schema'], source_name=None) + + self._infer_discriminator_values_for_choice(choice['strict_schema'], source_name=None) + ) + ) + + elif choice['type'] == 'tagged-union': + values: list[str | int] = [] + # Ignore str/int "choices" since these are just references to other choices + subchoices = [x for x in choice['choices'].values() if not isinstance(x, (str, int))] + for subchoice in subchoices: + subchoice_values = self._infer_discriminator_values_for_choice(subchoice, source_name=None) + values.extend(subchoice_values) + return values + + elif choice['type'] == 'union': + values = [] + for subchoice in choice['choices']: + subchoice_schema = subchoice[0] if isinstance(subchoice, tuple) else subchoice + subchoice_values = self._infer_discriminator_values_for_choice(subchoice_schema, source_name=None) + values.extend(subchoice_values) + return values + + elif choice['type'] == 'nullable': + self._should_be_nullable = True + return self._infer_discriminator_values_for_choice(choice['schema'], source_name=None) + + elif choice['type'] == 'model': + return self._infer_discriminator_values_for_choice(choice['schema'], source_name=choice['cls'].__name__) + + elif choice['type'] == 'dataclass': + return self._infer_discriminator_values_for_choice(choice['schema'], source_name=choice['cls'].__name__) + + elif choice['type'] == 'model-fields': + return self._infer_discriminator_values_for_model_choice(choice, source_name=source_name) + + elif choice['type'] == 'dataclass-args': + return self._infer_discriminator_values_for_dataclass_choice(choice, source_name=source_name) + + elif choice['type'] == 'typed-dict': + return self._infer_discriminator_values_for_typed_dict_choice(choice, source_name=source_name) + + elif choice['type'] == 'definition-ref': + schema_ref = choice['schema_ref'] + if schema_ref not in self.definitions: + raise MissingDefinitionForUnionRef(schema_ref) + return self._infer_discriminator_values_for_choice(self.definitions[schema_ref], source_name=source_name) + else: + err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.' + if choice['type'] == 'list': + err_str += ( + ' If you are making use of a list of union types, make sure the discriminator is applied to the ' + 'union type and not the list (e.g. `list[Annotated[ | , Field(discriminator=...)]]`).' + ) + raise TypeError(err_str) + + def _infer_discriminator_values_for_typed_dict_choice( + self, choice: core_schema.TypedDictSchema, source_name: str | None = None + ) -> list[str | int]: + """This method just extracts the _infer_discriminator_values_for_choice logic specific to TypedDictSchema + for the sake of readability. + """ + source = 'TypedDict' if source_name is None else f'TypedDict {source_name!r}' + field = choice['fields'].get(self.discriminator) + if field is None: + raise PydanticUserError( + f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field' + ) + return self._infer_discriminator_values_for_field(field, source) + + def _infer_discriminator_values_for_model_choice( + self, choice: core_schema.ModelFieldsSchema, source_name: str | None = None + ) -> list[str | int]: + source = 'ModelFields' if source_name is None else f'Model {source_name!r}' + field = choice['fields'].get(self.discriminator) + if field is None: + raise PydanticUserError( + f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field' + ) + return self._infer_discriminator_values_for_field(field, source) + + def _infer_discriminator_values_for_dataclass_choice( + self, choice: core_schema.DataclassArgsSchema, source_name: str | None = None + ) -> list[str | int]: + source = 'DataclassArgs' if source_name is None else f'Dataclass {source_name!r}' + for field in choice['fields']: + if field['name'] == self.discriminator: + break + else: + raise PydanticUserError( + f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field' + ) + return self._infer_discriminator_values_for_field(field, source) + + def _infer_discriminator_values_for_field(self, field: CoreSchemaField, source: str) -> list[str | int]: + if field['type'] == 'computed-field': + # This should never occur as a discriminator, as it is only relevant to serialization + return [] + alias = field.get('validation_alias', self.discriminator) + if not isinstance(alias, str): + raise PydanticUserError( + f'Alias {alias!r} is not supported in a discriminated union', code='discriminator-alias-type' + ) + if self._discriminator_alias is None: + self._discriminator_alias = alias + elif self._discriminator_alias != alias: + raise PydanticUserError( + f'Aliases for discriminator {self.discriminator!r} must be the same ' + f'(got {alias}, {self._discriminator_alias})', + code='discriminator-alias', + ) + return self._infer_discriminator_values_for_inner_schema(field['schema'], source) + + def _infer_discriminator_values_for_inner_schema( + self, schema: core_schema.CoreSchema, source: str + ) -> list[str | int]: + """When inferring discriminator values for a field, we typically extract the expected values from a literal + schema. This function does that, but also handles nested unions and defaults. + """ + if schema['type'] == 'literal': + return schema['expected'] + + elif schema['type'] == 'union': + # Generally when multiple values are allowed they should be placed in a single `Literal`, but + # we add this case to handle the situation where a field is annotated as a `Union` of `Literal`s. + # For example, this lets us handle `Union[Literal['key'], Union[Literal['Key'], Literal['KEY']]]` + values: list[Any] = [] + for choice in schema['choices']: + choice_schema = choice[0] if isinstance(choice, tuple) else choice + choice_values = self._infer_discriminator_values_for_inner_schema(choice_schema, source) + values.extend(choice_values) + return values + + elif schema['type'] == 'default': + # This will happen if the field has a default value; we ignore it while extracting the discriminator values + return self._infer_discriminator_values_for_inner_schema(schema['schema'], source) + + elif schema['type'] == 'function-after': + # After validators don't affect the discriminator values + return self._infer_discriminator_values_for_inner_schema(schema['schema'], source) + + elif schema['type'] == 'model' and schema.get('root_model'): + # Support RootModel[Literal[...]] as discriminator field type + return self._infer_discriminator_values_for_inner_schema(schema['schema'], source) + + elif schema['type'] in {'function-before', 'function-wrap', 'function-plain'}: + validator_type = repr(schema['type'].split('-')[1]) + raise PydanticUserError( + f'Cannot use a mode={validator_type} validator in the' + f' discriminator field {self.discriminator!r} of {source}', + code='discriminator-validator', + ) + + else: + raise PydanticUserError( + f'{source} needs field {self.discriminator!r} to be of type `Literal`', + code='discriminator-needs-literal', + ) + + def _set_unique_choice_for_values(self, choice: core_schema.CoreSchema, values: Sequence[str | int]) -> None: + """This method updates `self.tagged_union_choices` so that all provided (discriminator) `values` map to the + provided `choice`, validating that none of these values already map to another (different) choice. + """ + for discriminator_value in values: + if discriminator_value in self._tagged_union_choices: + # It is okay if `value` is already in tagged_union_choices as long as it maps to the same value. + # Because tagged_union_choices may map values to other values, we need to walk the choices dict + # until we get to a "real" choice, and confirm that is equal to the one assigned. + existing_choice = self._tagged_union_choices[discriminator_value] + if existing_choice != choice: + raise TypeError( + f'Value {discriminator_value!r} for discriminator ' + f'{self.discriminator!r} mapped to multiple choices' + ) + else: + self._tagged_union_choices[discriminator_value] = choice diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py new file mode 100644 index 0000000..6df77bf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py @@ -0,0 +1,113 @@ +"""Utilities related to attribute docstring extraction.""" + +from __future__ import annotations + +import ast +import inspect +import sys +import textwrap +from typing import Any + + +class DocstringVisitor(ast.NodeVisitor): + def __init__(self) -> None: + super().__init__() + + self.target: str | None = None + self.attrs: dict[str, str] = {} + self.previous_node_type: type[ast.AST] | None = None + + def visit(self, node: ast.AST) -> Any: + node_result = super().visit(node) + self.previous_node_type = type(node) + return node_result + + def visit_AnnAssign(self, node: ast.AnnAssign) -> Any: + if isinstance(node.target, ast.Name): + self.target = node.target.id + + def visit_Expr(self, node: ast.Expr) -> Any: + if ( + isinstance(node.value, ast.Constant) + and isinstance(node.value.value, str) + and self.previous_node_type is ast.AnnAssign + ): + docstring = inspect.cleandoc(node.value.value) + if self.target: + self.attrs[self.target] = docstring + self.target = None + + +def _dedent_source_lines(source: list[str]) -> str: + # Required for nested class definitions, e.g. in a function block + dedent_source = textwrap.dedent(''.join(source)) + if dedent_source.startswith((' ', '\t')): + # We are in the case where there's a dedented (usually multiline) string + # at a lower indentation level than the class itself. We wrap our class + # in a function as a workaround. + dedent_source = f'def dedent_workaround():\n{dedent_source}' + return dedent_source + + +def _extract_source_from_frame(cls: type[Any]) -> list[str] | None: + frame = inspect.currentframe() + + while frame: + if inspect.getmodule(frame) is inspect.getmodule(cls): + lnum = frame.f_lineno + try: + lines, _ = inspect.findsource(frame) + except OSError: # pragma: no cover + # Source can't be retrieved (maybe because running in an interactive terminal), + # we don't want to error here. + pass + else: + block_lines = inspect.getblock(lines[lnum - 1 :]) + dedent_source = _dedent_source_lines(block_lines) + try: + block_tree = ast.parse(dedent_source) + except SyntaxError: + pass + else: + stmt = block_tree.body[0] + if isinstance(stmt, ast.FunctionDef) and stmt.name == 'dedent_workaround': + # `_dedent_source_lines` wrapped the class around the workaround function + stmt = stmt.body[0] + if isinstance(stmt, ast.ClassDef) and stmt.name == cls.__name__: + return block_lines + + frame = frame.f_back + + +def extract_docstrings_from_cls(cls: type[Any], use_inspect: bool = False) -> dict[str, str]: + """Map model attributes and their corresponding docstring. + + Args: + cls: The class of the Pydantic model to inspect. + use_inspect: Whether to skip usage of frames to find the object and use + the `inspect` module instead. + + Returns: + A mapping containing attribute names and their corresponding docstring. + """ + if use_inspect or sys.version_info >= (3, 13): + # On Python < 3.13, `inspect.getsourcelines()` might not work as expected + # if two classes have the same name in the same source file. + # On Python 3.13+, it will use the new `__firstlineno__` class attribute, + # making it way more robust. + try: + source, _ = inspect.getsourcelines(cls) + except OSError: # pragma: no cover + return {} + else: + # TODO remove this implementation when we drop support for Python 3.12: + source = _extract_source_from_frame(cls) + + if not source: + return {} + + dedent_source = _dedent_source_lines(source) + + visitor = DocstringVisitor() + visitor.visit(ast.parse(dedent_source)) + return visitor.attrs diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py new file mode 100644 index 0000000..9a2a96b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py @@ -0,0 +1,729 @@ +"""Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`.""" + +from __future__ import annotations as _annotations + +import dataclasses +import warnings +from collections.abc import Mapping +from functools import cache +from inspect import Parameter, ismethoddescriptor +from re import Pattern +from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast + +from pydantic_core import PydanticUndefined +from typing_extensions import TypeIs +from typing_inspection.introspection import AnnotationSource + +from pydantic import PydanticDeprecatedSince211 +from pydantic.errors import PydanticUserError + +from ..aliases import AliasGenerator +from . import _generics, _typing_extra +from ._config import ConfigWrapper +from ._docs_extraction import extract_docstrings_from_cls +from ._import_utils import import_cached_base_model, import_cached_field_info +from ._internal_dataclass import slots_true +from ._namespace_utils import NsResolver +from ._repr import Representation +from ._utils import can_be_positional, get_first_not_none + +if TYPE_CHECKING: + from annotated_types import BaseMetadata + + from ..fields import FieldInfo + from ..main import BaseModel + from ._dataclasses import PydanticDataclass, StandardDataclass + from ._decorators import DecoratorInfos + + +class PydanticMetadata(Representation): + """Base class for annotation markers like `Strict`.""" + + __slots__ = () + + +@dataclasses.dataclass(**slots_true) # TODO: make kw_only when we drop support for 3.9. +class PydanticExtraInfo: + # TODO: make use of PEP 747: + annotation: Any + complete: bool + + +def pydantic_general_metadata(**metadata: Any) -> BaseMetadata: + """Create a new `_PydanticGeneralMetadata` class with the given metadata. + + Args: + **metadata: The metadata to add. + + Returns: + The new `_PydanticGeneralMetadata` class. + """ + return _general_metadata_cls()(metadata) # type: ignore + + +@cache +def _general_metadata_cls() -> type[BaseMetadata]: + """Do it this way to avoid importing `annotated_types` at import time.""" + from annotated_types import BaseMetadata + + class _PydanticGeneralMetadata(PydanticMetadata, BaseMetadata): + """Pydantic general metadata like `max_digits`.""" + + def __init__(self, metadata: Any): + self.__dict__ = metadata + + return _PydanticGeneralMetadata # type: ignore + + +def _check_protected_namespaces( + protected_namespaces: tuple[str | Pattern[str], ...], + ann_name: str, + bases: tuple[type[Any], ...], + cls_name: str, +) -> None: + BaseModel = import_cached_base_model() + + for protected_namespace in protected_namespaces: + ns_violation = False + if isinstance(protected_namespace, Pattern): + ns_violation = protected_namespace.match(ann_name) is not None + elif isinstance(protected_namespace, str): + ns_violation = ann_name.startswith(protected_namespace) + + if ns_violation: + for b in bases: + if hasattr(b, ann_name): + if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})): + raise ValueError( + f'Field {ann_name!r} conflicts with member {getattr(b, ann_name)}' + f' of protected namespace {protected_namespace!r}.' + ) + else: + valid_namespaces: list[str] = [] + for pn in protected_namespaces: + if isinstance(pn, Pattern): + if not pn.match(ann_name): + valid_namespaces.append(f're.compile({pn.pattern!r})') + else: + if not ann_name.startswith(pn): + valid_namespaces.append(f"'{pn}'") + + valid_namespaces_str = f'({", ".join(valid_namespaces)}{",)" if len(valid_namespaces) == 1 else ")"}' + + warnings.warn( + f'Field {ann_name!r} in {cls_name!r} conflicts with protected namespace {protected_namespace!r}.\n\n' + f"You may be able to solve this by setting the 'protected_namespaces' configuration to {valid_namespaces_str}.", + UserWarning, + stacklevel=5, + ) + + +def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], use_inspect: bool = False) -> None: + fields_docs = extract_docstrings_from_cls(cls, use_inspect=use_inspect) + for ann_name, field_info in fields.items(): + if field_info.description is None and ann_name in fields_docs: + field_info.description = fields_docs[ann_name] + + +def _apply_field_title_generator_to_field_info( + title_generator: Callable[[str, FieldInfo], str], + field_name: str, + field_info: FieldInfo, +): + if field_info.title is None: + title = title_generator(field_name, field_info) + if not isinstance(title, str): + raise TypeError(f'field_title_generator {title_generator} must return str, not {title.__class__}') + + field_info.title = title + + +def _apply_alias_generator_to_field_info( + alias_generator: Callable[[str], str] | AliasGenerator, field_name: str, field_info: FieldInfo +): + """Apply an alias generator to aliases on a `FieldInfo` instance if appropriate. + + Args: + alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance. + field_name: The name of the field from which to generate the alias. + field_info: The `FieldInfo` instance to which the alias generator is (maybe) applied. + """ + # Apply an alias_generator if + # 1. An alias is not specified + # 2. An alias is specified, but the priority is <= 1 + if ( + field_info.alias_priority is None + or field_info.alias_priority <= 1 + or field_info.alias is None + or field_info.validation_alias is None + or field_info.serialization_alias is None + ): + alias, validation_alias, serialization_alias = None, None, None + + if isinstance(alias_generator, AliasGenerator): + alias, validation_alias, serialization_alias = alias_generator.generate_aliases(field_name) + elif callable(alias_generator): + alias = alias_generator(field_name) + if not isinstance(alias, str): + raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}') + + # if priority is not set, we set to 1 + # which supports the case where the alias_generator from a child class is used + # to generate an alias for a field in a parent class + if field_info.alias_priority is None or field_info.alias_priority <= 1: + field_info.alias_priority = 1 + + # if the priority is 1, then we set the aliases to the generated alias + if field_info.alias_priority == 1: + field_info.serialization_alias = get_first_not_none(serialization_alias, alias) + field_info.validation_alias = get_first_not_none(validation_alias, alias) + field_info.alias = alias + + # if any of the aliases are not set, then we set them to the corresponding generated alias + if field_info.alias is None: + field_info.alias = alias + if field_info.serialization_alias is None: + field_info.serialization_alias = get_first_not_none(serialization_alias, alias) + if field_info.validation_alias is None: + field_info.validation_alias = get_first_not_none(validation_alias, alias) + + +def update_field_from_config(config_wrapper: ConfigWrapper, field_name: str, field_info: FieldInfo) -> None: + """Update the `FieldInfo` instance from the configuration set on the model it belongs to. + + This will apply the title and alias generators from the configuration. + + Args: + config_wrapper: The configuration from the model. + field_name: The field name the `FieldInfo` instance is attached to. + field_info: The `FieldInfo` instance to update. + """ + field_title_generator = field_info.field_title_generator or config_wrapper.field_title_generator + if field_title_generator is not None: + _apply_field_title_generator_to_field_info(field_title_generator, field_name, field_info) + if config_wrapper.alias_generator is not None: + _apply_alias_generator_to_field_info(config_wrapper.alias_generator, field_name, field_info) + + +_deprecated_method_names = {'dict', 'json', 'copy', '_iter', '_copy_and_set_values', '_calculate_keys'} + +_deprecated_classmethod_names = { + 'parse_obj', + 'parse_raw', + 'parse_file', + 'from_orm', + 'construct', + 'schema', + 'schema_json', + 'validate', + 'update_forward_refs', + '_get_value', +} + + +def collect_model_fields( # noqa: C901 + cls: type[BaseModel], + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver, + *, + typevars_map: Mapping[TypeVar, Any] | None = None, +) -> tuple[dict[str, FieldInfo], PydanticExtraInfo | None, set[str]]: + """Collect the fields and class variables names of a nascent Pydantic model. + + The fields collection process is *lenient*, meaning it won't error if string annotations + fail to evaluate. If this happens, the original annotation (and assigned value, if any) + is stored on the created `FieldInfo` instance. + + The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model), + and will make use of these stored attributes. + + Args: + cls: BaseModel or dataclass. + config_wrapper: The config wrapper instance. + ns_resolver: Namespace resolver to use when getting model annotations. + typevars_map: A dictionary mapping type variables to their concrete types. + + Returns: + A three-tuple containing the model fields, the `PydanticExtraInfo` instance if the `__pydantic_extra__` annotation is set, + and class variables names. + + Raises: + NameError: + - If there is a conflict between a field name and protected namespaces. + - If there is a field other than `root` in `RootModel`. + - If a field shadows an attribute in the parent model. + """ + FieldInfo_ = import_cached_field_info() + BaseModel_ = import_cached_base_model() + + bases = cls.__bases__ + parent_fields_lookup: dict[str, FieldInfo] = {} + for base in reversed(bases): + if model_fields := getattr(base, '__pydantic_fields__', None): + parent_fields_lookup.update(model_fields) + + type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver) + + # `cls_annotations` is only used to determine if an annotation comes from a parent class + cls_annotations = _typing_extra.safe_get_annotations(cls) + + fields: dict[str, FieldInfo] = {} + + class_vars: set[str] = set() + for ann_name, (ann_type, evaluated) in type_hints.items(): + if ann_name == 'model_config': + # We never want to treat `model_config` as a field + # Note: we may need to change this logic if/when we introduce a `BareModel` class with no + # protected namespaces (where `model_config` might be allowed as a field name) + continue + + _check_protected_namespaces( + protected_namespaces=config_wrapper.protected_namespaces, + ann_name=ann_name, + bases=bases, + cls_name=cls.__name__, + ) + + if _typing_extra.is_classvar_annotation(ann_type): + class_vars.add(ann_name) + continue + + assigned_value = getattr(cls, ann_name, PydanticUndefined) + if assigned_value is not PydanticUndefined and ( + # One of the deprecated instance methods was used as a field name (e.g. `dict()`): + any(getattr(BaseModel_, depr_name, None) is assigned_value for depr_name in _deprecated_method_names) + # One of the deprecated class methods was used as a field name (e.g. `schema()`): + or ( + hasattr(assigned_value, '__func__') + and any( + getattr(getattr(BaseModel_, depr_name, None), '__func__', None) is assigned_value.__func__ # pyright: ignore[reportAttributeAccessIssue] + for depr_name in _deprecated_classmethod_names + ) + ) + ): + # Then `assigned_value` would be the method, even though no default was specified: + assigned_value = PydanticUndefined + + if not is_valid_field_name(ann_name): + continue + if cls.__pydantic_root_model__ and ann_name != 'root': + raise NameError( + f"Unexpected field with name {ann_name!r}; only 'root' is allowed as a field of a `RootModel`" + ) + + for base in bases: + if hasattr(base, ann_name): + if ann_name not in cls_annotations: + # Don't warn when a field exists in a parent class but has not been defined in the current class + continue + + # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get + # "... shadows an attribute" warnings + generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') + if base is generic_origin: + # Don't warn when "shadowing" of attributes in parametrized generics + continue + + dataclass_fields = { + field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ()) + } + if ann_name in dataclass_fields: + # Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set + # on the class instance. + continue + + warnings.warn( + f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent ' + f'"{base.__qualname__}"', + UserWarning, + stacklevel=4, + ) + + if assigned_value is PydanticUndefined: # no assignment, just a plain annotation + if ann_name in cls_annotations or ann_name not in parent_fields_lookup: + # field is either: + # - present in the current model's annotations (and *not* from parent classes) + # - not found on any base classes; this seems to be caused by fields not getting + # generated due to models not being fully defined while initializing recursive models. + # Nothing stops us from just creating a `FieldInfo` for this type hint, so we do this. + field_info = FieldInfo_.from_annotation(ann_type, _source=AnnotationSource.CLASS) + field_info._original_annotation = ann_type + if not evaluated: + field_info._complete = False + # Store the original annotation that should be used to rebuild + # the field info later: + else: + # The field was present on one of the (possibly multiple) base classes, we make a copy directly from it. + parent_field_info = parent_fields_lookup[ann_name]._copy() + + # The only case where substituting the type variables is relevant (i.e. when `typevars_map` is not empty) + # is when a generic class is parameterized (e.g. `MyGenericModel[int, str]`), which creates a new class object + # (unlike the stdlib genercis that create a generic alias). In this case, we are guaranteed to only have to copy + # from the origin/parent model (e.g. `MyGenericModel`). + if typevars_map: + field_info = _recreate_field_info( + parent_field_info, ns_resolver=ns_resolver, typevars_map=typevars_map, lenient=True + ) + else: + field_info = parent_field_info + + else: # An assigned value is present (either the default value, or a `Field()` function) + if isinstance(assigned_value, FieldInfo_) and ismethoddescriptor(assigned_value.default): + # `assigned_value` was fetched using `getattr`, which triggers a call to `__get__` + # for descriptors, so we do the same if the `= field(default=...)` form is used. + # Note that we only do this for method descriptors for now, we might want to + # extend this to any descriptor in the future (by simply checking for + # `hasattr(assigned_value.default, '__get__')`). + default = assigned_value.default.__get__(None, cls) + assigned_value.default = default + assigned_value._attributes_set['default'] = default + + field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS) + + # Store the original annotation and assignment value that could be used to rebuild the field info later. + field_info._original_assignment = assigned_value + field_info._original_annotation = ann_type + if not evaluated: + field_info._complete = False + elif 'final' in field_info._qualifiers and not field_info.is_required(): + warnings.warn( + f'Annotation {ann_name!r} is marked as final and has a default value. Pydantic treats {ann_name!r} as a ' + 'class variable, but it will be considered as a normal field in V3 to be aligned with dataclasses. If you ' + f'still want {ann_name!r} to be considered as a class variable, annotate it as: `ClassVar[] = .`', + category=PydanticDeprecatedSince211, + # Incorrect when `create_model` is used, but the chance that final with a default is used is low in that case: + stacklevel=4, + ) + class_vars.add(ann_name) + continue + + # attributes which are fields are removed from the class namespace: + # 1. To match the behaviour of annotation-only fields + # 2. To avoid false positives in the NameError check above + try: + delattr(cls, ann_name) + except AttributeError: + pass # indicates the attribute was on a parent class + + # Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__ + # to make sure the decorators have already been built for this exact class + decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__'] + if ann_name in decorators.computed_fields: + raise TypeError( + f'Field {ann_name!r} of class {cls.__name__!r} overrides symbol of same name in a parent class. ' + 'This override with a computed_field is incompatible.' + ) + fields[ann_name] = field_info + + if field_info._complete: + # If not complete, this will be called in `rebuild_model_fields()`: + update_field_from_config(config_wrapper, ann_name, field_info) + + if config_wrapper.use_attribute_docstrings: + _update_fields_from_docstrings(cls, fields) + + pydantic_extra_info: PydanticExtraInfo | None = None + if '__pydantic_extra__' in type_hints: + ann, complete = type_hints['__pydantic_extra__'] + pydantic_extra_info = PydanticExtraInfo( + annotation=ann, + complete=complete, + ) + + return fields, pydantic_extra_info, class_vars + + +def rebuild_model_fields( + cls: type[BaseModel], + *, + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver, + typevars_map: Mapping[TypeVar, Any], +) -> tuple[dict[str, FieldInfo], PydanticExtraInfo | None]: + """Rebuild the (already present) model fields by trying to reevaluate annotations. + + This function should be called whenever a model with incomplete fields is encountered. + + Returns: + A two-tuple, the first element being the rebuilt fields, the second element being + the rebuild `PydanticExtraInfo` instance, if available. + + Raises: + NameError: If one of the annotations failed to evaluate. + + Note: + This function *doesn't* mutate the model fields in place, as it can be called during + schema generation, where you don't want to mutate other model's fields. + """ + rebuilt_fields: dict[str, FieldInfo] = {} + with ns_resolver.push(cls): + for f_name, field_info in cls.__pydantic_fields__.items(): + if field_info._complete: + rebuilt_fields[f_name] = field_info + else: + new_field = _recreate_field_info( + field_info, ns_resolver=ns_resolver, typevars_map=typevars_map, lenient=False + ) + update_field_from_config(config_wrapper, f_name, new_field) + rebuilt_fields[f_name] = new_field + + if cls.__pydantic_extra_info__ is not None and not cls.__pydantic_extra_info__.complete: + rebuilt_extra_info = PydanticExtraInfo( + annotation=_typing_extra.eval_type( + cls.__pydantic_extra_info__.annotation, *ns_resolver.types_namespace + ), + complete=True, + ) + else: + rebuilt_extra_info = cls.__pydantic_extra_info__ + + return rebuilt_fields, rebuilt_extra_info + + +def _recreate_field_info( + field_info: FieldInfo, + ns_resolver: NsResolver, + typevars_map: Mapping[TypeVar, Any], + *, + lenient: bool, +) -> FieldInfo: + FieldInfo_ = import_cached_field_info() + + existing_desc = field_info.description + if lenient: + ann = _generics.replace_types(field_info._original_annotation, typevars_map) + ann, evaluated = _typing_extra.try_eval_type( + ann, + *ns_resolver.types_namespace, + ) + else: + # Not the best pattern, maybe we could ship our own `eval_type()`, + # that would replace the type variables on the fly during evaluation. + ann = _typing_extra.eval_type( + field_info._original_annotation, + *ns_resolver.types_namespace, + ) + ann = _generics.replace_types(ann, typevars_map) + ann = _typing_extra.eval_type( + ann, + *ns_resolver.types_namespace, + ) + evaluated = True + + if (assign := field_info._original_assignment) is PydanticUndefined: + new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS) + else: + new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS) + new_field._original_assignment = assign + new_field._original_annotation = ann + # The description might come from the docstring if `use_attribute_docstrings` was `True`: + new_field.description = new_field.description if new_field.description is not None else existing_desc + if not evaluated: + new_field._complete = False + + return new_field + + +def collect_dataclass_fields( + cls: type[StandardDataclass], + *, + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver | None = None, + typevars_map: dict[Any, Any] | None = None, +) -> dict[str, FieldInfo]: + """Collect the fields of a dataclass. + + Args: + cls: dataclass. + config_wrapper: The config wrapper instance. + ns_resolver: Namespace resolver to use when getting dataclass annotations. + Defaults to an empty instance. + typevars_map: A dictionary mapping type variables to their concrete types. + + Returns: + The dataclass fields. + """ + FieldInfo_ = import_cached_field_info() + + fields: dict[str, FieldInfo] = {} + ns_resolver = ns_resolver or NsResolver() + dataclass_fields = cls.__dataclass_fields__ + + # The logic here is similar to `_typing_extra.get_cls_type_hints`, + # although we do it manually as stdlib dataclasses already have annotations + # collected in each class: + for base in reversed(cls.__mro__): + if not dataclasses.is_dataclass(base): + continue + + with ns_resolver.push(base): + for ann_name, dataclass_field in dataclass_fields.items(): + base_anns = _typing_extra.safe_get_annotations(base) + + if ann_name not in base_anns: + # `__dataclass_fields__`contains every field, even the ones from base classes. + # Only collect the ones defined on `base`. + continue + + globalns, localns = ns_resolver.types_namespace + ann_type, evaluated = _typing_extra.try_eval_type(dataclass_field.type, globalns, localns) + + if _typing_extra.is_classvar_annotation(ann_type): + continue + + if ( + not dataclass_field.init + and dataclass_field.default is dataclasses.MISSING + and dataclass_field.default_factory is dataclasses.MISSING + ): + # TODO: We should probably do something with this so that validate_assignment behaves properly + # Issue: https://github.com/pydantic/pydantic/issues/5470 + continue + + if isinstance(dataclass_field.default, FieldInfo_): + if dataclass_field.default.init_var: + if dataclass_field.default.init is False: + raise PydanticUserError( + f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.', + code='clashing-init-and-init-var', + ) + + # TODO: same note as above re validate_assignment + continue + field_info = FieldInfo_.from_annotated_attribute( + ann_type, dataclass_field.default, _source=AnnotationSource.DATACLASS + ) + field_info._original_assignment = dataclass_field.default + else: + field_info = FieldInfo_.from_annotated_attribute( + ann_type, dataclass_field, _source=AnnotationSource.DATACLASS + ) + field_info._original_assignment = dataclass_field + + if not evaluated: + field_info._complete = False + field_info._original_annotation = ann_type + + fields[ann_name] = field_info + update_field_from_config(config_wrapper, ann_name, field_info) + + if field_info.default is not PydanticUndefined and isinstance( + getattr(cls, ann_name, field_info), FieldInfo_ + ): + # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo + setattr(cls, ann_name, field_info.default) + + if typevars_map: + for field in fields.values(): + # We don't pass any ns, as `field.annotation` + # was already evaluated. TODO: is this method relevant? + # Can't we juste use `_generics.replace_types`? + field.apply_typevars_map(typevars_map) + + if config_wrapper.use_attribute_docstrings: + _update_fields_from_docstrings( + cls, + fields, + # We can't rely on the (more reliable) frame inspection method + # for stdlib dataclasses: + use_inspect=not hasattr(cls, '__is_pydantic_dataclass__'), + ) + + return fields + + +def rebuild_dataclass_fields( + cls: type[PydanticDataclass], + *, + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver, + typevars_map: Mapping[TypeVar, Any], +) -> dict[str, FieldInfo]: + """Rebuild the (already present) dataclass fields by trying to reevaluate annotations. + + This function should be called whenever a dataclass with incomplete fields is encountered. + + Raises: + NameError: If one of the annotations failed to evaluate. + + Note: + This function *doesn't* mutate the dataclass fields in place, as it can be called during + schema generation, where you don't want to mutate other dataclass's fields. + """ + FieldInfo_ = import_cached_field_info() + + rebuilt_fields: dict[str, FieldInfo] = {} + with ns_resolver.push(cls): + for f_name, field_info in cls.__pydantic_fields__.items(): + if field_info._complete: + rebuilt_fields[f_name] = field_info + else: + existing_desc = field_info.description + ann = _typing_extra.eval_type( + field_info._original_annotation, + *ns_resolver.types_namespace, + ) + ann = _generics.replace_types(ann, typevars_map) + new_field = FieldInfo_.from_annotated_attribute( + ann, + field_info._original_assignment, + _source=AnnotationSource.DATACLASS, + ) + + # The description might come from the docstring if `use_attribute_docstrings` was `True`: + new_field.description = new_field.description if new_field.description is not None else existing_desc + update_field_from_config(config_wrapper, f_name, new_field) + rebuilt_fields[f_name] = new_field + + return rebuilt_fields + + +def is_valid_field_name(name: str) -> bool: + return not name.startswith('_') + + +def is_valid_privateattr_name(name: str) -> bool: + return name.startswith('_') and not name.startswith('__') + + +def takes_validated_data_argument( + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any], +) -> TypeIs[Callable[[dict[str, Any]], Any]]: + """Whether the provided default factory callable has a validated data parameter.""" + try: + sig = _typing_extra.signature_no_eval(default_factory) + except (ValueError, TypeError): + # `inspect.signature` might not be able to infer a signature, e.g. with C objects. + # In this case, we assume no data argument is present: + return False + + parameters = list(sig.parameters.values()) + + return len(parameters) == 1 and can_be_positional(parameters[0]) and parameters[0].default is Parameter.empty + + +def resolve_default_value( + default: Any, + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None, + *, + validated_data: dict[str, Any] | None = None, + call_default_factory: bool = False, +) -> Any: + """Resolve the default value using either a static default or a default_factory.""" + from ._utils import smart_deepcopy + + if default_factory is None: + return smart_deepcopy(default) + if call_default_factory: + if takes_validated_data_argument(default_factory=default_factory): + fac = cast('Callable[[dict[str, Any]], Any]', default_factory) + if validated_data is None: + raise ValueError( + "The default factory requires the 'validated_data' argument, which was not provided when calling 'get_default()'." + ) + return fac(validated_data) + else: + fac = cast('Callable[[], Any]', default_factory) + return fac() + + return PydanticUndefined diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py new file mode 100644 index 0000000..231f81d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py @@ -0,0 +1,23 @@ +from __future__ import annotations as _annotations + +from dataclasses import dataclass +from typing import Union + + +@dataclass +class PydanticRecursiveRef: + type_ref: str + + __name__ = 'PydanticRecursiveRef' + __hash__ = object.__hash__ + + def __call__(self) -> None: + """Defining __call__ is necessary for the `typing` module to let you use an instance of + this class as the result of resolving a standard ForwardRef. + """ + + def __or__(self, other): + return Union[self, other] # type: ignore + + def __ror__(self, other): + return Union[other, self] # type: ignore diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py new file mode 100644 index 0000000..98fae40 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py @@ -0,0 +1,2934 @@ +"""Convert python types to pydantic-core schema.""" + +from __future__ import annotations as _annotations + +import collections.abc +import dataclasses +import datetime +import inspect +import os +import pathlib +import re +import sys +import typing +import warnings +from collections.abc import Generator, Iterable, Iterator, Mapping +from contextlib import contextmanager +from copy import copy +from decimal import Decimal +from enum import Enum +from fractions import Fraction +from functools import partial +from inspect import Parameter, _ParameterKind +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from itertools import chain +from operator import attrgetter +from types import FunctionType, GenericAlias, LambdaType, MethodType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Final, + ForwardRef, + Literal, + TypeVar, + Union, + cast, + overload, +) +from uuid import UUID +from zoneinfo import ZoneInfo + +import typing_extensions +from pydantic_core import ( + MISSING, + CoreSchema, + MultiHostUrl, + PydanticCustomError, + PydanticSerializationUnexpectedValue, + PydanticUndefined, + Url, + core_schema, + to_jsonable_python, +) +from typing_extensions import TypeAlias, TypeAliasType, get_args, get_origin, is_typeddict +from typing_inspection import typing_objects +from typing_inspection.introspection import AnnotationSource, get_literal_values, is_union_origin + +from ..aliases import AliasChoices, AliasPath +from ..annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler +from ..config import ConfigDict, JsonDict, JsonEncoder, JsonSchemaExtraCallable +from ..errors import ( + PydanticForbiddenQualifier, + PydanticInvalidForJsonSchema, + PydanticSchemaGenerationError, + PydanticUndefinedAnnotation, + PydanticUserError, +) +from ..functional_validators import AfterValidator, BeforeValidator, FieldValidatorModes, PlainValidator, WrapValidator +from ..json_schema import JsonSchemaValue +from ..version import version_short +from ..warnings import ( + ArbitraryTypeWarning, + PydanticDeprecatedSince20, + TypedDictExtraConfigWarning, + UnsupportedFieldAttributeWarning, +) +from . import _decorators, _discriminated_union, _known_annotated_metadata, _repr, _typing_extra +from ._config import ConfigWrapper, ConfigWrapperStack +from ._core_metadata import CoreMetadata, update_core_metadata +from ._core_utils import ( + get_ref, + get_type_ref, + is_list_like_schema_with_items_schema, +) +from ._decorators import ( + Decorator, + DecoratorInfos, + FieldSerializerDecoratorInfo, + FieldValidatorDecoratorInfo, + ModelSerializerDecoratorInfo, + ModelValidatorDecoratorInfo, + RootValidatorDecoratorInfo, + ValidatorDecoratorInfo, + get_attribute_from_bases, + inspect_field_serializer, + inspect_model_serializer, + inspect_validator, +) +from ._docs_extraction import extract_docstrings_from_cls +from ._fields import ( + collect_dataclass_fields, + rebuild_dataclass_fields, + rebuild_model_fields, + takes_validated_data_argument, + update_field_from_config, +) +from ._forward_ref import PydanticRecursiveRef +from ._generics import get_standard_typevars_map, replace_types +from ._import_utils import import_cached_base_model, import_cached_field_info +from ._mock_val_ser import MockCoreSchema +from ._namespace_utils import NamespacesTuple, NsResolver +from ._schema_gather import MissingDefinitionError, gather_schemas_for_cleaning +from ._schema_generation_shared import CallbackGetCoreSchemaHandler +from ._utils import lenient_issubclass, smart_deepcopy + +if TYPE_CHECKING: + from ..fields import ComputedFieldInfo, FieldInfo + from ..main import BaseModel + from ..types import Discriminator + from ._dataclasses import StandardDataclass + from ._schema_generation_shared import GetJsonSchemaFunction + +_SUPPORTS_TYPEDDICT = sys.version_info >= (3, 12) + +FieldDecoratorInfo = Union[ValidatorDecoratorInfo, FieldValidatorDecoratorInfo, FieldSerializerDecoratorInfo] +FieldDecoratorInfoType = TypeVar('FieldDecoratorInfoType', bound=FieldDecoratorInfo) +AnyFieldDecorator = Union[ + Decorator[ValidatorDecoratorInfo], + Decorator[FieldValidatorDecoratorInfo], + Decorator[FieldSerializerDecoratorInfo], +] + +ModifyCoreSchemaWrapHandler: TypeAlias = GetCoreSchemaHandler +GetCoreSchemaFunction: TypeAlias = Callable[[Any, ModifyCoreSchemaWrapHandler], core_schema.CoreSchema] +ParametersCallback: TypeAlias = "Callable[[int, str, Any], Literal['skip'] | None]" + +TUPLE_TYPES: list[type] = [typing.Tuple, tuple] # noqa: UP006 +LIST_TYPES: list[type] = [typing.List, list, collections.abc.MutableSequence] # noqa: UP006 +SET_TYPES: list[type] = [typing.Set, set, collections.abc.MutableSet] # noqa: UP006 +FROZEN_SET_TYPES: list[type] = [typing.FrozenSet, frozenset, collections.abc.Set] # noqa: UP006 +DICT_TYPES: list[type] = [typing.Dict, dict] # noqa: UP006 +IP_TYPES: list[type] = [IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network] +SEQUENCE_TYPES: list[type] = [typing.Sequence, collections.abc.Sequence] +ITERABLE_TYPES: list[type] = [typing.Iterable, collections.abc.Iterable, typing.Generator, collections.abc.Generator] +TYPE_TYPES: list[type] = [typing.Type, type] # noqa: UP006 +PATTERN_TYPES: list[type] = [typing.Pattern, re.Pattern] +PATH_TYPES: list[type] = [ + os.PathLike, + pathlib.Path, + pathlib.PurePath, + pathlib.PosixPath, + pathlib.PurePosixPath, + pathlib.PureWindowsPath, +] +MAPPING_TYPES = [ + typing.Mapping, + typing.MutableMapping, + collections.abc.Mapping, + collections.abc.MutableMapping, + collections.OrderedDict, + typing_extensions.OrderedDict, + typing.DefaultDict, # noqa: UP006 + collections.defaultdict, +] +COUNTER_TYPES = [collections.Counter, typing.Counter] +DEQUE_TYPES: list[type] = [collections.deque, typing.Deque] # noqa: UP006 + +# Note: This does not play very well with type checkers. For example, +# `a: LambdaType = lambda x: x` will raise a type error by Pyright. +ValidateCallSupportedTypes = Union[ + LambdaType, + FunctionType, + MethodType, + partial, +] + +VALIDATE_CALL_SUPPORTED_TYPES = get_args(ValidateCallSupportedTypes) +UNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES: list[tuple[str, Any]] = [ + ('alias', None), + ('validation_alias', None), + ('serialization_alias', None), + # will be set if any alias is set, so disable it to avoid double warnings: + # 'alias_priority', + ('default', PydanticUndefined), + ('default_factory', None), + ('exclude', None), + ('deprecated', None), + ('repr', True), + ('validate_default', None), + ('frozen', None), + ('init', None), + ('init_var', None), + ('kw_only', None), +] +"""`FieldInfo` attributes (and their default value) that can't be used outside of a model (e.g. in a type adapter or a PEP 695 type alias).""" + +_mode_to_validator: dict[ + FieldValidatorModes, type[BeforeValidator | AfterValidator | PlainValidator | WrapValidator] +] = {'before': BeforeValidator, 'after': AfterValidator, 'plain': PlainValidator, 'wrap': WrapValidator} + + +def check_validator_fields_against_field_name( + info: FieldDecoratorInfo, + field: str, +) -> bool: + """Check if field name is in validator fields. + + Args: + info: The field info. + field: The field name to check. + + Returns: + `True` if field name is in validator fields, `False` otherwise. + """ + fields = info.fields + return '*' in fields or field in fields + + +def check_decorator_fields_exist(decorators: Iterable[AnyFieldDecorator], fields: Iterable[str]) -> None: + """Check if the defined fields in decorators exist in `fields` param. + + It ignores the check for a decorator if the decorator has `*` as field or `check_fields=False`. + + Args: + decorators: An iterable of decorators. + fields: An iterable of fields name. + + Raises: + PydanticUserError: If one of the field names does not exist in `fields` param. + """ + fields = set(fields) + for dec in decorators: + if '*' in dec.info.fields: + continue + if dec.info.check_fields is False: + continue + for field in dec.info.fields: + if field not in fields: + raise PydanticUserError( + f'Decorators defined with incorrect fields: {dec.cls_ref}.{dec.cls_var_name}' + " (use check_fields=False if you're inheriting from the model and intended this)", + code='decorator-missing-field', + ) + + +def filter_field_decorator_info_by_field( + validator_functions: Iterable[Decorator[FieldDecoratorInfoType]], field: str +) -> list[Decorator[FieldDecoratorInfoType]]: + return [dec for dec in validator_functions if check_validator_fields_against_field_name(dec.info, field)] + + +def apply_each_item_validators( + schema: core_schema.CoreSchema, + each_item_validators: list[Decorator[ValidatorDecoratorInfo]], +) -> core_schema.CoreSchema: + # This V1 compatibility shim should eventually be removed + + # fail early if each_item_validators is empty + if not each_item_validators: + return schema + + # push down any `each_item=True` validators + # note that this won't work for any Annotated types that get wrapped by a function validator + # but that's okay because that didn't exist in V1 + if schema['type'] == 'nullable': + schema['schema'] = apply_each_item_validators(schema['schema'], each_item_validators) + return schema + elif schema['type'] == 'tuple': + if (variadic_item_index := schema.get('variadic_item_index')) is not None: + schema['items_schema'][variadic_item_index] = apply_validators( + schema['items_schema'][variadic_item_index], + each_item_validators, + ) + elif is_list_like_schema_with_items_schema(schema): + inner_schema = schema.get('items_schema', core_schema.any_schema()) + schema['items_schema'] = apply_validators(inner_schema, each_item_validators) + elif schema['type'] == 'dict': + inner_schema = schema.get('values_schema', core_schema.any_schema()) + schema['values_schema'] = apply_validators(inner_schema, each_item_validators) + else: + raise TypeError( + f'`@validator(..., each_item=True)` cannot be applied to fields with a schema of {schema["type"]}' + ) + return schema + + +def _extract_json_schema_info_from_field_info( + info: FieldInfo | ComputedFieldInfo, +) -> tuple[JsonDict | None, JsonDict | JsonSchemaExtraCallable | None]: + json_schema_updates = { + 'title': info.title, + 'description': info.description, + 'deprecated': bool(info.deprecated) or info.deprecated == '' or None, + 'examples': to_jsonable_python(info.examples), + } + json_schema_updates = {k: v for k, v in json_schema_updates.items() if v is not None} + return (json_schema_updates or None, info.json_schema_extra) + + +JsonEncoders = dict[type[Any], JsonEncoder] + + +def _add_custom_serialization_from_json_encoders( + json_encoders: JsonEncoders | None, tp: Any, schema: CoreSchema +) -> CoreSchema: + """Iterate over the json_encoders and add the first matching encoder to the schema. + + Args: + json_encoders: A dictionary of types and their encoder functions. + tp: The type to check for a matching encoder. + schema: The schema to add the encoder to. + """ + if not json_encoders: + return schema + if 'serialization' in schema: + return schema + # Check the class type and its superclasses for a matching encoder + # Decimal.__class__.__mro__ (and probably other cases) doesn't include Decimal itself + # if the type is a GenericAlias (e.g. from list[int]) we need to use __class__ instead of .__mro__ + for base in (tp, *getattr(tp, '__mro__', tp.__class__.__mro__)[:-1]): + encoder = json_encoders.get(base) + if encoder is None: + continue + + warnings.warn( + f'`json_encoders` is deprecated. See https://docs.pydantic.dev/{version_short()}/concepts/serialization/#custom-serializers for alternatives', + PydanticDeprecatedSince20, + ) + + # TODO: in theory we should check that the schema accepts a serialization key + schema['serialization'] = core_schema.plain_serializer_function_ser_schema(encoder, when_used='json') + return schema + + return schema + + +GENERATE_SCHEMA_ERRORS = ( + PydanticForbiddenQualifier, + PydanticInvalidForJsonSchema, + PydanticSchemaGenerationError, + PydanticUndefinedAnnotation, +) +"""Errors raised during core schema generation. This does *not* include `InvalidSchemaError`, which is raised during schema cleaning.""" + + +class InvalidSchemaError(Exception): + """The core schema is invalid.""" + + +class GenerateSchema: + """Generate core schema for a Pydantic model, dataclass and types like `str`, `datetime`, ... .""" + + __slots__ = ( + '_config_wrapper_stack', + '_ns_resolver', + '_typevars_map', + 'field_name_stack', + 'model_type_stack', + 'defs', + ) + + def __init__( + self, + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver | None = None, + typevars_map: Mapping[TypeVar, Any] | None = None, + ) -> None: + # we need a stack for recursing into nested models + self._config_wrapper_stack = ConfigWrapperStack(config_wrapper) + self._ns_resolver = ns_resolver or NsResolver() + self._typevars_map = typevars_map + self.field_name_stack = _FieldNameStack() + self.model_type_stack = _ModelTypeStack() + self.defs = _Definitions() + + def __init_subclass__(cls) -> None: + super().__init_subclass__() + warnings.warn( + 'Subclassing `GenerateSchema` is not supported. The API is highly subject to change in minor versions.', + UserWarning, + stacklevel=2, + ) + + @property + def _config_wrapper(self) -> ConfigWrapper: + return self._config_wrapper_stack.tail + + @property + def _types_namespace(self) -> NamespacesTuple: + return self._ns_resolver.types_namespace + + @property + def _arbitrary_types(self) -> bool: + return self._config_wrapper.arbitrary_types_allowed + + # the following methods can be overridden but should be considered + # unstable / private APIs + def _list_schema(self, items_type: Any) -> CoreSchema: + return core_schema.list_schema(self.generate_schema(items_type)) + + def _dict_schema(self, keys_type: Any, values_type: Any) -> CoreSchema: + return core_schema.dict_schema(self.generate_schema(keys_type), self.generate_schema(values_type)) + + def _set_schema(self, items_type: Any) -> CoreSchema: + return core_schema.set_schema(self.generate_schema(items_type)) + + def _frozenset_schema(self, items_type: Any) -> CoreSchema: + return core_schema.frozenset_schema(self.generate_schema(items_type)) + + def _enum_schema(self, enum_type: type[Enum]) -> CoreSchema: + cases: list[Any] = list(enum_type.__members__.values()) + + enum_ref = get_type_ref(enum_type) + description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) + if ( + description == 'An enumeration.' + ): # This is the default value provided by enum.EnumMeta.__new__; don't use it + description = None + js_updates = {'title': enum_type.__name__, 'description': description} + js_updates = {k: v for k, v in js_updates.items() if v is not None} + + sub_type: Literal['str', 'int', 'float'] | None = None + if issubclass(enum_type, int): + sub_type = 'int' + value_ser_type: core_schema.SerSchema = core_schema.simple_ser_schema('int') + elif issubclass(enum_type, str): + # this handles `StrEnum` (3.11 only), and also `Foobar(str, Enum)` + sub_type = 'str' + value_ser_type = core_schema.simple_ser_schema('str') + elif issubclass(enum_type, float): + sub_type = 'float' + value_ser_type = core_schema.simple_ser_schema('float') + else: + # TODO this is an ugly hack, how do we trigger an Any schema for serialization? + value_ser_type = core_schema.plain_serializer_function_ser_schema(lambda x: x) + + if cases: + + def get_json_schema(schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + json_schema = handler(schema) + original_schema = handler.resolve_ref_schema(json_schema) + original_schema.update(js_updates) + return json_schema + + # we don't want to add the missing to the schema if it's the default one + default_missing = getattr(enum_type._missing_, '__func__', None) is Enum._missing_.__func__ # pyright: ignore[reportFunctionMemberAccess] + enum_schema = core_schema.enum_schema( + enum_type, + cases, + sub_type=sub_type, + missing=None if default_missing else enum_type._missing_, + ref=enum_ref, + metadata={'pydantic_js_functions': [get_json_schema]}, + ) + + if self._config_wrapper.use_enum_values: + enum_schema = core_schema.no_info_after_validator_function( + attrgetter('value'), enum_schema, serialization=value_ser_type + ) + + return enum_schema + + else: + + def get_json_schema_no_cases(_, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + json_schema = handler(core_schema.enum_schema(enum_type, cases, sub_type=sub_type, ref=enum_ref)) + original_schema = handler.resolve_ref_schema(json_schema) + original_schema.update(js_updates) + return json_schema + + # Use an isinstance check for enums with no cases. + # The most important use case for this is creating TypeVar bounds for generics that should + # be restricted to enums. This is more consistent than it might seem at first, since you can only + # subclass enum.Enum (or subclasses of enum.Enum) if all parent classes have no cases. + # We use the get_json_schema function when an Enum subclass has been declared with no cases + # so that we can still generate a valid json schema. + return core_schema.is_instance_schema( + enum_type, + metadata={'pydantic_js_functions': [get_json_schema_no_cases]}, + ) + + def _ip_schema(self, tp: Any) -> CoreSchema: + from ._validators import IP_VALIDATOR_LOOKUP, IpType + + ip_type_json_schema_format: dict[type[IpType], str] = { + IPv4Address: 'ipv4', + IPv4Network: 'ipv4network', + IPv4Interface: 'ipv4interface', + IPv6Address: 'ipv6', + IPv6Network: 'ipv6network', + IPv6Interface: 'ipv6interface', + } + + def ser_ip(ip: Any, info: core_schema.SerializationInfo) -> str | IpType: + if not isinstance(ip, (tp, str)): + raise PydanticSerializationUnexpectedValue( + f"Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected." + ) + if info.mode == 'python': + return ip + return str(ip) + + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(IP_VALIDATOR_LOOKUP[tp]), + strict_schema=core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(tp, core_schema.str_schema()), + python_schema=core_schema.is_instance_schema(tp), + ), + serialization=core_schema.plain_serializer_function_ser_schema(ser_ip, info_arg=True, when_used='always'), + metadata={ + 'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': ip_type_json_schema_format[tp]}] + }, + ) + + def _path_schema(self, tp: Any, path_type: Any) -> CoreSchema: + if tp is os.PathLike and (path_type not in {str, bytes} and not typing_objects.is_any(path_type)): + raise PydanticUserError( + '`os.PathLike` can only be used with `str`, `bytes` or `Any`', code='schema-for-unknown-type' + ) + + path_constructor = pathlib.PurePath if tp is os.PathLike else tp + strict_inner_schema = ( + core_schema.bytes_schema(strict=True) if (path_type is bytes) else core_schema.str_schema(strict=True) + ) + lax_inner_schema = core_schema.bytes_schema() if (path_type is bytes) else core_schema.str_schema() + + def path_validator(input_value: str | bytes) -> os.PathLike[Any]: # type: ignore + try: + if path_type is bytes: + if isinstance(input_value, bytes): + try: + input_value = input_value.decode() + except UnicodeDecodeError as e: + raise PydanticCustomError('bytes_type', 'Input must be valid bytes') from e + else: + raise PydanticCustomError('bytes_type', 'Input must be bytes') + elif not isinstance(input_value, str): + raise PydanticCustomError('path_type', 'Input is not a valid path') + + return path_constructor(input_value) # type: ignore + except TypeError as e: + raise PydanticCustomError('path_type', 'Input is not a valid path') from e + + def ser_path(path: Any, info: core_schema.SerializationInfo) -> str | os.PathLike[Any]: + if not isinstance(path, (tp, str)): + raise PydanticSerializationUnexpectedValue( + f"Expected `{tp}` but got `{type(path)}` with value `'{path}'` - serialized value may not be as expected." + ) + if info.mode == 'python': + return path + return str(path) + + instance_schema = core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(path_validator, lax_inner_schema), + python_schema=core_schema.is_instance_schema(tp), + ) + + schema = core_schema.lax_or_strict_schema( + lax_schema=core_schema.union_schema( + [ + instance_schema, + core_schema.no_info_after_validator_function(path_validator, strict_inner_schema), + ], + custom_error_type='path_type', + custom_error_message=f'Input is not a valid path for {tp}', + ), + strict_schema=instance_schema, + serialization=core_schema.plain_serializer_function_ser_schema(ser_path, info_arg=True, when_used='always'), + metadata={'pydantic_js_functions': [lambda source, handler: {**handler(source), 'format': 'path'}]}, + ) + return schema + + def _deque_schema(self, items_type: Any) -> CoreSchema: + from ._serializers import serialize_sequence_via_list + from ._validators import deque_validator + + item_type_schema = self.generate_schema(items_type) + + # we have to use a lax list schema here, because we need to validate the deque's + # items via a list schema, but it's ok if the deque itself is not a list + list_schema = core_schema.list_schema(item_type_schema, strict=False) + + check_instance = core_schema.json_or_python_schema( + json_schema=list_schema, + python_schema=core_schema.is_instance_schema(collections.deque, cls_repr='Deque'), + ) + + lax_schema = core_schema.no_info_wrap_validator_function(deque_validator, list_schema) + + return core_schema.lax_or_strict_schema( + lax_schema=lax_schema, + strict_schema=core_schema.chain_schema([check_instance, lax_schema]), + serialization=core_schema.wrap_serializer_function_ser_schema( + serialize_sequence_via_list, schema=item_type_schema, info_arg=True + ), + ) + + def _mapping_schema(self, tp: Any, keys_type: Any, values_type: Any) -> CoreSchema: + from ._validators import MAPPING_ORIGIN_MAP, defaultdict_validator, get_defaultdict_default_default_factory + + mapped_origin = MAPPING_ORIGIN_MAP[tp] + keys_schema = self.generate_schema(keys_type) + with warnings.catch_warnings(): + # We kind of abused `Field()` default factories to be able to specify + # the `defaultdict`'s `default_factory`. As a consequence, we get warnings + # as normally `FieldInfo.default_factory` is unsupported in the context where + # `Field()` is used and our only solution is to ignore them (note that this might + # wrongfully ignore valid warnings, e.g. if the `value_type` is a PEP 695 type alias + # with unsupported metadata). + warnings.simplefilter('ignore', category=UnsupportedFieldAttributeWarning) + values_schema = self.generate_schema(values_type) + dict_schema = core_schema.dict_schema(keys_schema, values_schema, strict=False) + + if mapped_origin is dict: + schema = dict_schema + else: + check_instance = core_schema.json_or_python_schema( + json_schema=dict_schema, + python_schema=core_schema.is_instance_schema(mapped_origin), + ) + + if tp is collections.defaultdict: + default_default_factory = get_defaultdict_default_default_factory(values_type) + coerce_instance_wrap = partial( + core_schema.no_info_wrap_validator_function, + partial(defaultdict_validator, default_default_factory=default_default_factory), + ) + else: + coerce_instance_wrap = partial(core_schema.no_info_after_validator_function, mapped_origin) + + lax_schema = coerce_instance_wrap(dict_schema) + strict_schema = core_schema.chain_schema([check_instance, lax_schema]) + + schema = core_schema.lax_or_strict_schema( + lax_schema=lax_schema, + strict_schema=strict_schema, + serialization=core_schema.wrap_serializer_function_ser_schema( + lambda v, h: h(v), schema=dict_schema, info_arg=False + ), + ) + + return schema + + def _fraction_schema(self) -> CoreSchema: + """Support for [`fractions.Fraction`][fractions.Fraction].""" + from ._validators import fraction_validator + + # TODO: note, this is a fairly common pattern, re lax / strict for attempted type coercion, + # can we use a helper function to reduce boilerplate? + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(fraction_validator), + strict_schema=core_schema.json_or_python_schema( + json_schema=core_schema.no_info_plain_validator_function(fraction_validator), + python_schema=core_schema.is_instance_schema(Fraction), + ), + # use str serialization to guarantee round trip behavior + serialization=core_schema.to_string_ser_schema(when_used='always'), + metadata={'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'fraction'}]}, + ) + + def _arbitrary_type_schema(self, tp: Any) -> CoreSchema: + if not isinstance(tp, type): + warnings.warn( + f'{tp!r} is not a Python type (it may be an instance of an object),' + ' Pydantic will allow any object with no validation since we cannot even' + ' enforce that the input is an instance of the given type.' + ' To get rid of this error wrap the type with `pydantic.SkipValidation`.', + ArbitraryTypeWarning, + ) + return core_schema.any_schema() + return core_schema.is_instance_schema(tp) + + def _unknown_type_schema(self, obj: Any) -> CoreSchema: + raise PydanticSchemaGenerationError( + f'Unable to generate pydantic-core schema for {obj!r}. ' + 'Set `arbitrary_types_allowed=True` in the model_config to ignore this error' + ' or implement `__get_pydantic_core_schema__` on your type to fully support it.' + '\n\nIf you got this error by calling handler() within' + ' `__get_pydantic_core_schema__` then you likely need to call' + ' `handler.generate_schema()` since we do not call' + ' `__get_pydantic_core_schema__` on `` otherwise to avoid infinite recursion.' + ) + + def _apply_discriminator_to_union( + self, schema: CoreSchema, discriminator: str | Discriminator | None + ) -> CoreSchema: + if discriminator is None: + return schema + try: + return _discriminated_union.apply_discriminator( + schema, + discriminator, + self.defs._definitions, + ) + except _discriminated_union.MissingDefinitionForUnionRef: + # defer until defs are resolved + _discriminated_union.set_discriminator_in_metadata( + schema, + discriminator, + ) + return schema + + def clean_schema(self, schema: CoreSchema) -> CoreSchema: + return self.defs.finalize_schema(schema) + + def _add_js_function(self, metadata_schema: CoreSchema, js_function: Callable[..., Any]) -> None: + metadata = metadata_schema.get('metadata', {}) + pydantic_js_functions = metadata.setdefault('pydantic_js_functions', []) + # because of how we generate core schemas for nested generic models + # we can end up adding `BaseModel.__get_pydantic_json_schema__` multiple times + # this check may fail to catch duplicates if the function is a `functools.partial` + # or something like that, but if it does it'll fail by inserting the duplicate + if js_function not in pydantic_js_functions: + pydantic_js_functions.append(js_function) + metadata_schema['metadata'] = metadata + + def generate_schema( + self, + obj: Any, + ) -> core_schema.CoreSchema: + """Generate core schema. + + Args: + obj: The object to generate core schema for. + + Returns: + The generated core schema. + + Raises: + PydanticUndefinedAnnotation: + If it is not possible to evaluate forward reference. + PydanticSchemaGenerationError: + If it is not possible to generate pydantic-core schema. + TypeError: + - If `alias_generator` returns a disallowed type (must be str, AliasPath or AliasChoices). + - If V1 style validator with `each_item=True` applied on a wrong field. + PydanticUserError: + - If `typing.TypedDict` is used instead of `typing_extensions.TypedDict` on Python < 3.12. + - If `__modify_schema__` method is used instead of `__get_pydantic_json_schema__`. + """ + schema = self._generate_schema_from_get_schema_method(obj, obj) + + if schema is None: + schema = self._generate_schema_inner(obj) + + metadata_js_function = _extract_get_pydantic_json_schema(obj) + if metadata_js_function is not None: + metadata_schema = resolve_original_schema(schema, self.defs) + if metadata_schema: + self._add_js_function(metadata_schema, metadata_js_function) + + schema = _add_custom_serialization_from_json_encoders(self._config_wrapper.json_encoders, obj, schema) + + return schema + + def _model_schema(self, cls: type[BaseModel]) -> core_schema.CoreSchema: + """Generate schema for a Pydantic model.""" + BaseModel_ = import_cached_base_model() + + with self.defs.get_schema_or_ref(cls) as (model_ref, maybe_schema): + if maybe_schema is not None: + return maybe_schema + + schema = cls.__dict__.get('__pydantic_core_schema__') + if schema is not None and not isinstance(schema, MockCoreSchema): + if schema['type'] == 'definitions': + schema = self.defs.unpack_definitions(schema) + ref = get_ref(schema) + if ref: + return self.defs.create_definition_reference_schema(schema) + else: + return schema + + config_wrapper = ConfigWrapper(cls.model_config, check=False) + + with self._config_wrapper_stack.push(config_wrapper), self._ns_resolver.push(cls): + core_config = self._config_wrapper.core_config(title=cls.__name__) + + if cls.__pydantic_fields_complete__ or cls is BaseModel_: + fields = getattr(cls, '__pydantic_fields__', {}) + extra_info = getattr(cls, '__pydantic_extra_info__', None) + else: + if '__pydantic_fields__' not in cls.__dict__: + # This happens when we have a loop in the schema generation: + # class Base[T](BaseModel): + # t: T + # + # class Other(BaseModel): + # b: 'Base[Other]' + # When we build fields for `Other`, we evaluate the forward annotation. + # At this point, `Other` doesn't have the model fields set. We create + # `Base[Other]`; model fields are successfully built, and we try to generate + # a schema for `t: Other`. As `Other.__pydantic_fields__` aren't set, we abort. + raise PydanticUndefinedAnnotation( + name=cls.__name__, + message=f'Class {cls.__name__!r} is not defined', + ) + try: + fields, extra_info = rebuild_model_fields( + cls, + config_wrapper=self._config_wrapper, + ns_resolver=self._ns_resolver, + typevars_map=self._typevars_map or {}, + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + decorators = cls.__pydantic_decorators__ + computed_fields = decorators.computed_fields + check_decorator_fields_exist( + chain( + decorators.field_validators.values(), + decorators.field_serializers.values(), + decorators.validators.values(), + ), + {*fields.keys(), *computed_fields.keys()}, + ) + + model_validators = decorators.model_validators.values() + + extras_schema = None + extras_keys_schema = None + if core_config.get('extra_fields_behavior') == 'allow' and extra_info is not None: + tp = get_origin(extra_info.annotation) + if tp not in DICT_TYPES: + raise PydanticSchemaGenerationError( + 'The type annotation for `__pydantic_extra__` must be `dict[str, ...]`' + ) + # See the comments in `_get_args_resolving_forward_refs()` for why we need + # to re-evaluate the annotation: + extra_keys_type, extra_items_type = self._get_args_resolving_forward_refs( + extra_info.annotation, + required=True, + ) + if extra_keys_type is not str: + extras_keys_schema = self.generate_schema(extra_keys_type) + if not typing_objects.is_any(extra_items_type): + extras_schema = self.generate_schema(extra_items_type) + + generic_origin: type[BaseModel] | None = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') + + if cls.__pydantic_root_model__: + inner_schema, metadata = self._common_field_schema('root', fields['root'], decorators) + if cls.__doc__ and metadata.get('pydantic_js_updates', {}).get('description'): + # This is a bit of a leaky abstraction, but as the model docstring takes priority + # over the root field's description, we need to override it here. This can't be done + # in the JSON Schema generation logic because the metadata's `pydantic_js_updates` are + # applied last, and overrides any value previously set (so the description set from the + # docstring in `GenerateJsonSchema._update_class_schema()` is overridden): + update_core_metadata( + metadata, pydantic_js_updates={'description': inspect.cleandoc(cls.__doc__)} + ) + + inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') + model_schema = core_schema.model_schema( + cls, + inner_schema, + generic_origin=generic_origin, + custom_init=getattr(cls, '__pydantic_custom_init__', None), + root_model=True, + post_init=getattr(cls, '__pydantic_post_init__', None), + config=core_config, + ref=model_ref, + metadata=metadata, + ) + else: + fields_schema: core_schema.CoreSchema = core_schema.model_fields_schema( + {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()}, + computed_fields=[ + self._computed_field_schema(d, decorators.field_serializers) + for d in computed_fields.values() + ], + extras_schema=extras_schema, + extras_keys_schema=extras_keys_schema, + model_name=cls.__name__, + ) + inner_schema = apply_validators(fields_schema, decorators.root_validators.values()) + inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') + + model_schema = core_schema.model_schema( + cls, + inner_schema, + generic_origin=generic_origin, + custom_init=getattr(cls, '__pydantic_custom_init__', None), + root_model=False, + post_init=getattr(cls, '__pydantic_post_init__', None), + config=core_config, + ref=model_ref, + ) + + schema = self._apply_model_serializers(model_schema, decorators.model_serializers.values()) + schema = apply_model_validators(schema, model_validators, 'outer') + return self.defs.create_definition_reference_schema(schema) + + def _resolve_self_type(self, obj: Any) -> Any: + obj = self.model_type_stack.get() + if obj is None: + raise PydanticUserError('`typing.Self` is invalid in this context', code='invalid-self-type') + return obj + + def _generate_schema_from_get_schema_method(self, obj: Any, source: Any) -> core_schema.CoreSchema | None: + BaseModel_ = import_cached_base_model() + + get_schema = getattr(obj, '__get_pydantic_core_schema__', None) + is_base_model_get_schema = ( + getattr(get_schema, '__func__', None) is BaseModel_.__get_pydantic_core_schema__.__func__ # pyright: ignore[reportFunctionMemberAccess] + ) + + if ( + get_schema is not None + # BaseModel.__get_pydantic_core_schema__ is defined for backwards compatibility, + # to allow existing code to call `super().__get_pydantic_core_schema__` in Pydantic + # model that overrides `__get_pydantic_core_schema__`. However, it raises a deprecation + # warning stating that the method will be removed, and during the core schema gen we actually + # don't call the method: + and not is_base_model_get_schema + ): + # Some referenceable types might have a `__get_pydantic_core_schema__` method + # defined on it by users (e.g. on a dataclass). This generally doesn't play well + # as these types are already recognized by the `GenerateSchema` class and isn't ideal + # as we might end up calling `get_schema_or_ref` (expensive) on types that are actually + # not referenceable: + with self.defs.get_schema_or_ref(obj) as (_, maybe_schema): + if maybe_schema is not None: + return maybe_schema + + if obj is source: + ref_mode = 'unpack' + else: + ref_mode = 'to-def' + schema = get_schema( + source, CallbackGetCoreSchemaHandler(self._generate_schema_inner, self, ref_mode=ref_mode) + ) + if schema['type'] == 'definitions': + schema = self.defs.unpack_definitions(schema) + + ref = get_ref(schema) + if ref: + return self.defs.create_definition_reference_schema(schema) + + # Note: if schema is of type `'definition-ref'`, we might want to copy it as a + # safety measure (because these are inlined in place -- i.e. mutated directly) + return schema + + if get_schema is None and (validators := getattr(obj, '__get_validators__', None)) is not None: + from pydantic.v1 import BaseModel as BaseModelV1 + + if issubclass(obj, BaseModelV1): + warnings.warn( + f'Mixing V1 models and V2 models (or constructs, like `TypeAdapter`) is not supported. Please upgrade `{obj.__name__}` to V2.', + UserWarning, + ) + else: + warnings.warn( + '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', + PydanticDeprecatedSince20, + ) + return core_schema.chain_schema([core_schema.with_info_plain_validator_function(v) for v in validators()]) + + def _resolve_forward_ref(self, obj: Any) -> Any: + # we assume that types_namespace has the target of forward references in its scope, + # but this could fail, for example, if calling Validator on an imported type which contains + # forward references to other types only defined in the module from which it was imported + # `Validator(SomeImportedTypeAliasWithAForwardReference)` + # or the equivalent for BaseModel + # class Model(BaseModel): + # x: SomeImportedTypeAliasWithAForwardReference + try: + obj = _typing_extra.eval_type_backport(obj, *self._types_namespace) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + # if obj is still a ForwardRef, it means we can't evaluate it, raise PydanticUndefinedAnnotation + if isinstance(obj, ForwardRef): + raise PydanticUndefinedAnnotation(obj.__forward_arg__, f'Unable to evaluate forward reference {obj}') + + if self._typevars_map: + obj = replace_types(obj, self._typevars_map) + + return obj + + @overload + def _get_args_resolving_forward_refs(self, obj: Any, required: Literal[True]) -> tuple[Any, ...]: ... + + @overload + def _get_args_resolving_forward_refs(self, obj: Any) -> tuple[Any, ...] | None: ... + + def _get_args_resolving_forward_refs(self, obj: Any, required: bool = False) -> tuple[Any, ...] | None: + args = get_args(obj) + if args: + if isinstance(obj, GenericAlias): + # PEP 585 generic aliases don't convert args to ForwardRefs, unlike `typing.List/Dict` etc. + # This was fixed in https://github.com/python/cpython/pull/30900 (Python 3.11). + # TODO: this shouldn't be necessary (probably even this `_get_args_resolving_forward_refs()` function) + # once we drop support for Python 3.10 *or* if we implement our own `typing._eval_type()` implementation. + args = (_typing_extra._make_forward_ref(a) if isinstance(a, str) else a for a in args) + args = tuple(self._resolve_forward_ref(a) if isinstance(a, ForwardRef) else a for a in args) + elif required: # pragma: no cover + raise TypeError(f'Expected {obj} to have generic parameters but it had none') + return args + + def _get_first_arg_or_any(self, obj: Any) -> Any: + args = self._get_args_resolving_forward_refs(obj) + if not args: + return Any + return args[0] + + def _get_first_two_args_or_any(self, obj: Any) -> tuple[Any, Any]: + args = self._get_args_resolving_forward_refs(obj) + if not args: + return (Any, Any) + if len(args) < 2: + origin = get_origin(obj) + raise TypeError(f'Expected two type arguments for {origin}, got 1') + return args[0], args[1] + + def _generate_schema_inner(self, obj: Any) -> core_schema.CoreSchema: + if typing_objects.is_self(obj): + obj = self._resolve_self_type(obj) + + if typing_objects.is_annotated(get_origin(obj)): + return self._annotated_schema(obj) + + if isinstance(obj, dict): + # we assume this is already a valid schema + return obj # type: ignore[return-value] + + if isinstance(obj, str): + obj = ForwardRef(obj) + + if isinstance(obj, ForwardRef): + return self.generate_schema(self._resolve_forward_ref(obj)) + + BaseModel = import_cached_base_model() + + if lenient_issubclass(obj, BaseModel): + with self.model_type_stack.push(obj): + return self._model_schema(obj) + + if isinstance(obj, PydanticRecursiveRef): + return core_schema.definition_reference_schema(schema_ref=obj.type_ref) + + return self.match_type(obj) + + def match_type(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901 + """Main mapping of types to schemas. + + The general structure is a series of if statements starting with the simple cases + (non-generic primitive types) and then handling generics and other more complex cases. + + Each case either generates a schema directly, calls into a public user-overridable method + (like `GenerateSchema.tuple_variable_schema`) or calls into a private method that handles some + boilerplate before calling into the user-facing method (e.g. `GenerateSchema._tuple_schema`). + + The idea is that we'll evolve this into adding more and more user facing methods over time + as they get requested and we figure out what the right API for them is. + """ + if obj is str: + return core_schema.str_schema() + elif obj is bytes: + return core_schema.bytes_schema() + elif obj is int: + return core_schema.int_schema() + elif obj is float: + return core_schema.float_schema() + elif obj is bool: + return core_schema.bool_schema() + elif obj is complex: + return core_schema.complex_schema() + elif typing_objects.is_any(obj) or obj is object: + return core_schema.any_schema() + elif obj is datetime.date: + return core_schema.date_schema() + elif obj is datetime.datetime: + return core_schema.datetime_schema() + elif obj is datetime.time: + return core_schema.time_schema() + elif obj is datetime.timedelta: + return core_schema.timedelta_schema() + elif obj is Decimal: + return core_schema.decimal_schema() + elif obj is UUID: + return core_schema.uuid_schema() + elif obj is Url: + return core_schema.url_schema() + elif obj is Fraction: + return self._fraction_schema() + elif obj is MultiHostUrl: + return core_schema.multi_host_url_schema() + elif obj is None or obj is _typing_extra.NoneType: + return core_schema.none_schema() + if obj is MISSING: + return core_schema.missing_sentinel_schema() + elif obj in IP_TYPES: + return self._ip_schema(obj) + elif obj in TUPLE_TYPES: + return self._tuple_schema(obj) + elif obj in LIST_TYPES: + return self._list_schema(Any) + elif obj in SET_TYPES: + return self._set_schema(Any) + elif obj in FROZEN_SET_TYPES: + return self._frozenset_schema(Any) + elif obj in SEQUENCE_TYPES: + return self._sequence_schema(Any) + elif obj in ITERABLE_TYPES: + return self._iterable_schema(obj) + elif obj in DICT_TYPES: + return self._dict_schema(Any, Any) + elif obj in PATH_TYPES: + return self._path_schema(obj, Any) + elif obj in DEQUE_TYPES: + return self._deque_schema(Any) + elif obj in MAPPING_TYPES: + return self._mapping_schema(obj, Any, Any) + elif obj in COUNTER_TYPES: + return self._mapping_schema(obj, Any, int) + elif typing_objects.is_typealiastype(obj): + return self._type_alias_type_schema(obj) + elif obj is type: + return self._type_schema() + elif _typing_extra.is_callable(obj): + return core_schema.callable_schema() + elif typing_objects.is_literal(get_origin(obj)): + return self._literal_schema(obj) + elif is_typeddict(obj): + return self._typed_dict_schema(obj, None) + elif inspect.isclass(obj) and issubclass(obj, Enum): + # NOTE: this must come before the `is_namedtuple()` check as enums values + # can be namedtuples: + return self._enum_schema(obj) + elif _typing_extra.is_namedtuple(obj): + return self._namedtuple_schema(obj, None) + elif typing_objects.is_newtype(obj): + # NewType, can't use isinstance because it fails <3.10 + return self.generate_schema(obj.__supertype__) + elif obj in PATTERN_TYPES: + return self._pattern_schema(obj) + elif _typing_extra.is_hashable(obj): + return self._hashable_schema() + elif isinstance(obj, typing.TypeVar): + return self._unsubstituted_typevar_schema(obj) + elif _typing_extra.is_finalvar(obj): + if obj is Final: + return core_schema.any_schema() + return self.generate_schema( + self._get_first_arg_or_any(obj), + ) + elif isinstance(obj, VALIDATE_CALL_SUPPORTED_TYPES): + return self._call_schema(obj) # pyright: ignore[reportArgumentType] + elif obj is ZoneInfo: + return self._zoneinfo_schema() + + # dataclasses.is_dataclass coerces dc instances to types, but we only handle + # the case of a dc type here + if dataclasses.is_dataclass(obj): + return self._dataclass_schema(obj, None) # pyright: ignore[reportArgumentType] + + origin = get_origin(obj) + if origin is not None: + return self._match_generic_type(obj, origin) + + if self._arbitrary_types: + return self._arbitrary_type_schema(obj) + return self._unknown_type_schema(obj) + + def _match_generic_type(self, obj: Any, origin: Any) -> CoreSchema: # noqa: C901 + # Need to handle generic dataclasses before looking for the schema properties because attribute accesses + # on _GenericAlias delegate to the origin type, so lose the information about the concrete parametrization + # As a result, currently, there is no way to cache the schema for generic dataclasses. This may be possible + # to resolve by modifying the value returned by `Generic.__class_getitem__`, but that is a dangerous game. + if dataclasses.is_dataclass(origin): + return self._dataclass_schema(obj, origin) # pyright: ignore[reportArgumentType] + if _typing_extra.is_namedtuple(origin): + return self._namedtuple_schema(obj, origin) + + schema = self._generate_schema_from_get_schema_method(origin, obj) + if schema is not None: + return schema + + if typing_objects.is_typealiastype(origin): + return self._type_alias_type_schema(obj) + elif is_union_origin(origin): + return self._union_schema(obj) + elif origin in TUPLE_TYPES: + return self._tuple_schema(obj) + elif origin in LIST_TYPES: + return self._list_schema(self._get_first_arg_or_any(obj)) + elif origin in SET_TYPES: + return self._set_schema(self._get_first_arg_or_any(obj)) + elif origin in FROZEN_SET_TYPES: + return self._frozenset_schema(self._get_first_arg_or_any(obj)) + elif origin in DICT_TYPES: + return self._dict_schema(*self._get_first_two_args_or_any(obj)) + elif origin in PATH_TYPES: + return self._path_schema(origin, self._get_first_arg_or_any(obj)) + elif origin in DEQUE_TYPES: + return self._deque_schema(self._get_first_arg_or_any(obj)) + elif origin in MAPPING_TYPES: + return self._mapping_schema(origin, *self._get_first_two_args_or_any(obj)) + elif origin in COUNTER_TYPES: + return self._mapping_schema(origin, self._get_first_arg_or_any(obj), int) + elif is_typeddict(origin): + return self._typed_dict_schema(obj, origin) + elif origin in TYPE_TYPES: + return self._subclass_schema(obj) + elif origin in SEQUENCE_TYPES: + return self._sequence_schema(self._get_first_arg_or_any(obj)) + elif origin in ITERABLE_TYPES: + return self._iterable_schema(obj) + elif origin in PATTERN_TYPES: + return self._pattern_schema(obj) + + if self._arbitrary_types: + return self._arbitrary_type_schema(origin) + return self._unknown_type_schema(obj) + + def _generate_td_field_schema( + self, + name: str, + field_info: FieldInfo, + decorators: DecoratorInfos, + *, + required: bool = True, + ) -> core_schema.TypedDictField: + """Prepare a TypedDictField to represent a model or typeddict field.""" + schema, metadata = self._common_field_schema(name, field_info, decorators) + return core_schema.typed_dict_field( + schema, + required=False if not field_info.is_required() else required, + serialization_exclude=field_info.exclude, + validation_alias=_convert_to_aliases(field_info.validation_alias), + serialization_alias=field_info.serialization_alias, + serialization_exclude_if=field_info.exclude_if, + metadata=metadata, + ) + + def _generate_md_field_schema( + self, + name: str, + field_info: FieldInfo, + decorators: DecoratorInfos, + ) -> core_schema.ModelField: + """Prepare a ModelField to represent a model field.""" + schema, metadata = self._common_field_schema(name, field_info, decorators) + return core_schema.model_field( + schema, + serialization_exclude=field_info.exclude, + validation_alias=_convert_to_aliases(field_info.validation_alias), + serialization_alias=field_info.serialization_alias, + serialization_exclude_if=field_info.exclude_if, + frozen=field_info.frozen, + metadata=metadata, + ) + + def _generate_dc_field_schema( + self, + name: str, + field_info: FieldInfo, + decorators: DecoratorInfos, + ) -> core_schema.DataclassField: + """Prepare a DataclassField to represent the parameter/field, of a dataclass.""" + schema, metadata = self._common_field_schema(name, field_info, decorators) + return core_schema.dataclass_field( + name, + schema, + init=field_info.init, + init_only=field_info.init_var or None, + kw_only=None if field_info.kw_only else False, + serialization_exclude=field_info.exclude, + validation_alias=_convert_to_aliases(field_info.validation_alias), + serialization_alias=field_info.serialization_alias, + serialization_exclude_if=field_info.exclude_if, + frozen=field_info.frozen, + metadata=metadata, + ) + + def _common_field_schema( # C901 + self, name: str, field_info: FieldInfo, decorators: DecoratorInfos + ) -> tuple[CoreSchema, dict[str, Any]]: + source_type, annotations = field_info.annotation, field_info.metadata + + def set_discriminator(schema: CoreSchema) -> CoreSchema: + schema = self._apply_discriminator_to_union(schema, field_info.discriminator) + return schema + + # Convert `@field_validator` decorators to `Before/After/Plain/WrapValidator` instances: + validators_from_decorators = [ + _mode_to_validator[decorator.info.mode]._from_decorator(decorator) + for decorator in filter_field_decorator_info_by_field(decorators.field_validators.values(), name) + ] + + with self.field_name_stack.push(name): + if field_info.discriminator is not None: + schema = self._apply_annotations( + source_type, annotations + validators_from_decorators, transform_inner_schema=set_discriminator + ) + else: + schema = self._apply_annotations( + source_type, + annotations + validators_from_decorators, + ) + + # This V1 compatibility shim should eventually be removed + # push down any `each_item=True` validators + # note that this won't work for any Annotated types that get wrapped by a function validator + # but that's okay because that didn't exist in V1 + this_field_validators = filter_field_decorator_info_by_field(decorators.validators.values(), name) + if _validators_require_validate_default(this_field_validators): + field_info.validate_default = True + each_item_validators = [v for v in this_field_validators if v.info.each_item is True] + this_field_validators = [v for v in this_field_validators if v not in each_item_validators] + schema = apply_each_item_validators(schema, each_item_validators) + + schema = apply_validators(schema, this_field_validators) + + # the default validator needs to go outside of any other validators + # so that it is the topmost validator for the field validator + # which uses it to check if the field has a default value or not + if not field_info.is_required(): + schema = wrap_default(field_info, schema) + + schema = self._apply_field_serializers( + schema, filter_field_decorator_info_by_field(decorators.field_serializers.values(), name) + ) + + pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(field_info) + core_metadata: dict[str, Any] = {} + update_core_metadata( + core_metadata, pydantic_js_updates=pydantic_js_updates, pydantic_js_extra=pydantic_js_extra + ) + + return schema, core_metadata + + def _union_schema(self, union_type: Any) -> core_schema.CoreSchema: + """Generate schema for a Union.""" + args = self._get_args_resolving_forward_refs(union_type, required=True) + choices: list[CoreSchema] = [] + nullable = False + for arg in args: + if arg is None or arg is _typing_extra.NoneType: + nullable = True + else: + choices.append(self.generate_schema(arg)) + + if len(choices) == 1: + s = choices[0] + else: + choices_with_tags: list[CoreSchema | tuple[CoreSchema, str]] = [] + for choice in choices: + tag = cast(CoreMetadata, choice.get('metadata', {})).get('pydantic_internal_union_tag_key') + if tag is not None: + choices_with_tags.append((choice, tag)) + else: + choices_with_tags.append(choice) + s = core_schema.union_schema(choices_with_tags) + + if nullable: + s = core_schema.nullable_schema(s) + return s + + def _type_alias_type_schema(self, obj: TypeAliasType) -> CoreSchema: + with self.defs.get_schema_or_ref(obj) as (ref, maybe_schema): + if maybe_schema is not None: + return maybe_schema + + origin: TypeAliasType = get_origin(obj) or obj + typevars_map = get_standard_typevars_map(obj) + + with self._ns_resolver.push(origin): + try: + annotation = _typing_extra.eval_type(origin.__value__, *self._types_namespace) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + annotation = replace_types(annotation, typevars_map) + schema = self.generate_schema(annotation) + assert schema['type'] != 'definitions' + schema['ref'] = ref # type: ignore + return self.defs.create_definition_reference_schema(schema) + + def _literal_schema(self, literal_type: Any) -> CoreSchema: + """Generate schema for a Literal.""" + expected = list(get_literal_values(literal_type, type_check=False, unpack_type_aliases='eager')) + assert expected, f'literal "expected" cannot be empty, obj={literal_type}' + schema = core_schema.literal_schema(expected) + + if self._config_wrapper.use_enum_values and any(isinstance(v, Enum) for v in expected): + schema = core_schema.no_info_after_validator_function( + lambda v: v.value if isinstance(v, Enum) else v, schema + ) + + return schema + + def _typed_dict_schema(self, typed_dict_cls: Any, origin: Any) -> core_schema.CoreSchema: + """Generate a core schema for a `TypedDict` class. + + To be able to build a `DecoratorInfos` instance for the `TypedDict` class (which will include + validators, serializers, etc.), we need to have access to the original bases of the class + (see https://docs.python.org/3/library/types.html#types.get_original_bases). + However, the `__orig_bases__` attribute was only added in 3.12 (https://github.com/python/cpython/pull/103698). + + For this reason, we require Python 3.12 (or using the `typing_extensions` backport). + """ + FieldInfo = import_cached_field_info() + + with ( + self.model_type_stack.push(typed_dict_cls), + self.defs.get_schema_or_ref(typed_dict_cls) as ( + typed_dict_ref, + maybe_schema, + ), + ): + if maybe_schema is not None: + return maybe_schema + + typevars_map = get_standard_typevars_map(typed_dict_cls) + if origin is not None: + typed_dict_cls = origin + + if not _SUPPORTS_TYPEDDICT and type(typed_dict_cls).__module__ == 'typing': + raise PydanticUserError( + 'Please use `typing_extensions.TypedDict` instead of `typing.TypedDict` on Python < 3.12.', + code='typed-dict-version', + ) + + try: + # if a typed dictionary class doesn't have config, we use the parent's config, hence a default of `None` + # see https://github.com/pydantic/pydantic/issues/10917 + config: ConfigDict | None = get_attribute_from_bases(typed_dict_cls, '__pydantic_config__') + except AttributeError: + config = None + + with self._config_wrapper_stack.push(config): + core_config = self._config_wrapper.core_config(title=typed_dict_cls.__name__) + + required_keys: frozenset[str] = typed_dict_cls.__required_keys__ + + fields: dict[str, core_schema.TypedDictField] = {} + + decorators = DecoratorInfos.build(typed_dict_cls, replace_wrapped_methods=False) + decorators.update_from_config(self._config_wrapper) + + if self._config_wrapper.use_attribute_docstrings: + field_docstrings = extract_docstrings_from_cls(typed_dict_cls, use_inspect=True) + else: + field_docstrings = None + + try: + annotations = _typing_extra.get_cls_type_hints(typed_dict_cls, ns_resolver=self._ns_resolver) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + readonly_fields: list[str] = [] + + for field_name, annotation in annotations.items(): + field_info = FieldInfo.from_annotation(annotation, _source=AnnotationSource.TYPED_DICT) + field_info.annotation = replace_types(field_info.annotation, typevars_map) + + required = ( + field_name in required_keys or 'required' in field_info._qualifiers + ) and 'not_required' not in field_info._qualifiers + if 'read_only' in field_info._qualifiers: + readonly_fields.append(field_name) + + if ( + field_docstrings is not None + and field_info.description is None + and field_name in field_docstrings + ): + field_info.description = field_docstrings[field_name] + update_field_from_config(self._config_wrapper, field_name, field_info) + + fields[field_name] = self._generate_td_field_schema( + field_name, field_info, decorators, required=required + ) + + if readonly_fields: + fields_repr = ', '.join(repr(f) for f in readonly_fields) + plural = len(readonly_fields) >= 2 + warnings.warn( + f'Item{"s" if plural else ""} {fields_repr} on TypedDict class {typed_dict_cls.__name__!r} ' + f'{"are" if plural else "is"} using the `ReadOnly` qualifier. Pydantic will not protect items ' + 'from any mutation on dictionary instances.', + UserWarning, + ) + + extra_behavior: core_schema.ExtraBehavior = 'ignore' + extras_schema: CoreSchema | None = None # For 'allow', equivalent to `Any` - no validation performed. + + # `__closed__` is `None` when not specified (equivalent to `False`): + is_closed = bool(getattr(typed_dict_cls, '__closed__', False)) + extra_items = getattr(typed_dict_cls, '__extra_items__', typing_extensions.NoExtraItems) + if is_closed: + extra_behavior = 'forbid' + extras_schema = None + elif not typing_objects.is_noextraitems(extra_items): + extra_behavior = 'allow' + extras_schema = self.generate_schema(replace_types(extra_items, typevars_map)) + + if (config_extra := self._config_wrapper.extra) in ('allow', 'forbid'): + if is_closed and config_extra == 'allow': + warnings.warn( + f"TypedDict class {typed_dict_cls.__qualname__!r} is closed, but 'extra' configuration " + "is set to `'allow'`. The 'extra' configuration value will be ignored.", + category=TypedDictExtraConfigWarning, + ) + elif not typing_objects.is_noextraitems(extra_items) and config_extra == 'forbid': + warnings.warn( + f"TypedDict class {typed_dict_cls.__qualname__!r} allows extra items, but 'extra' configuration " + "is set to `'forbid'`. The 'extra' configuration value will be ignored.", + category=TypedDictExtraConfigWarning, + ) + else: + extra_behavior = config_extra + + td_schema = core_schema.typed_dict_schema( + fields, + cls=typed_dict_cls, + computed_fields=[ + self._computed_field_schema(d, decorators.field_serializers) + for d in decorators.computed_fields.values() + ], + extra_behavior=extra_behavior, + extras_schema=extras_schema, + ref=typed_dict_ref, + config=core_config, + ) + + schema = self._apply_model_serializers(td_schema, decorators.model_serializers.values()) + schema = apply_model_validators(schema, decorators.model_validators.values(), 'all') + return self.defs.create_definition_reference_schema(schema) + + def _namedtuple_schema(self, namedtuple_cls: Any, origin: Any) -> core_schema.CoreSchema: + """Generate schema for a NamedTuple.""" + with ( + self.model_type_stack.push(namedtuple_cls), + self.defs.get_schema_or_ref(namedtuple_cls) as ( + namedtuple_ref, + maybe_schema, + ), + ): + if maybe_schema is not None: + return maybe_schema + typevars_map = get_standard_typevars_map(namedtuple_cls) + if origin is not None: + namedtuple_cls = origin + + try: + annotations = _typing_extra.get_cls_type_hints(namedtuple_cls, ns_resolver=self._ns_resolver) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + # Filter annotations to only include fields that are actually in the NamedTuple + # (as subclassing an existing NamedTuple is not supported yet - see https://github.com/python/typing/issues/427) + # and use `Any` if no annotation exist (i.e. when using `collections.namedtuple()`). + annotations = {field_name: annotations.get(field_name, Any) for field_name in namedtuple_cls._fields} + + if typevars_map: + annotations = { + field_name: replace_types(annotation, typevars_map) + for field_name, annotation in annotations.items() + } + + arguments_schema = core_schema.arguments_schema( + [ + self._generate_parameter_schema( + field_name, + annotation, + source=AnnotationSource.NAMED_TUPLE, + default=namedtuple_cls._field_defaults.get(field_name, Parameter.empty), + ) + for field_name, annotation in annotations.items() + ], + metadata={'pydantic_js_prefer_positional_arguments': True}, + ) + schema = core_schema.call_schema(arguments_schema, namedtuple_cls, ref=namedtuple_ref) + return self.defs.create_definition_reference_schema(schema) + + def _generate_parameter_schema( + self, + name: str, + annotation: type[Any], + source: AnnotationSource, + default: Any = Parameter.empty, + mode: Literal['positional_only', 'positional_or_keyword', 'keyword_only'] | None = None, + ) -> core_schema.ArgumentsParameter: + """Generate the definition of a field in a namedtuple or a parameter in a function signature. + + This definition is meant to be used for the `'arguments'` core schema, which will be replaced + in V3 by the `'arguments-v3`'. + """ + FieldInfo = import_cached_field_info() + + if default is Parameter.empty: + field = FieldInfo.from_annotation(annotation, _source=source) + else: + field = FieldInfo.from_annotated_attribute(annotation, default, _source=source) + + assert field.annotation is not None, 'field.annotation should not be None when generating a schema' + update_field_from_config(self._config_wrapper, name, field) + + with self.field_name_stack.push(name): + schema = self._apply_annotations( + field.annotation, + [field], + # Because we pass `field` as metadata above (required for attributes relevant for + # JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo` + # attributes that will not be used: + check_unsupported_field_info_attributes=False, + ) + + if not field.is_required(): + schema = wrap_default(field, schema) + + parameter_schema = core_schema.arguments_parameter( + name, + schema, + mode=mode, + alias=_convert_to_aliases(field.validation_alias), + ) + + return parameter_schema + + def _generate_parameter_v3_schema( + self, + name: str, + annotation: Any, + source: AnnotationSource, + mode: Literal[ + 'positional_only', + 'positional_or_keyword', + 'keyword_only', + 'var_args', + 'var_kwargs_uniform', + 'var_kwargs_unpacked_typed_dict', + ], + default: Any = Parameter.empty, + ) -> core_schema.ArgumentsV3Parameter: + """Generate the definition of a parameter in a function signature. + + This definition is meant to be used for the `'arguments-v3'` core schema, which will replace + the `'arguments`' schema in V3. + """ + FieldInfo = import_cached_field_info() + + if default is Parameter.empty: + field = FieldInfo.from_annotation(annotation, _source=source) + else: + field = FieldInfo.from_annotated_attribute(annotation, default, _source=source) + update_field_from_config(self._config_wrapper, name, field) + + with self.field_name_stack.push(name): + schema = self._apply_annotations( + field.annotation, + [field], + # Because we pass `field` as metadata above (required for attributes relevant for + # JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo` + # attributes that will not be used: + check_unsupported_field_info_attributes=False, + ) + + if not field.is_required(): + schema = wrap_default(field, schema) + + parameter_schema = core_schema.arguments_v3_parameter( + name=name, + schema=schema, + mode=mode, + alias=_convert_to_aliases(field.validation_alias), + ) + + return parameter_schema + + def _tuple_schema(self, tuple_type: Any) -> core_schema.CoreSchema: + """Generate schema for a Tuple, e.g. `tuple[int, str]` or `tuple[int, ...]`.""" + # TODO: do we really need to resolve type vars here? + typevars_map = get_standard_typevars_map(tuple_type) + params = self._get_args_resolving_forward_refs(tuple_type) + + if typevars_map and params: + params = tuple(replace_types(param, typevars_map) for param in params) + + # NOTE: subtle difference: `tuple[()]` gives `params=()`, whereas `typing.Tuple[()]` gives `params=((),)` + # This is only true for <3.11, on Python 3.11+ `typing.Tuple[()]` gives `params=()` + if not params: + if tuple_type in TUPLE_TYPES: + return core_schema.tuple_schema([core_schema.any_schema()], variadic_item_index=0) + else: + # special case for `tuple[()]` which means `tuple[]` - an empty tuple + return core_schema.tuple_schema([]) + elif params[-1] is Ellipsis: + if len(params) == 2: + return core_schema.tuple_schema([self.generate_schema(params[0])], variadic_item_index=0) + else: + # TODO: something like https://github.com/pydantic/pydantic/issues/5952 + raise ValueError('Variable tuples can only have one type') + elif len(params) == 1 and params[0] == (): + # special case for `tuple[()]` which means `tuple[]` - an empty tuple + # NOTE: This conditional can be removed when we drop support for Python 3.10. + return core_schema.tuple_schema([]) + else: + return core_schema.tuple_schema([self.generate_schema(param) for param in params]) + + def _type_schema(self) -> core_schema.CoreSchema: + return core_schema.custom_error_schema( + core_schema.is_instance_schema(type), + custom_error_type='is_type', + custom_error_message='Input should be a type', + ) + + def _zoneinfo_schema(self) -> core_schema.CoreSchema: + """Generate schema for a zone_info.ZoneInfo object""" + from ._validators import validate_str_is_valid_iana_tz + + metadata = {'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'zoneinfo'}]} + return core_schema.no_info_plain_validator_function( + validate_str_is_valid_iana_tz, + serialization=core_schema.to_string_ser_schema(), + metadata=metadata, + ) + + def _union_is_subclass_schema(self, union_type: Any) -> core_schema.CoreSchema: + """Generate schema for `type[Union[X, ...]]`.""" + args = self._get_args_resolving_forward_refs(union_type, required=True) + return core_schema.union_schema([self.generate_schema(type[args]) for args in args]) + + def _subclass_schema(self, type_: Any) -> core_schema.CoreSchema: + """Generate schema for a type, e.g. `type[int]`.""" + type_param = self._get_first_arg_or_any(type_) + + # Assume `type[Annotated[, ...]]` is equivalent to `type[]`: + type_param = _typing_extra.annotated_type(type_param) or type_param + + if typing_objects.is_any(type_param): + return self._type_schema() + elif typing_objects.is_typealiastype(type_param): + return self.generate_schema(type[type_param.__value__]) + elif typing_objects.is_typevar(type_param): + if type_param.__bound__: + if is_union_origin(get_origin(type_param.__bound__)): + return self._union_is_subclass_schema(type_param.__bound__) + return core_schema.is_subclass_schema(type_param.__bound__) + elif type_param.__constraints__: + return core_schema.union_schema([self.generate_schema(type[c]) for c in type_param.__constraints__]) + else: + return self._type_schema() + elif is_union_origin(get_origin(type_param)): + return self._union_is_subclass_schema(type_param) + else: + if typing_objects.is_self(type_param): + type_param = self._resolve_self_type(type_param) + if _typing_extra.is_generic_alias(type_param): + raise PydanticUserError( + 'Subscripting `type[]` with an already parametrized type is not supported. ' + f'Instead of using type[{type_param!r}], use type[{_repr.display_as_type(get_origin(type_param))}].', + code=None, + ) + if not inspect.isclass(type_param): + # when using type[None], this doesn't type convert to type[NoneType], and None isn't a class + # so we handle it manually here + if type_param is None: + return core_schema.is_subclass_schema(_typing_extra.NoneType) + raise TypeError(f'Expected a class, got {type_param!r}') + return core_schema.is_subclass_schema(type_param) + + def _sequence_schema(self, items_type: Any) -> core_schema.CoreSchema: + """Generate schema for a Sequence, e.g. `Sequence[int]`.""" + from ._serializers import serialize_sequence_via_list + + item_type_schema = self.generate_schema(items_type) + list_schema = core_schema.list_schema(item_type_schema) + + json_schema = smart_deepcopy(list_schema) + python_schema = core_schema.is_instance_schema(typing.Sequence, cls_repr='Sequence') + if not typing_objects.is_any(items_type): + from ._validators import sequence_validator + + python_schema = core_schema.chain_schema( + [python_schema, core_schema.no_info_wrap_validator_function(sequence_validator, list_schema)], + ) + + serialization = core_schema.wrap_serializer_function_ser_schema( + serialize_sequence_via_list, schema=item_type_schema, info_arg=True + ) + return core_schema.json_or_python_schema( + json_schema=json_schema, python_schema=python_schema, serialization=serialization + ) + + def _iterable_schema(self, type_: Any) -> core_schema.GeneratorSchema: + """Generate a schema for an `Iterable`.""" + item_type = self._get_first_arg_or_any(type_) + + return core_schema.generator_schema(self.generate_schema(item_type)) + + def _pattern_schema(self, pattern_type: Any) -> core_schema.CoreSchema: + from . import _validators + + metadata = {'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'regex'}]} + ser = core_schema.plain_serializer_function_ser_schema( + attrgetter('pattern'), when_used='json', return_schema=core_schema.str_schema() + ) + if pattern_type is typing.Pattern or pattern_type is re.Pattern: + # bare type + return core_schema.no_info_plain_validator_function( + _validators.pattern_either_validator, serialization=ser, metadata=metadata + ) + + param = self._get_args_resolving_forward_refs( + pattern_type, + required=True, + )[0] + if param is str: + return core_schema.no_info_plain_validator_function( + _validators.pattern_str_validator, serialization=ser, metadata=metadata + ) + elif param is bytes: + return core_schema.no_info_plain_validator_function( + _validators.pattern_bytes_validator, serialization=ser, metadata=metadata + ) + else: + raise PydanticSchemaGenerationError(f'Unable to generate pydantic-core schema for {pattern_type!r}.') + + def _hashable_schema(self) -> core_schema.CoreSchema: + return core_schema.custom_error_schema( + schema=core_schema.json_or_python_schema( + json_schema=core_schema.chain_schema( + [core_schema.any_schema(), core_schema.is_instance_schema(collections.abc.Hashable)] + ), + python_schema=core_schema.is_instance_schema(collections.abc.Hashable), + ), + custom_error_type='is_hashable', + custom_error_message='Input should be hashable', + ) + + def _dataclass_schema( + self, dataclass: type[StandardDataclass], origin: type[StandardDataclass] | None + ) -> core_schema.CoreSchema: + """Generate schema for a dataclass.""" + with ( + self.model_type_stack.push(dataclass), + self.defs.get_schema_or_ref(dataclass) as ( + dataclass_ref, + maybe_schema, + ), + ): + if maybe_schema is not None: + return maybe_schema + + schema = dataclass.__dict__.get('__pydantic_core_schema__') + if schema is not None and not isinstance(schema, MockCoreSchema): + if schema['type'] == 'definitions': + schema = self.defs.unpack_definitions(schema) + ref = get_ref(schema) + if ref: + return self.defs.create_definition_reference_schema(schema) + else: + return schema + + typevars_map = get_standard_typevars_map(dataclass) + if origin is not None: + dataclass = origin + + # if (plain) dataclass doesn't have config, we use the parent's config, hence a default of `None` + # (Pydantic dataclasses have an empty dict config by default). + # see https://github.com/pydantic/pydantic/issues/10917 + config = getattr(dataclass, '__pydantic_config__', None) + + from ..dataclasses import is_pydantic_dataclass + + with self._ns_resolver.push(dataclass), self._config_wrapper_stack.push(config): + if is_pydantic_dataclass(dataclass): + if dataclass.__pydantic_fields_complete__(): + # Copy the field info instances to avoid mutating the `FieldInfo` instances + # of the generic dataclass generic origin (e.g. `apply_typevars_map` below). + # Note that we don't apply `deepcopy` on `__pydantic_fields__` because we + # don't want to copy the `FieldInfo` attributes: + fields = { + f_name: copy(field_info) for f_name, field_info in dataclass.__pydantic_fields__.items() + } + if typevars_map: + for field in fields.values(): + field.apply_typevars_map(typevars_map, *self._types_namespace) + else: + try: + fields = rebuild_dataclass_fields( + dataclass, + config_wrapper=self._config_wrapper, + ns_resolver=self._ns_resolver, + typevars_map=typevars_map or {}, + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + else: + fields = collect_dataclass_fields( + dataclass, + typevars_map=typevars_map, + config_wrapper=self._config_wrapper, + ) + + if self._config_wrapper.extra == 'allow': + # disallow combination of init=False on a dataclass field and extra='allow' on a dataclass + for field_name, field in fields.items(): + if field.init is False: + raise PydanticUserError( + f'Field {field_name} has `init=False` and dataclass has config setting `extra="allow"`. ' + f'This combination is not allowed.', + code='dataclass-init-false-extra-allow', + ) + + decorators = dataclass.__dict__.get('__pydantic_decorators__') + if decorators is None: + decorators = DecoratorInfos.build(dataclass, replace_wrapped_methods=False) + decorators.update_from_config(self._config_wrapper) + # Move kw_only=False args to the start of the list, as this is how vanilla dataclasses work. + # Note that when kw_only is missing or None, it is treated as equivalent to kw_only=True + args = sorted( + (self._generate_dc_field_schema(k, v, decorators) for k, v in fields.items()), + key=lambda a: a.get('kw_only') is not False, + ) + has_post_init = hasattr(dataclass, '__post_init__') + has_slots = hasattr(dataclass, '__slots__') + + args_schema = core_schema.dataclass_args_schema( + dataclass.__name__, + args, + computed_fields=[ + self._computed_field_schema(d, decorators.field_serializers) + for d in decorators.computed_fields.values() + ], + collect_init_only=has_post_init, + ) + + inner_schema = apply_validators(args_schema, decorators.root_validators.values()) + + model_validators = decorators.model_validators.values() + inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') + + core_config = self._config_wrapper.core_config(title=dataclass.__name__) + + dc_schema = core_schema.dataclass_schema( + dataclass, + inner_schema, + generic_origin=origin, + post_init=has_post_init, + ref=dataclass_ref, + fields=[field.name for field in dataclasses.fields(dataclass)], + slots=has_slots, + config=core_config, + # we don't use a custom __setattr__ for dataclasses, so we must + # pass along the frozen config setting to the pydantic-core schema + frozen=self._config_wrapper_stack.tail.frozen, + ) + schema = self._apply_model_serializers(dc_schema, decorators.model_serializers.values()) + schema = apply_model_validators(schema, model_validators, 'outer') + return self.defs.create_definition_reference_schema(schema) + + def _call_schema(self, function: ValidateCallSupportedTypes) -> core_schema.CallSchema: + """Generate schema for a Callable. + + TODO support functional validators once we support them in Config + """ + arguments_schema = self._arguments_schema(function) + + return_schema: core_schema.CoreSchema | None = None + config_wrapper = self._config_wrapper + if config_wrapper.validate_return: + sig = _typing_extra.signature_no_eval(function) + return_hint = sig.return_annotation + if return_hint is not sig.empty: + globalns, localns = self._types_namespace + type_hints = _typing_extra.get_function_type_hints( + function, globalns=globalns, localns=localns, include_keys={'return'} + ) + return_schema = self.generate_schema(type_hints['return']) + + return core_schema.call_schema( + arguments_schema, + function, + return_schema=return_schema, + ) + + def _arguments_schema( + self, function: ValidateCallSupportedTypes, parameters_callback: ParametersCallback | None = None + ) -> core_schema.ArgumentsSchema: + """Generate schema for a Signature.""" + mode_lookup: dict[_ParameterKind, Literal['positional_only', 'positional_or_keyword', 'keyword_only']] = { + Parameter.POSITIONAL_ONLY: 'positional_only', + Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword', + Parameter.KEYWORD_ONLY: 'keyword_only', + } + + sig = _typing_extra.signature_no_eval(function) + globalns, localns = self._types_namespace + type_hints = _typing_extra.get_function_type_hints(function, globalns=globalns, localns=localns) + + arguments_list: list[core_schema.ArgumentsParameter] = [] + var_args_schema: core_schema.CoreSchema | None = None + var_kwargs_schema: core_schema.CoreSchema | None = None + var_kwargs_mode: core_schema.VarKwargsMode | None = None + + for i, (name, p) in enumerate(sig.parameters.items()): + if p.annotation is sig.empty: + annotation = typing.cast(Any, Any) + else: + annotation = type_hints[name] + + if parameters_callback is not None: + result = parameters_callback(i, name, annotation) + if result == 'skip': + continue + + parameter_mode = mode_lookup.get(p.kind) + if parameter_mode is not None: + arg_schema = self._generate_parameter_schema( + name, annotation, AnnotationSource.FUNCTION, p.default, parameter_mode + ) + arguments_list.append(arg_schema) + elif p.kind == Parameter.VAR_POSITIONAL: + var_args_schema = self.generate_schema(annotation) + else: + assert p.kind == Parameter.VAR_KEYWORD, p.kind + + unpack_type = _typing_extra.unpack_type(annotation) + if unpack_type is not None: + origin = get_origin(unpack_type) or unpack_type + if not is_typeddict(origin): + raise PydanticUserError( + f'Expected a `TypedDict` class inside `Unpack[...]`, got {unpack_type!r}', + code='unpack-typed-dict', + ) + non_pos_only_param_names = { + name for name, p in sig.parameters.items() if p.kind != Parameter.POSITIONAL_ONLY + } + overlapping_params = non_pos_only_param_names.intersection(origin.__annotations__) + if overlapping_params: + raise PydanticUserError( + f'Typed dictionary {origin.__name__!r} overlaps with parameter' + f'{"s" if len(overlapping_params) >= 2 else ""} ' + f'{", ".join(repr(p) for p in sorted(overlapping_params))}', + code='overlapping-unpack-typed-dict', + ) + + var_kwargs_mode = 'unpacked-typed-dict' + var_kwargs_schema = self._typed_dict_schema(unpack_type, get_origin(unpack_type)) + else: + var_kwargs_mode = 'uniform' + var_kwargs_schema = self.generate_schema(annotation) + + return core_schema.arguments_schema( + arguments_list, + var_args_schema=var_args_schema, + var_kwargs_mode=var_kwargs_mode, + var_kwargs_schema=var_kwargs_schema, + validate_by_name=self._config_wrapper.validate_by_name, + ) + + def _arguments_v3_schema( + self, function: ValidateCallSupportedTypes, parameters_callback: ParametersCallback | None = None + ) -> core_schema.ArgumentsV3Schema: + mode_lookup: dict[ + _ParameterKind, Literal['positional_only', 'positional_or_keyword', 'var_args', 'keyword_only'] + ] = { + Parameter.POSITIONAL_ONLY: 'positional_only', + Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword', + Parameter.VAR_POSITIONAL: 'var_args', + Parameter.KEYWORD_ONLY: 'keyword_only', + } + + sig = _typing_extra.signature_no_eval(function) + globalns, localns = self._types_namespace + type_hints = _typing_extra.get_function_type_hints(function, globalns=globalns, localns=localns) + + parameters_list: list[core_schema.ArgumentsV3Parameter] = [] + + for i, (name, p) in enumerate(sig.parameters.items()): + if parameters_callback is not None: + result = parameters_callback(i, name, p.annotation) + if result == 'skip': + continue + + if p.annotation is Parameter.empty: + annotation = typing.cast(Any, Any) + else: + annotation = type_hints[name] + + parameter_mode = mode_lookup.get(p.kind) + if parameter_mode is None: + assert p.kind == Parameter.VAR_KEYWORD, p.kind + + unpack_type = _typing_extra.unpack_type(annotation) + if unpack_type is not None: + origin = get_origin(unpack_type) or unpack_type + if not is_typeddict(origin): + raise PydanticUserError( + f'Expected a `TypedDict` class inside `Unpack[...]`, got {unpack_type!r}', + code='unpack-typed-dict', + ) + non_pos_only_param_names = { + name for name, p in sig.parameters.items() if p.kind != Parameter.POSITIONAL_ONLY + } + overlapping_params = non_pos_only_param_names.intersection(origin.__annotations__) + if overlapping_params: + raise PydanticUserError( + f'Typed dictionary {origin.__name__!r} overlaps with parameter' + f'{"s" if len(overlapping_params) >= 2 else ""} ' + f'{", ".join(repr(p) for p in sorted(overlapping_params))}', + code='overlapping-unpack-typed-dict', + ) + parameter_mode = 'var_kwargs_unpacked_typed_dict' + annotation = unpack_type + else: + parameter_mode = 'var_kwargs_uniform' + + parameters_list.append( + self._generate_parameter_v3_schema( + name, annotation, AnnotationSource.FUNCTION, parameter_mode, default=p.default + ) + ) + + return core_schema.arguments_v3_schema( + parameters_list, + validate_by_name=self._config_wrapper.validate_by_name, + ) + + def _unsubstituted_typevar_schema(self, typevar: typing.TypeVar) -> core_schema.CoreSchema: + try: + has_default = typevar.has_default() # pyright: ignore[reportAttributeAccessIssue] + except AttributeError: + # Happens if using `typing.TypeVar` (and not `typing_extensions`) on Python < 3.13 + pass + else: + if has_default: + return self.generate_schema(typevar.__default__) # pyright: ignore[reportAttributeAccessIssue] + + if constraints := typevar.__constraints__: + return self._union_schema(typing.Union[constraints]) + + if bound := typevar.__bound__: + schema = self.generate_schema(bound) + schema['serialization'] = core_schema.simple_ser_schema('any') + return schema + + return core_schema.any_schema() + + def _computed_field_schema( + self, + d: Decorator[ComputedFieldInfo], + field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]], + ) -> core_schema.ComputedField: + if d.info.return_type is not PydanticUndefined: + return_type = d.info.return_type + else: + try: + # Do not pass in globals as the function could be defined in a different module. + # Instead, let `get_callable_return_type` infer the globals to use, but still pass + # in locals that may contain a parent/rebuild namespace: + return_type = _decorators.get_callable_return_type(d.func, localns=self._types_namespace.locals) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + if return_type is PydanticUndefined: + raise PydanticUserError( + 'Computed field is missing return type annotation or specifying `return_type`' + ' to the `@computed_field` decorator (e.g. `@computed_field(return_type=int | str)`)', + code='model-field-missing-annotation', + ) + + return_type = replace_types(return_type, self._typevars_map) + # Create a new ComputedFieldInfo so that different type parametrizations of the same + # generic model's computed field can have different return types. + d.info = dataclasses.replace(d.info, return_type=return_type) + return_type_schema = self.generate_schema(return_type) + # Apply serializers to computed field if there exist + return_type_schema = self._apply_field_serializers( + return_type_schema, + filter_field_decorator_info_by_field(field_serializers.values(), d.cls_var_name), + ) + + pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(d.info) + core_metadata: dict[str, Any] = {} + update_core_metadata( + core_metadata, + pydantic_js_updates={'readOnly': True, **(pydantic_js_updates if pydantic_js_updates else {})}, + pydantic_js_extra=pydantic_js_extra, + ) + exclude_if = d.info.exclude_if + # TODO: Should we support exclude_if from annotations? + return core_schema.computed_field( + d.cls_var_name, + return_schema=return_type_schema, + alias=d.info.alias, + serialization_exclude_if=exclude_if, + metadata=core_metadata, + ) + + def _annotated_schema(self, annotated_type: Any) -> core_schema.CoreSchema: + """Generate schema for an Annotated type, e.g. `Annotated[int, Field(...)]` or `Annotated[int, Gt(0)]`.""" + FieldInfo = import_cached_field_info() + source_type, *annotations = self._get_args_resolving_forward_refs( + annotated_type, + required=True, + ) + schema = self._apply_annotations(source_type, annotations) + # put the default validator last so that TypeAdapter.get_default_value() works + # even if there are function validators involved + for annotation in annotations: + if isinstance(annotation, FieldInfo): + schema = wrap_default(annotation, schema) + return schema + + def _apply_annotations( + self, + source_type: Any, + annotations: list[Any], + transform_inner_schema: Callable[[CoreSchema], CoreSchema] = lambda x: x, + check_unsupported_field_info_attributes: bool = True, + ) -> CoreSchema: + """Apply arguments from `Annotated` or from `FieldInfo` to a schema. + + This gets called by `GenerateSchema._annotated_schema` but differs from it in that it does + not expect `source_type` to be an `Annotated` object, it expects it to be the first argument of that + (in other words, `GenerateSchema._annotated_schema` just unpacks `Annotated`, this process it). + """ + annotations = list(_known_annotated_metadata.expand_grouped_metadata(annotations)) + + pydantic_js_annotation_functions: list[GetJsonSchemaFunction] = [] + + def inner_handler(obj: Any) -> CoreSchema: + schema = self._generate_schema_from_get_schema_method(obj, source_type) + + if schema is None: + schema = self._generate_schema_inner(obj) + + metadata_js_function = _extract_get_pydantic_json_schema(obj) + if metadata_js_function is not None: + metadata_schema = resolve_original_schema(schema, self.defs) + if metadata_schema is not None: + self._add_js_function(metadata_schema, metadata_js_function) + return transform_inner_schema(schema) + + get_inner_schema = CallbackGetCoreSchemaHandler(inner_handler, self) + + for annotation in annotations: + if annotation is None: + continue + get_inner_schema = self._get_wrapped_inner_schema( + get_inner_schema, + annotation, + pydantic_js_annotation_functions, + check_unsupported_field_info_attributes=check_unsupported_field_info_attributes, + ) + + schema = get_inner_schema(source_type) + if pydantic_js_annotation_functions: + core_metadata = schema.setdefault('metadata', {}) + update_core_metadata(core_metadata, pydantic_js_annotation_functions=pydantic_js_annotation_functions) + return _add_custom_serialization_from_json_encoders(self._config_wrapper.json_encoders, source_type, schema) + + def _apply_single_annotation( + self, + schema: core_schema.CoreSchema, + metadata: Any, + check_unsupported_field_info_attributes: bool = True, + ) -> core_schema.CoreSchema: + FieldInfo = import_cached_field_info() + + if isinstance(metadata, FieldInfo): + if ( + check_unsupported_field_info_attributes + # HACK: we don't want to emit the warning for `FieldInfo` subclasses, because FastAPI does weird manipulations + # with its subclasses and their annotations: + and type(metadata) is FieldInfo + ): + for attr, value in (unsupported_attributes := self._get_unsupported_field_info_attributes(metadata)): + warnings.warn( + f'The {attr!r} attribute with value {value!r} was provided to the `Field()` function, ' + f'which has no effect in the context it was used. {attr!r} is field-specific metadata, ' + 'and can only be attached to a model field using `Annotated` metadata or by assignment. ' + 'This may have happened because an `Annotated` type alias using the `type` statement was ' + 'used, or if the `Field()` function was attached to a single member of a union type.', + category=UnsupportedFieldAttributeWarning, + ) + + if ( + metadata.default_factory_takes_validated_data + and self.model_type_stack.get() is None + and 'defaut_factory' not in unsupported_attributes + ): + warnings.warn( + "A 'default_factory' taking validated data as an argument was provided to the `Field()` function, " + 'but no validated data is available in the context it was used.', + category=UnsupportedFieldAttributeWarning, + ) + + for field_metadata in metadata.metadata: + schema = self._apply_single_annotation(schema, field_metadata) + + if metadata.discriminator is not None: + schema = self._apply_discriminator_to_union(schema, metadata.discriminator) + return schema + + if schema['type'] == 'nullable': + # for nullable schemas, metadata is automatically applied to the inner schema + inner = schema.get('schema', core_schema.any_schema()) + inner = self._apply_single_annotation(inner, metadata) + if inner: + schema['schema'] = inner + return schema + + if schema['type'] == 'union' and any( + choice['type'] == 'missing-sentinel' for choice in core_schema.iter_union_choices(schema) + ): + # Same behavior as for nullable schemas. This is a bit gross, but we have to support the same pattern + filtered_choices = [ + choice + for choice in schema['choices'] + if (choice[0] if isinstance(choice, tuple) else choice)['type'] != 'missing-sentinel' + ] + if len(filtered_choices) >= 2: + # e.g. `Annotated[int | str | MISSING, Constraint(...)]`. We apply `Constraint(...)` to `int | str`, + # and create a new union semantically equivalent to `Annotated[int | str, Constraint(...)] | MISSING`: + filtered_union = core_schema.union_schema(filtered_choices) + filtered_union = self._apply_single_annotation(filtered_union, metadata) + new_union = schema.copy() + new_union['choices'] = [ + filtered_union, + next( + choice + for choice in schema['choices'] + if (choice[0] if isinstance(choice, tuple) else choice)['type'] == 'missing-sentinel' + ), + ] + return new_union + elif len(filtered_choices) == 1: + # e.g. `Annotated[int | MISSING, Constraint(...)]`. We apply `Constraint(...)` to `int`, and reconstruct + # a new union preserving the order. + inner = filtered_choices[0][0] if isinstance(filtered_choices[0], tuple) else filtered_choices[0] + inner = self._apply_single_annotation(inner, metadata) + + # Create a new union schema, preserving the order of the union: + new_union = schema.copy() + new_union['choices'] = [ + (inner, choice[1]) + if isinstance(choice, tuple) and choice[0]['type'] != 'missing-sentinel' + else inner + if not isinstance(choice, tuple) and choice['type'] != 'missing-sentinel' + else choice + for choice in schema['choices'] + ] + return new_union + + original_schema = schema + ref = schema.get('ref') + if ref is not None: + schema = schema.copy() + new_ref = ref + f'_{repr(metadata)}' + if (existing := self.defs.get_schema_from_ref(new_ref)) is not None: + return existing + schema['ref'] = new_ref # pyright: ignore[reportGeneralTypeIssues] + elif schema['type'] == 'definition-ref': + ref = schema['schema_ref'] + if (referenced_schema := self.defs.get_schema_from_ref(ref)) is not None: + schema = referenced_schema.copy() + new_ref = ref + f'_{repr(metadata)}' + if (existing := self.defs.get_schema_from_ref(new_ref)) is not None: + return existing + schema['ref'] = new_ref # pyright: ignore[reportGeneralTypeIssues] + + maybe_updated_schema = _known_annotated_metadata.apply_known_metadata(metadata, schema) + + if maybe_updated_schema is not None: + return maybe_updated_schema + return original_schema + + def _apply_single_annotation_json_schema( + self, schema: core_schema.CoreSchema, metadata: Any + ) -> core_schema.CoreSchema: + FieldInfo = import_cached_field_info() + + if isinstance(metadata, FieldInfo): + for field_metadata in metadata.metadata: + schema = self._apply_single_annotation_json_schema(schema, field_metadata) + + pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(metadata) + core_metadata = schema.setdefault('metadata', {}) + update_core_metadata( + core_metadata, pydantic_js_updates=pydantic_js_updates, pydantic_js_extra=pydantic_js_extra + ) + return schema + + def _get_unsupported_field_info_attributes(self, field_info: FieldInfo) -> list[tuple[str, Any]]: + """Get the list of unsupported `FieldInfo` attributes when not directly used in `Annotated` for field annotations.""" + unused_metadata: list[tuple[str, Any]] = [] + for unused_metadata_name, unset_value in UNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES: + if ( + (unused_metadata_value := getattr(field_info, unused_metadata_name)) is not unset_value + # `default` and `default_factory` can still be used with a type adapter, so only include them + # if used with a model-like class: + and ( + unused_metadata_name not in ('default', 'default_factory') + or self.model_type_stack.get() is not None + ) + # Setting `alias` will set `validation/serialization_alias` as well, so we want to avoid duplicate warnings: + and ( + unused_metadata_name not in ('validation_alias', 'serialization_alias') + or 'alias' not in field_info._attributes_set + ) + ): + unused_metadata.append((unused_metadata_name, unused_metadata_value)) + + return unused_metadata + + def _get_wrapped_inner_schema( + self, + get_inner_schema: GetCoreSchemaHandler, + annotation: Any, + pydantic_js_annotation_functions: list[GetJsonSchemaFunction], + check_unsupported_field_info_attributes: bool = False, + ) -> CallbackGetCoreSchemaHandler: + annotation_get_schema: GetCoreSchemaFunction | None = getattr(annotation, '__get_pydantic_core_schema__', None) + + def new_handler(source: Any) -> core_schema.CoreSchema: + if annotation_get_schema is not None: + schema = annotation_get_schema(source, get_inner_schema) + else: + schema = get_inner_schema(source) + schema = self._apply_single_annotation( + schema, + annotation, + check_unsupported_field_info_attributes=check_unsupported_field_info_attributes, + ) + schema = self._apply_single_annotation_json_schema(schema, annotation) + + metadata_js_function = _extract_get_pydantic_json_schema(annotation) + if metadata_js_function is not None: + pydantic_js_annotation_functions.append(metadata_js_function) + return schema + + return CallbackGetCoreSchemaHandler(new_handler, self) + + def _apply_field_serializers( + self, + schema: core_schema.CoreSchema, + serializers: list[Decorator[FieldSerializerDecoratorInfo]], + ) -> core_schema.CoreSchema: + """Apply field serializers to a schema.""" + if serializers: + schema = copy(schema) + if schema['type'] == 'definitions': + inner_schema = schema['schema'] + schema['schema'] = self._apply_field_serializers(inner_schema, serializers) + return schema + elif 'ref' in schema: + schema = self.defs.create_definition_reference_schema(schema) + + # use the last serializer to make it easy to override a serializer set on a parent model + serializer = serializers[-1] + is_field_serializer, info_arg = inspect_field_serializer(serializer.func, serializer.info.mode) + + if serializer.info.return_type is not PydanticUndefined: + return_type = serializer.info.return_type + else: + try: + # Do not pass in globals as the function could be defined in a different module. + # Instead, let `get_callable_return_type` infer the globals to use, but still pass + # in locals that may contain a parent/rebuild namespace: + return_type = _decorators.get_callable_return_type( + serializer.func, localns=self._types_namespace.locals + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + if return_type is PydanticUndefined: + return_schema = None + else: + return_schema = self.generate_schema(return_type) + + if serializer.info.mode == 'wrap': + schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( + serializer.func, + is_field_serializer=is_field_serializer, + info_arg=info_arg, + return_schema=return_schema, + when_used=serializer.info.when_used, + ) + else: + assert serializer.info.mode == 'plain' + schema['serialization'] = core_schema.plain_serializer_function_ser_schema( + serializer.func, + is_field_serializer=is_field_serializer, + info_arg=info_arg, + return_schema=return_schema, + when_used=serializer.info.when_used, + ) + return schema + + def _apply_model_serializers( + self, schema: core_schema.CoreSchema, serializers: Iterable[Decorator[ModelSerializerDecoratorInfo]] + ) -> core_schema.CoreSchema: + """Apply model serializers to a schema.""" + ref: str | None = schema.pop('ref', None) # type: ignore + if serializers: + serializer = list(serializers)[-1] + info_arg = inspect_model_serializer(serializer.func, serializer.info.mode) + + if serializer.info.return_type is not PydanticUndefined: + return_type = serializer.info.return_type + else: + try: + # Do not pass in globals as the function could be defined in a different module. + # Instead, let `get_callable_return_type` infer the globals to use, but still pass + # in locals that may contain a parent/rebuild namespace: + return_type = _decorators.get_callable_return_type( + serializer.func, localns=self._types_namespace.locals + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + if return_type is PydanticUndefined: + return_schema = None + else: + return_schema = self.generate_schema(return_type) + + if serializer.info.mode == 'wrap': + ser_schema: core_schema.SerSchema = core_schema.wrap_serializer_function_ser_schema( + serializer.func, + info_arg=info_arg, + return_schema=return_schema, + when_used=serializer.info.when_used, + ) + else: + # plain + ser_schema = core_schema.plain_serializer_function_ser_schema( + serializer.func, + info_arg=info_arg, + return_schema=return_schema, + when_used=serializer.info.when_used, + ) + schema['serialization'] = ser_schema + if ref: + schema['ref'] = ref # type: ignore + return schema + + +_VALIDATOR_F_MATCH: Mapping[ + tuple[FieldValidatorModes, Literal['no-info', 'with-info']], + Callable[[Callable[..., Any], core_schema.CoreSchema], core_schema.CoreSchema], +] = { + ('before', 'no-info'): lambda f, schema: core_schema.no_info_before_validator_function(f, schema), + ('after', 'no-info'): lambda f, schema: core_schema.no_info_after_validator_function(f, schema), + ('plain', 'no-info'): lambda f, _: core_schema.no_info_plain_validator_function(f), + ('wrap', 'no-info'): lambda f, schema: core_schema.no_info_wrap_validator_function(f, schema), + ('before', 'with-info'): lambda f, schema: core_schema.with_info_before_validator_function(f, schema), + ('after', 'with-info'): lambda f, schema: core_schema.with_info_after_validator_function(f, schema), + ('plain', 'with-info'): lambda f, _: core_schema.with_info_plain_validator_function(f), + ('wrap', 'with-info'): lambda f, schema: core_schema.with_info_wrap_validator_function(f, schema), +} + + +# TODO V3: this function is only used for deprecated decorators. It should +# be removed once we drop support for those. +def apply_validators( + schema: core_schema.CoreSchema, + validators: Iterable[Decorator[RootValidatorDecoratorInfo]] + | Iterable[Decorator[ValidatorDecoratorInfo]] + | Iterable[Decorator[FieldValidatorDecoratorInfo]], +) -> core_schema.CoreSchema: + """Apply validators to a schema. + + Args: + schema: The schema to apply validators on. + validators: An iterable of validators. + field_name: The name of the field if validators are being applied to a model field. + + Returns: + The updated schema. + """ + for validator in validators: + # Actually, type could be 'field' or 'model', but this is only used for deprecated + # decorators, so let's not worry about it. + info_arg = inspect_validator(validator.func, mode=validator.info.mode, type='field') + val_type = 'with-info' if info_arg else 'no-info' + + schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema) + return schema + + +def _validators_require_validate_default(validators: Iterable[Decorator[ValidatorDecoratorInfo]]) -> bool: + """In v1, if any of the validators for a field had `always=True`, the default value would be validated. + + This serves as an auxiliary function for re-implementing that logic, by looping over a provided + collection of (v1-style) ValidatorDecoratorInfo's and checking if any of them have `always=True`. + + We should be able to drop this function and the associated logic calling it once we drop support + for v1-style validator decorators. (Or we can extend it and keep it if we add something equivalent + to the v1-validator `always` kwarg to `field_validator`.) + """ + for validator in validators: + if validator.info.always: + return True + return False + + +def _convert_to_aliases( + alias: str | AliasChoices | AliasPath | None, +) -> str | list[str | int] | list[list[str | int]] | None: + if isinstance(alias, (AliasChoices, AliasPath)): + return alias.convert_to_aliases() + else: + return alias + + +def apply_model_validators( + schema: core_schema.CoreSchema, + validators: Iterable[Decorator[ModelValidatorDecoratorInfo]], + mode: Literal['inner', 'outer', 'all'], +) -> core_schema.CoreSchema: + """Apply model validators to a schema. + + If mode == 'inner', only "before" validators are applied + If mode == 'outer', validators other than "before" are applied + If mode == 'all', all validators are applied + + Args: + schema: The schema to apply validators on. + validators: An iterable of validators. + mode: The validator mode. + + Returns: + The updated schema. + """ + ref: str | None = schema.pop('ref', None) # type: ignore + for validator in validators: + if mode == 'inner' and validator.info.mode != 'before': + continue + if mode == 'outer' and validator.info.mode == 'before': + continue + info_arg = inspect_validator(validator.func, mode=validator.info.mode, type='model') + if validator.info.mode == 'wrap': + if info_arg: + schema = core_schema.with_info_wrap_validator_function(function=validator.func, schema=schema) + else: + schema = core_schema.no_info_wrap_validator_function(function=validator.func, schema=schema) + elif validator.info.mode == 'before': + if info_arg: + schema = core_schema.with_info_before_validator_function(function=validator.func, schema=schema) + else: + schema = core_schema.no_info_before_validator_function(function=validator.func, schema=schema) + else: + assert validator.info.mode == 'after' + if info_arg: + schema = core_schema.with_info_after_validator_function(function=validator.func, schema=schema) + else: + schema = core_schema.no_info_after_validator_function(function=validator.func, schema=schema) + if ref: + schema['ref'] = ref # type: ignore + return schema + + +def wrap_default(field_info: FieldInfo, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + """Wrap schema with default schema if default value or `default_factory` are available. + + Args: + field_info: The field info object. + schema: The schema to apply default on. + + Returns: + Updated schema by default value or `default_factory`. + """ + if field_info.default_factory: + return core_schema.with_default_schema( + schema, + default_factory=field_info.default_factory, + default_factory_takes_data=takes_validated_data_argument(field_info.default_factory), + validate_default=field_info.validate_default, + ) + elif field_info.default is not PydanticUndefined: + return core_schema.with_default_schema( + schema, default=field_info.default, validate_default=field_info.validate_default + ) + else: + return schema + + +def _extract_get_pydantic_json_schema(tp: Any) -> GetJsonSchemaFunction | None: + """Extract `__get_pydantic_json_schema__` from a type, handling the deprecated `__modify_schema__`.""" + js_modify_function = getattr(tp, '__get_pydantic_json_schema__', None) + + if hasattr(tp, '__modify_schema__'): + BaseModel = import_cached_base_model() + + has_custom_v2_modify_js_func = ( + js_modify_function is not None + and BaseModel.__get_pydantic_json_schema__.__func__ # type: ignore + not in (js_modify_function, getattr(js_modify_function, '__func__', None)) + ) + + if not has_custom_v2_modify_js_func: + cls_name = getattr(tp, '__name__', None) + raise PydanticUserError( + f'The `__modify_schema__` method is not supported in Pydantic v2. ' + f'Use `__get_pydantic_json_schema__` instead{f" in class `{cls_name}`" if cls_name else ""}.', + code='custom-json-schema', + ) + + if (origin := get_origin(tp)) is not None: + # Generic aliases proxy attribute access to the origin, *except* dunder attributes, + # such as `__get_pydantic_json_schema__`, hence the explicit check. + return _extract_get_pydantic_json_schema(origin) + + if js_modify_function is None: + return None + + return js_modify_function + + +def resolve_original_schema(schema: CoreSchema, definitions: _Definitions) -> CoreSchema | None: + if schema['type'] == 'definition-ref': + return definitions.get_schema_from_ref(schema['schema_ref']) + elif schema['type'] == 'definitions': + return schema['schema'] + else: + return schema + + +def _inlining_behavior( + def_ref: core_schema.DefinitionReferenceSchema, +) -> Literal['inline', 'keep', 'preserve_metadata']: + """Determine the inlining behavior of the `'definition-ref'` schema. + + - If no `'serialization'` schema and no metadata is attached, the schema can safely be inlined. + - If it has metadata but only related to the deferred discriminator application, it can be inlined + provided that such metadata is kept. + - Otherwise, the schema should not be inlined. Doing so would remove the `'serialization'` schema or metadata. + """ + if 'serialization' in def_ref: + return 'keep' + metadata = def_ref.get('metadata') + if not metadata: + return 'inline' + if len(metadata) == 1 and 'pydantic_internal_union_discriminator' in metadata: + return 'preserve_metadata' + return 'keep' + + +class _Definitions: + """Keeps track of references and definitions.""" + + _recursively_seen: set[str] + """A set of recursively seen references. + + When a referenceable type is encountered, the `get_schema_or_ref` context manager is + entered to compute the reference. If the type references itself by some way (e.g. for + a dataclass a Pydantic model, the class can be referenced as a field annotation), + entering the context manager again will yield a `'definition-ref'` schema that should + short-circuit the normal generation process, as the reference was already in this set. + """ + + _definitions: dict[str, core_schema.CoreSchema] + """A mapping of references to their corresponding schema. + + When a schema for a referenceable type is generated, it is stored in this mapping. If the + same type is encountered again, the reference is yielded by the `get_schema_or_ref` context + manager. + """ + + def __init__(self) -> None: + self._recursively_seen = set() + self._definitions = {} + + @contextmanager + def get_schema_or_ref(self, tp: Any, /) -> Generator[tuple[str, core_schema.DefinitionReferenceSchema | None]]: + """Get a definition for `tp` if one exists. + + If a definition exists, a tuple of `(ref_string, CoreSchema)` is returned. + If no definition exists yet, a tuple of `(ref_string, None)` is returned. + + Note that the returned `CoreSchema` will always be a `DefinitionReferenceSchema`, + not the actual definition itself. + + This should be called for any type that can be identified by reference. + This includes any recursive types. + + At present the following types can be named/recursive: + + - Pydantic model + - Pydantic and stdlib dataclasses + - Typed dictionaries + - Named tuples + - `TypeAliasType` instances + - Enums + """ + ref = get_type_ref(tp) + # return the reference if we're either (1) in a cycle or (2) it the reference was already encountered: + if ref in self._recursively_seen or ref in self._definitions: + yield (ref, core_schema.definition_reference_schema(ref)) + else: + self._recursively_seen.add(ref) + try: + yield (ref, None) + finally: + self._recursively_seen.discard(ref) + + def get_schema_from_ref(self, ref: str) -> CoreSchema | None: + """Resolve the schema from the given reference.""" + return self._definitions.get(ref) + + def create_definition_reference_schema(self, schema: CoreSchema) -> core_schema.DefinitionReferenceSchema: + """Store the schema as a definition and return a `'definition-reference'` schema pointing to it. + + The schema must have a reference attached to it. + """ + ref = schema['ref'] # pyright: ignore + self._definitions[ref] = schema + return core_schema.definition_reference_schema(ref) + + def unpack_definitions(self, schema: core_schema.DefinitionsSchema) -> CoreSchema: + """Store the definitions of the `'definitions'` core schema and return the inner core schema.""" + for def_schema in schema['definitions']: + self._definitions[def_schema['ref']] = def_schema # pyright: ignore + return schema['schema'] + + def finalize_schema(self, schema: CoreSchema) -> CoreSchema: + """Finalize the core schema. + + This traverses the core schema and referenced definitions, replaces `'definition-ref'` schemas + by the referenced definition if possible, and applies deferred discriminators. + """ + definitions = self._definitions + try: + gather_result = gather_schemas_for_cleaning( + schema, + definitions=definitions, + ) + except MissingDefinitionError as e: + raise InvalidSchemaError from e + + remaining_defs: dict[str, CoreSchema] = {} + + # Note: this logic doesn't play well when core schemas with deferred discriminator metadata + # and references are encountered. See the `test_deferred_discriminated_union_and_references()` test. + for ref, inlinable_def_ref in gather_result['collected_references'].items(): + if inlinable_def_ref is not None and (inlining_behavior := _inlining_behavior(inlinable_def_ref)) != 'keep': + if inlining_behavior == 'inline': + # `ref` was encountered, and only once: + # - `inlinable_def_ref` is a `'definition-ref'` schema and is guaranteed to be + # the only one. Transform it into the definition it points to. + # - Do not store the definition in the `remaining_defs`. + inlinable_def_ref.clear() # pyright: ignore[reportAttributeAccessIssue] + inlinable_def_ref.update(self._resolve_definition(ref, definitions)) # pyright: ignore + elif inlining_behavior == 'preserve_metadata': + # `ref` was encountered, and only once, but contains discriminator metadata. + # We will do the same thing as if `inlining_behavior` was `'inline'`, but make + # sure to keep the metadata for the deferred discriminator application logic below. + meta = inlinable_def_ref.pop('metadata') + inlinable_def_ref.clear() # pyright: ignore[reportAttributeAccessIssue] + inlinable_def_ref.update(self._resolve_definition(ref, definitions)) # pyright: ignore + inlinable_def_ref['metadata'] = meta + else: + # `ref` was encountered, at least two times (or only once, but with metadata or a serialization schema): + # - Do not inline the `'definition-ref'` schemas (they are not provided in the gather result anyway). + # - Store the definition in the `remaining_defs` + remaining_defs[ref] = self._resolve_definition(ref, definitions) + + for cs in gather_result['deferred_discriminator_schemas']: + discriminator: str | None = cs['metadata'].pop('pydantic_internal_union_discriminator', None) # pyright: ignore[reportTypedDictNotRequiredAccess] + if discriminator is None: + # This can happen in rare scenarios, when a deferred schema is present multiple times in the + # gather result (e.g. when using the `Sequence` type -- see `test_sequence_discriminated_union()`). + # In this case, a previous loop iteration applied the discriminator and so we can just skip it here. + continue + applied = _discriminated_union.apply_discriminator(cs.copy(), discriminator, remaining_defs) + # Mutate the schema directly to have the discriminator applied + cs.clear() # pyright: ignore[reportAttributeAccessIssue] + cs.update(applied) # pyright: ignore + + if remaining_defs: + schema = core_schema.definitions_schema(schema=schema, definitions=[*remaining_defs.values()]) + return schema + + def _resolve_definition(self, ref: str, definitions: dict[str, CoreSchema]) -> CoreSchema: + definition = definitions[ref] + if definition['type'] != 'definition-ref': + return definition + + # Some `'definition-ref'` schemas might act as "intermediate" references (e.g. when using + # a PEP 695 type alias (which is referenceable) that references another PEP 695 type alias): + visited: set[str] = set() + while definition['type'] == 'definition-ref' and _inlining_behavior(definition) == 'inline': + schema_ref = definition['schema_ref'] + if schema_ref in visited: + raise PydanticUserError( + f'{ref} contains a circular reference to itself.', code='circular-reference-schema' + ) + visited.add(schema_ref) + definition = definitions[schema_ref] + return {**definition, 'ref': ref} # pyright: ignore[reportReturnType] + + +class _FieldNameStack: + __slots__ = ('_stack',) + + def __init__(self) -> None: + self._stack: list[str] = [] + + @contextmanager + def push(self, field_name: str) -> Iterator[None]: + self._stack.append(field_name) + yield + self._stack.pop() + + def get(self) -> str | None: + if self._stack: + return self._stack[-1] + else: + return None + + +class _ModelTypeStack: + __slots__ = ('_stack',) + + def __init__(self) -> None: + self._stack: list[type] = [] + + @contextmanager + def push(self, type_obj: type) -> Iterator[None]: + self._stack.append(type_obj) + yield + self._stack.pop() + + def get(self) -> type | None: + if self._stack: + return self._stack[-1] + else: + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py new file mode 100644 index 0000000..f3e927c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py @@ -0,0 +1,530 @@ +from __future__ import annotations + +import operator +import sys +import types +import typing +from collections import ChainMap +from collections.abc import Iterator, Mapping +from contextlib import contextmanager +from contextvars import ContextVar +from functools import reduce +from itertools import zip_longest +from types import prepare_class +from typing import TYPE_CHECKING, Annotated, Any, TypedDict, TypeVar, cast +from weakref import WeakValueDictionary + +import typing_extensions +from typing_inspection import typing_objects +from typing_inspection.introspection import is_union_origin + +from . import _typing_extra +from ._core_utils import get_type_ref +from ._forward_ref import PydanticRecursiveRef +from ._utils import all_identical, is_model_class + +if TYPE_CHECKING: + from ..main import BaseModel + +GenericTypesCacheKey = tuple[Any, Any, tuple[Any, ...]] + +# Note: We want to remove LimitedDict, but to do this, we'd need to improve the handling of generics caching. +# Right now, to handle recursive generics, we some types must remain cached for brief periods without references. +# By chaining the WeakValuesDict with a LimitedDict, we have a way to retain caching for all types with references, +# while also retaining a limited number of types even without references. This is generally enough to build +# specific recursive generic models without losing required items out of the cache. + +KT = TypeVar('KT') +VT = TypeVar('VT') +_LIMITED_DICT_SIZE = 100 + + +class LimitedDict(dict[KT, VT]): + def __init__(self, size_limit: int = _LIMITED_DICT_SIZE) -> None: + self.size_limit = size_limit + super().__init__() + + def __setitem__(self, key: KT, value: VT, /) -> None: + super().__setitem__(key, value) + if len(self) > self.size_limit: + excess = len(self) - self.size_limit + self.size_limit // 10 + to_remove = list(self.keys())[:excess] + for k in to_remove: + del self[k] + + +# weak dictionaries allow the dynamically created parametrized versions of generic models to get collected +# once they are no longer referenced by the caller. +GenericTypesCache = WeakValueDictionary[GenericTypesCacheKey, 'type[BaseModel]'] + +if TYPE_CHECKING: + + class DeepChainMap(ChainMap[KT, VT]): # type: ignore + ... + +else: + + class DeepChainMap(ChainMap): + """Variant of ChainMap that allows direct updates to inner scopes. + + Taken from https://docs.python.org/3/library/collections.html#collections.ChainMap, + with some light modifications for this use case. + """ + + def clear(self) -> None: + for mapping in self.maps: + mapping.clear() + + def __setitem__(self, key: KT, value: VT) -> None: + for mapping in self.maps: + mapping[key] = value + + def __delitem__(self, key: KT) -> None: + hit = False + for mapping in self.maps: + if key in mapping: + del mapping[key] + hit = True + if not hit: + raise KeyError(key) + + +# Despite the fact that LimitedDict _seems_ no longer necessary, I'm very nervous to actually remove it +# and discover later on that we need to re-add all this infrastructure... +# _GENERIC_TYPES_CACHE = DeepChainMap(GenericTypesCache(), LimitedDict()) + +_GENERIC_TYPES_CACHE = GenericTypesCache() + + +class PydanticGenericMetadata(TypedDict): + origin: type[BaseModel] | None # analogous to typing._GenericAlias.__origin__ + args: tuple[Any, ...] # analogous to typing._GenericAlias.__args__ + parameters: tuple[TypeVar, ...] # analogous to typing.Generic.__parameters__ + + +def create_generic_submodel( + model_name: str, origin: type[BaseModel], args: tuple[Any, ...], params: tuple[Any, ...] +) -> type[BaseModel]: + """Dynamically create a submodel of a provided (generic) BaseModel. + + This is used when producing concrete parametrizations of generic models. This function + only *creates* the new subclass; the schema/validators/serialization must be updated to + reflect a concrete parametrization elsewhere. + + Args: + model_name: The name of the newly created model. + origin: The base class for the new model to inherit from. + args: A tuple of generic metadata arguments. + params: A tuple of generic metadata parameters. + + Returns: + The created submodel. + """ + namespace: dict[str, Any] = {'__module__': origin.__module__} + bases = (origin,) + meta, ns, kwds = prepare_class(model_name, bases) + namespace.update(ns) + created_model = meta( + model_name, + bases, + namespace, + __pydantic_generic_metadata__={ + 'origin': origin, + 'args': args, + 'parameters': params, + }, + __pydantic_reset_parent_namespace__=False, + **kwds, + ) + + model_module, called_globally = _get_caller_frame_info(depth=3) + if called_globally: # create global reference and therefore allow pickling + object_by_reference = None + reference_name = model_name + reference_module_globals = sys.modules[created_model.__module__].__dict__ + while object_by_reference is not created_model: + object_by_reference = reference_module_globals.setdefault(reference_name, created_model) + reference_name += '_' + + return created_model + + +def _get_caller_frame_info(depth: int = 2) -> tuple[str | None, bool]: + """Used inside a function to check whether it was called globally. + + Args: + depth: The depth to get the frame. + + Returns: + A tuple contains `module_name` and `called_globally`. + + Raises: + RuntimeError: If the function is not called inside a function. + """ + try: + previous_caller_frame = sys._getframe(depth) + except ValueError as e: + raise RuntimeError('This function must be used inside another function') from e + except AttributeError: # sys module does not have _getframe function, so there's nothing we can do about it + return None, False + frame_globals = previous_caller_frame.f_globals + return frame_globals.get('__name__'), previous_caller_frame.f_locals is frame_globals + + +DictValues: type[Any] = {}.values().__class__ + + +def iter_contained_typevars(v: Any) -> Iterator[TypeVar]: + """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found. + + This is inspired as an alternative to directly accessing the `__parameters__` attribute of a GenericAlias, + since __parameters__ of (nested) generic BaseModel subclasses won't show up in that list. + """ + if isinstance(v, TypeVar): + yield v + elif is_model_class(v): + yield from v.__pydantic_generic_metadata__['parameters'] + elif isinstance(v, (DictValues, list)): + for var in v: + yield from iter_contained_typevars(var) + else: + args = get_args(v) + for arg in args: + yield from iter_contained_typevars(arg) + + +def get_args(v: Any) -> Any: + pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None) + if pydantic_generic_metadata: + return pydantic_generic_metadata.get('args') + return typing_extensions.get_args(v) + + +def get_origin(v: Any) -> Any: + pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None) + if pydantic_generic_metadata: + return pydantic_generic_metadata.get('origin') + return typing_extensions.get_origin(v) + + +def get_standard_typevars_map(cls: Any) -> dict[TypeVar, Any] | None: + """Package a generic type's typevars and parametrization (if present) into a dictionary compatible with the + `replace_types` function. Specifically, this works with standard typing generics and typing._GenericAlias. + """ + origin = get_origin(cls) + if origin is None: + return None + if not hasattr(origin, '__parameters__'): + return None + + # In this case, we know that cls is a _GenericAlias, and origin is the generic type + # So it is safe to access cls.__args__ and origin.__parameters__ + args: tuple[Any, ...] = cls.__args__ # type: ignore + parameters: tuple[TypeVar, ...] = origin.__parameters__ + return dict(zip(parameters, args)) + + +def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVar, Any]: + """Package a generic BaseModel's typevars and concrete parametrization (if present) into a dictionary compatible + with the `replace_types` function. + + Since BaseModel.__class_getitem__ does not produce a typing._GenericAlias, and the BaseModel generic info is + stored in the __pydantic_generic_metadata__ attribute, we need special handling here. + """ + # TODO: This could be unified with `get_standard_typevars_map` if we stored the generic metadata + # in the __origin__, __args__, and __parameters__ attributes of the model. + generic_metadata = cls.__pydantic_generic_metadata__ + origin = generic_metadata['origin'] + args = generic_metadata['args'] + if not args: + # No need to go into `iter_contained_typevars`: + return {} + return dict(zip(iter_contained_typevars(origin), args)) + + +def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: + """Return type with all occurrences of `type_map` keys recursively replaced with their values. + + Args: + type_: The class or generic alias. + type_map: Mapping from `TypeVar` instance to concrete types. + + Returns: + A new type representing the basic structure of `type_` with all + `typevar_map` keys recursively replaced. + + Example: + ```python + from typing import Union + + from pydantic._internal._generics import replace_types + + replace_types(tuple[str, Union[list[str], float]], {str: int}) + #> tuple[int, Union[list[int], float]] + ``` + """ + if not type_map: + return type_ + + type_args = get_args(type_) + origin_type = get_origin(type_) + + if typing_objects.is_annotated(origin_type): + annotated_type, *annotations = type_args + annotated_type = replace_types(annotated_type, type_map) + # TODO remove parentheses when we drop support for Python 3.10: + return Annotated[(annotated_type, *annotations)] + + # Having type args is a good indicator that this is a typing special form + # instance or a generic alias of some sort. + if type_args: + resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args) + if all_identical(type_args, resolved_type_args): + # If all arguments are the same, there is no need to modify the + # type or create a new object at all + return type_ + + if ( + origin_type is not None + and isinstance(type_, _typing_extra.typing_base) + and not isinstance(origin_type, _typing_extra.typing_base) + and getattr(type_, '_name', None) is not None + ): + # In python < 3.9 generic aliases don't exist so any of these like `list`, + # `type` or `collections.abc.Callable` need to be translated. + # See: https://www.python.org/dev/peps/pep-0585 + origin_type = getattr(typing, type_._name) + assert origin_type is not None + + if is_union_origin(origin_type): + if any(typing_objects.is_any(arg) for arg in resolved_type_args): + # `Any | T` ~ `Any`: + resolved_type_args = (Any,) + # `Never | T` ~ `T`: + resolved_type_args = tuple( + arg + for arg in resolved_type_args + if not (typing_objects.is_noreturn(arg) or typing_objects.is_never(arg)) + ) + + # PEP-604 syntax (e.g. `list | str`) is represented with a types.UnionType object that does not + # implement `__getitem__()`. In Python 3.14+, `typing.Union` and `types.UnionType` are the same, + # and we instead rely on `typing.Union` as it implicitly converts string annotations to `ForwardRef` + # instances (this is to avoid type errors as per https://github.com/python/cpython/pull/105366). + # TODO remove type ignore comment when we drop support for Python 3.9 (https://github.com/microsoft/pyright/issues/11241): + if (3, 10) <= sys.version_info < (3, 14) and origin_type is types.UnionType: # pyright: ignore[reportAttributeAccessIssue] + return reduce(operator.or_, resolved_type_args) + # NotRequired[T] and Required[T] don't support tuple type resolved_type_args, hence the condition below + return origin_type[resolved_type_args[0] if len(resolved_type_args) == 1 else resolved_type_args] + + # We handle pydantic generic models separately as they don't have the same + # semantics as "typing" classes or generic aliases + + if not origin_type and is_model_class(type_): + parameters = type_.__pydantic_generic_metadata__['parameters'] + if not parameters: + return type_ + resolved_type_args = tuple(replace_types(t, type_map) for t in parameters) + if all_identical(parameters, resolved_type_args): + return type_ + return type_[resolved_type_args] + + # Handle special case for typehints that can have lists as arguments. + # `typing.Callable[[int, str], int]` is an example for this. + if isinstance(type_, list): + resolved_list = [replace_types(element, type_map) for element in type_] + if all_identical(type_, resolved_list): + return type_ + return resolved_list + + # If all else fails, we try to resolve the type directly and otherwise just + # return the input with no modifications. + return type_map.get(type_, type_) + + +def map_generic_model_arguments(cls: type[BaseModel], args: tuple[Any, ...]) -> dict[TypeVar, Any]: + """Return a mapping between the parameters of a generic model and the provided arguments during parameterization. + + Raises: + TypeError: If the number of arguments does not match the parameters (i.e. if providing too few or too many arguments). + + Example: + ```python {test="skip" lint="skip"} + class Model[T, U, V = int](BaseModel): ... + + map_generic_model_arguments(Model, (str, bytes)) + #> {T: str, U: bytes, V: int} + + map_generic_model_arguments(Model, (str,)) + #> TypeError: Too few arguments for ; actual 1, expected at least 2 + + map_generic_model_arguments(Model, (str, bytes, int, complex)) + #> TypeError: Too many arguments for ; actual 4, expected 3 + ``` + + Note: + This function is analogous to the private `typing._check_generic_specialization` function. + """ + parameters = cls.__pydantic_generic_metadata__['parameters'] + expected_len = len(parameters) + typevars_map: dict[TypeVar, Any] = {} + + _missing = object() + for parameter, argument in zip_longest(parameters, args, fillvalue=_missing): + if parameter is _missing: + raise TypeError(f'Too many arguments for {cls}; actual {len(args)}, expected {expected_len}') + + if argument is _missing: + param = cast(TypeVar, parameter) + try: + has_default = param.has_default() # pyright: ignore[reportAttributeAccessIssue] + except AttributeError: + # Happens if using `typing.TypeVar` (and not `typing_extensions`) on Python < 3.13. + has_default = False + if has_default: + # The default might refer to other type parameters. For an example, see: + # https://typing.python.org/en/latest/spec/generics.html#type-parameters-as-parameters-to-generics + typevars_map[param] = replace_types(param.__default__, typevars_map) # pyright: ignore[reportAttributeAccessIssue] + else: + expected_len -= sum(hasattr(p, 'has_default') and p.has_default() for p in parameters) # pyright: ignore[reportAttributeAccessIssue] + raise TypeError(f'Too few arguments for {cls}; actual {len(args)}, expected at least {expected_len}') + else: + param = cast(TypeVar, parameter) + typevars_map[param] = argument + + return typevars_map + + +_generic_recursion_cache: ContextVar[set[str] | None] = ContextVar('_generic_recursion_cache', default=None) + + +@contextmanager +def generic_recursion_self_type( + origin: type[BaseModel], args: tuple[Any, ...] +) -> Iterator[PydanticRecursiveRef | None]: + """This contextmanager should be placed around the recursive calls used to build a generic type, + and accept as arguments the generic origin type and the type arguments being passed to it. + + If the same origin and arguments are observed twice, it implies that a self-reference placeholder + can be used while building the core schema, and will produce a schema_ref that will be valid in the + final parent schema. + """ + previously_seen_type_refs = _generic_recursion_cache.get() + if previously_seen_type_refs is None: + previously_seen_type_refs = set() + token = _generic_recursion_cache.set(previously_seen_type_refs) + else: + token = None + + try: + type_ref = get_type_ref(origin, args_override=args) + if type_ref in previously_seen_type_refs: + self_type = PydanticRecursiveRef(type_ref=type_ref) + yield self_type + else: + previously_seen_type_refs.add(type_ref) + yield + previously_seen_type_refs.remove(type_ref) + finally: + if token: + _generic_recursion_cache.reset(token) + + +def recursively_defined_type_refs() -> set[str]: + visited = _generic_recursion_cache.get() + if not visited: + return set() # not in a generic recursion, so there are no types + + return visited.copy() # don't allow modifications + + +def get_cached_generic_type_early(parent: type[BaseModel], typevar_values: Any) -> type[BaseModel] | None: + """The use of a two-stage cache lookup approach was necessary to have the highest performance possible for + repeated calls to `__class_getitem__` on generic types (which may happen in tighter loops during runtime), + while still ensuring that certain alternative parametrizations ultimately resolve to the same type. + + As a concrete example, this approach was necessary to make Model[List[T]][int] equal to Model[List[int]]. + The approach could be modified to not use two different cache keys at different points, but the + _early_cache_key is optimized to be as quick to compute as possible (for repeated-access speed), and the + _late_cache_key is optimized to be as "correct" as possible, so that two types that will ultimately be the + same after resolving the type arguments will always produce cache hits. + + If we wanted to move to only using a single cache key per type, we would either need to always use the + slower/more computationally intensive logic associated with _late_cache_key, or would need to accept + that Model[List[T]][int] is a different type than Model[List[T]][int]. Because we rely on subclass relationships + during validation, I think it is worthwhile to ensure that types that are functionally equivalent are actually + equal. + """ + return _GENERIC_TYPES_CACHE.get(_early_cache_key(parent, typevar_values)) + + +def get_cached_generic_type_late( + parent: type[BaseModel], typevar_values: Any, origin: type[BaseModel], args: tuple[Any, ...] +) -> type[BaseModel] | None: + """See the docstring of `get_cached_generic_type_early` for more information about the two-stage cache lookup.""" + cached = _GENERIC_TYPES_CACHE.get(_late_cache_key(origin, args, typevar_values)) + if cached is not None: + set_cached_generic_type(parent, typevar_values, cached, origin, args) + return cached + + +def set_cached_generic_type( + parent: type[BaseModel], + typevar_values: tuple[Any, ...], + type_: type[BaseModel], + origin: type[BaseModel] | None = None, + args: tuple[Any, ...] | None = None, +) -> None: + """See the docstring of `get_cached_generic_type_early` for more information about why items are cached with + two different keys. + """ + _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values)] = type_ + if len(typevar_values) == 1: + _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values[0])] = type_ + if origin and args: + _GENERIC_TYPES_CACHE[_late_cache_key(origin, args, typevar_values)] = type_ + + +def _union_orderings_key(typevar_values: Any) -> Any: + """This is intended to help differentiate between Union types with the same arguments in different order. + + Thanks to caching internal to the `typing` module, it is not possible to distinguish between + List[Union[int, float]] and List[Union[float, int]] (and similarly for other "parent" origins besides List) + because `typing` considers Union[int, float] to be equal to Union[float, int]. + + However, you _can_ distinguish between (top-level) Union[int, float] vs. Union[float, int]. + Because we parse items as the first Union type that is successful, we get slightly more consistent behavior + if we make an effort to distinguish the ordering of items in a union. It would be best if we could _always_ + get the exact-correct order of items in the union, but that would require a change to the `typing` module itself. + (See https://github.com/python/cpython/issues/86483 for reference.) + """ + if isinstance(typevar_values, tuple): + return tuple(_union_orderings_key(value) for value in typevar_values) + elif typing_objects.is_union(typing_extensions.get_origin(typevar_values)): + return get_args(typevar_values) + else: + return () + + +def _early_cache_key(cls: type[BaseModel], typevar_values: Any) -> GenericTypesCacheKey: + """This is intended for minimal computational overhead during lookups of cached types. + + Note that this is overly simplistic, and it's possible that two different cls/typevar_values + inputs would ultimately result in the same type being created in BaseModel.__class_getitem__. + To handle this, we have a fallback _late_cache_key that is checked later if the _early_cache_key + lookup fails, and should result in a cache hit _precisely_ when the inputs to __class_getitem__ + would result in the same type. + """ + return cls, typevar_values, _union_orderings_key(typevar_values) + + +def _late_cache_key(origin: type[BaseModel], args: tuple[Any, ...], typevar_values: Any) -> GenericTypesCacheKey: + """This is intended for use later in the process of creating a new type, when we have more information + about the exact args that will be passed. If it turns out that a different set of inputs to + __class_getitem__ resulted in the same inputs to the generic type creation process, we can still + return the cached type, and update the cache with the _early_cache_key as well. + """ + # The _union_orderings_key is placed at the start here to ensure there cannot be a collision with an + # _early_cache_key, as that function will always produce a BaseModel subclass as the first item in the key, + # whereas this function will always produce a tuple as the first item in the key. + return _union_orderings_key(typevar_values), origin, args diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py new file mode 100644 index 0000000..96dcda2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py @@ -0,0 +1,27 @@ +"""Git utilities, adopted from mypy's git utilities (https://github.com/python/mypy/blob/master/mypy/git.py).""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + + +def is_git_repo(dir: Path) -> bool: + """Is the given directory version-controlled with git?""" + return dir.joinpath('.git').exists() + + +def have_git() -> bool: # pragma: no cover + """Can we run the git executable?""" + try: + subprocess.check_output(['git', '--help']) + return True + except subprocess.CalledProcessError: + return False + except OSError: + return False + + +def git_revision(dir: Path) -> str: + """Get the SHA-1 of the HEAD of a git repository.""" + return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=dir).decode('utf-8').strip() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py new file mode 100644 index 0000000..638102f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py @@ -0,0 +1,20 @@ +from functools import cache +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pydantic import BaseModel + from pydantic.fields import FieldInfo + + +@cache +def import_cached_base_model() -> type['BaseModel']: + from pydantic import BaseModel + + return BaseModel + + +@cache +def import_cached_field_info() -> type['FieldInfo']: + from pydantic.fields import FieldInfo + + return FieldInfo diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py new file mode 100644 index 0000000..33e152c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py @@ -0,0 +1,7 @@ +import sys + +# `slots` is available on Python >= 3.10 +if sys.version_info >= (3, 10): + slots_true = {'slots': True} +else: + slots_true = {} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py new file mode 100644 index 0000000..5954dba --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py @@ -0,0 +1,403 @@ +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Iterable +from copy import copy +from functools import lru_cache, partial +from typing import TYPE_CHECKING, Any + +from pydantic_core import CoreSchema, PydanticCustomError, ValidationError, to_jsonable_python +from pydantic_core import core_schema as cs + +from ._fields import PydanticMetadata +from ._import_utils import import_cached_field_info + +if TYPE_CHECKING: + pass + +STRICT = {'strict'} +FAIL_FAST = {'fail_fast'} +LENGTH_CONSTRAINTS = {'min_length', 'max_length'} +INEQUALITY = {'le', 'ge', 'lt', 'gt'} +NUMERIC_CONSTRAINTS = {'multiple_of', *INEQUALITY} +ALLOW_INF_NAN = {'allow_inf_nan'} + +STR_CONSTRAINTS = { + *LENGTH_CONSTRAINTS, + *STRICT, + 'strip_whitespace', + 'to_lower', + 'to_upper', + 'pattern', + 'coerce_numbers_to_str', + 'ascii_only', +} +BYTES_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} + +LIST_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} +TUPLE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} +SET_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} +DICT_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} +GENERATOR_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} +SEQUENCE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *FAIL_FAST} + +FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT} +DECIMAL_CONSTRAINTS = {'max_digits', 'decimal_places', *FLOAT_CONSTRAINTS} +INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT} +BOOL_CONSTRAINTS = STRICT +UUID_CONSTRAINTS = STRICT + +DATE_TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} +TIMEDELTA_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} +TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} +LAX_OR_STRICT_CONSTRAINTS = STRICT +ENUM_CONSTRAINTS = STRICT +COMPLEX_CONSTRAINTS = STRICT + +UNION_CONSTRAINTS = {'union_mode'} +URL_CONSTRAINTS = { + 'max_length', + 'allowed_schemes', + 'host_required', + 'default_host', + 'default_port', + 'default_path', +} + +TEXT_SCHEMA_TYPES = ('str', 'bytes', 'url', 'multi-host-url') +SEQUENCE_SCHEMA_TYPES = ('list', 'tuple', 'set', 'frozenset', 'generator', *TEXT_SCHEMA_TYPES) +NUMERIC_SCHEMA_TYPES = ('float', 'int', 'date', 'time', 'timedelta', 'datetime') + +CONSTRAINTS_TO_ALLOWED_SCHEMAS: dict[str, set[str]] = defaultdict(set) + +constraint_schema_pairings: list[tuple[set[str], tuple[str, ...]]] = [ + (STR_CONSTRAINTS, TEXT_SCHEMA_TYPES), + (BYTES_CONSTRAINTS, ('bytes',)), + (LIST_CONSTRAINTS, ('list',)), + (TUPLE_CONSTRAINTS, ('tuple',)), + (SET_CONSTRAINTS, ('set', 'frozenset')), + (DICT_CONSTRAINTS, ('dict',)), + (GENERATOR_CONSTRAINTS, ('generator',)), + (FLOAT_CONSTRAINTS, ('float',)), + (INT_CONSTRAINTS, ('int',)), + (DATE_TIME_CONSTRAINTS, ('date', 'time', 'datetime', 'timedelta')), + # TODO: this is a bit redundant, we could probably avoid some of these + (STRICT, (*TEXT_SCHEMA_TYPES, *SEQUENCE_SCHEMA_TYPES, *NUMERIC_SCHEMA_TYPES, 'typed-dict', 'model')), + (UNION_CONSTRAINTS, ('union',)), + (URL_CONSTRAINTS, ('url', 'multi-host-url')), + (BOOL_CONSTRAINTS, ('bool',)), + (UUID_CONSTRAINTS, ('uuid',)), + (LAX_OR_STRICT_CONSTRAINTS, ('lax-or-strict',)), + (ENUM_CONSTRAINTS, ('enum',)), + (DECIMAL_CONSTRAINTS, ('decimal',)), + (COMPLEX_CONSTRAINTS, ('complex',)), +] + +for constraints, schemas in constraint_schema_pairings: + for c in constraints: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[c].update(schemas) + + +def as_jsonable_value(v: Any) -> Any: + if type(v) not in (int, str, float, bytes, bool, type(None)): + return to_jsonable_python(v) + return v + + +def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: + """Expand the annotations. + + Args: + annotations: An iterable of annotations. + + Returns: + An iterable of expanded annotations. + + Example: + ```python + from annotated_types import Ge, Len + + from pydantic._internal._known_annotated_metadata import expand_grouped_metadata + + print(list(expand_grouped_metadata([Ge(4), Len(5)]))) + #> [Ge(ge=4), MinLen(min_length=5)] + ``` + """ + import annotated_types as at + + FieldInfo = import_cached_field_info() + + for annotation in annotations: + if isinstance(annotation, at.GroupedMetadata): + yield from annotation + elif isinstance(annotation, FieldInfo): + yield from annotation.metadata + # this is a bit problematic in that it results in duplicate metadata + # all of our "consumers" can handle it, but it is not ideal + # we probably should split up FieldInfo into: + # - annotated types metadata + # - individual metadata known only to Pydantic + annotation = copy(annotation) + annotation.metadata = [] + yield annotation + else: + yield annotation + + +@lru_cache +def _get_at_to_constraint_map() -> dict[type, str]: + """Return a mapping of annotated types to constraints. + + Normally, we would define a mapping like this in the module scope, but we can't do that + because we don't permit module level imports of `annotated_types`, in an attempt to speed up + the import time of `pydantic`. We still only want to have this dictionary defined in one place, + so we use this function to cache the result. + """ + import annotated_types as at + + return { + at.Gt: 'gt', + at.Ge: 'ge', + at.Lt: 'lt', + at.Le: 'le', + at.MultipleOf: 'multiple_of', + at.MinLen: 'min_length', + at.MaxLen: 'max_length', + } + + +def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | None: # noqa: C901 + """Apply `annotation` to `schema` if it is an annotation we know about (Gt, Le, etc.). + Otherwise return `None`. + + This does not handle all known annotations. If / when it does, it can always + return a CoreSchema and return the unmodified schema if the annotation should be ignored. + + Assumes that GroupedMetadata has already been expanded via `expand_grouped_metadata`. + + Args: + annotation: The annotation. + schema: The schema. + + Returns: + An updated schema with annotation if it is an annotation we know about, `None` otherwise. + + Raises: + RuntimeError: If a constraint can't be applied to a specific schema type. + ValueError: If an unknown constraint is encountered. + """ + import annotated_types as at + + from ._validators import NUMERIC_VALIDATOR_LOOKUP, forbid_inf_nan_check + + schema = schema.copy() + schema_update, other_metadata = collect_known_metadata([annotation]) + schema_type = schema['type'] + + chain_schema_constraints: set[str] = { + 'pattern', + 'strip_whitespace', + 'to_lower', + 'to_upper', + 'coerce_numbers_to_str', + 'ascii_only', + } + chain_schema_steps: list[CoreSchema] = [] + + for constraint, value in schema_update.items(): + if constraint not in CONSTRAINTS_TO_ALLOWED_SCHEMAS: + raise ValueError(f'Unknown constraint {constraint}') + allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint] + + # if it becomes necessary to handle more than one constraint + # in this recursive case with function-after or function-wrap, we should refactor + # this is a bit challenging because we sometimes want to apply constraints to the inner schema, + # whereas other times we want to wrap the existing schema with a new one that enforces a new constraint. + if schema_type in {'function-before', 'function-wrap', 'function-after'} and constraint == 'strict': + schema['schema'] = apply_known_metadata(annotation, schema['schema']) # type: ignore # schema is function schema + return schema + + # if we're allowed to apply constraint directly to the schema, like le to int, do that + if schema_type in allowed_schemas: + if constraint == 'union_mode' and schema_type == 'union': + schema['mode'] = value # type: ignore # schema is UnionSchema + else: + schema[constraint] = value + continue + + # else, apply a function after validator to the schema to enforce the corresponding constraint + if constraint in chain_schema_constraints: + + def _apply_constraint_with_incompatibility_info( + value: Any, handler: cs.ValidatorFunctionWrapHandler + ) -> Any: + try: + x = handler(value) + except ValidationError as ve: + # if the error is about the type, it's likely that the constraint is incompatible the type of the field + # for example, the following invalid schema wouldn't be caught during schema build, but rather at this point + # with a cryptic 'string_type' error coming from the string validator, + # that we'd rather express as a constraint incompatibility error (TypeError) + # Annotated[list[int], Field(pattern='abc')] + if 'type' in ve.errors()[0]['type']: + raise TypeError( + f"Unable to apply constraint '{constraint}' to supplied value {value} for schema of type '{schema_type}'" # noqa: B023 + ) + raise ve + return x + + chain_schema_steps.append( + cs.no_info_wrap_validator_function( + _apply_constraint_with_incompatibility_info, cs.str_schema(**{constraint: value}) + ) + ) + elif constraint in NUMERIC_VALIDATOR_LOOKUP: + if constraint in LENGTH_CONSTRAINTS: + inner_schema = schema + while inner_schema['type'] in {'function-before', 'function-wrap', 'function-after'}: + inner_schema = inner_schema['schema'] # type: ignore + inner_schema_type = inner_schema['type'] + if inner_schema_type == 'list' or ( + inner_schema_type == 'json-or-python' and inner_schema['json_schema']['type'] == 'list' # type: ignore + ): + js_constraint_key = 'minItems' if constraint == 'min_length' else 'maxItems' + else: + js_constraint_key = 'minLength' if constraint == 'min_length' else 'maxLength' + else: + js_constraint_key = constraint + + schema = cs.no_info_after_validator_function( + partial(NUMERIC_VALIDATOR_LOOKUP[constraint], **{constraint: value}), schema + ) + metadata = schema.get('metadata', {}) + if (existing_json_schema_updates := metadata.get('pydantic_js_updates')) is not None: + metadata['pydantic_js_updates'] = { + **existing_json_schema_updates, + **{js_constraint_key: as_jsonable_value(value)}, + } + else: + metadata['pydantic_js_updates'] = {js_constraint_key: as_jsonable_value(value)} + schema['metadata'] = metadata + elif constraint == 'allow_inf_nan' and value is False: + schema = cs.no_info_after_validator_function( + forbid_inf_nan_check, + schema, + ) + else: + # It's rare that we'd get here, but it's possible if we add a new constraint and forget to handle it + # Most constraint errors are caught at runtime during attempted application + raise RuntimeError(f"Unable to apply constraint '{constraint}' to schema of type '{schema_type}'") + + for annotation in other_metadata: + if (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()): + constraint = at_to_constraint_map[annotation_type] + validator = NUMERIC_VALIDATOR_LOOKUP.get(constraint) + if validator is None: + raise ValueError(f'Unknown constraint {constraint}') + schema = cs.no_info_after_validator_function( + partial(validator, {constraint: getattr(annotation, constraint)}), schema + ) + continue + elif isinstance(annotation, (at.Predicate, at.Not)): + predicate_name = f'{annotation.func.__qualname__!r} ' if hasattr(annotation.func, '__qualname__') else '' + + # Note: B023 is ignored because even though we iterate over `other_metadata`, it is guaranteed + # to be of length 1. `apply_known_metadata()` is called from `GenerateSchema`, where annotations + # were already expanded via `expand_grouped_metadata()`. Confusing, but this falls into the annotations + # refactor. + if isinstance(annotation, at.Predicate): + + def val_func(v: Any) -> Any: + predicate_satisfied = annotation.func(v) # noqa: B023 + if not predicate_satisfied: + raise PydanticCustomError( + 'predicate_failed', + f'Predicate {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023 + ) + return v + + else: + + def val_func(v: Any) -> Any: + predicate_satisfied = annotation.func(v) # noqa: B023 + if predicate_satisfied: + raise PydanticCustomError( + 'not_operation_failed', + f'Not of {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023 + ) + return v + + schema = cs.no_info_after_validator_function(val_func, schema) + else: + # ignore any other unknown metadata + return None + + if chain_schema_steps: + chain_schema_steps = [schema] + chain_schema_steps + return cs.chain_schema(chain_schema_steps) + + return schema + + +def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], list[Any]]: + """Split `annotations` into known metadata and unknown annotations. + + Args: + annotations: An iterable of annotations. + + Returns: + A tuple contains a dict of known metadata and a list of unknown annotations. + + Example: + ```python + from annotated_types import Gt, Len + + from pydantic._internal._known_annotated_metadata import collect_known_metadata + + print(collect_known_metadata([Gt(1), Len(42), ...])) + #> ({'gt': 1, 'min_length': 42}, [Ellipsis]) + ``` + """ + annotations = expand_grouped_metadata(annotations) + + res: dict[str, Any] = {} + remaining: list[Any] = [] + + for annotation in annotations: + # isinstance(annotation, PydanticMetadata) also covers ._fields:_PydanticGeneralMetadata + if isinstance(annotation, PydanticMetadata): + res.update(annotation.__dict__) + # we don't use dataclasses.asdict because that recursively calls asdict on the field values + elif (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()): + constraint = at_to_constraint_map[annotation_type] + res[constraint] = getattr(annotation, constraint) + elif isinstance(annotation, type) and issubclass(annotation, PydanticMetadata): + # also support PydanticMetadata classes being used without initialisation, + # e.g. `Annotated[int, Strict]` as well as `Annotated[int, Strict()]` + res.update({k: v for k, v in vars(annotation).items() if not k.startswith('_')}) + else: + remaining.append(annotation) + # Nones can sneak in but pydantic-core will reject them + # 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) + # but this is simple enough to kick that can down the road + res = {k: v for k, v in res.items() if v is not None} + return res, remaining + + +def check_metadata(metadata: dict[str, Any], allowed: Iterable[str], source_type: Any) -> None: + """A small utility function to validate that the given metadata can be applied to the target. + More than saving lines of code, this gives us a consistent error message for all of our internal implementations. + + Args: + metadata: A dict of metadata. + allowed: An iterable of allowed metadata. + source_type: The source type. + + Raises: + TypeError: If there is metadatas that can't be applied on source type. + """ + unknown = metadata.keys() - set(allowed) + if unknown: + raise TypeError( + f'The following constraints cannot be applied to {source_type!r}: {", ".join([f"{k!r}" for k in unknown])}' + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py new file mode 100644 index 0000000..9125ab3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +from collections.abc import Iterator, Mapping +from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, Union + +from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator + +from ..errors import PydanticErrorCodes, PydanticUserError +from ..plugin._schema_validator import PluggableSchemaValidator + +if TYPE_CHECKING: + from ..dataclasses import PydanticDataclass + from ..main import BaseModel + from ..type_adapter import TypeAdapter + + +ValSer = TypeVar('ValSer', bound=Union[SchemaValidator, PluggableSchemaValidator, SchemaSerializer]) +T = TypeVar('T') + + +class MockCoreSchema(Mapping[str, Any]): + """Mocker for `pydantic_core.CoreSchema` which optionally attempts to + rebuild the thing it's mocking when one of its methods is accessed and raises an error if that fails. + """ + + __slots__ = '_error_message', '_code', '_attempt_rebuild', '_built_memo' + + def __init__( + self, + error_message: str, + *, + code: PydanticErrorCodes, + attempt_rebuild: Callable[[], CoreSchema | None] | None = None, + ) -> None: + self._error_message = error_message + self._code: PydanticErrorCodes = code + self._attempt_rebuild = attempt_rebuild + self._built_memo: CoreSchema | None = None + + def __getitem__(self, key: str) -> Any: + return self._get_built().__getitem__(key) + + def __len__(self) -> int: + return self._get_built().__len__() + + def __iter__(self) -> Iterator[str]: + return self._get_built().__iter__() + + def _get_built(self) -> CoreSchema: + if self._built_memo is not None: + return self._built_memo + + if self._attempt_rebuild: + schema = self._attempt_rebuild() + if schema is not None: + self._built_memo = schema + return schema + raise PydanticUserError(self._error_message, code=self._code) + + def rebuild(self) -> CoreSchema | None: + self._built_memo = None + if self._attempt_rebuild: + schema = self._attempt_rebuild() + if schema is not None: + return schema + else: + raise PydanticUserError(self._error_message, code=self._code) + return None + + +class MockValSer(Generic[ValSer]): + """Mocker for `pydantic_core.SchemaValidator` or `pydantic_core.SchemaSerializer` which optionally attempts to + rebuild the thing it's mocking when one of its methods is accessed and raises an error if that fails. + """ + + __slots__ = '_error_message', '_code', '_val_or_ser', '_attempt_rebuild' + + def __init__( + self, + error_message: str, + *, + code: PydanticErrorCodes, + val_or_ser: Literal['validator', 'serializer'], + attempt_rebuild: Callable[[], ValSer | None] | None = None, + ) -> None: + self._error_message = error_message + self._val_or_ser = SchemaValidator if val_or_ser == 'validator' else SchemaSerializer + self._code: PydanticErrorCodes = code + self._attempt_rebuild = attempt_rebuild + + def __getattr__(self, item: str) -> None: + __tracebackhide__ = True + if self._attempt_rebuild: + val_ser = self._attempt_rebuild() + if val_ser is not None: + return getattr(val_ser, item) + + # raise an AttributeError if `item` doesn't exist + getattr(self._val_or_ser, item) + raise PydanticUserError(self._error_message, code=self._code) + + def rebuild(self) -> ValSer | None: + if self._attempt_rebuild: + val_ser = self._attempt_rebuild() + if val_ser is not None: + return val_ser + else: + raise PydanticUserError(self._error_message, code=self._code) + return None + + +def set_type_adapter_mocks(adapter: TypeAdapter) -> None: + """Set `core_schema`, `validator` and `serializer` to mock core types on a type adapter instance. + + Args: + adapter: The type adapter instance to set the mocks on + """ + type_repr = str(adapter._type) + undefined_type_error_message = ( + f'`TypeAdapter[{type_repr}]` is not fully defined; you should define `{type_repr}` and all referenced types,' + f' then call `.rebuild()` on the instance.' + ) + + def attempt_rebuild_fn(attr_fn: Callable[[TypeAdapter], T]) -> Callable[[], T | None]: + def handler() -> T | None: + if adapter.rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: + return attr_fn(adapter) + return None + + return handler + + adapter.core_schema = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.core_schema), + ) + adapter.validator = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='validator', + attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.validator), + ) + adapter.serializer = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='serializer', + attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.serializer), + ) + + +def set_model_mocks(cls: type[BaseModel], undefined_name: str = 'all referenced types') -> None: + """Set `__pydantic_core_schema__`, `__pydantic_validator__` and `__pydantic_serializer__` to mock core types on a model. + + Args: + cls: The model class to set the mocks on + undefined_name: Name of the undefined thing, used in error messages + """ + undefined_type_error_message = ( + f'`{cls.__name__}` is not fully defined; you should define {undefined_name},' + f' then call `{cls.__name__}.model_rebuild()`.' + ) + + def attempt_rebuild_fn(attr_fn: Callable[[type[BaseModel]], T]) -> Callable[[], T | None]: + def handler() -> T | None: + if cls.model_rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: + return attr_fn(cls) + return None + + return handler + + cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__), + ) + cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='validator', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__), + ) + cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='serializer', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__), + ) + + +def set_dataclass_mocks(cls: type[PydanticDataclass], undefined_name: str = 'all referenced types') -> None: + """Set `__pydantic_validator__` and `__pydantic_serializer__` to `MockValSer`s on a dataclass. + + Args: + cls: The model class to set the mocks on + undefined_name: Name of the undefined thing, used in error messages + """ + from ..dataclasses import rebuild_dataclass + + undefined_type_error_message = ( + f'`{cls.__name__}` is not fully defined; you should define {undefined_name},' + f' then call `pydantic.dataclasses.rebuild_dataclass({cls.__name__})`.' + ) + + def attempt_rebuild_fn(attr_fn: Callable[[type[PydanticDataclass]], T]) -> Callable[[], T | None]: + def handler() -> T | None: + if rebuild_dataclass(cls, raise_errors=False, _parent_namespace_depth=5) is not False: + return attr_fn(cls) + return None + + return handler + + cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__), + ) + cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='validator', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__), + ) + cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + undefined_type_error_message, + code='class-not-fully-defined', + val_or_ser='serializer', + attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py new file mode 100644 index 0000000..b794544 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py @@ -0,0 +1,868 @@ +"""Private logic for creating models.""" + +from __future__ import annotations as _annotations + +import operator +import sys +import typing +import warnings +import weakref +from abc import ABCMeta +from functools import cache, partial, wraps +from types import FunctionType +from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, cast + +from pydantic_core import PydanticUndefined, SchemaSerializer +from typing_extensions import TypeAliasType, dataclass_transform, deprecated, get_args, get_origin +from typing_inspection import typing_objects + +from ..errors import PydanticUndefinedAnnotation, PydanticUserError +from ..plugin._schema_validator import create_schema_validator +from ..warnings import GenericBeforeBaseModelWarning, PydanticDeprecatedSince20 +from ._config import ConfigWrapper +from ._decorators import DecoratorInfos, PydanticDescriptorProxy, get_attribute_from_bases, unwrap_wrapped_function +from ._fields import collect_model_fields, is_valid_field_name, is_valid_privateattr_name, rebuild_model_fields +from ._generate_schema import GenerateSchema, InvalidSchemaError +from ._generics import PydanticGenericMetadata, get_model_typevars_map +from ._import_utils import import_cached_base_model, import_cached_field_info +from ._mock_val_ser import set_model_mocks +from ._namespace_utils import NsResolver +from ._signature import generate_pydantic_signature +from ._typing_extra import ( + _make_forward_ref, + eval_type_backport, + is_classvar_annotation, + parent_frame_namespace, +) +from ._utils import LazyClassAttribute, SafeGetItemProxy + +if TYPE_CHECKING: + from ..fields import Field as PydanticModelField + from ..fields import FieldInfo, ModelPrivateAttr + from ..fields import PrivateAttr as PydanticModelPrivateAttr + from ..main import BaseModel + from ._fields import PydanticExtraInfo +else: + PydanticModelField = object() + PydanticModelPrivateAttr = object() + +object_setattr = object.__setattr__ + + +class _ModelNamespaceDict(dict): + """A dictionary subclass that intercepts attribute setting on model classes and + warns about overriding of decorators. + """ + + def __setitem__(self, k: str, v: object) -> None: + existing: Any = self.get(k, None) + if existing and v is not existing and isinstance(existing, PydanticDescriptorProxy): + warnings.warn( + f'`{k}` overrides an existing Pydantic `{existing.decorator_info.decorator_repr}` decorator', + stacklevel=2, + ) + + return super().__setitem__(k, v) + + +def NoInitField( + *, + init: Literal[False] = False, +) -> Any: + """Only for typing purposes. Used as default value of `__pydantic_fields_set__`, + `__pydantic_extra__`, `__pydantic_private__`, so they could be ignored when + synthesizing the `__init__` signature. + """ + + +# For ModelMetaclass.register(): +_T = TypeVar('_T') + + +@dataclass_transform(kw_only_default=True, field_specifiers=(PydanticModelField, PydanticModelPrivateAttr, NoInitField)) +class ModelMetaclass(ABCMeta): + def __new__( + mcs, + cls_name: str, + bases: tuple[type[Any], ...], + namespace: dict[str, Any], + __pydantic_generic_metadata__: PydanticGenericMetadata | None = None, + __pydantic_reset_parent_namespace__: bool = True, + _create_model_module: str | None = None, + **kwargs: Any, + ) -> type: + """Metaclass for creating Pydantic models. + + Args: + cls_name: The name of the class to be created. + bases: The base classes of the class to be created. + namespace: The attribute dictionary of the class to be created. + __pydantic_generic_metadata__: Metadata for generic models. + __pydantic_reset_parent_namespace__: Reset parent namespace. + _create_model_module: The module of the class to be created, if created by `create_model`. + **kwargs: Catch-all for any other keyword arguments. + + Returns: + The new class created by the metaclass. + """ + # Note `ModelMetaclass` refers to `BaseModel`, but is also used to *create* `BaseModel`, so we rely on the fact + # that `BaseModel` itself won't have any bases, but any subclass of it will, to determine whether the `__new__` + # call we're in the middle of is for the `BaseModel` class. + if bases: + raw_annotations: dict[str, Any] + if sys.version_info >= (3, 14): + if ( + '__annotations__' in namespace + ): # `from __future__ import annotations` was used in the model's module + raw_annotations = namespace['__annotations__'] + else: + # See https://docs.python.org/3.14/library/annotationlib.html#using-annotations-in-a-metaclass: + from annotationlib import Format, call_annotate_function, get_annotate_from_class_namespace + + if annotate := get_annotate_from_class_namespace(namespace): + raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF) + else: + raw_annotations = {} + else: + raw_annotations = namespace.get('__annotations__', {}) + + base_field_names, class_vars, base_private_attributes = mcs._collect_bases_data(bases) + + config_wrapper = ConfigWrapper.for_model(bases, namespace, raw_annotations, kwargs) + namespace['model_config'] = config_wrapper.config_dict + private_attributes = inspect_namespace( + namespace, raw_annotations, config_wrapper.ignored_types, class_vars, base_field_names + ) + if private_attributes or base_private_attributes: + original_model_post_init = get_model_post_init(namespace, bases) + if original_model_post_init is not None: + # if there are private attributes and a model_post_init function, we handle both + + @wraps(original_model_post_init) + def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: + """We need to both initialize private attributes and call the user-defined model_post_init + method. + """ + init_private_attributes(self, context) + original_model_post_init(self, context) + + namespace['model_post_init'] = wrapped_model_post_init + else: + namespace['model_post_init'] = init_private_attributes + + namespace['__class_vars__'] = class_vars + namespace['__private_attributes__'] = {**base_private_attributes, **private_attributes} + + cls = cast('type[BaseModel]', super().__new__(mcs, cls_name, bases, namespace, **kwargs)) + BaseModel_ = import_cached_base_model() + + mro = cls.__mro__ + if Generic in mro and mro.index(Generic) < mro.index(BaseModel_): + warnings.warn( + GenericBeforeBaseModelWarning( + 'Classes should inherit from `BaseModel` before generic classes (e.g. `typing.Generic[T]`) ' + 'for pydantic generics to work properly.' + ), + stacklevel=2, + ) + + cls.__pydantic_custom_init__ = not getattr(cls.__init__, '__pydantic_base_init__', False) + cls.__pydantic_post_init__ = ( + None if cls.model_post_init is BaseModel_.model_post_init else 'model_post_init' + ) + + cls.__pydantic_setattr_handlers__ = {} + + cls.__pydantic_decorators__ = DecoratorInfos.build(cls, replace_wrapped_methods=True) + cls.__pydantic_decorators__.update_from_config(config_wrapper) + + # Use the getattr below to grab the __parameters__ from the `typing.Generic` parent class + if __pydantic_generic_metadata__: + cls.__pydantic_generic_metadata__ = __pydantic_generic_metadata__ + else: + parent_parameters = getattr(cls, '__pydantic_generic_metadata__', {}).get('parameters', ()) + parameters = getattr(cls, '__parameters__', None) or parent_parameters + if parameters and parent_parameters and not all(x in parameters for x in parent_parameters): + from ..root_model import RootModelRootType + + missing_parameters = tuple(x for x in parameters if x not in parent_parameters) + if RootModelRootType in parent_parameters and RootModelRootType not in parameters: + # This is a special case where the user has subclassed RootModel, but has not parameterized + # RootModel with the generic type identifiers being used. Ex: + # class MyModel(RootModel, Generic[T]): + # root: T + # Should instead just be: + # class MyModel(RootModel[T]): + # root: T + parameters_str = ', '.join([x.__name__ for x in missing_parameters]) + error_message = ( + f'{cls.__name__} is a subclass of `RootModel`, but does not include the generic type identifier(s) ' + f'{parameters_str} in its parameters. ' + f'You should parametrize RootModel directly, e.g., `class {cls.__name__}(RootModel[{parameters_str}]): ...`.' + ) + else: + combined_parameters = parent_parameters + missing_parameters + parameters_str = ', '.join([str(x) for x in combined_parameters]) + generic_type_label = f'typing.Generic[{parameters_str}]' + error_message = ( + f'All parameters must be present on typing.Generic;' + f' you should inherit from {generic_type_label}.' + ) + if Generic not in bases: # pragma: no cover + # We raise an error here not because it is desirable, but because some cases are mishandled. + # It would be nice to remove this error and still have things behave as expected, it's just + # challenging because we are using a custom `__class_getitem__` to parametrize generic models, + # and not returning a typing._GenericAlias from it. + bases_str = ', '.join([x.__name__ for x in bases] + [generic_type_label]) + error_message += ( + f' Note: `typing.Generic` must go last: `class {cls.__name__}({bases_str}): ...`)' + ) + raise TypeError(error_message) + + cls.__pydantic_generic_metadata__ = { + 'origin': None, + 'args': (), + 'parameters': parameters, + } + + cls.__pydantic_complete__ = False # Ensure this specific class gets completed + + # preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487 + # for attributes not in `new_namespace` (e.g. private attributes) + for name, obj in private_attributes.items(): + obj.__set_name__(cls, name) + + if __pydantic_reset_parent_namespace__: + cls.__pydantic_parent_namespace__ = build_lenient_weakvaluedict(parent_frame_namespace()) + parent_namespace: dict[str, Any] | None = getattr(cls, '__pydantic_parent_namespace__', None) + if isinstance(parent_namespace, dict): + parent_namespace = unpack_lenient_weakvaluedict(parent_namespace) + + ns_resolver = NsResolver(parent_namespace=parent_namespace) + + set_model_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) + + # This is also set in `complete_model_class()`, after schema gen because they are recreated. + # We set them here as well for backwards compatibility: + cls.__pydantic_computed_fields__ = { + k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items() + } + + if config_wrapper.defer_build: + set_model_mocks(cls) + else: + # Any operation that requires accessing the field infos instances should be put inside + # `complete_model_class()`: + complete_model_class( + cls, + config_wrapper, + ns_resolver, + raise_errors=False, + create_model_module=_create_model_module, + ) + + if config_wrapper.frozen and '__hash__' not in namespace: + set_default_hash_func(cls, bases) + + # using super(cls, cls) on the next line ensures we only call the parent class's __pydantic_init_subclass__ + # I believe the `type: ignore` is only necessary because mypy doesn't realize that this code branch is + # only hit for _proper_ subclasses of BaseModel + super(cls, cls).__pydantic_init_subclass__(**kwargs) # type: ignore[misc] + return cls + else: + # These are instance variables, but have been assigned to `NoInitField` to trick the type checker. + for instance_slot in '__pydantic_fields_set__', '__pydantic_extra__', '__pydantic_private__': + namespace.pop( + instance_slot, + None, # In case the metaclass is used with a class other than `BaseModel`. + ) + namespace.get('__annotations__', {}).clear() + return super().__new__(mcs, cls_name, bases, namespace, **kwargs) + + if not TYPE_CHECKING: # pragma: no branch + # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access + + def __getattr__(self, item: str) -> Any: + """This is necessary to keep attribute access working for class attribute access.""" + private_attributes = self.__dict__.get('__private_attributes__') + if private_attributes and item in private_attributes: + return private_attributes[item] + raise AttributeError(item) + + @classmethod + def __prepare__(cls, *args: Any, **kwargs: Any) -> dict[str, object]: + return _ModelNamespaceDict() + + # Due to performance and memory issues, in the ABCMeta.__subclasscheck__ implementation, we don't support + # registered virtual subclasses. See https://github.com/python/cpython/issues/92810#issuecomment-2762454345. + # This may change once CPython is fixed (possibly in 3.15), in which case we should conditionally + # define `register()`. + def register(self, subclass: type[_T]) -> type[_T]: + warnings.warn( + f"For performance reasons, virtual subclasses registered using '{self.__qualname__}.register()' " + "are not supported in 'isinstance()' and 'issubclass()' checks.", + stacklevel=2, + ) + return super().register(subclass) + + __instancecheck__ = type.__instancecheck__ # pyright: ignore[reportAssignmentType] + __subclasscheck__ = type.__subclasscheck__ # pyright: ignore[reportAssignmentType] + + @staticmethod + def _collect_bases_data(bases: tuple[type[Any], ...]) -> tuple[set[str], set[str], dict[str, ModelPrivateAttr]]: + BaseModel = import_cached_base_model() + + field_names: set[str] = set() + class_vars: set[str] = set() + private_attributes: dict[str, ModelPrivateAttr] = {} + for base in bases: + if issubclass(base, BaseModel) and base is not BaseModel: + # model_fields might not be defined yet in the case of generics, so we use getattr here: + field_names.update(getattr(base, '__pydantic_fields__', {}).keys()) + class_vars.update(base.__class_vars__) + private_attributes.update(base.__private_attributes__) + return field_names, class_vars, private_attributes + + @property + @deprecated( + 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', category=None + ) + def __fields__(self) -> dict[str, FieldInfo]: + warnings.warn( + 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', + PydanticDeprecatedSince20, + stacklevel=2, + ) + return getattr(self, '__pydantic_fields__', {}) + + @property + def __pydantic_fields_complete__(self) -> bool: + """Whether the fields were successfully collected (i.e. type hints were successfully resolved). + + This is a private attribute, not meant to be used outside Pydantic. + """ + if '__pydantic_fields__' not in self.__dict__: + return False + + field_infos = cast('dict[str, FieldInfo]', self.__pydantic_fields__) # pyright: ignore[reportAttributeAccessIssue] + + pydantic_extra_info = cast('PydanticExtraInfo | None', self.__pydantic_extra_info__) # pyright: ignore[reportAttributeAccessIssue] + if pydantic_extra_info is not None: + extra_complete = pydantic_extra_info.complete + else: + extra_complete = True + + return all(field_info._complete for field_info in field_infos.values()) and extra_complete + + def __dir__(self) -> list[str]: + attributes = list(super().__dir__()) + if '__fields__' in attributes: + attributes.remove('__fields__') + return attributes + + +def init_private_attributes(self: BaseModel, context: Any, /) -> None: + """This function is meant to behave like a BaseModel method to initialize private attributes. + + It takes context as an argument since that's what pydantic-core passes when calling it. + + Args: + self: The BaseModel instance. + context: The context. + """ + if getattr(self, '__pydantic_private__', None) is None: + pydantic_private = {} + for name, private_attr in self.__private_attributes__.items(): + # Avoid needlessly creating a new dict for the validated data: + if private_attr.default_factory_takes_validated_data: + default = private_attr.get_default( + call_default_factory=True, validated_data={**self.__dict__, **pydantic_private} + ) + else: + default = private_attr.get_default(call_default_factory=True) + if default is not PydanticUndefined: + pydantic_private[name] = default + object_setattr(self, '__pydantic_private__', pydantic_private) + + +def get_model_post_init(namespace: dict[str, Any], bases: tuple[type[Any], ...]) -> Callable[..., Any] | None: + """Get the `model_post_init` method from the namespace or the class bases, or `None` if not defined.""" + if 'model_post_init' in namespace: + return namespace['model_post_init'] + + BaseModel = import_cached_base_model() + + model_post_init = get_attribute_from_bases(bases, 'model_post_init') + if model_post_init is not BaseModel.model_post_init: + return model_post_init + + +def inspect_namespace( # noqa C901 + namespace: dict[str, Any], + raw_annotations: dict[str, Any], + ignored_types: tuple[type[Any], ...], + base_class_vars: set[str], + base_class_fields: set[str], +) -> dict[str, ModelPrivateAttr]: + """Iterate over the namespace and: + * gather private attributes + * check for items which look like fields but are not (e.g. have no annotation) and warn. + + Args: + namespace: The attribute dictionary of the class to be created. + raw_annotations: The (non-evaluated) annotations of the model. + ignored_types: A tuple of ignore types. + base_class_vars: A set of base class class variables. + base_class_fields: A set of base class fields. + + Returns: + A dict containing private attributes info. + + Raises: + TypeError: If there is a `__root__` field in model. + NameError: If private attribute name is invalid. + PydanticUserError: + - If a field does not have a type annotation. + - If a field on base class was overridden by a non-annotated attribute. + """ + from ..fields import ModelPrivateAttr, PrivateAttr + + FieldInfo = import_cached_field_info() + + all_ignored_types = ignored_types + default_ignored_types() + + private_attributes: dict[str, ModelPrivateAttr] = {} + + if '__root__' in raw_annotations or '__root__' in namespace: + raise TypeError("To define root models, use `pydantic.RootModel` rather than a field called '__root__'") + + ignored_names: set[str] = set() + for var_name, value in list(namespace.items()): + if var_name == 'model_config' or var_name == '__pydantic_extra__': + continue + elif ( + isinstance(value, type) + and value.__module__ == namespace['__module__'] + and '__qualname__' in namespace + and value.__qualname__.startswith(f'{namespace["__qualname__"]}.') + ): + # `value` is a nested type defined in this namespace; don't error + continue + elif isinstance(value, all_ignored_types) or value.__class__.__module__ == 'functools': + ignored_names.add(var_name) + continue + elif isinstance(value, ModelPrivateAttr): + if var_name.startswith('__'): + raise NameError( + 'Private attributes must not use dunder names;' + f' use a single underscore prefix instead of {var_name!r}.' + ) + elif is_valid_field_name(var_name): + raise NameError( + 'Private attributes must not use valid field names;' + f' use sunder names, e.g. {"_" + var_name!r} instead of {var_name!r}.' + ) + private_attributes[var_name] = value + del namespace[var_name] + elif isinstance(value, FieldInfo) and not is_valid_field_name(var_name): + suggested_name = var_name.lstrip('_') or 'my_field' # don't suggest '' for all-underscore name + raise NameError( + f'Fields must not use names with leading underscores;' + f' e.g., use {suggested_name!r} instead of {var_name!r}.' + ) + + elif var_name.startswith('__'): + continue + elif is_valid_privateattr_name(var_name): + if var_name not in raw_annotations or not is_classvar_annotation(raw_annotations[var_name]): + private_attributes[var_name] = cast(ModelPrivateAttr, PrivateAttr(default=value)) + del namespace[var_name] + elif var_name in base_class_vars: + continue + elif var_name not in raw_annotations: + if var_name in base_class_fields: + raise PydanticUserError( + f'Field {var_name!r} defined on a base class was overridden by a non-annotated attribute. ' + f'All field definitions, including overrides, require a type annotation.', + code='model-field-overridden', + ) + elif isinstance(value, FieldInfo): + raise PydanticUserError( + f'Field {var_name!r} requires a type annotation', code='model-field-missing-annotation' + ) + else: + raise PydanticUserError( + f'A non-annotated attribute was detected: `{var_name} = {value!r}`. All model fields require a ' + f'type annotation; if `{var_name}` is not meant to be a field, you may be able to resolve this ' + f"error by annotating it as a `ClassVar` or updating `model_config['ignored_types']`.", + code='model-field-missing-annotation', + ) + + for ann_name, ann_type in raw_annotations.items(): + if ( + is_valid_privateattr_name(ann_name) + and ann_name not in private_attributes + and ann_name not in ignored_names + # This condition can be a false negative when `ann_type` is stringified, + # but it is handled in most cases in `set_model_fields`: + and not is_classvar_annotation(ann_type) + and ann_type not in all_ignored_types + and getattr(ann_type, '__module__', None) != 'functools' + ): + if isinstance(ann_type, str): + # Walking up the frames to get the module namespace where the model is defined + # (as the model class wasn't created yet, we unfortunately can't use `cls.__module__`): + frame = sys._getframe(2) + if frame is not None: + try: + ann_type = eval_type_backport( + _make_forward_ref(ann_type, is_argument=False, is_class=True), + globalns=frame.f_globals, + localns=frame.f_locals, + ) + except (NameError, TypeError): + pass + + if typing_objects.is_annotated(get_origin(ann_type)): + _, *metadata = get_args(ann_type) + private_attr = next((v for v in metadata if isinstance(v, ModelPrivateAttr)), None) + if private_attr is not None: + private_attributes[ann_name] = private_attr + continue + private_attributes[ann_name] = PrivateAttr() + + return private_attributes + + +def set_default_hash_func(cls: type[BaseModel], bases: tuple[type[Any], ...]) -> None: + base_hash_func = get_attribute_from_bases(bases, '__hash__') + new_hash_func = make_hash_func(cls) + if base_hash_func in {None, object.__hash__} or getattr(base_hash_func, '__code__', None) == new_hash_func.__code__: + # If `__hash__` is some default, we generate a hash function. + # It will be `None` if not overridden from BaseModel. + # It may be `object.__hash__` if there is another + # parent class earlier in the bases which doesn't override `__hash__` (e.g. `typing.Generic`). + # It may be a value set by `set_default_hash_func` if `cls` is a subclass of another frozen model. + # In the last case we still need a new hash function to account for new `model_fields`. + cls.__hash__ = new_hash_func + + +def make_hash_func(cls: type[BaseModel]) -> Any: + getter = operator.itemgetter(*cls.__pydantic_fields__.keys()) if cls.__pydantic_fields__ else lambda _: 0 + + def hash_func(self: Any) -> int: + try: + return hash(getter(self.__dict__)) + except KeyError: + # In rare cases (such as when using the deprecated copy method), the __dict__ may not contain + # all model fields, which is how we can get here. + # getter(self.__dict__) is much faster than any 'safe' method that accounts for missing keys, + # and wrapping it in a `try` doesn't slow things down much in the common case. + return hash(getter(SafeGetItemProxy(self.__dict__))) + + return hash_func + + +def set_model_fields( + cls: type[BaseModel], + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver, +) -> None: + """Collect and set `cls.__pydantic_fields__` and `cls.__class_vars__`. + + Args: + cls: BaseModel or dataclass. + config_wrapper: The config wrapper instance. + ns_resolver: Namespace resolver to use when getting model annotations. + """ + typevars_map = get_model_typevars_map(cls) + fields, pydantic_extra_info, class_vars = collect_model_fields( + cls, config_wrapper, ns_resolver, typevars_map=typevars_map + ) + + cls.__pydantic_fields__ = fields + cls.__pydantic_extra_info__ = pydantic_extra_info + cls.__class_vars__.update(class_vars) + + for k in class_vars: + # Class vars should not be private attributes + # We remove them _here_ and not earlier because we rely on inspecting the class to determine its classvars, + # but private attributes are determined by inspecting the namespace _prior_ to class creation. + # In the case that a classvar with a leading-'_' is defined via a ForwardRef (e.g., when using + # `__future__.annotations`), we want to remove the private attribute which was detected _before_ we knew it + # evaluated to a classvar + + value = cls.__private_attributes__.pop(k, None) + if value is not None and value.default is not PydanticUndefined: + setattr(cls, k, value.default) + + +def complete_model_class( + cls: type[BaseModel], + config_wrapper: ConfigWrapper, + ns_resolver: NsResolver, + *, + raise_errors: bool = True, + call_on_complete_hook: bool = True, + create_model_module: str | None = None, + is_force_rebuild: bool = False, +) -> bool: + """Finish building a model class. + + This logic must be called after class has been created since validation functions must be bound + and `get_type_hints` requires a class object. + + Args: + cls: BaseModel or dataclass. + config_wrapper: The config wrapper instance. + ns_resolver: The namespace resolver instance to use during schema building. + raise_errors: Whether to raise errors. + call_on_complete_hook: Whether to call the `__pydantic_on_complete__` hook. + create_model_module: The module of the class to be created, if created by `create_model`. + is_force_rebuild: Whether the model is being force-rebuilt (if True, pre-built serializers and + validators are not used, to avoid stale references). + + Returns: + `True` if the model is successfully completed, else `False`. + + Raises: + PydanticUndefinedAnnotation: If PydanticUndefinedAnnotation occurs in __get_pydantic_core_schema__ + and `raise_errors=True`. + """ + typevars_map = get_model_typevars_map(cls) + + if not cls.__pydantic_fields_complete__: + # Note: when coming from `ModelMetaclass.__new__()`, this results in fields being built twice. + # We do so a second time here so that we can get the ``NameError`` for the specific undefined annotation. + # Alternatively, we could let `GenerateSchema()` raise the error, but there are cases where incomplete + # fields are inherited in `collect_model_fields()` and can actually have their annotation resolved in the + # generate schema process. As we want to avoid having `__pydantic_fields_complete__` set to `False` + # when `__pydantic_complete__` is `True`, we rebuild here: + try: + cls.__pydantic_fields__, cls.__pydantic_extra_info__ = rebuild_model_fields( + cls, + config_wrapper=config_wrapper, + ns_resolver=ns_resolver, + typevars_map=typevars_map, + ) + except NameError as e: + exc = PydanticUndefinedAnnotation.from_name_error(e) + set_model_mocks(cls, f'`{exc.name}`') + if raise_errors: + raise exc from e + + if not raise_errors and not cls.__pydantic_fields_complete__: + # No need to continue with schema gen, it is guaranteed to fail + return False + + assert cls.__pydantic_fields_complete__ + + gen_schema = GenerateSchema( + config_wrapper, + ns_resolver, + typevars_map, + ) + + try: + schema = gen_schema.generate_schema(cls) + except PydanticUndefinedAnnotation as e: + if raise_errors: + raise + set_model_mocks(cls, f'`{e.name}`') + return False + + core_config = config_wrapper.core_config(title=cls.__name__) + + try: + schema = gen_schema.clean_schema(schema) + except InvalidSchemaError: + set_model_mocks(cls) + return False + + # This needs to happen *after* model schema generation, as the return types + # of the properties are evaluated and the `ComputedFieldInfo` are recreated: + cls.__pydantic_computed_fields__ = {k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items()} + + set_deprecated_descriptors(cls) + + cls.__pydantic_core_schema__ = schema + + cls.__pydantic_validator__ = create_schema_validator( + schema, + cls, + create_model_module or cls.__module__, + cls.__qualname__, + 'create_model' if create_model_module else 'BaseModel', + core_config, + config_wrapper.plugin_settings, + _use_prebuilt=not is_force_rebuild, + ) + cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config, _use_prebuilt=not is_force_rebuild) + + # set __signature__ attr only for model class, but not for its instances + # (because instances can define `__call__`, and `inspect.signature` shouldn't + # use the `__signature__` attribute and instead generate from `__call__`). + cls.__signature__ = LazyClassAttribute( + '__signature__', + partial( + generate_pydantic_signature, + init=cls.__init__, + fields=cls.__pydantic_fields__, + validate_by_name=config_wrapper.validate_by_name, + extra=config_wrapper.extra, + ), + ) + + cls.__pydantic_complete__ = True + + if call_on_complete_hook: + cls.__pydantic_on_complete__() + + return True + + +def set_deprecated_descriptors(cls: type[BaseModel]) -> None: + """Set data descriptors on the class for deprecated fields.""" + for field, field_info in cls.__pydantic_fields__.items(): + if (msg := field_info.deprecation_message) is not None: + desc = _DeprecatedFieldDescriptor(msg) + desc.__set_name__(cls, field) + setattr(cls, field, desc) + + for field, computed_field_info in cls.__pydantic_computed_fields__.items(): + if ( + (msg := computed_field_info.deprecation_message) is not None + # Avoid having two warnings emitted: + and not hasattr(unwrap_wrapped_function(computed_field_info.wrapped_property), '__deprecated__') + ): + desc = _DeprecatedFieldDescriptor(msg, computed_field_info.wrapped_property) + desc.__set_name__(cls, field) + setattr(cls, field, desc) + + +class _DeprecatedFieldDescriptor: + """Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field. + + Attributes: + msg: The deprecation message to be emitted. + wrapped_property: The property instance if the deprecated field is a computed field, or `None`. + field_name: The name of the field being deprecated. + """ + + field_name: str + + def __init__(self, msg: str, wrapped_property: property | None = None) -> None: + self.msg = msg + self.wrapped_property = wrapped_property + + def __set_name__(self, cls: type[BaseModel], name: str) -> None: + self.field_name = name + + def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: + if obj is None: + if self.wrapped_property is not None: + return self.wrapped_property.__get__(None, obj_type) + raise AttributeError(self.field_name) + + warnings.warn(self.msg, DeprecationWarning, stacklevel=2) + + if self.wrapped_property is not None: + return self.wrapped_property.__get__(obj, obj_type) + return obj.__dict__[self.field_name] + + # Defined to make it a data descriptor and take precedence over the instance's dictionary. + # Note that it will not be called when setting a value on a model instance + # as `BaseModel.__setattr__` is defined and takes priority. + def __set__(self, obj: Any, value: Any) -> NoReturn: + raise AttributeError(self.field_name) + + +class _PydanticWeakRef: + """Wrapper for `weakref.ref` that enables `pickle` serialization. + + Cloudpickle fails to serialize weakref.ref objects due to an arcane error related to + to abstract base classes (`abc.ABC`). This class works around the issue by wrapping + `weakref.ref` instead of subclassing it. + + See https://github.com/pydantic/pydantic/issues/6763 for context. + + Semantics: + - If not pickled, behaves the same as a `weakref.ref`. + - If pickled along with the referenced object, the same `weakref.ref` behavior + will be maintained between them after unpickling. + - If pickled without the referenced object, after unpickling the underlying + reference will be cleared (`__call__` will always return `None`). + """ + + def __init__(self, obj: Any): + if obj is None: + # The object will be `None` upon deserialization if the serialized weakref + # had lost its underlying object. + self._wr = None + else: + self._wr = weakref.ref(obj) + + def __call__(self) -> Any: + if self._wr is None: + return None + else: + return self._wr() + + def __reduce__(self) -> tuple[Callable, tuple[weakref.ReferenceType | None]]: + return _PydanticWeakRef, (self(),) + + +def build_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | None: + """Takes an input dictionary, and produces a new value that (invertibly) replaces the values with weakrefs. + + We can't just use a WeakValueDictionary because many types (including int, str, etc.) can't be stored as values + in a WeakValueDictionary. + + The `unpack_lenient_weakvaluedict` function can be used to reverse this operation. + """ + if d is None: + return None + result = {} + for k, v in d.items(): + try: + proxy = _PydanticWeakRef(v) + except TypeError: + proxy = v + result[k] = proxy + return result + + +def unpack_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | None: + """Inverts the transform performed by `build_lenient_weakvaluedict`.""" + if d is None: + return None + + result = {} + for k, v in d.items(): + if isinstance(v, _PydanticWeakRef): + v = v() + if v is not None: + result[k] = v + else: + result[k] = v + return result + + +@cache +def default_ignored_types() -> tuple[type[Any], ...]: + from ..fields import ComputedFieldInfo + + ignored_types = [ + FunctionType, + property, + classmethod, + staticmethod, + PydanticDescriptorProxy, + ComputedFieldInfo, + TypeAliasType, # from `typing_extensions` + ] + + if sys.version_info >= (3, 12): + ignored_types.append(typing.TypeAliasType) + + return tuple(ignored_types) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py new file mode 100644 index 0000000..af0cddb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py @@ -0,0 +1,293 @@ +from __future__ import annotations + +import sys +from collections.abc import Generator, Iterator, Mapping +from contextlib import contextmanager +from functools import cached_property +from typing import Any, Callable, NamedTuple, TypeVar + +from typing_extensions import ParamSpec, TypeAlias, TypeAliasType, TypeVarTuple + +GlobalsNamespace: TypeAlias = 'dict[str, Any]' +"""A global namespace. + +In most cases, this is a reference to the `__dict__` attribute of a module. +This namespace type is expected as the `globals` argument during annotations evaluation. +""" + +MappingNamespace: TypeAlias = Mapping[str, Any] +"""Any kind of namespace. + +In most cases, this is a local namespace (e.g. the `__dict__` attribute of a class, +the [`f_locals`][frame.f_locals] attribute of a frame object, when dealing with types +defined inside functions). +This namespace type is expected as the `locals` argument during annotations evaluation. +""" + +_TypeVarLike: TypeAlias = 'TypeVar | ParamSpec | TypeVarTuple' + + +class NamespacesTuple(NamedTuple): + """A tuple of globals and locals to be used during annotations evaluation. + + This datastructure is defined as a named tuple so that it can easily be unpacked: + + ```python {lint="skip" test="skip"} + def eval_type(typ: type[Any], ns: NamespacesTuple) -> None: + return eval(typ, *ns) + ``` + """ + + globals: GlobalsNamespace + """The namespace to be used as the `globals` argument during annotations evaluation.""" + + locals: MappingNamespace + """The namespace to be used as the `locals` argument during annotations evaluation.""" + + +def get_module_ns_of(obj: Any) -> dict[str, Any]: + """Get the namespace of the module where the object is defined. + + Caution: this function does not return a copy of the module namespace, so the result + should not be mutated. The burden of enforcing this is on the caller. + """ + module_name = getattr(obj, '__module__', None) + if module_name: + try: + return sys.modules[module_name].__dict__ + except KeyError: + # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 + return {} + return {} + + +# Note that this class is almost identical to `collections.ChainMap`, but need to enforce +# immutable mappings here: +class LazyLocalNamespace(Mapping[str, Any]): + """A lazily evaluated mapping, to be used as the `locals` argument during annotations evaluation. + + While the [`eval`][eval] function expects a mapping as the `locals` argument, it only + performs `__getitem__` calls. The [`Mapping`][collections.abc.Mapping] abstract base class + is fully implemented only for type checking purposes. + + Args: + *namespaces: The namespaces to consider, in ascending order of priority. + + Example: + ```python {lint="skip" test="skip"} + ns = LazyLocalNamespace({'a': 1, 'b': 2}, {'a': 3}) + ns['a'] + #> 3 + ns['b'] + #> 2 + ``` + """ + + def __init__(self, *namespaces: MappingNamespace) -> None: + self._namespaces = namespaces + + @cached_property + def data(self) -> dict[str, Any]: + return {k: v for ns in self._namespaces for k, v in ns.items()} + + def __len__(self) -> int: + return len(self.data) + + def __getitem__(self, key: str) -> Any: + return self.data[key] + + def __contains__(self, key: object) -> bool: + return key in self.data + + def __iter__(self) -> Iterator[str]: + return iter(self.data) + + +def ns_for_function(obj: Callable[..., Any], parent_namespace: MappingNamespace | None = None) -> NamespacesTuple: + """Return the global and local namespaces to be used when evaluating annotations for the provided function. + + The global namespace will be the `__dict__` attribute of the module the function was defined in. + The local namespace will contain the `__type_params__` introduced by PEP 695. + + Args: + obj: The object to use when building namespaces. + parent_namespace: Optional namespace to be added with the lowest priority in the local namespace. + If the passed function is a method, the `parent_namespace` will be the namespace of the class + the method is defined in. Thus, we also fetch type `__type_params__` from there (i.e. the + class-scoped type variables). + """ + locals_list: list[MappingNamespace] = [] + if parent_namespace is not None: + locals_list.append(parent_namespace) + + # Get the `__type_params__` attribute introduced by PEP 695. + # Note that the `typing._eval_type` function expects type params to be + # passed as a separate argument. However, internally, `_eval_type` calls + # `ForwardRef._evaluate` which will merge type params with the localns, + # essentially mimicking what we do here. + type_params: tuple[_TypeVarLike, ...] = getattr(obj, '__type_params__', ()) + if parent_namespace is not None: + # We also fetch type params from the parent namespace. If present, it probably + # means the function was defined in a class. This is to support the following: + # https://github.com/python/cpython/issues/124089. + type_params += parent_namespace.get('__type_params__', ()) + + locals_list.append({t.__name__: t for t in type_params}) + + # What about short-circuiting to `obj.__globals__`? + globalns = get_module_ns_of(obj) + + return NamespacesTuple(globalns, LazyLocalNamespace(*locals_list)) + + +class NsResolver: + """A class responsible for the namespaces resolving logic for annotations evaluation. + + This class handles the namespace logic when evaluating annotations mainly for class objects. + + It holds a stack of classes that are being inspected during the core schema building, + and the `types_namespace` property exposes the globals and locals to be used for + type annotation evaluation. Additionally -- if no class is present in the stack -- a + fallback globals and locals can be provided using the `namespaces_tuple` argument + (this is useful when generating a schema for a simple annotation, e.g. when using + `TypeAdapter`). + + The namespace creation logic is unfortunately flawed in some cases, for backwards + compatibility reasons and to better support valid edge cases. See the description + for the `parent_namespace` argument and the example for more details. + + Args: + namespaces_tuple: The default globals and locals to use if no class is present + on the stack. This can be useful when using the `GenerateSchema` class + with `TypeAdapter`, where the "type" being analyzed is a simple annotation. + parent_namespace: An optional parent namespace that will be added to the locals + with the lowest priority. For a given class defined in a function, the locals + of this function are usually used as the parent namespace: + + ```python {lint="skip" test="skip"} + from pydantic import BaseModel + + def func() -> None: + SomeType = int + + class Model(BaseModel): + f: 'SomeType' + + # when collecting fields, an namespace resolver instance will be created + # this way: + # ns_resolver = NsResolver(parent_namespace={'SomeType': SomeType}) + ``` + + For backwards compatibility reasons and to support valid edge cases, this parent + namespace will be used for *every* type being pushed to the stack. In the future, + we might want to be smarter by only doing so when the type being pushed is defined + in the same module as the parent namespace. + + Example: + ```python {lint="skip" test="skip"} + ns_resolver = NsResolver( + parent_namespace={'fallback': 1}, + ) + + class Sub: + m: 'Model' + + class Model: + some_local = 1 + sub: Sub + + ns_resolver = NsResolver() + + # This is roughly what happens when we build a core schema for `Model`: + with ns_resolver.push(Model): + ns_resolver.types_namespace + #> NamespacesTuple({'Sub': Sub}, {'Model': Model, 'some_local': 1}) + # First thing to notice here, the model being pushed is added to the locals. + # Because `NsResolver` is being used during the model definition, it is not + # yet added to the globals. This is useful when resolving self-referencing annotations. + + with ns_resolver.push(Sub): + ns_resolver.types_namespace + #> NamespacesTuple({'Sub': Sub}, {'Sub': Sub, 'Model': Model}) + # Second thing to notice: `Sub` is present in both the globals and locals. + # This is not an issue, just that as described above, the model being pushed + # is added to the locals, but it happens to be present in the globals as well + # because it is already defined. + # Third thing to notice: `Model` is also added in locals. This is a backwards + # compatibility workaround that allows for `Sub` to be able to resolve `'Model'` + # correctly (as otherwise models would have to be rebuilt even though this + # doesn't look necessary). + ``` + """ + + def __init__( + self, + namespaces_tuple: NamespacesTuple | None = None, + parent_namespace: MappingNamespace | None = None, + ) -> None: + self._base_ns_tuple = namespaces_tuple or NamespacesTuple({}, {}) + self._parent_ns = parent_namespace + self._types_stack: list[type[Any] | TypeAliasType] = [] + + @cached_property + def types_namespace(self) -> NamespacesTuple: + """The current global and local namespaces to be used for annotations evaluation.""" + if not self._types_stack: + # TODO: should we merge the parent namespace here? + # This is relevant for TypeAdapter, where there are no types on the stack, and we might + # need access to the parent_ns. Right now, we sidestep this in `type_adapter.py` by passing + # locals to both parent_ns and the base_ns_tuple, but this is a bit hacky. + # we might consider something like: + # if self._parent_ns is not None: + # # Hacky workarounds, see class docstring: + # # An optional parent namespace that will be added to the locals with the lowest priority + # locals_list: list[MappingNamespace] = [self._parent_ns, self._base_ns_tuple.locals] + # return NamespacesTuple(self._base_ns_tuple.globals, LazyLocalNamespace(*locals_list)) + return self._base_ns_tuple + + typ = self._types_stack[-1] + + globalns = get_module_ns_of(typ) + + locals_list: list[MappingNamespace] = [] + # Hacky workarounds, see class docstring: + # An optional parent namespace that will be added to the locals with the lowest priority + if self._parent_ns is not None: + locals_list.append(self._parent_ns) + if len(self._types_stack) > 1: + first_type = self._types_stack[0] + locals_list.append({first_type.__name__: first_type}) + + # Adding `__type_params__` *before* `vars(typ)`, as the latter takes priority + # (see https://github.com/python/cpython/pull/120272). + # TODO `typ.__type_params__` when we drop support for Python 3.11: + type_params: tuple[_TypeVarLike, ...] = getattr(typ, '__type_params__', ()) + if type_params: + # Adding `__type_params__` is mostly useful for generic classes defined using + # PEP 695 syntax *and* using forward annotations (see the example in + # https://github.com/python/cpython/issues/114053). For TypeAliasType instances, + # it is way less common, but still required if using a string annotation in the alias + # value, e.g. `type A[T] = 'T'` (which is not necessary in most cases). + locals_list.append({t.__name__: t for t in type_params}) + + # TypeAliasType instances don't have a `__dict__` attribute, so the check + # is necessary: + if hasattr(typ, '__dict__'): + locals_list.append(vars(typ)) + + # The `len(self._types_stack) > 1` check above prevents this from being added twice: + locals_list.append({typ.__name__: typ}) + + return NamespacesTuple(globalns, LazyLocalNamespace(*locals_list)) + + @contextmanager + def push(self, typ: type[Any] | TypeAliasType, /) -> Generator[None]: + """Push a type to the stack.""" + self._types_stack.append(typ) + # Reset the cached property: + self.__dict__.pop('types_namespace', None) + try: + yield + finally: + self._types_stack.pop() + self.__dict__.pop('types_namespace', None) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py new file mode 100644 index 0000000..7e80a9c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py @@ -0,0 +1,124 @@ +"""Tools to provide pretty/human-readable display of objects.""" + +from __future__ import annotations as _annotations + +import types +from collections.abc import Callable, Collection, Generator, Iterable +from typing import TYPE_CHECKING, Any, ForwardRef, cast + +import typing_extensions +from typing_extensions import TypeAlias +from typing_inspection import typing_objects +from typing_inspection.introspection import is_union_origin + +from . import _typing_extra + +if TYPE_CHECKING: + # TODO remove type error comments when we drop support for Python 3.9 + ReprArgs: TypeAlias = Iterable[tuple[str | None, Any]] # pyright: ignore[reportGeneralTypeIssues] + RichReprResult: TypeAlias = Iterable[Any | tuple[Any] | tuple[str, Any] | tuple[str, Any, Any]] # pyright: ignore[reportGeneralTypeIssues] + + +class PlainRepr(str): + """String class where repr doesn't include quotes. Useful with Representation when you want to return a string + representation of something that is valid (or pseudo-valid) python. + """ + + def __repr__(self) -> str: + return str(self) + + +class Representation: + # Mixin to provide `__str__`, `__repr__`, and `__pretty__` and `__rich_repr__` methods. + # `__pretty__` is used by [devtools](https://python-devtools.helpmanual.io/). + # `__rich_repr__` is used by [rich](https://rich.readthedocs.io/en/stable/pretty.html). + # (this is not a docstring to avoid adding a docstring to classes which inherit from Representation) + + __slots__ = () + + def __repr_args__(self) -> ReprArgs: + """Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden. + + Can either return: + * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]` + * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]` + """ + attrs_names = cast(Collection[str], self.__slots__) + if not attrs_names and hasattr(self, '__dict__'): + attrs_names = self.__dict__.keys() + attrs = ((s, getattr(self, s)) for s in attrs_names) + return [(a, v if v is not self else self.__repr_recursion__(v)) for a, v in attrs if v is not None] + + def __repr_name__(self) -> str: + """Name of the instance's class, used in __repr__.""" + return self.__class__.__name__ + + def __repr_recursion__(self, object: Any) -> str: + """Returns the string representation of a recursive object.""" + # This is copied over from the stdlib `pprint` module: + return f'' + + def __repr_str__(self, join_str: str) -> str: + return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__()) + + def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any]: + """Used by devtools (https://python-devtools.helpmanual.io/) to pretty print objects.""" + yield self.__repr_name__() + '(' + yield 1 + for name, value in self.__repr_args__(): + if name is not None: + yield name + '=' + yield fmt(value) + yield ',' + yield 0 + yield -1 + yield ')' + + def __rich_repr__(self) -> RichReprResult: + """Used by Rich (https://rich.readthedocs.io/en/stable/pretty.html) to pretty print objects.""" + for name, field_repr in self.__repr_args__(): + if name is None: + yield field_repr + else: + yield name, field_repr + + def __str__(self) -> str: + return self.__repr_str__(' ') + + def __repr__(self) -> str: + return f'{self.__repr_name__()}({self.__repr_str__(", ")})' + + +def display_as_type(obj: Any) -> str: + """Pretty representation of a type, should be as close as possible to the original type definition string. + + Takes some logic from `typing._type_repr`. + """ + if isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)): + return obj.__name__ + elif obj is ...: + return '...' + elif isinstance(obj, Representation): + return repr(obj) + elif isinstance(obj, ForwardRef) or typing_objects.is_typealiastype(obj): + return str(obj) + + if not isinstance(obj, (_typing_extra.typing_base, _typing_extra.WithArgsTypes, type)): + obj = obj.__class__ + + if is_union_origin(typing_extensions.get_origin(obj)): + args = ', '.join(map(display_as_type, typing_extensions.get_args(obj))) + return f'Union[{args}]' + elif isinstance(obj, _typing_extra.WithArgsTypes): + if typing_objects.is_literal(typing_extensions.get_origin(obj)): + args = ', '.join(map(repr, typing_extensions.get_args(obj))) + else: + args = ', '.join(map(display_as_type, typing_extensions.get_args(obj))) + try: + return f'{obj.__qualname__}[{args}]' + except AttributeError: + return str(obj).replace('typing.', '').replace('typing_extensions.', '') # handles TypeAliasType in 3.12 + elif isinstance(obj, type): + return obj.__qualname__ + else: + return repr(obj).replace('typing.', '').replace('typing_extensions.', '') diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py new file mode 100644 index 0000000..8e07b20 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py @@ -0,0 +1,212 @@ +# pyright: reportTypedDictNotRequiredAccess=false, reportGeneralTypeIssues=false, reportArgumentType=false, reportAttributeAccessIssue=false +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TypedDict + +from pydantic_core.core_schema import ( + ComputedField, + CoreSchema, + DefinitionReferenceSchema, + SerSchema, + iter_union_choices, +) +from typing_extensions import TypeAlias + +AllSchemas: TypeAlias = 'CoreSchema | SerSchema | ComputedField' + + +class GatherResult(TypedDict): + """Schema traversing result.""" + + collected_references: dict[str, DefinitionReferenceSchema | None] + """The collected definition references. + + If a definition reference schema can be inlined, it means that there is + only one in the whole core schema. As such, it is stored as the value. + Otherwise, the value is set to `None`. + """ + + deferred_discriminator_schemas: list[CoreSchema] + """The list of core schemas having the discriminator application deferred.""" + + +class MissingDefinitionError(LookupError): + """A reference was pointing to a non-existing core schema.""" + + def __init__(self, schema_reference: str, /) -> None: + self.schema_reference = schema_reference + + +@dataclass +class GatherContext: + """The current context used during core schema traversing. + + Context instances should only be used during schema traversing. + """ + + definitions: dict[str, CoreSchema] + """The available definitions.""" + + deferred_discriminator_schemas: list[CoreSchema] = field(init=False, default_factory=list) + """The list of core schemas having the discriminator application deferred. + + Internally, these core schemas have a specific key set in the core metadata dict. + """ + + collected_references: dict[str, DefinitionReferenceSchema | None] = field(init=False, default_factory=dict) + """The collected definition references. + + If a definition reference schema can be inlined, it means that there is + only one in the whole core schema. As such, it is stored as the value. + Otherwise, the value is set to `None`. + + During schema traversing, definition reference schemas can be added as candidates, or removed + (by setting the value to `None`). + """ + + +def traverse_metadata(schema: AllSchemas, ctx: GatherContext) -> None: + meta = schema.get('metadata') + if meta is not None and 'pydantic_internal_union_discriminator' in meta: + ctx.deferred_discriminator_schemas.append(schema) # pyright: ignore[reportArgumentType] + + +def traverse_definition_ref(def_ref_schema: DefinitionReferenceSchema, ctx: GatherContext) -> None: + schema_ref = def_ref_schema['schema_ref'] + + if schema_ref not in ctx.collected_references: + definition = ctx.definitions.get(schema_ref) + if definition is None: + raise MissingDefinitionError(schema_ref) + + # The `'definition-ref'` schema was only encountered once, make it + # a candidate to be inlined: + ctx.collected_references[schema_ref] = def_ref_schema + traverse_schema(definition, ctx) + if 'serialization' in def_ref_schema: + traverse_schema(def_ref_schema['serialization'], ctx) + traverse_metadata(def_ref_schema, ctx) + else: + # The `'definition-ref'` schema was already encountered, meaning + # the previously encountered schema (and this one) can't be inlined: + ctx.collected_references[schema_ref] = None + + +def traverse_schema(schema: AllSchemas, context: GatherContext) -> None: + # TODO When we drop 3.9, use a match statement to get better type checking and remove + # file-level type ignore. + # (the `'type'` could also be fetched in every `if/elif` statement, but this alters performance). + schema_type = schema['type'] + + if schema_type == 'definition-ref': + traverse_definition_ref(schema, context) + # `traverse_definition_ref` handles the possible serialization and metadata schemas: + return + elif schema_type == 'definitions': + traverse_schema(schema['schema'], context) + for definition in schema['definitions']: + traverse_schema(definition, context) + elif schema_type in {'list', 'set', 'frozenset', 'generator'}: + if 'items_schema' in schema: + traverse_schema(schema['items_schema'], context) + elif schema_type == 'tuple': + if 'items_schema' in schema: + for s in schema['items_schema']: + traverse_schema(s, context) + elif schema_type == 'dict': + if 'keys_schema' in schema: + traverse_schema(schema['keys_schema'], context) + if 'values_schema' in schema: + traverse_schema(schema['values_schema'], context) + elif schema_type == 'union': + for choice in iter_union_choices(schema): + traverse_schema(choice, context) + elif schema_type == 'tagged-union': + for v in schema['choices'].values(): + traverse_schema(v, context) + elif schema_type == 'chain': + for step in schema['steps']: + traverse_schema(step, context) + elif schema_type == 'lax-or-strict': + traverse_schema(schema['lax_schema'], context) + traverse_schema(schema['strict_schema'], context) + elif schema_type == 'json-or-python': + traverse_schema(schema['json_schema'], context) + traverse_schema(schema['python_schema'], context) + elif schema_type in {'model-fields', 'typed-dict'}: + if 'extras_schema' in schema: + traverse_schema(schema['extras_schema'], context) + if 'computed_fields' in schema: + for s in schema['computed_fields']: + traverse_schema(s, context) + for s in schema['fields'].values(): + traverse_schema(s, context) + elif schema_type == 'dataclass-args': + if 'computed_fields' in schema: + for s in schema['computed_fields']: + traverse_schema(s, context) + for s in schema['fields']: + traverse_schema(s, context) + elif schema_type == 'arguments': + for s in schema['arguments_schema']: + traverse_schema(s['schema'], context) + if 'var_args_schema' in schema: + traverse_schema(schema['var_args_schema'], context) + if 'var_kwargs_schema' in schema: + traverse_schema(schema['var_kwargs_schema'], context) + elif schema_type == 'arguments-v3': + for s in schema['arguments_schema']: + traverse_schema(s['schema'], context) + elif schema_type == 'call': + traverse_schema(schema['arguments_schema'], context) + if 'return_schema' in schema: + traverse_schema(schema['return_schema'], context) + elif schema_type == 'computed-field': + traverse_schema(schema['return_schema'], context) + elif schema_type == 'function-before': + if 'schema' in schema: + traverse_schema(schema['schema'], context) + if 'json_schema_input_schema' in schema: + traverse_schema(schema['json_schema_input_schema'], context) + elif schema_type == 'function-plain': + # TODO duplicate schema types for serializers and validators, needs to be deduplicated. + if 'return_schema' in schema: + traverse_schema(schema['return_schema'], context) + if 'json_schema_input_schema' in schema: + traverse_schema(schema['json_schema_input_schema'], context) + elif schema_type == 'function-wrap': + # TODO duplicate schema types for serializers and validators, needs to be deduplicated. + if 'return_schema' in schema: + traverse_schema(schema['return_schema'], context) + if 'schema' in schema: + traverse_schema(schema['schema'], context) + if 'json_schema_input_schema' in schema: + traverse_schema(schema['json_schema_input_schema'], context) + else: + if 'schema' in schema: + traverse_schema(schema['schema'], context) + + if 'serialization' in schema: + traverse_schema(schema['serialization'], context) + traverse_metadata(schema, context) + + +def gather_schemas_for_cleaning(schema: CoreSchema, definitions: dict[str, CoreSchema]) -> GatherResult: + """Traverse the core schema and definitions and return the necessary information for schema cleaning. + + During the core schema traversing, any `'definition-ref'` schema is: + + - Validated: the reference must point to an existing definition. If this is not the case, a + `MissingDefinitionError` exception is raised. + - Stored in the context: the actual reference is stored in the context. Depending on whether + the `'definition-ref'` schema is encountered more that once, the schema itself is also + saved in the context to be inlined (i.e. replaced by the definition it points to). + """ + context = GatherContext(definitions) + traverse_schema(schema, context) + + return { + 'collected_references': context.collected_references, + 'deferred_discriminator_schemas': context.deferred_discriminator_schemas, + } diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py new file mode 100644 index 0000000..b231a82 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py @@ -0,0 +1,125 @@ +"""Types and utility functions used by various other internal tools.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable, Literal + +from pydantic_core import core_schema + +from ..annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler + +if TYPE_CHECKING: + from ..json_schema import GenerateJsonSchema, JsonSchemaValue + from ._core_utils import CoreSchemaOrField + from ._generate_schema import GenerateSchema + from ._namespace_utils import NamespacesTuple + + GetJsonSchemaFunction = Callable[[CoreSchemaOrField, GetJsonSchemaHandler], JsonSchemaValue] + HandlerOverride = Callable[[CoreSchemaOrField], JsonSchemaValue] + + +class GenerateJsonSchemaHandler(GetJsonSchemaHandler): + """JsonSchemaHandler implementation that doesn't do ref unwrapping by default. + + This is used for any Annotated metadata so that we don't end up with conflicting + modifications to the definition schema. + + Used internally by Pydantic, please do not rely on this implementation. + See `GetJsonSchemaHandler` for the handler API. + """ + + def __init__(self, generate_json_schema: GenerateJsonSchema, handler_override: HandlerOverride | None) -> None: + self.generate_json_schema = generate_json_schema + self.handler = handler_override or generate_json_schema.generate_inner + self.mode = generate_json_schema.mode + + def __call__(self, core_schema: CoreSchemaOrField, /) -> JsonSchemaValue: + return self.handler(core_schema) + + def resolve_ref_schema(self, maybe_ref_json_schema: JsonSchemaValue) -> JsonSchemaValue: + """Resolves `$ref` in the json schema. + + This returns the input json schema if there is no `$ref` in json schema. + + Args: + maybe_ref_json_schema: The input json schema that may contains `$ref`. + + Returns: + Resolved json schema. + + Raises: + LookupError: If it can't find the definition for `$ref`. + """ + if '$ref' not in maybe_ref_json_schema: + return maybe_ref_json_schema + ref = maybe_ref_json_schema['$ref'] + json_schema = self.generate_json_schema.get_schema_from_definitions(ref) + if json_schema is None: + raise LookupError( + f'Could not find a ref for {ref}.' + ' Maybe you tried to call resolve_ref_schema from within a recursive model?' + ) + return json_schema + + +class CallbackGetCoreSchemaHandler(GetCoreSchemaHandler): + """Wrapper to use an arbitrary function as a `GetCoreSchemaHandler`. + + Used internally by Pydantic, please do not rely on this implementation. + See `GetCoreSchemaHandler` for the handler API. + """ + + def __init__( + self, + handler: Callable[[Any], core_schema.CoreSchema], + generate_schema: GenerateSchema, + ref_mode: Literal['to-def', 'unpack'] = 'to-def', + ) -> None: + self._handler = handler + self._generate_schema = generate_schema + self._ref_mode = ref_mode + + def __call__(self, source_type: Any, /) -> core_schema.CoreSchema: + schema = self._handler(source_type) + if self._ref_mode == 'to-def': + ref = schema.get('ref') + if ref is not None: + return self._generate_schema.defs.create_definition_reference_schema(schema) + return schema + else: # ref_mode = 'unpack' + return self.resolve_ref_schema(schema) + + def _get_types_namespace(self) -> NamespacesTuple: + return self._generate_schema._types_namespace + + def generate_schema(self, source_type: Any, /) -> core_schema.CoreSchema: + return self._generate_schema.generate_schema(source_type) + + @property + def field_name(self) -> str | None: + return self._generate_schema.field_name_stack.get() + + def resolve_ref_schema(self, maybe_ref_schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + """Resolves reference in the core schema. + + Args: + maybe_ref_schema: The input core schema that may contains reference. + + Returns: + Resolved core schema. + + Raises: + LookupError: If it can't find the definition for reference. + """ + if maybe_ref_schema['type'] == 'definition-ref': + ref = maybe_ref_schema['schema_ref'] + definition = self._generate_schema.defs.get_schema_from_ref(ref) + if definition is None: + raise LookupError( + f'Could not find a ref for {ref}.' + ' Maybe you tried to call resolve_ref_schema from within a recursive model?' + ) + return definition + elif maybe_ref_schema['type'] == 'definitions': + return self.resolve_ref_schema(maybe_ref_schema['schema']) + return maybe_ref_schema diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py new file mode 100644 index 0000000..a4058e0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import collections +import collections.abc +import typing +from typing import Any + +from pydantic_core import PydanticOmit, core_schema + +SEQUENCE_ORIGIN_MAP: dict[Any, Any] = { + typing.Deque: collections.deque, # noqa: UP006 + collections.deque: collections.deque, + list: list, + typing.List: list, # noqa: UP006 + tuple: tuple, + typing.Tuple: tuple, # noqa: UP006 + set: set, + typing.AbstractSet: set, + typing.Set: set, # noqa: UP006 + frozenset: frozenset, + typing.FrozenSet: frozenset, # noqa: UP006 + typing.Sequence: list, + typing.MutableSequence: list, + typing.MutableSet: set, + # this doesn't handle subclasses of these + # parametrized typing.Set creates one of these + collections.abc.MutableSet: set, + collections.abc.Set: frozenset, +} + + +def serialize_sequence_via_list( + v: Any, handler: core_schema.SerializerFunctionWrapHandler, info: core_schema.SerializationInfo +) -> Any: + items: list[Any] = [] + + mapped_origin = SEQUENCE_ORIGIN_MAP.get(type(v), None) + if mapped_origin is None: + # we shouldn't hit this branch, should probably add a serialization error or something + return v + + for index, item in enumerate(v): + try: + v = handler(item, index) + except PydanticOmit: # noqa: PERF203 + pass + else: + items.append(v) + + if info.mode_is_json(): + return items + else: + return mapped_origin(items) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py new file mode 100644 index 0000000..3b0c5ae --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py @@ -0,0 +1,189 @@ +from __future__ import annotations + +import dataclasses +from inspect import Parameter, Signature +from typing import TYPE_CHECKING, Any, Callable + +from pydantic_core import PydanticUndefined + +from ._typing_extra import signature_no_eval +from ._utils import is_valid_identifier + +if TYPE_CHECKING: + from ..config import ExtraValues + from ..fields import FieldInfo + + +# Copied over from stdlib dataclasses +class _HAS_DEFAULT_FACTORY_CLASS: + def __repr__(self): + return '' + + +_HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() + + +def _field_name_for_signature(field_name: str, field_info: FieldInfo) -> str: + """Extract the correct name to use for the field when generating a signature. + + Assuming the field has a valid alias, this will return the alias. Otherwise, it will return the field name. + First priority is given to the alias, then the validation_alias, then the field name. + + Args: + field_name: The name of the field + field_info: The corresponding FieldInfo object. + + Returns: + The correct name to use when generating a signature. + """ + if isinstance(field_info.alias, str) and is_valid_identifier(field_info.alias): + return field_info.alias + if isinstance(field_info.validation_alias, str) and is_valid_identifier(field_info.validation_alias): + return field_info.validation_alias + + return field_name + + +def _process_param_defaults(param: Parameter) -> Parameter: + """Modify the signature for a parameter in a dataclass where the default value is a FieldInfo instance. + + Args: + param (Parameter): The parameter + + Returns: + Parameter: The custom processed parameter + """ + from ..fields import FieldInfo + + param_default = param.default + if isinstance(param_default, FieldInfo): + annotation = param.annotation + # Replace the annotation if appropriate + # inspect does "clever" things to show annotations as strings because we have + # `from __future__ import annotations` in main, we don't want that + if annotation == 'Any': + annotation = Any + + # Replace the field default + default = param_default.default + if default is PydanticUndefined: + if param_default.default_factory is None: + default = Signature.empty + else: + # this is used by dataclasses to indicate a factory exists: + default = dataclasses._HAS_DEFAULT_FACTORY # type: ignore + return param.replace( + annotation=annotation, name=_field_name_for_signature(param.name, param_default), default=default + ) + return param + + +def _generate_signature_parameters( # noqa: C901 (ignore complexity, could use a refactor) + init: Callable[..., None], + fields: dict[str, FieldInfo], + validate_by_name: bool, + extra: ExtraValues | None, +) -> dict[str, Parameter]: + """Generate a mapping of parameter names to Parameter objects for a pydantic BaseModel or dataclass.""" + from itertools import islice + + present_params = signature_no_eval(init).parameters.values() + merged_params: dict[str, Parameter] = {} + var_kw = None + use_var_kw = False + + for param in islice(present_params, 1, None): # skip self arg + # inspect does "clever" things to show annotations as strings because we have + # `from __future__ import annotations` in main, we don't want that + if fields.get(param.name): + # exclude params with init=False + if getattr(fields[param.name], 'init', True) is False: + continue + param = param.replace(name=_field_name_for_signature(param.name, fields[param.name])) + if param.annotation == 'Any': + param = param.replace(annotation=Any) + if param.kind is param.VAR_KEYWORD: + var_kw = param + continue + merged_params[param.name] = param + + if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through + allow_names = validate_by_name + for field_name, field in fields.items(): + # when alias is a str it should be used for signature generation + param_name = _field_name_for_signature(field_name, field) + + if field_name in merged_params or param_name in merged_params: + continue + + if not is_valid_identifier(param_name): + if allow_names: + param_name = field_name + else: + use_var_kw = True + continue + + if field.is_required(): + default = Parameter.empty + elif field.default_factory is not None: + # Mimics stdlib dataclasses: + default = _HAS_DEFAULT_FACTORY + else: + default = field.default + merged_params[param_name] = Parameter( + param_name, + Parameter.KEYWORD_ONLY, + annotation=field.rebuild_annotation(), + default=default, + ) + + if extra == 'allow': + use_var_kw = True + + if var_kw and use_var_kw: + # Make sure the parameter for extra kwargs + # does not have the same name as a field + default_model_signature = [ + ('self', Parameter.POSITIONAL_ONLY), + ('data', Parameter.VAR_KEYWORD), + ] + if [(p.name, p.kind) for p in present_params] == default_model_signature: + # if this is the standard model signature, use extra_data as the extra args name + var_kw_name = 'extra_data' + else: + # else start from var_kw + var_kw_name = var_kw.name + + # generate a name that's definitely unique + while var_kw_name in fields: + var_kw_name += '_' + merged_params[var_kw_name] = var_kw.replace(name=var_kw_name) + + return merged_params + + +def generate_pydantic_signature( + init: Callable[..., None], + fields: dict[str, FieldInfo], + validate_by_name: bool, + extra: ExtraValues | None, + is_dataclass: bool = False, +) -> Signature: + """Generate signature for a pydantic BaseModel or dataclass. + + Args: + init: The class init. + fields: The model fields. + validate_by_name: The `validate_by_name` value of the config. + extra: The `extra` value of the config. + is_dataclass: Whether the model is a dataclass. + + Returns: + The dataclass/BaseModel subclass signature. + """ + merged_params = _generate_signature_parameters(init, fields, validate_by_name, extra) + + if is_dataclass: + merged_params = {k: _process_param_defaults(v) for k, v in merged_params.items()} + + return Signature(parameters=list(merged_params.values()), return_annotation=None) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py new file mode 100644 index 0000000..c217f11 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py @@ -0,0 +1,785 @@ +"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module.""" + +from __future__ import annotations + +import collections.abc +import re +import sys +import types +import typing +from functools import partial +from inspect import Signature, signature +from typing import TYPE_CHECKING, Any, Callable, cast + +import typing_extensions +from typing_extensions import deprecated, get_args, get_origin +from typing_inspection import typing_objects +from typing_inspection.introspection import is_union_origin + +from pydantic.version import version_short + +from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of + +if sys.version_info < (3, 10): + NoneType = type(None) + EllipsisType = type(Ellipsis) +else: + from types import EllipsisType as EllipsisType + from types import NoneType as NoneType + +if sys.version_info >= (3, 14): + import annotationlib + +if TYPE_CHECKING: + from pydantic import BaseModel + from pydantic.fields import FieldInfo + +# As per https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types, +# always check for both `typing` and `typing_extensions` variants of a typing construct. +# (this is implemented differently than the suggested approach in the `typing_extensions` +# docs for performance). + + +_t_annotated = typing.Annotated +_te_annotated = typing_extensions.Annotated + + +def is_annotated(tp: Any, /) -> bool: + """Return whether the provided argument is a `Annotated` special form. + + ```python {test="skip" lint="skip"} + is_annotated(Annotated[int, ...]) + #> True + ``` + """ + origin = get_origin(tp) + return origin is _t_annotated or origin is _te_annotated + + +def annotated_type(tp: Any, /) -> Any | None: + """Return the type of the `Annotated` special form, or `None`.""" + return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None + + +def unpack_type(tp: Any, /) -> Any | None: + """Return the type wrapped by the `Unpack` special form, or `None`.""" + return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None + + +def is_hashable(tp: Any, /) -> bool: + """Return whether the provided argument is the `Hashable` class. + + ```python {test="skip" lint="skip"} + is_hashable(Hashable) + #> True + ``` + """ + # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, + # hence the second check: + return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable + + +def is_callable(tp: Any, /) -> bool: + """Return whether the provided argument is a `Callable`, parametrized or not. + + ```python {test="skip" lint="skip"} + is_callable(Callable[[int], str]) + #> True + is_callable(typing.Callable) + #> True + is_callable(collections.abc.Callable) + #> True + ``` + """ + # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, + # hence the second check: + return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable + + +_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') + + +def is_classvar_annotation(tp: Any, /) -> bool: + """Return whether the provided argument represents a class variable annotation. + + Although not explicitly stated by the typing specification, `ClassVar` can be used + inside `Annotated` and as such, this function checks for this specific scenario. + + Because this function is used to detect class variables before evaluating forward references + (or because evaluation failed), we also implement a naive regex match implementation. This is + required because class variables are inspected before fields are collected, so we try to be + as accurate as possible. + """ + if typing_objects.is_classvar(tp): + return True + + origin = get_origin(tp) + + if typing_objects.is_classvar(origin): + return True + + if typing_objects.is_annotated(origin): + annotated_type = tp.__origin__ + if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): + return True + + str_ann: str | None = None + if isinstance(tp, typing.ForwardRef): + str_ann = tp.__forward_arg__ + if isinstance(tp, str): + str_ann = tp + + if str_ann is not None and _classvar_re.match(str_ann): + # stdlib dataclasses do something similar, although a bit more advanced + # (see `dataclass._is_type`). + return True + + return False + + +_t_final = typing.Final +_te_final = typing_extensions.Final + + +# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms: +def is_finalvar(tp: Any, /) -> bool: + """Return whether the provided argument is a `Final` special form, parametrized or not. + + ```python {test="skip" lint="skip"} + is_finalvar(Final[int]) + #> True + is_finalvar(Final) + #> True + """ + # Final is not necessarily parametrized: + if tp is _t_final or tp is _te_final: + return True + origin = get_origin(tp) + return origin is _t_final or origin is _te_final + + +_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) + + +def is_none_type(tp: Any, /) -> bool: + """Return whether the argument represents the `None` type as part of an annotation. + + ```python {test="skip" lint="skip"} + is_none_type(None) + #> True + is_none_type(NoneType) + #> True + is_none_type(Literal[None]) + #> True + is_none_type(type[None]) + #> False + """ + return tp in _NONE_TYPES + + +def is_namedtuple(tp: Any, /) -> bool: + """Return whether the provided argument is a named tuple class. + + The class can be created using `typing.NamedTuple` or `collections.namedtuple`. + Parametrized generic classes are *not* assumed to be named tuples. + """ + from ._utils import lenient_issubclass # circ. import + + return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') + + +# TODO In 2.12, delete this export. It is currently defined only to not break +# pydantic-settings which relies on it: +origin_is_union = is_union_origin + + +def is_generic_alias(tp: Any, /) -> bool: + return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] + + +# TODO: Ideally, we should avoid relying on the private `typing` constructs: + +if sys.version_info < (3, 10): + WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # pyright: ignore[reportAttributeAccessIssue] +else: + WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias, types.UnionType) # pyright: ignore[reportAttributeAccessIssue] + + +# Similarly, we shouldn't rely on this `_Final` class, which is even more private than `_GenericAlias`: +typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] + + +### Annotation evaluations functions: + + +def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: + """Fetch the local namespace of the parent frame where this function is called. + + Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace, + such as assignments inside a function. Using the standard library tools, it is currently not possible to resolve + such annotations: + + ```python {lint="skip" test="skip"} + from typing import get_type_hints + + def func() -> None: + Alias = int + + class C: + a: 'Alias' + + # Raises a `NameError: 'Alias' is not defined` + get_type_hints(C) + ``` + + Pydantic uses this function when a Pydantic model is being defined to fetch the parent frame locals. However, + this only allows us to fetch the parent frame namespace and not other parents (e.g. a model defined in a function, + itself defined in another function). Inspecting the next outer frames (using `f_back`) is not reliable enough + (see https://discuss.python.org/t/20659). + + Because this function is mostly used to better resolve forward annotations, nothing is returned if the parent frame's + code object is defined at the module level. In this case, the locals of the frame will be the same as the module + globals where the class is defined (see `_namespace_utils.get_module_ns_of`). However, if you still want to fetch + the module globals (e.g. when rebuilding a model, where the frame where the rebuild call is performed might contain + members that you want to use for forward annotations evaluation), you can use the `force` parameter. + + Args: + parent_depth: The depth at which to get the frame. Defaults to 2, meaning the parent frame where this function + is called will be used. + force: Whether to always return the frame locals, even if the frame's code object is defined at the module level. + + Returns: + The locals of the namespace, or `None` if it was skipped as per the described logic. + """ + frame = sys._getframe(parent_depth) + + if frame.f_code.co_name.startswith('`, + # and we need to skip this frame as it is irrelevant. + frame = cast(types.FrameType, frame.f_back) # guaranteed to not be `None` + + # note, we don't copy frame.f_locals here (or during the last return call), because we don't expect the namespace to be + # modified down the line if this becomes a problem, we could implement some sort of frozen mapping structure to enforce this. + if force: + return frame.f_locals + + # If either of the following conditions are true, the class is defined at the top module level. + # To better understand why we need both of these checks, see + # https://github.com/pydantic/pydantic/pull/10113#discussion_r1714981531. + if frame.f_back is None or frame.f_code.co_name == '': + return None + + return frame.f_locals + + +def _type_convert(arg: Any) -> Any: + """Convert `None` to `NoneType` and strings to `ForwardRef` instances. + + This is a backport of the private `typing._type_convert` function. When + evaluating a type, `ForwardRef._evaluate` ends up being called, and is + responsible for making this conversion. However, we still have to apply + it for the first argument passed to our type evaluation functions, similarly + to the `typing.get_type_hints` function. + """ + if arg is None: + return NoneType + if isinstance(arg, str): + # Like `typing.get_type_hints`, assume the arg can be in any context, + # hence the proper `is_argument` and `is_class` args: + return _make_forward_ref(arg, is_argument=False, is_class=True) + return arg + + +def safe_get_annotations(obj: Any) -> dict[str, Any]: + """Get the annotations for the provided object, accounting for potential deferred forward references. + + Starting with Python 3.14, accessing the `__annotations__` attribute might raise a `NameError` if + a referenced symbol isn't defined yet. In this case, we return the annotation in the *forward ref* + format. + """ + if sys.version_info >= (3, 14): + return annotationlib.get_annotations(obj, format=annotationlib.Format.FORWARDREF) + else: + # TODO just do getattr(obj, '__annotations__', {}) when dropping support for Python 3.9: + if isinstance(obj, type): + return obj.__dict__.get('__annotations__', {}) + else: + return getattr(obj, '__annotations__', {}) + + +def get_model_type_hints( + model_class: type[BaseModel], + *, + ns_resolver: NsResolver | None = None, +) -> dict[str, tuple[Any, bool]]: + """Collect annotations from a Pydantic model class, including those from parent classes. + + Args: + model_class: The Pydantic model class to inspect. + ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. + + Returns: + A dictionary mapping annotation names to a two-tuple: the first element is the evaluated + type or the original annotation if a `NameError` occurred, the second element is a boolean + indicating if whether the evaluation succeeded. + """ + hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} + ns_resolver = ns_resolver or NsResolver() + + for base in reversed(model_class.__mro__[:-1]): + # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals + # from the ns_resolver, but we want to be able to know which specific field failed + # to evaluate: + ann = safe_get_annotations(base) + + if not ann: + continue + + with ns_resolver.push(base): + base_model_fields: dict[str, FieldInfo] | None = base.__dict__.get('__pydantic_fields__') + + for name, value in ann.items(): + if name.startswith('_'): + globalns, localns = ns_resolver.types_namespace + + # For private attributes, we only need the annotation to detect the `ClassVar` special form. + # For this reason, we still try to evaluate it, but we also catch any possible exception (on + # top of the `NameError`s caught in `try_eval_type`) that could happen so that users are free + # to use any kind of forward annotation for private fields (e.g. circular imports, new typing + # syntax, etc). + try: + hints[name] = try_eval_type(value, globalns, localns) + except Exception: + hints[name] = (value, False) + else: + if base_model_fields is not None and name in base_model_fields: + # Avoid unnecessarily evaluating annotations from parent models, as we'll end up + # copying the `FieldInfo` instance from it anyway if we need to. + # We use the `annotation` attribute here, but in reality could put anything here, + # As we are guaranteed to not make use of it: + hints[name] = (base_model_fields[name].annotation, True) + else: + globalns, localns = ns_resolver.types_namespace + + hints[name] = try_eval_type(value, globalns, localns) + + return hints + + +def get_cls_type_hints( + obj: type[Any], + *, + ns_resolver: NsResolver | None = None, +) -> dict[str, Any]: + """Collect annotations from a class, including those from parent classes. + + Args: + obj: The class to inspect. + ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. + """ + hints: dict[str, Any] = {} + ns_resolver = ns_resolver or NsResolver() + + for base in reversed(obj.__mro__): + # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals + # from the ns_resolver, but we want to be able to know which specific field failed + # to evaluate: + ann = safe_get_annotations(base) + + if not ann: + continue + + with ns_resolver.push(base): + globalns, localns = ns_resolver.types_namespace + for name, value in ann.items(): + hints[name] = eval_type(value, globalns, localns) + return hints + + +def try_eval_type( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, +) -> tuple[Any, bool]: + """Try evaluating the annotation using the provided namespaces. + + Args: + value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance + of `str`, it will be converted to a `ForwardRef`. + localns: The global namespace to use during annotation evaluation. + globalns: The local namespace to use during annotation evaluation. + + Returns: + A two-tuple containing the possibly evaluated type and a boolean indicating + whether the evaluation succeeded or not. + """ + value = _type_convert(value) + + try: + return eval_type_backport(value, globalns, localns), True + except NameError: + return value, False + + +def eval_type( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, +) -> Any: + """Evaluate the annotation using the provided namespaces. + + Args: + value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance + of `str`, it will be converted to a `ForwardRef`. + localns: The global namespace to use during annotation evaluation. + globalns: The local namespace to use during annotation evaluation. + """ + value = _type_convert(value) + return eval_type_backport(value, globalns, localns) + + +@deprecated( + '`eval_type_lenient` is deprecated, use `try_eval_type` instead.', + category=None, +) +def eval_type_lenient( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, +) -> Any: + ev, _ = try_eval_type(value, globalns, localns) + return ev + + +def eval_type_backport( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, + type_params: tuple[Any, ...] | None = None, +) -> Any: + """An enhanced version of `typing._eval_type` which will fall back to using the `eval_type_backport` + package if it's installed to let older Python versions use newer typing constructs. + + Specifically, this transforms `X | Y` into `typing.Union[X, Y]` and `list[X]` into `typing.List[X]` + (as well as all the types made generic in PEP 585) if the original syntax is not supported in the + current Python version. + + This function will also display a helpful error if the value passed fails to evaluate. + """ + try: + return _eval_type_backport(value, globalns, localns, type_params) + except TypeError as e: + if 'Unable to evaluate type annotation' in str(e): + raise + + # If it is a `TypeError` and value isn't a `ForwardRef`, it would have failed during annotation definition. + # Thus we assert here for type checking purposes: + assert isinstance(value, typing.ForwardRef) + + message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' + if sys.version_info >= (3, 11): + e.add_note(message) + raise + else: + raise TypeError(message) from e + except RecursionError as e: + # TODO ideally recursion errors should be checked in `eval_type` above, but `eval_type_backport` + # is used directly in some places. + message = ( + "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), " + 'consider using PEP 695 type aliases instead. For more details, refer to the documentation: ' + f'https://docs.pydantic.dev/{version_short()}/concepts/types/#named-recursive-types' + ) + if sys.version_info >= (3, 11): + e.add_note(message) + raise + else: + raise RecursionError(f'{e.args[0]}\n{message}') + + +def _eval_type_backport( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, + type_params: tuple[Any, ...] | None = None, +) -> Any: + try: + return _eval_type(value, globalns, localns, type_params) + except TypeError as e: + if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): + raise + + try: + from eval_type_backport import eval_type_backport + except ImportError: + raise TypeError( + f'Unable to evaluate type annotation {value.__forward_arg__!r}. If you are making use ' + 'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting ' + 'since Python 3.9), you should either replace the use of new syntax with the existing ' + '`typing` constructs or install the `eval_type_backport` package.' + ) from e + + return eval_type_backport( + value, + globalns, + localns, # pyright: ignore[reportArgumentType], waiting on a new `eval_type_backport` release. + try_default=False, + ) + + +def _eval_type( + value: Any, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, + type_params: tuple[Any, ...] | None = None, +) -> Any: + if sys.version_info >= (3, 14): + # Starting in 3.14, `_eval_type()` does *not* apply `_type_convert()` + # anymore. This means the `None` -> `type(None)` conversion does not apply: + evaluated = typing._eval_type( # type: ignore + value, + globalns, + localns, + type_params=type_params, + # This is relevant when evaluating types from `TypedDict` classes, where string annotations + # are automatically converted to `ForwardRef` instances with a module set. In this case, + # Our `globalns` is irrelevant and we need to indicate `typing._eval_type()` that it should + # infer it from the `ForwardRef.__forward_module__` attribute instead (`typing.get_type_hints()` + # does the same). Note that this would probably be unnecessary if we properly iterated over the + # `__orig_bases__` for TypedDicts in `get_cls_type_hints()`: + prefer_fwd_module=True, + ) + if evaluated is None: + evaluated = type(None) + return evaluated + elif sys.version_info >= (3, 13): + return typing._eval_type( # type: ignore + value, globalns, localns, type_params=type_params + ) + else: + return typing._eval_type( # type: ignore + value, globalns, localns + ) + + +def is_backport_fixable_error(e: TypeError) -> bool: + msg = str(e) + + return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') + + +def signature_no_eval(f: Callable[..., Any]) -> Signature: + """Get the signature of a callable without evaluating any annotations.""" + if sys.version_info >= (3, 14): + from annotationlib import Format + + return signature(f, annotation_format=Format.FORWARDREF) + else: + return signature(f) + + +def get_function_type_hints( + function: Callable[..., Any], + *, + include_keys: set[str] | None = None, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, +) -> dict[str, Any]: + """Return type hints for a function. + + This is similar to the `typing.get_type_hints` function, with a few differences: + - Support `functools.partial` by using the underlying `func` attribute. + - Do not wrap type annotation of a parameter with `Optional` if it has a default value of `None` + (related bug: https://github.com/python/cpython/issues/90353, only fixed in 3.11+). + """ + if isinstance(function, partial): + annotations = safe_get_annotations(function.func) + else: + annotations = safe_get_annotations(function) + + if globalns is None: + globalns = get_module_ns_of(function) + type_params: tuple[Any, ...] | None = None + if localns is None: + # If localns was specified, it is assumed to already contain type params. This is because + # Pydantic has more advanced logic to do so (see `_namespace_utils.ns_for_function`). + type_params = getattr(function, '__type_params__', ()) + + type_hints = {} + for name, value in annotations.items(): + if include_keys is not None and name not in include_keys: + continue + if value is None: + value = NoneType + elif isinstance(value, str): + value = _make_forward_ref(value) + + type_hints[name] = eval_type_backport(value, globalns, localns, type_params) + + return type_hints + + +# TODO use typing.ForwardRef directly when we stop supporting 3.9: +if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): + + def _make_forward_ref( + arg: Any, + is_argument: bool = True, + *, + is_class: bool = False, + ) -> typing.ForwardRef: + """Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions. + The `module` argument is omitted as it breaks <3.9.8, =3.10.0 and isn't used in the calls below. + + See https://github.com/python/cpython/pull/28560 for some background. + The backport happened on 3.9.8, see: + https://github.com/pydantic/pydantic/discussions/6244#discussioncomment-6275458, + and on 3.10.1 for the 3.10 branch, see: + https://github.com/pydantic/pydantic/issues/6912 + + Implemented as EAFP with memory. + """ + return typing.ForwardRef(arg, is_argument) # pyright: ignore[reportCallIssue] + +else: + _make_forward_ref = typing.ForwardRef # pyright: ignore[reportAssignmentType] + + +if sys.version_info >= (3, 10): + get_type_hints = typing.get_type_hints + +else: + """ + For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to + the implementation in CPython 3.10.8. + """ + + @typing.no_type_check + def get_type_hints( # noqa: C901 + obj: Any, + globalns: dict[str, Any] | None = None, + localns: dict[str, Any] | None = None, + include_extras: bool = False, + ) -> dict[str, Any]: # pragma: no cover + """Taken verbatim from python 3.10.8 unchanged, except: + * type annotations of the function definition above. + * prefixing `typing.` where appropriate + * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument. + + https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875 + + DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. + ====================================================== + + Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. For classes, the search + order is globals first then locals. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if getattr(obj, '__no_type_check__', None): + return {} + # Classes require a special treatment. + if isinstance(obj, type): + hints = {} + for base in reversed(obj.__mro__): + if globalns is None: + base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) + else: + base_globals = globalns + ann = base.__dict__.get('__annotations__', {}) + if isinstance(ann, types.GetSetDescriptorType): + ann = {} + base_locals = dict(vars(base)) if localns is None else localns + if localns is None and globalns is None: + # This is surprising, but required. Before Python 3.10, + # get_type_hints only evaluated the globalns of + # a class. To maintain backwards compatibility, we reverse + # the globalns and localns order so that eval() looks into + # *base_globals* first rather than *base_locals*. + # This only affects ForwardRefs. + base_globals, base_locals = base_locals, base_globals + for name, value in ann.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _make_forward_ref(value, is_argument=False, is_class=True) + + value = eval_type_backport(value, base_globals, base_locals) + hints[name] = value + if not include_extras and hasattr(typing, '_strip_annotations'): + return { + k: typing._strip_annotations(t) # type: ignore + for k, t in hints.items() + } + else: + return hints + + if globalns is None: + if isinstance(obj, types.ModuleType): + globalns = obj.__dict__ + else: + nsobj = obj + # Find globalns for the unwrapped object. + while hasattr(nsobj, '__wrapped__'): + nsobj = nsobj.__wrapped__ + globalns = getattr(nsobj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + hints = getattr(obj, '__annotations__', None) + if hints is None: + # Return empty annotations for something that _could_ have them. + if isinstance(obj, typing._allowed_types): # type: ignore + return {} + else: + raise TypeError(f'{obj!r} is not a module, class, method, or function.') + defaults = typing._get_defaults(obj) # type: ignore + hints = dict(hints) + for name, value in hints.items(): + if value is None: + value = type(None) + if isinstance(value, str): + # class-level forward refs were handled above, this must be either + # a module-level annotation or a function argument annotation + + value = _make_forward_ref( + value, + is_argument=not isinstance(obj, types.ModuleType), + is_class=False, + ) + value = eval_type_backport(value, globalns, localns) + if name in defaults and defaults[name] is None: + value = typing.Optional[value] + hints[name] = value + return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py new file mode 100644 index 0000000..7a0346d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py @@ -0,0 +1,446 @@ +"""Bucket of reusable internal utilities. + +This should be reduced as much as possible with functions only used in one place, moved to that place. +""" + +from __future__ import annotations as _annotations + +import dataclasses +import keyword +import sys +import warnings +import weakref +from collections import OrderedDict, defaultdict, deque +from collections.abc import Callable, Iterable, Mapping +from collections.abc import Set as AbstractSet +from copy import deepcopy +from functools import cached_property +from inspect import Parameter +from itertools import zip_longest +from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType +from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload + +from pydantic_core import MISSING, PydanticUndefined +from typing_extensions import TypeAlias, TypeGuard, deprecated + +from pydantic import PydanticDeprecatedSince211 + +from . import _repr, _typing_extra +from ._import_utils import import_cached_base_model + +if TYPE_CHECKING: + # TODO remove type error comments when we drop support for Python 3.9 + MappingIntStrAny: TypeAlias = Mapping[int, Any] | Mapping[str, Any] # pyright: ignore[reportGeneralTypeIssues] + AbstractSetIntStr: TypeAlias = AbstractSet[int] | AbstractSet[str] # pyright: ignore[reportGeneralTypeIssues] + from ..main import BaseModel + + +# these are types that are returned unchanged by deepcopy +IMMUTABLE_NON_COLLECTIONS_TYPES: set[type[Any]] = { + int, + float, + complex, + str, + bool, + bytes, + type, + _typing_extra.NoneType, + FunctionType, + BuiltinFunctionType, + LambdaType, + weakref.ref, + CodeType, + # note: including ModuleType will differ from behaviour of deepcopy by not producing error. + # It might be not a good idea in general, but considering that this function used only internally + # against default values of fields, this will allow to actually have a field with module as default value + ModuleType, + NotImplemented.__class__, + Ellipsis.__class__, +} + +# these are types that if empty, might be copied with simple copy() instead of deepcopy() +BUILTIN_COLLECTIONS: set[type[Any]] = { + list, + set, + tuple, + frozenset, + dict, + OrderedDict, + defaultdict, + deque, +} + + +def can_be_positional(param: Parameter) -> bool: + """Return whether the parameter accepts a positional argument. + + ```python {test="skip" lint="skip"} + def func(a, /, b, *, c): + pass + + params = inspect.signature(func).parameters + can_be_positional(params['a']) + #> True + can_be_positional(params['b']) + #> True + can_be_positional(params['c']) + #> False + ``` + """ + return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) + + +def sequence_like(v: Any) -> bool: + return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) + + +def lenient_isinstance(o: Any, class_or_tuple: type[Any] | tuple[type[Any], ...] | None) -> bool: # pragma: no cover + try: + return isinstance(o, class_or_tuple) # type: ignore[arg-type] + except TypeError: + return False + + +def lenient_issubclass(cls: Any, class_or_tuple: Any) -> bool: # pragma: no cover + try: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) + except TypeError: + if isinstance(cls, _typing_extra.WithArgsTypes): + return False + raise # pragma: no cover + + +def is_model_class(cls: Any) -> TypeGuard[type[BaseModel]]: + """Returns true if cls is a _proper_ subclass of BaseModel, and provides proper type-checking, + unlike raw calls to lenient_issubclass. + """ + BaseModel = import_cached_base_model() + + return lenient_issubclass(cls, BaseModel) and cls is not BaseModel + + +def is_valid_identifier(identifier: str) -> bool: + """Checks that a string is a valid identifier and not a Python keyword. + :param identifier: The identifier to test. + :return: True if the identifier is valid. + """ + return identifier.isidentifier() and not keyword.iskeyword(identifier) + + +KeyType = TypeVar('KeyType') + + +def deep_update(mapping: dict[KeyType, Any], *updating_mappings: dict[KeyType, Any]) -> dict[KeyType, Any]: + updated_mapping = mapping.copy() + for updating_mapping in updating_mappings: + for k, v in updating_mapping.items(): + if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict): + updated_mapping[k] = deep_update(updated_mapping[k], v) + else: + updated_mapping[k] = v + return updated_mapping + + +def update_not_none(mapping: dict[Any, Any], **update: Any) -> None: + mapping.update({k: v for k, v in update.items() if v is not None}) + + +T = TypeVar('T') + + +def unique_list( + input_list: list[T] | tuple[T, ...], + *, + name_factory: Callable[[T], str] = str, +) -> list[T]: + """Make a list unique while maintaining order. + We update the list if another one with the same name is set + (e.g. model validator overridden in subclass). + """ + result: list[T] = [] + result_names: list[str] = [] + for v in input_list: + v_name = name_factory(v) + if v_name not in result_names: + result_names.append(v_name) + result.append(v) + else: + result[result_names.index(v_name)] = v + + return result + + +class ValueItems(_repr.Representation): + """Class for more convenient calculation of excluded or included fields on values.""" + + __slots__ = ('_items', '_type') + + def __init__(self, value: Any, items: AbstractSetIntStr | MappingIntStrAny) -> None: + items = self._coerce_items(items) + + if isinstance(value, (list, tuple)): + items = self._normalize_indexes(items, len(value)) # type: ignore + + self._items: MappingIntStrAny = items # type: ignore + + def is_excluded(self, item: Any) -> bool: + """Check if item is fully excluded. + + :param item: key or index of a value + """ + return self.is_true(self._items.get(item)) + + def is_included(self, item: Any) -> bool: + """Check if value is contained in self._items. + + :param item: key or index of value + """ + return item in self._items + + def for_element(self, e: int | str) -> AbstractSetIntStr | MappingIntStrAny | None: + """:param e: key or index of element on value + :return: raw values for element if self._items is dict and contain needed element + """ + item = self._items.get(e) # type: ignore + return item if not self.is_true(item) else None + + def _normalize_indexes(self, items: MappingIntStrAny, v_length: int) -> dict[int | str, Any]: + """:param items: dict or set of indexes which will be normalized + :param v_length: length of sequence indexes of which will be + + >>> self._normalize_indexes({0: True, -2: True, -1: True}, 4) + {0: True, 2: True, 3: True} + >>> self._normalize_indexes({'__all__': True}, 4) + {0: True, 1: True, 2: True, 3: True} + """ + normalized_items: dict[int | str, Any] = {} + all_items = None + for i, v in items.items(): + if not (isinstance(v, Mapping) or isinstance(v, AbstractSet) or self.is_true(v)): + raise TypeError(f'Unexpected type of exclude value for index "{i}" {v.__class__}') + if i == '__all__': + all_items = self._coerce_value(v) + continue + if not isinstance(i, int): + raise TypeError( + 'Excluding fields from a sequence of sub-models or dicts must be performed index-wise: ' + 'expected integer keys or keyword "__all__"' + ) + normalized_i = v_length + i if i < 0 else i + normalized_items[normalized_i] = self.merge(v, normalized_items.get(normalized_i)) + + if not all_items: + return normalized_items + if self.is_true(all_items): + for i in range(v_length): + normalized_items.setdefault(i, ...) + return normalized_items + for i in range(v_length): + normalized_item = normalized_items.setdefault(i, {}) + if not self.is_true(normalized_item): + normalized_items[i] = self.merge(all_items, normalized_item) + return normalized_items + + @classmethod + def merge(cls, base: Any, override: Any, intersect: bool = False) -> Any: + """Merge a `base` item with an `override` item. + + Both `base` and `override` are converted to dictionaries if possible. + Sets are converted to dictionaries with the sets entries as keys and + Ellipsis as values. + + Each key-value pair existing in `base` is merged with `override`, + while the rest of the key-value pairs are updated recursively with this function. + + Merging takes place based on the "union" of keys if `intersect` is + set to `False` (default) and on the intersection of keys if + `intersect` is set to `True`. + """ + override = cls._coerce_value(override) + base = cls._coerce_value(base) + if override is None: + return base + if cls.is_true(base) or base is None: + return override + if cls.is_true(override): + return base if intersect else override + + # intersection or union of keys while preserving ordering: + if intersect: + merge_keys = [k for k in base if k in override] + [k for k in override if k in base] + else: + merge_keys = list(base) + [k for k in override if k not in base] + + merged: dict[int | str, Any] = {} + for k in merge_keys: + merged_item = cls.merge(base.get(k), override.get(k), intersect=intersect) + if merged_item is not None: + merged[k] = merged_item + + return merged + + @staticmethod + def _coerce_items(items: AbstractSetIntStr | MappingIntStrAny) -> MappingIntStrAny: + if isinstance(items, Mapping): + pass + elif isinstance(items, AbstractSet): + items = dict.fromkeys(items, ...) # type: ignore + else: + class_name = getattr(items, '__class__', '???') + raise TypeError(f'Unexpected type of exclude value {class_name}') + return items # type: ignore + + @classmethod + def _coerce_value(cls, value: Any) -> Any: + if value is None or cls.is_true(value): + return value + return cls._coerce_items(value) + + @staticmethod + def is_true(v: Any) -> bool: + return v is True or v is ... + + def __repr_args__(self) -> _repr.ReprArgs: + return [(None, self._items)] + + +if TYPE_CHECKING: + + def LazyClassAttribute(name: str, get_value: Callable[[], T]) -> T: ... + +else: + + class LazyClassAttribute: + """A descriptor exposing an attribute only accessible on a class (hidden from instances). + + The attribute is lazily computed and cached during the first access. + """ + + def __init__(self, name: str, get_value: Callable[[], Any]) -> None: + self.name = name + self.get_value = get_value + + @cached_property + def value(self) -> Any: + return self.get_value() + + def __get__(self, instance: Any, owner: type[Any]) -> None: + if instance is None: + return self.value + raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only') + + +Obj = TypeVar('Obj') + + +def smart_deepcopy(obj: Obj) -> Obj: + """Return type as is for immutable built-in types + Use obj.copy() for built-in empty collections + Use copy.deepcopy() for non-empty collections and unknown objects. + """ + if obj is MISSING or obj is PydanticUndefined: + return obj # pyright: ignore[reportReturnType] + obj_type = obj.__class__ + if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES: + return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway + try: + if not obj and obj_type in BUILTIN_COLLECTIONS: + # faster way for empty collections, no need to copy its members + return obj if obj_type is tuple else obj.copy() # tuple doesn't have copy method # type: ignore + except (TypeError, ValueError, RuntimeError): + # do we really dare to catch ALL errors? Seems a bit risky + pass + + return deepcopy(obj) # slowest way when we actually might need a deepcopy + + +_SENTINEL = object() + + +def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool: + """Check that the items of `left` are the same objects as those in `right`. + + >>> a, b = object(), object() + >>> all_identical([a, b, a], [a, b, a]) + True + >>> all_identical([a, b, [a]], [a, b, [a]]) # new list object, while "equal" is not "identical" + False + """ + for left_item, right_item in zip_longest(left, right, fillvalue=_SENTINEL): + if left_item is not right_item: + return False + return True + + +def get_first_not_none(a: Any, b: Any) -> Any: + """Return the first argument if it is not `None`, otherwise return the second argument.""" + return a if a is not None else b + + +@dataclasses.dataclass(frozen=True) +class SafeGetItemProxy: + """Wrapper redirecting `__getitem__` to `get` with a sentinel value as default + + This makes is safe to use in `operator.itemgetter` when some keys may be missing + """ + + # Define __slots__manually for performances + # @dataclasses.dataclass() only support slots=True in python>=3.10 + __slots__ = ('wrapped',) + + wrapped: Mapping[str, Any] + + def __getitem__(self, key: str, /) -> Any: + return self.wrapped.get(key, _SENTINEL) + + # required to pass the object to operator.itemgetter() instances due to a quirk of typeshed + # https://github.com/python/mypy/issues/13713 + # https://github.com/python/typeshed/pull/8785 + # Since this is typing-only, hide it in a typing.TYPE_CHECKING block + if TYPE_CHECKING: + + def __contains__(self, key: str, /) -> bool: + return self.wrapped.__contains__(key) + + +_ModelT = TypeVar('_ModelT', bound='BaseModel') +_RT = TypeVar('_RT') + + +class deprecated_instance_property(Generic[_ModelT, _RT]): + """A decorator exposing the decorated class method as a property, with a warning on instance access. + + This decorator takes a class method defined on the `BaseModel` class and transforms it into + an attribute. The attribute can be accessed on both the class and instances of the class. If accessed + via an instance, a deprecation warning is emitted stating that instance access will be removed in V3. + """ + + def __init__(self, fget: Callable[[type[_ModelT]], _RT], /) -> None: + # Note: fget should be a classmethod: + self.fget = fget + + @overload + def __get__(self, instance: None, objtype: type[_ModelT]) -> _RT: ... + @overload + @deprecated( + 'Accessing this attribute on the instance is deprecated, and will be removed in Pydantic V3. ' + 'Instead, you should access this attribute from the model class.', + category=None, + ) + def __get__(self, instance: _ModelT, objtype: type[_ModelT]) -> _RT: ... + def __get__(self, instance: _ModelT | None, objtype: type[_ModelT]) -> _RT: + if instance is not None: + # fmt: off + attr_name = ( + self.fget.__name__ + if sys.version_info >= (3, 10) + else self.fget.__func__.__name__ # pyright: ignore[reportFunctionMemberAccess] + ) + # fmt: on + warnings.warn( + f'Accessing the {attr_name!r} attribute on the instance is deprecated. ' + 'Instead, you should access this attribute from the model class.', + category=PydanticDeprecatedSince211, + stacklevel=2, + ) + return self.fget.__get__(instance, objtype)() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py new file mode 100644 index 0000000..49ccd4f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py @@ -0,0 +1,141 @@ +from __future__ import annotations as _annotations + +import functools +import inspect +from collections.abc import Awaitable +from functools import partial +from typing import Any, Callable + +import pydantic_core + +from ..config import ConfigDict +from ..plugin._schema_validator import create_schema_validator +from ._config import ConfigWrapper +from ._generate_schema import GenerateSchema, ValidateCallSupportedTypes +from ._namespace_utils import MappingNamespace, NsResolver, ns_for_function +from ._typing_extra import signature_no_eval + + +def extract_function_name(func: ValidateCallSupportedTypes) -> str: + """Extract the name of a `ValidateCallSupportedTypes` object.""" + return f'partial({func.func.__name__})' if isinstance(func, functools.partial) else func.__name__ + + +def extract_function_qualname(func: ValidateCallSupportedTypes) -> str: + """Extract the qualname of a `ValidateCallSupportedTypes` object.""" + return f'partial({func.func.__qualname__})' if isinstance(func, functools.partial) else func.__qualname__ + + +def update_wrapper_attributes(wrapped: ValidateCallSupportedTypes, wrapper: Callable[..., Any]): + """Update the `wrapper` function with the attributes of the `wrapped` function. Return the updated function.""" + if inspect.iscoroutinefunction(wrapped): + + @functools.wraps(wrapped) + async def wrapper_function(*args, **kwargs): # type: ignore + return await wrapper(*args, **kwargs) + else: + + @functools.wraps(wrapped) + def wrapper_function(*args, **kwargs): + return wrapper(*args, **kwargs) + + # We need to manually update this because `partial` object has no `__name__` and `__qualname__`. + wrapper_function.__name__ = extract_function_name(wrapped) + wrapper_function.__qualname__ = extract_function_qualname(wrapped) + wrapper_function.raw_function = wrapped # type: ignore + + return wrapper_function + + +class ValidateCallWrapper: + """This is a wrapper around a function that validates the arguments passed to it, and optionally the return value.""" + + __slots__ = ( + 'function', + 'validate_return', + 'schema_type', + 'module', + 'qualname', + 'ns_resolver', + 'config_wrapper', + '__pydantic_complete__', + '__pydantic_validator__', + '__return_pydantic_validator__', + ) + + def __init__( + self, + function: ValidateCallSupportedTypes, + config: ConfigDict | None, + validate_return: bool, + parent_namespace: MappingNamespace | None, + ) -> None: + self.function = function + self.validate_return = validate_return + if isinstance(function, partial): + self.schema_type = function.func + self.module = function.func.__module__ + else: + self.schema_type = function + self.module = function.__module__ + self.qualname = extract_function_qualname(function) + + self.ns_resolver = NsResolver( + namespaces_tuple=ns_for_function(self.schema_type, parent_namespace=parent_namespace) + ) + self.config_wrapper = ConfigWrapper(config) + if not self.config_wrapper.defer_build: + self._create_validators() + else: + self.__pydantic_complete__ = False + + def _create_validators(self) -> None: + gen_schema = GenerateSchema(self.config_wrapper, self.ns_resolver) + schema = gen_schema.clean_schema(gen_schema.generate_schema(self.function)) + core_config = self.config_wrapper.core_config(title=self.qualname) + + self.__pydantic_validator__ = create_schema_validator( + schema, + self.schema_type, + self.module, + self.qualname, + 'validate_call', + core_config, + self.config_wrapper.plugin_settings, + ) + if self.validate_return: + signature = signature_no_eval(self.function) + return_type = signature.return_annotation if signature.return_annotation is not signature.empty else Any + gen_schema = GenerateSchema(self.config_wrapper, self.ns_resolver) + schema = gen_schema.clean_schema(gen_schema.generate_schema(return_type)) + validator = create_schema_validator( + schema, + self.schema_type, + self.module, + self.qualname, + 'validate_call', + core_config, + self.config_wrapper.plugin_settings, + ) + if inspect.iscoroutinefunction(self.function): + + async def return_val_wrapper(aw: Awaitable[Any]) -> None: + return validator.validate_python(await aw) + + self.__return_pydantic_validator__ = return_val_wrapper + else: + self.__return_pydantic_validator__ = validator.validate_python + else: + self.__return_pydantic_validator__ = None + + self.__pydantic_complete__ = True + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + if not self.__pydantic_complete__: + self._create_validators() + + res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs)) + if self.__return_pydantic_validator__: + return self.__return_pydantic_validator__(res) + else: + return res diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py new file mode 100644 index 0000000..3ad6c92 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py @@ -0,0 +1,534 @@ +"""Validator functions for standard library types. + +Import of this module is deferred since it contains imports of many standard library modules. +""" + +from __future__ import annotations as _annotations + +import collections.abc +import math +import re +import typing +from collections.abc import Sequence +from decimal import Decimal +from fractions import Fraction +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from typing import Any, Callable, TypeVar, Union, cast +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +import typing_extensions +from pydantic_core import PydanticCustomError, PydanticKnownError, core_schema +from typing_extensions import get_args, get_origin +from typing_inspection import typing_objects + +from pydantic._internal._import_utils import import_cached_field_info +from pydantic.errors import PydanticSchemaGenerationError + + +def sequence_validator( + input_value: Sequence[Any], + /, + validator: core_schema.ValidatorFunctionWrapHandler, +) -> Sequence[Any]: + """Validator for `Sequence` types, isinstance(v, Sequence) has already been called.""" + value_type = type(input_value) + + # We don't accept any plain string as a sequence + # Relevant issue: https://github.com/pydantic/pydantic/issues/5595 + if issubclass(value_type, (str, bytes)): + raise PydanticCustomError( + 'sequence_str', + "'{type_name}' instances are not allowed as a Sequence value", + {'type_name': value_type.__name__}, + ) + + # TODO: refactor sequence validation to validate with either a list or a tuple + # schema, depending on the type of the value. + # Additionally, we should be able to remove one of either this validator or the + # SequenceValidator in _std_types_schema.py (preferably this one, while porting over some logic). + # Effectively, a refactor for sequence validation is needed. + if value_type is tuple: + input_value = list(input_value) + + v_list = validator(input_value) + + # the rest of the logic is just re-creating the original type from `v_list` + if value_type is list: + return v_list + elif issubclass(value_type, range): + # return the list as we probably can't re-create the range + return v_list + elif value_type is tuple: + return tuple(v_list) + else: + # best guess at how to re-create the original type, more custom construction logic might be required + return value_type(v_list) # type: ignore[call-arg] + + +def import_string(value: Any) -> Any: + if isinstance(value, str): + try: + return _import_string_logic(value) + except ImportError as e: + raise PydanticCustomError('import_error', 'Invalid python path: {error}', {'error': str(e)}) from e + else: + # otherwise we just return the value and let the next validator do the rest of the work + return value + + +def _import_string_logic(dotted_path: str) -> Any: + """Inspired by uvicorn — dotted paths should include a colon before the final item if that item is not a module. + (This is necessary to distinguish between a submodule and an attribute when there is a conflict.). + + If the dotted path does not include a colon and the final item is not a valid module, importing as an attribute + rather than a submodule will be attempted automatically. + + So, for example, the following values of `dotted_path` result in the following returned values: + * 'collections': + * 'collections.abc': + * 'collections.abc:Mapping': + * `collections.abc.Mapping`: (though this is a bit slower than the previous line) + + An error will be raised under any of the following scenarios: + * `dotted_path` contains more than one colon (e.g., 'collections:abc:Mapping') + * the substring of `dotted_path` before the colon is not a valid module in the environment (e.g., '123:Mapping') + * the substring of `dotted_path` after the colon is not an attribute of the module (e.g., 'collections:abc123') + """ + from importlib import import_module + + components = dotted_path.strip().split(':') + if len(components) > 2: + raise ImportError(f"Import strings should have at most one ':'; received {dotted_path!r}") + attribute = None + if len(components) == 2: + attribute = components[1] + module_path = components[0] + if not module_path: + raise ImportError(f'Import strings should have a nonempty module name; received {dotted_path!r}') + + try: + module = import_module(module_path) + except ModuleNotFoundError: + if attribute is None and '.' in module_path: + # Try interpreting the final dotted segment as an attribute, not a submodule + maybe_module_path, maybe_attribute = module_path.rsplit('.', 1) + + try: + return _import_string_logic(f'{maybe_module_path}:{maybe_attribute}') + except ImportError: + pass + raise + + if attribute is not None: + try: + return getattr(module, attribute) + except AttributeError as e: + raise ImportError(f'cannot import name {attribute!r} from {module_path!r}') from e + else: + return module + + +def pattern_either_validator(input_value: Any, /) -> re.Pattern[Any]: + if isinstance(input_value, re.Pattern): + return input_value + elif isinstance(input_value, (str, bytes)): + # todo strict mode + return compile_pattern(input_value) # type: ignore + else: + raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') + + +def pattern_str_validator(input_value: Any, /) -> re.Pattern[str]: + if isinstance(input_value, re.Pattern): + if isinstance(input_value.pattern, str): + return input_value + else: + raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') + elif isinstance(input_value, str): + return compile_pattern(input_value) + elif isinstance(input_value, bytes): + raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') + else: + raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') + + +def pattern_bytes_validator(input_value: Any, /) -> re.Pattern[bytes]: + if isinstance(input_value, re.Pattern): + if isinstance(input_value.pattern, bytes): + return input_value + else: + raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') + elif isinstance(input_value, bytes): + return compile_pattern(input_value) + elif isinstance(input_value, str): + raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') + else: + raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') + + +PatternType = TypeVar('PatternType', str, bytes) + + +def compile_pattern(pattern: PatternType) -> re.Pattern[PatternType]: + try: + return re.compile(pattern) + except re.error: + raise PydanticCustomError('pattern_regex', 'Input should be a valid regular expression') + + +def ip_v4_address_validator(input_value: Any, /) -> IPv4Address: + if isinstance(input_value, IPv4Address): + return input_value + + try: + return IPv4Address(input_value) + except ValueError: + raise PydanticCustomError('ip_v4_address', 'Input is not a valid IPv4 address') + + +def ip_v6_address_validator(input_value: Any, /) -> IPv6Address: + if isinstance(input_value, IPv6Address): + return input_value + + try: + return IPv6Address(input_value) + except ValueError: + raise PydanticCustomError('ip_v6_address', 'Input is not a valid IPv6 address') + + +def ip_v4_network_validator(input_value: Any, /) -> IPv4Network: + """Assume IPv4Network initialised with a default `strict` argument. + + See more: + https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network + """ + if isinstance(input_value, IPv4Network): + return input_value + + try: + return IPv4Network(input_value) + except ValueError: + raise PydanticCustomError('ip_v4_network', 'Input is not a valid IPv4 network') + + +def ip_v6_network_validator(input_value: Any, /) -> IPv6Network: + """Assume IPv6Network initialised with a default `strict` argument. + + See more: + https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network + """ + if isinstance(input_value, IPv6Network): + return input_value + + try: + return IPv6Network(input_value) + except ValueError: + raise PydanticCustomError('ip_v6_network', 'Input is not a valid IPv6 network') + + +def ip_v4_interface_validator(input_value: Any, /) -> IPv4Interface: + if isinstance(input_value, IPv4Interface): + return input_value + + try: + return IPv4Interface(input_value) + except ValueError: + raise PydanticCustomError('ip_v4_interface', 'Input is not a valid IPv4 interface') + + +def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: + if isinstance(input_value, IPv6Interface): + return input_value + + try: + return IPv6Interface(input_value) + except ValueError: + raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') + + +def fraction_validator(input_value: Any, /) -> Fraction: + if isinstance(input_value, Fraction): + return input_value + + try: + return Fraction(input_value) + except ValueError: + raise PydanticCustomError('fraction_parsing', 'Input is not a valid fraction') + + +def forbid_inf_nan_check(x: Any) -> Any: + if not math.isfinite(x): + raise PydanticKnownError('finite_number') + return x + + +def _safe_repr(v: Any) -> int | float | str: + """The context argument for `PydanticKnownError` requires a number or str type, so we do a simple repr() coercion for types like timedelta. + + See tests/test_types.py::test_annotated_metadata_any_order for some context. + """ + if isinstance(v, (int, float, str)): + return v + return repr(v) + + +def greater_than_validator(x: Any, gt: Any) -> Any: + try: + if not (x > gt): + raise PydanticKnownError('greater_than', {'gt': _safe_repr(gt)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'gt' to supplied value {x}") + + +def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: + try: + if not (x >= ge): + raise PydanticKnownError('greater_than_equal', {'ge': _safe_repr(ge)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'ge' to supplied value {x}") + + +def less_than_validator(x: Any, lt: Any) -> Any: + try: + if not (x < lt): + raise PydanticKnownError('less_than', {'lt': _safe_repr(lt)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'lt' to supplied value {x}") + + +def less_than_or_equal_validator(x: Any, le: Any) -> Any: + try: + if not (x <= le): + raise PydanticKnownError('less_than_equal', {'le': _safe_repr(le)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'le' to supplied value {x}") + + +def multiple_of_validator(x: Any, multiple_of: Any) -> Any: + try: + if x % multiple_of: + raise PydanticKnownError('multiple_of', {'multiple_of': _safe_repr(multiple_of)}) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'multiple_of' to supplied value {x}") + + +def min_length_validator(x: Any, min_length: Any) -> Any: + try: + if not (len(x) >= min_length): + raise PydanticKnownError( + 'too_short', {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)} + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'min_length' to supplied value {x}") + + +def max_length_validator(x: Any, max_length: Any) -> Any: + try: + if len(x) > max_length: + raise PydanticKnownError( + 'too_long', + {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'max_length' to supplied value {x}") + + +def _extract_decimal_digits_info(decimal: Decimal) -> tuple[int, int]: + """Compute the total number of digits and decimal places for a given [`Decimal`][decimal.Decimal] instance. + + This function handles both normalized and non-normalized Decimal instances. + Example: Decimal('1.230') -> 4 digits, 3 decimal places + + Args: + decimal (Decimal): The decimal number to analyze. + + Returns: + tuple[int, int]: A tuple containing the number of decimal places and total digits. + + Though this could be divided into two separate functions, the logic is easier to follow if we couple the computation + of the number of decimals and digits together. + """ + try: + decimal_tuple = decimal.as_tuple() + + assert isinstance(decimal_tuple.exponent, int) + + exponent = decimal_tuple.exponent + num_digits = len(decimal_tuple.digits) + + if exponent >= 0: + # A positive exponent adds that many trailing zeros + # Ex: digit_tuple=(1, 2, 3), exponent=2 -> 12300 -> 0 decimal places, 5 digits + num_digits += exponent + decimal_places = 0 + else: + # If the absolute value of the negative exponent is larger than the + # number of digits, then it's the same as the number of digits, + # because it'll consume all the digits in digit_tuple and then + # add abs(exponent) - len(digit_tuple) leading zeros after the decimal point. + # Ex: digit_tuple=(1, 2, 3), exponent=-2 -> 1.23 -> 2 decimal places, 3 digits + # Ex: digit_tuple=(1, 2, 3), exponent=-4 -> 0.0123 -> 4 decimal places, 4 digits + decimal_places = abs(exponent) + num_digits = max(num_digits, decimal_places) + + return decimal_places, num_digits + except (AssertionError, AttributeError): + raise TypeError(f'Unable to extract decimal digits info from supplied value {decimal}') + + +def max_digits_validator(x: Any, max_digits: Any) -> Any: + try: + _, num_digits = _extract_decimal_digits_info(x) + _, normalized_num_digits = _extract_decimal_digits_info(x.normalize()) + if (num_digits > max_digits) and (normalized_num_digits > max_digits): + raise PydanticKnownError( + 'decimal_max_digits', + {'max_digits': max_digits}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'max_digits' to supplied value {x}") + + +def decimal_places_validator(x: Any, decimal_places: Any) -> Any: + try: + decimal_places_, _ = _extract_decimal_digits_info(x) + if decimal_places_ > decimal_places: + normalized_decimal_places, _ = _extract_decimal_digits_info(x.normalize()) + if normalized_decimal_places > decimal_places: + raise PydanticKnownError( + 'decimal_max_places', + {'decimal_places': decimal_places}, + ) + return x + except TypeError: + raise TypeError(f"Unable to apply constraint 'decimal_places' to supplied value {x}") + + +def deque_validator(input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> collections.deque[Any]: + return collections.deque(handler(input_value), maxlen=getattr(input_value, 'maxlen', None)) + + +def defaultdict_validator( + input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, default_default_factory: Callable[[], Any] +) -> collections.defaultdict[Any, Any]: + if isinstance(input_value, collections.defaultdict): + default_factory = input_value.default_factory + return collections.defaultdict(default_factory, handler(input_value)) + else: + return collections.defaultdict(default_default_factory, handler(input_value)) + + +def get_defaultdict_default_default_factory(values_source_type: Any) -> Callable[[], Any]: + FieldInfo = import_cached_field_info() + + values_type_origin = get_origin(values_source_type) + + def infer_default() -> Callable[[], Any]: + allowed_default_types: dict[Any, Any] = { + tuple: tuple, + collections.abc.Sequence: tuple, + collections.abc.MutableSequence: list, + list: list, + typing.Sequence: list, + set: set, + typing.MutableSet: set, + collections.abc.MutableSet: set, + collections.abc.Set: frozenset, + typing.MutableMapping: dict, + typing.Mapping: dict, + collections.abc.Mapping: dict, + collections.abc.MutableMapping: dict, + float: float, + int: int, + str: str, + bool: bool, + } + values_type = values_type_origin or values_source_type + instructions = 'set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]`' + if typing_objects.is_typevar(values_type): + + def type_var_default_factory() -> None: + raise RuntimeError( + 'Generic defaultdict cannot be used without a concrete value type or an' + ' explicit default factory, ' + instructions + ) + + return type_var_default_factory + elif values_type not in allowed_default_types: + # a somewhat subjective set of types that have reasonable default values + allowed_msg = ', '.join([t.__name__ for t in set(allowed_default_types.values())]) + raise PydanticSchemaGenerationError( + f'Unable to infer a default factory for keys of type {values_source_type}.' + f' Only {allowed_msg} are supported, other types require an explicit default factory' + ' ' + instructions + ) + return allowed_default_types[values_type] + + # Assume Annotated[..., Field(...)] + if typing_objects.is_annotated(values_type_origin): + field_info = next((v for v in get_args(values_source_type) if isinstance(v, FieldInfo)), None) + else: + field_info = None + if field_info and field_info.default_factory: + # Assume the default factory does not take any argument: + default_default_factory = cast(Callable[[], Any], field_info.default_factory) + else: + default_default_factory = infer_default() + return default_default_factory + + +def validate_str_is_valid_iana_tz(value: Any, /) -> ZoneInfo: + if isinstance(value, ZoneInfo): + return value + try: + return ZoneInfo(value) + except (ZoneInfoNotFoundError, ValueError, TypeError): + raise PydanticCustomError('zoneinfo_str', 'invalid timezone: {value}', {'value': value}) + + +NUMERIC_VALIDATOR_LOOKUP: dict[str, Callable] = { + 'gt': greater_than_validator, + 'ge': greater_than_or_equal_validator, + 'lt': less_than_validator, + 'le': less_than_or_equal_validator, + 'multiple_of': multiple_of_validator, + 'min_length': min_length_validator, + 'max_length': max_length_validator, + 'max_digits': max_digits_validator, + 'decimal_places': decimal_places_validator, +} + +IpType = Union[IPv4Address, IPv6Address, IPv4Network, IPv6Network, IPv4Interface, IPv6Interface] + +IP_VALIDATOR_LOOKUP: dict[type[IpType], Callable] = { + IPv4Address: ip_v4_address_validator, + IPv6Address: ip_v6_address_validator, + IPv4Network: ip_v4_network_validator, + IPv6Network: ip_v6_network_validator, + IPv4Interface: ip_v4_interface_validator, + IPv6Interface: ip_v6_interface_validator, +} + +MAPPING_ORIGIN_MAP: dict[Any, Any] = { + typing.DefaultDict: collections.defaultdict, # noqa: UP006 + collections.defaultdict: collections.defaultdict, + typing.OrderedDict: collections.OrderedDict, # noqa: UP006 + collections.OrderedDict: collections.OrderedDict, + typing_extensions.OrderedDict: collections.OrderedDict, + typing.Counter: collections.Counter, + collections.Counter: collections.Counter, + # this doesn't handle subclasses of these + typing.Mapping: dict, + typing.MutableMapping: dict, + # parametrized typing.{Mutable}Mapping creates one of these + collections.abc.Mapping: dict, + collections.abc.MutableMapping: dict, +} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py new file mode 100644 index 0000000..b4ecd28 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py @@ -0,0 +1,316 @@ +import sys +from typing import Any, Callable + +from pydantic.warnings import PydanticDeprecatedSince20 + +from .version import version_short + +MOVED_IN_V2 = { + 'pydantic.utils:version_info': 'pydantic.version:version_info', + 'pydantic.error_wrappers:ValidationError': 'pydantic:ValidationError', + 'pydantic.utils:to_camel': 'pydantic.alias_generators:to_pascal', + 'pydantic.utils:to_lower_camel': 'pydantic.alias_generators:to_camel', + 'pydantic:PyObject': 'pydantic.types:ImportString', + 'pydantic.types:PyObject': 'pydantic.types:ImportString', + 'pydantic.generics:GenericModel': 'pydantic.BaseModel', +} + +DEPRECATED_MOVED_IN_V2 = { + 'pydantic.tools:schema_of': 'pydantic.deprecated.tools:schema_of', + 'pydantic.tools:parse_obj_as': 'pydantic.deprecated.tools:parse_obj_as', + 'pydantic.tools:schema_json_of': 'pydantic.deprecated.tools:schema_json_of', + 'pydantic.json:pydantic_encoder': 'pydantic.deprecated.json:pydantic_encoder', + 'pydantic:validate_arguments': 'pydantic.deprecated.decorator:validate_arguments', + 'pydantic.json:custom_pydantic_encoder': 'pydantic.deprecated.json:custom_pydantic_encoder', + 'pydantic.json:timedelta_isoformat': 'pydantic.deprecated.json:timedelta_isoformat', + 'pydantic.decorator:validate_arguments': 'pydantic.deprecated.decorator:validate_arguments', + 'pydantic.class_validators:validator': 'pydantic.deprecated.class_validators:validator', + 'pydantic.class_validators:root_validator': 'pydantic.deprecated.class_validators:root_validator', + 'pydantic.config:BaseConfig': 'pydantic.deprecated.config:BaseConfig', + 'pydantic.config:Extra': 'pydantic.deprecated.config:Extra', +} + +REDIRECT_TO_V1 = { + f'pydantic.utils:{obj}': f'pydantic.v1.utils:{obj}' + for obj in ( + 'deep_update', + 'GetterDict', + 'lenient_issubclass', + 'lenient_isinstance', + 'is_valid_field', + 'update_not_none', + 'import_string', + 'Representation', + 'ROOT_KEY', + 'smart_deepcopy', + 'sequence_like', + ) +} + + +REMOVED_IN_V2 = { + 'pydantic:ConstrainedBytes', + 'pydantic:ConstrainedDate', + 'pydantic:ConstrainedDecimal', + 'pydantic:ConstrainedFloat', + 'pydantic:ConstrainedFrozenSet', + 'pydantic:ConstrainedInt', + 'pydantic:ConstrainedList', + 'pydantic:ConstrainedSet', + 'pydantic:ConstrainedStr', + 'pydantic:JsonWrapper', + 'pydantic:NoneBytes', + 'pydantic:NoneStr', + 'pydantic:NoneStrBytes', + 'pydantic:Protocol', + 'pydantic:Required', + 'pydantic:StrBytes', + 'pydantic:compiled', + 'pydantic.config:get_config', + 'pydantic.config:inherit_config', + 'pydantic.config:prepare_config', + 'pydantic:create_model_from_namedtuple', + 'pydantic:create_model_from_typeddict', + 'pydantic.dataclasses:create_pydantic_model_from_dataclass', + 'pydantic.dataclasses:make_dataclass_validator', + 'pydantic.dataclasses:set_validation', + 'pydantic.datetime_parse:parse_date', + 'pydantic.datetime_parse:parse_time', + 'pydantic.datetime_parse:parse_datetime', + 'pydantic.datetime_parse:parse_duration', + 'pydantic.error_wrappers:ErrorWrapper', + 'pydantic.errors:AnyStrMaxLengthError', + 'pydantic.errors:AnyStrMinLengthError', + 'pydantic.errors:ArbitraryTypeError', + 'pydantic.errors:BoolError', + 'pydantic.errors:BytesError', + 'pydantic.errors:CallableError', + 'pydantic.errors:ClassError', + 'pydantic.errors:ColorError', + 'pydantic.errors:ConfigError', + 'pydantic.errors:DataclassTypeError', + 'pydantic.errors:DateError', + 'pydantic.errors:DateNotInTheFutureError', + 'pydantic.errors:DateNotInThePastError', + 'pydantic.errors:DateTimeError', + 'pydantic.errors:DecimalError', + 'pydantic.errors:DecimalIsNotFiniteError', + 'pydantic.errors:DecimalMaxDigitsError', + 'pydantic.errors:DecimalMaxPlacesError', + 'pydantic.errors:DecimalWholeDigitsError', + 'pydantic.errors:DictError', + 'pydantic.errors:DurationError', + 'pydantic.errors:EmailError', + 'pydantic.errors:EnumError', + 'pydantic.errors:EnumMemberError', + 'pydantic.errors:ExtraError', + 'pydantic.errors:FloatError', + 'pydantic.errors:FrozenSetError', + 'pydantic.errors:FrozenSetMaxLengthError', + 'pydantic.errors:FrozenSetMinLengthError', + 'pydantic.errors:HashableError', + 'pydantic.errors:IPv4AddressError', + 'pydantic.errors:IPv4InterfaceError', + 'pydantic.errors:IPv4NetworkError', + 'pydantic.errors:IPv6AddressError', + 'pydantic.errors:IPv6InterfaceError', + 'pydantic.errors:IPv6NetworkError', + 'pydantic.errors:IPvAnyAddressError', + 'pydantic.errors:IPvAnyInterfaceError', + 'pydantic.errors:IPvAnyNetworkError', + 'pydantic.errors:IntEnumError', + 'pydantic.errors:IntegerError', + 'pydantic.errors:InvalidByteSize', + 'pydantic.errors:InvalidByteSizeUnit', + 'pydantic.errors:InvalidDiscriminator', + 'pydantic.errors:InvalidLengthForBrand', + 'pydantic.errors:JsonError', + 'pydantic.errors:JsonTypeError', + 'pydantic.errors:ListError', + 'pydantic.errors:ListMaxLengthError', + 'pydantic.errors:ListMinLengthError', + 'pydantic.errors:ListUniqueItemsError', + 'pydantic.errors:LuhnValidationError', + 'pydantic.errors:MissingDiscriminator', + 'pydantic.errors:MissingError', + 'pydantic.errors:NoneIsAllowedError', + 'pydantic.errors:NoneIsNotAllowedError', + 'pydantic.errors:NotDigitError', + 'pydantic.errors:NotNoneError', + 'pydantic.errors:NumberNotGeError', + 'pydantic.errors:NumberNotGtError', + 'pydantic.errors:NumberNotLeError', + 'pydantic.errors:NumberNotLtError', + 'pydantic.errors:NumberNotMultipleError', + 'pydantic.errors:PathError', + 'pydantic.errors:PathNotADirectoryError', + 'pydantic.errors:PathNotAFileError', + 'pydantic.errors:PathNotExistsError', + 'pydantic.errors:PatternError', + 'pydantic.errors:PyObjectError', + 'pydantic.errors:PydanticTypeError', + 'pydantic.errors:PydanticValueError', + 'pydantic.errors:SequenceError', + 'pydantic.errors:SetError', + 'pydantic.errors:SetMaxLengthError', + 'pydantic.errors:SetMinLengthError', + 'pydantic.errors:StrError', + 'pydantic.errors:StrRegexError', + 'pydantic.errors:StrictBoolError', + 'pydantic.errors:SubclassError', + 'pydantic.errors:TimeError', + 'pydantic.errors:TupleError', + 'pydantic.errors:TupleLengthError', + 'pydantic.errors:UUIDError', + 'pydantic.errors:UUIDVersionError', + 'pydantic.errors:UrlError', + 'pydantic.errors:UrlExtraError', + 'pydantic.errors:UrlHostError', + 'pydantic.errors:UrlHostTldError', + 'pydantic.errors:UrlPortError', + 'pydantic.errors:UrlSchemeError', + 'pydantic.errors:UrlSchemePermittedError', + 'pydantic.errors:UrlUserInfoError', + 'pydantic.errors:WrongConstantError', + 'pydantic.main:validate_model', + 'pydantic.networks:stricturl', + 'pydantic:parse_file_as', + 'pydantic:parse_raw_as', + 'pydantic:stricturl', + 'pydantic.tools:parse_file_as', + 'pydantic.tools:parse_raw_as', + 'pydantic.types:ConstrainedBytes', + 'pydantic.types:ConstrainedDate', + 'pydantic.types:ConstrainedDecimal', + 'pydantic.types:ConstrainedFloat', + 'pydantic.types:ConstrainedFrozenSet', + 'pydantic.types:ConstrainedInt', + 'pydantic.types:ConstrainedList', + 'pydantic.types:ConstrainedSet', + 'pydantic.types:ConstrainedStr', + 'pydantic.types:JsonWrapper', + 'pydantic.types:NoneBytes', + 'pydantic.types:NoneStr', + 'pydantic.types:NoneStrBytes', + 'pydantic.types:StrBytes', + 'pydantic.typing:evaluate_forwardref', + 'pydantic.typing:AbstractSetIntStr', + 'pydantic.typing:AnyCallable', + 'pydantic.typing:AnyClassMethod', + 'pydantic.typing:CallableGenerator', + 'pydantic.typing:DictAny', + 'pydantic.typing:DictIntStrAny', + 'pydantic.typing:DictStrAny', + 'pydantic.typing:IntStr', + 'pydantic.typing:ListStr', + 'pydantic.typing:MappingIntStrAny', + 'pydantic.typing:NoArgAnyCallable', + 'pydantic.typing:NoneType', + 'pydantic.typing:ReprArgs', + 'pydantic.typing:SetStr', + 'pydantic.typing:StrPath', + 'pydantic.typing:TupleGenerator', + 'pydantic.typing:WithArgsTypes', + 'pydantic.typing:all_literal_values', + 'pydantic.typing:display_as_type', + 'pydantic.typing:get_all_type_hints', + 'pydantic.typing:get_args', + 'pydantic.typing:get_origin', + 'pydantic.typing:get_sub_types', + 'pydantic.typing:is_callable_type', + 'pydantic.typing:is_classvar', + 'pydantic.typing:is_finalvar', + 'pydantic.typing:is_literal_type', + 'pydantic.typing:is_namedtuple', + 'pydantic.typing:is_new_type', + 'pydantic.typing:is_none_type', + 'pydantic.typing:is_typeddict', + 'pydantic.typing:is_typeddict_special', + 'pydantic.typing:is_union', + 'pydantic.typing:new_type_supertype', + 'pydantic.typing:resolve_annotations', + 'pydantic.typing:typing_base', + 'pydantic.typing:update_field_forward_refs', + 'pydantic.typing:update_model_forward_refs', + 'pydantic.utils:ClassAttribute', + 'pydantic.utils:DUNDER_ATTRIBUTES', + 'pydantic.utils:PyObjectStr', + 'pydantic.utils:ValueItems', + 'pydantic.utils:almost_equal_floats', + 'pydantic.utils:get_discriminator_alias_and_values', + 'pydantic.utils:get_model', + 'pydantic.utils:get_unique_discriminator_alias', + 'pydantic.utils:in_ipython', + 'pydantic.utils:is_valid_identifier', + 'pydantic.utils:path_type', + 'pydantic.utils:validate_field_name', + 'pydantic:validate_model', +} + + +def getattr_migration(module: str) -> Callable[[str], Any]: + """Implement PEP 562 for objects that were either moved or removed on the migration + to V2. + + Args: + module: The module name. + + Returns: + A callable that will raise an error if the object is not found. + """ + # This avoids circular import with errors.py. + from .errors import PydanticImportError + + def wrapper(name: str) -> object: + """Raise an error if the object is not found, or warn if it was moved. + + In case it was moved, it still returns the object. + + Args: + name: The object name. + + Returns: + The object. + """ + if name == '__path__': + raise AttributeError(f'module {module!r} has no attribute {name!r}') + + import warnings + + from ._internal._validators import import_string + + import_path = f'{module}:{name}' + if import_path in MOVED_IN_V2.keys(): + new_location = MOVED_IN_V2[import_path] + warnings.warn( + f'`{import_path}` has been moved to `{new_location}`.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return import_string(MOVED_IN_V2[import_path]) + if import_path in DEPRECATED_MOVED_IN_V2: + # skip the warning here because a deprecation warning will be raised elsewhere + return import_string(DEPRECATED_MOVED_IN_V2[import_path]) + if import_path in REDIRECT_TO_V1: + new_location = REDIRECT_TO_V1[import_path] + warnings.warn( + f'`{import_path}` has been removed. We are importing from `{new_location}` instead.' + 'See the migration guide for more details: https://docs.pydantic.dev/latest/migration/', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return import_string(REDIRECT_TO_V1[import_path]) + if import_path == 'pydantic:BaseSettings': + raise PydanticImportError( + '`BaseSettings` has been moved to the `pydantic-settings` package. ' + f'See https://docs.pydantic.dev/{version_short()}/migration/#basesettings-has-moved-to-pydantic-settings ' + 'for more details.' + ) + if import_path in REMOVED_IN_V2: + raise PydanticImportError(f'`{import_path}` has been removed in V2.') + globals: dict[str, Any] = sys.modules[module].__dict__ + if name in globals: + return globals[name] + raise AttributeError(f'module {module!r} has no attribute {name!r}') + + return wrapper diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py new file mode 100644 index 0000000..0b7653f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py @@ -0,0 +1,62 @@ +"""Alias generators for converting between different capitalization conventions.""" + +import re + +__all__ = ('to_pascal', 'to_camel', 'to_snake') + +# TODO: in V3, change the argument names to be more descriptive +# Generally, don't only convert from snake_case, or name the functions +# more specifically like snake_to_camel. + + +def to_pascal(snake: str) -> str: + """Convert a snake_case string to PascalCase. + + Args: + snake: The string to convert. + + Returns: + The PascalCase string. + """ + camel = snake.title() + return re.sub('([0-9A-Za-z])_(?=[0-9A-Z])', lambda m: m.group(1), camel) + + +def to_camel(snake: str) -> str: + """Convert a snake_case string to camelCase. + + Args: + snake: The string to convert. + + Returns: + The converted camelCase string. + """ + # If the string is already in camelCase and does not contain a digit followed + # by a lowercase letter, return it as it is + if re.match('^[a-z]+[A-Za-z0-9]*$', snake) and not re.search(r'\d[a-z]', snake): + return snake + + camel = to_pascal(snake) + return re.sub('(^_*[A-Z])', lambda m: m.group(1).lower(), camel) + + +def to_snake(camel: str) -> str: + """Convert a PascalCase, camelCase, or kebab-case string to snake_case. + + Args: + camel: The string to convert. + + Returns: + The converted string in snake_case. + """ + # Handle the sequence of uppercase letters followed by a lowercase letter + snake = re.sub(r'([A-Z]+)([A-Z][a-z])', lambda m: f'{m.group(1)}_{m.group(2)}', camel) + # Insert an underscore between a lowercase letter and an uppercase letter + snake = re.sub(r'([a-z])([A-Z])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) + # Insert an underscore between a digit and an uppercase letter + snake = re.sub(r'([0-9])([A-Z])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) + # Insert an underscore between a lowercase letter and a digit + snake = re.sub(r'([a-z])([0-9])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) + # Replace hyphens with underscores to handle kebab-case + snake = snake.replace('-', '_') + return snake.lower() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py new file mode 100644 index 0000000..ac22737 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py @@ -0,0 +1,135 @@ +"""Support for alias configurations.""" + +from __future__ import annotations + +import dataclasses +from typing import Any, Callable, Literal + +from pydantic_core import PydanticUndefined + +from ._internal import _internal_dataclass + +__all__ = ('AliasGenerator', 'AliasPath', 'AliasChoices') + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class AliasPath: + """!!! abstract "Usage Documentation" + [`AliasPath` and `AliasChoices`](../concepts/alias.md#aliaspath-and-aliaschoices) + + A data class used by `validation_alias` as a convenience to create aliases. + + Attributes: + path: A list of string or integer aliases. + """ + + path: list[int | str] + + def __init__(self, first_arg: str, *args: str | int) -> None: + self.path = [first_arg] + list(args) + + def convert_to_aliases(self) -> list[str | int]: + """Converts arguments to a list of string or integer aliases. + + Returns: + The list of aliases. + """ + return self.path + + def search_dict_for_path(self, d: dict) -> Any: + """Searches a dictionary for the path specified by the alias. + + Returns: + The value at the specified path, or `PydanticUndefined` if the path is not found. + """ + v = d + for k in self.path: + if isinstance(v, str): + # disallow indexing into a str, like for AliasPath('x', 0) and x='abc' + return PydanticUndefined + try: + v = v[k] + except (KeyError, IndexError, TypeError): + return PydanticUndefined + return v + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class AliasChoices: + """!!! abstract "Usage Documentation" + [`AliasPath` and `AliasChoices`](../concepts/alias.md#aliaspath-and-aliaschoices) + + A data class used by `validation_alias` as a convenience to create aliases. + + Attributes: + choices: A list containing a string or `AliasPath`. + """ + + choices: list[str | AliasPath] + + def __init__(self, first_choice: str | AliasPath, *choices: str | AliasPath) -> None: + self.choices = [first_choice] + list(choices) + + def convert_to_aliases(self) -> list[list[str | int]]: + """Converts arguments to a list of lists containing string or integer aliases. + + Returns: + The list of aliases. + """ + aliases: list[list[str | int]] = [] + for c in self.choices: + if isinstance(c, AliasPath): + aliases.append(c.convert_to_aliases()) + else: + aliases.append([c]) + return aliases + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class AliasGenerator: + """!!! abstract "Usage Documentation" + [Using an `AliasGenerator`](../concepts/alias.md#using-an-aliasgenerator) + + A data class used by `alias_generator` as a convenience to create various aliases. + + Attributes: + alias: A callable that takes a field name and returns an alias for it. + validation_alias: A callable that takes a field name and returns a validation alias for it. + serialization_alias: A callable that takes a field name and returns a serialization alias for it. + """ + + alias: Callable[[str], str] | None = None + validation_alias: Callable[[str], str | AliasPath | AliasChoices] | None = None + serialization_alias: Callable[[str], str] | None = None + + def _generate_alias( + self, + alias_kind: Literal['alias', 'validation_alias', 'serialization_alias'], + allowed_types: tuple[type[str] | type[AliasPath] | type[AliasChoices], ...], + field_name: str, + ) -> str | AliasPath | AliasChoices | None: + """Generate an alias of the specified kind. Returns None if the alias generator is None. + + Raises: + TypeError: If the alias generator produces an invalid type. + """ + alias = None + if alias_generator := getattr(self, alias_kind): + alias = alias_generator(field_name) + if alias and not isinstance(alias, allowed_types): + raise TypeError( + f'Invalid `{alias_kind}` type. `{alias_kind}` generator must produce one of `{allowed_types}`' + ) + return alias + + def generate_aliases(self, field_name: str) -> tuple[str | None, str | AliasPath | AliasChoices | None, str | None]: + """Generate `alias`, `validation_alias`, and `serialization_alias` for a field. + + Returns: + A tuple of three aliases - validation, alias, and serialization. + """ + alias = self._generate_alias('alias', (str,), field_name) + validation_alias = self._generate_alias('validation_alias', (str, AliasChoices, AliasPath), field_name) + serialization_alias = self._generate_alias('serialization_alias', (str,), field_name) + + return alias, validation_alias, serialization_alias # type: ignore diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py new file mode 100644 index 0000000..d0cb5d3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py @@ -0,0 +1,122 @@ +"""Type annotations to use with `__get_pydantic_core_schema__` and `__get_pydantic_json_schema__`.""" + +from __future__ import annotations as _annotations + +from typing import TYPE_CHECKING, Any, Union + +from pydantic_core import core_schema + +if TYPE_CHECKING: + from ._internal._namespace_utils import NamespacesTuple + from .json_schema import JsonSchemaMode, JsonSchemaValue + + CoreSchemaOrField = Union[ + core_schema.CoreSchema, + core_schema.ModelField, + core_schema.DataclassField, + core_schema.TypedDictField, + core_schema.ComputedField, + ] + +__all__ = 'GetJsonSchemaHandler', 'GetCoreSchemaHandler' + + +class GetJsonSchemaHandler: + """Handler to call into the next JSON schema generation function. + + Attributes: + mode: Json schema mode, can be `validation` or `serialization`. + """ + + mode: JsonSchemaMode + + def __call__(self, core_schema: CoreSchemaOrField, /) -> JsonSchemaValue: + """Call the inner handler and get the JsonSchemaValue it returns. + This will call the next JSON schema modifying function up until it calls + into `pydantic.json_schema.GenerateJsonSchema`, which will raise a + `pydantic.errors.PydanticInvalidForJsonSchema` error if it cannot generate + a JSON schema. + + Args: + core_schema: A `pydantic_core.core_schema.CoreSchema`. + + Returns: + JsonSchemaValue: The JSON schema generated by the inner JSON schema modify + functions. + """ + raise NotImplementedError + + def resolve_ref_schema(self, maybe_ref_json_schema: JsonSchemaValue, /) -> JsonSchemaValue: + """Get the real schema for a `{"$ref": ...}` schema. + If the schema given is not a `$ref` schema, it will be returned as is. + This means you don't have to check before calling this function. + + Args: + maybe_ref_json_schema: A JsonSchemaValue which may be a `$ref` schema. + + Raises: + LookupError: If the ref is not found. + + Returns: + JsonSchemaValue: A JsonSchemaValue that has no `$ref`. + """ + raise NotImplementedError + + +class GetCoreSchemaHandler: + """Handler to call into the next CoreSchema schema generation function.""" + + def __call__(self, source_type: Any, /) -> core_schema.CoreSchema: + """Call the inner handler and get the CoreSchema it returns. + This will call the next CoreSchema modifying function up until it calls + into Pydantic's internal schema generation machinery, which will raise a + `pydantic.errors.PydanticSchemaGenerationError` error if it cannot generate + a CoreSchema for the given source type. + + Args: + source_type: The input type. + + Returns: + CoreSchema: The `pydantic-core` CoreSchema generated. + """ + raise NotImplementedError + + def generate_schema(self, source_type: Any, /) -> core_schema.CoreSchema: + """Generate a schema unrelated to the current context. + Use this function if e.g. you are handling schema generation for a sequence + and want to generate a schema for its items. + Otherwise, you may end up doing something like applying a `min_length` constraint + that was intended for the sequence itself to its items! + + Args: + source_type: The input type. + + Returns: + CoreSchema: The `pydantic-core` CoreSchema generated. + """ + raise NotImplementedError + + def resolve_ref_schema(self, maybe_ref_schema: core_schema.CoreSchema, /) -> core_schema.CoreSchema: + """Get the real schema for a `definition-ref` schema. + If the schema given is not a `definition-ref` schema, it will be returned as is. + This means you don't have to check before calling this function. + + Args: + maybe_ref_schema: A `CoreSchema`, `ref`-based or not. + + Raises: + LookupError: If the `ref` is not found. + + Returns: + A concrete `CoreSchema`. + """ + raise NotImplementedError + + @property + def field_name(self) -> str | None: + """Get the name of the closest field to this validator.""" + raise NotImplementedError + + def _get_types_namespace(self) -> NamespacesTuple: + """Internal method used during type resolution for serializer annotations.""" + raise NotImplementedError diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py new file mode 100644 index 0000000..9977150 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py @@ -0,0 +1,5 @@ +"""`class_validators` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py new file mode 100644 index 0000000..9a42d58 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/color.py @@ -0,0 +1,604 @@ +"""Color definitions are used as per the CSS3 +[CSS Color Module Level 3](http://www.w3.org/TR/css3-color/#svg-color) specification. + +A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`. + +In these cases the _last_ color when sorted alphabetically takes preferences, +eg. `Color((0, 255, 255)).as_named() == 'cyan'` because "cyan" comes after "aqua". + +Warning: Deprecated + The `Color` class is deprecated, use `pydantic_extra_types` instead. + See [`pydantic-extra-types.Color`](../usage/types/extra_types/color_types.md) + for more information. +""" + +import math +import re +from colorsys import hls_to_rgb, rgb_to_hls +from typing import Any, Callable, Optional, Union, cast + +from pydantic_core import CoreSchema, PydanticCustomError, core_schema +from typing_extensions import deprecated + +from ._internal import _repr +from ._internal._schema_generation_shared import GetJsonSchemaHandler as _GetJsonSchemaHandler +from .json_schema import JsonSchemaValue +from .warnings import PydanticDeprecatedSince20 + +ColorTuple = Union[tuple[int, int, int], tuple[int, int, int, float]] +ColorType = Union[ColorTuple, str] +HslColorTuple = Union[tuple[float, float, float], tuple[float, float, float, float]] + + +class RGBA: + """Internal use only as a representation of a color.""" + + __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' + + def __init__(self, r: float, g: float, b: float, alpha: Optional[float]): + self.r = r + self.g = g + self.b = b + self.alpha = alpha + + self._tuple: tuple[float, float, float, Optional[float]] = (r, g, b, alpha) + + def __getitem__(self, item: Any) -> Any: + return self._tuple[item] + + +# these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached +_r_255 = r'(\d{1,3}(?:\.\d+)?)' +_r_comma = r'\s*,\s*' +_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)' +_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?' +_r_sl = r'(\d{1,3}(?:\.\d+)?)%' +r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*' +r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*' +# CSS3 RGB examples: rgb(0, 0, 0), rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 50%) +r_rgb = rf'\s*rgba?\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}(?:{_r_comma}{_r_alpha})?\s*\)\s*' +# CSS3 HSL examples: hsl(270, 60%, 50%), hsla(270, 60%, 50%, 0.5), hsla(270, 60%, 50%, 50%) +r_hsl = rf'\s*hsla?\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}(?:{_r_comma}{_r_alpha})?\s*\)\s*' +# CSS4 RGB examples: rgb(0 0 0), rgb(0 0 0 / 0.5), rgb(0 0 0 / 50%), rgba(0 0 0 / 50%) +r_rgb_v4_style = rf'\s*rgba?\(\s*{_r_255}\s+{_r_255}\s+{_r_255}(?:\s*/\s*{_r_alpha})?\s*\)\s*' +# CSS4 HSL examples: hsl(270 60% 50%), hsl(270 60% 50% / 0.5), hsl(270 60% 50% / 50%), hsla(270 60% 50% / 50%) +r_hsl_v4_style = rf'\s*hsla?\(\s*{_r_h}\s+{_r_sl}\s+{_r_sl}(?:\s*/\s*{_r_alpha})?\s*\)\s*' + +# colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used +repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'} +rads = 2 * math.pi + + +@deprecated( + 'The `Color` class is deprecated, use `pydantic_extra_types` instead. ' + 'See https://docs.pydantic.dev/latest/api/pydantic_extra_types_color/.', + category=PydanticDeprecatedSince20, +) +class Color(_repr.Representation): + """Represents a color.""" + + __slots__ = '_original', '_rgba' + + def __init__(self, value: ColorType) -> None: + self._rgba: RGBA + self._original: ColorType + if isinstance(value, (tuple, list)): + self._rgba = parse_tuple(value) + elif isinstance(value, str): + self._rgba = parse_str(value) + elif isinstance(value, Color): + self._rgba = value._rgba + value = value._original + else: + raise PydanticCustomError( + 'color_error', 'value is not a valid color: value must be a tuple, list or string' + ) + + # if we've got here value must be a valid color + self._original = value + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='color') + return field_schema + + def original(self) -> ColorType: + """Original value passed to `Color`.""" + return self._original + + def as_named(self, *, fallback: bool = False) -> str: + """Returns the name of the color if it can be found in `COLORS_BY_VALUE` dictionary, + otherwise returns the hexadecimal representation of the color or raises `ValueError`. + + Args: + fallback: If True, falls back to returning the hexadecimal representation of + the color instead of raising a ValueError when no named color is found. + + Returns: + The name of the color, or the hexadecimal representation of the color. + + Raises: + ValueError: When no named color is found and fallback is `False`. + """ + if self._rgba.alpha is None: + rgb = cast(tuple[int, int, int], self.as_rgb_tuple()) + try: + return COLORS_BY_VALUE[rgb] + except KeyError as e: + if fallback: + return self.as_hex() + else: + raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e + else: + return self.as_hex() + + def as_hex(self) -> str: + """Returns the hexadecimal representation of the color. + + Hex string representing the color can be 3, 4, 6, or 8 characters depending on whether the string + a "short" representation of the color is possible and whether there's an alpha channel. + + Returns: + The hexadecimal representation of the color. + """ + values = [float_to_255(c) for c in self._rgba[:3]] + if self._rgba.alpha is not None: + values.append(float_to_255(self._rgba.alpha)) + + as_hex = ''.join(f'{v:02x}' for v in values) + if all(c in repeat_colors for c in values): + as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2)) + return '#' + as_hex + + def as_rgb(self) -> str: + """Color as an `rgb(, , )` or `rgba(, , , )` string.""" + if self._rgba.alpha is None: + return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})' + else: + return ( + f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, ' + f'{round(self._alpha_float(), 2)})' + ) + + def as_rgb_tuple(self, *, alpha: Optional[bool] = None) -> ColorTuple: + """Returns the color as an RGB or RGBA tuple. + + Args: + alpha: Whether to include the alpha channel. There are three options for this input: + + - `None` (default): Include alpha only if it's set. (e.g. not `None`) + - `True`: Always include alpha. + - `False`: Always omit alpha. + + Returns: + A tuple that contains the values of the red, green, and blue channels in the range 0 to 255. + If alpha is included, it is in the range 0 to 1. + """ + r, g, b = (float_to_255(c) for c in self._rgba[:3]) + if alpha is None: + if self._rgba.alpha is None: + return r, g, b + else: + return r, g, b, self._alpha_float() + elif alpha: + return r, g, b, self._alpha_float() + else: + # alpha is False + return r, g, b + + def as_hsl(self) -> str: + """Color as an `hsl(, , )` or `hsl(, , , )` string.""" + if self._rgba.alpha is None: + h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore + return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})' + else: + h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore + return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})' + + def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple: + """Returns the color as an HSL or HSLA tuple. + + Args: + alpha: Whether to include the alpha channel. + + - `None` (default): Include the alpha channel only if it's set (e.g. not `None`). + - `True`: Always include alpha. + - `False`: Always omit alpha. + + Returns: + The color as a tuple of hue, saturation, lightness, and alpha (if included). + All elements are in the range 0 to 1. + + Note: + This is HSL as used in HTML and most other places, not HLS as used in Python's `colorsys`. + """ + h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) # noqa: E741 + if alpha is None: + if self._rgba.alpha is None: + return h, s, l + else: + return h, s, l, self._alpha_float() + if alpha: + return h, s, l, self._alpha_float() + else: + # alpha is False + return h, s, l + + def _alpha_float(self) -> float: + return 1 if self._rgba.alpha is None else self._rgba.alpha + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: Callable[[Any], CoreSchema] + ) -> core_schema.CoreSchema: + return core_schema.with_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) + + @classmethod + def _validate(cls, __input_value: Any, _: Any) -> 'Color': + return cls(__input_value) + + def __str__(self) -> str: + return self.as_named(fallback=True) + + def __repr_args__(self) -> '_repr.ReprArgs': + return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() + + def __hash__(self) -> int: + return hash(self.as_rgb_tuple()) + + +def parse_tuple(value: tuple[Any, ...]) -> RGBA: + """Parse a tuple or list to get RGBA values. + + Args: + value: A tuple or list. + + Returns: + An `RGBA` tuple parsed from the input tuple. + + Raises: + PydanticCustomError: If tuple is not valid. + """ + if len(value) == 3: + r, g, b = (parse_color_value(v) for v in value) + return RGBA(r, g, b, None) + elif len(value) == 4: + r, g, b = (parse_color_value(v) for v in value[:3]) + return RGBA(r, g, b, parse_float_alpha(value[3])) + else: + raise PydanticCustomError('color_error', 'value is not a valid color: tuples must have length 3 or 4') + + +def parse_str(value: str) -> RGBA: + """Parse a string representing a color to an RGBA tuple. + + Possible formats for the input string include: + + * named color, see `COLORS_BY_NAME` + * hex short eg. `fff` (prefix can be `#`, `0x` or nothing) + * hex long eg. `ffffff` (prefix can be `#`, `0x` or nothing) + * `rgb(, , )` + * `rgba(, , , )` + + Args: + value: A string representing a color. + + Returns: + An `RGBA` tuple parsed from the input string. + + Raises: + ValueError: If the input string cannot be parsed to an RGBA tuple. + """ + value_lower = value.lower() + try: + r, g, b = COLORS_BY_NAME[value_lower] + except KeyError: + pass + else: + return ints_to_rgba(r, g, b, None) + + m = re.fullmatch(r_hex_short, value_lower) + if m: + *rgb, a = m.groups() + r, g, b = (int(v * 2, 16) for v in rgb) + if a: + alpha: Optional[float] = int(a * 2, 16) / 255 + else: + alpha = None + return ints_to_rgba(r, g, b, alpha) + + m = re.fullmatch(r_hex_long, value_lower) + if m: + *rgb, a = m.groups() + r, g, b = (int(v, 16) for v in rgb) + if a: + alpha = int(a, 16) / 255 + else: + alpha = None + return ints_to_rgba(r, g, b, alpha) + + m = re.fullmatch(r_rgb, value_lower) or re.fullmatch(r_rgb_v4_style, value_lower) + if m: + return ints_to_rgba(*m.groups()) # type: ignore + + m = re.fullmatch(r_hsl, value_lower) or re.fullmatch(r_hsl_v4_style, value_lower) + if m: + return parse_hsl(*m.groups()) # type: ignore + + raise PydanticCustomError('color_error', 'value is not a valid color: string not recognised as a valid color') + + +def ints_to_rgba(r: Union[int, str], g: Union[int, str], b: Union[int, str], alpha: Optional[float] = None) -> RGBA: + """Converts integer or string values for RGB color and an optional alpha value to an `RGBA` object. + + Args: + r: An integer or string representing the red color value. + g: An integer or string representing the green color value. + b: An integer or string representing the blue color value. + alpha: A float representing the alpha value. Defaults to None. + + Returns: + An instance of the `RGBA` class with the corresponding color and alpha values. + """ + return RGBA(parse_color_value(r), parse_color_value(g), parse_color_value(b), parse_float_alpha(alpha)) + + +def parse_color_value(value: Union[int, str], max_val: int = 255) -> float: + """Parse the color value provided and return a number between 0 and 1. + + Args: + value: An integer or string color value. + max_val: Maximum range value. Defaults to 255. + + Raises: + PydanticCustomError: If the value is not a valid color. + + Returns: + A number between 0 and 1. + """ + try: + color = float(value) + except ValueError: + raise PydanticCustomError('color_error', 'value is not a valid color: color values must be a valid number') + if 0 <= color <= max_val: + return color / max_val + else: + raise PydanticCustomError( + 'color_error', + 'value is not a valid color: color values must be in the range 0 to {max_val}', + {'max_val': max_val}, + ) + + +def parse_float_alpha(value: Union[None, str, float, int]) -> Optional[float]: + """Parse an alpha value checking it's a valid float in the range 0 to 1. + + Args: + value: The input value to parse. + + Returns: + The parsed value as a float, or `None` if the value was None or equal 1. + + Raises: + PydanticCustomError: If the input value cannot be successfully parsed as a float in the expected range. + """ + if value is None: + return None + try: + if isinstance(value, str) and value.endswith('%'): + alpha = float(value[:-1]) / 100 + else: + alpha = float(value) + except ValueError: + raise PydanticCustomError('color_error', 'value is not a valid color: alpha values must be a valid float') + + if math.isclose(alpha, 1): + return None + elif 0 <= alpha <= 1: + return alpha + else: + raise PydanticCustomError('color_error', 'value is not a valid color: alpha values must be in the range 0 to 1') + + +def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: Optional[float] = None) -> RGBA: + """Parse raw hue, saturation, lightness, and alpha values and convert to RGBA. + + Args: + h: The hue value. + h_units: The unit for hue value. + sat: The saturation value. + light: The lightness value. + alpha: Alpha value. + + Returns: + An instance of `RGBA`. + """ + s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) + + h_value = float(h) + if h_units in {None, 'deg'}: + h_value = h_value % 360 / 360 + elif h_units == 'rad': + h_value = h_value % rads / rads + else: + # turns + h_value = h_value % 1 + + r, g, b = hls_to_rgb(h_value, l_value, s_value) + return RGBA(r, g, b, parse_float_alpha(alpha)) + + +def float_to_255(c: float) -> int: + """Converts a float value between 0 and 1 (inclusive) to an integer between 0 and 255 (inclusive). + + Args: + c: The float value to be converted. Must be between 0 and 1 (inclusive). + + Returns: + The integer equivalent of the given float value rounded to the nearest whole number. + + Raises: + ValueError: If the given float value is outside the acceptable range of 0 to 1 (inclusive). + """ + return int(round(c * 255)) + + +COLORS_BY_NAME = { + 'aliceblue': (240, 248, 255), + 'antiquewhite': (250, 235, 215), + 'aqua': (0, 255, 255), + 'aquamarine': (127, 255, 212), + 'azure': (240, 255, 255), + 'beige': (245, 245, 220), + 'bisque': (255, 228, 196), + 'black': (0, 0, 0), + 'blanchedalmond': (255, 235, 205), + 'blue': (0, 0, 255), + 'blueviolet': (138, 43, 226), + 'brown': (165, 42, 42), + 'burlywood': (222, 184, 135), + 'cadetblue': (95, 158, 160), + 'chartreuse': (127, 255, 0), + 'chocolate': (210, 105, 30), + 'coral': (255, 127, 80), + 'cornflowerblue': (100, 149, 237), + 'cornsilk': (255, 248, 220), + 'crimson': (220, 20, 60), + 'cyan': (0, 255, 255), + 'darkblue': (0, 0, 139), + 'darkcyan': (0, 139, 139), + 'darkgoldenrod': (184, 134, 11), + 'darkgray': (169, 169, 169), + 'darkgreen': (0, 100, 0), + 'darkgrey': (169, 169, 169), + 'darkkhaki': (189, 183, 107), + 'darkmagenta': (139, 0, 139), + 'darkolivegreen': (85, 107, 47), + 'darkorange': (255, 140, 0), + 'darkorchid': (153, 50, 204), + 'darkred': (139, 0, 0), + 'darksalmon': (233, 150, 122), + 'darkseagreen': (143, 188, 143), + 'darkslateblue': (72, 61, 139), + 'darkslategray': (47, 79, 79), + 'darkslategrey': (47, 79, 79), + 'darkturquoise': (0, 206, 209), + 'darkviolet': (148, 0, 211), + 'deeppink': (255, 20, 147), + 'deepskyblue': (0, 191, 255), + 'dimgray': (105, 105, 105), + 'dimgrey': (105, 105, 105), + 'dodgerblue': (30, 144, 255), + 'firebrick': (178, 34, 34), + 'floralwhite': (255, 250, 240), + 'forestgreen': (34, 139, 34), + 'fuchsia': (255, 0, 255), + 'gainsboro': (220, 220, 220), + 'ghostwhite': (248, 248, 255), + 'gold': (255, 215, 0), + 'goldenrod': (218, 165, 32), + 'gray': (128, 128, 128), + 'green': (0, 128, 0), + 'greenyellow': (173, 255, 47), + 'grey': (128, 128, 128), + 'honeydew': (240, 255, 240), + 'hotpink': (255, 105, 180), + 'indianred': (205, 92, 92), + 'indigo': (75, 0, 130), + 'ivory': (255, 255, 240), + 'khaki': (240, 230, 140), + 'lavender': (230, 230, 250), + 'lavenderblush': (255, 240, 245), + 'lawngreen': (124, 252, 0), + 'lemonchiffon': (255, 250, 205), + 'lightblue': (173, 216, 230), + 'lightcoral': (240, 128, 128), + 'lightcyan': (224, 255, 255), + 'lightgoldenrodyellow': (250, 250, 210), + 'lightgray': (211, 211, 211), + 'lightgreen': (144, 238, 144), + 'lightgrey': (211, 211, 211), + 'lightpink': (255, 182, 193), + 'lightsalmon': (255, 160, 122), + 'lightseagreen': (32, 178, 170), + 'lightskyblue': (135, 206, 250), + 'lightslategray': (119, 136, 153), + 'lightslategrey': (119, 136, 153), + 'lightsteelblue': (176, 196, 222), + 'lightyellow': (255, 255, 224), + 'lime': (0, 255, 0), + 'limegreen': (50, 205, 50), + 'linen': (250, 240, 230), + 'magenta': (255, 0, 255), + 'maroon': (128, 0, 0), + 'mediumaquamarine': (102, 205, 170), + 'mediumblue': (0, 0, 205), + 'mediumorchid': (186, 85, 211), + 'mediumpurple': (147, 112, 219), + 'mediumseagreen': (60, 179, 113), + 'mediumslateblue': (123, 104, 238), + 'mediumspringgreen': (0, 250, 154), + 'mediumturquoise': (72, 209, 204), + 'mediumvioletred': (199, 21, 133), + 'midnightblue': (25, 25, 112), + 'mintcream': (245, 255, 250), + 'mistyrose': (255, 228, 225), + 'moccasin': (255, 228, 181), + 'navajowhite': (255, 222, 173), + 'navy': (0, 0, 128), + 'oldlace': (253, 245, 230), + 'olive': (128, 128, 0), + 'olivedrab': (107, 142, 35), + 'orange': (255, 165, 0), + 'orangered': (255, 69, 0), + 'orchid': (218, 112, 214), + 'palegoldenrod': (238, 232, 170), + 'palegreen': (152, 251, 152), + 'paleturquoise': (175, 238, 238), + 'palevioletred': (219, 112, 147), + 'papayawhip': (255, 239, 213), + 'peachpuff': (255, 218, 185), + 'peru': (205, 133, 63), + 'pink': (255, 192, 203), + 'plum': (221, 160, 221), + 'powderblue': (176, 224, 230), + 'purple': (128, 0, 128), + 'red': (255, 0, 0), + 'rosybrown': (188, 143, 143), + 'royalblue': (65, 105, 225), + 'saddlebrown': (139, 69, 19), + 'salmon': (250, 128, 114), + 'sandybrown': (244, 164, 96), + 'seagreen': (46, 139, 87), + 'seashell': (255, 245, 238), + 'sienna': (160, 82, 45), + 'silver': (192, 192, 192), + 'skyblue': (135, 206, 235), + 'slateblue': (106, 90, 205), + 'slategray': (112, 128, 144), + 'slategrey': (112, 128, 144), + 'snow': (255, 250, 250), + 'springgreen': (0, 255, 127), + 'steelblue': (70, 130, 180), + 'tan': (210, 180, 140), + 'teal': (0, 128, 128), + 'thistle': (216, 191, 216), + 'tomato': (255, 99, 71), + 'turquoise': (64, 224, 208), + 'violet': (238, 130, 238), + 'wheat': (245, 222, 179), + 'white': (255, 255, 255), + 'whitesmoke': (245, 245, 245), + 'yellow': (255, 255, 0), + 'yellowgreen': (154, 205, 50), +} + +COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py new file mode 100644 index 0000000..6d89525 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/config.py @@ -0,0 +1,1296 @@ +"""Configuration for Pydantic models.""" + +from __future__ import annotations as _annotations + +import warnings +from re import Pattern +from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, Union, cast, overload + +from typing_extensions import TypeAlias, TypedDict, Unpack, deprecated + +from ._migration import getattr_migration +from .aliases import AliasGenerator +from .errors import PydanticUserError +from .warnings import PydanticDeprecatedSince211 + +if TYPE_CHECKING: + from ._internal._generate_schema import GenerateSchema as _GenerateSchema + from .fields import ComputedFieldInfo, FieldInfo + +__all__ = ('ConfigDict', 'with_config') + + +JsonValue: TypeAlias = Union[int, float, str, bool, None, list['JsonValue'], 'JsonDict'] +JsonDict: TypeAlias = dict[str, JsonValue] + +JsonEncoder = Callable[[Any], Any] + +JsonSchemaExtraCallable: TypeAlias = Union[ + Callable[[JsonDict], None], + Callable[[JsonDict, type[Any]], None], +] + +ExtraValues = Literal['allow', 'ignore', 'forbid'] + + +class ConfigDict(TypedDict, total=False): + """A TypedDict for configuring Pydantic behaviour.""" + + title: str | None + """The title for the generated JSON schema, defaults to the model's name""" + + model_title_generator: Callable[[type], str] | None + """A callable that takes a model class and returns the title for it. Defaults to `None`.""" + + field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None + """A callable that takes a field's name and info and returns title for it. Defaults to `None`.""" + + str_to_lower: bool + """Whether to convert all characters to lowercase for str types. Defaults to `False`.""" + + str_to_upper: bool + """Whether to convert all characters to uppercase for str types. Defaults to `False`.""" + + str_strip_whitespace: bool + """Whether to strip leading and trailing whitespace for str types.""" + + str_min_length: int + """The minimum length for str types. Defaults to `None`.""" + + str_max_length: int | None + """The maximum length for str types. Defaults to `None`.""" + + extra: ExtraValues | None + ''' + Whether to ignore, allow, or forbid extra data during model initialization. Defaults to `'ignore'`. + + Three configuration values are available: + + - `'ignore'`: Providing extra data is ignored (the default): + ```python + from pydantic import BaseModel, ConfigDict + + class User(BaseModel): + model_config = ConfigDict(extra='ignore') # (1)! + + name: str + + user = User(name='John Doe', age=20) # (2)! + print(user) + #> name='John Doe' + ``` + + 1. This is the default behaviour. + 2. The `age` argument is ignored. + + - `'forbid'`: Providing extra data is not permitted, and a [`ValidationError`][pydantic_core.ValidationError] + will be raised if this is the case: + ```python + from pydantic import BaseModel, ConfigDict, ValidationError + + + class Model(BaseModel): + x: int + + model_config = ConfigDict(extra='forbid') + + + try: + Model(x=1, y='a') + except ValidationError as exc: + print(exc) + """ + 1 validation error for Model + y + Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str] + """ + ``` + + - `'allow'`: Providing extra data is allowed and stored in the `__pydantic_extra__` dictionary attribute: + ```python + from pydantic import BaseModel, ConfigDict + + + class Model(BaseModel): + x: int + + model_config = ConfigDict(extra='allow') + + + m = Model(x=1, y='a') + assert m.__pydantic_extra__ == {'y': 'a'} + ``` + By default, no validation will be applied to these extra items, but you can set a type for the values by overriding + the type annotation for `__pydantic_extra__`: + ```python + from pydantic import BaseModel, ConfigDict, Field, ValidationError + + + class Model(BaseModel): + __pydantic_extra__: dict[str, int] = Field(init=False) # (1)! + + x: int + + model_config = ConfigDict(extra='allow') + + + try: + Model(x=1, y='a') + except ValidationError as exc: + print(exc) + """ + 1 validation error for Model + y + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] + """ + + m = Model(x=1, y='2') + assert m.x == 1 + assert m.y == 2 + assert m.model_dump() == {'x': 1, 'y': 2} + assert m.__pydantic_extra__ == {'y': 2} + ``` + + 1. The `= Field(init=False)` does not have any effect at runtime, but prevents the `__pydantic_extra__` field from + being included as a parameter to the model's `__init__` method by type checkers. + + As well as specifying an `extra` configuration value on the model, you can also provide it as an argument to the validation methods. + This will override any `extra` configuration value set on the model: + ```python + from pydantic import BaseModel, ConfigDict, ValidationError + + class Model(BaseModel): + x: int + model_config = ConfigDict(extra="allow") + + try: + # Override model config and forbid extra fields just this time + Model.model_validate({"x": 1, "y": 2}, extra="forbid") + except ValidationError as exc: + print(exc) + """ + 1 validation error for Model + y + Extra inputs are not permitted [type=extra_forbidden, input_value=2, input_type=int] + """ + ``` + ''' + + frozen: bool + """ + Whether models are faux-immutable, i.e. whether `__setattr__` is allowed, and also generates + a `__hash__()` method for the model. This makes instances of the model potentially hashable if all the + attributes are hashable. Defaults to `False`. + + Note: + On V1, the inverse of this setting was called `allow_mutation`, and was `True` by default. + """ + + populate_by_name: bool + """ + Whether an aliased field may be populated by its name as given by the model + attribute, as well as the alias. Defaults to `False`. + + !!! warning + `populate_by_name` usage is not recommended in v2.11+ and will be deprecated in v3. + Instead, you should use the [`validate_by_name`][pydantic.config.ConfigDict.validate_by_name] configuration setting. + + When `validate_by_name=True` and `validate_by_alias=True`, this is strictly equivalent to the + previous behavior of `populate_by_name=True`. + + In v2.11, we also introduced a [`validate_by_alias`][pydantic.config.ConfigDict.validate_by_alias] setting that introduces more fine grained + control for validation behavior. + + Here's how you might go about using the new settings to achieve the same behavior: + + ```python + from pydantic import BaseModel, ConfigDict, Field + + class Model(BaseModel): + model_config = ConfigDict(validate_by_name=True, validate_by_alias=True) + + my_field: str = Field(alias='my_alias') # (1)! + + m = Model(my_alias='foo') # (2)! + print(m) + #> my_field='foo' + + m = Model(my_field='foo') # (3)! + print(m) + #> my_field='foo' + ``` + + 1. The field `'my_field'` has an alias `'my_alias'`. + 2. The model is populated by the alias `'my_alias'`. + 3. The model is populated by the attribute name `'my_field'`. + """ + + use_enum_values: bool + """ + Whether to populate models with the `value` property of enums, rather than the raw enum. + This may be useful if you want to serialize `model.model_dump()` later. Defaults to `False`. + + !!! note + If you have an `Optional[Enum]` value that you set a default for, you need to use `validate_default=True` + for said Field to ensure that the `use_enum_values` flag takes effect on the default, as extracting an + enum's value occurs during validation, not serialization. + + ```python + from enum import Enum + from typing import Optional + + from pydantic import BaseModel, ConfigDict, Field + + class SomeEnum(Enum): + FOO = 'foo' + BAR = 'bar' + BAZ = 'baz' + + class SomeModel(BaseModel): + model_config = ConfigDict(use_enum_values=True) + + some_enum: SomeEnum + another_enum: Optional[SomeEnum] = Field( + default=SomeEnum.FOO, validate_default=True + ) + + model1 = SomeModel(some_enum=SomeEnum.BAR) + print(model1.model_dump()) + #> {'some_enum': 'bar', 'another_enum': 'foo'} + + model2 = SomeModel(some_enum=SomeEnum.BAR, another_enum=SomeEnum.BAZ) + print(model2.model_dump()) + #> {'some_enum': 'bar', 'another_enum': 'baz'} + ``` + """ + + validate_assignment: bool + """ + Whether to validate the data when the model is changed. Defaults to `False`. + + The default behavior of Pydantic is to validate the data when the model is created. + + In case the user changes the data after the model is created, the model is _not_ revalidated. + + ```python + from pydantic import BaseModel + + class User(BaseModel): + name: str + + user = User(name='John Doe') # (1)! + print(user) + #> name='John Doe' + user.name = 123 # (1)! + print(user) + #> name=123 + ``` + + 1. The validation happens only when the model is created. + 2. The validation does not happen when the data is changed. + + In case you want to revalidate the model when the data is changed, you can use `validate_assignment=True`: + + ```python + from pydantic import BaseModel, ValidationError + + class User(BaseModel, validate_assignment=True): # (1)! + name: str + + user = User(name='John Doe') # (2)! + print(user) + #> name='John Doe' + try: + user.name = 123 # (3)! + except ValidationError as e: + print(e) + ''' + 1 validation error for User + name + Input should be a valid string [type=string_type, input_value=123, input_type=int] + ''' + ``` + + 1. You can either use class keyword arguments, or `model_config` to set `validate_assignment=True`. + 2. The validation happens when the model is created. + 3. The validation _also_ happens when the data is changed. + """ + + arbitrary_types_allowed: bool + """ + Whether arbitrary types are allowed for field types. Defaults to `False`. + + ```python + from pydantic import BaseModel, ConfigDict, ValidationError + + # This is not a pydantic model, it's an arbitrary class + class Pet: + def __init__(self, name: str): + self.name = name + + class Model(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + pet: Pet + owner: str + + pet = Pet(name='Hedwig') + # A simple check of instance type is used to validate the data + model = Model(owner='Harry', pet=pet) + print(model) + #> pet=<__main__.Pet object at 0x0123456789ab> owner='Harry' + print(model.pet) + #> <__main__.Pet object at 0x0123456789ab> + print(model.pet.name) + #> Hedwig + print(type(model.pet)) + #> + try: + # If the value is not an instance of the type, it's invalid + Model(owner='Harry', pet='Hedwig') + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + pet + Input should be an instance of Pet [type=is_instance_of, input_value='Hedwig', input_type=str] + ''' + + # Nothing in the instance of the arbitrary type is checked + # Here name probably should have been a str, but it's not validated + pet2 = Pet(name=42) + model2 = Model(owner='Harry', pet=pet2) + print(model2) + #> pet=<__main__.Pet object at 0x0123456789ab> owner='Harry' + print(model2.pet) + #> <__main__.Pet object at 0x0123456789ab> + print(model2.pet.name) + #> 42 + print(type(model2.pet)) + #> + ``` + """ + + from_attributes: bool + """ + Whether to build models and look up discriminators of tagged unions using python object attributes. + """ + + loc_by_alias: bool + """Whether to use the actual key provided in the data (e.g. alias) for error `loc`s rather than the field's name. Defaults to `True`.""" + + alias_generator: Callable[[str], str] | AliasGenerator | None + """ + A callable that takes a field name and returns an alias for it + or an instance of [`AliasGenerator`][pydantic.aliases.AliasGenerator]. Defaults to `None`. + + When using a callable, the alias generator is used for both validation and serialization. + If you want to use different alias generators for validation and serialization, you can use + [`AliasGenerator`][pydantic.aliases.AliasGenerator] instead. + + If data source field names do not match your code style (e.g. CamelCase fields), + you can automatically generate aliases using `alias_generator`. Here's an example with + a basic callable: + + ```python + from pydantic import BaseModel, ConfigDict + from pydantic.alias_generators import to_pascal + + class Voice(BaseModel): + model_config = ConfigDict(alias_generator=to_pascal) + + name: str + language_code: str + + voice = Voice(Name='Filiz', LanguageCode='tr-TR') + print(voice.language_code) + #> tr-TR + print(voice.model_dump(by_alias=True)) + #> {'Name': 'Filiz', 'LanguageCode': 'tr-TR'} + ``` + + If you want to use different alias generators for validation and serialization, you can use + [`AliasGenerator`][pydantic.aliases.AliasGenerator]. + + ```python + from pydantic import AliasGenerator, BaseModel, ConfigDict + from pydantic.alias_generators import to_camel, to_pascal + + class Athlete(BaseModel): + first_name: str + last_name: str + sport: str + + model_config = ConfigDict( + alias_generator=AliasGenerator( + validation_alias=to_camel, + serialization_alias=to_pascal, + ) + ) + + athlete = Athlete(firstName='John', lastName='Doe', sport='track') + print(athlete.model_dump(by_alias=True)) + #> {'FirstName': 'John', 'LastName': 'Doe', 'Sport': 'track'} + ``` + + Note: + Pydantic offers three built-in alias generators: [`to_pascal`][pydantic.alias_generators.to_pascal], + [`to_camel`][pydantic.alias_generators.to_camel], and [`to_snake`][pydantic.alias_generators.to_snake]. + """ + + ignored_types: tuple[type, ...] + """A tuple of types that may occur as values of class attributes without annotations. This is + typically used for custom descriptors (classes that behave like `property`). If an attribute is set on a + class without an annotation and has a type that is not in this tuple (or otherwise recognized by + _pydantic_), an error will be raised. Defaults to `()`. + """ + + allow_inf_nan: bool + """Whether to allow infinity (`+inf` an `-inf`) and NaN values to float and decimal fields. Defaults to `True`.""" + + json_schema_extra: JsonDict | JsonSchemaExtraCallable | None + """A dict or callable to provide extra JSON schema properties. Defaults to `None`.""" + + json_encoders: dict[type[object], JsonEncoder] | None + """ + A `dict` of custom JSON encoders for specific types. Defaults to `None`. + + /// version-deprecated | v2 + This configuration option is a carryover from v1. We originally planned to remove it in v2 but didn't have a 1:1 replacement + so we are keeping it for now. It is still deprecated and will likely be removed in the future. + /// + """ + + # new in V2 + strict: bool + """ + Whether strict validation is applied to all fields on the model. + + By default, Pydantic attempts to coerce values to the correct type, when possible. + + There are situations in which you may want to disable this behavior, and instead raise an error if a value's type + does not match the field's type annotation. + + To configure strict mode for all fields on a model, you can set `strict=True` on the model. + + ```python + from pydantic import BaseModel, ConfigDict + + class Model(BaseModel): + model_config = ConfigDict(strict=True) + + name: str + age: int + ``` + + See [Strict Mode](../concepts/strict_mode.md) for more details. + + See the [Conversion Table](../concepts/conversion_table.md) for more details on how Pydantic converts data in both + strict and lax modes. + + /// version-added | v2 + /// + """ + # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' + revalidate_instances: Literal['always', 'never', 'subclass-instances'] + """ + When and how to revalidate models and dataclasses during validation. Can be one of: + + - `'never'`: will *not* revalidate models and dataclasses during validation + - `'always'`: will revalidate models and dataclasses during validation + - `'subclass-instances'`: will revalidate models and dataclasses during validation if the instance is a + subclass of the model or dataclass + + The default is `'never'` (no revalidation). + + This configuration only affects *the current model* it is applied on, and does *not* propagate to the models + referenced in fields. + + ```python + from pydantic import BaseModel + + class User(BaseModel, revalidate_instances='never'): # (1)! + name: str + + class Transaction(BaseModel): + user: User + + my_user = User(name='John') + t = Transaction(user=my_user) + + my_user.name = 1 # (2)! + t = Transaction(user=my_user) # (3)! + print(t) + #> user=User(name=1) + ``` + + 1. This is the default behavior. + 2. The assignment is *not* validated, unless you set [`validate_assignment`][pydantic.ConfigDict.validate_assignment] in the configuration. + 3. Since `revalidate_instances` is set to `'never'`, the user instance is not revalidated. + + Here is an example demonstrating the behavior of `'subclass-instances'`: + + ```python + from pydantic import BaseModel + + class User(BaseModel, revalidate_instances='subclass-instances'): + name: str + + class SubUser(User): + age: int + + class Transaction(BaseModel): + user: User + + my_user = User(name='John') + my_user.name = 1 # (1)! + t = Transaction(user=my_user) # (2)! + print(t) + #> user=User(name=1) + + my_sub_user = SubUser(name='John', age=20) + t = Transaction(user=my_sub_user) + print(t) # (3)! + #> user=User(name='John') + ``` + + 1. The assignment is *not* validated, unless you set [`validate_assignment`][pydantic.ConfigDict.validate_assignment] in the configuration. + 2. Because `my_user` is a "direct" instance of `User`, it is *not* being revalidated. It would have been the case if + `revalidate_instances` was set to `'always'`. + 3. Because `my_sub_user` is an instance of a `User` subclass, it is being revalidated. In this case, Pydantic coerces `my_sub_user` to the defined + `User` class defined on `Transaction`. If one of its fields had an invalid value, a validation error would have been raised. + + /// version-added | v2 + /// + """ + + ser_json_timedelta: Literal['iso8601', 'float'] + """ + The format of JSON serialized timedeltas. Accepts the string values of `'iso8601'` and + `'float'`. Defaults to `'iso8601'`. + + - `'iso8601'` will serialize timedeltas to [ISO 8601 text format](https://en.wikipedia.org/wiki/ISO_8601#Durations). + - `'float'` will serialize timedeltas to the total number of seconds. + + /// version-changed | v2.12 + It is now recommended to use the [`ser_json_temporal`][pydantic.config.ConfigDict.ser_json_temporal] + setting. `ser_json_timedelta` will be deprecated in v3. + /// + """ + + ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] + """ + The format of JSON serialized temporal types from the [`datetime`][] module. This includes: + + - [`datetime.datetime`][] + - [`datetime.date`][] + - [`datetime.time`][] + - [`datetime.timedelta`][] + + Can be one of: + + - `'iso8601'` will serialize date-like types to [ISO 8601 text format](https://en.wikipedia.org/wiki/ISO_8601#Durations). + - `'milliseconds'` will serialize date-like types to a floating point number of milliseconds since the epoch. + - `'seconds'` will serialize date-like types to a floating point number of seconds since the epoch. + + Defaults to `'iso8601'`. + + /// version-added | v2.12 + This setting replaces [`ser_json_timedelta`][pydantic.config.ConfigDict.ser_json_timedelta], + which will be deprecated in v3. `ser_json_temporal` adds more configurability for the other temporal types. + /// + """ + + val_temporal_unit: Literal['seconds', 'milliseconds', 'infer'] + """ + The unit to assume for validating numeric input for datetime-like types ([`datetime.datetime`][] and [`datetime.date`][]). Can be one of: + + - `'seconds'` will validate date or time numeric inputs as seconds since the [epoch]. + - `'milliseconds'` will validate date or time numeric inputs as milliseconds since the [epoch]. + - `'infer'` will infer the unit from the string numeric input on unix time as: + + * seconds since the [epoch] if $-2^{10} <= v <= 2^{10}$ + * milliseconds since the [epoch] (if $v < -2^{10}$ or $v > 2^{10}$). + + Defaults to `'infer'`. + + /// version-added | v2.12 + /// + + [epoch]: https://en.wikipedia.org/wiki/Unix_time + """ + + ser_json_bytes: Literal['utf8', 'base64', 'hex'] + """ + The encoding of JSON serialized bytes. Defaults to `'utf8'`. + Set equal to `val_json_bytes` to get back an equal value after serialization round trip. + + - `'utf8'` will serialize bytes to UTF-8 strings. + - `'base64'` will serialize bytes to URL safe base64 strings. + - `'hex'` will serialize bytes to hexadecimal strings. + """ + + val_json_bytes: Literal['utf8', 'base64', 'hex'] + """ + /// version-added | v2.9 + /// + + The encoding of JSON serialized bytes to decode. Defaults to `'utf8'`. + Set equal to `ser_json_bytes` to get back an equal value after serialization round trip. + + - `'utf8'` will deserialize UTF-8 strings to bytes. + - `'base64'` will deserialize URL safe base64 strings to bytes. + - `'hex'` will deserialize hexadecimal strings to bytes. + """ + + ser_json_inf_nan: Literal['null', 'constants', 'strings'] + """ + The encoding of JSON serialized infinity and NaN float values. Defaults to `'null'`. + + - `'null'` will serialize infinity and NaN values as `null`. + - `'constants'` will serialize infinity and NaN values as `Infinity` and `NaN`. + - `'strings'` will serialize infinity as string `"Infinity"` and NaN as string `"NaN"`. + """ + + # whether to validate default values during validation, default False + validate_default: bool + """Whether to validate default values during validation. Defaults to `False`.""" + + validate_return: bool + """Whether to validate the return value from call validators. Defaults to `False`.""" + + protected_namespaces: tuple[str | Pattern[str], ...] + """ + A tuple of strings and/or regex patterns that prevent models from having fields with names that conflict with its existing members/methods. + + Strings are matched on a prefix basis. For instance, with `'dog'`, having a field named `'dog_name'` will be disallowed. + + Regex patterns are matched on the entire field name. For instance, with the pattern `'^dog$'`, having a field named `'dog'` will be disallowed, + but `'dog_name'` will be accepted. + + Defaults to `('model_validate', 'model_dump')`. This default is used to prevent collisions with the existing (and possibly future) + [validation](../concepts/models.md#validating-data) and [serialization](../concepts/serialization.md#serializing-data) methods. + + ```python + import warnings + + from pydantic import BaseModel + + warnings.filterwarnings('error') # Raise warnings as errors + + try: + + class Model(BaseModel): + model_dump_something: str + + except UserWarning as e: + print(e) + ''' + Field 'model_dump_something' in 'Model' conflicts with protected namespace 'model_dump'. + + You may be able to solve this by setting the 'protected_namespaces' configuration to ('model_validate',). + ''' + ``` + + You can customize this behavior using the `protected_namespaces` setting: + + ```python {test="skip"} + import re + import warnings + + from pydantic import BaseModel, ConfigDict + + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter('always') # Catch all warnings + + class Model(BaseModel): + safe_field: str + also_protect_field: str + protect_this: str + + model_config = ConfigDict( + protected_namespaces=( + 'protect_me_', + 'also_protect_', + re.compile('^protect_this$'), + ) + ) + + for warning in caught_warnings: + print(f'{warning.message}') + ''' + Field 'also_protect_field' in 'Model' conflicts with protected namespace 'also_protect_'. + You may be able to solve this by setting the 'protected_namespaces' configuration to ('protect_me_', re.compile('^protect_this$'))`. + + Field 'protect_this' in 'Model' conflicts with protected namespace 're.compile('^protect_this$')'. + You may be able to solve this by setting the 'protected_namespaces' configuration to ('protect_me_', 'also_protect_')`. + ''' + ``` + + While Pydantic will only emit a warning when an item is in a protected namespace but does not actually have a collision, + an error _is_ raised if there is an actual collision with an existing attribute: + + ```python + from pydantic import BaseModel, ConfigDict + + try: + + class Model(BaseModel): + model_validate: str + + model_config = ConfigDict(protected_namespaces=('model_',)) + + except ValueError as e: + print(e) + ''' + Field 'model_validate' conflicts with member > of protected namespace 'model_'. + ''' + ``` + + /// version-changed | v2.10 + The default protected namespaces was changed from `('model_',)` to `('model_validate', 'model_dump')`, to allow + for fields like `model_id`, `model_name` to be used. + /// + """ + + hide_input_in_errors: bool + """ + Whether to hide inputs when printing errors. Defaults to `False`. + + Pydantic shows the input value and type when it raises `ValidationError` during the validation. + + ```python + from pydantic import BaseModel, ValidationError + + class Model(BaseModel): + a: str + + try: + Model(a=123) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + a + Input should be a valid string [type=string_type, input_value=123, input_type=int] + ''' + ``` + + You can hide the input value and type by setting the `hide_input_in_errors` config to `True`. + + ```python + from pydantic import BaseModel, ConfigDict, ValidationError + + class Model(BaseModel): + a: str + model_config = ConfigDict(hide_input_in_errors=True) + + try: + Model(a=123) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + a + Input should be a valid string [type=string_type] + ''' + ``` + """ + + defer_build: bool + """ + Whether to defer model validator and serializer construction until the first model validation. Defaults to False. + + This can be useful to avoid the overhead of building models which are only + used nested within other models, or when you want to manually define type namespace via + [`Model.model_rebuild(_types_namespace=...)`][pydantic.BaseModel.model_rebuild]. + + /// version-changed | v2.10 + The setting also applies to [Pydantic dataclasses](../concepts/dataclasses.md) and [type adapters](../concepts/type_adapter.md). + /// + """ + + plugin_settings: dict[str, object] | None + """A `dict` of settings for plugins. Defaults to `None`.""" + + schema_generator: type[_GenerateSchema] | None + """ + The `GenerateSchema` class to use during core schema generation. + + /// version-deprecated | v2.10 + The `GenerateSchema` class is private and highly subject to change. + /// + """ + + json_schema_serialization_defaults_required: bool + """ + Whether fields with default values should be marked as required in the serialization schema. Defaults to `False`. + + This ensures that the serialization schema will reflect the fact a field with a default will always be present + when serializing the model, even though it is not required for validation. + + However, there are scenarios where this may be undesirable — in particular, if you want to share the schema + between validation and serialization, and don't mind fields with defaults being marked as not required during + serialization. See [#7209](https://github.com/pydantic/pydantic/issues/7209) for more details. + + ```python + from pydantic import BaseModel, ConfigDict + + class Model(BaseModel): + a: str = 'a' + + model_config = ConfigDict(json_schema_serialization_defaults_required=True) + + print(Model.model_json_schema(mode='validation')) + ''' + { + 'properties': {'a': {'default': 'a', 'title': 'A', 'type': 'string'}}, + 'title': 'Model', + 'type': 'object', + } + ''' + print(Model.model_json_schema(mode='serialization')) + ''' + { + 'properties': {'a': {'default': 'a', 'title': 'A', 'type': 'string'}}, + 'required': ['a'], + 'title': 'Model', + 'type': 'object', + } + ''' + ``` + + /// version-added | v2.4 + /// + """ + + json_schema_mode_override: Literal['validation', 'serialization', None] + """ + If not `None`, the specified mode will be used to generate the JSON schema regardless of what `mode` was passed to + the function call. Defaults to `None`. + + This provides a way to force the JSON schema generation to reflect a specific mode, e.g., to always use the + validation schema. + + It can be useful when using frameworks (such as FastAPI) that may generate different schemas for validation + and serialization that must both be referenced from the same schema; when this happens, we automatically append + `-Input` to the definition reference for the validation schema and `-Output` to the definition reference for the + serialization schema. By specifying a `json_schema_mode_override` though, this prevents the conflict between + the validation and serialization schemas (since both will use the specified schema), and so prevents the suffixes + from being added to the definition references. + + ```python + from pydantic import BaseModel, ConfigDict, Json + + class Model(BaseModel): + a: Json[int] # requires a string to validate, but will dump an int + + print(Model.model_json_schema(mode='serialization')) + ''' + { + 'properties': {'a': {'title': 'A', 'type': 'integer'}}, + 'required': ['a'], + 'title': 'Model', + 'type': 'object', + } + ''' + + class ForceInputModel(Model): + # the following ensures that even with mode='serialization', we + # will get the schema that would be generated for validation. + model_config = ConfigDict(json_schema_mode_override='validation') + + print(ForceInputModel.model_json_schema(mode='serialization')) + ''' + { + 'properties': { + 'a': { + 'contentMediaType': 'application/json', + 'contentSchema': {'type': 'integer'}, + 'title': 'A', + 'type': 'string', + } + }, + 'required': ['a'], + 'title': 'ForceInputModel', + 'type': 'object', + } + ''' + ``` + + /// version-added | v2.4 + /// + """ + + coerce_numbers_to_str: bool + """ + If `True`, enables automatic coercion of any `Number` type to `str` in "lax" (non-strict) mode. Defaults to `False`. + + Pydantic doesn't allow number types (`int`, `float`, `Decimal`) to be coerced as type `str` by default. + + ```python + from decimal import Decimal + + from pydantic import BaseModel, ConfigDict, ValidationError + + class Model(BaseModel): + value: str + + try: + print(Model(value=42)) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + value + Input should be a valid string [type=string_type, input_value=42, input_type=int] + ''' + + class Model(BaseModel): + model_config = ConfigDict(coerce_numbers_to_str=True) + + value: str + + repr(Model(value=42).value) + #> "42" + repr(Model(value=42.13).value) + #> "42.13" + repr(Model(value=Decimal('42.13')).value) + #> "42.13" + ``` + """ + + regex_engine: Literal['rust-regex', 'python-re'] + """ + The regex engine to be used for pattern validation. + Defaults to `'rust-regex'`. + + - `'rust-regex'` uses the [`regex`](https://docs.rs/regex) Rust crate, + which is non-backtracking and therefore more DDoS resistant, but does not support all regex features. + - `'python-re'` use the [`re`][] module, which supports all regex features, but may be slower. + + !!! note + If you use a compiled regex pattern, the `'python-re'` engine will be used regardless of this setting. + This is so that flags such as [`re.IGNORECASE`][] are respected. + + ```python + from pydantic import BaseModel, ConfigDict, Field, ValidationError + + class Model(BaseModel): + model_config = ConfigDict(regex_engine='python-re') + + value: str = Field(pattern=r'^abc(?=def)') + + print(Model(value='abcdef').value) + #> abcdef + + try: + print(Model(value='abxyzcdef')) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + value + String should match pattern '^abc(?=def)' [type=string_pattern_mismatch, input_value='abxyzcdef', input_type=str] + ''' + ``` + + /// version-added | v2.5 + /// + """ + + validation_error_cause: bool + """ + If `True`, Python exceptions that were part of a validation failure will be shown as an exception group as a cause. Can be useful for debugging. Defaults to `False`. + + Note: + Python 3.10 and older don't support exception groups natively. <=3.10, backport must be installed: `pip install exceptiongroup`. + + Note: + The structure of validation errors are likely to change in future Pydantic versions. Pydantic offers no guarantees about their structure. Should be used for visual traceback debugging only. + + /// version-added | v2.5 + /// + """ + + use_attribute_docstrings: bool + ''' + Whether docstrings of attributes (bare string literals immediately following the attribute declaration) + should be used for field descriptions. Defaults to `False`. + + ```python + from pydantic import BaseModel, ConfigDict, Field + + + class Model(BaseModel): + model_config = ConfigDict(use_attribute_docstrings=True) + + x: str + """ + Example of an attribute docstring + """ + + y: int = Field(description="Description in Field") + """ + Description in Field overrides attribute docstring + """ + + + print(Model.model_fields["x"].description) + # > Example of an attribute docstring + print(Model.model_fields["y"].description) + # > Description in Field + ``` + This requires the source code of the class to be available at runtime (and so won't work in the interactive interpreter shell). + + !!! warning "Usage with `TypedDict` and stdlib dataclasses" + Due to current limitations, attribute docstrings detection may not work as expected when using + [`TypedDict`][typing.TypedDict] and stdlib dataclasses, in particular when: + + - inheritance is being used. + - multiple classes have the same name in the same source file (unless Python 3.13 or greater is used). + + /// version-added | v2.7 + /// + ''' + + cache_strings: bool | Literal['all', 'keys', 'none'] + """ + Whether to cache strings to avoid constructing new Python objects. Defaults to True. + + Enabling this setting should significantly improve validation performance while increasing memory usage slightly. + + - `True` or `'all'` (the default): cache all strings + - `'keys'`: cache only dictionary keys + - `False` or `'none'`: no caching + + !!! note + `True` or `'all'` is required to cache strings during general validation because + validators don't know if they're in a key or a value. + + !!! tip + If repeated strings are rare, it's recommended to use `'keys'` or `'none'` to reduce memory usage, + as the performance difference is minimal if repeated strings are rare. + + /// version-added | v2.7 + /// + """ + + validate_by_alias: bool + """ + Whether an aliased field may be populated by its alias. Defaults to `True`. + + Here's an example of disabling validation by alias: + + ```py + from pydantic import BaseModel, ConfigDict, Field + + class Model(BaseModel): + model_config = ConfigDict(validate_by_name=True, validate_by_alias=False) + + my_field: str = Field(validation_alias='my_alias') # (1)! + + m = Model(my_field='foo') # (2)! + print(m) + #> my_field='foo' + ``` + + 1. The field `'my_field'` has an alias `'my_alias'`. + 2. The model can only be populated by the attribute name `'my_field'`. + + !!! warning + You cannot set both `validate_by_alias` and `validate_by_name` to `False`. + This would make it impossible to populate an attribute. + + See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example. + + If you set `validate_by_alias` to `False`, under the hood, Pydantic dynamically sets + `validate_by_name` to `True` to ensure that validation can still occur. + + /// version-added | v2.11 + This setting was introduced in conjunction with [`validate_by_name`][pydantic.ConfigDict.validate_by_name] + to empower users with more fine grained validation control. + /// + """ + + validate_by_name: bool + """ + Whether an aliased field may be populated by its name as given by the model + attribute. Defaults to `False`. + + ```python + from pydantic import BaseModel, ConfigDict, Field + + class Model(BaseModel): + model_config = ConfigDict(validate_by_name=True, validate_by_alias=True) + + my_field: str = Field(validation_alias='my_alias') # (1)! + + m = Model(my_alias='foo') # (2)! + print(m) + #> my_field='foo' + + m = Model(my_field='foo') # (3)! + print(m) + #> my_field='foo' + ``` + + 1. The field `'my_field'` has an alias `'my_alias'`. + 2. The model is populated by the alias `'my_alias'`. + 3. The model is populated by the attribute name `'my_field'`. + + !!! warning + You cannot set both `validate_by_alias` and `validate_by_name` to `False`. + This would make it impossible to populate an attribute. + + See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example. + + /// version-added | v2.11 + This setting was introduced in conjunction with [`validate_by_alias`][pydantic.ConfigDict.validate_by_alias] + to empower users with more fine grained validation control. It is an alternative to [`populate_by_name`][pydantic.ConfigDict.populate_by_name], + that enables validation by name **and** by alias. + /// + """ + + serialize_by_alias: bool + """ + Whether an aliased field should be serialized by its alias. Defaults to `False`. + + Note: In v2.11, `serialize_by_alias` was introduced to address the + [popular request](https://github.com/pydantic/pydantic/issues/8379) + for consistency with alias behavior for validation and serialization settings. + In v3, the default value is expected to change to `True` for consistency with the validation default. + + ```python + from pydantic import BaseModel, ConfigDict, Field + + class Model(BaseModel): + model_config = ConfigDict(serialize_by_alias=True) + + my_field: str = Field(serialization_alias='my_alias') # (1)! + + m = Model(my_field='foo') + print(m.model_dump()) # (2)! + #> {'my_alias': 'foo'} + ``` + + 1. The field `'my_field'` has an alias `'my_alias'`. + 2. The model is serialized using the alias `'my_alias'` for the `'my_field'` attribute. + + + /// version-added | v2.11 + This setting was introduced to address the [popular request](https://github.com/pydantic/pydantic/issues/8379) + for consistency with alias behavior for validation and serialization. + + In v3, the default value is expected to change to `True` for consistency with the validation default. + /// + """ + + url_preserve_empty_path: bool + """ + Whether to preserve empty URL paths when validating values for a URL type. Defaults to `False`. + + ```python + from pydantic import AnyUrl, BaseModel, ConfigDict + + class Model(BaseModel): + model_config = ConfigDict(url_preserve_empty_path=True) + + url: AnyUrl + + m = Model(url='http://example.com') + print(m.url) + #> http://example.com + ``` + + /// version-added | v2.12 + /// + """ + + polymorphic_serialization: bool + """ + Whether to use polymorphic serialization for subclasses of the model or Pydantic dataclass. Defaults to `False`. + """ + + +_TypeT = TypeVar('_TypeT', bound=type) + + +@overload +@deprecated('Passing `config` as a keyword argument is deprecated. Pass `config` as a positional argument instead.') +def with_config(*, config: ConfigDict) -> Callable[[_TypeT], _TypeT]: ... + + +@overload +def with_config(config: ConfigDict, /) -> Callable[[_TypeT], _TypeT]: ... + + +@overload +def with_config(**config: Unpack[ConfigDict]) -> Callable[[_TypeT], _TypeT]: ... + + +def with_config(config: ConfigDict | None = None, /, **kwargs: Any) -> Callable[[_TypeT], _TypeT]: + """!!! abstract "Usage Documentation" + [Configuration with other types](../concepts/config.md#configuration-on-other-supported-types) + + A convenience decorator to set a [Pydantic configuration](config.md) on a `TypedDict` or a `dataclass` from the standard library. + + Although the configuration can be set using the `__pydantic_config__` attribute, it does not play well with type checkers, + especially with `TypedDict`. + + !!! example "Usage" + + ```python + from typing_extensions import TypedDict + + from pydantic import ConfigDict, TypeAdapter, with_config + + @with_config(ConfigDict(str_to_lower=True)) + class TD(TypedDict): + x: str + + ta = TypeAdapter(TD) + + print(ta.validate_python({'x': 'ABC'})) + #> {'x': 'abc'} + ``` + + /// deprecated-removed | v2.11 v3 + Passing `config` as a keyword argument. + /// + + /// version-changed | v2.11 + Keyword arguments can be provided directly instead of a config dictionary. + /// + """ + if config is not None and kwargs: + raise ValueError('Cannot specify both `config` and keyword arguments') + + if len(kwargs) == 1 and (kwargs_conf := kwargs.get('config')) is not None: + warnings.warn( + 'Passing `config` as a keyword argument is deprecated. Pass `config` as a positional argument instead', + category=PydanticDeprecatedSince211, + stacklevel=2, + ) + final_config = cast(ConfigDict, kwargs_conf) + else: + final_config = config if config is not None else cast(ConfigDict, kwargs) + + def inner(class_: _TypeT, /) -> _TypeT: + # Ideally, we would check for `class_` to either be a `TypedDict` or a stdlib dataclass. + # However, the `@with_config` decorator can be applied *after* `@dataclass`. To avoid + # common mistakes, we at least check for `class_` to not be a Pydantic model. + from ._internal._utils import is_model_class + + if is_model_class(class_): + raise PydanticUserError( + f'Cannot use `with_config` on {class_.__name__} as it is a Pydantic model', + code='with-config-on-model', + ) + class_.__pydantic_config__ = final_config + return class_ + + return inner + + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py new file mode 100644 index 0000000..46401c3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py @@ -0,0 +1,413 @@ +"""Provide an enhanced dataclass that performs validation.""" + +from __future__ import annotations as _annotations + +import dataclasses +import functools +import sys +import types +from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, overload +from warnings import warn + +from typing_extensions import TypeGuard, dataclass_transform + +from ._internal import _config, _decorators, _mock_val_ser, _namespace_utils, _typing_extra +from ._internal import _dataclasses as _pydantic_dataclasses +from ._migration import getattr_migration +from .config import ConfigDict +from .errors import PydanticUserError +from .fields import Field, FieldInfo, PrivateAttr + +if TYPE_CHECKING: + from ._internal._dataclasses import PydanticDataclass + from ._internal._namespace_utils import MappingNamespace + +__all__ = 'dataclass', 'rebuild_dataclass' + +_T = TypeVar('_T') + +if sys.version_info >= (3, 10): + + @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @overload + def dataclass( + *, + init: Literal[False] = False, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: ConfigDict | type[object] | None = None, + validate_on_init: bool | None = None, + kw_only: bool = ..., + slots: bool = ..., + ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore + ... + + @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @overload + def dataclass( + _cls: type[_T], # type: ignore + *, + init: Literal[False] = False, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool | None = None, + config: ConfigDict | type[object] | None = None, + validate_on_init: bool | None = None, + kw_only: bool = ..., + slots: bool = ..., + ) -> type[PydanticDataclass]: ... + +else: + + @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @overload + def dataclass( + *, + init: Literal[False] = False, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool | None = None, + config: ConfigDict | type[object] | None = None, + validate_on_init: bool | None = None, + ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore + ... + + @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @overload + def dataclass( + _cls: type[_T], # type: ignore + *, + init: Literal[False] = False, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool | None = None, + config: ConfigDict | type[object] | None = None, + validate_on_init: bool | None = None, + ) -> type[PydanticDataclass]: ... + + +@dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) +def dataclass( + _cls: type[_T] | None = None, + *, + init: Literal[False] = False, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool | None = None, + config: ConfigDict | type[object] | None = None, + validate_on_init: bool | None = None, + kw_only: bool = False, + slots: bool = False, +) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]: + """!!! abstract "Usage Documentation" + [`dataclasses`](../concepts/dataclasses.md) + + A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclass`, + but with added validation. + + This function should be used similarly to `dataclasses.dataclass`. + + Args: + _cls: The target `dataclass`. + init: Included for signature compatibility with `dataclasses.dataclass`, and is passed through to + `dataclasses.dataclass` when appropriate. If specified, must be set to `False`, as pydantic inserts its + own `__init__` function. + repr: A boolean indicating whether to include the field in the `__repr__` output. + eq: Determines if a `__eq__` method should be generated for the class. + order: Determines if comparison magic methods should be generated, such as `__lt__`, but not `__eq__`. + unsafe_hash: Determines if a `__hash__` method should be included in the class, as in `dataclasses.dataclass`. + frozen: Determines if the generated class should be a 'frozen' `dataclass`, which does not allow its + attributes to be modified after it has been initialized. If not set, the value from the provided `config` argument will be used (and will default to `False` otherwise). + config: The Pydantic config to use for the `dataclass`. + validate_on_init: A deprecated parameter included for backwards compatibility; in V2, all Pydantic dataclasses + are validated on init. + kw_only: Determines if `__init__` method parameters must be specified by keyword only. Defaults to `False`. + slots: Determines if the generated class should be a 'slots' `dataclass`, which does not allow the addition of + new attributes after instantiation. + + Returns: + A decorator that accepts a class as its argument and returns a Pydantic `dataclass`. + + Raises: + AssertionError: Raised if `init` is not `False` or `validate_on_init` is `False`. + """ + assert init is False, 'pydantic.dataclasses.dataclass only supports init=False' + assert validate_on_init is not False, 'validate_on_init=False is no longer supported' + + if sys.version_info >= (3, 10): + kwargs = {'kw_only': kw_only, 'slots': slots} + else: + kwargs = {} + + def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]: + """Create a Pydantic dataclass from a regular dataclass. + + Args: + cls: The class to create the Pydantic dataclass from. + + Returns: + A Pydantic dataclass. + """ + from ._internal._utils import is_model_class + + if is_model_class(cls): + raise PydanticUserError( + f'Cannot create a Pydantic dataclass from {cls.__name__} as it is already a Pydantic model', + code='dataclass-on-model', + ) + + original_cls = cls + + # we warn on conflicting config specifications, but only if the class doesn't have a dataclass base + # because a dataclass base might provide a __pydantic_config__ attribute that we don't want to warn about + has_dataclass_base = any(dataclasses.is_dataclass(base) for base in cls.__bases__) + if not has_dataclass_base and config is not None and hasattr(cls, '__pydantic_config__'): + warn( + f'`config` is set via both the `dataclass` decorator and `__pydantic_config__` for dataclass {cls.__name__}. ' + f'The `config` specification from `dataclass` decorator will take priority.', + category=UserWarning, + stacklevel=2, + ) + + # if config is not explicitly provided, try to read it from the type + config_dict = config if config is not None else getattr(cls, '__pydantic_config__', None) + config_wrapper = _config.ConfigWrapper(config_dict) + decorators = _decorators.DecoratorInfos.build(cls, replace_wrapped_methods=True) + decorators.update_from_config(config_wrapper) + + # Keep track of the original __doc__ so that we can restore it after applying the dataclasses decorator + # Otherwise, classes with no __doc__ will have their signature added into the JSON schema description, + # since dataclasses.dataclass will set this as the __doc__ + original_doc = cls.__doc__ + + if _pydantic_dataclasses.is_stdlib_dataclass(cls): + # Vanilla dataclasses include a default docstring (representing the class signature), + # which we don't want to preserve. + original_doc = None + + # We don't want to add validation to the existing std lib dataclass, so we will subclass it + # If the class is generic, we need to make sure the subclass also inherits from Generic + # with all the same parameters. + bases = (cls,) + if issubclass(cls, Generic): + generic_base = Generic[cls.__parameters__] # type: ignore + bases = bases + (generic_base,) + cls = types.new_class(cls.__name__, bases) + + # Respect frozen setting from dataclass constructor and fallback to config setting if not provided + if frozen is not None: + frozen_ = frozen + if config_wrapper.frozen: + # It's not recommended to define both, as the setting from the dataclass decorator will take priority. + warn( + f'`frozen` is set via both the `dataclass` decorator and `config` for dataclass {cls.__name__!r}.' + 'This is not recommended. The `frozen` specification on `dataclass` will take priority.', + category=UserWarning, + stacklevel=2, + ) + else: + frozen_ = config_wrapper.frozen or False + + # Make Pydantic's `Field()` function compatible with stdlib dataclasses. As we'll decorate + # `cls` with the stdlib `@dataclass` decorator first, there are two attributes, `kw_only` and + # `repr` that need to be understood *during* the stdlib creation. We do so in two steps: + + # 1. On the decorated class, wrap `Field()` assignment with `dataclass.field()`, with the + # two attributes set (done in `as_dataclass_field()`) + cls_anns = _typing_extra.safe_get_annotations(cls) + for field_name in cls_anns: + # We should look for assignments in `__dict__` instead, but for now we follow + # the same behavior as stdlib dataclasses (see https://github.com/python/cpython/issues/88609) + field_value = getattr(cls, field_name, None) + if isinstance(field_value, FieldInfo): + setattr(cls, field_name, _pydantic_dataclasses.as_dataclass_field(field_value)) + + # 2. For bases of `cls` that are stdlib dataclasses, we temporarily patch their fields + # (see the docstring of the context manager): + with _pydantic_dataclasses.patch_base_fields(cls): + cls = dataclasses.dataclass( # pyright: ignore[reportCallIssue] + cls, + # the value of init here doesn't affect anything except that it makes it easier to generate a signature + init=True, + repr=repr, + eq=eq, + order=order, + unsafe_hash=unsafe_hash, + frozen=frozen_, + **kwargs, + ) + + if config_wrapper.validate_assignment: + original_setattr = cls.__setattr__ + + @functools.wraps(cls.__setattr__) + def validated_setattr(instance: PydanticDataclass, name: str, value: Any, /) -> None: + if frozen_: + return original_setattr(instance, name, value) # pyright: ignore[reportCallIssue] + inst_cls = type(instance) + attr = getattr(inst_cls, name, None) + + if isinstance(attr, property): + attr.__set__(instance, value) + elif isinstance(attr, functools.cached_property): + instance.__dict__.__setitem__(name, value) + else: + inst_cls.__pydantic_validator__.validate_assignment(instance, name, value) + + cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore + + if slots and not hasattr(cls, '__setstate__'): + # If slots is set, `pickle` (relied on by `copy.copy()`) will use + # `__setattr__()` to reconstruct the dataclass. However, the custom + # `__setattr__()` set above relies on `validate_assignment()`, which + # in turn expects all the field values to be already present on the + # instance, resulting in attribute errors. + # As such, we make use of `object.__setattr__()` instead. + # Note that we do so only if `__setstate__()` isn't already set (this is the + # case if on top of `slots`, `frozen` is used). + + # Taken from `dataclasses._dataclass_get/setstate()`: + def _dataclass_getstate(self: Any) -> list[Any]: + return [getattr(self, f.name) for f in dataclasses.fields(self)] + + def _dataclass_setstate(self: Any, state: list[Any]) -> None: + for field, value in zip(dataclasses.fields(self), state): + object.__setattr__(self, field.name, value) + + cls.__getstate__ = _dataclass_getstate # pyright: ignore[reportAttributeAccessIssue] + cls.__setstate__ = _dataclass_setstate # pyright: ignore[reportAttributeAccessIssue] + + # This is an undocumented attribute to distinguish stdlib/Pydantic dataclasses. + # It should be set as early as possible: + cls.__is_pydantic_dataclass__ = True + cls.__pydantic_decorators__ = decorators # type: ignore + cls.__doc__ = original_doc + # Can be non-existent for dynamically created classes: + firstlineno = getattr(original_cls, '__firstlineno__', None) + cls.__module__ = original_cls.__module__ + if sys.version_info >= (3, 13) and firstlineno is not None: + # As per https://docs.python.org/3/reference/datamodel.html#type.__firstlineno__: + # Setting the `__module__` attribute removes the `__firstlineno__` item from the type’s dictionary. + original_cls.__firstlineno__ = firstlineno + cls.__firstlineno__ = firstlineno + cls.__qualname__ = original_cls.__qualname__ + cls.__pydantic_fields_complete__ = classmethod(_pydantic_fields_complete) + cls.__pydantic_complete__ = False # `complete_dataclass` will set it to `True` if successful. + # TODO `parent_namespace` is currently None, but we could do the same thing as Pydantic models: + # fetch the parent ns using `parent_frame_namespace` (if the dataclass was defined in a function), + # and possibly cache it (see the `__pydantic_parent_namespace__` logic for models). + _pydantic_dataclasses.complete_dataclass(cls, config_wrapper, raise_errors=False) + return cls + + return create_dataclass if _cls is None else create_dataclass(_cls) + + +def _pydantic_fields_complete(cls: type[PydanticDataclass]) -> bool: + """Return whether the fields were successfully collected (i.e. type hints were successfully resolved). + + This is a private helper, not meant to be used outside Pydantic. + """ + return all(field_info._complete for field_info in cls.__pydantic_fields__.values()) + + +__getattr__ = getattr_migration(__name__) + +if sys.version_info < (3, 11): + # Monkeypatch dataclasses.InitVar so that typing doesn't error if it occurs as a type when evaluating type hints + # Starting in 3.11, typing.get_type_hints will not raise an error if the retrieved type hints are not callable. + + def _call_initvar(*args: Any, **kwargs: Any) -> NoReturn: + """This function does nothing but raise an error that is as similar as possible to what you'd get + if you were to try calling `InitVar[int]()` without this monkeypatch. The whole purpose is just + to ensure typing._type_check does not error if the type hint evaluates to `InitVar[]`. + """ + raise TypeError("'InitVar' object is not callable") + + dataclasses.InitVar.__call__ = _call_initvar + + +def rebuild_dataclass( + cls: type[PydanticDataclass], + *, + force: bool = False, + raise_errors: bool = True, + _parent_namespace_depth: int = 2, + _types_namespace: MappingNamespace | None = None, +) -> bool | None: + """Try to rebuild the pydantic-core schema for the dataclass. + + This may be necessary when one of the annotations is a ForwardRef which could not be resolved during + the initial attempt to build the schema, and automatic rebuilding fails. + + This is analogous to `BaseModel.model_rebuild`. + + Args: + cls: The class to rebuild the pydantic-core schema for. + force: Whether to force the rebuilding of the schema, defaults to `False`. + raise_errors: Whether to raise errors, defaults to `True`. + _parent_namespace_depth: The depth level of the parent namespace, defaults to 2. + _types_namespace: The types namespace, defaults to `None`. + + Returns: + Returns `None` if the schema is already "complete" and rebuilding was not required. + If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. + """ + if not force and cls.__pydantic_complete__: + return None + + for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): + if attr in cls.__dict__ and not isinstance(getattr(cls, attr), _mock_val_ser.MockValSer): + # Deleting the validator/serializer is necessary as otherwise they can get reused in + # pydantic-core. Same applies for the core schema that can be reused in schema generation. + delattr(cls, attr) + + cls.__pydantic_complete__ = False + + if _types_namespace is not None: + rebuild_ns = _types_namespace + elif _parent_namespace_depth > 0: + rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} + else: + rebuild_ns = {} + + ns_resolver = _namespace_utils.NsResolver( + parent_namespace=rebuild_ns, + ) + + return _pydantic_dataclasses.complete_dataclass( + cls, + _config.ConfigWrapper(cls.__pydantic_config__, check=False), + raise_errors=raise_errors, + ns_resolver=ns_resolver, + # We could provide a different config instead (with `'defer_build'` set to `True`) + # of this explicit `_force_build` argument, but because config can come from the + # decorator parameter or the `__pydantic_config__` attribute, `complete_dataclass` + # will overwrite `__pydantic_config__` with the provided config above: + _force_build=True, + ) + + +def is_pydantic_dataclass(class_: type[Any], /) -> TypeGuard[type[PydanticDataclass]]: + """Whether a class is a pydantic dataclass. + + Args: + class_: The class. + + Returns: + `True` if the class is a pydantic dataclass, `False` otherwise. + """ + try: + return '__is_pydantic_dataclass__' in class_.__dict__ and dataclasses.is_dataclass(class_) + except AttributeError: + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py new file mode 100644 index 0000000..53d5264 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py @@ -0,0 +1,5 @@ +"""The `datetime_parse` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py new file mode 100644 index 0000000..0d97560 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py @@ -0,0 +1,5 @@ +"""The `decorator` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py new file mode 100644 index 0000000..e6ba195 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py @@ -0,0 +1,256 @@ +"""Old `@validator` and `@root_validator` function validators from V1.""" + +from __future__ import annotations as _annotations + +from functools import partial, partialmethod +from types import FunctionType +from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, Union, overload +from warnings import warn + +from typing_extensions import Protocol, TypeAlias, deprecated + +from .._internal import _decorators, _decorators_v1 +from ..errors import PydanticUserError +from ..warnings import PydanticDeprecatedSince20 + +_ALLOW_REUSE_WARNING_MESSAGE = '`allow_reuse` is deprecated and will be ignored; it should no longer be necessary' + + +if TYPE_CHECKING: + + class _OnlyValueValidatorClsMethod(Protocol): + def __call__(self, __cls: Any, __value: Any) -> Any: ... + + class _V1ValidatorWithValuesClsMethod(Protocol): + def __call__(self, __cls: Any, __value: Any, values: dict[str, Any]) -> Any: ... + + class _V1ValidatorWithValuesKwOnlyClsMethod(Protocol): + def __call__(self, __cls: Any, __value: Any, *, values: dict[str, Any]) -> Any: ... + + class _V1ValidatorWithKwargsClsMethod(Protocol): + def __call__(self, __cls: Any, **kwargs: Any) -> Any: ... + + class _V1ValidatorWithValuesAndKwargsClsMethod(Protocol): + def __call__(self, __cls: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... + + class _V1RootValidatorClsMethod(Protocol): + def __call__( + self, __cls: Any, __values: _decorators_v1.RootValidatorValues + ) -> _decorators_v1.RootValidatorValues: ... + + V1Validator = Union[ + _OnlyValueValidatorClsMethod, + _V1ValidatorWithValuesClsMethod, + _V1ValidatorWithValuesKwOnlyClsMethod, + _V1ValidatorWithKwargsClsMethod, + _V1ValidatorWithValuesAndKwargsClsMethod, + _decorators_v1.V1ValidatorWithValues, + _decorators_v1.V1ValidatorWithValuesKwOnly, + _decorators_v1.V1ValidatorWithKwargs, + _decorators_v1.V1ValidatorWithValuesAndKwargs, + ] + + V1RootValidator = Union[ + _V1RootValidatorClsMethod, + _decorators_v1.V1RootValidatorFunction, + ] + + _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] + + # Allow both a V1 (assumed pre=False) or V2 (assumed mode='after') validator + # We lie to type checkers and say we return the same thing we get + # but in reality we return a proxy object that _mostly_ behaves like the wrapped thing + _V1ValidatorType = TypeVar('_V1ValidatorType', V1Validator, _PartialClsOrStaticMethod) + _V1RootValidatorFunctionType = TypeVar( + '_V1RootValidatorFunctionType', + _decorators_v1.V1RootValidatorFunction, + _V1RootValidatorClsMethod, + _PartialClsOrStaticMethod, + ) +else: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + + +@deprecated( + 'Pydantic V1 style `@validator` validators are deprecated.' + ' You should migrate to Pydantic V2 style `@field_validator` validators,' + ' see the migration guide for more details', + category=None, +) +def validator( + __field: str, + *fields: str, + pre: bool = False, + each_item: bool = False, + always: bool = False, + check_fields: bool | None = None, + allow_reuse: bool = False, +) -> Callable[[_V1ValidatorType], _V1ValidatorType]: + """Decorate methods on the class indicating that they should be used to validate fields. + + Args: + __field (str): The first field the validator should be called on; this is separate + from `fields` to ensure an error is raised if you don't pass at least one. + *fields (str): Additional field(s) the validator should be called on. + pre (bool, optional): Whether this validator should be called before the standard + validators (else after). Defaults to False. + each_item (bool, optional): For complex objects (sets, lists etc.) whether to validate + individual elements rather than the whole object. Defaults to False. + always (bool, optional): Whether this method and other validators should be called even if + the value is missing. Defaults to False. + check_fields (bool | None, optional): Whether to check that the fields actually exist on the model. + Defaults to None. + allow_reuse (bool, optional): Whether to track and raise an error if another validator refers to + the decorated function. Defaults to False. + + Returns: + Callable: A decorator that can be used to decorate a + function to be used as a validator. + """ + warn( + 'Pydantic V1 style `@validator` validators are deprecated.' + ' You should migrate to Pydantic V2 style `@field_validator` validators,' + ' see the migration guide for more details', + DeprecationWarning, + stacklevel=2, + ) + + if allow_reuse is True: # pragma: no cover + warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning, stacklevel=2) + fields = __field, *fields + if isinstance(fields[0], FunctionType): + raise PydanticUserError( + '`@validator` should be used with fields and keyword arguments, not bare. ' + "E.g. usage should be `@validator('', ...)`", + code='decorator-missing-arguments', + ) + elif not all(isinstance(field, str) for field in fields): + raise PydanticUserError( + '`@validator` fields should be passed as separate string args. ' + "E.g. usage should be `@validator('', '', ...)`", + code='decorator-invalid-fields', + ) + + mode: Literal['before', 'after'] = 'before' if pre is True else 'after' + + def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]: + if _decorators.is_instance_method_from_sig(f): + raise PydanticUserError( + '`@validator` cannot be applied to instance methods', code='validator-instance-method' + ) + # auto apply the @classmethod decorator + f = _decorators.ensure_classmethod_based_on_signature(f) + wrap = _decorators_v1.make_generic_v1_field_validator + validator_wrapper_info = _decorators.ValidatorDecoratorInfo( + fields=fields, + mode=mode, + each_item=each_item, + always=always, + check_fields=check_fields, + ) + return _decorators.PydanticDescriptorProxy(f, validator_wrapper_info, shim=wrap) + + return dec # type: ignore[return-value] + + +@overload +def root_validator( + *, + # if you don't specify `pre` the default is `pre=False` + # which means you need to specify `skip_on_failure=True` + skip_on_failure: Literal[True], + allow_reuse: bool = ..., +) -> Callable[ + [_V1RootValidatorFunctionType], + _V1RootValidatorFunctionType, +]: ... + + +@overload +def root_validator( + *, + # if you specify `pre=True` then you don't need to specify + # `skip_on_failure`, in fact it is not allowed as an argument! + pre: Literal[True], + allow_reuse: bool = ..., +) -> Callable[ + [_V1RootValidatorFunctionType], + _V1RootValidatorFunctionType, +]: ... + + +@overload +def root_validator( + *, + # if you explicitly specify `pre=False` then you + # MUST specify `skip_on_failure=True` + pre: Literal[False], + skip_on_failure: Literal[True], + allow_reuse: bool = ..., +) -> Callable[ + [_V1RootValidatorFunctionType], + _V1RootValidatorFunctionType, +]: ... + + +@deprecated( + 'Pydantic V1 style `@root_validator` validators are deprecated.' + ' You should migrate to Pydantic V2 style `@model_validator` validators,' + ' see the migration guide for more details', + category=None, +) +def root_validator( + *__args, + pre: bool = False, + skip_on_failure: bool = False, + allow_reuse: bool = False, +) -> Any: + """Decorate methods on a model indicating that they should be used to validate (and perhaps + modify) data either before or after standard model parsing/validation is performed. + + Args: + pre (bool, optional): Whether this validator should be called before the standard + validators (else after). Defaults to False. + skip_on_failure (bool, optional): Whether to stop validation and return as soon as a + failure is encountered. Defaults to False. + allow_reuse (bool, optional): Whether to track and raise an error if another validator + refers to the decorated function. Defaults to False. + + Returns: + Any: A decorator that can be used to decorate a function to be used as a root_validator. + """ + warn( + 'Pydantic V1 style `@root_validator` validators are deprecated.' + ' You should migrate to Pydantic V2 style `@model_validator` validators,' + ' see the migration guide for more details', + DeprecationWarning, + stacklevel=2, + ) + + if __args: + # Ensure a nice error is raised if someone attempts to use the bare decorator + return root_validator()(*__args) # type: ignore + + if allow_reuse is True: # pragma: no cover + warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning, stacklevel=2) + mode: Literal['before', 'after'] = 'before' if pre is True else 'after' + if pre is False and skip_on_failure is not True: + raise PydanticUserError( + 'If you use `@root_validator` with pre=False (the default) you MUST specify `skip_on_failure=True`.' + ' Note that `@root_validator` is deprecated and should be replaced with `@model_validator`.', + code='root-validator-pre-skip', + ) + + wrap = partial(_decorators_v1.make_v1_generic_root_validator, pre=pre) + + def dec(f: Callable[..., Any] | classmethod[Any, Any, Any] | staticmethod[Any, Any]) -> Any: + if _decorators.is_instance_method_from_sig(f): + raise TypeError('`@root_validator` cannot be applied to instance methods') + # auto apply the @classmethod decorator + res = _decorators.ensure_classmethod_based_on_signature(f) + dec_info = _decorators.RootValidatorDecoratorInfo(mode=mode) + return _decorators.PydanticDescriptorProxy(res, dec_info, shim=wrap) + + return dec diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py new file mode 100644 index 0000000..bd4692a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py @@ -0,0 +1,72 @@ +from __future__ import annotations as _annotations + +import warnings +from typing import TYPE_CHECKING, Any, Literal + +from typing_extensions import deprecated + +from .._internal import _config +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + +__all__ = 'BaseConfig', 'Extra' + + +class _ConfigMetaclass(type): + def __getattr__(self, item: str) -> Any: + try: + obj = _config.config_defaults[item] + warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) + return obj + except KeyError as exc: + raise AttributeError(f"type object '{self.__name__}' has no attribute {exc}") from exc + + +@deprecated('BaseConfig is deprecated. Use the `pydantic.ConfigDict` instead.', category=PydanticDeprecatedSince20) +class BaseConfig(metaclass=_ConfigMetaclass): + """This class is only retained for backwards compatibility. + + !!! Warning "Deprecated" + BaseConfig is deprecated. Use the [`pydantic.ConfigDict`][pydantic.ConfigDict] instead. + """ + + def __getattr__(self, item: str) -> Any: + try: + obj = super().__getattribute__(item) + warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) + return obj + except AttributeError as exc: + try: + return getattr(type(self), item) + except AttributeError: + # re-raising changes the displayed text to reflect that `self` is not a type + raise AttributeError(str(exc)) from exc + + def __init_subclass__(cls, **kwargs: Any) -> None: + warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) + return super().__init_subclass__(**kwargs) + + +class _ExtraMeta(type): + def __getattribute__(self, __name: str) -> Any: + # The @deprecated decorator accesses other attributes, so we only emit a warning for the expected ones + if __name in {'allow', 'ignore', 'forbid'}: + warnings.warn( + "`pydantic.config.Extra` is deprecated, use literal values instead (e.g. `extra='allow'`)", + DeprecationWarning, + stacklevel=2, + ) + return super().__getattribute__(__name) + + +@deprecated( + "Extra is deprecated. Use literal values instead (e.g. `extra='allow'`)", category=PydanticDeprecatedSince20 +) +class Extra(metaclass=_ExtraMeta): + allow: Literal['allow'] = 'allow' + ignore: Literal['ignore'] = 'ignore' + forbid: Literal['forbid'] = 'forbid' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py new file mode 100644 index 0000000..0170dc0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py @@ -0,0 +1,224 @@ +from __future__ import annotations as _annotations + +import typing +from copy import deepcopy +from enum import Enum +from typing import Any + +import typing_extensions + +from .._internal import ( + _model_construction, + _typing_extra, + _utils, +) + +if typing.TYPE_CHECKING: + from .. import BaseModel + from .._internal._utils import AbstractSetIntStr, MappingIntStrAny + + AnyClassMethod = classmethod[Any, Any, Any] + TupleGenerator = typing.Generator[tuple[str, Any], None, None] + Model = typing.TypeVar('Model', bound='BaseModel') + # should be `set[int] | set[str] | dict[int, IncEx] | dict[str, IncEx] | None`, but mypy can't cope + IncEx: typing_extensions.TypeAlias = 'set[int] | set[str] | dict[int, Any] | dict[str, Any] | None' + +_object_setattr = _model_construction.object_setattr + + +def _iter( + self: BaseModel, + to_dict: bool = False, + by_alias: bool = False, + include: AbstractSetIntStr | MappingIntStrAny | None = None, + exclude: AbstractSetIntStr | MappingIntStrAny | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, +) -> TupleGenerator: + # Merge field set excludes with explicit exclude parameter with explicit overriding field set options. + # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case. + if exclude is not None: + exclude = _utils.ValueItems.merge( + {k: v.exclude for k, v in self.__pydantic_fields__.items() if v.exclude is not None}, exclude + ) + + if include is not None: + include = _utils.ValueItems.merge(dict.fromkeys(self.__pydantic_fields__, True), include, intersect=True) + + allowed_keys = _calculate_keys(self, include=include, exclude=exclude, exclude_unset=exclude_unset) # type: ignore + if allowed_keys is None and not (to_dict or by_alias or exclude_unset or exclude_defaults or exclude_none): + # huge boost for plain _iter() + yield from self.__dict__.items() + if self.__pydantic_extra__: + yield from self.__pydantic_extra__.items() + return + + value_exclude = _utils.ValueItems(self, exclude) if exclude is not None else None + value_include = _utils.ValueItems(self, include) if include is not None else None + + if self.__pydantic_extra__ is None: + items = self.__dict__.items() + else: + items = list(self.__dict__.items()) + list(self.__pydantic_extra__.items()) + + for field_key, v in items: + if (allowed_keys is not None and field_key not in allowed_keys) or (exclude_none and v is None): + continue + + if exclude_defaults: + try: + field = self.__pydantic_fields__[field_key] + except KeyError: + pass + else: + if not field.is_required() and field.default == v: + continue + + if by_alias and field_key in self.__pydantic_fields__: + dict_key = self.__pydantic_fields__[field_key].alias or field_key + else: + dict_key = field_key + + if to_dict or value_include or value_exclude: + v = _get_value( + type(self), + v, + to_dict=to_dict, + by_alias=by_alias, + include=value_include and value_include.for_element(field_key), + exclude=value_exclude and value_exclude.for_element(field_key), + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + yield dict_key, v + + +def _copy_and_set_values( + self: Model, + values: dict[str, Any], + fields_set: set[str], + extra: dict[str, Any] | None = None, + private: dict[str, Any] | None = None, + *, + deep: bool, # UP006 +) -> Model: + if deep: + # chances of having empty dict here are quite low for using smart_deepcopy + values = deepcopy(values) + extra = deepcopy(extra) + private = deepcopy(private) + + cls = self.__class__ + m = cls.__new__(cls) + _object_setattr(m, '__dict__', values) + _object_setattr(m, '__pydantic_extra__', extra) + _object_setattr(m, '__pydantic_fields_set__', fields_set) + _object_setattr(m, '__pydantic_private__', private) + + return m + + +@typing.no_type_check +def _get_value( + cls: type[BaseModel], + v: Any, + to_dict: bool, + by_alias: bool, + include: AbstractSetIntStr | MappingIntStrAny | None, + exclude: AbstractSetIntStr | MappingIntStrAny | None, + exclude_unset: bool, + exclude_defaults: bool, + exclude_none: bool, +) -> Any: + from .. import BaseModel + + if isinstance(v, BaseModel): + if to_dict: + return v.model_dump( + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=include, # type: ignore + exclude=exclude, # type: ignore + exclude_none=exclude_none, + ) + else: + return v.copy(include=include, exclude=exclude) + + value_exclude = _utils.ValueItems(v, exclude) if exclude else None + value_include = _utils.ValueItems(v, include) if include else None + + if isinstance(v, dict): + return { + k_: _get_value( + cls, + v_, + to_dict=to_dict, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=value_include and value_include.for_element(k_), + exclude=value_exclude and value_exclude.for_element(k_), + exclude_none=exclude_none, + ) + for k_, v_ in v.items() + if (not value_exclude or not value_exclude.is_excluded(k_)) + and (not value_include or value_include.is_included(k_)) + } + + elif _utils.sequence_like(v): + seq_args = ( + _get_value( + cls, + v_, + to_dict=to_dict, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=value_include and value_include.for_element(i), + exclude=value_exclude and value_exclude.for_element(i), + exclude_none=exclude_none, + ) + for i, v_ in enumerate(v) + if (not value_exclude or not value_exclude.is_excluded(i)) + and (not value_include or value_include.is_included(i)) + ) + + return v.__class__(*seq_args) if _typing_extra.is_namedtuple(v.__class__) else v.__class__(seq_args) + + elif isinstance(v, Enum) and getattr(cls.model_config, 'use_enum_values', False): + return v.value + + else: + return v + + +def _calculate_keys( + self: BaseModel, + include: MappingIntStrAny | None, + exclude: MappingIntStrAny | None, + exclude_unset: bool, + update: dict[str, Any] | None = None, # noqa UP006 +) -> typing.AbstractSet[str] | None: + if include is None and exclude is None and exclude_unset is False: + return None + + keys: typing.AbstractSet[str] + if exclude_unset: + keys = self.__pydantic_fields_set__.copy() + else: + keys = set(self.__dict__.keys()) + keys = keys | (self.__pydantic_extra__ or {}).keys() + + if include is not None: + keys &= include.keys() + + if update: + keys -= update.keys() + + if exclude: + keys -= {k for k, v in exclude.items() if _utils.ValueItems.is_true(v)} + + return keys diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py new file mode 100644 index 0000000..e73ad20 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py @@ -0,0 +1,284 @@ +import warnings +from collections.abc import Mapping +from functools import wraps +from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union, overload + +from typing_extensions import deprecated + +from .._internal import _config, _typing_extra +from ..alias_generators import to_pascal +from ..errors import PydanticUserError +from ..functional_validators import field_validator +from ..main import BaseModel, create_model +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + +__all__ = ('validate_arguments',) + +if TYPE_CHECKING: + AnyCallable = Callable[..., Any] + + AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable) + ConfigType = Union[None, type[Any], dict[str, Any]] + + +@overload +def validate_arguments( + func: None = None, *, config: 'ConfigType' = None +) -> Callable[['AnyCallableT'], 'AnyCallableT']: ... + + +@overload +def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT': ... + + +@deprecated( + 'The `validate_arguments` method is deprecated; use `validate_call` instead.', + category=None, +) +def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'ConfigType' = None) -> Any: + """Decorator to validate the arguments passed to a function.""" + warnings.warn( + 'The `validate_arguments` method is deprecated; use `validate_call` instead.', + PydanticDeprecatedSince20, + stacklevel=2, + ) + + def validate(_func: 'AnyCallable') -> 'AnyCallable': + vd = ValidatedFunction(_func, config) + + @wraps(_func) + def wrapper_function(*args: Any, **kwargs: Any) -> Any: + return vd.call(*args, **kwargs) + + wrapper_function.vd = vd # type: ignore + wrapper_function.validate = vd.init_model_instance # type: ignore + wrapper_function.raw_function = vd.raw_function # type: ignore + wrapper_function.model = vd.model # type: ignore + return wrapper_function + + if func: + return validate(func) + else: + return validate + + +ALT_V_ARGS = 'v__args' +ALT_V_KWARGS = 'v__kwargs' +V_POSITIONAL_ONLY_NAME = 'v__positional_only' +V_DUPLICATE_KWARGS = 'v__duplicate_kwargs' + + +class ValidatedFunction: + def __init__(self, function: 'AnyCallable', config: 'ConfigType'): + from inspect import Parameter, signature + + parameters: Mapping[str, Parameter] = signature(function).parameters + + if parameters.keys() & {ALT_V_ARGS, ALT_V_KWARGS, V_POSITIONAL_ONLY_NAME, V_DUPLICATE_KWARGS}: + raise PydanticUserError( + f'"{ALT_V_ARGS}", "{ALT_V_KWARGS}", "{V_POSITIONAL_ONLY_NAME}" and "{V_DUPLICATE_KWARGS}" ' + f'are not permitted as argument names when using the "{validate_arguments.__name__}" decorator', + code=None, + ) + + self.raw_function = function + self.arg_mapping: dict[int, str] = {} + self.positional_only_args: set[str] = set() + self.v_args_name = 'args' + self.v_kwargs_name = 'kwargs' + + type_hints = _typing_extra.get_type_hints(function, include_extras=True) + takes_args = False + takes_kwargs = False + fields: dict[str, tuple[Any, Any]] = {} + for i, (name, p) in enumerate(parameters.items()): + if p.annotation is p.empty: + annotation = Any + else: + annotation = type_hints[name] + + default = ... if p.default is p.empty else p.default + if p.kind == Parameter.POSITIONAL_ONLY: + self.arg_mapping[i] = name + fields[name] = annotation, default + fields[V_POSITIONAL_ONLY_NAME] = list[str], None + self.positional_only_args.add(name) + elif p.kind == Parameter.POSITIONAL_OR_KEYWORD: + self.arg_mapping[i] = name + fields[name] = annotation, default + fields[V_DUPLICATE_KWARGS] = list[str], None + elif p.kind == Parameter.KEYWORD_ONLY: + fields[name] = annotation, default + elif p.kind == Parameter.VAR_POSITIONAL: + self.v_args_name = name + fields[name] = tuple[annotation, ...], None + takes_args = True + else: + assert p.kind == Parameter.VAR_KEYWORD, p.kind + self.v_kwargs_name = name + fields[name] = dict[str, annotation], None + takes_kwargs = True + + # these checks avoid a clash between "args" and a field with that name + if not takes_args and self.v_args_name in fields: + self.v_args_name = ALT_V_ARGS + + # same with "kwargs" + if not takes_kwargs and self.v_kwargs_name in fields: + self.v_kwargs_name = ALT_V_KWARGS + + if not takes_args: + # we add the field so validation below can raise the correct exception + fields[self.v_args_name] = list[Any], None + + if not takes_kwargs: + # same with kwargs + fields[self.v_kwargs_name] = dict[Any, Any], None + + self.create_model(fields, takes_args, takes_kwargs, config) + + def init_model_instance(self, *args: Any, **kwargs: Any) -> BaseModel: + values = self.build_values(args, kwargs) + return self.model(**values) + + def call(self, *args: Any, **kwargs: Any) -> Any: + m = self.init_model_instance(*args, **kwargs) + return self.execute(m) + + def build_values(self, args: tuple[Any, ...], kwargs: dict[str, Any]) -> dict[str, Any]: + values: dict[str, Any] = {} + if args: + arg_iter = enumerate(args) + while True: + try: + i, a = next(arg_iter) + except StopIteration: + break + arg_name = self.arg_mapping.get(i) + if arg_name is not None: + values[arg_name] = a + else: + values[self.v_args_name] = [a] + [a for _, a in arg_iter] + break + + var_kwargs: dict[str, Any] = {} + wrong_positional_args = [] + duplicate_kwargs = [] + fields_alias = [ + field.alias + for name, field in self.model.__pydantic_fields__.items() + if name not in (self.v_args_name, self.v_kwargs_name) + ] + non_var_fields = set(self.model.__pydantic_fields__) - {self.v_args_name, self.v_kwargs_name} + for k, v in kwargs.items(): + if k in non_var_fields or k in fields_alias: + if k in self.positional_only_args: + wrong_positional_args.append(k) + if k in values: + duplicate_kwargs.append(k) + values[k] = v + else: + var_kwargs[k] = v + + if var_kwargs: + values[self.v_kwargs_name] = var_kwargs + if wrong_positional_args: + values[V_POSITIONAL_ONLY_NAME] = wrong_positional_args + if duplicate_kwargs: + values[V_DUPLICATE_KWARGS] = duplicate_kwargs + return values + + def execute(self, m: BaseModel) -> Any: + d = { + k: v + for k, v in m.__dict__.items() + if k in m.__pydantic_fields_set__ or m.__pydantic_fields__[k].default_factory + } + var_kwargs = d.pop(self.v_kwargs_name, {}) + + if self.v_args_name in d: + args_: list[Any] = [] + in_kwargs = False + kwargs = {} + for name, value in d.items(): + if in_kwargs: + kwargs[name] = value + elif name == self.v_args_name: + args_ += value + in_kwargs = True + else: + args_.append(value) + return self.raw_function(*args_, **kwargs, **var_kwargs) + elif self.positional_only_args: + args_ = [] + kwargs = {} + for name, value in d.items(): + if name in self.positional_only_args: + args_.append(value) + else: + kwargs[name] = value + return self.raw_function(*args_, **kwargs, **var_kwargs) + else: + return self.raw_function(**d, **var_kwargs) + + def create_model(self, fields: dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None: + pos_args = len(self.arg_mapping) + + config_wrapper = _config.ConfigWrapper(config) + + if config_wrapper.alias_generator: + raise PydanticUserError( + 'Setting the "alias_generator" property on custom Config for ' + '@validate_arguments is not yet supported, please remove.', + code=None, + ) + if config_wrapper.extra is None: + config_wrapper.config_dict['extra'] = 'forbid' + + class DecoratorBaseModel(BaseModel): + @field_validator(self.v_args_name, check_fields=False) + @classmethod + def check_args(cls, v: Optional[list[Any]]) -> Optional[list[Any]]: + if takes_args or v is None: + return v + + raise TypeError(f'{pos_args} positional arguments expected but {pos_args + len(v)} given') + + @field_validator(self.v_kwargs_name, check_fields=False) + @classmethod + def check_kwargs(cls, v: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]: + if takes_kwargs or v is None: + return v + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v.keys())) + raise TypeError(f'unexpected keyword argument{plural}: {keys}') + + @field_validator(V_POSITIONAL_ONLY_NAME, check_fields=False) + @classmethod + def check_positional_only(cls, v: Optional[list[str]]) -> None: + if v is None: + return + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v)) + raise TypeError(f'positional-only argument{plural} passed as keyword argument{plural}: {keys}') + + @field_validator(V_DUPLICATE_KWARGS, check_fields=False) + @classmethod + def check_duplicate_kwargs(cls, v: Optional[list[str]]) -> None: + if v is None: + return + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v)) + raise TypeError(f'multiple values for argument{plural}: {keys}') + + model_config = config_wrapper.config_dict + + self.model = create_model(to_pascal(self.raw_function.__name__), __base__=DecoratorBaseModel, **fields) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py new file mode 100644 index 0000000..1e216a7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py @@ -0,0 +1,141 @@ +import datetime +import warnings +from collections import deque +from decimal import Decimal +from enum import Enum +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from pathlib import Path +from re import Pattern +from types import GeneratorType +from typing import TYPE_CHECKING, Any, Callable, Union +from uuid import UUID + +from typing_extensions import deprecated + +from .._internal._import_utils import import_cached_base_model +from ..color import Color +from ..networks import NameEmail +from ..types import SecretBytes, SecretStr +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + +__all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat' + + +def isoformat(o: Union[datetime.date, datetime.time]) -> str: + return o.isoformat() + + +def decimal_encoder(dec_value: Decimal) -> Union[int, float]: + """Encodes a Decimal as int of there's no exponent, otherwise float. + + This is useful when we use ConstrainedDecimal to represent Numeric(x,0) + where a integer (but not int typed) is used. Encoding this as a float + results in failed round-tripping between encode and parse. + Our Id type is a prime example of this. + + >>> decimal_encoder(Decimal("1.0")) + 1.0 + + >>> decimal_encoder(Decimal("1")) + 1 + """ + exponent = dec_value.as_tuple().exponent + if isinstance(exponent, int) and exponent >= 0: + return int(dec_value) + else: + return float(dec_value) + + +ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { + bytes: lambda o: o.decode(), + Color: str, + datetime.date: isoformat, + datetime.datetime: isoformat, + datetime.time: isoformat, + datetime.timedelta: lambda td: td.total_seconds(), + Decimal: decimal_encoder, + Enum: lambda o: o.value, + frozenset: list, + deque: list, + GeneratorType: list, + IPv4Address: str, + IPv4Interface: str, + IPv4Network: str, + IPv6Address: str, + IPv6Interface: str, + IPv6Network: str, + NameEmail: str, + Path: str, + Pattern: lambda o: o.pattern, + SecretBytes: str, + SecretStr: str, + set: list, + UUID: str, +} + + +@deprecated( + '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.', + category=None, +) +def pydantic_encoder(obj: Any) -> Any: + warnings.warn( + '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from dataclasses import asdict, is_dataclass + + BaseModel = import_cached_base_model() + + if isinstance(obj, BaseModel): + return obj.model_dump() + elif is_dataclass(obj): + return asdict(obj) # type: ignore + + # Check the class type and its superclasses for a matching encoder + for base in obj.__class__.__mro__[:-1]: + try: + encoder = ENCODERS_BY_TYPE[base] + except KeyError: + continue + return encoder(obj) + else: # We have exited the for loop without finding a suitable encoder + raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable") + + +# TODO: Add a suggested migration path once there is a way to use custom encoders +@deprecated( + '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.', + category=None, +) +def custom_pydantic_encoder(type_encoders: dict[Any, Callable[[type[Any]], Any]], obj: Any) -> Any: + warnings.warn( + '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + # Check the class type and its superclasses for a matching encoder + for base in obj.__class__.__mro__[:-1]: + try: + encoder = type_encoders[base] + except KeyError: + continue + + return encoder(obj) + else: # We have exited the for loop without finding a suitable encoder + return pydantic_encoder(obj) + + +@deprecated('`timedelta_isoformat` is deprecated.', category=None) +def timedelta_isoformat(td: datetime.timedelta) -> str: + """ISO 8601 encoding for Python timedelta object.""" + warnings.warn('`timedelta_isoformat` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + minutes, seconds = divmod(td.seconds, 60) + hours, minutes = divmod(minutes, 60) + return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py new file mode 100644 index 0000000..2a92e62 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import json +import pickle +import warnings +from enum import Enum +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable + +from typing_extensions import deprecated + +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + + +class Protocol(str, Enum): + json = 'json' + pickle = 'pickle' + + +@deprecated('`load_str_bytes` is deprecated.', category=None) +def load_str_bytes( + b: str | bytes, + *, + content_type: str | None = None, + encoding: str = 'utf8', + proto: Protocol | None = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, +) -> Any: + warnings.warn('`load_str_bytes` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + if proto is None and content_type: + if content_type.endswith(('json', 'javascript')): + pass + elif allow_pickle and content_type.endswith('pickle'): + proto = Protocol.pickle + else: + raise TypeError(f'Unknown content-type: {content_type}') + + proto = proto or Protocol.json + + if proto == Protocol.json: + if isinstance(b, bytes): + b = b.decode(encoding) + return json_loads(b) # type: ignore + elif proto == Protocol.pickle: + if not allow_pickle: + raise RuntimeError('Trying to decode with pickle with allow_pickle=False') + bb = b if isinstance(b, bytes) else b.encode() # type: ignore + return pickle.loads(bb) + else: + raise TypeError(f'Unknown protocol: {proto}') + + +@deprecated('`load_file` is deprecated.', category=None) +def load_file( + path: str | Path, + *, + content_type: str | None = None, + encoding: str = 'utf8', + proto: Protocol | None = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, +) -> Any: + warnings.warn('`load_file` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + path = Path(path) + b = path.read_bytes() + if content_type is None: + if path.suffix in ('.js', '.json'): + proto = Protocol.json + elif path.suffix == '.pkl': + proto = Protocol.pickle + + return load_str_bytes( + b, proto=proto, content_type=content_type, encoding=encoding, allow_pickle=allow_pickle, json_loads=json_loads + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py new file mode 100644 index 0000000..5ad7fae --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import json +import warnings +from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union + +from typing_extensions import deprecated + +from ..json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema +from ..type_adapter import TypeAdapter +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 + +__all__ = 'parse_obj_as', 'schema_of', 'schema_json_of' + +NameFactory = Union[str, Callable[[type[Any]], str]] + + +T = TypeVar('T') + + +@deprecated( + '`parse_obj_as` is deprecated. Use `pydantic.TypeAdapter.validate_python` instead.', + category=None, +) +def parse_obj_as(type_: type[T], obj: Any, type_name: NameFactory | None = None) -> T: + warnings.warn( + '`parse_obj_as` is deprecated. Use `pydantic.TypeAdapter.validate_python` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + if type_name is not None: # pragma: no cover + warnings.warn( + 'The type_name parameter is deprecated. parse_obj_as no longer creates temporary models', + DeprecationWarning, + stacklevel=2, + ) + return TypeAdapter(type_).validate_python(obj) + + +@deprecated( + '`schema_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', + category=None, +) +def schema_of( + type_: Any, + *, + title: NameFactory | None = None, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, +) -> dict[str, Any]: + """Generate a JSON schema (as dict) for the passed model or dynamically generated one.""" + warnings.warn( + '`schema_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + res = TypeAdapter(type_).json_schema( + by_alias=by_alias, + schema_generator=schema_generator, + ref_template=ref_template, + ) + if title is not None: + if isinstance(title, str): + res['title'] = title + else: + warnings.warn( + 'Passing a callable for the `title` parameter is deprecated and no longer supported', + DeprecationWarning, + stacklevel=2, + ) + res['title'] = title(type_) + return res + + +@deprecated( + '`schema_json_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', + category=None, +) +def schema_json_of( + type_: Any, + *, + title: NameFactory | None = None, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, + **dumps_kwargs: Any, +) -> str: + """Generate a JSON schema (as JSON) for the passed model or dynamically generated one.""" + warnings.warn( + '`schema_json_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return json.dumps( + schema_of(type_, title=title, by_alias=by_alias, ref_template=ref_template, schema_generator=schema_generator), + **dumps_kwargs, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py new file mode 100644 index 0000000..cd0b04e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py @@ -0,0 +1,5 @@ +"""The `env_settings` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py new file mode 100644 index 0000000..2985419 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py @@ -0,0 +1,5 @@ +"""The `error_wrappers` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py new file mode 100644 index 0000000..33851bd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/errors.py @@ -0,0 +1,189 @@ +"""Pydantic-specific errors.""" + +from __future__ import annotations as _annotations + +import re +from typing import Any, ClassVar, Literal + +from typing_extensions import Self +from typing_inspection.introspection import Qualifier + +from pydantic._internal import _repr + +from ._migration import getattr_migration +from .version import version_short + +__all__ = ( + 'PydanticUserError', + 'PydanticUndefinedAnnotation', + 'PydanticImportError', + 'PydanticSchemaGenerationError', + 'PydanticInvalidForJsonSchema', + 'PydanticForbiddenQualifier', + 'PydanticErrorCodes', +) + +# We use this URL to allow for future flexibility about how we host the docs, while allowing for Pydantic +# code in the while with "old" URLs to still work. +# 'u' refers to "user errors" - e.g. errors caused by developers using pydantic, as opposed to validation errors. +DEV_ERROR_DOCS_URL = f'https://errors.pydantic.dev/{version_short()}/u/' +PydanticErrorCodes = Literal[ + 'class-not-fully-defined', + 'custom-json-schema', + 'decorator-invalid-fields', + 'decorator-missing-arguments', + 'decorator-missing-field', + 'discriminator-no-field', + 'discriminator-alias-type', + 'discriminator-needs-literal', + 'discriminator-alias', + 'discriminator-validator', + 'callable-discriminator-no-tag', + 'typed-dict-version', + 'model-field-overridden', + 'model-field-missing-annotation', + 'config-both', + 'removed-kwargs', + 'circular-reference-schema', + 'invalid-for-json-schema', + 'json-schema-already-used', + 'base-model-instantiated', + 'undefined-annotation', + 'schema-for-unknown-type', + 'import-error', + 'create-model-field-definitions', + 'validator-instance-method', + 'validator-input-type', + 'root-validator-pre-skip', + 'model-serializer-instance-method', + 'validator-field-config-info', + 'validator-v1-signature', + 'validator-signature', + 'field-serializer-signature', + 'model-serializer-signature', + 'multiple-field-serializers', + 'invalid-annotated-type', + 'type-adapter-config-unused', + 'root-model-extra', + 'unevaluable-type-annotation', + 'dataclass-init-false-extra-allow', + 'clashing-init-and-init-var', + 'model-config-invalid-field-name', + 'with-config-on-model', + 'dataclass-on-model', + 'validate-call-type', + 'unpack-typed-dict', + 'overlapping-unpack-typed-dict', + 'invalid-self-type', + 'validate-by-alias-and-name-false', +] + + +class PydanticErrorMixin: + """A mixin class for common functionality shared by all Pydantic-specific errors. + + Attributes: + message: A message describing the error. + code: An optional error code from PydanticErrorCodes enum. + """ + + def __init__(self, message: str, *, code: PydanticErrorCodes | None) -> None: + self.message = message + self.code = code + + def __str__(self) -> str: + if self.code is None: + return self.message + else: + return f'{self.message}\n\nFor further information visit {DEV_ERROR_DOCS_URL}{self.code}' + + +class PydanticUserError(PydanticErrorMixin, RuntimeError): + """An error raised due to incorrect use of Pydantic.""" + + +class PydanticUndefinedAnnotation(PydanticErrorMixin, NameError): + """A subclass of `NameError` raised when handling undefined annotations during `CoreSchema` generation. + + Attributes: + name: Name of the error. + message: Description of the error. + """ + + def __init__(self, name: str, message: str) -> None: + self.name = name + super().__init__(message=message, code='undefined-annotation') + + @classmethod + def from_name_error(cls, name_error: NameError) -> Self: + """Convert a `NameError` to a `PydanticUndefinedAnnotation` error. + + Args: + name_error: `NameError` to be converted. + + Returns: + Converted `PydanticUndefinedAnnotation` error. + """ + try: + name = name_error.name # type: ignore # python > 3.10 + except AttributeError: + name = re.search(r".*'(.+?)'", str(name_error)).group(1) # type: ignore[union-attr] + return cls(name=name, message=str(name_error)) + + +class PydanticImportError(PydanticErrorMixin, ImportError): + """An error raised when an import fails due to module changes between V1 and V2. + + Attributes: + message: Description of the error. + """ + + def __init__(self, message: str) -> None: + super().__init__(message, code='import-error') + + +class PydanticSchemaGenerationError(PydanticUserError): + """An error raised during failures to generate a `CoreSchema` for some type. + + Attributes: + message: Description of the error. + """ + + def __init__(self, message: str) -> None: + super().__init__(message, code='schema-for-unknown-type') + + +class PydanticInvalidForJsonSchema(PydanticUserError): + """An error raised during failures to generate a JSON schema for some `CoreSchema`. + + Attributes: + message: Description of the error. + """ + + def __init__(self, message: str) -> None: + super().__init__(message, code='invalid-for-json-schema') + + +class PydanticForbiddenQualifier(PydanticUserError): + """An error raised if a forbidden type qualifier is found in a type annotation.""" + + _qualifier_repr_map: ClassVar[dict[Qualifier, str]] = { + 'required': 'typing.Required', + 'not_required': 'typing.NotRequired', + 'read_only': 'typing.ReadOnly', + 'class_var': 'typing.ClassVar', + 'init_var': 'dataclasses.InitVar', + 'final': 'typing.Final', + } + + def __init__(self, qualifier: Qualifier, annotation: Any) -> None: + super().__init__( + message=( + f'The annotation {_repr.display_as_type(annotation)!r} contains the {self._qualifier_repr_map[qualifier]!r} ' + f'type qualifier, which is invalid in the context it is defined.' + ), + code=None, + ) + + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py new file mode 100644 index 0000000..5b5add1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py @@ -0,0 +1 @@ +"""The "experimental" module of pydantic contains potential new features that are subject to change.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py new file mode 100644 index 0000000..af4a8f3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py @@ -0,0 +1,44 @@ +"""Experimental module exposing a function to generate a core schema that validates callable arguments.""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import Any, Literal + +from pydantic_core import CoreSchema + +from pydantic import ConfigDict +from pydantic._internal import _config, _generate_schema, _namespace_utils + + +def generate_arguments_schema( + func: Callable[..., Any], + schema_type: Literal['arguments', 'arguments-v3'] = 'arguments-v3', + parameters_callback: Callable[[int, str, Any], Literal['skip'] | None] | None = None, + config: ConfigDict | None = None, +) -> CoreSchema: + """Generate the schema for the arguments of a function. + + Args: + func: The function to generate the schema for. + schema_type: The type of schema to generate. + parameters_callback: A callable that will be invoked for each parameter. The callback + should take three required arguments: the index, the name and the type annotation + (or [`Parameter.empty`][inspect.Parameter.empty] if not annotated) of the parameter. + The callback can optionally return `'skip'`, so that the parameter gets excluded + from the resulting schema. + config: The configuration to use. + + Returns: + The generated schema. + """ + generate_schema = _generate_schema.GenerateSchema( + _config.ConfigWrapper(config), + ns_resolver=_namespace_utils.NsResolver(namespaces_tuple=_namespace_utils.ns_for_function(func)), + ) + + if schema_type == 'arguments': + schema = generate_schema._arguments_schema(func, parameters_callback) # pyright: ignore[reportArgumentType] + else: + schema = generate_schema._arguments_v3_schema(func, parameters_callback) # pyright: ignore[reportArgumentType] + return generate_schema.clean_schema(schema) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py new file mode 100644 index 0000000..3e7f820 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py @@ -0,0 +1,5 @@ +"""Experimental module exposing a function a `MISSING` sentinel.""" + +from pydantic_core import MISSING + +__all__ = ('MISSING',) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py new file mode 100644 index 0000000..9ead658 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py @@ -0,0 +1,663 @@ +"""Experimental pipeline API functionality. Be careful with this API, it's subject to change.""" + +from __future__ import annotations + +import datetime +import operator +import re +import sys +from collections import deque +from collections.abc import Container +from dataclasses import dataclass +from decimal import Decimal +from functools import cached_property, partial +from re import Pattern +from typing import TYPE_CHECKING, Annotated, Any, Callable, Generic, Protocol, TypeVar, Union, overload + +import annotated_types + +if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler + +from pydantic_core import PydanticCustomError +from pydantic_core import core_schema as cs + +from pydantic import Strict +from pydantic._internal._internal_dataclass import slots_true as _slots_true + +if sys.version_info < (3, 10): + EllipsisType = type(Ellipsis) +else: + from types import EllipsisType + +__all__ = ['validate_as', 'validate_as_deferred', 'transform'] + +_slots_frozen = {**_slots_true, 'frozen': True} + + +@dataclass(**_slots_frozen) +class _ValidateAs: + tp: type[Any] + strict: bool = False + + +@dataclass +class _ValidateAsDefer: + func: Callable[[], type[Any]] + + @cached_property + def tp(self) -> type[Any]: + return self.func() + + +@dataclass(**_slots_frozen) +class _Transform: + func: Callable[[Any], Any] + + +@dataclass(**_slots_frozen) +class _PipelineOr: + left: _Pipeline[Any, Any] + right: _Pipeline[Any, Any] + + +@dataclass(**_slots_frozen) +class _PipelineAnd: + left: _Pipeline[Any, Any] + right: _Pipeline[Any, Any] + + +@dataclass(**_slots_frozen) +class _Eq: + value: Any + + +@dataclass(**_slots_frozen) +class _NotEq: + value: Any + + +@dataclass(**_slots_frozen) +class _In: + values: Container[Any] + + +@dataclass(**_slots_frozen) +class _NotIn: + values: Container[Any] + + +_ConstraintAnnotation = Union[ + annotated_types.Le, + annotated_types.Ge, + annotated_types.Lt, + annotated_types.Gt, + annotated_types.Len, + annotated_types.MultipleOf, + annotated_types.Timezone, + annotated_types.Interval, + annotated_types.Predicate, + # common predicates not included in annotated_types + _Eq, + _NotEq, + _In, + _NotIn, + # regular expressions + Pattern[str], +] + + +@dataclass(**_slots_frozen) +class _Constraint: + constraint: _ConstraintAnnotation + + +_Step = Union[_ValidateAs, _ValidateAsDefer, _Transform, _PipelineOr, _PipelineAnd, _Constraint] + +_InT = TypeVar('_InT') +_OutT = TypeVar('_OutT') +_NewOutT = TypeVar('_NewOutT') + + +class _FieldTypeMarker: + pass + + +# TODO: ultimately, make this public, see https://github.com/pydantic/pydantic/pull/9459#discussion_r1628197626 +# Also, make this frozen eventually, but that doesn't work right now because of the generic base +# Which attempts to modify __orig_base__ and such. +# We could go with a manual freeze, but that seems overkill for now. +@dataclass(**_slots_true) +class _Pipeline(Generic[_InT, _OutT]): + """Abstract representation of a chain of validation, transformation, and parsing steps.""" + + _steps: tuple[_Step, ...] + + def transform( + self, + func: Callable[[_OutT], _NewOutT], + ) -> _Pipeline[_InT, _NewOutT]: + """Transform the output of the previous step. + + If used as the first step in a pipeline, the type of the field is used. + That is, the transformation is applied to after the value is parsed to the field's type. + """ + return _Pipeline[_InT, _NewOutT](self._steps + (_Transform(func),)) + + @overload + def validate_as(self, tp: type[_NewOutT], *, strict: bool = False) -> _Pipeline[_InT, _NewOutT]: ... + + @overload + def validate_as( + self, + tp: ellipsis, # noqa: F821 # TODO: use `_typing_extra.EllipsisType` when we drop Py3.9 + *, + strict: bool = False, + ) -> _Pipeline[_InT, Any]: ... + + # TODO PEP 747: use TypeForm to properly type Annotated aliases (e.g. NewPath, FilePath). + # This fallback accepts any type expression but loses generic type inference. + @overload + def validate_as(self, tp: Any, *, strict: bool = ...) -> _Pipeline[_InT, Any]: ... + + def validate_as(self, tp: type[_NewOutT] | EllipsisType | Any, *, strict: bool = False) -> _Pipeline[_InT, Any]: # type: ignore + """Validate / parse the input into a new type. + + If no type is provided, the type of the field is used. + + Types are parsed in Pydantic's `lax` mode by default, + but you can enable `strict` mode by passing `strict=True`. + """ + if isinstance(tp, EllipsisType): + return _Pipeline[_InT, Any](self._steps + (_ValidateAs(_FieldTypeMarker, strict=strict),)) + return _Pipeline[_InT, _NewOutT](self._steps + (_ValidateAs(tp, strict=strict),)) + + def validate_as_deferred(self, func: Callable[[], type[_NewOutT]]) -> _Pipeline[_InT, _NewOutT]: + """Parse the input into a new type, deferring resolution of the type until the current class + is fully defined. + + This is useful when you need to reference the class in it's own type annotations. + """ + return _Pipeline[_InT, _NewOutT](self._steps + (_ValidateAsDefer(func),)) + + # constraints + @overload + def constrain(self: _Pipeline[_InT, _NewOutGe], constraint: annotated_types.Ge) -> _Pipeline[_InT, _NewOutGe]: ... + + @overload + def constrain(self: _Pipeline[_InT, _NewOutGt], constraint: annotated_types.Gt) -> _Pipeline[_InT, _NewOutGt]: ... + + @overload + def constrain(self: _Pipeline[_InT, _NewOutLe], constraint: annotated_types.Le) -> _Pipeline[_InT, _NewOutLe]: ... + + @overload + def constrain(self: _Pipeline[_InT, _NewOutLt], constraint: annotated_types.Lt) -> _Pipeline[_InT, _NewOutLt]: ... + + @overload + def constrain( + self: _Pipeline[_InT, _NewOutLen], constraint: annotated_types.Len + ) -> _Pipeline[_InT, _NewOutLen]: ... + + @overload + def constrain( + self: _Pipeline[_InT, _NewOutT], constraint: annotated_types.MultipleOf + ) -> _Pipeline[_InT, _NewOutT]: ... + + @overload + def constrain( + self: _Pipeline[_InT, _NewOutDatetime], constraint: annotated_types.Timezone + ) -> _Pipeline[_InT, _NewOutDatetime]: ... + + @overload + def constrain(self: _Pipeline[_InT, _OutT], constraint: annotated_types.Predicate) -> _Pipeline[_InT, _OutT]: ... + + @overload + def constrain( + self: _Pipeline[_InT, _NewOutInterval], constraint: annotated_types.Interval + ) -> _Pipeline[_InT, _NewOutInterval]: ... + + @overload + def constrain(self: _Pipeline[_InT, _OutT], constraint: _Eq) -> _Pipeline[_InT, _OutT]: ... + + @overload + def constrain(self: _Pipeline[_InT, _OutT], constraint: _NotEq) -> _Pipeline[_InT, _OutT]: ... + + @overload + def constrain(self: _Pipeline[_InT, _OutT], constraint: _In) -> _Pipeline[_InT, _OutT]: ... + + @overload + def constrain(self: _Pipeline[_InT, _OutT], constraint: _NotIn) -> _Pipeline[_InT, _OutT]: ... + + @overload + def constrain(self: _Pipeline[_InT, _NewOutT], constraint: Pattern[str]) -> _Pipeline[_InT, _NewOutT]: ... + + def constrain(self, constraint: _ConstraintAnnotation) -> Any: + """Constrain a value to meet a certain condition. + + We support most conditions from `annotated_types`, as well as regular expressions. + + Most of the time you'll be calling a shortcut method like `gt`, `lt`, `len`, etc + so you don't need to call this directly. + """ + return _Pipeline[_InT, _OutT](self._steps + (_Constraint(constraint),)) + + def predicate(self: _Pipeline[_InT, _NewOutT], func: Callable[[_NewOutT], bool]) -> _Pipeline[_InT, _NewOutT]: + """Constrain a value to meet a certain predicate.""" + return self.constrain(annotated_types.Predicate(func)) + + def gt(self: _Pipeline[_InT, _NewOutGt], gt: _NewOutGt) -> _Pipeline[_InT, _NewOutGt]: + """Constrain a value to be greater than a certain value.""" + return self.constrain(annotated_types.Gt(gt)) + + def lt(self: _Pipeline[_InT, _NewOutLt], lt: _NewOutLt) -> _Pipeline[_InT, _NewOutLt]: + """Constrain a value to be less than a certain value.""" + return self.constrain(annotated_types.Lt(lt)) + + def ge(self: _Pipeline[_InT, _NewOutGe], ge: _NewOutGe) -> _Pipeline[_InT, _NewOutGe]: + """Constrain a value to be greater than or equal to a certain value.""" + return self.constrain(annotated_types.Ge(ge)) + + def le(self: _Pipeline[_InT, _NewOutLe], le: _NewOutLe) -> _Pipeline[_InT, _NewOutLe]: + """Constrain a value to be less than or equal to a certain value.""" + return self.constrain(annotated_types.Le(le)) + + def len(self: _Pipeline[_InT, _NewOutLen], min_len: int, max_len: int | None = None) -> _Pipeline[_InT, _NewOutLen]: + """Constrain a value to have a certain length.""" + return self.constrain(annotated_types.Len(min_len, max_len)) + + @overload + def multiple_of(self: _Pipeline[_InT, _NewOutDiv], multiple_of: _NewOutDiv) -> _Pipeline[_InT, _NewOutDiv]: ... + + @overload + def multiple_of(self: _Pipeline[_InT, _NewOutMod], multiple_of: _NewOutMod) -> _Pipeline[_InT, _NewOutMod]: ... + + def multiple_of(self: _Pipeline[_InT, Any], multiple_of: Any) -> _Pipeline[_InT, Any]: + """Constrain a value to be a multiple of a certain number.""" + return self.constrain(annotated_types.MultipleOf(multiple_of)) + + def eq(self: _Pipeline[_InT, _OutT], value: _OutT) -> _Pipeline[_InT, _OutT]: + """Constrain a value to be equal to a certain value.""" + return self.constrain(_Eq(value)) + + def not_eq(self: _Pipeline[_InT, _OutT], value: _OutT) -> _Pipeline[_InT, _OutT]: + """Constrain a value to not be equal to a certain value.""" + return self.constrain(_NotEq(value)) + + def in_(self: _Pipeline[_InT, _OutT], values: Container[_OutT]) -> _Pipeline[_InT, _OutT]: + """Constrain a value to be in a certain set.""" + return self.constrain(_In(values)) + + def not_in(self: _Pipeline[_InT, _OutT], values: Container[_OutT]) -> _Pipeline[_InT, _OutT]: + """Constrain a value to not be in a certain set.""" + return self.constrain(_NotIn(values)) + + # timezone methods + def datetime_tz_naive(self: _Pipeline[_InT, datetime.datetime]) -> _Pipeline[_InT, datetime.datetime]: + return self.constrain(annotated_types.Timezone(None)) + + def datetime_tz_aware(self: _Pipeline[_InT, datetime.datetime]) -> _Pipeline[_InT, datetime.datetime]: + return self.constrain(annotated_types.Timezone(...)) + + def datetime_tz( + self: _Pipeline[_InT, datetime.datetime], tz: datetime.tzinfo + ) -> _Pipeline[_InT, datetime.datetime]: + return self.constrain(annotated_types.Timezone(tz)) # type: ignore + + def datetime_with_tz( + self: _Pipeline[_InT, datetime.datetime], tz: datetime.tzinfo | None + ) -> _Pipeline[_InT, datetime.datetime]: + return self.transform(partial(datetime.datetime.replace, tzinfo=tz)) + + # string methods + def str_lower(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: + return self.transform(str.lower) + + def str_upper(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: + return self.transform(str.upper) + + def str_title(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: + return self.transform(str.title) + + def str_strip(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: + return self.transform(str.strip) + + def str_pattern(self: _Pipeline[_InT, str], pattern: str) -> _Pipeline[_InT, str]: + return self.constrain(re.compile(pattern)) + + def str_contains(self: _Pipeline[_InT, str], substring: str) -> _Pipeline[_InT, str]: + return self.predicate(lambda v: substring in v) + + def str_starts_with(self: _Pipeline[_InT, str], prefix: str) -> _Pipeline[_InT, str]: + return self.predicate(lambda v: v.startswith(prefix)) + + def str_ends_with(self: _Pipeline[_InT, str], suffix: str) -> _Pipeline[_InT, str]: + return self.predicate(lambda v: v.endswith(suffix)) + + # operators + def otherwise(self, other: _Pipeline[_OtherIn, _OtherOut]) -> _Pipeline[_InT | _OtherIn, _OutT | _OtherOut]: + """Combine two validation chains, returning the result of the first chain if it succeeds, and the second chain if it fails.""" + return _Pipeline((_PipelineOr(self, other),)) + + __or__ = otherwise + + def then(self, other: _Pipeline[_OutT, _OtherOut]) -> _Pipeline[_InT, _OtherOut]: + """Pipe the result of one validation chain into another.""" + return _Pipeline((_PipelineAnd(self, other),)) + + __and__ = then + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> cs.CoreSchema: + queue = deque(self._steps) + + s = None + + while queue: + step = queue.popleft() + s = _apply_step(step, s, handler, source_type) + + s = s or cs.any_schema() + return s + + def __supports_type__(self, _: _OutT) -> bool: + raise NotImplementedError + + +validate_as = _Pipeline[Any, Any](()).validate_as +validate_as_deferred = _Pipeline[Any, Any](()).validate_as_deferred +transform = _Pipeline[Any, Any]((_ValidateAs(_FieldTypeMarker),)).transform + + +def _check_func( + func: Callable[[Any], bool], predicate_err: str | Callable[[], str], s: cs.CoreSchema | None +) -> cs.CoreSchema: + def handler(v: Any) -> Any: + if func(v): + return v + raise ValueError(f'Expected {predicate_err if isinstance(predicate_err, str) else predicate_err()}') + + if s is None: + return cs.no_info_plain_validator_function(handler) + else: + return cs.no_info_after_validator_function(handler, s) + + +def _apply_step(step: _Step, s: cs.CoreSchema | None, handler: GetCoreSchemaHandler, source_type: Any) -> cs.CoreSchema: + if isinstance(step, _ValidateAs): + s = _apply_parse(s, step.tp, step.strict, handler, source_type) + elif isinstance(step, _ValidateAsDefer): + s = _apply_parse(s, step.tp, False, handler, source_type) + elif isinstance(step, _Transform): + s = _apply_transform(s, step.func, handler) + elif isinstance(step, _Constraint): + s = _apply_constraint(s, step.constraint) + elif isinstance(step, _PipelineOr): + s = cs.union_schema([handler(step.left), handler(step.right)]) + else: + assert isinstance(step, _PipelineAnd) + s = cs.chain_schema([handler(step.left), handler(step.right)]) + return s + + +def _apply_parse( + s: cs.CoreSchema | None, + tp: type[Any], + strict: bool, + handler: GetCoreSchemaHandler, + source_type: Any, +) -> cs.CoreSchema: + if tp is _FieldTypeMarker: + return cs.chain_schema([s, handler(source_type)]) if s else handler(source_type) + + if strict: + tp = Annotated[tp, Strict()] # type: ignore + + if s and s['type'] == 'any': + return handler(tp) + else: + return cs.chain_schema([s, handler(tp)]) if s else handler(tp) + + +def _apply_transform( + s: cs.CoreSchema | None, func: Callable[[Any], Any], handler: GetCoreSchemaHandler +) -> cs.CoreSchema: + if s is None: + return cs.no_info_plain_validator_function(func) + + if s['type'] == 'str': + if func is str.strip: + s = s.copy() + s['strip_whitespace'] = True + return s + elif func is str.lower: + s = s.copy() + s['to_lower'] = True + return s + elif func is str.upper: + s = s.copy() + s['to_upper'] = True + return s + + return cs.no_info_after_validator_function(func, s) + + +def _apply_constraint( # noqa: C901 + s: cs.CoreSchema | None, constraint: _ConstraintAnnotation +) -> cs.CoreSchema: + """Apply a single constraint to a schema.""" + if isinstance(constraint, annotated_types.Gt): + gt = constraint.gt + if s and s['type'] in {'int', 'float', 'decimal'}: + s = s.copy() + if s['type'] == 'int' and isinstance(gt, int): + s['gt'] = gt + elif s['type'] == 'float' and isinstance(gt, float): + s['gt'] = gt + elif s['type'] == 'decimal' and isinstance(gt, Decimal): + s['gt'] = gt + else: + + def check_gt(v: Any) -> bool: + return v > gt + + s = _check_func(check_gt, f'> {gt}', s) + elif isinstance(constraint, annotated_types.Ge): + ge = constraint.ge + if s and s['type'] in {'int', 'float', 'decimal'}: + s = s.copy() + if s['type'] == 'int' and isinstance(ge, int): + s['ge'] = ge + elif s['type'] == 'float' and isinstance(ge, float): + s['ge'] = ge + elif s['type'] == 'decimal' and isinstance(ge, Decimal): + s['ge'] = ge + + def check_ge(v: Any) -> bool: + return v >= ge + + s = _check_func(check_ge, f'>= {ge}', s) + elif isinstance(constraint, annotated_types.Lt): + lt = constraint.lt + if s and s['type'] in {'int', 'float', 'decimal'}: + s = s.copy() + if s['type'] == 'int' and isinstance(lt, int): + s['lt'] = lt + elif s['type'] == 'float' and isinstance(lt, float): + s['lt'] = lt + elif s['type'] == 'decimal' and isinstance(lt, Decimal): + s['lt'] = lt + + def check_lt(v: Any) -> bool: + return v < lt + + s = _check_func(check_lt, f'< {lt}', s) + elif isinstance(constraint, annotated_types.Le): + le = constraint.le + if s and s['type'] in {'int', 'float', 'decimal'}: + s = s.copy() + if s['type'] == 'int' and isinstance(le, int): + s['le'] = le + elif s['type'] == 'float' and isinstance(le, float): + s['le'] = le + elif s['type'] == 'decimal' and isinstance(le, Decimal): + s['le'] = le + + def check_le(v: Any) -> bool: + return v <= le + + s = _check_func(check_le, f'<= {le}', s) + elif isinstance(constraint, annotated_types.Len): + min_len = constraint.min_length + max_len = constraint.max_length + + if s and s['type'] in {'str', 'list', 'tuple', 'set', 'frozenset', 'dict'}: + assert ( + s['type'] == 'str' + or s['type'] == 'list' + or s['type'] == 'tuple' + or s['type'] == 'set' + or s['type'] == 'dict' + or s['type'] == 'frozenset' + ) + s = s.copy() + if min_len != 0: + s['min_length'] = min_len + if max_len is not None: + s['max_length'] = max_len + + def check_len(v: Any) -> bool: + if max_len is not None: + return (min_len <= len(v)) and (len(v) <= max_len) + return min_len <= len(v) + + s = _check_func(check_len, f'length >= {min_len} and length <= {max_len}', s) + elif isinstance(constraint, annotated_types.MultipleOf): + multiple_of = constraint.multiple_of + if s and s['type'] in {'int', 'float', 'decimal'}: + s = s.copy() + if s['type'] == 'int' and isinstance(multiple_of, int): + s['multiple_of'] = multiple_of + elif s['type'] == 'float' and isinstance(multiple_of, float): + s['multiple_of'] = multiple_of + elif s['type'] == 'decimal' and isinstance(multiple_of, Decimal): + s['multiple_of'] = multiple_of + + def check_multiple_of(v: Any) -> bool: + return v % multiple_of == 0 + + s = _check_func(check_multiple_of, f'% {multiple_of} == 0', s) + elif isinstance(constraint, annotated_types.Timezone): + tz = constraint.tz + + if tz is ...: + if s and s['type'] == 'datetime': + s = s.copy() + s['tz_constraint'] = 'aware' + else: + + def check_tz_aware(v: object) -> bool: + assert isinstance(v, datetime.datetime) + return v.tzinfo is not None + + s = _check_func(check_tz_aware, 'timezone aware', s) + elif tz is None: + if s and s['type'] == 'datetime': + s = s.copy() + s['tz_constraint'] = 'naive' + else: + + def check_tz_naive(v: object) -> bool: + assert isinstance(v, datetime.datetime) + return v.tzinfo is None + + s = _check_func(check_tz_naive, 'timezone naive', s) + else: + raise NotImplementedError('Constraining to a specific timezone is not yet supported') + elif isinstance(constraint, annotated_types.Interval): + if constraint.ge: + s = _apply_constraint(s, annotated_types.Ge(constraint.ge)) + if constraint.gt: + s = _apply_constraint(s, annotated_types.Gt(constraint.gt)) + if constraint.le: + s = _apply_constraint(s, annotated_types.Le(constraint.le)) + if constraint.lt: + s = _apply_constraint(s, annotated_types.Lt(constraint.lt)) + assert s is not None + elif isinstance(constraint, annotated_types.Predicate): + func = constraint.func + # Same logic as in `_known_annotated_metadata.apply_known_metadata()`: + predicate_name = f'{func.__qualname__!r} ' if hasattr(func, '__qualname__') else '' + + def predicate_func(v: Any) -> Any: + if not func(v): + raise PydanticCustomError( + 'predicate_failed', + f'Predicate {predicate_name}failed', # pyright: ignore[reportArgumentType] + ) + return v + + if s is None: + s = cs.no_info_plain_validator_function(predicate_func) + else: + s = cs.no_info_after_validator_function(predicate_func, s) + elif isinstance(constraint, _NotEq): + value = constraint.value + + def check_not_eq(v: Any) -> bool: + return operator.__ne__(v, value) + + s = _check_func(check_not_eq, f'!= {value}', s) + elif isinstance(constraint, _Eq): + value = constraint.value + + def check_eq(v: Any) -> bool: + return operator.__eq__(v, value) + + s = _check_func(check_eq, f'== {value}', s) + elif isinstance(constraint, _In): + values = constraint.values + + def check_in(v: Any) -> bool: + return operator.__contains__(values, v) + + s = _check_func(check_in, f'in {values}', s) + elif isinstance(constraint, _NotIn): + values = constraint.values + + def check_not_in(v: Any) -> bool: + return operator.__not__(operator.__contains__(values, v)) + + s = _check_func(check_not_in, f'not in {values}', s) + else: + assert isinstance(constraint, Pattern) + if s and s['type'] == 'str': + s = s.copy() + s['pattern'] = constraint.pattern + else: + + def check_pattern(v: object) -> bool: + assert isinstance(v, str) + return constraint.match(v) is not None + + s = _check_func(check_pattern, f'~ {constraint.pattern}', s) + return s + + +class _SupportsRange(annotated_types.SupportsLe, annotated_types.SupportsGe, Protocol): + pass + + +class _SupportsLen(Protocol): + def __len__(self) -> int: ... + + +_NewOutGt = TypeVar('_NewOutGt', bound=annotated_types.SupportsGt) +_NewOutGe = TypeVar('_NewOutGe', bound=annotated_types.SupportsGe) +_NewOutLt = TypeVar('_NewOutLt', bound=annotated_types.SupportsLt) +_NewOutLe = TypeVar('_NewOutLe', bound=annotated_types.SupportsLe) +_NewOutLen = TypeVar('_NewOutLen', bound=_SupportsLen) +_NewOutDiv = TypeVar('_NewOutDiv', bound=annotated_types.SupportsDiv) +_NewOutMod = TypeVar('_NewOutMod', bound=annotated_types.SupportsMod) +_NewOutDatetime = TypeVar('_NewOutDatetime', bound=datetime.datetime) +_NewOutInterval = TypeVar('_NewOutInterval', bound=_SupportsRange) +_OtherIn = TypeVar('_OtherIn') +_OtherOut = TypeVar('_OtherOut') diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py new file mode 100644 index 0000000..ede966f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/fields.py @@ -0,0 +1,1892 @@ +"""Defining fields on models.""" + +from __future__ import annotations as _annotations + +import dataclasses +import inspect +import re +import sys +from collections.abc import Callable, Mapping +from copy import copy +from dataclasses import Field as DataclassField +from functools import cached_property +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, TypeVar, final, overload +from warnings import warn + +import annotated_types +import typing_extensions +from pydantic_core import MISSING, PydanticUndefined +from typing_extensions import Self, TypeAlias, TypedDict, Unpack, deprecated +from typing_inspection import typing_objects +from typing_inspection.introspection import UNKNOWN, AnnotationSource, ForbiddenQualifier, Qualifier, inspect_annotation + +from . import types +from ._internal import _decorators, _fields, _generics, _internal_dataclass, _repr, _typing_extra, _utils +from ._internal._namespace_utils import GlobalsNamespace, MappingNamespace +from .aliases import AliasChoices, AliasGenerator, AliasPath +from .config import JsonDict +from .errors import PydanticForbiddenQualifier, PydanticUserError +from .json_schema import PydanticJsonSchemaWarning +from .warnings import PydanticDeprecatedSince20 + +if TYPE_CHECKING: + from ._internal._config import ConfigWrapper + from ._internal._repr import ReprArgs + + +__all__ = 'Field', 'FieldInfo', 'PrivateAttr', 'computed_field' + + +_Unset: Any = PydanticUndefined + +if sys.version_info >= (3, 13): + import warnings + + Deprecated: TypeAlias = warnings.deprecated | deprecated +else: + Deprecated: TypeAlias = deprecated + + +class _FromFieldInfoInputs(TypedDict, total=False): + """This class exists solely to add type checking for the `**kwargs` in `FieldInfo.from_field`.""" + + # TODO PEP 747: use TypeForm: + annotation: type[Any] | None + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None + alias: str | None + alias_priority: int | None + validation_alias: str | AliasPath | AliasChoices | None + serialization_alias: str | None + title: str | None + field_title_generator: Callable[[str, FieldInfo], str] | None + description: str | None + examples: list[Any] | None + exclude: bool | None + exclude_if: Callable[[Any], bool] | None + gt: annotated_types.SupportsGt | None + ge: annotated_types.SupportsGe | None + lt: annotated_types.SupportsLt | None + le: annotated_types.SupportsLe | None + multiple_of: float | None + strict: bool | None + min_length: int | None + max_length: int | None + pattern: str | re.Pattern[str] | None + allow_inf_nan: bool | None + max_digits: int | None + decimal_places: int | None + union_mode: Literal['smart', 'left_to_right'] | None + discriminator: str | types.Discriminator | None + deprecated: Deprecated | str | bool | None + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + frozen: bool | None + validate_default: bool | None + repr: bool + init: bool | None + init_var: bool | None + kw_only: bool | None + coerce_numbers_to_str: bool | None + fail_fast: bool | None + + +class _FieldInfoInputs(_FromFieldInfoInputs, total=False): + """This class exists solely to add type checking for the `**kwargs` in `FieldInfo.__init__`.""" + + default: Any + + +class _FieldInfoAsDict(TypedDict, closed=True): + # TODO PEP 747: use TypeForm: + annotation: Any + metadata: list[Any] + attributes: dict[str, Any] + + +@final +class FieldInfo(_repr.Representation): + """This class holds information about a field. + + `FieldInfo` is used for any field definition regardless of whether the [`Field()`][pydantic.fields.Field] + function is explicitly used. + + !!! warning + The `FieldInfo` class is meant to expose information about a field in a Pydantic model or dataclass. + `FieldInfo` instances shouldn't be instantiated directly, nor mutated. + + If you need to derive a new model from another one and are willing to alter `FieldInfo` instances, + refer to this [dynamic model example](../examples/dynamic_models.md). + + Attributes: + annotation: The type annotation of the field. + default: The default value of the field. + default_factory: A callable to generate the default value. The callable can either take 0 arguments + (in which case it is called as is) or a single argument containing the already validated data. + alias: The alias name of the field. + alias_priority: The priority of the field's alias. + validation_alias: The validation alias of the field. + serialization_alias: The serialization alias of the field. + title: The title of the field. + field_title_generator: A callable that takes a field name and returns title for it. + description: The description of the field. + examples: List of examples of the field. + exclude: Whether to exclude the field from the model serialization. + exclude_if: A callable that determines whether to exclude a field during serialization based on its value. + discriminator: Field name or Discriminator for discriminating the type in a tagged union. + deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, + or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. + json_schema_extra: A dict or callable to provide extra JSON schema properties. + frozen: Whether the field is frozen. + validate_default: Whether to validate the default value of the field. + repr: Whether to include the field in representation of the model. + init: Whether the field should be included in the constructor of the dataclass. + init_var: Whether the field should _only_ be included in the constructor of the dataclass, and not stored. + kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass. + metadata: The metadata list. Contains all the data that isn't expressed as direct `FieldInfo` attributes, including: + + * Type-specific constraints, such as `gt` or `min_length` (these are converted to metadata classes such as `annotated_types.Gt`). + * Any other arbitrary object used within [`Annotated`][typing.Annotated] metadata + (e.g. [custom types handlers](../concepts/types.md#as-an-annotation) or any object not recognized by Pydantic). + """ + + # TODO PEP 747: use TypeForm: + annotation: type[Any] | None + default: Any + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None + alias: str | None + alias_priority: int | None + validation_alias: str | AliasPath | AliasChoices | None + serialization_alias: str | None + title: str | None + field_title_generator: Callable[[str, FieldInfo], str] | None + description: str | None + examples: list[Any] | None + exclude: bool | None + exclude_if: Callable[[Any], bool] | None + discriminator: str | types.Discriminator | None + deprecated: Deprecated | str | bool | None + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + frozen: bool | None + validate_default: bool | None + repr: bool + init: bool | None + init_var: bool | None + kw_only: bool | None + metadata: list[Any] + + __slots__ = ( + 'annotation', + 'default', + 'default_factory', + 'alias', + 'alias_priority', + 'validation_alias', + 'serialization_alias', + 'title', + 'field_title_generator', + 'description', + 'examples', + 'exclude', + 'exclude_if', + 'discriminator', + 'deprecated', + 'json_schema_extra', + 'frozen', + 'validate_default', + 'repr', + 'init', + 'init_var', + 'kw_only', + 'metadata', + '_attributes_set', + '_qualifiers', + '_complete', + '_original_assignment', + '_original_annotation', + '_final', + ) + + # used to convert kwargs to metadata/constraints, + # None has a special meaning - these items are collected into a `PydanticGeneralMetadata` + metadata_lookup: ClassVar[dict[str, Callable[[Any], Any] | None]] = { + 'strict': types.Strict, + 'gt': annotated_types.Gt, + 'ge': annotated_types.Ge, + 'lt': annotated_types.Lt, + 'le': annotated_types.Le, + 'multiple_of': annotated_types.MultipleOf, + 'min_length': annotated_types.MinLen, + 'max_length': annotated_types.MaxLen, + 'pattern': None, + 'allow_inf_nan': None, + 'max_digits': None, + 'decimal_places': None, + 'union_mode': None, + 'coerce_numbers_to_str': None, + 'fail_fast': types.FailFast, + } + + def __init__(self, **kwargs: Unpack[_FieldInfoInputs]) -> None: + """This class should generally not be initialized directly; instead, use the `pydantic.fields.Field` function + or one of the constructor classmethods. + + See the signature of `pydantic.fields.Field` for more details about the expected arguments. + """ + # Tracking the explicitly set attributes is necessary to correctly merge `Field()` functions + # (e.g. with `Annotated[int, Field(alias='a'), Field(alias=None)]`, even though `None` is the default value, + # we need to track that `alias=None` was explicitly set): + self._attributes_set = {k: v for k, v in kwargs.items() if v is not _Unset and k not in self.metadata_lookup} + kwargs = {k: _DefaultValues.get(k) if v is _Unset else v for k, v in kwargs.items()} # type: ignore + self.annotation = kwargs.get('annotation') + + # Note: in theory, the second `pop()` arguments are not required below, as defaults are already set from `_DefaultsValues`. + default = kwargs.pop('default', PydanticUndefined) + if default is Ellipsis: + self.default = PydanticUndefined + self._attributes_set.pop('default', None) + else: + self.default = default + + self.default_factory = kwargs.pop('default_factory', None) + + if self.default is not PydanticUndefined and self.default_factory is not None: + raise TypeError('cannot specify both default and default_factory') + + self.alias = kwargs.pop('alias', None) + self.validation_alias = kwargs.pop('validation_alias', None) + self.serialization_alias = kwargs.pop('serialization_alias', None) + alias_is_set = any(alias is not None for alias in (self.alias, self.validation_alias, self.serialization_alias)) + self.alias_priority = kwargs.pop('alias_priority', None) or 2 if alias_is_set else None + self.title = kwargs.pop('title', None) + self.field_title_generator = kwargs.pop('field_title_generator', None) + self.description = kwargs.pop('description', None) + self.examples = kwargs.pop('examples', None) + self.exclude = kwargs.pop('exclude', None) + self.exclude_if = kwargs.pop('exclude_if', None) + self.discriminator = kwargs.pop('discriminator', None) + # For compatibility with FastAPI<=0.110.0, we preserve the existing value if it is not overridden + self.deprecated = kwargs.pop('deprecated', getattr(self, 'deprecated', None)) + self.repr = kwargs.pop('repr', True) + self.json_schema_extra = kwargs.pop('json_schema_extra', None) + self.validate_default = kwargs.pop('validate_default', None) + self.frozen = kwargs.pop('frozen', None) + # currently only used on dataclasses + self.init = kwargs.pop('init', None) + self.init_var = kwargs.pop('init_var', None) + self.kw_only = kwargs.pop('kw_only', None) + + self.metadata = self._collect_metadata(kwargs) # type: ignore + + # Private attributes: + self._qualifiers: set[Qualifier] = set() + # Used to rebuild FieldInfo instances: + self._complete = True + self._original_annotation: Any = PydanticUndefined + self._original_assignment: Any = PydanticUndefined + # Used to track whether the `FieldInfo` instance represents the data about a field (and is exposed in `model_fields`/`__pydantic_fields__`), + # or if it is the result of the `Field()` function being used as metadata in an `Annotated` type/as an assignment + # (not an ideal pattern, see https://github.com/pydantic/pydantic/issues/11122): + self._final = False + + @staticmethod + def from_field(default: Any = PydanticUndefined, **kwargs: Unpack[_FromFieldInfoInputs]) -> FieldInfo: + """Create a new `FieldInfo` object with the `Field` function. + + Args: + default: The default value for the field. Defaults to Undefined. + **kwargs: Additional arguments dictionary. + + Raises: + TypeError: If 'annotation' is passed as a keyword argument. + + Returns: + A new FieldInfo object with the given parameters. + + Example: + This is how you can create a field with default value like this: + + ```python + import pydantic + + class MyModel(pydantic.BaseModel): + foo: int = pydantic.Field(4) + ``` + """ + if 'annotation' in kwargs: + raise TypeError('"annotation" is not permitted as a Field keyword argument') + return FieldInfo(default=default, **kwargs) + + @staticmethod + def from_annotation(annotation: type[Any], *, _source: AnnotationSource = AnnotationSource.ANY) -> FieldInfo: + """Creates a `FieldInfo` instance from a bare annotation. + + This function is used internally to create a `FieldInfo` from a bare annotation like this: + + ```python + import pydantic + + class MyModel(pydantic.BaseModel): + foo: int # <-- like this + ``` + + We also account for the case where the annotation can be an instance of `Annotated` and where + one of the (not first) arguments in `Annotated` is an instance of `FieldInfo`, e.g.: + + ```python + from typing import Annotated + + import annotated_types + + import pydantic + + class MyModel(pydantic.BaseModel): + foo: Annotated[int, annotated_types.Gt(42)] + bar: Annotated[int, pydantic.Field(gt=42)] + ``` + + Args: + annotation: An annotation object. + + Returns: + An instance of the field metadata. + """ + try: + inspected_ann = inspect_annotation( + annotation, + annotation_source=_source, + unpack_type_aliases='skip', + ) + except ForbiddenQualifier as e: + raise PydanticForbiddenQualifier(e.qualifier, annotation) + + # TODO check for classvar and error? + + # No assigned value, this happens when using a bare `Final` qualifier (also for other + # qualifiers, but they shouldn't appear here). In this case we infer the type as `Any` + # because we don't have any assigned value. + type_expr: Any = Any if inspected_ann.type is UNKNOWN else inspected_ann.type + final = 'final' in inspected_ann.qualifiers + metadata = inspected_ann.metadata + + attr_overrides = {'annotation': type_expr} + if final: + attr_overrides['frozen'] = True + field_info = FieldInfo._construct(metadata, **attr_overrides) + field_info._qualifiers = inspected_ann.qualifiers + field_info._final = True + return field_info + + @staticmethod + def from_annotated_attribute( + annotation: type[Any], default: Any, *, _source: AnnotationSource = AnnotationSource.ANY + ) -> FieldInfo: + """Create `FieldInfo` from an annotation with a default value. + + This is used in cases like the following: + + ```python + from typing import Annotated + + import annotated_types + + import pydantic + + class MyModel(pydantic.BaseModel): + foo: int = 4 # <-- like this + bar: Annotated[int, annotated_types.Gt(4)] = 4 # <-- or this + spam: Annotated[int, pydantic.Field(gt=4)] = 4 # <-- or this + ``` + + Args: + annotation: The type annotation of the field. + default: The default value of the field. + + Returns: + A field object with the passed values. + """ + if annotation is not MISSING and annotation is default: + raise PydanticUserError( + 'Error when building FieldInfo from annotated attribute. ' + "Make sure you don't have any field name clashing with a type annotation.", + code='unevaluable-type-annotation', + ) + + try: + inspected_ann = inspect_annotation( + annotation, + annotation_source=_source, + unpack_type_aliases='skip', + ) + except ForbiddenQualifier as e: + raise PydanticForbiddenQualifier(e.qualifier, annotation) + + # TODO check for classvar and error? + + # TODO infer from the default, this can be done in v3 once we treat final fields with + # a default as proper fields and not class variables: + type_expr: Any = Any if inspected_ann.type is UNKNOWN else inspected_ann.type + final = 'final' in inspected_ann.qualifiers + metadata = inspected_ann.metadata + + # HACK 1: the order in which the metadata is merged is inconsistent; we need to prepend + # metadata from the assignment at the beginning of the metadata. Changing this is only + # possible in v3 (at least). See https://github.com/pydantic/pydantic/issues/10507 + prepend_metadata: list[Any] | None = None + attr_overrides = {'annotation': type_expr} + if final: + attr_overrides['frozen'] = True + + # HACK 2: FastAPI is subclassing `FieldInfo` and historically expected the actual + # instance's type to be preserved when constructing new models with its subclasses as assignments. + # This code is never reached by Pydantic itself, and in an ideal world this shouldn't be necessary. + if not metadata and isinstance(default, FieldInfo) and type(default) is not FieldInfo: + field_info = default._copy() + field_info._attributes_set.update(attr_overrides) + for k, v in attr_overrides.items(): + setattr(field_info, k, v) + return field_info + + if isinstance(default, FieldInfo): + default_copy = default._copy() # Copy unnecessary when we remove HACK 1. + prepend_metadata = default_copy.metadata + default_copy.metadata = [] + metadata = metadata + [default_copy] + if 'init_var' in inspected_ann.qualifiers: + # Only relevant for dataclasses, when `f: InitVar[] = Field(...)` + # is used: + attr_overrides['init_var'] = True + elif isinstance(default, dataclasses.Field): + from_field = FieldInfo._from_dataclass_field(default) + prepend_metadata = from_field.metadata # Unnecessary when we remove HACK 1. + from_field.metadata = [] + metadata = metadata + [from_field] + if 'init_var' in inspected_ann.qualifiers: + attr_overrides['init_var'] = True + if (init := getattr(default, 'init', None)) is not None: + attr_overrides['init'] = init + if (kw_only := getattr(default, 'kw_only', None)) is not None: + attr_overrides['kw_only'] = kw_only + else: + # `default` is the actual default value + attr_overrides['default'] = default + + field_info = FieldInfo._construct( + prepend_metadata + metadata if prepend_metadata is not None else metadata, **attr_overrides + ) + field_info._qualifiers = inspected_ann.qualifiers + field_info._final = True + return field_info + + @classmethod + def _construct(cls, metadata: list[Any], **attr_overrides: Any) -> Self: + """Construct the final `FieldInfo` instance, by merging the possibly existing `FieldInfo` instances from the metadata. + + With the following example: + + ```python {test="skip" lint="skip"} + class Model(BaseModel): + f: Annotated[int, Gt(1), Field(description='desc', lt=2)] + ``` + + `metadata` refers to the metadata elements of the `Annotated` form. This metadata is iterated over from left to right: + + - If the element is a `Field()` function (which is itself a `FieldInfo` instance), the field attributes (such as + `description`) are saved to be set on the final `FieldInfo` instance. + On the other hand, some kwargs (such as `lt`) are stored as `metadata` (see `FieldInfo.__init__()`, calling + `FieldInfo._collect_metadata()`). In this case, the final metadata list is extended with the one from this instance. + - Else, the element is considered as a single metadata object, and is appended to the final metadata list. + + Args: + metadata: The list of metadata elements to merge together. If the `FieldInfo` instance to be constructed is for + a field with an assigned `Field()`, this `Field()` assignment should be added as the last element of the + provided metadata. + **attr_overrides: Extra attributes that should be set on the final merged `FieldInfo` instance. + + Returns: + The final merged `FieldInfo` instance. + """ + merged_metadata: list[Any] = [] + merged_kwargs: dict[str, Any] = {} + + for meta in metadata: + if isinstance(meta, FieldInfo): + merged_metadata.extend(meta.metadata) + + new_js_extra: JsonDict | None = None + current_js_extra = meta.json_schema_extra + if current_js_extra is not None and 'json_schema_extra' in merged_kwargs: + # We need to merge `json_schema_extra`'s: + existing_js_extra = merged_kwargs['json_schema_extra'] + if isinstance(existing_js_extra, dict): + if isinstance(current_js_extra, dict): + new_js_extra = { + **existing_js_extra, + **current_js_extra, + } + elif callable(current_js_extra): + warn( + 'Composing `dict` and `callable` type `json_schema_extra` is not supported. ' + 'The `callable` type is being ignored. ' + "If you'd like support for this behavior, please open an issue on pydantic.", + UserWarning, + ) + elif callable(existing_js_extra) and isinstance(current_js_extra, dict): + warn( + 'Composing `dict` and `callable` type `json_schema_extra` is not supported. ' + 'The `callable` type is being ignored. ' + "If you'd like support for this behavior, please open an issue on pydantic.", + UserWarning, + ) + + # HACK: It is common for users to define "make model partial" (or similar) utilities, that + # convert all model fields to be optional (i.e. have a default value). To do so, they mutate + # each `FieldInfo` instance from `model_fields` to set a `default`, and use `create_model()` + # with `Annotated[ | None, mutated_field_info]`` as an annotation. However, such + # mutations (by doing simple assignments) are only accidentally working, because we also + # need to track attributes explicitly set in `_attributes_set` (relying on default values for + # each attribute is *not* enough, for instance with `Annotated[int, Field(alias='a'), Field(alias=None)]` + # the resulting `FieldInfo` should have `alias=None`). + # To mitigate this, we add a special case when a "final" `FieldInfo` instance (that is an instance coming + # from `model_fields`) is used in annotated metadata (or assignment). In this case, we assume *all* attributes + # were explicitly set, and as such we use all of them (and this will correctly pick up the mutations). + # In theory, this shouldn't really be supported, you are only supposed to use the `Field()` function, not + # a `FieldInfo` instance directly (granted, `Field()` returns a `FieldInfo`, see + # https://github.com/pydantic/pydantic/issues/11122): + if meta._final: + merged_kwargs.update({attr: getattr(meta, attr) for attr in _Attrs}) + else: + merged_kwargs.update(meta._attributes_set) + + if new_js_extra is not None: + merged_kwargs['json_schema_extra'] = new_js_extra + elif typing_objects.is_deprecated(meta): + merged_kwargs['deprecated'] = meta + else: + merged_metadata.append(meta) + + merged_kwargs.update(attr_overrides) + merged_field_info = cls(**merged_kwargs) + merged_field_info.metadata = merged_metadata + return merged_field_info + + @staticmethod + @typing_extensions.deprecated( + "The 'merge_field_infos()' method is deprecated and will be removed in a future version. " + 'If you relied on this method, please open an issue in the Pydantic issue tracker.', + category=None, + ) + def merge_field_infos(*field_infos: FieldInfo, **overrides: Any) -> FieldInfo: + """Merge `FieldInfo` instances keeping only explicitly set attributes. + + Later `FieldInfo` instances override earlier ones. + + Returns: + FieldInfo: A merged FieldInfo instance. + """ + if len(field_infos) == 1: + # No merging necessary, but we still need to make a copy and apply the overrides + field_info = field_infos[0]._copy() + field_info._attributes_set.update(overrides) + + default_override = overrides.pop('default', PydanticUndefined) + if default_override is Ellipsis: + default_override = PydanticUndefined + if default_override is not PydanticUndefined: + field_info.default = default_override + + for k, v in overrides.items(): + setattr(field_info, k, v) + return field_info # type: ignore + + merged_field_info_kwargs: dict[str, Any] = {} + metadata = {} + for field_info in field_infos: + attributes_set = field_info._attributes_set.copy() + + try: + json_schema_extra = attributes_set.pop('json_schema_extra') + existing_json_schema_extra = merged_field_info_kwargs.get('json_schema_extra') + + if existing_json_schema_extra is None: + merged_field_info_kwargs['json_schema_extra'] = json_schema_extra + if isinstance(existing_json_schema_extra, dict): + if isinstance(json_schema_extra, dict): + merged_field_info_kwargs['json_schema_extra'] = { + **existing_json_schema_extra, + **json_schema_extra, + } + if callable(json_schema_extra): + warn( + 'Composing `dict` and `callable` type `json_schema_extra` is not supported.' + 'The `callable` type is being ignored.' + "If you'd like support for this behavior, please open an issue on pydantic.", + PydanticJsonSchemaWarning, + ) + elif callable(json_schema_extra): + # if ever there's a case of a callable, we'll just keep the last json schema extra spec + merged_field_info_kwargs['json_schema_extra'] = json_schema_extra + except KeyError: + pass + + # later FieldInfo instances override everything except json_schema_extra from earlier FieldInfo instances + merged_field_info_kwargs.update(attributes_set) + + for x in field_info.metadata: + if not isinstance(x, FieldInfo): + metadata[type(x)] = x + + merged_field_info_kwargs.update(overrides) + field_info = FieldInfo(**merged_field_info_kwargs) + field_info.metadata = list(metadata.values()) + return field_info + + @staticmethod + def _from_dataclass_field(dc_field: DataclassField[Any]) -> FieldInfo: + """Return a new `FieldInfo` instance from a `dataclasses.Field` instance. + + Args: + dc_field: The `dataclasses.Field` instance to convert. + + Returns: + The corresponding `FieldInfo` instance. + + Raises: + TypeError: If any of the `FieldInfo` kwargs does not match the `dataclass.Field` kwargs. + """ + default = dc_field.default + if default is dataclasses.MISSING: + default = _Unset + + if dc_field.default_factory is dataclasses.MISSING: + default_factory = _Unset + else: + default_factory = dc_field.default_factory + + # use the `Field` function so in correct kwargs raise the correct `TypeError` + dc_field_metadata = {k: v for k, v in dc_field.metadata.items() if k in _FIELD_ARG_NAMES} + if sys.version_info >= (3, 14) and dc_field.doc is not None: + dc_field_metadata['description'] = dc_field.doc + return Field(default=default, default_factory=default_factory, repr=dc_field.repr, **dc_field_metadata) # pyright: ignore[reportCallIssue] + + @staticmethod + def _collect_metadata(kwargs: dict[str, Any]) -> list[Any]: + """Collect annotations from kwargs. + + Args: + kwargs: Keyword arguments passed to the function. + + Returns: + A list of metadata objects - a combination of `annotated_types.BaseMetadata` and + `PydanticMetadata`. + """ + metadata: list[Any] = [] + general_metadata = {} + for key, value in list(kwargs.items()): + try: + marker = FieldInfo.metadata_lookup[key] + except KeyError: + continue + + del kwargs[key] + if value is not None: + if marker is None: + general_metadata[key] = value + else: + metadata.append(marker(value)) + if general_metadata: + metadata.append(_fields.pydantic_general_metadata(**general_metadata)) + return metadata + + @property + def deprecation_message(self) -> str | None: + """The deprecation message to be emitted, or `None` if not set.""" + if self.deprecated is None: + return None + if isinstance(self.deprecated, bool): + return 'deprecated' if self.deprecated else None + return self.deprecated if isinstance(self.deprecated, str) else self.deprecated.message + + @property + def default_factory_takes_validated_data(self) -> bool | None: + """Whether the provided default factory callable has a validated data parameter. + + Returns `None` if no default factory is set. + """ + if self.default_factory is not None: + return _fields.takes_validated_data_argument(self.default_factory) + + @overload + def get_default( + self, *, call_default_factory: Literal[True], validated_data: dict[str, Any] | None = None + ) -> Any: ... + + @overload + def get_default(self, *, call_default_factory: Literal[False] = ...) -> Any: ... + + def get_default(self, *, call_default_factory: bool = False, validated_data: dict[str, Any] | None = None) -> Any: + """Get the default value. + + We expose an option for whether to call the default_factory (if present), as calling it may + result in side effects that we want to avoid. However, there are times when it really should + be called (namely, when instantiating a model via `model_construct`). + + Args: + call_default_factory: Whether to call the default factory or not. + validated_data: The already validated data to be passed to the default factory. + + Returns: + The default value, calling the default factory if requested or `PydanticUndefined` if not set. + """ + return _fields.resolve_default_value( + default=self.default, + default_factory=self.default_factory, + validated_data=validated_data, + call_default_factory=call_default_factory, + ) + + def is_required(self) -> bool: + """Check if the field is required (i.e., does not have a default value or factory). + + Returns: + `True` if the field is required, `False` otherwise. + """ + return self.default is PydanticUndefined and self.default_factory is None + + def rebuild_annotation(self) -> Any: + """Attempts to rebuild the original annotation for use in function signatures. + + If metadata is present, it adds it to the original annotation using + `Annotated`. Otherwise, it returns the original annotation as-is. + + Note that because the metadata has been flattened, the original annotation + may not be reconstructed exactly as originally provided, e.g. if the original + type had unrecognized annotations, or was annotated with a call to `pydantic.Field`. + + Returns: + The rebuilt annotation. + """ + if not self.metadata: + return self.annotation + else: + # Annotated arguments must be a tuple + return Annotated[(self.annotation, *self.metadata)] # type: ignore + + def apply_typevars_map( + self, + typevars_map: Mapping[TypeVar, Any] | None, + globalns: GlobalsNamespace | None = None, + localns: MappingNamespace | None = None, + ) -> None: + """Apply a `typevars_map` to the annotation. + + This method is used when analyzing parametrized generic types to replace typevars with their concrete types. + + This method applies the `typevars_map` to the annotation in place. + + Args: + typevars_map: A dictionary mapping type variables to their concrete types. + globalns: The globals namespace to use during type annotation evaluation. + localns: The locals namespace to use during type annotation evaluation. + + See Also: + pydantic._internal._generics.replace_types is used for replacing the typevars with + their concrete types. + """ + annotation = _generics.replace_types(self.annotation, typevars_map) + annotation, evaluated = _typing_extra.try_eval_type(annotation, globalns, localns) + self.annotation = annotation + if not evaluated: + self._complete = False + self._original_annotation = self.annotation + + def asdict(self) -> _FieldInfoAsDict: + """Return a dictionary representation of the `FieldInfo` instance. + + The returned value is a dictionary with three items: + + * `annotation`: The type annotation of the field. + * `metadata`: The metadata list. + * `attributes`: A mapping of the remaining `FieldInfo` attributes to their values (e.g. `alias`, `title`). + """ + return { + 'annotation': self.annotation, + 'metadata': self.metadata, + 'attributes': {attr: getattr(self, attr) for attr in _Attrs}, + } + + def _copy(self) -> Self: + """Return a copy of the `FieldInfo` instance.""" + # Note: we can't define a custom `__copy__()`, as `FieldInfo` is being subclassed + # by some third-party libraries with extra attributes defined (and as `FieldInfo` + # is slotted, we can't make a copy of the `__dict__`). + if type(self) is FieldInfo: + # Fast-path if the instance isn't a subclass (`copy.copy()` relies on pickling which is slower): + copied = FieldInfo.__new__(FieldInfo) + for attr_name in FieldInfo.__slots__: + setattr(copied, attr_name, getattr(self, attr_name)) + else: + copied = copy(self) + + for attr_name in ('metadata', '_attributes_set', '_qualifiers'): + # Apply "deep-copy" behavior on collections attributes: + setattr(copied, attr_name, getattr(copied, attr_name).copy()) + + return copied # pyright: ignore[reportReturnType] + + def __repr_args__(self) -> ReprArgs: + yield 'annotation', _repr.PlainRepr(_repr.display_as_type(self.annotation)) + yield 'required', self.is_required() + + for s in self.__slots__: + # TODO: properly make use of the protocol (https://rich.readthedocs.io/en/stable/pretty.html#rich-repr-protocol) + # By yielding a three-tuple: + if s in ( + 'annotation', + '_attributes_set', + '_qualifiers', + '_complete', + '_original_assignment', + '_original_annotation', + '_final', + ): + continue + elif s == 'metadata' and not self.metadata: + continue + elif s == 'repr' and self.repr is True: + continue + if s == 'frozen' and self.frozen is False: + continue + if s == 'validation_alias' and self.validation_alias == self.alias: + continue + if s == 'serialization_alias' and self.serialization_alias == self.alias: + continue + if s == 'default' and self.default is not PydanticUndefined: + yield 'default', self.default + elif s == 'default_factory' and self.default_factory is not None: + yield 'default_factory', _repr.PlainRepr(_repr.display_as_type(self.default_factory)) + else: + value = getattr(self, s) + if value is not None and value is not PydanticUndefined: + yield s, value + + +class _EmptyKwargs(TypedDict): + """This class exists solely to ensure that type checking warns about passing `**extra` in `Field`.""" + + +_Attrs = { + 'default': ..., + 'default_factory': None, + 'alias': None, + 'alias_priority': None, + 'validation_alias': None, + 'serialization_alias': None, + 'title': None, + 'field_title_generator': None, + 'description': None, + 'examples': None, + 'exclude': None, + 'exclude_if': None, + 'discriminator': None, + 'deprecated': None, + 'json_schema_extra': None, + 'frozen': None, + 'validate_default': None, + 'repr': True, + 'init': None, + 'init_var': None, + 'kw_only': None, +} + +_DefaultValues = { + **_Attrs, + 'kw_only': None, + 'pattern': None, + 'strict': None, + 'gt': None, + 'ge': None, + 'lt': None, + 'le': None, + 'multiple_of': None, + 'allow_inf_nan': None, + 'max_digits': None, + 'decimal_places': None, + 'min_length': None, + 'max_length': None, + 'coerce_numbers_to_str': None, +} + + +_T = TypeVar('_T') + + +# NOTE: Actual return type is 'FieldInfo', but we want to help type checkers +# to understand the magic that happens at runtime with the following overloads: +@overload # type hint the return value as `Any` to avoid type checking regressions when using `...`. +def Field( + default: ellipsis, # noqa: F821 # TODO: use `_typing_extra.EllipsisType` when we drop Py3.9 + *, + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: bool | None = _Unset, + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> Any: ... +@overload # `default` argument set, validate_default=True (no type checking on the default value) +def Field( + default: Any, + *, + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: Literal[True], + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> Any: ... +@overload # `default` argument set, validate_default=False or unset +def Field( + default: _T, + *, + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + # NOTE: to get proper type checking on `exclude_if`'s argument, we could use `_T` instead of `Any`. However, + # this requires (at least for pyright) adding an additional overload where `exclude_if` is required (otherwise + # `a: int = Field(default_factory=str)` results in a false negative). + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: Literal[False] = ..., + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> _T: ... +@overload # `default_factory` argument set, validate_default=True (no type checking on the default value) +def Field( # pyright: ignore[reportOverlappingOverload] + *, + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any], + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: Literal[True], + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> Any: ... +@overload # `default_factory` argument set, validate_default=False or unset +def Field( + *, + default_factory: Callable[[], _T] | Callable[[dict[str, Any]], _T], + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + # NOTE: to get proper type checking on `exclude_if`'s argument, we could use `_T` instead of `Any`. However, + # this requires (at least for pyright) adding an additional overload where `exclude_if` is required (otherwise + # `a: int = Field(default_factory=str)` results in a false negative). + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: Literal[False] | None = _Unset, + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> _T: ... +@overload +def Field( # No default set + *, + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: bool | None = _Unset, + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> Any: ... +def Field( # noqa: C901 + default: Any = PydanticUndefined, + *, + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None = _Unset, + alias: str | None = _Unset, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = _Unset, + serialization_alias: str | None = _Unset, + title: str | None = _Unset, + field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, + description: str | None = _Unset, + examples: list[Any] | None = _Unset, + exclude: bool | None = _Unset, + exclude_if: Callable[[Any], bool] | None = _Unset, + discriminator: str | types.Discriminator | None = _Unset, + deprecated: Deprecated | str | bool | None = _Unset, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + frozen: bool | None = _Unset, + validate_default: bool | None = _Unset, + repr: bool = _Unset, + init: bool | None = _Unset, + init_var: bool | None = _Unset, + kw_only: bool | None = _Unset, + pattern: str | re.Pattern[str] | None = _Unset, + strict: bool | None = _Unset, + coerce_numbers_to_str: bool | None = _Unset, + gt: annotated_types.SupportsGt | None = _Unset, + ge: annotated_types.SupportsGe | None = _Unset, + lt: annotated_types.SupportsLt | None = _Unset, + le: annotated_types.SupportsLe | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + min_length: int | None = _Unset, + max_length: int | None = _Unset, + union_mode: Literal['smart', 'left_to_right'] = _Unset, + fail_fast: bool | None = _Unset, + **extra: Unpack[_EmptyKwargs], +) -> Any: + """!!! abstract "Usage Documentation" + [Fields](../concepts/fields.md) + + Create a field for objects that can be configured. + + Used to provide extra information about a field, either for the model schema or complex validation. Some arguments + apply only to number fields (`int`, `float`, `Decimal`) and some apply only to `str`. + + Note: + - Any `_Unset` objects will be replaced by the corresponding value defined in the `_DefaultValues` dictionary. If a key for the `_Unset` object is not found in the `_DefaultValues` dictionary, it will default to `None` + + Args: + default: Default value if the field is not set. + default_factory: A callable to generate the default value. The callable can either take 0 arguments + (in which case it is called as is) or a single argument containing the already validated data. + alias: The name to use for the attribute when validating or serializing by alias. + This is often used for things like converting between snake and camel case. + alias_priority: Priority of the alias. This affects whether an alias generator is used. + validation_alias: Like `alias`, but only affects validation, not serialization. + serialization_alias: Like `alias`, but only affects serialization, not validation. + title: Human-readable title. + field_title_generator: A callable that takes a field name and returns title for it. + description: Human-readable description. + examples: Example values for this field. + exclude: Whether to exclude the field from the model serialization. + exclude_if: A callable that determines whether to exclude a field during serialization based on its value. + discriminator: Field name or Discriminator for discriminating the type in a tagged union. + deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, + or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. + json_schema_extra: A dict or callable to provide extra JSON schema properties. + frozen: Whether the field is frozen. If true, attempts to change the value on an instance will raise an error. + validate_default: If `True`, apply validation to the default value every time you create an instance. + Otherwise, for performance reasons, the default value of the field is trusted and not validated. + repr: A boolean indicating whether to include the field in the `__repr__` output. + init: Whether the field should be included in the constructor of the dataclass. + (Only applies to dataclasses.) + init_var: Whether the field should _only_ be included in the constructor of the dataclass. + (Only applies to dataclasses.) + kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass. + (Only applies to dataclasses.) + coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). + strict: If `True`, strict validation is applied to the field. + See [Strict Mode](../concepts/strict_mode.md) for details. + gt: Greater than. If set, value must be greater than this. Only applicable to numbers. + ge: Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers. + lt: Less than. If set, value must be less than this. Only applicable to numbers. + le: Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers. + multiple_of: Value must be a multiple of this. Only applicable to numbers. + min_length: Minimum length for iterables. + max_length: Maximum length for iterables. + pattern: Pattern for strings (a regular expression). + allow_inf_nan: Allow `inf`, `-inf`, `nan`. Only applicable to float and [`Decimal`][decimal.Decimal] numbers. + max_digits: Maximum number of allow digits for strings. + decimal_places: Maximum number of decimal places allowed for numbers. + union_mode: The strategy to apply when validating a union. Can be `smart` (the default), or `left_to_right`. + See [Union Mode](../concepts/unions.md#union-modes) for details. + fail_fast: If `True`, validation will stop on the first error. If `False`, all validation errors will be collected. + This option can be applied only to iterable types (list, tuple, set, and frozenset). + extra: (Deprecated) Extra fields that will be included in the JSON schema. + + !!! warning Deprecated + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + + Returns: + A new [`FieldInfo`][pydantic.fields.FieldInfo]. The return annotation is `Any` so `Field` can be used on + type-annotated fields without causing a type error. + """ + # Check deprecated and removed params from V1. This logic should eventually be removed. + const = extra.pop('const', None) # type: ignore + if const is not None: + raise PydanticUserError('`const` is removed, use `Literal` instead', code='removed-kwargs') + + min_items = extra.pop('min_items', None) # type: ignore + if min_items is not None: + warn( + '`min_items` is deprecated and will be removed, use `min_length` instead', + PydanticDeprecatedSince20, + stacklevel=2, + ) + if min_length in (None, _Unset): + min_length = min_items # type: ignore + + max_items = extra.pop('max_items', None) # type: ignore + if max_items is not None: + warn( + '`max_items` is deprecated and will be removed, use `max_length` instead', + PydanticDeprecatedSince20, + stacklevel=2, + ) + if max_length in (None, _Unset): + max_length = max_items # type: ignore + + unique_items = extra.pop('unique_items', None) # type: ignore + if unique_items is not None: + raise PydanticUserError( + ( + '`unique_items` is removed, use `Set` instead' + '(this feature is discussed in https://github.com/pydantic/pydantic-core/issues/296)' + ), + code='removed-kwargs', + ) + + allow_mutation = extra.pop('allow_mutation', None) # type: ignore + if allow_mutation is not None: + warn( + '`allow_mutation` is deprecated and will be removed. use `frozen` instead', + PydanticDeprecatedSince20, + stacklevel=2, + ) + if allow_mutation is False: + frozen = True + + regex = extra.pop('regex', None) # type: ignore + if regex is not None: + raise PydanticUserError('`regex` is removed. use `pattern` instead', code='removed-kwargs') + + if extra: + warn( + 'Using extra keyword arguments on `Field` is deprecated and will be removed.' + ' Use `json_schema_extra` instead.' + f' (Extra keys: {", ".join(k.__repr__() for k in extra.keys())})', + PydanticDeprecatedSince20, + stacklevel=2, + ) + if not json_schema_extra or json_schema_extra is _Unset: + json_schema_extra = extra # type: ignore + + if ( + validation_alias + and validation_alias is not _Unset + and not isinstance(validation_alias, (str, AliasChoices, AliasPath)) + ): + raise TypeError('Invalid `validation_alias` type. it should be `str`, `AliasChoices`, or `AliasPath`') + + if serialization_alias in (_Unset, None) and isinstance(alias, str): + serialization_alias = alias + + if validation_alias in (_Unset, None): + validation_alias = alias + + include = extra.pop('include', None) # type: ignore + if include is not None: + warn( + '`include` is deprecated and does nothing. It will be removed, use `exclude` instead', + PydanticDeprecatedSince20, + stacklevel=2, + ) + + return FieldInfo.from_field( + default, + default_factory=default_factory, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + field_title_generator=field_title_generator, + description=description, + examples=examples, + exclude=exclude, + exclude_if=exclude_if, + discriminator=discriminator, + deprecated=deprecated, + json_schema_extra=json_schema_extra, + frozen=frozen, + pattern=pattern, + validate_default=validate_default, + repr=repr, + init=init, + init_var=init_var, + kw_only=kw_only, + coerce_numbers_to_str=coerce_numbers_to_str, + strict=strict, + gt=gt, + ge=ge, + lt=lt, + le=le, + multiple_of=multiple_of, + min_length=min_length, + max_length=max_length, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + union_mode=union_mode, + fail_fast=fail_fast, + ) + + +_FIELD_ARG_NAMES = set(inspect.signature(Field).parameters) +_FIELD_ARG_NAMES.remove('extra') # do not include the varkwargs parameter + + +class ModelPrivateAttr(_repr.Representation): + """A descriptor for private attributes in class models. + + !!! warning + You generally shouldn't be creating `ModelPrivateAttr` instances directly, instead use + `pydantic.fields.PrivateAttr`. (This is similar to `FieldInfo` vs. `Field`.) + + Attributes: + default: The default value of the attribute if not provided. + default_factory: A callable to generate the default value. The callable can either take 0 arguments + (in which case it is called as is) or a single argument containing the validated data (the model's + [`__dict__`][object.__dict__]) and the already initialized private attributes. + """ + + __slots__ = ('default', 'default_factory') + + def __init__( + self, + default: Any = PydanticUndefined, + *, + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None = None, + ) -> None: + if default is Ellipsis: + self.default = PydanticUndefined + else: + self.default = default + self.default_factory = default_factory + + if not TYPE_CHECKING: + # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access + + def __getattr__(self, item: str) -> Any: + """This function improves compatibility with custom descriptors by ensuring delegation happens + as expected when the default value of a private attribute is a descriptor. + """ + if item in {'__get__', '__set__', '__delete__'}: + if hasattr(self.default, item): + return getattr(self.default, item) + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') + + def __set_name__(self, cls: type[Any], name: str) -> None: + """Preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487.""" + default = self.default + if default is PydanticUndefined: + return + set_name = getattr(default, '__set_name__', None) + if callable(set_name): + set_name(cls, name) + + @property + def default_factory_takes_validated_data(self) -> bool | None: + """Whether the provided default factory callable has a validated data parameter. + + Returns `None` if no default factory is set. + """ + if self.default_factory is not None: + return _fields.takes_validated_data_argument(self.default_factory) + + @overload + def get_default( + self, *, call_default_factory: Literal[True], validated_data: dict[str, Any] | None = None + ) -> Any: ... + + @overload + def get_default(self, *, call_default_factory: Literal[False] = ...) -> Any: ... + + def get_default(self, *, call_default_factory: bool = False, validated_data: dict[str, Any] | None = None) -> Any: + """Get the default value. + + We expose an option for whether to call the default_factory (if present), as calling it may + result in side effects that we want to avoid. However, there are times when it really should + be called (namely, when instantiating a model via `model_construct`). + + Args: + call_default_factory: Whether to call the default factory or not. + validated_data: The already validated data to be passed to the default factory. + + Returns: + The default value, calling the default factory if requested or `None` if not set. + """ + return _fields.resolve_default_value( + default=self.default, + default_factory=self.default_factory, + validated_data=validated_data, + call_default_factory=call_default_factory, + ) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) and (self.default, self.default_factory) == ( + other.default, + other.default_factory, + ) + + +# NOTE: Actual return type is 'ModelPrivateAttr', but we want to help type checkers +# to understand the magic that happens at runtime. +@overload # `default` argument set +def PrivateAttr( + default: _T, + *, + init: Literal[False] = False, +) -> _T: ... +@overload # `default_factory` argument set +def PrivateAttr( + *, + default_factory: Callable[[], _T] | Callable[[dict[str, Any]], _T], + init: Literal[False] = False, +) -> _T: ... +@overload # No default set +def PrivateAttr( + *, + init: Literal[False] = False, +) -> Any: ... +def PrivateAttr( + default: Any = PydanticUndefined, + *, + default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None = None, + init: Literal[False] = False, +) -> Any: + """!!! abstract "Usage Documentation" + [Private Model Attributes](../concepts/models.md#private-model-attributes) + + Indicates that an attribute is intended for private use and not handled during normal validation/serialization. + + Private attributes are not validated by Pydantic, so it's up to you to ensure they are used in a type-safe manner. + + Private attributes are stored in `__private_attributes__` on the model. + + Args: + default: The attribute's default value. Defaults to Undefined. + default_factory: A callable to generate the default value. The callable can either take 0 arguments + (in which case it is called as is) or a single argument containing the validated data (the model's + [`__dict__`][object.__dict__]) and the already initialized private attributes. + If both `default` and `default_factory` are set, an error will be raised. + init: Whether the attribute should be included in the constructor of the dataclass. Always `False`. + + Returns: + An instance of [`ModelPrivateAttr`][pydantic.fields.ModelPrivateAttr] class. + + Raises: + ValueError: If both `default` and `default_factory` are set. + """ + if default is not PydanticUndefined and default_factory is not None: + raise TypeError('cannot specify both default and default_factory') + + return ModelPrivateAttr( + default, + default_factory=default_factory, + ) + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class ComputedFieldInfo: + """A container for data from `@computed_field` so that we can access it while building the pydantic-core schema. + + Attributes: + decorator_repr: A class variable representing the decorator string, '@computed_field'. + wrapped_property: The wrapped computed field property. + return_type: The type of the computed field property's return value. + alias: The alias of the property to be used during serialization. + alias_priority: The priority of the alias. This affects whether an alias generator is used. + title: Title of the computed field to include in the serialization JSON schema. + field_title_generator: A callable that takes a field name and returns title for it. + description: Description of the computed field to include in the serialization JSON schema. + deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, + or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. + examples: Example values of the computed field to include in the serialization JSON schema. + json_schema_extra: A dict or callable to provide extra JSON schema properties. + repr: A boolean indicating whether to include the field in the __repr__ output. + """ + + decorator_repr: ClassVar[str] = '@computed_field' + wrapped_property: property + return_type: Any + alias: str | None + alias_priority: int | None + exclude_if: Callable[[Any], bool] | None + title: str | None + field_title_generator: Callable[[str, ComputedFieldInfo], str] | None + description: str | None + deprecated: Deprecated | str | bool | None + examples: list[Any] | None + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + repr: bool + # NOTE: if you add a new field, add it to the `__copy__()` implementation. + + def __copy__(self) -> Self: + return type(self)( + wrapped_property=self.wrapped_property, + return_type=self.return_type, + alias=self.alias, + alias_priority=self.alias_priority, + exclude_if=self.exclude_if, + title=self.title, + field_title_generator=self.field_title_generator, + description=self.description, + deprecated=self.deprecated, + examples=self.examples.copy() if isinstance(self.examples, list) else self.examples, + json_schema_extra=self.json_schema_extra.copy() + if isinstance(self.json_schema_extra, dict) + else self.json_schema_extra, + repr=self.repr, + ) + + @property + def deprecation_message(self) -> str | None: + """The deprecation message to be emitted, or `None` if not set.""" + if self.deprecated is None: + return None + if isinstance(self.deprecated, bool): + return 'deprecated' if self.deprecated else None + return self.deprecated if isinstance(self.deprecated, str) else self.deprecated.message + + def _update_from_config(self, config_wrapper: ConfigWrapper, name: str) -> None: + """Update the instance from the configuration set on the class this computed field belongs to.""" + title_generator = self.field_title_generator or config_wrapper.field_title_generator + if title_generator is not None and self.title is None: + self.title = title_generator(name, self) + if config_wrapper.alias_generator is not None: + self._apply_alias_generator(config_wrapper.alias_generator, name) + + def _apply_alias_generator(self, alias_generator: Callable[[str], str] | AliasGenerator, name: str) -> None: + """Apply an alias generator to aliases if appropriate. + + Args: + alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance. + name: The name of the computed field from which to generate the alias. + """ + # Apply an alias_generator if + # 1. An alias is not specified + # 2. An alias is specified, but the priority is <= 1 + + if self.alias_priority is None or self.alias_priority <= 1 or self.alias is None: + alias, _, serialization_alias = None, None, None + + if isinstance(alias_generator, AliasGenerator): + alias, _, serialization_alias = alias_generator.generate_aliases(name) + elif callable(alias_generator): + alias = alias_generator(name) + + # if priority is not set, we set to 1 + # which supports the case where the alias_generator from a child class is used + # to generate an alias for a field in a parent class + if self.alias_priority is None or self.alias_priority <= 1: + self.alias_priority = 1 + + # if the priority is 1, then we set the aliases to the generated alias + # note that we use the serialization_alias with priority over alias, as computed_field + # aliases are used for serialization only (not validation) + if self.alias_priority == 1: + self.alias = _utils.get_first_not_none(serialization_alias, alias) + + +def _wrapped_property_is_private(property_: cached_property | property) -> bool: # type: ignore + """Returns true if provided property is private, False otherwise.""" + wrapped_name: str = '' + + if isinstance(property_, property): + wrapped_name = getattr(property_.fget, '__name__', '') + elif isinstance(property_, cached_property): # type: ignore + wrapped_name = getattr(property_.func, '__name__', '') # type: ignore + + return wrapped_name.startswith('_') and not wrapped_name.startswith('__') + + +# this should really be `property[T], cached_property[T]` but property is not generic unlike cached_property +# See https://github.com/python/typing/issues/985 and linked issues +PropertyT = TypeVar('PropertyT') + + +@overload +def computed_field(func: PropertyT, /) -> PropertyT: ... + + +@overload +def computed_field( + *, + alias: str | None = None, + alias_priority: int | None = None, + exclude_if: Callable[[Any], bool] | None = None, + title: str | None = None, + field_title_generator: Callable[[str, ComputedFieldInfo], str] | None = None, + description: str | None = None, + deprecated: Deprecated | str | bool | None = None, + examples: list[Any] | None = None, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = None, + repr: bool = True, + return_type: Any = PydanticUndefined, +) -> Callable[[PropertyT], PropertyT]: ... + + +def computed_field( + func: PropertyT | None = None, + /, + *, + alias: str | None = None, + alias_priority: int | None = None, + exclude_if: Callable[[Any], bool] | None = None, + title: str | None = None, + field_title_generator: Callable[[str, ComputedFieldInfo], str] | None = None, + description: str | None = None, + deprecated: Deprecated | str | bool | None = None, + examples: list[Any] | None = None, + json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = None, + repr: bool | None = None, + return_type: Any = PydanticUndefined, +) -> PropertyT | Callable[[PropertyT], PropertyT]: + """!!! abstract "Usage Documentation" + [The `computed_field` decorator](../concepts/fields.md#the-computed_field-decorator) + + Decorator to include `property` and `cached_property` when serializing models or dataclasses. + + This is useful for fields that are computed from other fields, or for fields that are expensive to compute and should be cached. + + ```python + from pydantic import BaseModel, computed_field + + class Rectangle(BaseModel): + width: int + length: int + + @computed_field + @property + def area(self) -> int: + return self.width * self.length + + print(Rectangle(width=3, length=2).model_dump()) + #> {'width': 3, 'length': 2, 'area': 6} + ``` + + If applied to functions not yet decorated with `@property` or `@cached_property`, the function is + automatically wrapped with `property`. Although this is more concise, you will lose IntelliSense in your IDE, + and confuse static type checkers, thus explicit use of `@property` is recommended. + + !!! warning "Mypy Warning" + Even with the `@property` or `@cached_property` applied to your function before `@computed_field`, + mypy may throw a `Decorated property not supported` error. + See [mypy issue #1362](https://github.com/python/mypy/issues/1362), for more information. + To avoid this error message, add `# type: ignore[prop-decorator]` to the `@computed_field` line. + + [pyright](https://github.com/microsoft/pyright) supports `@computed_field` without error. + + ```python + import random + + from pydantic import BaseModel, computed_field + + class Square(BaseModel): + width: float + + @computed_field + def area(self) -> float: # converted to a `property` by `computed_field` + return round(self.width**2, 2) + + @area.setter + def area(self, new_area: float) -> None: + self.width = new_area**0.5 + + @computed_field(alias='the magic number', repr=False) + def random_number(self) -> int: + return random.randint(0, 1_000) + + square = Square(width=1.3) + + # `random_number` does not appear in representation + print(repr(square)) + #> Square(width=1.3, area=1.69) + + print(square.random_number) + #> 3 + + square.area = 4 + + print(square.model_dump_json(by_alias=True)) + #> {"width":2.0,"area":4.0,"the magic number":3} + ``` + + !!! warning "Overriding with `computed_field`" + You can't override a field from a parent class with a `computed_field` in the child class. + `mypy` complains about this behavior if allowed, and `dataclasses` doesn't allow this pattern either. + See the example below: + + ```python + from pydantic import BaseModel, computed_field + + class Parent(BaseModel): + a: str + + try: + + class Child(Parent): + @computed_field + @property + def a(self) -> str: + return 'new a' + + except TypeError as e: + print(e) + ''' + Field 'a' of class 'Child' overrides symbol of same name in a parent class. This override with a computed_field is incompatible. + ''' + ``` + + Private properties decorated with `@computed_field` have `repr=False` by default. + + ```python + from functools import cached_property + + from pydantic import BaseModel, computed_field + + class Model(BaseModel): + foo: int + + @computed_field + @cached_property + def _private_cached_property(self) -> int: + return -self.foo + + @computed_field + @property + def _private_property(self) -> int: + return -self.foo + + m = Model(foo=1) + print(repr(m)) + #> Model(foo=1) + ``` + + Args: + func: the function to wrap. + alias: alias to use when serializing this computed field, only used when `by_alias=True` + alias_priority: priority of the alias. This affects whether an alias generator is used + exclude_if: A callable that determines whether to exclude this computed field during serialization based on its value. + title: Title to use when including this computed field in JSON Schema + field_title_generator: A callable that takes a field name and returns title for it. + description: Description to use when including this computed field in JSON Schema, defaults to the function's + docstring + deprecated: A deprecation message (or an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport). + to be emitted when accessing the field. Or a boolean. This will automatically be set if the property is decorated with the + `deprecated` decorator. + examples: Example values to use when including this computed field in JSON Schema + json_schema_extra: A dict or callable to provide extra JSON schema properties. + repr: whether to include this computed field in model repr. + Default is `False` for private properties and `True` for public properties. + return_type: optional return for serialization logic to expect when serializing to JSON, if included + this must be correct, otherwise a `TypeError` is raised. + If you don't include a return type Any is used, which does runtime introspection to handle arbitrary + objects. + + Returns: + A proxy wrapper for the property. + """ + + def dec(f: Any) -> Any: + nonlocal description, deprecated, return_type, alias_priority + unwrapped = _decorators.unwrap_wrapped_function(f) + + if description is None and unwrapped.__doc__: + description = inspect.cleandoc(unwrapped.__doc__) + + if deprecated is None and hasattr(unwrapped, '__deprecated__'): + deprecated = unwrapped.__deprecated__ + + # if the function isn't already decorated with `@property` (or another descriptor), then we wrap it now + f = _decorators.ensure_property(f) + alias_priority = (alias_priority or 2) if alias is not None else None + + if repr is None: + repr_: bool = not _wrapped_property_is_private(property_=f) + else: + repr_ = repr + + dec_info = ComputedFieldInfo( + f, + return_type, + alias, + alias_priority, + exclude_if, + title, + field_title_generator, + description, + deprecated, + examples, + json_schema_extra, + repr_, + ) + return _decorators.PydanticDescriptorProxy(f, dec_info) + + if func is None: + return dec + else: + return dec(func) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py new file mode 100644 index 0000000..ed63336 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py @@ -0,0 +1,470 @@ +"""This module contains related classes and functions for serialization.""" + +from __future__ import annotations + +import dataclasses +from functools import partial, partialmethod +from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload + +from pydantic_core import PydanticUndefined, core_schema +from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed +from typing_extensions import TypeAlias + +from . import PydanticUndefinedAnnotation +from ._internal import _decorators, _internal_dataclass +from .annotated_handlers import GetCoreSchemaHandler +from .errors import PydanticUserError + + +@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) +class PlainSerializer: + """Plain serializers use a function to modify the output of serialization. + + This is particularly helpful when you want to customize the serialization for annotated types. + Consider an input of `list`, which will be serialized into a space-delimited string. + + ```python + from typing import Annotated + + from pydantic import BaseModel, PlainSerializer + + CustomStr = Annotated[ + list, PlainSerializer(lambda x: ' '.join(x), return_type=str) + ] + + class StudentModel(BaseModel): + courses: CustomStr + + student = StudentModel(courses=['Math', 'Chemistry', 'English']) + print(student.model_dump()) + #> {'courses': 'Math Chemistry English'} + ``` + + Attributes: + func: The serializer function. + return_type: The return type for the function. If omitted it will be inferred from the type annotation. + when_used: Determines when this serializer should be used. Accepts a string with values `'always'`, + `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'. + """ + + func: core_schema.SerializerFunction + return_type: Any = PydanticUndefined + when_used: WhenUsed = 'always' + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + """Gets the Pydantic core schema. + + Args: + source_type: The source type. + handler: The `GetCoreSchemaHandler` instance. + + Returns: + The Pydantic core schema. + """ + schema = handler(source_type) + if self.return_type is not PydanticUndefined: + return_type = self.return_type + else: + try: + # Do not pass in globals as the function could be defined in a different module. + # Instead, let `get_callable_return_type` infer the globals to use, but still pass + # in locals that may contain a parent/rebuild namespace: + return_type = _decorators.get_callable_return_type( + self.func, + localns=handler._get_types_namespace().locals, + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) + schema['serialization'] = core_schema.plain_serializer_function_ser_schema( + function=self.func, + info_arg=_decorators.inspect_annotated_serializer(self.func, 'plain'), + return_schema=return_schema, + when_used=self.when_used, + ) + return schema + + +@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) +class WrapSerializer: + """Wrap serializers receive the raw inputs along with a handler function that applies the standard serialization + logic, and can modify the resulting value before returning it as the final output of serialization. + + For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic. + + ```python + from datetime import datetime, timezone + from typing import Annotated, Any + + from pydantic import BaseModel, WrapSerializer + + class EventDatetime(BaseModel): + start: datetime + end: datetime + + def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]: + # Note that `handler` can actually help serialize the `value` for + # further custom serialization in case it's a subclass. + partial_result = handler(value, info) + if info.mode == 'json': + return { + k: datetime.fromisoformat(v).astimezone(timezone.utc) + for k, v in partial_result.items() + } + return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()} + + UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)] + + class EventModel(BaseModel): + event_datetime: UTCEventDatetime + + dt = EventDatetime( + start='2024-01-01T07:00:00-08:00', end='2024-01-03T20:00:00+06:00' + ) + event = EventModel(event_datetime=dt) + print(event.model_dump()) + ''' + { + 'event_datetime': { + 'start': datetime.datetime( + 2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc + ), + 'end': datetime.datetime( + 2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc + ), + } + } + ''' + + print(event.model_dump_json()) + ''' + {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}} + ''' + ``` + + Attributes: + func: The serializer function to be wrapped. + return_type: The return type for the function. If omitted it will be inferred from the type annotation. + when_used: Determines when this serializer should be used. Accepts a string with values `'always'`, + `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'. + """ + + func: core_schema.WrapSerializerFunction + return_type: Any = PydanticUndefined + when_used: WhenUsed = 'always' + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + """This method is used to get the Pydantic core schema of the class. + + Args: + source_type: Source type. + handler: Core schema handler. + + Returns: + The generated core schema of the class. + """ + schema = handler(source_type) + if self.return_type is not PydanticUndefined: + return_type = self.return_type + else: + try: + # Do not pass in globals as the function could be defined in a different module. + # Instead, let `get_callable_return_type` infer the globals to use, but still pass + # in locals that may contain a parent/rebuild namespace: + return_type = _decorators.get_callable_return_type( + self.func, + localns=handler._get_types_namespace().locals, + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e + + return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) + schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( + function=self.func, + info_arg=_decorators.inspect_annotated_serializer(self.func, 'wrap'), + return_schema=return_schema, + when_used=self.when_used, + ) + return schema + + +if TYPE_CHECKING: + _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]' + + FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial' + """A field serializer method or function in `plain` mode.""" + + FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial' + """A field serializer method or function in `wrap` mode.""" + + FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer' + """A field serializer method or function.""" + + _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer) + _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer) + + +@overload +def field_serializer( + field: str, + /, + *fields: str, + mode: Literal['wrap'], + return_type: Any = ..., + when_used: WhenUsed = ..., + check_fields: bool | None = ..., +) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... + + +@overload +def field_serializer( + field: str, + /, + *fields: str, + mode: Literal['plain'] = ..., + return_type: Any = ..., + when_used: WhenUsed = ..., + check_fields: bool | None = ..., +) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... + + +def field_serializer( # noqa: D417 + field: str, + /, + *fields: str, + mode: Literal['plain', 'wrap'] = 'plain', + # TODO PEP 747 (grep for 'return_type' on the whole code base): + return_type: Any = PydanticUndefined, + when_used: WhenUsed = 'always', + check_fields: bool | None = None, +) -> ( + Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT] + | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT] +): + """Decorator that enables custom field serialization. + + In the below example, a field of type `set` is used to mitigate duplication. A `field_serializer` is used to serialize the data as a sorted list. + + ```python + from pydantic import BaseModel, field_serializer + + class StudentModel(BaseModel): + name: str = 'Jane' + courses: set[str] + + @field_serializer('courses', when_used='json') + def serialize_courses_in_order(self, courses: set[str]): + return sorted(courses) + + student = StudentModel(courses={'Math', 'Chemistry', 'English'}) + print(student.model_dump_json()) + #> {"name":"Jane","courses":["Chemistry","English","Math"]} + ``` + + See [the usage documentation](../concepts/serialization.md#serializers) for more information. + + Four signatures are supported for the decorated serializer: + + - `(self, value: Any, info: FieldSerializationInfo)` + - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)` + - `(value: Any, info: SerializationInfo)` + - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)` + + Args: + *fields: The field names the serializer should apply to. + mode: The serialization mode. + + - `plain` means the function will be called instead of the default serialization logic, + - `wrap` means the function will be called with an argument to optionally call the + default serialization logic. + return_type: Optional return type for the function, if omitted it will be inferred from the type annotation. + when_used: Determines the serializer will be used for serialization. + check_fields: Whether to check that the fields actually exist on the model. + + Raises: + PydanticUserError: + - If the decorator is used without any arguments (at least one field name must be provided). + - If the provided field names are not strings. + """ + if callable(field) or isinstance(field, classmethod): + raise PydanticUserError( + 'The `@field_serializer` decorator cannot be used without arguments, at least one field must be provided. ' + "For example: `@field_serializer('', ...)`.", + code='decorator-missing-arguments', + ) + + fields = field, *fields + if not all(isinstance(field, str) for field in fields): + raise PydanticUserError( + 'The provided field names to the `@field_serializer` decorator should be strings. ' + "For example: `@field_serializer('', '', ...).`", + code='decorator-invalid-fields', + ) + + def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: + dec_info = _decorators.FieldSerializerDecoratorInfo( + fields=fields, + mode=mode, + return_type=return_type, + when_used=when_used, + check_fields=check_fields, + ) + return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType] + + return dec # pyright: ignore[reportReturnType] + + +if TYPE_CHECKING: + # The first argument in the following callables represent the `self` type: + + ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any] + """A model serializer method with the `info` argument, in `plain` mode.""" + + ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any] + """A model serializer method without the `info` argument, in `plain` mode.""" + + ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo' + """A model serializer method in `plain` mode.""" + + ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any] + """A model serializer method with the `info` argument, in `wrap` mode.""" + + ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any] + """A model serializer method without the `info` argument, in `wrap` mode.""" + + ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo' + """A model serializer method in `wrap` mode.""" + + ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer' + + _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer) + _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer) + + +@overload +def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... + + +@overload +def model_serializer( + *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ... +) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... + + +@overload +def model_serializer( + *, + mode: Literal['plain'] = ..., + when_used: WhenUsed = 'always', + return_type: Any = ..., +) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... + + +def model_serializer( + f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None, + /, + *, + mode: Literal['plain', 'wrap'] = 'plain', + when_used: WhenUsed = 'always', + return_type: Any = PydanticUndefined, +) -> ( + _ModelPlainSerializerT + | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT] + | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT] +): + """Decorator that enables custom model serialization. + + This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields. + + An example would be to serialize temperature to the same temperature scale, such as degrees Celsius. + + ```python + from typing import Literal + + from pydantic import BaseModel, model_serializer + + class TemperatureModel(BaseModel): + unit: Literal['C', 'F'] + value: int + + @model_serializer() + def serialize_model(self): + if self.unit == 'F': + return {'unit': 'C', 'value': int((self.value - 32) / 1.8)} + return {'unit': self.unit, 'value': self.value} + + temperature = TemperatureModel(unit='F', value=212) + print(temperature.model_dump()) + #> {'unit': 'C', 'value': 100} + ``` + + Two signatures are supported for `mode='plain'`, which is the default: + + - `(self)` + - `(self, info: SerializationInfo)` + + And two other signatures for `mode='wrap'`: + + - `(self, nxt: SerializerFunctionWrapHandler)` + - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)` + + See [the usage documentation](../concepts/serialization.md#serializers) for more information. + + Args: + f: The function to be decorated. + mode: The serialization mode. + + - `'plain'` means the function will be called instead of the default serialization logic + - `'wrap'` means the function will be called with an argument to optionally call the default + serialization logic. + when_used: Determines when this serializer should be used. + return_type: The return type for the function. If omitted it will be inferred from the type annotation. + + Returns: + The decorator function. + """ + + def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: + dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) + return _decorators.PydanticDescriptorProxy(f, dec_info) + + if f is None: + return dec # pyright: ignore[reportReturnType] + else: + return dec(f) # pyright: ignore[reportReturnType] + + +AnyType = TypeVar('AnyType') + + +if TYPE_CHECKING: + SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str] + """Annotation used to mark a type as having duck-typing serialization behavior. + + See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. + """ +else: + + @dataclasses.dataclass(**_internal_dataclass.slots_true) + class SerializeAsAny: + """Annotation used to mark a type as having duck-typing serialization behavior. + + See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. + """ + + def __class_getitem__(cls, item: Any) -> Any: + return Annotated[item, SerializeAsAny()] + + def __get_pydantic_core_schema__( + self, source_type: Any, handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + schema = handler(source_type) + schema_to_update = schema + while schema_to_update['type'] == 'definitions': + schema_to_update = schema_to_update.copy() + schema_to_update = schema_to_update['schema'] + schema_to_update['serialization'] = core_schema.simple_ser_schema('any') + return schema + + __hash__ = object.__hash__ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py new file mode 100644 index 0000000..558e99c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py @@ -0,0 +1,889 @@ +"""This module contains related classes and functions for validation.""" + +from __future__ import annotations as _annotations + +import dataclasses +import sys +import warnings +from functools import partialmethod +from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, Union, cast, overload + +from pydantic_core import PydanticUndefined, core_schema +from typing_extensions import Self, TypeAlias + +from ._internal import _decorators, _generics, _internal_dataclass +from .annotated_handlers import GetCoreSchemaHandler +from .errors import PydanticUserError +from .version import version_short +from .warnings import ArbitraryTypeWarning, PydanticDeprecatedSince212 + +if sys.version_info < (3, 11): + from typing_extensions import Protocol +else: + from typing import Protocol + +_inspect_validator = _decorators.inspect_validator + + +@dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) +class AfterValidator: + """!!! abstract "Usage Documentation" + [field *after* validators](../concepts/validators.md#field-after-validator) + + A metadata class that indicates that a validation should be applied **after** the inner validation logic. + + Attributes: + func: The validator function. + + Example: + ```python + from typing import Annotated + + from pydantic import AfterValidator, BaseModel, ValidationError + + MyInt = Annotated[int, AfterValidator(lambda v: v + 1)] + + class Model(BaseModel): + a: MyInt + + print(Model(a=1).a) + #> 2 + + try: + Model(a='a') + except ValidationError as e: + print(e.json(indent=2)) + ''' + [ + { + "type": "int_parsing", + "loc": [ + "a" + ], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "a", + "url": "https://errors.pydantic.dev/2/v/int_parsing" + } + ] + ''' + ``` + """ + + func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source_type) + info_arg = _inspect_validator(self.func, mode='after', type='field') + if info_arg: + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_after_validator_function(func, schema=schema) + else: + func = cast(core_schema.NoInfoValidatorFunction, self.func) + return core_schema.no_info_after_validator_function(func, schema=schema) + + @classmethod + def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: + return cls(func=decorator.func) + + +@dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) +class BeforeValidator: + """!!! abstract "Usage Documentation" + [field *before* validators](../concepts/validators.md#field-before-validator) + + A metadata class that indicates that a validation should be applied **before** the inner validation logic. + + Attributes: + func: The validator function. + json_schema_input_type: The input type used to generate the appropriate + JSON Schema (in validation mode). The actual input type is `Any`. + + Example: + ```python + from typing import Annotated + + from pydantic import BaseModel, BeforeValidator + + MyInt = Annotated[int, BeforeValidator(lambda v: v + 1)] + + class Model(BaseModel): + a: MyInt + + print(Model(a=1).a) + #> 2 + + try: + Model(a='a') + except TypeError as e: + print(e) + #> can only concatenate str (not "int") to str + ``` + """ + + func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction + json_schema_input_type: Any = PydanticUndefined + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source_type) + input_schema = ( + None + if self.json_schema_input_type is PydanticUndefined + else handler.generate_schema(self.json_schema_input_type) + ) + + info_arg = _inspect_validator(self.func, mode='before', type='field') + if info_arg: + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_before_validator_function( + func, + schema=schema, + json_schema_input_schema=input_schema, + ) + else: + func = cast(core_schema.NoInfoValidatorFunction, self.func) + return core_schema.no_info_before_validator_function( + func, schema=schema, json_schema_input_schema=input_schema + ) + + @classmethod + def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: + return cls( + func=decorator.func, + json_schema_input_type=decorator.info.json_schema_input_type, + ) + + +@dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) +class PlainValidator: + """!!! abstract "Usage Documentation" + [field *plain* validators](../concepts/validators.md#field-plain-validator) + + A metadata class that indicates that a validation should be applied **instead** of the inner validation logic. + + !!! note + Before v2.9, `PlainValidator` wasn't always compatible with JSON Schema generation for `mode='validation'`. + You can now use the `json_schema_input_type` argument to specify the input type of the function + to be used in the JSON schema when `mode='validation'` (the default). See the example below for more details. + + Attributes: + func: The validator function. + json_schema_input_type: The input type used to generate the appropriate + JSON Schema (in validation mode). The actual input type is `Any`. + + Example: + ```python + from typing import Annotated, Union + + from pydantic import BaseModel, PlainValidator + + def validate(v: object) -> int: + if not isinstance(v, (int, str)): + raise ValueError(f'Expected int or str, got {type(v)}') + + return int(v) + 1 + + MyInt = Annotated[ + int, + PlainValidator(validate, json_schema_input_type=Union[str, int]), # (1)! + ] + + class Model(BaseModel): + a: MyInt + + print(Model(a='1').a) + #> 2 + + print(Model(a=1).a) + #> 2 + ``` + + 1. In this example, we've specified the `json_schema_input_type` as `Union[str, int]` which indicates to the JSON schema + generator that in validation mode, the input type for the `a` field can be either a [`str`][] or an [`int`][]. + """ + + func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction + json_schema_input_type: Any = Any + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + # Note that for some valid uses of PlainValidator, it is not possible to generate a core schema for the + # source_type, so calling `handler(source_type)` will error, which prevents us from generating a proper + # serialization schema. To work around this for use cases that will not involve serialization, we simply + # catch any PydanticSchemaGenerationError that may be raised while attempting to build the serialization schema + # and abort any attempts to handle special serialization. + from pydantic import PydanticSchemaGenerationError + + try: + schema = handler(source_type) + # TODO if `schema['serialization']` is one of `'include-exclude-dict/sequence', + # schema validation will fail. That's why we use 'type ignore' comments below. + serialization = schema.get( + 'serialization', + core_schema.wrap_serializer_function_ser_schema( + function=lambda v, h: h(v), + schema=schema, + return_schema=handler.generate_schema(source_type), + ), + ) + except PydanticSchemaGenerationError: + serialization = None + + input_schema = handler.generate_schema(self.json_schema_input_type) + + info_arg = _inspect_validator(self.func, mode='plain', type='field') + if info_arg: + func = cast(core_schema.WithInfoValidatorFunction, self.func) + return core_schema.with_info_plain_validator_function( + func, + serialization=serialization, # pyright: ignore[reportArgumentType] + json_schema_input_schema=input_schema, + ) + else: + func = cast(core_schema.NoInfoValidatorFunction, self.func) + return core_schema.no_info_plain_validator_function( + func, + serialization=serialization, # pyright: ignore[reportArgumentType] + json_schema_input_schema=input_schema, + ) + + @classmethod + def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: + return cls( + func=decorator.func, + json_schema_input_type=decorator.info.json_schema_input_type, + ) + + +@dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) +class WrapValidator: + """!!! abstract "Usage Documentation" + [field *wrap* validators](../concepts/validators.md#field-wrap-validator) + + A metadata class that indicates that a validation should be applied **around** the inner validation logic. + + Attributes: + func: The validator function. + json_schema_input_type: The input type used to generate the appropriate + JSON Schema (in validation mode). The actual input type is `Any`. + + ```python + from datetime import datetime + from typing import Annotated + + from pydantic import BaseModel, ValidationError, WrapValidator + + def validate_timestamp(v, handler): + if v == 'now': + # we don't want to bother with further validation, just return the new value + return datetime.now() + try: + return handler(v) + except ValidationError: + # validation failed, in this case we want to return a default value + return datetime(2000, 1, 1) + + MyTimestamp = Annotated[datetime, WrapValidator(validate_timestamp)] + + class Model(BaseModel): + a: MyTimestamp + + print(Model(a='now').a) + #> 2032-01-02 03:04:05.000006 + print(Model(a='invalid').a) + #> 2000-01-01 00:00:00 + ``` + """ + + func: core_schema.NoInfoWrapValidatorFunction | core_schema.WithInfoWrapValidatorFunction + json_schema_input_type: Any = PydanticUndefined + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source_type) + input_schema = ( + None + if self.json_schema_input_type is PydanticUndefined + else handler.generate_schema(self.json_schema_input_type) + ) + + info_arg = _inspect_validator(self.func, mode='wrap', type='field') + if info_arg: + func = cast(core_schema.WithInfoWrapValidatorFunction, self.func) + return core_schema.with_info_wrap_validator_function( + func, + schema=schema, + json_schema_input_schema=input_schema, + ) + else: + func = cast(core_schema.NoInfoWrapValidatorFunction, self.func) + return core_schema.no_info_wrap_validator_function( + func, + schema=schema, + json_schema_input_schema=input_schema, + ) + + @classmethod + def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: + return cls( + func=decorator.func, + json_schema_input_type=decorator.info.json_schema_input_type, + ) + + +if TYPE_CHECKING: + + class _OnlyValueValidatorClsMethod(Protocol): + def __call__(self, cls: Any, value: Any, /) -> Any: ... + + class _V2ValidatorClsMethod(Protocol): + def __call__(self, cls: Any, value: Any, info: core_schema.ValidationInfo[Any], /) -> Any: ... + + class _OnlyValueWrapValidatorClsMethod(Protocol): + def __call__(self, cls: Any, value: Any, handler: core_schema.ValidatorFunctionWrapHandler, /) -> Any: ... + + class _V2WrapValidatorClsMethod(Protocol): + def __call__( + self, + cls: Any, + value: Any, + handler: core_schema.ValidatorFunctionWrapHandler, + info: core_schema.ValidationInfo[Any], + /, + ) -> Any: ... + + _V2Validator = Union[ + _V2ValidatorClsMethod, + core_schema.WithInfoValidatorFunction, + _OnlyValueValidatorClsMethod, + core_schema.NoInfoValidatorFunction, + ] + + _V2WrapValidator = Union[ + _V2WrapValidatorClsMethod, + core_schema.WithInfoWrapValidatorFunction, + _OnlyValueWrapValidatorClsMethod, + core_schema.NoInfoWrapValidatorFunction, + ] + + _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] + + _V2BeforeAfterOrPlainValidatorType = TypeVar( + '_V2BeforeAfterOrPlainValidatorType', + bound=Union[_V2Validator, _PartialClsOrStaticMethod], + ) + _V2WrapValidatorType = TypeVar('_V2WrapValidatorType', bound=Union[_V2WrapValidator, _PartialClsOrStaticMethod]) + +FieldValidatorModes: TypeAlias = Literal['before', 'after', 'wrap', 'plain'] + + +@overload +def field_validator( + field: str, + /, + *fields: str, + mode: Literal['wrap'], + check_fields: bool | None = ..., + json_schema_input_type: Any = ..., +) -> Callable[[_V2WrapValidatorType], _V2WrapValidatorType]: ... + + +@overload +def field_validator( + field: str, + /, + *fields: str, + mode: Literal['before', 'plain'], + check_fields: bool | None = ..., + json_schema_input_type: Any = ..., +) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: ... + + +@overload +def field_validator( + field: str, + /, + *fields: str, + mode: Literal['after'] = ..., + check_fields: bool | None = ..., +) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: ... + + +def field_validator( # noqa: D417 + field: str, + /, + *fields: str, + mode: FieldValidatorModes = 'after', + check_fields: bool | None = None, + json_schema_input_type: Any = PydanticUndefined, +) -> Callable[[Any], Any]: + """!!! abstract "Usage Documentation" + [field validators](../concepts/validators.md#field-validators) + + Decorate methods on the class indicating that they should be used to validate fields. + + Example usage: + ```python + from typing import Any + + from pydantic import ( + BaseModel, + ValidationError, + field_validator, + ) + + class Model(BaseModel): + a: str + + @field_validator('a') + @classmethod + def ensure_foobar(cls, v: Any): + if 'foobar' not in v: + raise ValueError('"foobar" not found in a') + return v + + print(repr(Model(a='this is foobar good'))) + #> Model(a='this is foobar good') + + try: + Model(a='snap') + except ValidationError as exc_info: + print(exc_info) + ''' + 1 validation error for Model + a + Value error, "foobar" not found in a [type=value_error, input_value='snap', input_type=str] + ''' + ``` + + For more in depth examples, see [Field Validators](../concepts/validators.md#field-validators). + + Args: + *fields: The field names the validator should apply to. + mode: Specifies whether to validate the fields before or after validation. + check_fields: Whether to check that the fields actually exist on the model. + json_schema_input_type: The input type of the function. This is only used to generate + the appropriate JSON Schema (in validation mode) and can only specified + when `mode` is either `'before'`, `'plain'` or `'wrap'`. + + Raises: + PydanticUserError: + - If the decorator is used without any arguments (at least one field name must be provided). + - If the provided field names are not strings. + - If `json_schema_input_type` is provided with an unsupported `mode`. + - If the decorator is applied to an instance method. + """ + if callable(field) or isinstance(field, classmethod): + raise PydanticUserError( + 'The `@field_validator` decorator cannot be used without arguments, at least one field must be provided. ' + "For example: `@field_validator('', ...)`.", + code='decorator-missing-arguments', + ) + + if mode not in ('before', 'plain', 'wrap') and json_schema_input_type is not PydanticUndefined: + raise PydanticUserError( + f"`json_schema_input_type` can't be used when mode is set to {mode!r}", + code='validator-input-type', + ) + + if json_schema_input_type is PydanticUndefined and mode == 'plain': + json_schema_input_type = Any + + fields = field, *fields + if not all(isinstance(field, str) for field in fields): + raise PydanticUserError( + 'The provided field names to the `@field_validator` decorator should be strings. ' + "For example: `@field_validator('', '', ...).`", + code='decorator-invalid-fields', + ) + + def dec( + f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any], + ) -> _decorators.PydanticDescriptorProxy[Any]: + if _decorators.is_instance_method_from_sig(f): + raise PydanticUserError( + 'The `@field_validator` decorator cannot be applied to instance methods', + code='validator-instance-method', + ) + + # auto apply the @classmethod decorator + f = _decorators.ensure_classmethod_based_on_signature(f) + + dec_info = _decorators.FieldValidatorDecoratorInfo( + fields=fields, mode=mode, check_fields=check_fields, json_schema_input_type=json_schema_input_type + ) + return _decorators.PydanticDescriptorProxy(f, dec_info) + + return dec + + +_ModelType = TypeVar('_ModelType') +_ModelTypeCo = TypeVar('_ModelTypeCo', covariant=True) + + +class ModelWrapValidatorHandler(core_schema.ValidatorFunctionWrapHandler, Protocol[_ModelTypeCo]): + """`@model_validator` decorated function handler argument type. This is used when `mode='wrap'`.""" + + def __call__( # noqa: D102 + self, + value: Any, + outer_location: str | int | None = None, + /, + ) -> _ModelTypeCo: # pragma: no cover + ... + + +class ModelWrapValidatorWithoutInfo(Protocol[_ModelType]): + """A `@model_validator` decorated function signature. + This is used when `mode='wrap'` and the function does not have info argument. + """ + + def __call__( # noqa: D102 + self, + cls: type[_ModelType], + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + handler: ModelWrapValidatorHandler[_ModelType], + /, + ) -> _ModelType: ... + + +class ModelWrapValidator(Protocol[_ModelType]): + """A `@model_validator` decorated function signature. This is used when `mode='wrap'`.""" + + def __call__( # noqa: D102 + self, + cls: type[_ModelType], + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + handler: ModelWrapValidatorHandler[_ModelType], + info: core_schema.ValidationInfo, + /, + ) -> _ModelType: ... + + +class FreeModelBeforeValidatorWithoutInfo(Protocol): + """A `@model_validator` decorated function signature. + This is used when `mode='before'` and the function does not have info argument. + """ + + def __call__( # noqa: D102 + self, + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + /, + ) -> Any: ... + + +class ModelBeforeValidatorWithoutInfo(Protocol): + """A `@model_validator` decorated function signature. + This is used when `mode='before'` and the function does not have info argument. + """ + + def __call__( # noqa: D102 + self, + cls: Any, + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + /, + ) -> Any: ... + + +class FreeModelBeforeValidator(Protocol): + """A `@model_validator` decorated function signature. This is used when `mode='before'`.""" + + def __call__( # noqa: D102 + self, + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + info: core_schema.ValidationInfo[Any], + /, + ) -> Any: ... + + +class ModelBeforeValidator(Protocol): + """A `@model_validator` decorated function signature. This is used when `mode='before'`.""" + + def __call__( # noqa: D102 + self, + cls: Any, + # this can be a dict, a model instance + # or anything else that gets passed to validate_python + # thus validators _must_ handle all cases + value: Any, + info: core_schema.ValidationInfo[Any], + /, + ) -> Any: ... + + +ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType] +"""A `@model_validator` decorated function signature. This is used when `mode='after'` and the function does not +have info argument. +""" + +ModelAfterValidator = Callable[[_ModelType, core_schema.ValidationInfo[Any]], _ModelType] +"""A `@model_validator` decorated function signature. This is used when `mode='after'`.""" + +_AnyModelWrapValidator = Union[ModelWrapValidator[_ModelType], ModelWrapValidatorWithoutInfo[_ModelType]] +_AnyModelBeforeValidator = Union[ + FreeModelBeforeValidator, ModelBeforeValidator, FreeModelBeforeValidatorWithoutInfo, ModelBeforeValidatorWithoutInfo +] +_AnyModelAfterValidator = Union[ModelAfterValidator[_ModelType], ModelAfterValidatorWithoutInfo[_ModelType]] + + +@overload +def model_validator( + *, + mode: Literal['wrap'], +) -> Callable[ + [_AnyModelWrapValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] +]: ... + + +@overload +def model_validator( + *, + mode: Literal['before'], +) -> Callable[ + [_AnyModelBeforeValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] +]: ... + + +@overload +def model_validator( + *, + mode: Literal['after'], +) -> Callable[ + [_AnyModelAfterValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] +]: ... + + +def model_validator( + *, + mode: Literal['wrap', 'before', 'after'], +) -> Any: + """!!! abstract "Usage Documentation" + [Model Validators](../concepts/validators.md#model-validators) + + Decorate model methods for validation purposes. + + Example usage: + ```python + from typing_extensions import Self + + from pydantic import BaseModel, ValidationError, model_validator + + class Square(BaseModel): + width: float + height: float + + @model_validator(mode='after') + def verify_square(self) -> Self: + if self.width != self.height: + raise ValueError('width and height do not match') + return self + + s = Square(width=1, height=1) + print(repr(s)) + #> Square(width=1.0, height=1.0) + + try: + Square(width=1, height=2) + except ValidationError as e: + print(e) + ''' + 1 validation error for Square + Value error, width and height do not match [type=value_error, input_value={'width': 1, 'height': 2}, input_type=dict] + ''' + ``` + + For more in depth examples, see [Model Validators](../concepts/validators.md#model-validators). + + Args: + mode: A required string literal that specifies the validation mode. + It can be one of the following: 'wrap', 'before', or 'after'. + + Returns: + A decorator that can be used to decorate a function to be used as a model validator. + """ + + def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]: + # auto apply the @classmethod decorator. NOTE: in V3, do not apply the conversion for 'after' validators: + f = _decorators.ensure_classmethod_based_on_signature(f) + if mode == 'after' and isinstance(f, classmethod): + warnings.warn( + category=PydanticDeprecatedSince212, + message=( + "Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. " + f'See the documentation at https://docs.pydantic.dev/{version_short()}/concepts/validators/#model-after-validator.' + ), + stacklevel=2, + ) + + dec_info = _decorators.ModelValidatorDecoratorInfo(mode=mode) + return _decorators.PydanticDescriptorProxy(f, dec_info) + + return dec + + +AnyType = TypeVar('AnyType') + + +if TYPE_CHECKING: + # If we add configurable attributes to IsInstance, we'd probably need to stop hiding it from type checkers like this + InstanceOf = Annotated[AnyType, ...] # `IsInstance[Sequence]` will be recognized by type checkers as `Sequence` + +else: + + @dataclasses.dataclass(**_internal_dataclass.slots_true) + class InstanceOf: + '''Generic type for annotating a type that is an instance of a given class. + + Example: + ```python + from pydantic import BaseModel, InstanceOf + + class Foo: + ... + + class Bar(BaseModel): + foo: InstanceOf[Foo] + + Bar(foo=Foo()) + try: + Bar(foo=42) + except ValidationError as e: + print(e) + """ + [ + │ { + │ │ 'type': 'is_instance_of', + │ │ 'loc': ('foo',), + │ │ 'msg': 'Input should be an instance of Foo', + │ │ 'input': 42, + │ │ 'ctx': {'class': 'Foo'}, + │ │ 'url': 'https://errors.pydantic.dev/0.38.0/v/is_instance_of' + │ } + ] + """ + ``` + ''' + + @classmethod + def __class_getitem__(cls, item: AnyType) -> AnyType: + return Annotated[item, cls()] + + @classmethod + def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + from pydantic._internal._generate_schema import GENERATE_SCHEMA_ERRORS + + # use the generic _origin_ as the second argument to isinstance when appropriate + instance_of_schema = core_schema.is_instance_schema(_generics.get_origin(source) or source) + + try: + # Try to generate the "standard" schema, which will be used when loading from JSON + original_schema = handler(source) + except GENERATE_SCHEMA_ERRORS: + # If that fails, just produce a schema that can validate from python + return instance_of_schema + else: + # Use the "original" approach to serialization + instance_of_schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( + function=lambda v, h: h(v), schema=original_schema + ) + return core_schema.json_or_python_schema(python_schema=instance_of_schema, json_schema=original_schema) + + __hash__ = object.__hash__ + + +if TYPE_CHECKING: + SkipValidation = Annotated[AnyType, ...] # SkipValidation[list[str]] will be treated by type checkers as list[str] +else: + + @dataclasses.dataclass(**_internal_dataclass.slots_true) + class SkipValidation: + """If this is applied as an annotation (e.g., via `x: Annotated[int, SkipValidation]`), validation will be + skipped. You can also use `SkipValidation[int]` as a shorthand for `Annotated[int, SkipValidation]`. + + This can be useful if you want to use a type annotation for documentation/IDE/type-checking purposes, + and know that it is safe to skip validation for one or more of the fields. + + Because this converts the validation schema to `any_schema`, subsequent annotation-applied transformations + may not have the expected effects. Therefore, when used, this annotation should generally be the final + annotation applied to a type. + """ + + def __class_getitem__(cls, item: Any) -> Any: + return Annotated[item, SkipValidation()] + + @classmethod + def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', ArbitraryTypeWarning) + original_schema = handler(source) + metadata = {'pydantic_js_annotation_functions': [lambda _c, h: h(original_schema)]} + return core_schema.any_schema( + metadata=metadata, + serialization=core_schema.wrap_serializer_function_ser_schema( + function=lambda v, h: h(v), schema=original_schema + ), + ) + + __hash__ = object.__hash__ + + +_FromTypeT = TypeVar('_FromTypeT') + + +class ValidateAs: + """A helper class to validate a custom type from a type that is natively supported by Pydantic. + + Args: + from_type: The type natively supported by Pydantic to use to perform validation. + instantiation_hook: A callable taking the validated type as an argument, and returning + the populated custom type. + + Example: + ```python {lint="skip"} + from typing import Annotated + + from pydantic import BaseModel, TypeAdapter, ValidateAs + + class MyCls: + def __init__(self, a: int) -> None: + self.a = a + + def __repr__(self) -> str: + return f"MyCls(a={self.a})" + + class Model(BaseModel): + a: int + + + ta = TypeAdapter( + Annotated[MyCls, ValidateAs(Model, lambda v: MyCls(a=v.a))] + ) + + print(ta.validate_python({'a': 1})) + #> MyCls(a=1) + ``` + """ + + # TODO: make use of PEP 747 + def __init__(self, from_type: type[_FromTypeT], /, instantiation_hook: Callable[[_FromTypeT], Any]) -> None: + self.from_type = from_type + self.instantiation_hook = instantiation_hook + + def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(self.from_type) + return core_schema.no_info_after_validator_function( + self.instantiation_hook, + schema=schema, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py new file mode 100644 index 0000000..3f1070d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/generics.py @@ -0,0 +1,5 @@ +"""The `generics` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py new file mode 100644 index 0000000..bcaff9f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json.py @@ -0,0 +1,5 @@ +"""The `json` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py new file mode 100644 index 0000000..b9b21fd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py @@ -0,0 +1,2911 @@ +"""!!! abstract "Usage Documentation" + [JSON Schema](../concepts/json_schema.md) + +The `json_schema` module contains classes and functions to allow the way [JSON Schema](https://json-schema.org/) +is generated to be customized. + +In general you shouldn't need to use this module directly; instead, you can use +[`BaseModel.model_json_schema`][pydantic.BaseModel.model_json_schema] and +[`TypeAdapter.json_schema`][pydantic.TypeAdapter.json_schema]. +""" + +from __future__ import annotations as _annotations + +import collections.abc +import dataclasses +import inspect +import math +import os +import re +import warnings +from collections import Counter, defaultdict +from collections.abc import Hashable, Iterable, Sequence +from copy import deepcopy +from enum import Enum +from re import Pattern +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Callable, + Literal, + NewType, + TypeVar, + Union, + cast, + overload, +) + +import pydantic_core +from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python +from pydantic_core.core_schema import ComputedField +from typing_extensions import TypeAlias, assert_never, deprecated, final +from typing_inspection.introspection import get_literal_values + +from pydantic.warnings import PydanticDeprecatedSince26, PydanticDeprecatedSince29 + +from ._internal import ( + _config, + _core_metadata, + _core_utils, + _decorators, + _internal_dataclass, + _mock_val_ser, + _schema_generation_shared, + _typing_extra, +) +from .annotated_handlers import GetJsonSchemaHandler +from .config import JsonDict, JsonValue +from .errors import PydanticInvalidForJsonSchema, PydanticSchemaGenerationError, PydanticUserError + +if TYPE_CHECKING: + from . import ConfigDict + from ._internal._core_utils import CoreSchemaField, CoreSchemaOrField + from ._internal._dataclasses import PydanticDataclass + from ._internal._schema_generation_shared import GetJsonSchemaFunction + from .main import BaseModel + + +CoreSchemaOrFieldType = Literal[core_schema.CoreSchemaType, core_schema.CoreSchemaFieldType] +""" +A type alias for defined schema types that represents a union of +`core_schema.CoreSchemaType` and +`core_schema.CoreSchemaFieldType`. +""" + +JsonSchemaValue = dict[str, Any] +""" +A type alias for a JSON schema value. This is a dictionary of string keys to arbitrary JSON values. +""" + +JsonSchemaMode = Literal['validation', 'serialization'] +""" +A type alias that represents the mode of a JSON schema; either 'validation' or 'serialization'. + +For some types, the inputs to validation differ from the outputs of serialization. For example, +computed fields will only be present when serializing, and should not be provided when +validating. This flag provides a way to indicate whether you want the JSON schema required +for validation inputs, or that will be matched by serialization outputs. +""" + +_MODE_TITLE_MAPPING: dict[JsonSchemaMode, str] = {'validation': 'Input', 'serialization': 'Output'} + + +JsonSchemaWarningKind = Literal['skipped-choice', 'non-serializable-default', 'skipped-discriminator'] +""" +A type alias representing the kinds of warnings that can be emitted during JSON schema generation. + +See [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message] +for more details. +""" + + +class PydanticJsonSchemaWarning(UserWarning): + """This class is used to emit warnings produced during JSON schema generation. + See the [`GenerateJsonSchema.emit_warning`][pydantic.json_schema.GenerateJsonSchema.emit_warning] and + [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message] + methods for more details; these can be overridden to control warning behavior. + """ + + +NoDefault = object() +"""A sentinel value used to indicate that no default value should be used when generating a JSON Schema +for a core schema with a default value. +""" + + +# ##### JSON Schema Generation ##### +DEFAULT_REF_TEMPLATE = '#/$defs/{model}' +"""The default format string used to generate reference names.""" + +# There are three types of references relevant to building JSON schemas: +# 1. core_schema "ref" values; these are not exposed as part of the JSON schema +# * these might look like the fully qualified path of a model, its id, or something similar +CoreRef = NewType('CoreRef', str) +# 2. keys of the "definitions" object that will eventually go into the JSON schema +# * by default, these look like "MyModel", though may change in the presence of collisions +# * eventually, we may want to make it easier to modify the way these names are generated +DefsRef = NewType('DefsRef', str) +# 3. the values corresponding to the "$ref" key in the schema +# * By default, these look like "#/$defs/MyModel", as in {"$ref": "#/$defs/MyModel"} +JsonRef = NewType('JsonRef', str) + +CoreModeRef = tuple[CoreRef, JsonSchemaMode] +JsonSchemaKeyT = TypeVar('JsonSchemaKeyT', bound=Hashable) + +_PRIMITIVE_JSON_SCHEMA_TYPES = ('string', 'boolean', 'null', 'integer', 'number') + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class _DefinitionsRemapping: + defs_remapping: dict[DefsRef, DefsRef] + json_remapping: dict[JsonRef, JsonRef] + + @staticmethod + def from_prioritized_choices( + prioritized_choices: dict[DefsRef, list[DefsRef]], + defs_to_json: dict[DefsRef, JsonRef], + definitions: dict[DefsRef, JsonSchemaValue], + ) -> _DefinitionsRemapping: + """ + This function should produce a remapping that replaces complex DefsRef with the simpler ones from the + prioritized_choices such that applying the name remapping would result in an equivalent JSON schema. + """ + # We need to iteratively simplify the definitions until we reach a fixed point. + # The reason for this is that outer definitions may reference inner definitions that get simplified + # into an equivalent reference, and the outer definitions won't be equivalent until we've simplified + # the inner definitions. + copied_definitions = deepcopy(definitions) + definitions_schema = {'$defs': copied_definitions} + for _iter in range(100): # prevent an infinite loop in the case of a bug, 100 iterations should be enough + # For every possible remapped DefsRef, collect all schemas that DefsRef might be used for: + schemas_for_alternatives: dict[DefsRef, list[JsonSchemaValue]] = defaultdict(list) + for defs_ref in copied_definitions: + alternatives = prioritized_choices[defs_ref] + for alternative in alternatives: + schemas_for_alternatives[alternative].append(copied_definitions[defs_ref]) + + # Deduplicate the schemas for each alternative; the idea is that we only want to remap to a new DefsRef + # if it introduces no ambiguity, i.e., there is only one distinct schema for that DefsRef. + for defs_ref in schemas_for_alternatives: + schemas_for_alternatives[defs_ref] = _deduplicate_schemas(schemas_for_alternatives[defs_ref]) + + # Build the remapping + defs_remapping: dict[DefsRef, DefsRef] = {} + json_remapping: dict[JsonRef, JsonRef] = {} + for original_defs_ref in definitions: + alternatives = prioritized_choices[original_defs_ref] + # Pick the first alternative that has only one schema, since that means there is no collision + remapped_defs_ref = next(x for x in alternatives if len(schemas_for_alternatives[x]) == 1) + defs_remapping[original_defs_ref] = remapped_defs_ref + + # Map all alternatives after the remapped one to the remapped one + # This ensures that intermediate simplifications are also remapped + remapped_index = alternatives.index(remapped_defs_ref) + for alt in alternatives[remapped_index:]: + json_remapping[defs_to_json[alt]] = defs_to_json[remapped_defs_ref] + remapping = _DefinitionsRemapping(defs_remapping, json_remapping) + new_definitions_schema = remapping.remap_json_schema({'$defs': copied_definitions}) + if definitions_schema == new_definitions_schema: + # We've reached the fixed point + return remapping + definitions_schema = new_definitions_schema + + raise PydanticInvalidForJsonSchema('Failed to simplify the JSON schema definitions') + + def remap_defs_ref(self, ref: DefsRef) -> DefsRef: + return self.defs_remapping.get(ref, ref) + + def remap_json_ref(self, ref: JsonRef) -> JsonRef: + return self.json_remapping.get(ref, ref) + + def remap_json_schema(self, schema: Any) -> Any: + """ + Recursively update the JSON schema replacing all $refs + """ + if isinstance(schema, str): + # Note: this may not really be a JsonRef; we rely on having no collisions between JsonRefs and other strings + return self.remap_json_ref(JsonRef(schema)) + elif isinstance(schema, list): + return [self.remap_json_schema(item) for item in schema] + elif isinstance(schema, dict): + for key, value in schema.items(): + if key == '$ref' and isinstance(value, str): + schema['$ref'] = self.remap_json_ref(JsonRef(value)) + elif key == '$defs': + schema['$defs'] = { + self.remap_defs_ref(DefsRef(key)): self.remap_json_schema(value) + for key, value in schema['$defs'].items() + } + else: + schema[key] = self.remap_json_schema(value) + return schema + + +class GenerateJsonSchema: + """!!! abstract "Usage Documentation" + [Customizing the JSON Schema Generation Process](../concepts/json_schema.md#customizing-the-json-schema-generation-process) + + A class for generating JSON schemas. + + This class generates JSON schemas based on configured parameters. The default schema dialect + is [https://json-schema.org/draft/2020-12/schema](https://json-schema.org/draft/2020-12/schema). + The class uses `by_alias` to configure how fields with + multiple names are handled and `ref_template` to format reference names. + + Attributes: + schema_dialect: The JSON schema dialect used to generate the schema. See + [Declaring a Dialect](https://json-schema.org/understanding-json-schema/reference/schema.html#id4) + in the JSON Schema documentation for more information about dialects. + ignored_warning_kinds: Warnings to ignore when generating the schema. `self.render_warning_message` will + do nothing if its argument `kind` is in `ignored_warning_kinds`; + this value can be modified on subclasses to easily control which warnings are emitted. + by_alias: Whether to use field aliases when generating the schema. + ref_template: The format string used when generating reference names. + core_to_json_refs: A mapping of core refs to JSON refs. + core_to_defs_refs: A mapping of core refs to definition refs. + defs_to_core_refs: A mapping of definition refs to core refs. + json_to_defs_refs: A mapping of JSON refs to definition refs. + definitions: Definitions in the schema. + + Args: + by_alias: Whether to use field aliases in the generated schemas. + ref_template: The format string to use when generating reference names. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + + Raises: + JsonSchemaError: If the instance of the class is inadvertently reused after generating a schema. + """ + + schema_dialect = 'https://json-schema.org/draft/2020-12/schema' + + # `self.render_warning_message` will do nothing if its argument `kind` is in `ignored_warning_kinds`; + # this value can be modified on subclasses to easily control which warnings are emitted + ignored_warning_kinds: set[JsonSchemaWarningKind] = {'skipped-choice'} + + def __init__( + self, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + ) -> None: + self.by_alias = by_alias + self.ref_template = ref_template + self.union_format: Literal['any_of', 'primitive_type_array'] = union_format + + self.core_to_json_refs: dict[CoreModeRef, JsonRef] = {} + self.core_to_defs_refs: dict[CoreModeRef, DefsRef] = {} + self.defs_to_core_refs: dict[DefsRef, CoreModeRef] = {} + self.json_to_defs_refs: dict[JsonRef, DefsRef] = {} + + self.definitions: dict[DefsRef, JsonSchemaValue] = {} + self._config_wrapper_stack = _config.ConfigWrapperStack(_config.ConfigWrapper({})) + + self._mode: JsonSchemaMode = 'validation' + + # The following includes a mapping of a fully-unique defs ref choice to a list of preferred + # alternatives, which are generally simpler, such as only including the class name. + # At the end of schema generation, we use these to produce a JSON schema with more human-readable + # definitions, which would also work better in a generated OpenAPI client, etc. + self._prioritized_defsref_choices: dict[DefsRef, list[DefsRef]] = {} + self._collision_counter: dict[str, int] = defaultdict(int) + self._collision_index: dict[str, int] = {} + + self._schema_type_to_method = self.build_schema_type_to_method() + + # When we encounter definitions we need to try to build them immediately + # so that they are available schemas that reference them + # But it's possible that CoreSchema was never going to be used + # (e.g. because the CoreSchema that references short circuits is JSON schema generation without needing + # the reference) so instead of failing altogether if we can't build a definition we + # store the error raised and re-throw it if we end up needing that def + self._core_defs_invalid_for_json_schema: dict[DefsRef, PydanticInvalidForJsonSchema] = {} + + # This changes to True after generating a schema, to prevent issues caused by accidental reuse + # of a single instance of a schema generator + self._used = False + + @property + def _config(self) -> _config.ConfigWrapper: + return self._config_wrapper_stack.tail + + @property + def mode(self) -> JsonSchemaMode: + if self._config.json_schema_mode_override is not None: + return self._config.json_schema_mode_override + else: + return self._mode + + def build_schema_type_to_method( + self, + ) -> dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]]: + """Builds a dictionary mapping fields to methods for generating JSON schemas. + + Returns: + A dictionary containing the mapping of `CoreSchemaOrFieldType` to a handler method. + + Raises: + TypeError: If no method has been defined for generating a JSON schema for a given pydantic core schema type. + """ + mapping: dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]] = {} + core_schema_types: list[CoreSchemaOrFieldType] = list(get_literal_values(CoreSchemaOrFieldType)) + for key in core_schema_types: + method_name = f'{key.replace("-", "_")}_schema' + try: + mapping[key] = getattr(self, method_name) + except AttributeError as e: # pragma: no cover + if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'): + continue + raise TypeError( + f'No method for generating JsonSchema for core_schema.type={key!r} ' + f'(expected: {type(self).__name__}.{method_name})' + ) from e + return mapping + + def generate_definitions( + self, inputs: Sequence[tuple[JsonSchemaKeyT, JsonSchemaMode, core_schema.CoreSchema]] + ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], dict[DefsRef, JsonSchemaValue]]: + """Generates JSON schema definitions from a list of core schemas, pairing the generated definitions with a + mapping that links the input keys to the definition references. + + Args: + inputs: A sequence of tuples, where: + + - The first element is a JSON schema key type. + - The second element is the JSON mode: either 'validation' or 'serialization'. + - The third element is a core schema. + + Returns: + A tuple where: + + - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and + whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have + JsonRef references to definitions that are defined in the second returned element.) + - The second element is a dictionary whose keys are definition references for the JSON schemas + from the first returned element, and whose values are the actual JSON schema definitions. + + Raises: + PydanticUserError: Raised if the JSON schema generator has already been used to generate a JSON schema. + """ + if self._used: + raise PydanticUserError( + 'This JSON schema generator has already been used to generate a JSON schema. ' + f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.', + code='json-schema-already-used', + ) + + for _, mode, schema in inputs: + self._mode = mode + self.generate_inner(schema) + + definitions_remapping = self._build_definitions_remapping() + + json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {} + for key, mode, schema in inputs: + self._mode = mode + json_schema = self.generate_inner(schema) + json_schemas_map[(key, mode)] = definitions_remapping.remap_json_schema(json_schema) + + json_schema = {'$defs': self.definitions} + json_schema = definitions_remapping.remap_json_schema(json_schema) + self._used = True + return json_schemas_map, self.sort(json_schema['$defs']) # type: ignore + + def generate(self, schema: CoreSchema, mode: JsonSchemaMode = 'validation') -> JsonSchemaValue: + """Generates a JSON schema for a specified schema in a specified mode. + + Args: + schema: A Pydantic model. + mode: The mode in which to generate the schema. Defaults to 'validation'. + + Returns: + A JSON schema representing the specified schema. + + Raises: + PydanticUserError: If the JSON schema generator has already been used to generate a JSON schema. + """ + self._mode = mode + if self._used: + raise PydanticUserError( + 'This JSON schema generator has already been used to generate a JSON schema. ' + f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.', + code='json-schema-already-used', + ) + + json_schema: JsonSchemaValue = self.generate_inner(schema) + json_ref_counts = self.get_json_ref_counts(json_schema) + + ref = cast(JsonRef, json_schema.get('$ref')) + while ref is not None: # may need to unpack multiple levels + ref_json_schema = self.get_schema_from_definitions(ref) + if json_ref_counts[ref] == 1 and ref_json_schema is not None and len(json_schema) == 1: + # "Unpack" the ref since this is the only reference and there are no sibling keys + json_schema = ref_json_schema.copy() # copy to prevent recursive dict reference + json_ref_counts[ref] -= 1 + ref = cast(JsonRef, json_schema.get('$ref')) + ref = None + + self._garbage_collect_definitions(json_schema) + definitions_remapping = self._build_definitions_remapping() + + if self.definitions: + json_schema['$defs'] = self.definitions + + json_schema = definitions_remapping.remap_json_schema(json_schema) + + # For now, we will not set the $schema key. However, if desired, this can be easily added by overriding + # this method and adding the following line after a call to super().generate(schema): + # json_schema['$schema'] = self.schema_dialect + + self._used = True + return self.sort(json_schema) + + def generate_inner(self, schema: CoreSchemaOrField) -> JsonSchemaValue: # noqa: C901 + """Generates a JSON schema for a given core schema. + + Args: + schema: The given core schema. + + Returns: + The generated JSON schema. + + TODO: the nested function definitions here seem like bad practice, I'd like to unpack these + in a future PR. It'd be great if we could shorten the call stack a bit for JSON schema generation, + and I think there's potential for that here. + """ + # If a schema with the same CoreRef has been handled, just return a reference to it + # Note that this assumes that it will _never_ be the case that the same CoreRef is used + # on types that should have different JSON schemas + if 'ref' in schema: + core_ref = CoreRef(schema['ref']) # type: ignore[typeddict-item] + core_mode_ref = (core_ref, self.mode) + if core_mode_ref in self.core_to_defs_refs and self.core_to_defs_refs[core_mode_ref] in self.definitions: + return {'$ref': self.core_to_json_refs[core_mode_ref]} + + def populate_defs(core_schema: CoreSchema, json_schema: JsonSchemaValue) -> JsonSchemaValue: + if 'ref' in core_schema: + core_ref = CoreRef(core_schema['ref']) # type: ignore[typeddict-item] + defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) + json_ref = JsonRef(ref_json_schema['$ref']) + # Replace the schema if it's not a reference to itself + # What we want to avoid is having the def be just a ref to itself + # which is what would happen if we blindly assigned any + if json_schema.get('$ref', None) != json_ref: + self.definitions[defs_ref] = json_schema + self._core_defs_invalid_for_json_schema.pop(defs_ref, None) + json_schema = ref_json_schema + return json_schema + + def handler_func(schema_or_field: CoreSchemaOrField) -> JsonSchemaValue: + """Generate a JSON schema based on the input schema. + + Args: + schema_or_field: The core schema to generate a JSON schema from. + + Returns: + The generated JSON schema. + + Raises: + TypeError: If an unexpected schema type is encountered. + """ + # Generate the core-schema-type-specific bits of the schema generation: + json_schema: JsonSchemaValue | None = None + if self.mode == 'serialization' and 'serialization' in schema_or_field: + # In this case, we skip the JSON Schema generation of the schema + # and use the `'serialization'` schema instead (canonical example: + # `Annotated[int, PlainSerializer(str)]`). + ser_schema = schema_or_field['serialization'] # type: ignore + json_schema = self.ser_schema(ser_schema) + + # It might be that the 'serialization'` is skipped depending on `when_used`. + # This is only relevant for `nullable` schemas though, so we special case here. + if ( + json_schema is not None + and ser_schema.get('when_used') in ('unless-none', 'json-unless-none') + and schema_or_field['type'] == 'nullable' + ): + json_schema = self.get_union_of_schemas([{'type': 'null'}, json_schema]) + if json_schema is None: + if _core_utils.is_core_schema(schema_or_field) or _core_utils.is_core_schema_field(schema_or_field): + generate_for_schema_type = self._schema_type_to_method[schema_or_field['type']] + json_schema = generate_for_schema_type(schema_or_field) + else: + raise TypeError(f'Unexpected schema type: schema={schema_or_field}') + return json_schema + + current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func) + + metadata = cast(_core_metadata.CoreMetadata, schema.get('metadata', {})) + + # TODO: I dislike that we have to wrap these basic dict updates in callables, is there any way around this? + + if js_updates := metadata.get('pydantic_js_updates'): + + def js_updates_handler_func( + schema_or_field: CoreSchemaOrField, + current_handler: GetJsonSchemaHandler = current_handler, + ) -> JsonSchemaValue: + json_schema = {**current_handler(schema_or_field), **js_updates} + return json_schema + + current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func) + + if js_extra := metadata.get('pydantic_js_extra'): + + def js_extra_handler_func( + schema_or_field: CoreSchemaOrField, + current_handler: GetJsonSchemaHandler = current_handler, + ) -> JsonSchemaValue: + json_schema = current_handler(schema_or_field) + if isinstance(js_extra, dict): + json_schema.update(to_jsonable_python(js_extra)) + elif callable(js_extra): + # similar to typing issue in _update_class_schema when we're working with callable js extra + js_extra(json_schema) # type: ignore + return json_schema + + current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func) + + for js_modify_function in metadata.get('pydantic_js_functions', ()): + + def new_handler_func( + schema_or_field: CoreSchemaOrField, + current_handler: GetJsonSchemaHandler = current_handler, + js_modify_function: GetJsonSchemaFunction = js_modify_function, + ) -> JsonSchemaValue: + json_schema = js_modify_function(schema_or_field, current_handler) + if _core_utils.is_core_schema(schema_or_field): + json_schema = populate_defs(schema_or_field, json_schema) + original_schema = current_handler.resolve_ref_schema(json_schema) + ref = json_schema.pop('$ref', None) + if ref and json_schema: + original_schema.update(json_schema) + return original_schema + + current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) + + for js_modify_function in metadata.get('pydantic_js_annotation_functions', ()): + + def new_handler_func( + schema_or_field: CoreSchemaOrField, + current_handler: GetJsonSchemaHandler = current_handler, + js_modify_function: GetJsonSchemaFunction = js_modify_function, + ) -> JsonSchemaValue: + return js_modify_function(schema_or_field, current_handler) + + current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) + + json_schema = current_handler(schema) + if _core_utils.is_core_schema(schema): + json_schema = populate_defs(schema, json_schema) + return json_schema + + def sort(self, value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue: + """Override this method to customize the sorting of the JSON schema (e.g., don't sort at all, sort all keys unconditionally, etc.) + + By default, alphabetically sort the keys in the JSON schema, skipping the 'properties' and 'default' keys to preserve field definition order. + This sort is recursive, so it will sort all nested dictionaries as well. + """ + sorted_dict: dict[str, JsonSchemaValue] = {} + keys = value.keys() + if parent_key not in ('properties', 'default'): + keys = sorted(keys) + for key in keys: + sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) + return sorted_dict + + def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any: + """Recursively sort a JSON schema value.""" + if isinstance(value, dict): + sorted_dict: dict[str, JsonSchemaValue] = {} + keys = value.keys() + if parent_key not in ('properties', 'default'): + keys = sorted(keys) + for key in keys: + sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) + return sorted_dict + elif isinstance(value, list): + sorted_list: list[JsonSchemaValue] = [self._sort_recursive(item, parent_key) for item in value] + return sorted_list + else: + return value + + # ### Schema generation methods + + def invalid_schema(self, schema: core_schema.InvalidSchema) -> JsonSchemaValue: + """Placeholder - should never be called.""" + + raise RuntimeError('Cannot generate schema for invalid_schema. This is a bug! Please report it.') + + def any_schema(self, schema: core_schema.AnySchema) -> JsonSchemaValue: + """Generates a JSON schema that matches any value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {} + + def none_schema(self, schema: core_schema.NoneSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches `None`. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'null'} + + def bool_schema(self, schema: core_schema.BoolSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a bool value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'boolean'} + + def int_schema(self, schema: core_schema.IntSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches an int value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema: dict[str, Any] = {'type': 'integer'} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric) + json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}} + return json_schema + + def float_schema(self, schema: core_schema.FloatSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a float value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema: dict[str, Any] = {'type': 'number'} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric) + json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}} + return json_schema + + def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a decimal value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + + def get_decimal_pattern(schema: core_schema.DecimalSchema) -> str: + max_digits = schema.get('max_digits') + decimal_places = schema.get('decimal_places') + + pattern = ( + r'^(?!^[-+.]*$)[+-]?0*' # check it is not empty string and not one or sequence of ".+-" characters. + ) + + # Case 1: Both max_digits and decimal_places are set + if max_digits is not None and decimal_places is not None: + integer_places = max(0, max_digits - decimal_places) + pattern += ( + rf'(?:' + rf'\d{{0,{integer_places}}}' + rf'|' + rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' + rf'\d{{0,{integer_places}}}\.\d{{0,{decimal_places}}}0*$' + rf')' + ) + + # Case 2: Only max_digits is set + elif max_digits is not None and decimal_places is None: + pattern += ( + rf'(?:' + rf'\d{{0,{max_digits}}}' + rf'|' + rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' + rf'\d*\.\d*0*$' + rf')' + ) + + # Case 3: Only decimal_places is set + elif max_digits is None and decimal_places is not None: + pattern += rf'\d*\.?\d{{0,{decimal_places}}}0*$' + + # Case 4: Both are None (no restrictions) + else: + pattern += r'\d*\.?\d*$' # look for arbitrary integer or decimal + + return pattern + + json_schema = self.str_schema(core_schema.str_schema(pattern=get_decimal_pattern(schema))) + if self.mode == 'validation': + multiple_of = schema.get('multiple_of') + le = schema.get('le') + ge = schema.get('ge') + lt = schema.get('lt') + gt = schema.get('gt') + json_schema = { + 'anyOf': [ + self.float_schema( + core_schema.float_schema( + allow_inf_nan=schema.get('allow_inf_nan'), + multiple_of=None if multiple_of is None else float(multiple_of), + le=None if le is None else float(le), + ge=None if ge is None else float(ge), + lt=None if lt is None else float(lt), + gt=None if gt is None else float(gt), + ) + ), + json_schema, + ], + } + return json_schema + + def str_schema(self, schema: core_schema.StringSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a string value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema = {'type': 'string'} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) + if isinstance(json_schema.get('pattern'), Pattern): + # TODO: should we add regex flags to the pattern? + json_schema['pattern'] = json_schema.get('pattern').pattern # type: ignore + return json_schema + + def bytes_schema(self, schema: core_schema.BytesSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a bytes value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema = {'type': 'string', 'format': 'base64url' if self._config.ser_json_bytes == 'base64' else 'binary'} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) + return json_schema + + def date_schema(self, schema: core_schema.DateSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a date value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'string', 'format': 'date'} + + def time_schema(self, schema: core_schema.TimeSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a time value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'string', 'format': 'time'} + + def datetime_schema(self, schema: core_schema.DatetimeSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a datetime value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'string', 'format': 'date-time'} + + def timedelta_schema(self, schema: core_schema.TimedeltaSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a timedelta value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + if self._config.ser_json_timedelta == 'float': + return {'type': 'number'} + return {'type': 'string', 'format': 'duration'} + + def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a literal value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + expected = [to_jsonable_python(v.value if isinstance(v, Enum) else v) for v in schema['expected']] + + result: dict[str, Any] = {} + if len(expected) == 1: + result['const'] = expected[0] + else: + result['enum'] = expected + + types = {type(e) for e in expected} + if types == {str}: + result['type'] = 'string' + elif types == {int}: + result['type'] = 'integer' + elif types == {float}: + result['type'] = 'number' + elif types == {bool}: + result['type'] = 'boolean' + elif types == {list}: + result['type'] = 'array' + elif types == {type(None)}: + result['type'] = 'null' + return result + + def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches the `MISSING` sentinel value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + raise PydanticOmit + + def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches an Enum value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + enum_type = schema['cls'] + description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) + if ( + description == 'An enumeration.' + ): # This is the default value provided by enum.EnumMeta.__new__; don't use it + description = None + result: dict[str, Any] = {'title': enum_type.__name__, 'description': description} + result = {k: v for k, v in result.items() if v is not None} + + expected = [to_jsonable_python(v.value) for v in schema['members']] + + result['enum'] = expected + + types = {type(e) for e in expected} + if isinstance(enum_type, str) or types == {str}: + result['type'] = 'string' + elif isinstance(enum_type, int) or types == {int}: + result['type'] = 'integer' + elif isinstance(enum_type, float) or types == {float}: + result['type'] = 'number' + elif types == {bool}: + result['type'] = 'boolean' + elif types == {list}: + result['type'] = 'array' + + return result + + def is_instance_schema(self, schema: core_schema.IsInstanceSchema) -> JsonSchemaValue: + """Handles JSON schema generation for a core schema that checks if a value is an instance of a class. + + Unless overridden in a subclass, this raises an error. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})') + + def is_subclass_schema(self, schema: core_schema.IsSubclassSchema) -> JsonSchemaValue: + """Handles JSON schema generation for a core schema that checks if a value is a subclass of a class. + + For backwards compatibility with v1, this does not raise an error, but can be overridden to change this. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + # Note: This is for compatibility with V1; you can override if you want different behavior. + return {} + + def callable_schema(self, schema: core_schema.CallableSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a callable value. + + Unless overridden in a subclass, this raises an error. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.handle_invalid_for_json_schema(schema, 'core_schema.CallableSchema') + + def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue: + """Returns a schema that matches a list schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) + json_schema = {'type': 'array', 'items': items_schema} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) + return json_schema + + @deprecated('`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', category=None) + @final + def tuple_positional_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: + """Replaced by `tuple_schema`.""" + warnings.warn( + '`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', + PydanticDeprecatedSince26, + stacklevel=2, + ) + return self.tuple_schema(schema) + + @deprecated('`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', category=None) + @final + def tuple_variable_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: + """Replaced by `tuple_schema`.""" + warnings.warn( + '`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', + PydanticDeprecatedSince26, + stacklevel=2, + ) + return self.tuple_schema(schema) + + def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a tuple schema e.g. `tuple[int, + str, bool]` or `tuple[int, ...]`. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema: JsonSchemaValue = {'type': 'array'} + if 'variadic_item_index' in schema: + variadic_item_index = schema['variadic_item_index'] + if variadic_item_index > 0: + json_schema['minItems'] = variadic_item_index + json_schema['prefixItems'] = [ + self.generate_inner(item) for item in schema['items_schema'][:variadic_item_index] + ] + if variadic_item_index + 1 == len(schema['items_schema']): + # if the variadic item is the last item, then represent it faithfully + json_schema['items'] = self.generate_inner(schema['items_schema'][variadic_item_index]) + else: + # otherwise, 'items' represents the schema for the variadic + # item plus the suffix, so just allow anything for simplicity + # for now + json_schema['items'] = True + else: + prefixItems = [self.generate_inner(item) for item in schema['items_schema']] + if prefixItems: + json_schema['prefixItems'] = prefixItems + json_schema['minItems'] = len(prefixItems) + json_schema['maxItems'] = len(prefixItems) + self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) + return json_schema + + def set_schema(self, schema: core_schema.SetSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a set schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self._common_set_schema(schema) + + def frozenset_schema(self, schema: core_schema.FrozenSetSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a frozenset schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self._common_set_schema(schema) + + def _common_set_schema(self, schema: core_schema.SetSchema | core_schema.FrozenSetSchema) -> JsonSchemaValue: + items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) + json_schema = {'type': 'array', 'uniqueItems': True, 'items': items_schema} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) + return json_schema + + def generator_schema(self, schema: core_schema.GeneratorSchema) -> JsonSchemaValue: + """Returns a JSON schema that represents the provided GeneratorSchema. + + Args: + schema: The schema. + + Returns: + The generated JSON schema. + """ + items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) + json_schema = {'type': 'array', 'items': items_schema} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) + return json_schema + + def dict_schema(self, schema: core_schema.DictSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a dict schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema: JsonSchemaValue = {'type': 'object'} + + keys_schema = self.generate_inner(schema['keys_schema']).copy() if 'keys_schema' in schema else {} + if '$ref' not in keys_schema: + keys_pattern = keys_schema.pop('pattern', None) + # Don't give a title to patternProperties/propertyNames: + keys_schema.pop('title', None) + else: + # Here, we assume that if the keys schema is a definition reference, + # it can't be a simple string core schema (and thus no pattern can exist). + # However, this is only in practice (in theory, a definition reference core + # schema could be generated for a simple string schema). + # Note that we avoid calling `self.resolve_ref_schema`, as it might not exist yet. + keys_pattern = None + + values_schema = self.generate_inner(schema['values_schema']).copy() if 'values_schema' in schema else {} + # don't give a title to additionalProperties: + values_schema.pop('title', None) + + if values_schema or keys_pattern is not None: + if keys_pattern is None: + json_schema['additionalProperties'] = values_schema + else: + json_schema['patternProperties'] = {keys_pattern: values_schema} + else: # for `dict[str, Any]`, we allow any key and any value, since `str` is the default key type + json_schema['additionalProperties'] = True + + if ( + # The len check indicates that constraints are probably present: + (keys_schema.get('type') == 'string' and len(keys_schema) > 1) + # If this is a definition reference schema, it most likely has constraints: + or '$ref' in keys_schema + ): + keys_schema.pop('type', None) + json_schema['propertyNames'] = keys_schema + + self.update_with_validations(json_schema, schema, self.ValidationsMapping.object) + return json_schema + + def function_before_schema(self, schema: core_schema.BeforeValidatorFunctionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a function-before schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): + return self.generate_inner(input_schema) + + return self.generate_inner(schema['schema']) + + def function_after_schema(self, schema: core_schema.AfterValidatorFunctionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a function-after schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['schema']) + + def function_plain_schema(self, schema: core_schema.PlainValidatorFunctionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a function-plain schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): + return self.generate_inner(input_schema) + + return self.handle_invalid_for_json_schema( + schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})' + ) + + def function_wrap_schema(self, schema: core_schema.WrapValidatorFunctionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a function-wrap schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): + return self.generate_inner(input_schema) + + return self.generate_inner(schema['schema']) + + def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema with a default value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema = self.generate_inner(schema['schema']) + + default = self.get_default_value(schema) + if default is NoDefault or default is MISSING: + return json_schema + + # we reflect the application of custom plain, no-info serializers to defaults for + # JSON Schemas viewed in serialization mode: + # TODO: improvements along with https://github.com/pydantic/pydantic/issues/8208 + if self.mode == 'serialization': + # `_get_ser_schema_for_default_value()` is used to unpack potentially nested validator schemas: + ser_schema = _get_ser_schema_for_default_value(schema['schema']) + if ( + ser_schema is not None + and (ser_func := ser_schema.get('function')) + and not (default is None and ser_schema.get('when_used') in ('unless-none', 'json-unless-none')) + ): + try: + default = ser_func(default) # type: ignore + except Exception: + # It might be that the provided default needs to be validated (read: parsed) first + # (assuming `validate_default` is enabled). However, we can't perform + # such validation during JSON Schema generation so we don't support + # this pattern for now. + # (One example is when using `foo: ByteSize = '1MB'`, which validates and + # serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails). + self.emit_warning( + 'non-serializable-default', + f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema', + ) + return json_schema + + # Sort set/frozenset defaults to ensure deterministic JSON schema generation + # We only sort if len > 1 because sets of size 0 or 1 are already deterministic + if isinstance(default, collections.abc.Set) and len(default) > 1: + try: + default = sorted(default) + except TypeError: # pragma: no cover + # If items aren't comparable (e.g. mixed types), we can't sort them. + pass + + try: + encoded_default = self.encode_default(default) + except pydantic_core.PydanticSerializationError: + self.emit_warning( + 'non-serializable-default', + f'Default value {default} is not JSON serializable; excluding default from JSON schema', + ) + # Return the inner schema, as though there was no default + return json_schema + + json_schema['default'] = encoded_default + return json_schema + + def get_default_value(self, schema: core_schema.WithDefaultSchema) -> Any: + """Get the default value to be used when generating a JSON Schema for a core schema with a default. + + The default implementation is to use the statically defined default value. This method can be overridden + if you want to make use of the default factory. + + Args: + schema: The `'with-default'` core schema. + + Returns: + The default value to use, or [`NoDefault`][pydantic.json_schema.NoDefault] if no default + value is available. + """ + return schema.get('default', NoDefault) + + def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that allows null values. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + null_schema = {'type': 'null'} + inner_json_schema = self.generate_inner(schema['schema']) + + if inner_json_schema == null_schema: + return null_schema + else: + return self.get_union_of_schemas([inner_json_schema, null_schema]) + + def union_schema(self, schema: core_schema.UnionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that allows values matching any of the given schemas. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + generated: list[JsonSchemaValue] = [] + + for choice in core_schema.iter_union_choices(schema): + try: + generated.append(self.generate_inner(choice)) + except PydanticOmit: # noqa: PERF203 + continue + except PydanticInvalidForJsonSchema as exc: + self.emit_warning('skipped-choice', exc.message) + if len(generated) == 1: + return generated[0] + return self.get_union_of_schemas(generated) + + def get_union_of_schemas(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: + """Returns the JSON Schema representation for the union of the provided JSON Schemas. + + The result depends on the configured `'union_format'`. + + Args: + schemas: The list of JSON Schemas to be included in the union. + + Returns: + The JSON Schema representing the union of schemas. + """ + if self.union_format == 'primitive_type_array': + types: list[str] = [] + for schema in schemas: + schema_types: list[str] | str | None = schema.get('type') + if schema_types is None: + # No type, meaning it can be a ref or an empty schema. + break + if not isinstance(schema_types, list): + schema_types = [schema_types] + if not all(t in _PRIMITIVE_JSON_SCHEMA_TYPES for t in schema_types): + break + if len(schema) != 1: + # We only want to include types that don't have any constraints. For instance, + # if `schemas = [{'type': 'string', 'maxLength': 3}, {'type': 'string', 'minLength': 5}]`, + # we don't want to produce `{'type': 'string', 'maxLength': 3, 'minLength': 5}`. + # Same if we have some metadata (e.g. `title`) on a specific union member, we want to preserve it. + break + + types.extend(schema_types) + else: + # If we got there, all the schemas where valid to be used with the `'primitive_type_array` format + return {'type': list(dict.fromkeys(types))} + + return self.get_flattened_anyof(schemas) + + def tagged_union_schema(self, schema: core_schema.TaggedUnionSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that allows values matching any of the given schemas, where + the schemas are tagged with a discriminator field that indicates which schema should be used to validate + the value. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + generated: dict[str, JsonSchemaValue] = {} + for k, v in schema['choices'].items(): + if isinstance(k, Enum): + k = k.value + try: + # Use str(k) since keys must be strings for json; while not technically correct, + # it's the closest that can be represented in valid JSON + generated[str(k)] = self.generate_inner(v).copy() + except PydanticOmit: + continue + except PydanticInvalidForJsonSchema as exc: + self.emit_warning('skipped-choice', exc.message) + + one_of_choices = _deduplicate_schemas(generated.values()) + json_schema: JsonSchemaValue = {'oneOf': one_of_choices} + + # This reflects the v1 behavior; TODO: we should make it possible to exclude OpenAPI stuff from the JSON schema + openapi_discriminator = self._extract_discriminator(schema, one_of_choices) + if openapi_discriminator is not None: + json_schema['discriminator'] = { + 'propertyName': openapi_discriminator, + 'mapping': {k: v.get('$ref', v) for k, v in generated.items()}, + } + + return json_schema + + def _extract_discriminator( + self, schema: core_schema.TaggedUnionSchema, one_of_choices: list[JsonDict] + ) -> str | None: + """Extract a compatible OpenAPI discriminator from the schema and one_of choices that end up in the final + schema.""" + openapi_discriminator: str | None = None + + if isinstance(schema['discriminator'], str): + return schema['discriminator'] + + if isinstance(schema['discriminator'], list): + # If the discriminator is a single item list containing a string, that is equivalent to the string case + if len(schema['discriminator']) == 1 and isinstance(schema['discriminator'][0], str): + return schema['discriminator'][0] + # When an alias is used that is different from the field name, the discriminator will be a list of single + # str lists, one for the attribute and one for the actual alias. The logic here will work even if there is + # more than one possible attribute, and looks for whether a single alias choice is present as a documented + # property on all choices. If so, that property will be used as the OpenAPI discriminator. + for alias_path in schema['discriminator']: + if not isinstance(alias_path, list): + break # this means that the discriminator is not a list of alias paths + if len(alias_path) != 1: + continue # this means that the "alias" does not represent a single field + alias = alias_path[0] + if not isinstance(alias, str): + continue # this means that the "alias" does not represent a field + alias_is_present_on_all_choices = True + for choice in one_of_choices: + try: + choice = self.resolve_ref_schema(choice) + except RuntimeError as exc: + # TODO: fixme - this is a workaround for the fact that we can't always resolve refs + # for tagged union choices at this point in the schema gen process, we might need to do + # another pass at the end like we do for core schemas + self.emit_warning('skipped-discriminator', str(exc)) + choice = {} + properties = choice.get('properties', {}) + if not isinstance(properties, dict) or alias not in properties: + alias_is_present_on_all_choices = False + break + if alias_is_present_on_all_choices: + openapi_discriminator = alias + break + return openapi_discriminator + + def chain_schema(self, schema: core_schema.ChainSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a core_schema.ChainSchema. + + When generating a schema for validation, we return the validation JSON schema for the first step in the chain. + For serialization, we return the serialization JSON schema for the last step in the chain. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + step_index = 0 if self.mode == 'validation' else -1 # use first step for validation, last for serialization + return self.generate_inner(schema['steps'][step_index]) + + def lax_or_strict_schema(self, schema: core_schema.LaxOrStrictSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that allows values matching either the lax schema or the + strict schema. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + # TODO: Need to read the default value off of model config or whatever + use_strict = schema.get('strict', False) # TODO: replace this default False + # If your JSON schema fails to generate it is probably + # because one of the following two branches failed. + if use_strict: + return self.generate_inner(schema['strict_schema']) + else: + return self.generate_inner(schema['lax_schema']) + + def json_or_python_schema(self, schema: core_schema.JsonOrPythonSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that allows values matching either the JSON schema or the + Python schema. + + The JSON schema is used instead of the Python schema. If you want to use the Python schema, you should override + this method. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['json_schema']) + + def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a typed dict. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + total = schema.get('total', True) + named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ + (name, self.field_is_required(field, total), field) + for name, field in schema['fields'].items() + if self.field_is_present(field) + ] + if self.mode == 'serialization': + named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) + cls = schema.get('cls') + config = _get_typed_dict_config(cls) + with self._config_wrapper_stack.push(config): + json_schema = self._named_required_fields_schema(named_required_fields) + + # There's some duplication between `extra_behavior` and + # the config's `extra`/core config's `extra_fields_behavior`. + # However, it is common to manually create TypedDictSchemas, + # where you don't necessarily have a class. + # At runtime, `extra_behavior` takes priority over the config + # for validation, so follow the same for the JSON Schema: + if 'extras_schema' in schema and schema['extras_schema'] != core_schema.any_schema(): + allow_additional_props = self.generate_inner(schema['extras_schema']) + else: + allow_additional_props = True + + if schema.get('extra_behavior') == 'forbid': + json_schema['additionalProperties'] = False + elif schema.get('extra_behavior') == 'allow': + json_schema['additionalProperties'] = allow_additional_props + + if cls is not None: + # `_update_class_schema()` will not override + # `additionalProperties` if already present: + self._update_class_schema(json_schema, cls, config) + elif 'additionalProperties' not in json_schema: + extra = schema.get('config', {}).get('extra_fields_behavior') + if extra == 'forbid': + json_schema['additionalProperties'] = False + elif extra == 'allow': + json_schema['additionalProperties'] = allow_additional_props + + return json_schema + + @staticmethod + def _name_required_computed_fields( + computed_fields: list[ComputedField], + ) -> list[tuple[str, bool, core_schema.ComputedField]]: + return [(field['property_name'], True, field) for field in computed_fields] + + def _named_required_fields_schema( + self, named_required_fields: Sequence[tuple[str, bool, CoreSchemaField]] + ) -> JsonSchemaValue: + properties: dict[str, JsonSchemaValue] = {} + required_fields: list[str] = [] + for name, required, field in named_required_fields: + if self.by_alias: + name = self._get_alias_name(field, name) + try: + field_json_schema = self.generate_inner(field).copy() + except PydanticOmit: + continue + if 'title' not in field_json_schema and self.field_title_should_be_set(field): + title = self.get_title_from_name(name) + field_json_schema['title'] = title + field_json_schema = self.handle_ref_overrides(field_json_schema) + properties[name] = field_json_schema + if required: + required_fields.append(name) + + json_schema = {'type': 'object', 'properties': properties} + if required_fields: + json_schema['required'] = required_fields + return json_schema + + def _get_alias_name(self, field: CoreSchemaField, name: str) -> str: + if field['type'] == 'computed-field': + alias: Any = field.get('alias', name) + elif self.mode == 'validation': + alias = field.get('validation_alias', name) + else: + alias = field.get('serialization_alias', name) + if isinstance(alias, str): + name = alias + elif isinstance(alias, list): + alias = cast('list[str] | str', alias) + for path in alias: + if isinstance(path, list) and len(path) == 1 and isinstance(path[0], str): + # Use the first valid single-item string path; the code that constructs the alias array + # should ensure the first such item is what belongs in the JSON schema + name = path[0] + break + else: + assert_never(alias) + return name + + def typed_dict_field_schema(self, schema: core_schema.TypedDictField) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a typed dict field. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['schema']) + + def dataclass_field_schema(self, schema: core_schema.DataclassField) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a dataclass field. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['schema']) + + def model_field_schema(self, schema: core_schema.ModelField) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a model field. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['schema']) + + def computed_field_schema(self, schema: core_schema.ComputedField) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a computed field. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['return_schema']) + + def model_schema(self, schema: core_schema.ModelSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a model. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + # We do not use schema['model'].model_json_schema() here + # because it could lead to inconsistent refs handling, etc. + cls = cast('type[BaseModel]', schema['cls']) + config = cls.model_config + + with self._config_wrapper_stack.push(config): + json_schema = self.generate_inner(schema['schema']) + + self._update_class_schema(json_schema, cls, config) + + return json_schema + + def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None: + """Update json_schema with the following, extracted from `config` and `cls`: + + * title + * description + * additional properties + * json_schema_extra + * deprecated + + Done in place, hence there's no return value as the original json_schema is mutated. + No ref resolving is involved here, as that's not appropriate for simple updates. + """ + from ._internal._dataclasses import is_stdlib_dataclass + from .main import BaseModel + + if (config_title := config.get('title')) is not None: + json_schema.setdefault('title', config_title) + elif model_title_generator := config.get('model_title_generator'): + title = model_title_generator(cls) + if not isinstance(title, str): + raise TypeError(f'model_title_generator {model_title_generator} must return str, not {title.__class__}') + json_schema.setdefault('title', title) + if 'title' not in json_schema: + json_schema['title'] = cls.__name__ + + # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default + if cls is BaseModel: + docstring = None + elif is_stdlib_dataclass(cls): # For Pydantic dataclasses, we already handle this at class creation + # The `dataclass` module generates a `__doc__` based on the `inspect.signature()` + # result, which we don't want to use as a description. Such `__doc__` startswith + # `cls.__name__(`, which could lead to mistakenly discarding it if for some reason + # an explicitly set class docstring follows the same pattern, but this is unlikely + # to happen. + doc = cls.__doc__ + docstring = None if doc is None or doc.startswith(f'{cls.__name__}(') else doc + else: + docstring = cls.__doc__ + + if docstring: + json_schema.setdefault('description', inspect.cleandoc(docstring)) + + extra = config.get('extra') + if 'additionalProperties' not in json_schema: # This check is particularly important for `typed_dict_schema()` + if extra == 'allow': + json_schema['additionalProperties'] = True + elif extra == 'forbid': + json_schema['additionalProperties'] = False + + json_schema_extra = config.get('json_schema_extra') + if issubclass(cls, BaseModel) and cls.__pydantic_root_model__: + root_json_schema_extra = cls.model_fields['root'].json_schema_extra + if json_schema_extra and root_json_schema_extra: + raise ValueError( + '"model_config[\'json_schema_extra\']" and "Field.json_schema_extra" on "RootModel.root"' + ' field must not be set simultaneously' + ) + if root_json_schema_extra: + json_schema_extra = root_json_schema_extra + + if isinstance(json_schema_extra, (staticmethod, classmethod)): + # In older versions of python, this is necessary to ensure staticmethod/classmethods are callable + json_schema_extra = json_schema_extra.__get__(cls) + + if isinstance(json_schema_extra, dict): + json_schema.update(json_schema_extra) + elif callable(json_schema_extra): + if len(_typing_extra.signature_no_eval(json_schema_extra).parameters) > 1: + json_schema_extra = cast(Callable[[JsonDict, type[Any]], None], json_schema_extra) + json_schema_extra(json_schema, cls) + else: + json_schema_extra = cast(Callable[[JsonDict], None], json_schema_extra) + json_schema_extra(json_schema) + elif json_schema_extra is not None: + raise ValueError( + f"model_config['json_schema_extra']={json_schema_extra} should be a dict, callable, or None" + ) + + if hasattr(cls, '__deprecated__'): + json_schema['deprecated'] = True + + def resolve_ref_schema(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: + """Resolve a JsonSchemaValue to the non-ref schema if it is a $ref schema. + + Args: + json_schema: The schema to resolve. + + Returns: + The resolved schema. + + Raises: + RuntimeError: If the schema reference can't be found in definitions. + """ + while '$ref' in json_schema: + ref = json_schema['$ref'] + schema_to_update = self.get_schema_from_definitions(JsonRef(ref)) + if schema_to_update is None: + raise RuntimeError(f'Cannot update undefined schema for $ref={ref}') + json_schema = schema_to_update + return json_schema + + def model_fields_schema(self, schema: core_schema.ModelFieldsSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a model's fields. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ + (name, self.field_is_required(field, total=True), field) + for name, field in schema['fields'].items() + if self.field_is_present(field) + ] + if self.mode == 'serialization': + named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) + json_schema = self._named_required_fields_schema(named_required_fields) + extras_schema = schema.get('extras_schema', None) + if extras_schema is not None: + schema_to_update = self.resolve_ref_schema(json_schema) + schema_to_update['additionalProperties'] = self.generate_inner(extras_schema) + return json_schema + + def field_is_present(self, field: CoreSchemaField) -> bool: + """Whether the field should be included in the generated JSON schema. + + Args: + field: The schema for the field itself. + + Returns: + `True` if the field should be included in the generated JSON schema, `False` otherwise. + """ + if self.mode == 'serialization': + # If you still want to include the field in the generated JSON schema, + # override this method and return True + return not field.get('serialization_exclude') + elif self.mode == 'validation': + return True + else: + assert_never(self.mode) + + def field_is_required( + self, + field: core_schema.ModelField | core_schema.DataclassField | core_schema.TypedDictField, + total: bool, + ) -> bool: + """Whether the field should be marked as required in the generated JSON schema. + (Note that this is irrelevant if the field is not present in the JSON schema.). + + Args: + field: The schema for the field itself. + total: Only applies to `TypedDictField`s. + Indicates if the `TypedDict` this field belongs to is total, in which case any fields that don't + explicitly specify `required=False` are required. + + Returns: + `True` if the field should be marked as required in the generated JSON schema, `False` otherwise. + """ + if field['type'] == 'typed-dict-field': + required = field.get('required', total) + else: + required = field['schema']['type'] != 'default' + + if self.mode == 'serialization': + has_exclude_if = field.get('serialization_exclude_if') is not None + if self._config.json_schema_serialization_defaults_required: + return not has_exclude_if + else: + return required and not has_exclude_if + else: + return required + + def dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a dataclass's constructor arguments. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ + (field['name'], self.field_is_required(field, total=True), field) + for field in schema['fields'] + if self.field_is_present(field) + ] + if self.mode == 'serialization': + named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) + return self._named_required_fields_schema(named_required_fields) + + def dataclass_schema(self, schema: core_schema.DataclassSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a dataclass. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + + cls = schema['cls'] + config = cast('ConfigDict', getattr(cls, '__pydantic_config__', {})) + + with self._config_wrapper_stack.push(config): + json_schema = self.generate_inner(schema['schema']).copy() + + self._update_class_schema(json_schema, cls, config) + + return json_schema + + def arguments_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a function's arguments. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + prefer_positional = schema.get('metadata', {}).get('pydantic_js_prefer_positional_arguments') + + arguments = schema['arguments_schema'] + kw_only_arguments = [a for a in arguments if a.get('mode') == 'keyword_only'] + kw_or_p_arguments = [a for a in arguments if a.get('mode') in {'positional_or_keyword', None}] + p_only_arguments = [a for a in arguments if a.get('mode') == 'positional_only'] + var_args_schema = schema.get('var_args_schema') + var_kwargs_schema = schema.get('var_kwargs_schema') + + if prefer_positional: + positional_possible = not kw_only_arguments and not var_kwargs_schema + if positional_possible: + return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema) + + keyword_possible = not p_only_arguments and not var_args_schema + if keyword_possible: + return self.kw_arguments_schema(kw_or_p_arguments + kw_only_arguments, var_kwargs_schema) + + if not prefer_positional: + positional_possible = not kw_only_arguments and not var_kwargs_schema + if positional_possible: + return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema) + + raise PydanticInvalidForJsonSchema( + 'Unable to generate JSON schema for arguments validator with positional-only and keyword-only arguments' + ) + + def kw_arguments_schema( + self, arguments: list[core_schema.ArgumentsParameter], var_kwargs_schema: CoreSchema | None + ) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a function's keyword arguments. + + Args: + arguments: The core schema. + + Returns: + The generated JSON schema. + """ + properties: dict[str, JsonSchemaValue] = {} + required: list[str] = [] + for argument in arguments: + name = self.get_argument_name(argument) + argument_schema = self.generate_inner(argument['schema']).copy() + if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): + argument_schema['title'] = self.get_title_from_name(name) + properties[name] = argument_schema + + if argument['schema']['type'] != 'default': + # This assumes that if the argument has a default value, + # the inner schema must be of type WithDefaultSchema. + # I believe this is true, but I am not 100% sure + required.append(name) + + json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties} + if required: + json_schema['required'] = required + + if var_kwargs_schema: + additional_properties_schema = self.generate_inner(var_kwargs_schema) + if additional_properties_schema: + json_schema['additionalProperties'] = additional_properties_schema + else: + json_schema['additionalProperties'] = False + return json_schema + + def p_arguments_schema( + self, arguments: list[core_schema.ArgumentsParameter], var_args_schema: CoreSchema | None + ) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a function's positional arguments. + + Args: + arguments: The core schema. + + Returns: + The generated JSON schema. + """ + prefix_items: list[JsonSchemaValue] = [] + min_items = 0 + + for argument in arguments: + name = self.get_argument_name(argument) + + argument_schema = self.generate_inner(argument['schema']).copy() + if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): + argument_schema['title'] = self.get_title_from_name(name) + prefix_items.append(argument_schema) + + if argument['schema']['type'] != 'default': + # This assumes that if the argument has a default value, + # the inner schema must be of type WithDefaultSchema. + # I believe this is true, but I am not 100% sure + min_items += 1 + + json_schema: JsonSchemaValue = {'type': 'array'} + if prefix_items: + json_schema['prefixItems'] = prefix_items + if min_items: + json_schema['minItems'] = min_items + + if var_args_schema: + items_schema = self.generate_inner(var_args_schema) + if items_schema: + json_schema['items'] = items_schema + else: + json_schema['maxItems'] = len(prefix_items) + + return json_schema + + def get_argument_name(self, argument: core_schema.ArgumentsParameter | core_schema.ArgumentsV3Parameter) -> str: + """Retrieves the name of an argument. + + Args: + argument: The core schema. + + Returns: + The name of the argument. + """ + name = argument['name'] + if self.by_alias: + alias = argument.get('alias') + if isinstance(alias, str): + name = alias + else: + pass # might want to do something else? + return name + + def arguments_v3_schema(self, schema: core_schema.ArgumentsV3Schema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a function's arguments. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + arguments = schema['arguments_schema'] + properties: dict[str, JsonSchemaValue] = {} + required: list[str] = [] + for argument in arguments: + mode = argument.get('mode', 'positional_or_keyword') + name = self.get_argument_name(argument) + argument_schema = self.generate_inner(argument['schema']).copy() + if mode == 'var_args': + argument_schema = {'type': 'array', 'items': argument_schema} + elif mode == 'var_kwargs_uniform': + argument_schema = {'type': 'object', 'additionalProperties': argument_schema} + + argument_schema.setdefault('title', self.get_title_from_name(name)) + properties[name] = argument_schema + + if ( + (mode == 'var_kwargs_unpacked_typed_dict' and 'required' in argument_schema) + or mode not in {'var_args', 'var_kwargs_uniform', 'var_kwargs_unpacked_typed_dict'} + and argument['schema']['type'] != 'default' + ): + # This assumes that if the argument has a default value, + # the inner schema must be of type WithDefaultSchema. + # I believe this is true, but I am not 100% sure + required.append(name) + + json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties} + if required: + json_schema['required'] = required + return json_schema + + def call_schema(self, schema: core_schema.CallSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a function call. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['arguments_schema']) + + def custom_error_schema(self, schema: core_schema.CustomErrorSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a custom error. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return self.generate_inner(schema['schema']) + + def json_schema(self, schema: core_schema.JsonSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a JSON object. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + content_core_schema = schema.get('schema') or core_schema.any_schema() + content_json_schema = self.generate_inner(content_core_schema) + if self.mode == 'validation': + return {'type': 'string', 'contentMediaType': 'application/json', 'contentSchema': content_json_schema} + else: + # self.mode == 'serialization' + return content_json_schema + + def url_schema(self, schema: core_schema.UrlSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a URL. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + json_schema = {'type': 'string', 'format': 'uri', 'minLength': 1} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) + return json_schema + + def multi_host_url_schema(self, schema: core_schema.MultiHostUrlSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a URL that can be used with multiple hosts. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + # Note: 'multi-host-uri' is a custom/pydantic-specific format, not part of the JSON Schema spec + json_schema = {'type': 'string', 'format': 'multi-host-uri', 'minLength': 1} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) + return json_schema + + def uuid_schema(self, schema: core_schema.UuidSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a UUID. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'string', 'format': 'uuid'} + + def definitions_schema(self, schema: core_schema.DefinitionsSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that defines a JSON object with definitions. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + for definition in schema['definitions']: + try: + self.generate_inner(definition) + except PydanticInvalidForJsonSchema as e: # noqa: PERF203 + core_ref: CoreRef = CoreRef(definition['ref']) # type: ignore + self._core_defs_invalid_for_json_schema[self.get_defs_ref((core_ref, self.mode))] = e + continue + return self.generate_inner(schema['schema']) + + def definition_ref_schema(self, schema: core_schema.DefinitionReferenceSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a schema that references a definition. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + core_ref = CoreRef(schema['schema_ref']) + _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) + return ref_json_schema + + def ser_schema( + self, schema: core_schema.SerSchema | core_schema.IncExSeqSerSchema | core_schema.IncExDictSerSchema + ) -> JsonSchemaValue | None: + """Generates a JSON schema that matches a schema that defines a serialized object. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + schema_type = schema['type'] + if schema_type == 'function-plain' or schema_type == 'function-wrap': + # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema + return_schema = schema.get('return_schema') + if return_schema is not None: + return self.generate_inner(return_schema) + elif schema_type == 'format' or schema_type == 'to-string': + # FormatSerSchema or ToStringSerSchema + return self.str_schema(core_schema.str_schema()) + elif schema['type'] == 'model': + # ModelSerSchema + return self.generate_inner(schema['schema']) + return None + + def complex_schema(self, schema: core_schema.ComplexSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a complex number. + + JSON has no standard way to represent complex numbers. Complex number is not a numeric + type. Here we represent complex number as strings following the rule defined by Python. + For instance, '1+2j' is an accepted complex string. Details can be found in + [Python's `complex` documentation][complex]. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + return {'type': 'string'} + + # ### Utility methods + + def get_title_from_name(self, name: str) -> str: + """Retrieves a title from a name. + + Args: + name: The name to retrieve a title from. + + Returns: + The title. + """ + return name.title().replace('_', ' ').strip() + + def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool: + """Returns true if a field with the given schema should have a title set based on the field name. + + Intuitively, we want this to return true for schemas that wouldn't otherwise provide their own title + (e.g., int, float, str), and false for those that would (e.g., BaseModel subclasses). + + Args: + schema: The schema to check. + + Returns: + `True` if the field should have a title set, `False` otherwise. + """ + if _core_utils.is_core_schema_field(schema): + if schema['type'] == 'computed-field': + field_schema = schema['return_schema'] + else: + field_schema = schema['schema'] + return self.field_title_should_be_set(field_schema) + + elif _core_utils.is_core_schema(schema): + if schema.get('ref'): # things with refs, such as models and enums, should not have titles set + return False + if schema['type'] in {'default', 'nullable', 'definitions'}: + return self.field_title_should_be_set(schema['schema']) # type: ignore[typeddict-item] + if _core_utils.is_function_with_inner_schema(schema): + return self.field_title_should_be_set(schema['schema']) + if schema['type'] == 'definition-ref': + # Referenced schemas should not have titles set for the same reason + # schemas with refs should not + return False + return True # anything else should have title set + + else: + raise PydanticInvalidForJsonSchema(f'Unexpected schema type: schema={schema}') # pragma: no cover + + def normalize_name(self, name: str) -> str: + """Normalizes a name to be used as a key in a dictionary. + + Args: + name: The name to normalize. + + Returns: + The normalized name. + """ + return re.sub(r'[^a-zA-Z0-9.\-_]', '_', name).replace('.', '__') + + def get_defs_ref(self, core_mode_ref: CoreModeRef) -> DefsRef: + """Override this method to change the way that definitions keys are generated from a core reference. + + Args: + core_mode_ref: The core reference. + + Returns: + The definitions key. + """ + # Split the core ref into "components"; generic origins and arguments are each separate components + core_ref, mode = core_mode_ref + components = re.split(r'([\][,])', core_ref) + # Remove IDs from each component + components = [x.rsplit(':', 1)[0] for x in components] + core_ref_no_id = ''.join(components) + # Remove everything before the last period from each "component" + components = [re.sub(r'(?:[^.[\]]+\.)+((?:[^.[\]]+))', r'\1', x) for x in components] + short_ref = ''.join(components) + + mode_title = _MODE_TITLE_MAPPING[mode] + + # It is important that the generated defs_ref values be such that at least one choice will not + # be generated for any other core_ref. Currently, this should be the case because we include + # the id of the source type in the core_ref + name = DefsRef(self.normalize_name(short_ref)) + name_mode = DefsRef(self.normalize_name(short_ref) + f'-{mode_title}') + module_qualname = DefsRef(self.normalize_name(core_ref_no_id)) + module_qualname_mode = DefsRef(f'{module_qualname}-{mode_title}') + module_qualname_id = DefsRef(self.normalize_name(core_ref)) + occurrence_index = self._collision_index.get(module_qualname_id) + if occurrence_index is None: + self._collision_counter[module_qualname] += 1 + occurrence_index = self._collision_index[module_qualname_id] = self._collision_counter[module_qualname] + + module_qualname_occurrence = DefsRef(f'{module_qualname}__{occurrence_index}') + module_qualname_occurrence_mode = DefsRef(f'{module_qualname_mode}__{occurrence_index}') + + self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [ + name, + name_mode, + module_qualname, + module_qualname_mode, + module_qualname_occurrence, + module_qualname_occurrence_mode, + ] + + return module_qualname_occurrence_mode + + def get_cache_defs_ref_schema(self, core_ref: CoreRef) -> tuple[DefsRef, JsonSchemaValue]: + """This method wraps the get_defs_ref method with some cache-lookup/population logic, + and returns both the produced defs_ref and the JSON schema that will refer to the right definition. + + Args: + core_ref: The core reference to get the definitions reference for. + + Returns: + A tuple of the definitions reference and the JSON schema that will refer to it. + """ + core_mode_ref = (core_ref, self.mode) + maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref) + if maybe_defs_ref is not None: + json_ref = self.core_to_json_refs[core_mode_ref] + return maybe_defs_ref, {'$ref': json_ref} + + defs_ref = self.get_defs_ref(core_mode_ref) + + # populate the ref translation mappings + self.core_to_defs_refs[core_mode_ref] = defs_ref + self.defs_to_core_refs[defs_ref] = core_mode_ref + + json_ref = JsonRef(self.ref_template.format(model=defs_ref)) + self.core_to_json_refs[core_mode_ref] = json_ref + self.json_to_defs_refs[json_ref] = defs_ref + ref_json_schema = {'$ref': json_ref} + return defs_ref, ref_json_schema + + def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: + """Remove any sibling keys that are redundant with the referenced schema. + + Args: + json_schema: The schema to remove redundant sibling keys from. + + Returns: + The schema with redundant sibling keys removed. + """ + if '$ref' in json_schema: + # prevent modifications to the input; this copy may be safe to drop if there is significant overhead + json_schema = json_schema.copy() + + referenced_json_schema = self.get_schema_from_definitions(JsonRef(json_schema['$ref'])) + if referenced_json_schema is None: + # This can happen when building schemas for models with not-yet-defined references. + # It may be a good idea to do a recursive pass at the end of the generation to remove + # any redundant override keys. + return json_schema + for k, v in list(json_schema.items()): + if k == '$ref': + continue + if k in referenced_json_schema and referenced_json_schema[k] == v: + del json_schema[k] # redundant key + + return json_schema + + def get_schema_from_definitions(self, json_ref: JsonRef) -> JsonSchemaValue | None: + try: + def_ref = self.json_to_defs_refs[json_ref] + if def_ref in self._core_defs_invalid_for_json_schema: + raise self._core_defs_invalid_for_json_schema[def_ref] + return self.definitions.get(def_ref, None) + except KeyError: + if json_ref.startswith(('http://', 'https://')): + return None + raise + + def encode_default(self, dft: Any) -> Any: + """Encode a default value to a JSON-serializable value. + + This is used to encode default values for fields in the generated JSON schema. + + Args: + dft: The default value to encode. + + Returns: + The encoded default value. + """ + from .type_adapter import TypeAdapter, _type_has_config + + config = self._config + try: + default = ( + dft + if _type_has_config(type(dft)) + else TypeAdapter(type(dft), config=config.config_dict).dump_python( + dft, by_alias=self.by_alias, mode='json' + ) + ) + except PydanticSchemaGenerationError: + raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}') + + return pydantic_core.to_jsonable_python( + default, timedelta_mode=config.ser_json_timedelta, bytes_mode=config.ser_json_bytes, by_alias=self.by_alias + ) + + def update_with_validations( + self, json_schema: JsonSchemaValue, core_schema: CoreSchema, mapping: dict[str, str] + ) -> None: + """Update the json_schema with the corresponding validations specified in the core_schema, + using the provided mapping to translate keys in core_schema to the appropriate keys for a JSON schema. + + Args: + json_schema: The JSON schema to update. + core_schema: The core schema to get the validations from. + mapping: A mapping from core_schema attribute names to the corresponding JSON schema attribute names. + """ + for core_key, json_schema_key in mapping.items(): + if core_key in core_schema: + json_schema[json_schema_key] = core_schema[core_key] + + class ValidationsMapping: + """This class just contains mappings from core_schema attribute names to the corresponding + JSON schema attribute names. While I suspect it is unlikely to be necessary, you can in + principle override this class in a subclass of GenerateJsonSchema (by inheriting from + GenerateJsonSchema.ValidationsMapping) to change these mappings. + """ + + numeric = { + 'multiple_of': 'multipleOf', + 'le': 'maximum', + 'ge': 'minimum', + 'lt': 'exclusiveMaximum', + 'gt': 'exclusiveMinimum', + } + bytes = { + 'min_length': 'minLength', + 'max_length': 'maxLength', + } + string = { + 'min_length': 'minLength', + 'max_length': 'maxLength', + 'pattern': 'pattern', + } + array = { + 'min_length': 'minItems', + 'max_length': 'maxItems', + } + object = { + 'min_length': 'minProperties', + 'max_length': 'maxProperties', + } + + def get_flattened_anyof(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: + members = [] + for schema in schemas: + if len(schema) == 1 and 'anyOf' in schema: + members.extend(schema['anyOf']) + else: + members.append(schema) + members = _deduplicate_schemas(members) + if len(members) == 1: + return members[0] + return {'anyOf': members} + + def get_json_ref_counts(self, json_schema: JsonSchemaValue) -> dict[JsonRef, int]: + """Get all values corresponding to the key '$ref' anywhere in the json_schema.""" + json_refs: dict[JsonRef, int] = Counter() + + def _add_json_refs(schema: Any) -> None: + if isinstance(schema, dict): + if '$ref' in schema: + json_ref = JsonRef(schema['$ref']) + if not isinstance(json_ref, str): + return # in this case, '$ref' might have been the name of a property + already_visited = json_ref in json_refs + json_refs[json_ref] += 1 + if already_visited: + return # prevent recursion on a definition that was already visited + try: + defs_ref = self.json_to_defs_refs[json_ref] + if defs_ref in self._core_defs_invalid_for_json_schema: + raise self._core_defs_invalid_for_json_schema[defs_ref] + _add_json_refs(self.definitions[defs_ref]) + except KeyError: + if not json_ref.startswith(('http://', 'https://')): + raise + + for k, v in schema.items(): + if k == 'examples' and isinstance(v, list): + # Skip examples that may contain arbitrary values and references + # (see the comment in `_get_all_json_refs` for more details). + continue + _add_json_refs(v) + elif isinstance(schema, list): + for v in schema: + _add_json_refs(v) + + _add_json_refs(json_schema) + return json_refs + + def handle_invalid_for_json_schema(self, schema: CoreSchemaOrField, error_info: str) -> JsonSchemaValue: + raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}') + + def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None: + """This method simply emits PydanticJsonSchemaWarnings based on handling in the `warning_message` method.""" + message = self.render_warning_message(kind, detail) + if message is not None: + warnings.warn(message, PydanticJsonSchemaWarning) + + def render_warning_message(self, kind: JsonSchemaWarningKind, detail: str) -> str | None: + """This method is responsible for ignoring warnings as desired, and for formatting the warning messages. + + You can override the value of `ignored_warning_kinds` in a subclass of GenerateJsonSchema + to modify what warnings are generated. If you want more control, you can override this method; + just return None in situations where you don't want warnings to be emitted. + + Args: + kind: The kind of warning to render. It can be one of the following: + + - 'skipped-choice': A choice field was skipped because it had no valid choices. + - 'non-serializable-default': A default value was skipped because it was not JSON-serializable. + detail: A string with additional details about the warning. + + Returns: + The formatted warning message, or `None` if no warning should be emitted. + """ + if kind in self.ignored_warning_kinds: + return None + return f'{detail} [{kind}]' + + def _build_definitions_remapping(self) -> _DefinitionsRemapping: + defs_to_json: dict[DefsRef, JsonRef] = {} + for defs_refs in self._prioritized_defsref_choices.values(): + for defs_ref in defs_refs: + json_ref = JsonRef(self.ref_template.format(model=defs_ref)) + defs_to_json[defs_ref] = json_ref + + return _DefinitionsRemapping.from_prioritized_choices( + self._prioritized_defsref_choices, defs_to_json, self.definitions + ) + + def _garbage_collect_definitions(self, schema: JsonSchemaValue) -> None: + visited_defs_refs: set[DefsRef] = set() + unvisited_json_refs = _get_all_json_refs(schema) + while unvisited_json_refs: + next_json_ref = unvisited_json_refs.pop() + try: + next_defs_ref = self.json_to_defs_refs[next_json_ref] + if next_defs_ref in visited_defs_refs: + continue + visited_defs_refs.add(next_defs_ref) + unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref])) + except KeyError: + if not next_json_ref.startswith(('http://', 'https://')): + raise + + self.definitions = {k: v for k, v in self.definitions.items() if k in visited_defs_refs} + + +# ##### Start JSON Schema Generation Functions ##### + + +def model_json_schema( + cls: type[BaseModel] | type[PydanticDataclass], + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, + mode: JsonSchemaMode = 'validation', +) -> dict[str, Any]: + """Utility function to generate a JSON Schema for a model. + + Args: + cls: The model class to generate a JSON Schema for. + by_alias: If `True` (the default), fields will be serialized according to their alias. + If `False`, fields will be serialized according to their attribute name. + ref_template: The template to use for generating JSON Schema references. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + schema_generator: The class to use for generating the JSON Schema. + mode: The mode to use for generating the JSON Schema. It can be one of the following: + + - 'validation': Generate a JSON Schema for validating data. + - 'serialization': Generate a JSON Schema for serializing data. + + Returns: + The generated JSON Schema. + """ + from .main import BaseModel + + schema_generator_instance = schema_generator( + by_alias=by_alias, ref_template=ref_template, union_format=union_format + ) + + if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): + cls.__pydantic_core_schema__.rebuild() + + if cls is BaseModel: + raise AttributeError('model_json_schema() must be called on a subclass of BaseModel, not BaseModel itself.') + + assert not isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it' + return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode) + + +def models_json_schema( + models: Sequence[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode]], + *, + by_alias: bool = True, + title: str | None = None, + description: str | None = None, + ref_template: str = DEFAULT_REF_TEMPLATE, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, +) -> tuple[dict[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]: + """Utility function to generate a JSON Schema for multiple models. + + Args: + models: A sequence of tuples of the form (model, mode). + by_alias: Whether field aliases should be used as keys in the generated JSON Schema. + title: The title of the generated JSON Schema. + description: The description of the generated JSON Schema. + ref_template: The reference template to use for generating JSON Schema references. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + schema_generator: The schema generator to use for generating the JSON Schema. + + Returns: + A tuple where: + - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and + whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have + JsonRef references to definitions that are defined in the second returned element.) + - The second element is a JSON schema containing all definitions referenced in the first returned + element, along with the optional title and description keys. + """ + for cls, _ in models: + if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): + cls.__pydantic_core_schema__.rebuild() + + instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format) + inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [ + (m, mode, m.__pydantic_core_schema__) for m, mode in models + ] + json_schemas_map, definitions = instance.generate_definitions(inputs) + + json_schema: dict[str, Any] = {} + if definitions: + json_schema['$defs'] = definitions + if title: + json_schema['title'] = title + if description: + json_schema['description'] = description + + return json_schemas_map, json_schema + + +# ##### End JSON Schema Generation Functions ##### + + +_HashableJsonValue: TypeAlias = Union[ + int, float, str, bool, None, tuple['_HashableJsonValue', ...], tuple[tuple[str, '_HashableJsonValue'], ...] +] + + +def _deduplicate_schemas(schemas: Iterable[JsonDict]) -> list[JsonDict]: + return list({_make_json_hashable(schema): schema for schema in schemas}.values()) + + +def _make_json_hashable(value: JsonValue) -> _HashableJsonValue: + if isinstance(value, dict): + return tuple(sorted((k, _make_json_hashable(v)) for k, v in value.items())) + elif isinstance(value, list): + return tuple(_make_json_hashable(v) for v in value) + else: + return value + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class WithJsonSchema: + """!!! abstract "Usage Documentation" + [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation) + + An annotation used to override the JSON Schema for a type. + + This is useful when you want to set a JSON Schema for a type that don't produce any JSON Schemas by default + (e.g. [`Callable`][collections.abc.Callable]). + + If `mode` is set this will only apply to that schema generation mode, allowing you to set different JSON Schemas for validation and serialization. + + !!! note + If the `WithJsonSchema` annotation is coupled with the [`Field()`][pydantic.Field] function, the behavior overriding will vary depending on the location: + + * If the [`Annotated`][typing.Annotated] metadata is specified at the "top-level" field, `Field()` metadata arguments + (excluding [constraints](../concepts/fields.md#field-constraints)) such as `title` and `description` will be applied on + top of the `WithJsonSchema`, no matter the order: + + ```python + from typing import Annotated + + from pydantic import BaseModel, Field, WithJsonSchema + + class Model(BaseModel): + field: Annotated[ + int, + Field(title='My Field'), + WithJsonSchema({'type': 'integer', 'extra': 'data'}), + ] + + Model.model_json_schema()['properties']['field'] + #> {'type': 'integer', 'extra': 'data', 'title': 'My Field'} + ``` + + * If the [`Annotated`][typing.Annotated] metadata is specified on a specific inner type, `WithJsonSchema` will unconditionally + override the JSON Schema: + + ```python + from typing import Annotated + + from pydantic import BaseModel, Field, WithJsonSchema + + class Model(BaseModel): + field: list[ + Annotated[ + int, + Field(title='My Field'), + WithJsonSchema({'type': 'integer', 'extra': 'data'}), + ] + ] + + Model.model_json_schema()['properties']['field'] + #> {'items': {'extra': 'data', 'type': 'integer'}, 'title': 'Field', 'type': 'array'} + ``` + + See also the documentation about [the annotated pattern](../concepts/fields.md#the-annotated-pattern). + """ + + json_schema: JsonSchemaValue | None + mode: Literal['validation', 'serialization'] | None = None + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + if self.mode is not None and self.mode != handler.mode: + return handler(core_schema) + if self.json_schema is None: + # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema + raise PydanticOmit + else: + return self.json_schema.copy() + + def __hash__(self) -> int: + return hash(type(self.mode)) + + +class Examples: + """Add examples to a JSON schema. + + If the JSON Schema already contains examples, the provided examples + will be appended. + + If `mode` is set this will only apply to that schema generation mode, + allowing you to add different examples for validation and serialization. + """ + + @overload + @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.') + def __init__( + self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None + ) -> None: ... + + @overload + def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ... + + def __init__( + self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None + ) -> None: + if isinstance(examples, dict): + warnings.warn( + 'Using a dict for `examples` is deprecated, use a list instead.', + PydanticDeprecatedSince29, + stacklevel=2, + ) + self.examples = examples + self.mode = mode + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + mode = self.mode or handler.mode + json_schema = handler(core_schema) + if mode != handler.mode: + return json_schema + examples = json_schema.get('examples') + if examples is None: + json_schema['examples'] = to_jsonable_python(self.examples) + if isinstance(examples, dict): + if isinstance(self.examples, list): + warnings.warn( + 'Updating existing JSON Schema examples of type dict with examples of type list. ' + 'Only the existing examples values will be retained. Note that dict support for ' + 'examples is deprecated and will be removed in v3.0.', + UserWarning, + ) + json_schema['examples'] = to_jsonable_python( + [ex for value in examples.values() for ex in value] + self.examples + ) + else: + json_schema['examples'] = to_jsonable_python({**examples, **self.examples}) + if isinstance(examples, list): + if isinstance(self.examples, list): + json_schema['examples'] = to_jsonable_python(examples + self.examples) + elif isinstance(self.examples, dict): + warnings.warn( + 'Updating existing JSON Schema examples of type list with examples of type dict. ' + 'Only the examples values will be retained. Note that dict support for ' + 'examples is deprecated and will be removed in v3.0.', + UserWarning, + ) + json_schema['examples'] = to_jsonable_python( + examples + [ex for value in self.examples.values() for ex in value] + ) + + return json_schema + + def __hash__(self) -> int: + return hash(type(self.mode)) + + +def _get_all_json_refs(item: Any) -> set[JsonRef]: + """Get all the definitions references from a JSON schema.""" + refs: set[JsonRef] = set() + stack = [item] + + while stack: + current = stack.pop() + if isinstance(current, dict): + for key, value in current.items(): + if key == 'examples' and isinstance(value, list): + # Skip examples that may contain arbitrary values and references + # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value + # of type list is necessary to avoid skipping valid portions of the schema, + # for instance when "examples" is used as a property key. A more robust solution + # could be found, but would require more advanced JSON Schema parsing logic. + continue + if key == '$ref' and isinstance(value, str): + refs.add(JsonRef(value)) + elif isinstance(value, dict): + stack.append(value) + elif isinstance(value, list): + stack.extend(value) + elif isinstance(current, list): + stack.extend(current) + + return refs + + +AnyType = TypeVar('AnyType') + +if TYPE_CHECKING: + SkipJsonSchema = Annotated[AnyType, ...] +else: + + @dataclasses.dataclass(**_internal_dataclass.slots_true) + class SkipJsonSchema: + """!!! abstract "Usage Documentation" + [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation) + + Add this as an annotation on a field to skip generating a JSON schema for that field. + + Example: + ```python + from pprint import pprint + from typing import Union + + from pydantic import BaseModel + from pydantic.json_schema import SkipJsonSchema + + class Model(BaseModel): + a: Union[int, None] = None # (1)! + b: Union[int, SkipJsonSchema[None]] = None # (2)! + c: SkipJsonSchema[Union[int, None]] = None # (3)! + + pprint(Model.model_json_schema()) + ''' + { + 'properties': { + 'a': { + 'anyOf': [ + {'type': 'integer'}, + {'type': 'null'} + ], + 'default': None, + 'title': 'A' + }, + 'b': { + 'default': None, + 'title': 'B', + 'type': 'integer' + } + }, + 'title': 'Model', + 'type': 'object' + } + ''' + ``` + + 1. The integer and null types are both included in the schema for `a`. + 2. The integer type is the only type included in the schema for `b`. + 3. The entirety of the `c` field is omitted from the schema. + """ + + def __class_getitem__(cls, item: AnyType) -> AnyType: + return Annotated[item, cls()] + + def __get_pydantic_json_schema__( + self, core_schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + raise PydanticOmit + + def __hash__(self) -> int: + return hash(type(self)) + + +def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict: + if cls is not None: + try: + return _decorators.get_attribute_from_bases(cls, '__pydantic_config__') + except AttributeError: + pass + return {} + + +def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None: + """Get a `'function-plain'` serialization schema that can be used to serialize a default value. + + This takes into account having the serialization schema nested under validation schema(s). + """ + if ( + (ser_schema := schema.get('serialization')) + and ser_schema['type'] == 'function-plain' + and not ser_schema.get('info_arg') + ): + return ser_schema + if _core_utils.is_function_with_inner_schema(schema): + return _get_ser_schema_for_default_value(schema['schema']) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py new file mode 100644 index 0000000..2ed62e5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/main.py @@ -0,0 +1,1838 @@ +"""Logic for creating models.""" + +# Because `dict` is in the local namespace of the `BaseModel` class, we use `Dict` for annotations. +# TODO v3 fallback to `dict` when the deprecated `dict` method gets removed. +# ruff: noqa: UP035 + +from __future__ import annotations as _annotations + +import operator +import sys +import types +import warnings +from collections.abc import Generator, Mapping +from copy import copy, deepcopy +from functools import cached_property +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + Generic, + Literal, + TypeVar, + Union, + cast, + overload, +) + +import pydantic_core +import typing_extensions +from pydantic_core import PydanticUndefined, ValidationError +from typing_extensions import Self, TypeAlias, Unpack + +from . import PydanticDeprecatedSince20, PydanticDeprecatedSince211 +from ._internal import ( + _config, + _decorators, + _fields, + _forward_ref, + _generics, + _mock_val_ser, + _model_construction, + _namespace_utils, + _repr, + _typing_extra, + _utils, +) +from ._migration import getattr_migration +from .aliases import AliasChoices, AliasPath +from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler +from .config import ConfigDict, ExtraValues +from .errors import PydanticUndefinedAnnotation, PydanticUserError +from .json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema, JsonSchemaMode, JsonSchemaValue, model_json_schema +from .plugin._schema_validator import PluggableSchemaValidator + +if TYPE_CHECKING: + from inspect import Signature + from pathlib import Path + + from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator + + from ._internal._fields import PydanticExtraInfo + from ._internal._namespace_utils import MappingNamespace + from ._internal._utils import AbstractSetIntStr, MappingIntStrAny + from .deprecated.parse import Protocol as DeprecatedParseProtocol + from .fields import ComputedFieldInfo, FieldInfo, ModelPrivateAttr + + +__all__ = 'BaseModel', 'create_model' + +# Keep these type aliases available at runtime: +TupleGenerator: TypeAlias = Generator[tuple[str, Any], None, None] +# NOTE: In reality, `bool` should be replaced by `Literal[True]` but mypy fails to correctly apply bidirectional +# type inference (e.g. when using `{'a': {'b': True}}`): +# NOTE: Keep this type alias in sync with the stub definition in `pydantic-core`: +IncEx: TypeAlias = Union[set[int], set[str], Mapping[int, Union['IncEx', bool]], Mapping[str, Union['IncEx', bool]]] + +_object_setattr = _model_construction.object_setattr + + +def _check_frozen(model_cls: type[BaseModel], name: str, value: Any) -> None: + if model_cls.model_config.get('frozen'): + error_type = 'frozen_instance' + elif getattr(model_cls.__pydantic_fields__.get(name), 'frozen', False): + error_type = 'frozen_field' + else: + return + + raise ValidationError.from_exception_data( + model_cls.__name__, [{'type': error_type, 'loc': (name,), 'input': value}] + ) + + +def _model_field_setattr_handler(model: BaseModel, name: str, val: Any) -> None: + model.__dict__[name] = val + model.__pydantic_fields_set__.add(name) + + +def _private_setattr_handler(model: BaseModel, name: str, val: Any) -> None: + if getattr(model, '__pydantic_private__', None) is None: + # While the attribute should be present at this point, this may not be the case if + # users do unusual stuff with `model_post_init()` (which is where the `__pydantic_private__` + # is initialized, by wrapping the user-defined `model_post_init()`), e.g. if they mock + # the `model_post_init()` call. Ideally we should find a better way to init private attrs. + object.__setattr__(model, '__pydantic_private__', {}) + model.__pydantic_private__[name] = val # pyright: ignore[reportOptionalSubscript] + + +_SIMPLE_SETATTR_HANDLERS: Mapping[str, Callable[[BaseModel, str, Any], None]] = { + 'model_field': _model_field_setattr_handler, + 'validate_assignment': lambda model, name, val: model.__pydantic_validator__.validate_assignment(model, name, val), # pyright: ignore[reportAssignmentType] + 'private': _private_setattr_handler, + 'cached_property': lambda model, name, val: model.__dict__.__setitem__(name, val), + 'extra_known': lambda model, name, val: _object_setattr(model, name, val), +} + + +class BaseModel(metaclass=_model_construction.ModelMetaclass): + """!!! abstract "Usage Documentation" + [Models](../concepts/models.md) + + A base class for creating Pydantic models. + + Attributes: + __class_vars__: The names of the class variables defined on the model. + __private_attributes__: Metadata about the private attributes of the model. + __signature__: The synthesized `__init__` [`Signature`][inspect.Signature] of the model. + + __pydantic_complete__: Whether model building is completed, or if there are still undefined fields. + __pydantic_core_schema__: The core schema of the model. + __pydantic_custom_init__: Whether the model has a custom `__init__` function. + __pydantic_decorators__: Metadata containing the decorators defined on the model. + This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1. + __pydantic_generic_metadata__: A dictionary containing metadata about generic Pydantic models. + The `origin` and `args` items map to the [`__origin__`][genericalias.__origin__] + and [`__args__`][genericalias.__args__] attributes of [generic aliases][types-genericalias], + and the `parameter` item maps to the `__parameter__` attribute of generic classes. + __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models. + __pydantic_post_init__: The name of the post-init method for the model, if defined. + __pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel]. + __pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model. + __pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model. + + __pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. + __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects. + + __pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] + is set to `'allow'`. + __pydantic_fields_set__: The names of fields explicitly set during instantiation. + __pydantic_private__: Values of private attributes set on the model instance. + """ + + # Note: Many of the below class vars are defined in the metaclass, but we define them here for type checking purposes. + + model_config: ClassVar[ConfigDict] = ConfigDict() + """ + Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. + """ + + __class_vars__: ClassVar[set[str]] + """The names of the class variables defined on the model.""" + + __private_attributes__: ClassVar[Dict[str, ModelPrivateAttr]] # noqa: UP006 + """Metadata about the private attributes of the model.""" + + __signature__: ClassVar[Signature] + """The synthesized `__init__` [`Signature`][inspect.Signature] of the model.""" + + __pydantic_complete__: ClassVar[bool] = False + """Whether model building is completed, or if there are still undefined fields.""" + + __pydantic_core_schema__: ClassVar[CoreSchema] + """The core schema of the model.""" + + __pydantic_custom_init__: ClassVar[bool] + """Whether the model has a custom `__init__` method.""" + + # Must be set for `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation. + __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] = _decorators.DecoratorInfos() + """Metadata containing the decorators defined on the model. + This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.""" + + __pydantic_generic_metadata__: ClassVar[_generics.PydanticGenericMetadata] + """A dictionary containing metadata about generic Pydantic models. + + The `origin` and `args` items map to the [`__origin__`][genericalias.__origin__] + and [`__args__`][genericalias.__args__] attributes of [generic aliases][types-genericalias], + and the `parameter` item maps to the `__parameter__` attribute of generic classes. + """ + + __pydantic_parent_namespace__: ClassVar[Dict[str, Any] | None] = None # noqa: UP006 + """Parent namespace of the model, used for automatic rebuilding of models.""" + + __pydantic_post_init__: ClassVar[None | Literal['model_post_init']] + """The name of the post-init method for the model, if defined.""" + + __pydantic_root_model__: ClassVar[bool] = False + """Whether the model is a [`RootModel`][pydantic.root_model.RootModel].""" + + __pydantic_serializer__: ClassVar[SchemaSerializer] + """The `pydantic-core` `SchemaSerializer` used to dump instances of the model.""" + + __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] + """The `pydantic-core` `SchemaValidator` used to validate instances of the model.""" + + __pydantic_fields__: ClassVar[Dict[str, FieldInfo]] # noqa: UP006 + """A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. + This replaces `Model.__fields__` from Pydantic V1. + """ + + __pydantic_setattr_handlers__: ClassVar[Dict[str, Callable[[BaseModel, str, Any], None]]] # noqa: UP006 + """`__setattr__` handlers. Memoizing the handlers leads to a dramatic performance improvement in `__setattr__`""" + + __pydantic_computed_fields__: ClassVar[Dict[str, ComputedFieldInfo]] # noqa: UP006 + """A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.""" + + __pydantic_extra_info__: ClassVar[PydanticExtraInfo | None] + """A wrapper around the `__pydantic_extra__` annotation, if explicitly annotated on a model. + + This is a private attribute, not meant to be used outside Pydantic. + """ + + __pydantic_extra__: Dict[str, Any] | None = _model_construction.NoInitField(init=False) # noqa: UP006 + """A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] is set to `'allow'`.""" + + __pydantic_fields_set__: set[str] = _model_construction.NoInitField(init=False) + """The names of fields explicitly set during instantiation.""" + + __pydantic_private__: Dict[str, Any] | None = _model_construction.NoInitField(init=False) # noqa: UP006 + """Values of private attributes set on the model instance.""" + + if not TYPE_CHECKING: + # Prevent `BaseModel` from being instantiated directly + # (defined in an `if not TYPE_CHECKING` block for clarity and to avoid type checking errors): + __pydantic_core_schema__ = _mock_val_ser.MockCoreSchema( + 'Pydantic models should inherit from BaseModel, BaseModel cannot be instantiated directly', + code='base-model-instantiated', + ) + __pydantic_validator__ = _mock_val_ser.MockValSer( + 'Pydantic models should inherit from BaseModel, BaseModel cannot be instantiated directly', + val_or_ser='validator', + code='base-model-instantiated', + ) + __pydantic_serializer__ = _mock_val_ser.MockValSer( + 'Pydantic models should inherit from BaseModel, BaseModel cannot be instantiated directly', + val_or_ser='serializer', + code='base-model-instantiated', + ) + + __slots__ = '__dict__', '__pydantic_fields_set__', '__pydantic_extra__', '__pydantic_private__' + + def __init__(self, /, **data: Any) -> None: + """Create a new model by parsing and validating input data from keyword arguments. + + Raises [`ValidationError`][pydantic_core.ValidationError] if the input data cannot be + validated to form a valid model. + + `self` is explicitly positional-only to allow `self` as a field name. + """ + # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks + __tracebackhide__ = True + validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) + if self is not validated_self: + warnings.warn( + 'A custom validator is returning a value other than `self`.\n' + "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n" + 'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.', + stacklevel=2, + ) + + # The following line sets a flag that we use to determine when `__init__` gets overridden by the user + __init__.__pydantic_base_init__ = True # pyright: ignore[reportFunctionMemberAccess] + + @_utils.deprecated_instance_property + @classmethod + def model_fields(cls) -> dict[str, FieldInfo]: + """A mapping of field names to their respective [`FieldInfo`][pydantic.fields.FieldInfo] instances. + + !!! warning + Accessing this attribute from a model instance is deprecated, and will not work in Pydantic V3. + Instead, you should access this attribute from the model class. + """ + return getattr(cls, '__pydantic_fields__', {}) + + @_utils.deprecated_instance_property + @classmethod + def model_computed_fields(cls) -> dict[str, ComputedFieldInfo]: + """A mapping of computed field names to their respective [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] instances. + + !!! warning + Accessing this attribute from a model instance is deprecated, and will not work in Pydantic V3. + Instead, you should access this attribute from the model class. + """ + return getattr(cls, '__pydantic_computed_fields__', {}) + + @property + def model_extra(self) -> dict[str, Any] | None: + """Get extra fields set during validation. + + Returns: + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + """ + return self.__pydantic_extra__ + + @property + def model_fields_set(self) -> set[str]: + """Returns the set of fields that have been explicitly set on this model instance. + + Returns: + A set of strings representing the fields that have been set, + i.e. that were not filled from defaults. + """ + return self.__pydantic_fields_set__ + + @classmethod + def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: # noqa: C901 + """Creates a new instance of the `Model` class with validated data. + + Creates a new model setting `__dict__` and `__pydantic_fields_set__` from trusted or pre-validated data. + Default values are respected, but no other validation is performed. + + !!! note + `model_construct()` generally respects the `model_config.extra` setting on the provided model. + That is, if `model_config.extra == 'allow'`, then all extra passed values are added to the model instance's `__dict__` + and `__pydantic_extra__` fields. If `model_config.extra == 'ignore'` (the default), then all extra passed values are ignored. + Because no validation is performed with a call to `model_construct()`, having `model_config.extra == 'forbid'` does not result in + an error if extra values are passed, but they will be ignored. + + Args: + _fields_set: A set of field names that were originally explicitly set during instantiation. If provided, + this is directly used for the [`model_fields_set`][pydantic.BaseModel.model_fields_set] attribute. + Otherwise, the field names from the `values` argument will be used. + values: Trusted or pre-validated data dictionary. + + Returns: + A new instance of the `Model` class with validated data. + """ + m = cls.__new__(cls) + fields_values: dict[str, Any] = {} + fields_set = set() + + for name, field in cls.__pydantic_fields__.items(): + if field.alias is not None and field.alias in values: + fields_values[name] = values.pop(field.alias) + fields_set.add(name) + + if (name not in fields_set) and (field.validation_alias is not None): + validation_aliases: list[str | AliasPath] = ( + field.validation_alias.choices + if isinstance(field.validation_alias, AliasChoices) + else [field.validation_alias] + ) + + for alias in validation_aliases: + if isinstance(alias, str) and alias in values: + fields_values[name] = values.pop(alias) + fields_set.add(name) + break + elif isinstance(alias, AliasPath): + value = alias.search_dict_for_path(values) + if value is not PydanticUndefined: + fields_values[name] = value + fields_set.add(name) + break + + if name not in fields_set: + if name in values: + fields_values[name] = values.pop(name) + fields_set.add(name) + elif not field.is_required(): + fields_values[name] = field.get_default(call_default_factory=True, validated_data=fields_values) + if _fields_set is None: + _fields_set = fields_set + + _extra: dict[str, Any] | None = values if cls.model_config.get('extra') == 'allow' else None + _object_setattr(m, '__dict__', fields_values) + _object_setattr(m, '__pydantic_fields_set__', _fields_set) + if not cls.__pydantic_root_model__: + _object_setattr(m, '__pydantic_extra__', _extra) + _object_setattr(m, '__pydantic_private__', None) + + if cls.__pydantic_post_init__: + m.model_post_init(None) + # update private attributes with values set + if hasattr(m, '__pydantic_private__') and m.__pydantic_private__ is not None: + for k, v in values.items(): + if k in m.__private_attributes__: + m.__pydantic_private__[k] = v + + return m + + def model_copy(self, *, update: Mapping[str, Any] | None = None, deep: bool = False) -> Self: + """!!! abstract "Usage Documentation" + [`model_copy`](../concepts/models.md#model-copy) + + Returns a copy of the model. + + !!! note + The underlying instance's [`__dict__`][object.__dict__] attribute is copied. This + might have unexpected side effects if you store anything in it, on top of the model + fields (e.g. the value of [cached properties][functools.cached_property]). + + Args: + update: Values to change/add in the new model. Note: the data is not validated + before creating the new model. You should trust this data. + deep: Set to `True` to make a deep copy of the model. + + Returns: + New model instance. + """ + copied = self.__deepcopy__() if deep else self.__copy__() + if update: + if self.model_config.get('extra') == 'allow': + for k, v in update.items(): + if k in self.__pydantic_fields__: + copied.__dict__[k] = v + else: + if copied.__pydantic_extra__ is None: + copied.__pydantic_extra__ = {} + copied.__pydantic_extra__[k] = v + else: + copied.__dict__.update(update) + copied.__pydantic_fields_set__.update(update.keys()) + return copied + + def model_dump( + self, + *, + mode: Literal['json', 'python'] | str = 'python', + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + ) -> dict[str, Any]: + """!!! abstract "Usage Documentation" + [`model_dump`](../concepts/serialization.md#python-mode) + + Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + Args: + mode: The mode in which `to_python` should run. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. + by_alias: Whether to use the field's alias in the dictionary key if defined. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + + Returns: + A dictionary representation of the model. + """ + return self.__pydantic_serializer__.to_python( + self, + mode=mode, + by_alias=by_alias, + include=include, + exclude=exclude, + context=context, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + exclude_computed_fields=exclude_computed_fields, + round_trip=round_trip, + warnings=warnings, + fallback=fallback, + serialize_as_any=serialize_as_any, + polymorphic_serialization=polymorphic_serialization, + ) + + def model_dump_json( + self, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + ) -> str: + """!!! abstract "Usage Documentation" + [`model_dump_json`](../concepts/serialization.md#json-mode) + + Generates a JSON representation of the model using Pydantic's `to_json` method. + + Args: + indent: Indentation to use in the JSON output. If None is passed, the output will be compact. + ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. + If `False` (the default), these characters will be output as-is. + include: Field(s) to include in the JSON output. + exclude: Field(s) to exclude from the JSON output. + context: Additional context to pass to the serializer. + by_alias: Whether to serialize using field aliases. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + + Returns: + A JSON string representation of the model. + """ + return self.__pydantic_serializer__.to_json( + self, + indent=indent, + ensure_ascii=ensure_ascii, + include=include, + exclude=exclude, + context=context, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + exclude_computed_fields=exclude_computed_fields, + round_trip=round_trip, + warnings=warnings, + fallback=fallback, + serialize_as_any=serialize_as_any, + polymorphic_serialization=polymorphic_serialization, + ).decode() + + @classmethod + def model_json_schema( + cls, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, + mode: JsonSchemaMode = 'validation', + *, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + ) -> dict[str, Any]: + """Generates a JSON schema for a model class. + + Args: + by_alias: Whether to use attribute aliases or not. + ref_template: The reference template. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + schema_generator: To override the logic used to generate the JSON schema, as a subclass of + `GenerateJsonSchema` with your desired modifications + mode: The mode in which to generate the schema. + + Returns: + The JSON schema for the given model class. + """ + return model_json_schema( + cls, + by_alias=by_alias, + ref_template=ref_template, + union_format=union_format, + schema_generator=schema_generator, + mode=mode, + ) + + @classmethod + def model_parametrized_name(cls, params: tuple[type[Any], ...]) -> str: + """Compute the class name for parametrizations of generic classes. + + This method can be overridden to achieve a custom naming scheme for generic BaseModels. + + Args: + params: Tuple of types of the class. Given a generic class + `Model` with 2 type variables and a concrete model `Model[str, int]`, + the value `(str, int)` would be passed to `params`. + + Returns: + String representing the new class where `params` are passed to `cls` as type variables. + + Raises: + TypeError: Raised when trying to generate concrete names for non-generic models. + """ + if not issubclass(cls, Generic): + raise TypeError('Concrete names should only be generated for generic models.') + + # Any strings received should represent forward references, so we handle them specially below. + # If we eventually move toward wrapping them in a ForwardRef in __class_getitem__ in the future, + # we may be able to remove this special case. + param_names = [param if isinstance(param, str) else _repr.display_as_type(param) for param in params] + params_component = ', '.join(param_names) + return f'{cls.__name__}[{params_component}]' + + def model_post_init(self, context: Any, /) -> None: + """Override this method to perform additional initialization after `__init__` and `model_construct`. + This is useful if you want to do some validation that requires the entire model to be initialized. + """ + + @classmethod + def model_rebuild( + cls, + *, + force: bool = False, + raise_errors: bool = True, + _parent_namespace_depth: int = 2, + _types_namespace: MappingNamespace | None = None, + ) -> bool | None: + """Try to rebuild the pydantic-core schema for the model. + + This may be necessary when one of the annotations is a ForwardRef which could not be resolved during + the initial attempt to build the schema, and automatic rebuilding fails. + + Args: + force: Whether to force the rebuilding of the model schema, defaults to `False`. + raise_errors: Whether to raise errors, defaults to `True`. + _parent_namespace_depth: The depth level of the parent namespace, defaults to 2. + _types_namespace: The types namespace, defaults to `None`. + + Returns: + Returns `None` if the schema is already "complete" and rebuilding was not required. + If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. + """ + already_complete = cls.__pydantic_complete__ + if already_complete and not force: + return None + + cls.__pydantic_complete__ = False + + for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): + if attr in cls.__dict__ and not isinstance(getattr(cls, attr), _mock_val_ser.MockValSer): + # Deleting the validator/serializer is necessary as otherwise they can get reused in + # pydantic-core. We do so only if they aren't mock instances, otherwise — as `model_rebuild()` + # isn't thread-safe — concurrent model instantiations can lead to the parent validator being used. + # Same applies for the core schema that can be reused in schema generation. + delattr(cls, attr) + + if _types_namespace is not None: + rebuild_ns = _types_namespace + elif _parent_namespace_depth > 0: + rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} + else: + rebuild_ns = {} + + parent_ns = _model_construction.unpack_lenient_weakvaluedict(cls.__pydantic_parent_namespace__) or {} + + ns_resolver = _namespace_utils.NsResolver( + parent_namespace={**rebuild_ns, **parent_ns}, + ) + + return _model_construction.complete_model_class( + cls, + _config.ConfigWrapper(cls.model_config, check=False), + ns_resolver, + raise_errors=raise_errors, + # If the model was already complete, we don't need to call the hook again. + call_on_complete_hook=not already_complete, + is_force_rebuild=force, + ) + + @classmethod + def model_validate( + cls, + obj: Any, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Self: + """Validate a pydantic model instance. + + Args: + obj: The object to validate. + strict: Whether to enforce types strictly. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + from_attributes: Whether to extract data from object attributes. + context: Additional context to pass to the validator. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Raises: + ValidationError: If the object could not be validated. + + Returns: + The validated model instance. + """ + # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks + __tracebackhide__ = True + + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return cls.__pydantic_validator__.validate_python( + obj, + strict=strict, + extra=extra, + from_attributes=from_attributes, + context=context, + by_alias=by_alias, + by_name=by_name, + ) + + @classmethod + def model_validate_json( + cls, + json_data: str | bytes | bytearray, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Self: + """!!! abstract "Usage Documentation" + [JSON Parsing](../concepts/json.md#json-parsing) + + Validate the given JSON data against the Pydantic model. + + Args: + json_data: The JSON data to validate. + strict: Whether to enforce types strictly. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + context: Extra variables to pass to the validator. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Returns: + The validated Pydantic model. + + Raises: + ValidationError: If `json_data` is not a JSON string or the object could not be validated. + """ + # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks + __tracebackhide__ = True + + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return cls.__pydantic_validator__.validate_json( + json_data, strict=strict, extra=extra, context=context, by_alias=by_alias, by_name=by_name + ) + + @classmethod + def model_validate_strings( + cls, + obj: Any, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Self: + """Validate the given object with string data against the Pydantic model. + + Args: + obj: The object containing string data to validate. + strict: Whether to enforce types strictly. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + context: Extra variables to pass to the validator. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Returns: + The validated Pydantic model. + """ + # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks + __tracebackhide__ = True + + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return cls.__pydantic_validator__.validate_strings( + obj, strict=strict, extra=extra, context=context, by_alias=by_alias, by_name=by_name + ) + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[BaseModel], handler: GetCoreSchemaHandler, /) -> CoreSchema: + # This warning is only emitted when calling `super().__get_pydantic_core_schema__` from a model subclass. + # In the generate schema logic, this method (`BaseModel.__get_pydantic_core_schema__`) is special cased to + # *not* be called if not overridden. + warnings.warn( + 'The `__get_pydantic_core_schema__` method of the `BaseModel` class is deprecated. If you are calling ' + '`super().__get_pydantic_core_schema__` when overriding the method on a Pydantic model, consider using ' + '`handler(source)` instead. However, note that overriding this method on models can lead to unexpected ' + 'side effects.', + PydanticDeprecatedSince211, + stacklevel=2, + ) + # Logic copied over from `GenerateSchema._model_schema`: + schema = cls.__dict__.get('__pydantic_core_schema__') + if schema is not None and not isinstance(schema, _mock_val_ser.MockCoreSchema): + return cls.__pydantic_core_schema__ + + return handler(source) + + @classmethod + def __get_pydantic_json_schema__( + cls, + core_schema: CoreSchema, + handler: GetJsonSchemaHandler, + /, + ) -> JsonSchemaValue: + """Hook into generating the model's JSON schema. + + Args: + core_schema: A `pydantic-core` CoreSchema. + You can ignore this argument and call the handler with a new CoreSchema, + wrap this CoreSchema (`{'type': 'nullable', 'schema': current_schema}`), + or just call the handler with the original schema. + handler: Call into Pydantic's internal JSON schema generation. + This will raise a `pydantic.errors.PydanticInvalidForJsonSchema` if JSON schema + generation fails. + Since this gets called by `BaseModel.model_json_schema` you can override the + `schema_generator` argument to that function to change JSON schema generation globally + for a type. + + Returns: + A JSON schema, as a Python object. + """ + return handler(core_schema) + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: + """This is intended to behave just like `__init_subclass__`, but is called by `ModelMetaclass` + only after basic class initialization is complete. In particular, attributes like `model_fields` will + be present when this is called, but forward annotations are not guaranteed to be resolved yet, + meaning that creating an instance of the class may fail. + + This is necessary because `__init_subclass__` will always be called by `type.__new__`, + and it would require a prohibitively large refactor to the `ModelMetaclass` to ensure that + `type.__new__` was called in such a manner that the class would already be sufficiently initialized. + + This will receive the same `kwargs` that would be passed to the standard `__init_subclass__`, namely, + any kwargs passed to the class definition that aren't used internally by Pydantic. + + Args: + **kwargs: Any keyword arguments passed to the class definition that aren't used internally + by Pydantic. + + Note: + You may want to override [`__pydantic_on_complete__()`][pydantic.main.BaseModel.__pydantic_on_complete__] + instead, which is called once the class and its fields are fully initialized and ready for validation. + """ + + @classmethod + def __pydantic_on_complete__(cls) -> None: + """This is called once the class and its fields are fully initialized and ready to be used. + + This typically happens when the class is created (just before + [`__pydantic_init_subclass__()`][pydantic.main.BaseModel.__pydantic_init_subclass__] is called on the superclass), + except when forward annotations are used that could not immediately be resolved. + In that case, it will be called later, when the model is rebuilt automatically or explicitly using + [`model_rebuild()`][pydantic.main.BaseModel.model_rebuild]. + """ + + def __class_getitem__( + cls, typevar_values: type[Any] | tuple[type[Any], ...] + ) -> type[BaseModel] | _forward_ref.PydanticRecursiveRef: + cached = _generics.get_cached_generic_type_early(cls, typevar_values) + if cached is not None: + return cached + + if cls is BaseModel: + raise TypeError('Type parameters should be placed on typing.Generic, not BaseModel') + if not hasattr(cls, '__parameters__'): + raise TypeError(f'{cls} cannot be parametrized because it does not inherit from typing.Generic') + if not cls.__pydantic_generic_metadata__['parameters'] and Generic not in cls.__bases__: + raise TypeError(f'{cls} is not a generic class') + + if not isinstance(typevar_values, tuple): + typevar_values = (typevar_values,) + + # For a model `class Model[T, U, V = int](BaseModel): ...` parametrized with `(str, bool)`, + # this gives us `{T: str, U: bool, V: int}`: + typevars_map = _generics.map_generic_model_arguments(cls, typevar_values) + # We also update the provided args to use defaults values (`(str, bool)` becomes `(str, bool, int)`): + typevar_values = tuple(v for v in typevars_map.values()) + + if _utils.all_identical(typevars_map.keys(), typevars_map.values()) and typevars_map: + submodel = cls # if arguments are equal to parameters it's the same object + _generics.set_cached_generic_type(cls, typevar_values, submodel) + else: + parent_args = cls.__pydantic_generic_metadata__['args'] + if not parent_args: + args = typevar_values + else: + args = tuple(_generics.replace_types(arg, typevars_map) for arg in parent_args) + + origin = cls.__pydantic_generic_metadata__['origin'] or cls + model_name = origin.model_parametrized_name(args) + params = tuple( + dict.fromkeys(_generics.iter_contained_typevars(typevars_map.values())) + ) # use dict as ordered set + + with _generics.generic_recursion_self_type(origin, args) as maybe_self_type: + cached = _generics.get_cached_generic_type_late(cls, typevar_values, origin, args) + if cached is not None: + return cached + + if maybe_self_type is not None: + return maybe_self_type + + # Attempt to rebuild the origin in case new types have been defined + try: + # depth 2 gets you above this __class_getitem__ call. + # Note that we explicitly provide the parent ns, otherwise + # `model_rebuild` will use the parent ns no matter if it is the ns of a module. + # We don't want this here, as this has unexpected effects when a model + # is being parametrized during a forward annotation evaluation. + parent_ns = _typing_extra.parent_frame_namespace(parent_depth=2) or {} + origin.model_rebuild(_types_namespace=parent_ns) + except PydanticUndefinedAnnotation: + # It's okay if it fails, it just means there are still undefined types + # that could be evaluated later. + pass + + submodel = _generics.create_generic_submodel(model_name, origin, args, params) + + _generics.set_cached_generic_type(cls, typevar_values, submodel, origin, args) + + return submodel + + def __copy__(self) -> Self: + """Returns a shallow copy of the model.""" + cls = type(self) + m = cls.__new__(cls) + _object_setattr(m, '__dict__', copy(self.__dict__)) + _object_setattr(m, '__pydantic_extra__', copy(self.__pydantic_extra__)) + _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) + + if not hasattr(self, '__pydantic_private__') or self.__pydantic_private__ is None: + _object_setattr(m, '__pydantic_private__', None) + else: + _object_setattr( + m, + '__pydantic_private__', + {k: v for k, v in self.__pydantic_private__.items() if v is not PydanticUndefined}, + ) + + return m + + def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Self: + """Returns a deep copy of the model.""" + cls = type(self) + m = cls.__new__(cls) + _object_setattr(m, '__dict__', deepcopy(self.__dict__, memo=memo)) + _object_setattr(m, '__pydantic_extra__', deepcopy(self.__pydantic_extra__, memo=memo)) + # This next line doesn't need a deepcopy because __pydantic_fields_set__ is a set[str], + # and attempting a deepcopy would be marginally slower. + _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) + + if not hasattr(self, '__pydantic_private__') or self.__pydantic_private__ is None: + _object_setattr(m, '__pydantic_private__', None) + else: + _object_setattr( + m, + '__pydantic_private__', + deepcopy({k: v for k, v in self.__pydantic_private__.items() if v is not PydanticUndefined}, memo=memo), + ) + + return m + + if not TYPE_CHECKING: + # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access + # The same goes for __setattr__ and __delattr__, see: https://github.com/pydantic/pydantic/issues/8643 + + def __getattr__(self, item: str) -> Any: + private_attributes = object.__getattribute__(self, '__private_attributes__') + if item in private_attributes: + attribute = private_attributes[item] + if hasattr(attribute, '__get__'): + return attribute.__get__(self, type(self)) # type: ignore + + try: + # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items + return self.__pydantic_private__[item] # type: ignore + except KeyError as exc: + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc + else: + # `__pydantic_extra__` can fail to be set if the model is not yet fully initialized. + # See `BaseModel.__repr_args__` for more details + try: + pydantic_extra = object.__getattribute__(self, '__pydantic_extra__') + except AttributeError: + pydantic_extra = None + + if pydantic_extra and item in pydantic_extra: + return pydantic_extra[item] + else: + if hasattr(self.__class__, item): + return super().__getattribute__(item) # Raises AttributeError if appropriate + else: + # this is the current error + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') + + def __setattr__(self, name: str, value: Any) -> None: + if (setattr_handler := self.__pydantic_setattr_handlers__.get(name)) is not None: + setattr_handler(self, name, value) + # if None is returned from _setattr_handler, the attribute was set directly + elif (setattr_handler := self._setattr_handler(name, value)) is not None: + setattr_handler(self, name, value) # call here to not memo on possibly unknown fields + self.__pydantic_setattr_handlers__[name] = setattr_handler # memoize the handler for faster access + + def _setattr_handler(self, name: str, value: Any) -> Callable[[BaseModel, str, Any], None] | None: + """Get a handler for setting an attribute on the model instance. + + Returns: + A handler for setting an attribute on the model instance. Used for memoization of the handler. + Memoizing the handlers leads to a dramatic performance improvement in `__setattr__` + Returns `None` when memoization is not safe, then the attribute is set directly. + """ + cls = self.__class__ + if name in cls.__class_vars__: + raise AttributeError( + f'{name!r} is a ClassVar of `{cls.__name__}` and cannot be set on an instance. ' + f'If you want to set a value on the class, use `{cls.__name__}.{name} = value`.' + ) + elif not _fields.is_valid_field_name(name): + if (attribute := cls.__private_attributes__.get(name)) is not None: + if hasattr(attribute, '__set__'): + return lambda model, _name, val: attribute.__set__(model, val) + else: + return _SIMPLE_SETATTR_HANDLERS['private'] + else: + _object_setattr(self, name, value) + return None # Can not return memoized handler with possibly freeform attr names + + attr = getattr(cls, name, None) + # NOTE: We currently special case properties and `cached_property`, but we might need + # to generalize this to all data/non-data descriptors at some point. For non-data descriptors + # (such as `cached_property`), it isn't obvious though. `cached_property` caches the value + # to the instance's `__dict__`, but other non-data descriptors might do things differently. + if isinstance(attr, cached_property): + return _SIMPLE_SETATTR_HANDLERS['cached_property'] + + _check_frozen(cls, name, value) + + # We allow properties to be set only on non frozen models for now (to match dataclasses). + # This can be changed if it ever gets requested. + if isinstance(attr, property): + return lambda model, _name, val: attr.__set__(model, val) + elif cls.model_config.get('validate_assignment'): + return _SIMPLE_SETATTR_HANDLERS['validate_assignment'] + elif name not in cls.__pydantic_fields__: + if cls.model_config.get('extra') != 'allow': + # TODO - matching error + raise ValueError(f'"{cls.__name__}" object has no field "{name}"') + elif attr is None: + # attribute does not exist, so put it in extra + self.__pydantic_extra__[name] = value + self.__pydantic_fields_set__.add(name) + return None # Can not return memoized handler with possibly freeform attr names + else: + # attribute _does_ exist, and was not in extra, so update it + return _SIMPLE_SETATTR_HANDLERS['extra_known'] + else: + return _SIMPLE_SETATTR_HANDLERS['model_field'] + + def __delattr__(self, item: str) -> Any: + cls = self.__class__ + + if item in self.__private_attributes__: + attribute = self.__private_attributes__[item] + if hasattr(attribute, '__delete__'): + attribute.__delete__(self) # type: ignore + return + + try: + # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items + del self.__pydantic_private__[item] # type: ignore + return + except KeyError as exc: + raise AttributeError(f'{cls.__name__!r} object has no attribute {item!r}') from exc + + # Allow cached properties to be deleted (even if the class is frozen): + attr = getattr(cls, item, None) + if isinstance(attr, cached_property): + return object.__delattr__(self, item) + + _check_frozen(cls, name=item, value=None) + + if item in self.__pydantic_fields__: + object.__delattr__(self, item) + elif self.__pydantic_extra__ is not None and item in self.__pydantic_extra__: + del self.__pydantic_extra__[item] + else: + try: + object.__delattr__(self, item) + except AttributeError: + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') + + # Because we make use of `@dataclass_transform()`, `__replace__` is already synthesized by + # type checkers, so we define the implementation in this `if not TYPE_CHECKING:` block: + def __replace__(self, **changes: Any) -> Self: + return self.model_copy(update=changes) + + def __getstate__(self) -> dict[Any, Any]: + private = self.__pydantic_private__ + if private: + private = {k: v for k, v in private.items() if v is not PydanticUndefined} + return { + '__dict__': self.__dict__, + '__pydantic_extra__': self.__pydantic_extra__, + '__pydantic_fields_set__': self.__pydantic_fields_set__, + '__pydantic_private__': private, + } + + def __setstate__(self, state: dict[Any, Any]) -> None: + _object_setattr(self, '__pydantic_fields_set__', state.get('__pydantic_fields_set__', {})) + _object_setattr(self, '__pydantic_extra__', state.get('__pydantic_extra__', {})) + _object_setattr(self, '__pydantic_private__', state.get('__pydantic_private__', {})) + _object_setattr(self, '__dict__', state.get('__dict__', {})) + + if not TYPE_CHECKING: + + def __eq__(self, other: Any) -> bool: + if isinstance(other, BaseModel): + # When comparing instances of generic types for equality, as long as all field values are equal, + # only require their generic origin types to be equal, rather than exact type equality. + # This prevents headaches like MyGeneric(x=1) != MyGeneric[Any](x=1). + self_type = self.__pydantic_generic_metadata__['origin'] or self.__class__ + other_type = other.__pydantic_generic_metadata__['origin'] or other.__class__ + + # Perform common checks first + if not ( + self_type is other_type + and getattr(self, '__pydantic_private__', None) == getattr(other, '__pydantic_private__', None) + # We need to assume `None` and `{}` are equivalent, because extra behavior + # can be controlled at validation time: + and (self.__pydantic_extra__ or {}) == (other.__pydantic_extra__ or {}) + ): + return False + + # We only want to compare pydantic fields but ignoring fields is costly. + # We'll perform a fast check first, and fallback only when needed + # See GH-7444 and GH-7825 for rationale and a performance benchmark + + # First, do the fast (and sometimes faulty) __dict__ comparison + if self.__dict__ == other.__dict__: + # If the check above passes, then pydantic fields are equal, we can return early + return True + + # We don't want to trigger unnecessary costly filtering of __dict__ on all unequal objects, so we return + # early if there are no keys to ignore (we would just return False later on anyway) + model_fields = type(self).__pydantic_fields__.keys() + if self.__dict__.keys() <= model_fields and other.__dict__.keys() <= model_fields: + return False + + # If we reach here, there are non-pydantic-fields keys, mapped to unequal values, that we need to ignore + # Resort to costly filtering of the __dict__ objects + # We use operator.itemgetter because it is much faster than dict comprehensions + # NOTE: Contrary to standard python class and instances, when the Model class has a default value for an + # attribute and the model instance doesn't have a corresponding attribute, accessing the missing attribute + # raises an error in BaseModel.__getattr__ instead of returning the class attribute + # So we can use operator.itemgetter() instead of operator.attrgetter() + getter = operator.itemgetter(*model_fields) if model_fields else lambda _: _utils._SENTINEL + try: + return getter(self.__dict__) == getter(other.__dict__) + except KeyError: + # In rare cases (such as when using the deprecated BaseModel.copy() method), + # the __dict__ may not contain all model fields, which is how we can get here. + # getter(self.__dict__) is much faster than any 'safe' method that accounts + # for missing keys, and wrapping it in a `try` doesn't slow things down much + # in the common case. + self_fields_proxy = _utils.SafeGetItemProxy(self.__dict__) + other_fields_proxy = _utils.SafeGetItemProxy(other.__dict__) + return getter(self_fields_proxy) == getter(other_fields_proxy) + + # other instance is not a BaseModel + else: + return NotImplemented # delegate to the other item in the comparison + + if TYPE_CHECKING: + # We put `__init_subclass__` in a TYPE_CHECKING block because, even though we want the type-checking benefits + # described in the signature of `__init_subclass__` below, we don't want to modify the default behavior of + # subclass initialization. + + def __init_subclass__(cls, **kwargs: Unpack[ConfigDict]): + """This signature is included purely to help type-checkers check arguments to class declaration, which + provides a way to conveniently set model_config key/value pairs. + + ```python + from pydantic import BaseModel + + class MyModel(BaseModel, extra='allow'): ... + ``` + + However, this may be deceiving, since the _actual_ calls to `__init_subclass__` will not receive any + of the config arguments, and will only receive any keyword arguments passed during class initialization + that are _not_ expected keys in ConfigDict. (This is due to the way `ModelMetaclass.__new__` works.) + + Args: + **kwargs: Keyword arguments passed to the class definition, which set model_config + + Note: + You may want to override `__pydantic_init_subclass__` instead, which behaves similarly but is called + *after* the class is fully initialized. + """ + + def __iter__(self) -> TupleGenerator: + """So `dict(model)` works.""" + yield from [(k, v) for (k, v) in self.__dict__.items() if not k.startswith('_')] + extra = self.__pydantic_extra__ + if extra: + yield from extra.items() + + def __repr__(self) -> str: + return f'{self.__repr_name__()}({self.__repr_str__(", ")})' + + def __repr_args__(self) -> _repr.ReprArgs: + # Eagerly create the repr of computed fields, as this may trigger access of cached properties and as such + # modify the instance's `__dict__`. If we don't do it now, it could happen when iterating over the `__dict__` + # below if the instance happens to be referenced in a field, and would modify the `__dict__` size *during* iteration. + computed_fields_repr_args = [ + (k, getattr(self, k)) for k, v in self.__pydantic_computed_fields__.items() if v.repr + ] + + for k, v in self.__dict__.items(): + field = self.__pydantic_fields__.get(k) + if field and field.repr: + if v is not self: + yield k, v + else: + yield k, self.__repr_recursion__(v) + # `__pydantic_extra__` can fail to be set if the model is not yet fully initialized. + # This can happen if a `ValidationError` is raised during initialization and the instance's + # repr is generated as part of the exception handling. Therefore, we use `getattr` here + # with a fallback, even though the type hints indicate the attribute will always be present. + try: + pydantic_extra = object.__getattribute__(self, '__pydantic_extra__') + except AttributeError: + pydantic_extra = None + + if pydantic_extra is not None: + yield from ((k, v) for k, v in pydantic_extra.items()) + yield from computed_fields_repr_args + + # take logic from `_repr.Representation` without the side effects of inheritance, see #5740 + __repr_name__ = _repr.Representation.__repr_name__ + __repr_recursion__ = _repr.Representation.__repr_recursion__ + __repr_str__ = _repr.Representation.__repr_str__ + __pretty__ = _repr.Representation.__pretty__ + __rich_repr__ = _repr.Representation.__rich_repr__ + + def __str__(self) -> str: + return self.__repr_str__(' ') + + # ##### Deprecated methods from v1 ##### + @property + @typing_extensions.deprecated( + 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', category=None + ) + def __fields__(self) -> dict[str, FieldInfo]: + warnings.warn( + 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return getattr(type(self), '__pydantic_fields__', {}) + + @property + @typing_extensions.deprecated( + 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', + category=None, + ) + def __fields_set__(self) -> set[str]: + warnings.warn( + 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return self.__pydantic_fields_set__ + + @typing_extensions.deprecated('The `dict` method is deprecated; use `model_dump` instead.', category=None) + def dict( # noqa: D102 + self, + *, + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Dict[str, Any]: # noqa UP006 + warnings.warn( + 'The `dict` method is deprecated; use `model_dump` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return self.model_dump( + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + @typing_extensions.deprecated('The `json` method is deprecated; use `model_dump_json` instead.', category=None) + def json( # noqa: D102 + self, + *, + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + encoder: Callable[[Any], Any] | None = PydanticUndefined, # type: ignore[assignment] + models_as_dict: bool = PydanticUndefined, # type: ignore[assignment] + **dumps_kwargs: Any, + ) -> str: + warnings.warn( + 'The `json` method is deprecated; use `model_dump_json` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + if encoder is not PydanticUndefined: + raise TypeError('The `encoder` argument is no longer supported; use field serializers instead.') + if models_as_dict is not PydanticUndefined: + raise TypeError('The `models_as_dict` argument is no longer supported; use a model serializer instead.') + if dumps_kwargs: + raise TypeError('`dumps_kwargs` keyword arguments are no longer supported.') + return self.model_dump_json( + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + @classmethod + @typing_extensions.deprecated('The `parse_obj` method is deprecated; use `model_validate` instead.', category=None) + def parse_obj(cls, obj: Any) -> Self: # noqa: D102 + warnings.warn( + 'The `parse_obj` method is deprecated; use `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return cls.model_validate(obj) + + @classmethod + @typing_extensions.deprecated( + 'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, ' + 'otherwise load the data then use `model_validate` instead.', + category=None, + ) + def parse_raw( # noqa: D102 + cls, + b: str | bytes, + *, + content_type: str | None = None, + encoding: str = 'utf8', + proto: DeprecatedParseProtocol | None = None, + allow_pickle: bool = False, + ) -> Self: # pragma: no cover + warnings.warn( + 'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, ' + 'otherwise load the data then use `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import parse + + try: + obj = parse.load_str_bytes( + b, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + ) + except (ValueError, TypeError) as exc: + import json + + # try to match V1 + if isinstance(exc, UnicodeDecodeError): + type_str = 'value_error.unicodedecode' + elif isinstance(exc, json.JSONDecodeError): + type_str = 'value_error.jsondecode' + elif isinstance(exc, ValueError): + type_str = 'value_error' + else: + type_str = 'type_error' + + # ctx is missing here, but since we've added `input` to the error, we're not pretending it's the same + error: pydantic_core.InitErrorDetails = { + # The type: ignore on the next line is to ignore the requirement of LiteralString + 'type': pydantic_core.PydanticCustomError(type_str, str(exc)), # type: ignore + 'loc': ('__root__',), + 'input': b, + } + raise pydantic_core.ValidationError.from_exception_data(cls.__name__, [error]) + return cls.model_validate(obj) + + @classmethod + @typing_extensions.deprecated( + 'The `parse_file` method is deprecated; load the data from file, then if your data is JSON ' + 'use `model_validate_json`, otherwise `model_validate` instead.', + category=None, + ) + def parse_file( # noqa: D102 + cls, + path: str | Path, + *, + content_type: str | None = None, + encoding: str = 'utf8', + proto: DeprecatedParseProtocol | None = None, + allow_pickle: bool = False, + ) -> Self: + warnings.warn( + 'The `parse_file` method is deprecated; load the data from file, then if your data is JSON ' + 'use `model_validate_json`, otherwise `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import parse + + obj = parse.load_file( + path, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + ) + return cls.parse_obj(obj) + + @classmethod + @typing_extensions.deprecated( + 'The `from_orm` method is deprecated; set ' + "`model_config['from_attributes']=True` and use `model_validate` instead.", + category=None, + ) + def from_orm(cls, obj: Any) -> Self: # noqa: D102 + warnings.warn( + 'The `from_orm` method is deprecated; set ' + "`model_config['from_attributes']=True` and use `model_validate` instead.", + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + if not cls.model_config.get('from_attributes', None): + raise PydanticUserError( + 'You must set the config attribute `from_attributes=True` to use from_orm', code=None + ) + return cls.model_validate(obj) + + @classmethod + @typing_extensions.deprecated('The `construct` method is deprecated; use `model_construct` instead.', category=None) + def construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: # noqa: D102 + warnings.warn( + 'The `construct` method is deprecated; use `model_construct` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return cls.model_construct(_fields_set=_fields_set, **values) + + @typing_extensions.deprecated( + 'The `copy` method is deprecated; use `model_copy` instead. ' + 'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.', + category=None, + ) + def copy( + self, + *, + include: AbstractSetIntStr | MappingIntStrAny | None = None, + exclude: AbstractSetIntStr | MappingIntStrAny | None = None, + update: Dict[str, Any] | None = None, # noqa UP006 + deep: bool = False, + ) -> Self: # pragma: no cover + """Returns a copy of the model. + + !!! warning "Deprecated" + This method is now deprecated; use `model_copy` instead. + + If you need `include` or `exclude`, use: + + ```python {test="skip" lint="skip"} + data = self.model_dump(include=include, exclude=exclude, round_trip=True) + data = {**data, **(update or {})} + copied = self.model_validate(data) + ``` + + Args: + include: Optional set or mapping specifying which fields to include in the copied model. + exclude: Optional set or mapping specifying which fields to exclude in the copied model. + update: Optional dictionary of field-value pairs to override field values in the copied model. + deep: If True, the values of fields that are Pydantic models will be deep-copied. + + Returns: + A copy of the model with included, excluded and updated fields as specified. + """ + warnings.warn( + 'The `copy` method is deprecated; use `model_copy` instead. ' + 'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import copy_internals + + values = dict( + copy_internals._iter( + self, to_dict=False, by_alias=False, include=include, exclude=exclude, exclude_unset=False + ), + **(update or {}), + ) + if self.__pydantic_private__ is None: + private = None + else: + private = {k: v for k, v in self.__pydantic_private__.items() if v is not PydanticUndefined} + + if self.__pydantic_extra__ is None: + extra: dict[str, Any] | None = None + else: + extra = self.__pydantic_extra__.copy() + for k in list(self.__pydantic_extra__): + if k not in values: # k was in the exclude + extra.pop(k) + for k in list(values): + if k in self.__pydantic_extra__: # k must have come from extra + extra[k] = values.pop(k) + + # new `__pydantic_fields_set__` can have unset optional fields with a set value in `update` kwarg + if update: + fields_set = self.__pydantic_fields_set__ | update.keys() + else: + fields_set = set(self.__pydantic_fields_set__) + + # removing excluded fields from `__pydantic_fields_set__` + if exclude: + fields_set -= set(exclude) + + return copy_internals._copy_and_set_values(self, values, fields_set, extra, private, deep=deep) + + @classmethod + @typing_extensions.deprecated('The `schema` method is deprecated; use `model_json_schema` instead.', category=None) + def schema( # noqa: D102 + cls, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE + ) -> Dict[str, Any]: # noqa UP006 + warnings.warn( + 'The `schema` method is deprecated; use `model_json_schema` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return cls.model_json_schema(by_alias=by_alias, ref_template=ref_template) + + @classmethod + @typing_extensions.deprecated( + 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', + category=None, + ) + def schema_json( # noqa: D102 + cls, *, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, **dumps_kwargs: Any + ) -> str: # pragma: no cover + warnings.warn( + 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + import json + + from .deprecated.json import pydantic_encoder + + return json.dumps( + cls.model_json_schema(by_alias=by_alias, ref_template=ref_template), + default=pydantic_encoder, + **dumps_kwargs, + ) + + @classmethod + @typing_extensions.deprecated('The `validate` method is deprecated; use `model_validate` instead.', category=None) + def validate(cls, value: Any) -> Self: # noqa: D102 + warnings.warn( + 'The `validate` method is deprecated; use `model_validate` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + return cls.model_validate(value) + + @classmethod + @typing_extensions.deprecated( + 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', + category=None, + ) + def update_forward_refs(cls, **localns: Any) -> None: # noqa: D102 + warnings.warn( + 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + if localns: # pragma: no cover + raise TypeError('`localns` arguments are not longer accepted.') + cls.model_rebuild(force=True) + + @typing_extensions.deprecated( + 'The private method `_iter` will be removed and should no longer be used.', category=None + ) + def _iter(self, *args: Any, **kwargs: Any) -> Any: + warnings.warn( + 'The private method `_iter` will be removed and should no longer be used.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import copy_internals + + return copy_internals._iter(self, *args, **kwargs) + + @typing_extensions.deprecated( + 'The private method `_copy_and_set_values` will be removed and should no longer be used.', + category=None, + ) + def _copy_and_set_values(self, *args: Any, **kwargs: Any) -> Any: + warnings.warn( + 'The private method `_copy_and_set_values` will be removed and should no longer be used.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import copy_internals + + return copy_internals._copy_and_set_values(self, *args, **kwargs) + + @classmethod + @typing_extensions.deprecated( + 'The private method `_get_value` will be removed and should no longer be used.', + category=None, + ) + def _get_value(cls, *args: Any, **kwargs: Any) -> Any: + warnings.warn( + 'The private method `_get_value` will be removed and should no longer be used.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import copy_internals + + return copy_internals._get_value(cls, *args, **kwargs) + + @typing_extensions.deprecated( + 'The private method `_calculate_keys` will be removed and should no longer be used.', + category=None, + ) + def _calculate_keys(self, *args: Any, **kwargs: Any) -> Any: + warnings.warn( + 'The private method `_calculate_keys` will be removed and should no longer be used.', + category=PydanticDeprecatedSince20, + stacklevel=2, + ) + from .deprecated import copy_internals + + return copy_internals._calculate_keys(self, *args, **kwargs) + + +ModelT = TypeVar('ModelT', bound=BaseModel) + + +@overload +def create_model( + model_name: str, + /, + *, + __config__: ConfigDict | None = None, + __doc__: str | None = None, + __base__: None = None, + __module__: str = __name__, + __validators__: dict[str, Callable[..., Any]] | None = None, + __cls_kwargs__: dict[str, Any] | None = None, + __qualname__: str | None = None, + **field_definitions: Any | tuple[Any, Any], +) -> type[BaseModel]: ... + + +@overload +def create_model( + model_name: str, + /, + *, + __config__: ConfigDict | None = None, + __doc__: str | None = None, + __base__: type[ModelT] | tuple[type[ModelT], ...], + __module__: str = __name__, + __validators__: dict[str, Callable[..., Any]] | None = None, + __cls_kwargs__: dict[str, Any] | None = None, + __qualname__: str | None = None, + **field_definitions: Any | tuple[Any, Any], +) -> type[ModelT]: ... + + +def create_model( # noqa: C901 + model_name: str, + /, + *, + __config__: ConfigDict | None = None, + __doc__: str | None = None, + __base__: type[ModelT] | tuple[type[ModelT], ...] | None = None, + __module__: str | None = None, + __validators__: dict[str, Callable[..., Any]] | None = None, + __cls_kwargs__: dict[str, Any] | None = None, + __qualname__: str | None = None, + # TODO PEP 747: replace `Any` by the TypeForm: + **field_definitions: Any | tuple[Any, Any], +) -> type[ModelT]: + """!!! abstract "Usage Documentation" + [Dynamic Model Creation](../concepts/models.md#dynamic-model-creation) + + Dynamically creates and returns a new Pydantic model, in other words, `create_model` dynamically creates a + subclass of [`BaseModel`][pydantic.BaseModel]. + + !!! warning + This function may execute arbitrary code contained in field annotations, if string references need to be evaluated. + + See [Security implications of introspecting annotations](https://docs.python.org/3/library/annotationlib.html#annotationlib-security) for more information. + + Args: + model_name: The name of the newly created model. + __config__: The configuration of the new model. + __doc__: The docstring of the new model. + __base__: The base class or classes for the new model. + __module__: The name of the module that the model belongs to; + if `None`, the value is taken from `sys._getframe(1)` + __validators__: A dictionary of methods that validate fields. The keys are the names of the validation methods to + be added to the model, and the values are the validation methods themselves. You can read more about functional + validators [here](https://docs.pydantic.dev/2.9/concepts/validators/#field-validators). + __cls_kwargs__: A dictionary of keyword arguments for class creation, such as `metaclass`. + __qualname__: The qualified name of the newly created model. + **field_definitions: Field definitions of the new model. Either: + + - a single element, representing the type annotation of the field. + - a two-tuple, the first element being the type and the second element the assigned value + (either a default or the [`Field()`][pydantic.Field] function). + + Returns: + The new [model][pydantic.BaseModel]. + + Raises: + PydanticUserError: If `__base__` and `__config__` are both passed. + """ + if __base__ is None: + __base__ = (cast('type[ModelT]', BaseModel),) + elif not isinstance(__base__, tuple): + __base__ = (__base__,) + + __cls_kwargs__ = __cls_kwargs__ or {} + + fields: dict[str, Any] = {} + annotations: dict[str, Any] = {} + + for f_name, f_def in field_definitions.items(): + if isinstance(f_def, tuple): + if len(f_def) != 2: + raise PydanticUserError( + f'Field definition for {f_name!r} should a single element representing the type or a two-tuple, the first element ' + 'being the type and the second element the assigned value (either a default or the `Field()` function).', + code='create-model-field-definitions', + ) + + annotations[f_name] = f_def[0] + fields[f_name] = f_def[1] + else: + annotations[f_name] = f_def + + if __module__ is None: + f = sys._getframe(1) + __module__ = f.f_globals['__name__'] + + namespace: dict[str, Any] = {'__annotations__': annotations, '__module__': __module__} + if __doc__: + namespace['__doc__'] = __doc__ + if __qualname__ is not None: + namespace['__qualname__'] = __qualname__ + if __validators__: + namespace.update(__validators__) + namespace.update(fields) + if __config__: + namespace['model_config'] = __config__ + resolved_bases = types.resolve_bases(__base__) + meta, ns, kwds = types.prepare_class(model_name, resolved_bases, kwds=__cls_kwargs__) + if resolved_bases is not __base__: + ns['__orig_bases__'] = __base__ + namespace.update(ns) + + return meta( + model_name, + resolved_bases, + namespace, + __pydantic_reset_parent_namespace__=False, + _create_model_module=__module__, + **kwds, + ) + + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py new file mode 100644 index 0000000..4c73b54 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py @@ -0,0 +1,1414 @@ +"""This module includes classes and functions designed specifically for use with the mypy plugin.""" + +from __future__ import annotations + +import sys +from collections.abc import Iterator +from configparser import ConfigParser +from typing import Any, Callable + +# To avoid import error (https://github.com/python/mypy/issues/17726): +import mypy.types # noqa: F401 +from mypy.errorcodes import ErrorCode +from mypy.expandtype import expand_type, expand_type_by_instance +from mypy.nodes import ( + ARG_NAMED, + ARG_NAMED_OPT, + ARG_OPT, + ARG_POS, + ARG_STAR2, + INVARIANT, + MDEF, + Argument, + AssignmentStmt, + Block, + CallExpr, + ClassDef, + Context, + Decorator, + DictExpr, + EllipsisExpr, + Expression, + FuncDef, + IfStmt, + JsonDict, + MemberExpr, + NameExpr, + PassStmt, + PlaceholderNode, + RefExpr, + Statement, + StrExpr, + SymbolTableNode, + TempNode, + TypeAlias, + TypeInfo, + Var, +) +from mypy.options import Options +from mypy.plugin import ( + CheckerPluginInterface, + ClassDefContext, + DynamicClassDefContext, + MethodContext, + Plugin, + ReportConfigContext, + SemanticAnalyzerPluginInterface, +) +from mypy.plugins.common import ( + deserialize_and_fixup_type, +) +from mypy.semanal import set_callable_name +from mypy.server.trigger import make_wildcard_trigger +from mypy.state import state +from mypy.type_visitor import TypeTranslator +from mypy.typeops import map_type_from_supertype +from mypy.types import ( + AnyType, + CallableType, + Instance, + NoneType, + Type, + TypeOfAny, + TypeType, + TypeVarType, + UnionType, + get_proper_type, +) +from mypy.typevars import fill_typevars +from mypy.util import get_unique_redefinition_name +from mypy.version import __version__ as mypy_version + +from pydantic._internal import _fields +from pydantic.version import parse_mypy_version + +CONFIGFILE_KEY = 'pydantic-mypy' +METADATA_KEY = 'pydantic-mypy-metadata' +BASEMODEL_FULLNAME = 'pydantic.main.BaseModel' +CREATE_MODEL_FULLNAME = 'pydantic.main.create_model' +BASESETTINGS_FULLNAME = 'pydantic_settings.main.BaseSettings' +ROOT_MODEL_FULLNAME = 'pydantic.root_model.RootModel' +MODEL_METACLASS_FULLNAME = 'pydantic._internal._model_construction.ModelMetaclass' +FIELD_FULLNAME = 'pydantic.fields.Field' +DATACLASS_FULLNAME = 'pydantic.dataclasses.dataclass' +MODEL_VALIDATOR_FULLNAME = 'pydantic.functional_validators.model_validator' +DECORATOR_FULLNAMES = { + 'pydantic.functional_validators.field_validator', + 'pydantic.functional_validators.model_validator', + 'pydantic.functional_serializers.serializer', + 'pydantic.functional_serializers.model_serializer', + 'pydantic.deprecated.class_validators.validator', + 'pydantic.deprecated.class_validators.root_validator', +} +IMPLICIT_CLASSMETHOD_DECORATOR_FULLNAMES = DECORATOR_FULLNAMES - {'pydantic.functional_serializers.model_serializer'} + + +MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version) +BUILTINS_NAME = 'builtins' + +# Increment version if plugin changes and mypy caches should be invalidated +__version__ = 2 + + +def plugin(version: str) -> type[Plugin]: + """`version` is the mypy version string. + + We might want to use this to print a warning if the mypy version being used is + newer, or especially older, than we expect (or need). + + Args: + version: The mypy version string. + + Return: + The Pydantic mypy plugin type. + """ + return PydanticPlugin + + +class PydanticPlugin(Plugin): + """The Pydantic mypy plugin.""" + + def __init__(self, options: Options) -> None: + self.plugin_config = PydanticPluginConfig(options) + self._plugin_data = self.plugin_config.to_data() + super().__init__(options) + + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: + """Update Pydantic model class.""" + sym = self.lookup_fully_qualified(fullname) + if sym and isinstance(sym.node, TypeInfo): # pragma: no branch + # No branching may occur if the mypy cache has not been cleared + if sym.node.has_base(BASEMODEL_FULLNAME): + return self._pydantic_model_class_maker_callback + return None + + def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: + """Update Pydantic `ModelMetaclass` definition.""" + if fullname == MODEL_METACLASS_FULLNAME: + return self._pydantic_model_metaclass_marker_callback + return None + + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: + """Adjust return type of `from_orm` method call.""" + if fullname.endswith('.from_orm'): + return from_attributes_callback + return None + + def get_dynamic_class_hook(self, fullname: str) -> Callable[[DynamicClassDefContext], None] | None: + """Recognize `create_model()` calls as dynamic BaseModel subclasses.""" + if fullname == CREATE_MODEL_FULLNAME: + return self._pydantic_create_model_callback + return None + + def report_config_data(self, ctx: ReportConfigContext) -> dict[str, Any]: + """Return all plugin config data. + + Used by mypy to determine if cache needs to be discarded. + """ + return self._plugin_data + + def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None: + transformer = PydanticModelTransformer(ctx.cls, ctx.reason, ctx.api, self.plugin_config) + transformer.transform() + + def _pydantic_model_metaclass_marker_callback(self, ctx: ClassDefContext) -> None: + """Reset dataclass_transform_spec attribute of ModelMetaclass. + + Let the plugin handle it. This behavior can be disabled + if 'debug_dataclass_transform' is set to True', for testing purposes. + """ + if self.plugin_config.debug_dataclass_transform: + return + info_metaclass = ctx.cls.info.declared_metaclass + assert info_metaclass, "callback not passed from 'get_metaclass_hook'" + if getattr(info_metaclass.type, 'dataclass_transform_spec', None): + info_metaclass.type.dataclass_transform_spec = None + + def _pydantic_create_model_callback(self, ctx: DynamicClassDefContext) -> None: + """Make variables assigned from `create_model()` usable as types by mypy.""" + # Determine the base class from __base__ argument if provided + base_fullname = BASEMODEL_FULLNAME + for arg_name, arg_expr in zip(ctx.call.arg_names, ctx.call.args): + if arg_name == '__base__' and isinstance(arg_expr, RefExpr) and arg_expr.node is not None: + if isinstance(arg_expr.node, TypeInfo): + base_fullname = arg_expr.node.fullname + elif isinstance(arg_expr.node, Var) and isinstance(arg_expr.node.type, Instance): + base_fullname = arg_expr.node.type.type.fullname + + base_sym = ctx.api.lookup_fully_qualified_or_none(base_fullname) + if base_sym is None or not isinstance(base_sym.node, TypeInfo): + # Fall back to BaseModel + base_sym = ctx.api.lookup_fully_qualified_or_none(BASEMODEL_FULLNAME) + if base_sym is None or not isinstance(base_sym.node, TypeInfo): + return + + base_info = base_sym.node + base_instance = fill_typevars(base_info) + assert isinstance(base_instance, Instance) + + info = ctx.api.basic_new_typeinfo(ctx.name, base_instance, ctx.call.line) + info.metaclass_type = base_info.metaclass_type + + ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(MDEF, info)) + + +class PydanticPluginConfig: + """A Pydantic mypy plugin config holder. + + Attributes: + init_forbid_extra: Whether to add a `**kwargs` at the end of the generated `__init__` signature. + init_typed: Whether to annotate fields in the generated `__init__`. + warn_required_dynamic_aliases: Whether to raise required dynamic aliases error. + debug_dataclass_transform: Whether to not reset `dataclass_transform_spec` attribute + of `ModelMetaclass` for testing purposes. + """ + + __slots__ = ( + 'init_forbid_extra', + 'init_typed', + 'warn_required_dynamic_aliases', + 'debug_dataclass_transform', + ) + init_forbid_extra: bool + init_typed: bool + warn_required_dynamic_aliases: bool + debug_dataclass_transform: bool # undocumented + + def __init__(self, options: Options) -> None: + if options.config_file is None: # pragma: no cover + return + + toml_config = parse_toml(options.config_file) + if toml_config is not None: + config = toml_config.get('tool', {}).get('pydantic-mypy', {}) + for key in self.__slots__: + setting = config.get(key, False) + if not isinstance(setting, bool): + raise ValueError(f'Configuration value must be a boolean for key: {key}') + setattr(self, key, setting) + else: + plugin_config = ConfigParser() + plugin_config.read(options.config_file) + for key in self.__slots__: + setting = plugin_config.getboolean(CONFIGFILE_KEY, key, fallback=False) + setattr(self, key, setting) + + def to_data(self) -> dict[str, Any]: + """Returns a dict of config names to their values.""" + return {key: getattr(self, key) for key in self.__slots__} + + +def from_attributes_callback(ctx: MethodContext) -> Type: + """Raise an error if from_attributes is not enabled.""" + model_type: Instance + ctx_type = ctx.type + if isinstance(ctx_type, TypeType): + ctx_type = ctx_type.item + if isinstance(ctx_type, CallableType) and isinstance(ctx_type.ret_type, Instance): + model_type = ctx_type.ret_type # called on the class + elif isinstance(ctx_type, Instance): + model_type = ctx_type # called on an instance (unusual, but still valid) + else: # pragma: no cover + detail = f'ctx.type: {ctx_type} (of type {ctx_type.__class__.__name__})' + error_unexpected_behavior(detail, ctx.api, ctx.context) + return ctx.default_return_type + pydantic_metadata = model_type.type.metadata.get(METADATA_KEY) + if pydantic_metadata is None: + return ctx.default_return_type + if not model_type.type.has_base(BASEMODEL_FULLNAME): + # not a Pydantic v2 model + return ctx.default_return_type + from_attributes = pydantic_metadata.get('config', {}).get('from_attributes') + if from_attributes is not True: + error_from_attributes(model_type.type.name, ctx.api, ctx.context) + return ctx.default_return_type + + +class PydanticModelField: + """Based on mypy.plugins.dataclasses.DataclassAttribute.""" + + def __init__( + self, + name: str, + alias: str | None, + is_frozen: bool, + has_dynamic_alias: bool, + has_default: bool, + strict: bool | None, + line: int, + column: int, + type: Type | None, + info: TypeInfo, + ): + self.name = name + self.alias = alias + self.is_frozen = is_frozen + self.has_dynamic_alias = has_dynamic_alias + self.has_default = has_default + self.strict = strict + self.line = line + self.column = column + self.type = type + self.info = info + + def to_argument( + self, + current_info: TypeInfo, + typed: bool, + model_strict: bool, + force_optional: bool, + use_alias: bool, + api: SemanticAnalyzerPluginInterface, + force_typevars_invariant: bool, + is_root_model_root: bool, + ) -> Argument: + """Based on mypy.plugins.dataclasses.DataclassAttribute.to_argument.""" + variable = self.to_var(current_info, api, use_alias, force_typevars_invariant) + + strict = model_strict if self.strict is None else self.strict + if typed or strict: + type_annotation = self.expand_type(current_info, api, include_root_type=True) + else: + type_annotation = AnyType(TypeOfAny.explicit) + + return Argument( + variable=variable, + type_annotation=type_annotation, + initializer=None, + kind=ARG_OPT + if is_root_model_root + else (ARG_NAMED_OPT if force_optional or self.has_default else ARG_NAMED), + ) + + def expand_type( + self, + current_info: TypeInfo, + api: SemanticAnalyzerPluginInterface, + force_typevars_invariant: bool = False, + include_root_type: bool = False, + ) -> Type | None: + """Based on mypy.plugins.dataclasses.DataclassAttribute.expand_type.""" + if force_typevars_invariant: + # In some cases, mypy will emit an error "Cannot use a covariant type variable as a parameter" + # To prevent that, we add an option to replace typevars with invariant ones while building certain + # method signatures (in particular, `__init__`). There may be a better way to do this, if this causes + # us problems in the future, we should look into why the dataclasses plugin doesn't have this issue. + if isinstance(self.type, TypeVarType): + modified_type = self.type.copy_modified() + modified_type.variance = INVARIANT + self.type = modified_type + + if self.type is not None and self.info.self_type is not None: + # In general, it is not safe to call `expand_type()` during semantic analysis, + # however this plugin is called very late, so all types should be fully ready. + # Also, it is tricky to avoid eager expansion of Self types here (e.g. because + # we serialize attributes). + with state.strict_optional_set(api.options.strict_optional): + filled_with_typevars = fill_typevars(current_info) + # Cannot be TupleType as current_info represents a Pydantic model: + assert isinstance(filled_with_typevars, Instance) + if force_typevars_invariant: + for arg in filled_with_typevars.args: + if isinstance(arg, TypeVarType): + arg.variance = INVARIANT + + expanded_type = expand_type(self.type, {self.info.self_type.id: filled_with_typevars}) + if include_root_type and isinstance(expanded_type, Instance) and is_root_model(expanded_type.type): + # When a root model is used as a field, Pydantic allows both an instance of the root model + # as well as instances of the `root` field type: + root_type = expanded_type.type['root'].type + if root_type is None: + # Happens if the hint for 'root' has unsolved forward references + return expanded_type + expanded_root_type = expand_type_by_instance(root_type, expanded_type) + expanded_type = UnionType([expanded_type, expanded_root_type]) + return expanded_type + return self.type + + def to_var( + self, + current_info: TypeInfo, + api: SemanticAnalyzerPluginInterface, + use_alias: bool, + force_typevars_invariant: bool = False, + ) -> Var: + """Based on mypy.plugins.dataclasses.DataclassAttribute.to_var.""" + if use_alias and self.alias is not None: + name = self.alias + else: + name = self.name + + return Var(name, self.expand_type(current_info, api, force_typevars_invariant)) + + def serialize(self) -> JsonDict: + """Based on mypy.plugins.dataclasses.DataclassAttribute.serialize.""" + assert self.type + return { + 'name': self.name, + 'alias': self.alias, + 'is_frozen': self.is_frozen, + 'has_dynamic_alias': self.has_dynamic_alias, + 'has_default': self.has_default, + 'strict': self.strict, + 'line': self.line, + 'column': self.column, + 'type': self.type.serialize(), + } + + @classmethod + def deserialize(cls, info: TypeInfo, data: JsonDict, api: SemanticAnalyzerPluginInterface) -> PydanticModelField: + """Based on mypy.plugins.dataclasses.DataclassAttribute.deserialize.""" + data = data.copy() + typ = deserialize_and_fixup_type(data.pop('type'), api) + return cls(type=typ, info=info, **data) + + def expand_typevar_from_subtype(self, sub_type: TypeInfo, api: SemanticAnalyzerPluginInterface) -> None: + """Expands type vars in the context of a subtype when an attribute is inherited + from a generic super type. + """ + if self.type is not None: + with state.strict_optional_set(api.options.strict_optional): + self.type = map_type_from_supertype(self.type, sub_type, self.info) + + +class PydanticModelClassVar: + """Based on mypy.plugins.dataclasses.DataclassAttribute. + + ClassVars are ignored by subclasses. + + Attributes: + name: the ClassVar name + """ + + def __init__(self, name): + self.name = name + + @classmethod + def deserialize(cls, data: JsonDict) -> PydanticModelClassVar: + """Based on mypy.plugins.dataclasses.DataclassAttribute.deserialize.""" + data = data.copy() + return cls(**data) + + def serialize(self) -> JsonDict: + """Based on mypy.plugins.dataclasses.DataclassAttribute.serialize.""" + return { + 'name': self.name, + } + + +class PydanticModelTransformer: + """Transform the BaseModel subclass according to the plugin settings. + + Attributes: + tracked_config_fields: A set of field configs that the plugin has to track their value. + """ + + tracked_config_fields: set[str] = { + 'extra', + 'frozen', + 'from_attributes', + 'populate_by_name', + 'validate_by_alias', + 'validate_by_name', + 'alias_generator', + 'strict', + } + + def __init__( + self, + cls: ClassDef, + reason: Expression | Statement, + api: SemanticAnalyzerPluginInterface, + plugin_config: PydanticPluginConfig, + ) -> None: + self._cls = cls + self._reason = reason + self._api = api + + self.plugin_config = plugin_config + + def transform(self) -> bool: + """Configures the BaseModel subclass according to the plugin settings. + + In particular: + + * determines the model config and fields, + * adds a fields-aware signature for the initializer and construct methods + * freezes the class if frozen = True + * stores the fields, config, and if the class is settings in the mypy metadata for access by subclasses + """ + info = self._cls.info + is_a_root_model = is_root_model(info) + config = self.collect_config() + fields, class_vars = self.collect_fields_and_class_vars(config, is_a_root_model) + if fields is None or class_vars is None: + # Some definitions are not ready. We need another pass. + return False + for field in fields: + if field.type is None: + return False + + is_settings = info.has_base(BASESETTINGS_FULLNAME) + self.add_initializer(fields, config, is_settings, is_a_root_model) + self.add_model_construct_method(fields, config, is_settings, is_a_root_model) + self.set_frozen(fields, self._api, frozen=config.frozen is True) + + self.adjust_decorator_signatures() + + info.metadata[METADATA_KEY] = { + 'fields': {field.name: field.serialize() for field in fields}, + 'class_vars': {class_var.name: class_var.serialize() for class_var in class_vars}, + 'config': config.get_values_dict(), + } + + return True + + def adjust_decorator_signatures(self) -> None: + """When we decorate a function `f` with `pydantic.validator(...)`, `pydantic.field_validator` + or `pydantic.serializer(...)`, mypy sees `f` as a regular method taking a `self` instance, + even though pydantic internally wraps `f` with `classmethod` if necessary. + + Teach mypy this by marking any function whose outermost decorator is a `validator()`, + `field_validator()` or `serializer()` call as a `classmethod`. + """ + for sym in self._cls.info.names.values(): + if isinstance(sym.node, Decorator): + first_dec = sym.node.original_decorators[0] + if ( + isinstance(first_dec, CallExpr) + and isinstance(first_dec.callee, NameExpr) + and first_dec.callee.fullname in IMPLICIT_CLASSMETHOD_DECORATOR_FULLNAMES + # @model_validator(mode="after") is an exception, it expects a regular method + and not ( + first_dec.callee.fullname == MODEL_VALIDATOR_FULLNAME + and any( + first_dec.arg_names[i] == 'mode' and isinstance(arg, StrExpr) and arg.value == 'after' + for i, arg in enumerate(first_dec.args) + ) + ) + ): + # TODO: Only do this if the first argument of the decorated function is `cls` + sym.node.func.is_class = True + + def collect_config(self) -> ModelConfigData: # noqa: C901 (ignore complexity) + """Collects the values of the config attributes that are used by the plugin, accounting for parent classes.""" + cls = self._cls + config = ModelConfigData() + + has_config_kwargs = False + has_config_from_namespace = False + + # Handle `class MyModel(BaseModel, =, ...):` + for name, expr in cls.keywords.items(): + config_data = self.get_config_update(name, expr) + if config_data: + has_config_kwargs = True + config.update(config_data) + + # Handle `model_config` + stmt: Statement | None = None + for stmt in cls.defs.body: + if not isinstance(stmt, (AssignmentStmt, ClassDef)): + continue + + if isinstance(stmt, AssignmentStmt): + lhs = stmt.lvalues[0] + if not isinstance(lhs, NameExpr) or lhs.name != 'model_config': + continue + + if isinstance(stmt.rvalue, CallExpr): # calls to `dict` or `ConfigDict` + for arg_name, arg in zip(stmt.rvalue.arg_names, stmt.rvalue.args): + if arg_name is None: + continue + config.update(self.get_config_update(arg_name, arg, lax_extra=True)) + elif isinstance(stmt.rvalue, DictExpr): # dict literals + for key_expr, value_expr in stmt.rvalue.items: + if not isinstance(key_expr, StrExpr): + continue + config.update(self.get_config_update(key_expr.value, value_expr)) + + elif isinstance(stmt, ClassDef): + if stmt.name != 'Config': # 'deprecated' Config-class + continue + for substmt in stmt.defs.body: + if not isinstance(substmt, AssignmentStmt): + continue + lhs = substmt.lvalues[0] + if not isinstance(lhs, NameExpr): + continue + config.update(self.get_config_update(lhs.name, substmt.rvalue)) + + if has_config_kwargs: + self._api.fail( + 'Specifying config in two places is ambiguous, use either Config attribute or class kwargs', + cls, + ) + break + + has_config_from_namespace = True + + if has_config_kwargs or has_config_from_namespace: + if ( + stmt + and config.has_alias_generator + and not (config.validate_by_name or config.populate_by_name) + and self.plugin_config.warn_required_dynamic_aliases + ): + error_required_dynamic_aliases(self._api, stmt) + + for info in cls.info.mro[1:]: # 0 is the current class + if METADATA_KEY not in info.metadata: + continue + + # Each class depends on the set of fields in its ancestors + self._api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) + for name, value in info.metadata[METADATA_KEY]['config'].items(): + config.setdefault(name, value) + return config + + def collect_fields_and_class_vars( + self, model_config: ModelConfigData, is_root_model: bool + ) -> tuple[list[PydanticModelField] | None, list[PydanticModelClassVar] | None]: + """Collects the fields for the model, accounting for parent classes.""" + cls = self._cls + + # First, collect fields and ClassVars belonging to any class in the MRO, ignoring duplicates. + # + # We iterate through the MRO in reverse because attrs defined in the parent must appear + # earlier in the attributes list than attrs defined in the child. See: + # https://docs.python.org/3/library/dataclasses.html#inheritance + # + # However, we also want fields defined in the subtype to override ones defined + # in the parent. We can implement this via a dict without disrupting the attr order + # because dicts preserve insertion order in Python 3.7+. + found_fields: dict[str, PydanticModelField] = {} + found_class_vars: dict[str, PydanticModelClassVar] = {} + for info in reversed(cls.info.mro[1:-1]): # 0 is the current class, -2 is BaseModel, -1 is object + # if BASEMODEL_METADATA_TAG_KEY in info.metadata and BASEMODEL_METADATA_KEY not in info.metadata: + # # We haven't processed the base class yet. Need another pass. + # return None, None + if METADATA_KEY not in info.metadata: + continue + + # Each class depends on the set of attributes in its dataclass ancestors. + self._api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) + + for name, data in info.metadata[METADATA_KEY]['fields'].items(): + field = PydanticModelField.deserialize(info, data, self._api) + # (The following comment comes directly from the dataclasses plugin) + # TODO: We shouldn't be performing type operations during the main + # semantic analysis pass, since some TypeInfo attributes might + # still be in flux. This should be performed in a later phase. + field.expand_typevar_from_subtype(cls.info, self._api) + found_fields[name] = field + + sym_node = cls.info.names.get(name) + if sym_node and sym_node.node and not isinstance(sym_node.node, (Var, PlaceholderNode)): + self._api.fail( + 'BaseModel field may only be overridden by another field', + sym_node.node, + ) + # Collect ClassVars + for name, data in info.metadata[METADATA_KEY]['class_vars'].items(): + found_class_vars[name] = PydanticModelClassVar.deserialize(data) + + # Second, collect fields and ClassVars belonging to the current class. + current_field_names: set[str] = set() + current_class_vars_names: set[str] = set() + for stmt in self._get_assignment_statements_from_block(cls.defs): + maybe_field = self.collect_field_or_class_var_from_stmt(stmt, model_config, found_class_vars) + if maybe_field is None: + continue + + lhs = stmt.lvalues[0] + assert isinstance(lhs, NameExpr) # collect_field_or_class_var_from_stmt guarantees this + if isinstance(maybe_field, PydanticModelField): + if is_root_model and lhs.name != 'root': + error_extra_fields_on_root_model(self._api, stmt) + else: + current_field_names.add(lhs.name) + found_fields[lhs.name] = maybe_field + elif isinstance(maybe_field, PydanticModelClassVar): + current_class_vars_names.add(lhs.name) + found_class_vars[lhs.name] = maybe_field + + return list(found_fields.values()), list(found_class_vars.values()) + + def _get_assignment_statements_from_if_statement(self, stmt: IfStmt) -> Iterator[AssignmentStmt]: + for body in stmt.body: + if not body.is_unreachable: + yield from self._get_assignment_statements_from_block(body) + if stmt.else_body is not None and not stmt.else_body.is_unreachable: + yield from self._get_assignment_statements_from_block(stmt.else_body) + + def _get_assignment_statements_from_block(self, block: Block) -> Iterator[AssignmentStmt]: + for stmt in block.body: + if isinstance(stmt, AssignmentStmt): + yield stmt + elif isinstance(stmt, IfStmt): + yield from self._get_assignment_statements_from_if_statement(stmt) + + def collect_field_or_class_var_from_stmt( # noqa C901 + self, stmt: AssignmentStmt, model_config: ModelConfigData, class_vars: dict[str, PydanticModelClassVar] + ) -> PydanticModelField | PydanticModelClassVar | None: + """Get pydantic model field from statement. + + Args: + stmt: The statement. + model_config: Configuration settings for the model. + class_vars: ClassVars already known to be defined on the model. + + Returns: + A pydantic model field if it could find the field in statement. Otherwise, `None`. + """ + cls = self._cls + + lhs = stmt.lvalues[0] + if not isinstance(lhs, NameExpr) or not _fields.is_valid_field_name(lhs.name) or lhs.name == 'model_config': + return None + + if not stmt.new_syntax: + if ( + isinstance(stmt.rvalue, CallExpr) + and isinstance(stmt.rvalue.callee, CallExpr) + and isinstance(stmt.rvalue.callee.callee, NameExpr) + and stmt.rvalue.callee.callee.fullname in DECORATOR_FULLNAMES + ): + # This is a (possibly-reused) validator or serializer, not a field + # In particular, it looks something like: my_validator = validator('my_field')(f) + # Eventually, we may want to attempt to respect model_config['ignored_types'] + return None + + if lhs.name in class_vars: + # Class vars are not fields and are not required to be annotated + return None + + # The assignment does not have an annotation, and it's not anything else we recognize + error_untyped_fields(self._api, stmt) + return None + + lhs = stmt.lvalues[0] + if not isinstance(lhs, NameExpr): + return None + + if not _fields.is_valid_field_name(lhs.name) or lhs.name == 'model_config': + return None + + sym = cls.info.names.get(lhs.name) + if sym is None: # pragma: no cover + # This is likely due to a star import (see the dataclasses plugin for a more detailed explanation) + # This is the same logic used in the dataclasses plugin + return None + + node = sym.node + if isinstance(node, PlaceholderNode): # pragma: no cover + # See the PlaceholderNode docstring for more detail about how this can occur + # Basically, it is an edge case when dealing with complex import logic + + # The dataclasses plugin now asserts this cannot happen, but I'd rather not error if it does.. + return None + + if isinstance(node, TypeAlias): + self._api.fail( + 'Type aliases inside BaseModel definitions are not supported at runtime', + node, + ) + # Skip processing this node. This doesn't match the runtime behaviour, + # but the only alternative would be to modify the SymbolTable, + # and it's a little hairy to do that in a plugin. + return None + + if not isinstance(node, Var): # pragma: no cover + # Don't know if this edge case still happens with the `is_valid_field` check above + # but better safe than sorry + + # The dataclasses plugin now asserts this cannot happen, but I'd rather not error if it does.. + return None + + # x: ClassVar[int] is not a field + if node.is_classvar: + return PydanticModelClassVar(lhs.name) + + # x: InitVar[int] is not supported in BaseModel + node_type = get_proper_type(node.type) + if isinstance(node_type, Instance) and node_type.type.fullname == 'dataclasses.InitVar': + self._api.fail( + 'InitVar is not supported in BaseModel', + node, + ) + + has_default = self.get_has_default(stmt) + strict = self.get_strict(stmt) + + if sym.type is None and node.is_final and node.is_inferred: + # This follows the logic from the dataclasses plugin. The following comment is taken verbatim: + # + # This is a special case, assignment like x: Final = 42 is classified + # annotated above, but mypy strips the `Final` turning it into x = 42. + # We do not support inferred types in dataclasses, so we can try inferring + # type for simple literals, and otherwise require an explicit type + # argument for Final[...]. + typ = self._api.analyze_simple_literal_type(stmt.rvalue, is_final=True) + if typ: + node.type = typ + else: + self._api.fail( + 'Need type argument for Final[...] with non-literal default in BaseModel', + stmt, + ) + node.type = AnyType(TypeOfAny.from_error) + + if node.is_final and has_default: + # TODO this path should be removed (see https://github.com/pydantic/pydantic/issues/11119) + return PydanticModelClassVar(lhs.name) + + alias, has_dynamic_alias = self.get_alias_info(stmt) + if ( + has_dynamic_alias + and not (model_config.validate_by_name or model_config.populate_by_name) + and self.plugin_config.warn_required_dynamic_aliases + ): + error_required_dynamic_aliases(self._api, stmt) + is_frozen = self.is_field_frozen(stmt) + + init_type = self._infer_dataclass_attr_init_type(sym, lhs.name, stmt) + return PydanticModelField( + name=lhs.name, + has_dynamic_alias=has_dynamic_alias, + has_default=has_default, + strict=strict, + alias=alias, + is_frozen=is_frozen, + line=stmt.line, + column=stmt.column, + type=init_type, + info=cls.info, + ) + + def _infer_dataclass_attr_init_type(self, sym: SymbolTableNode, name: str, context: Context) -> Type | None: + """Infer __init__ argument type for an attribute. + + In particular, possibly use the signature of __set__. + """ + default = sym.type + if sym.implicit: + return default + t = get_proper_type(sym.type) + + # Perform a simple-minded inference from the signature of __set__, if present. + # We can't use mypy.checkmember here, since this plugin runs before type checking. + # We only support some basic scanerios here, which is hopefully sufficient for + # the vast majority of use cases. + if not isinstance(t, Instance): + return default + setter = t.type.get('__set__') + if setter: + if isinstance(setter.node, FuncDef): + super_info = t.type.get_containing_type_info('__set__') + assert super_info + if setter.type: + setter_type = get_proper_type(map_type_from_supertype(setter.type, t.type, super_info)) + else: + return AnyType(TypeOfAny.unannotated) + if isinstance(setter_type, CallableType) and setter_type.arg_kinds == [ + ARG_POS, + ARG_POS, + ARG_POS, + ]: + return expand_type_by_instance(setter_type.arg_types[2], t) + else: + self._api.fail(f'Unsupported signature for "__set__" in "{t.type.name}"', context) + else: + self._api.fail(f'Unsupported "__set__" in "{t.type.name}"', context) + + return default + + def add_initializer( + self, fields: list[PydanticModelField], config: ModelConfigData, is_settings: bool, is_root_model: bool + ) -> None: + """Adds a fields-aware `__init__` method to the class. + + The added `__init__` will be annotated with types vs. all `Any` depending on the plugin settings. + """ + if '__init__' in self._cls.info.names and not self._cls.info.names['__init__'].plugin_generated: + return # Don't generate an __init__ if one already exists + + typed = self.plugin_config.init_typed + model_strict = bool(config.strict) + use_alias = not (config.validate_by_name or config.populate_by_name) and config.validate_by_alias is not False + requires_dynamic_aliases = bool(config.has_alias_generator and not config.validate_by_name) + args = self.get_field_arguments( + fields, + typed=typed, + model_strict=model_strict, + requires_dynamic_aliases=requires_dynamic_aliases, + use_alias=use_alias, + is_settings=is_settings, + is_root_model=is_root_model, + force_typevars_invariant=True, + ) + + if is_settings: + base_settings_node = self._api.lookup_fully_qualified(BASESETTINGS_FULLNAME).node + assert isinstance(base_settings_node, TypeInfo) + if '__init__' in base_settings_node.names: + base_settings_init_node = base_settings_node.names['__init__'].node + assert isinstance(base_settings_init_node, FuncDef) + if base_settings_init_node is not None and base_settings_init_node.type is not None: + func_type = base_settings_init_node.type + assert isinstance(func_type, CallableType) + for arg_idx, arg_name in enumerate(func_type.arg_names): + if arg_name is None or arg_name.startswith('__') or not arg_name.startswith('_'): + continue + analyzed_variable_type = self._api.anal_type(func_type.arg_types[arg_idx]) + if analyzed_variable_type is not None and arg_name in ( + '_cli_settings_source', + '_build_sources', + ): + # These arg names are annotated with types explicitly parameterized with `Any`, and as such + # the Any causes issues with --disallow-any-explicit. As a workaround, change + # the Any type (as if the generic type was left unparameterized): + analyzed_variable_type = analyzed_variable_type.accept( + ChangeExplicitTypeOfAny(TypeOfAny.from_omitted_generics) + ) + variable = Var(arg_name, analyzed_variable_type) + args.append(Argument(variable, analyzed_variable_type, None, ARG_OPT)) + + if not self.should_init_forbid_extra(fields, config): + var = Var('kwargs') + args.append(Argument(var, AnyType(TypeOfAny.explicit), None, ARG_STAR2)) + + add_method(self._api, self._cls, '__init__', args=args, return_type=NoneType()) + + def add_model_construct_method( + self, + fields: list[PydanticModelField], + config: ModelConfigData, + is_settings: bool, + is_root_model: bool, + ) -> None: + """Adds a fully typed `model_construct` classmethod to the class. + + Similar to the fields-aware __init__ method, but always uses the field names (not aliases), + and does not treat settings fields as optional. + """ + set_str = self._api.named_type(f'{BUILTINS_NAME}.set', [self._api.named_type(f'{BUILTINS_NAME}.str')]) + optional_set_str = UnionType([set_str, NoneType()]) + fields_set_argument = Argument(Var('_fields_set', optional_set_str), optional_set_str, None, ARG_OPT) + with state.strict_optional_set(self._api.options.strict_optional): + args = self.get_field_arguments( + fields, + typed=True, + model_strict=bool(config.strict), + requires_dynamic_aliases=False, + use_alias=False, + is_settings=is_settings, + is_root_model=is_root_model, + ) + if not self.should_init_forbid_extra(fields, config): + var = Var('kwargs') + args.append(Argument(var, AnyType(TypeOfAny.explicit), None, ARG_STAR2)) + + args = args + [fields_set_argument] if is_root_model else [fields_set_argument] + args + + add_method( + self._api, + self._cls, + 'model_construct', + args=args, + return_type=fill_typevars(self._cls.info), + is_classmethod=True, + ) + + def set_frozen(self, fields: list[PydanticModelField], api: SemanticAnalyzerPluginInterface, frozen: bool) -> None: + """Marks all fields as properties so that attempts to set them trigger mypy errors. + + This is the same approach used by the attrs and dataclasses plugins. + """ + info = self._cls.info + for field in fields: + sym_node = info.names.get(field.name) + if sym_node is not None: + var = sym_node.node + if isinstance(var, Var): + var.is_property = frozen or field.is_frozen + elif isinstance(var, PlaceholderNode) and not self._api.final_iteration: + # See https://github.com/pydantic/pydantic/issues/5191 to hit this branch for test coverage + self._api.defer() + # `var` can also be a FuncDef or Decorator node (e.g. when overriding a field with a function or property). + # In that case, we don't want to do anything. Mypy will already raise an error that a field was not properly + # overridden. + else: + var = field.to_var(info, api, use_alias=False) + var.info = info + var.is_property = frozen + var._fullname = info.fullname + '.' + var.name + info.names[var.name] = SymbolTableNode(MDEF, var) + + def get_config_update(self, name: str, arg: Expression, lax_extra: bool = False) -> ModelConfigData | None: + """Determines the config update due to a single kwarg in the ConfigDict definition. + + Warns if a tracked config attribute is set to a value the plugin doesn't know how to interpret (e.g., an int) + """ + if name not in self.tracked_config_fields: + return None + if name == 'extra': + if isinstance(arg, StrExpr): + forbid_extra = arg.value == 'forbid' + elif isinstance(arg, MemberExpr): + forbid_extra = arg.name == 'forbid' + else: + if not lax_extra: + # Only emit an error for other types of `arg` (e.g., `NameExpr`, `ConditionalExpr`, etc.) when + # reading from a config class, etc. If a ConfigDict is used, then we don't want to emit an error + # because you'll get type checking from the ConfigDict itself. + # + # It would be nice if we could introspect the types better otherwise, but I don't know what the API + # is to evaluate an expr into its type and then check if that type is compatible with the expected + # type. Note that you can still get proper type checking via: `model_config = ConfigDict(...)`, just + # if you don't use an explicit string, the plugin won't be able to infer whether extra is forbidden. + error_invalid_config_value(name, self._api, arg) + return None + return ModelConfigData(forbid_extra=forbid_extra) + if name == 'alias_generator': + has_alias_generator = True + if isinstance(arg, NameExpr) and arg.fullname == 'builtins.None': + has_alias_generator = False + return ModelConfigData(has_alias_generator=has_alias_generator) + if isinstance(arg, NameExpr) and arg.fullname in ('builtins.True', 'builtins.False'): + return ModelConfigData(**{name: arg.fullname == 'builtins.True'}) + error_invalid_config_value(name, self._api, arg) + return None + + @staticmethod + def get_has_default(stmt: AssignmentStmt) -> bool: + """Returns a boolean indicating whether the field defined in `stmt` is a required field.""" + expr = stmt.rvalue + if isinstance(expr, TempNode): + # TempNode means annotation-only, so has no default + return False + if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME: + # The "default value" is a call to `Field`; at this point, the field has a default if and only if: + # * there is a positional argument that is not `...` + # * there is a keyword argument named "default" that is not `...` + # * there is a "default_factory" that is not `None` + for arg, name in zip(expr.args, expr.arg_names): + # If name is None, then this arg is the default because it is the only positional argument. + if name is None or name == 'default': + return arg.__class__ is not EllipsisExpr + if name == 'default_factory': + return not (isinstance(arg, NameExpr) and arg.fullname == 'builtins.None') + return False + # Has no default if the "default value" is Ellipsis (i.e., `field_name: Annotation = ...`) + return not isinstance(expr, EllipsisExpr) + + @staticmethod + def get_strict(stmt: AssignmentStmt) -> bool | None: + """Returns a the `strict` value of a field if defined, otherwise `None`.""" + expr = stmt.rvalue + if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME: + for arg, name in zip(expr.args, expr.arg_names): + if name != 'strict': + continue + if isinstance(arg, NameExpr): + if arg.fullname == 'builtins.True': + return True + elif arg.fullname == 'builtins.False': + return False + return None + return None + + @staticmethod + def get_alias_info(stmt: AssignmentStmt) -> tuple[str | None, bool]: + """Returns a pair (alias, has_dynamic_alias), extracted from the declaration of the field defined in `stmt`. + + `has_dynamic_alias` is True if and only if an alias is provided, but not as a string literal. + If `has_dynamic_alias` is True, `alias` will be None. + """ + expr = stmt.rvalue + if isinstance(expr, TempNode): + # TempNode means annotation-only + return None, False + + if not ( + isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME + ): + # Assigned value is not a call to pydantic.fields.Field + return None, False + + if 'validation_alias' in expr.arg_names: + arg = expr.args[expr.arg_names.index('validation_alias')] + elif 'alias' in expr.arg_names: + arg = expr.args[expr.arg_names.index('alias')] + else: + return None, False + + if isinstance(arg, StrExpr): + return arg.value, False + else: + return None, True + + @staticmethod + def is_field_frozen(stmt: AssignmentStmt) -> bool: + """Returns whether the field is frozen, extracted from the declaration of the field defined in `stmt`. + + Note that this is only whether the field was declared to be frozen in a ` = Field(frozen=True)` + sense; this does not determine whether the field is frozen because the entire model is frozen; that is + handled separately. + """ + expr = stmt.rvalue + if isinstance(expr, TempNode): + # TempNode means annotation-only + return False + + if not ( + isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME + ): + # Assigned value is not a call to pydantic.fields.Field + return False + + for i, arg_name in enumerate(expr.arg_names): + if arg_name == 'frozen': + arg = expr.args[i] + return isinstance(arg, NameExpr) and arg.fullname == 'builtins.True' + return False + + def get_field_arguments( + self, + fields: list[PydanticModelField], + typed: bool, + model_strict: bool, + use_alias: bool, + requires_dynamic_aliases: bool, + is_settings: bool, + is_root_model: bool, + force_typevars_invariant: bool = False, + ) -> list[Argument]: + """Helper function used during the construction of the `__init__` and `model_construct` method signatures. + + Returns a list of mypy Argument instances for use in the generated signatures. + """ + info = self._cls.info + arguments = [ + field.to_argument( + info, + typed=typed, + model_strict=model_strict, + force_optional=requires_dynamic_aliases or is_settings, + use_alias=use_alias, + api=self._api, + force_typevars_invariant=force_typevars_invariant, + is_root_model_root=is_root_model and field.name == 'root', + ) + for field in fields + if not (use_alias and field.has_dynamic_alias) + ] + return arguments + + def should_init_forbid_extra(self, fields: list[PydanticModelField], config: ModelConfigData) -> bool: + """Indicates whether the generated `__init__` should get a `**kwargs` at the end of its signature. + + We disallow arbitrary kwargs if the extra config setting is "forbid", or if the plugin config says to, + *unless* a required dynamic alias is present (since then we can't determine a valid signature). + """ + if not (config.validate_by_name or config.populate_by_name): + if self.is_dynamic_alias_present(fields, bool(config.has_alias_generator)): + return False + if config.forbid_extra: + return True + return self.plugin_config.init_forbid_extra + + @staticmethod + def is_dynamic_alias_present(fields: list[PydanticModelField], has_alias_generator: bool) -> bool: + """Returns whether any fields on the model have a "dynamic alias", i.e., an alias that cannot be + determined during static analysis. + """ + for field in fields: + if field.has_dynamic_alias: + return True + if has_alias_generator: + for field in fields: + if field.alias is None: + return True + return False + + +class ChangeExplicitTypeOfAny(TypeTranslator): + """A type translator used to change type of Any's, if explicit.""" + + def __init__(self, type_of_any: int) -> None: + self._type_of_any = type_of_any + super().__init__() + + def visit_any(self, t: AnyType) -> Type: # noqa: D102 + if t.type_of_any == TypeOfAny.explicit: + return t.copy_modified(type_of_any=self._type_of_any) + else: + return t + + +class ModelConfigData: + """Pydantic mypy plugin model config class.""" + + def __init__( + self, + forbid_extra: bool | None = None, + frozen: bool | None = None, + from_attributes: bool | None = None, + populate_by_name: bool | None = None, + validate_by_alias: bool | None = None, + validate_by_name: bool | None = None, + has_alias_generator: bool | None = None, + strict: bool | None = None, + ): + self.forbid_extra = forbid_extra + self.frozen = frozen + self.from_attributes = from_attributes + self.populate_by_name = populate_by_name + self.validate_by_alias = validate_by_alias + self.validate_by_name = validate_by_name + self.has_alias_generator = has_alias_generator + self.strict = strict + + def get_values_dict(self) -> dict[str, Any]: + """Returns a dict of Pydantic model config names to their values. + + It includes the config if config value is not `None`. + """ + return {k: v for k, v in self.__dict__.items() if v is not None} + + def update(self, config: ModelConfigData | None) -> None: + """Update Pydantic model config values.""" + if config is None: + return + for k, v in config.get_values_dict().items(): + setattr(self, k, v) + + def setdefault(self, key: str, value: Any) -> None: + """Set default value for Pydantic model config if config value is `None`.""" + if getattr(self, key) is None: + setattr(self, key, value) + + +def is_root_model(info: TypeInfo) -> bool: + """Return whether the type info is a root model subclass (or the `RootModel` class itself).""" + return info.has_base(ROOT_MODEL_FULLNAME) + + +ERROR_ORM = ErrorCode('pydantic-orm', 'Invalid from_attributes call', 'Pydantic') +ERROR_CONFIG = ErrorCode('pydantic-config', 'Invalid config value', 'Pydantic') +ERROR_ALIAS = ErrorCode('pydantic-alias', 'Dynamic alias disallowed', 'Pydantic') +ERROR_UNEXPECTED = ErrorCode('pydantic-unexpected', 'Unexpected behavior', 'Pydantic') +ERROR_UNTYPED = ErrorCode('pydantic-field', 'Untyped field disallowed', 'Pydantic') +ERROR_FIELD_DEFAULTS = ErrorCode('pydantic-field', 'Invalid Field defaults', 'Pydantic') +ERROR_EXTRA_FIELD_ROOT_MODEL = ErrorCode('pydantic-field', 'Extra field on RootModel subclass', 'Pydantic') + + +def error_from_attributes(model_name: str, api: CheckerPluginInterface, context: Context) -> None: + """Emits an error when the model does not have `from_attributes=True`.""" + api.fail(f'"{model_name}" does not have from_attributes=True', context, code=ERROR_ORM) + + +def error_invalid_config_value(name: str, api: SemanticAnalyzerPluginInterface, context: Context) -> None: + """Emits an error when the config value is invalid.""" + api.fail(f'Invalid value for "Config.{name}"', context, code=ERROR_CONFIG) + + +def error_required_dynamic_aliases(api: SemanticAnalyzerPluginInterface, context: Context) -> None: + """Emits required dynamic aliases error. + + This will be called when `warn_required_dynamic_aliases=True`. + """ + api.fail('Required dynamic aliases disallowed', context, code=ERROR_ALIAS) + + +def error_unexpected_behavior( + detail: str, api: CheckerPluginInterface | SemanticAnalyzerPluginInterface, context: Context +) -> None: # pragma: no cover + """Emits unexpected behavior error.""" + # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path + link = 'https://github.com/pydantic/pydantic/issues/new/choose' + full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n' + full_message += f'Please consider reporting this bug at {link} so we can try to fix it!' + api.fail(full_message, context, code=ERROR_UNEXPECTED) + + +def error_untyped_fields(api: SemanticAnalyzerPluginInterface, context: Context) -> None: + """Emits an error when there is an untyped field in the model.""" + api.fail('Untyped fields disallowed', context, code=ERROR_UNTYPED) + + +def error_extra_fields_on_root_model(api: CheckerPluginInterface, context: Context) -> None: + """Emits an error when there is more than just a root field defined for a subclass of RootModel.""" + api.fail('Only `root` is allowed as a field of a `RootModel`', context, code=ERROR_EXTRA_FIELD_ROOT_MODEL) + + +def add_method( + api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, + cls: ClassDef, + name: str, + args: list[Argument], + return_type: Type, + self_type: Type | None = None, + tvar_def: TypeVarType | None = None, + is_classmethod: bool = False, +) -> None: + """Very closely related to `mypy.plugins.common.add_method_to_class`, with a few pydantic-specific changes.""" + info = cls.info + + # First remove any previously generated methods with the same name + # to avoid clashes and problems in the semantic analyzer. + if name in info.names: + sym = info.names[name] + if sym.plugin_generated and isinstance(sym.node, FuncDef): + cls.defs.body.remove(sym.node) # pragma: no cover + + if isinstance(api, SemanticAnalyzerPluginInterface): + function_type = api.named_type('builtins.function') + else: + function_type = api.named_generic_type('builtins.function', []) + + if is_classmethod: + self_type = self_type or TypeType(fill_typevars(info)) + first = [Argument(Var('_cls'), self_type, None, ARG_POS, True)] + else: + self_type = self_type or fill_typevars(info) + # `self` is positional *ONLY* here, but this can't be expressed + # fully in the mypy internal API. ARG_POS is the closest we can get. + # Using ARG_POS will, however, give mypy errors if a `self` field + # is present on a model: + # + # Name "self" already defined (possibly by an import) [no-redef] + # + # As a workaround, we give this argument a name that will + # never conflict. By its positional nature, this name will not + # be used or exposed to users. + first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)] + args = first + args + + arg_types, arg_names, arg_kinds = [], [], [] + for arg in args: + assert arg.type_annotation, 'All arguments must be fully typed.' + arg_types.append(arg.type_annotation) + arg_names.append(arg.variable.name) + arg_kinds.append(arg.kind) + + signature = CallableType( + arg_types, arg_kinds, arg_names, return_type, function_type, variables=[tvar_def] if tvar_def else None + ) + + func = FuncDef(name, args, Block([PassStmt()])) + func.info = info + func.type = set_callable_name(signature, func) + func.is_class = is_classmethod + func._fullname = info.fullname + '.' + name + func.line = info.line + + # NOTE: we would like the plugin generated node to dominate, but we still + # need to keep any existing definitions so they get semantically analyzed. + if name in info.names: + # Get a nice unique name instead. + r_name = get_unique_redefinition_name(name, info.names) + info.names[r_name] = info.names[name] + + # Add decorator for is_classmethod + # The dataclasses plugin claims this is unnecessary for classmethods, but not including it results in a + # signature incompatible with the superclass, which causes mypy errors to occur for every subclass of BaseModel. + if is_classmethod: + func.is_decorated = True + v = Var(name, func.type) + v.info = info + v._fullname = func._fullname + v.is_classmethod = True + dec = Decorator(func, [NameExpr('classmethod')], v) + dec.line = info.line + sym = SymbolTableNode(MDEF, dec) + else: + sym = SymbolTableNode(MDEF, func) + sym.plugin_generated = True + info.names[name] = sym + + info.defn.defs.body.append(func) + + +def parse_toml(config_file: str) -> dict[str, Any] | None: + """Returns a dict of config keys to values. + + It reads configs from toml file and returns `None` if the file is not a toml file. + """ + if not config_file.endswith('.toml'): + return None + + if sys.version_info >= (3, 11): + import tomllib as toml_ + else: + try: + import tomli as toml_ + except ImportError: # pragma: no cover + import warnings + + warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.', stacklevel=2) + return None + + with open(config_file, 'rb') as rf: + return toml_.load(rf) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py new file mode 100644 index 0000000..4015bad --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/networks.py @@ -0,0 +1,1332 @@ +"""The networks module contains types for common network-related fields.""" + +from __future__ import annotations as _annotations + +import dataclasses as _dataclasses +import re +from dataclasses import fields +from functools import lru_cache +from importlib.metadata import version +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from typing import TYPE_CHECKING, Annotated, Any, ClassVar + +from pydantic_core import ( + MultiHostHost, + PydanticCustomError, + PydanticSerializationUnexpectedValue, + SchemaSerializer, + core_schema, +) +from pydantic_core import MultiHostUrl as _CoreMultiHostUrl +from pydantic_core import Url as _CoreUrl +from typing_extensions import Self, TypeAlias + +from pydantic.errors import PydanticUserError + +from ._internal import _repr, _schema_generation_shared +from ._migration import getattr_migration +from .annotated_handlers import GetCoreSchemaHandler +from .json_schema import JsonSchemaValue +from .type_adapter import TypeAdapter + +if TYPE_CHECKING: + import email_validator + + NetworkType: TypeAlias = 'str | bytes | int | tuple[str | bytes | int, str | int]' + +else: + email_validator = None + + +__all__ = [ + 'AnyUrl', + 'AnyHttpUrl', + 'FileUrl', + 'FtpUrl', + 'HttpUrl', + 'WebsocketUrl', + 'AnyWebsocketUrl', + 'UrlConstraints', + 'EmailStr', + 'NameEmail', + 'IPvAnyAddress', + 'IPvAnyInterface', + 'IPvAnyNetwork', + 'PostgresDsn', + 'CockroachDsn', + 'AmqpDsn', + 'RedisDsn', + 'MongoDsn', + 'KafkaDsn', + 'NatsDsn', + 'validate_email', + 'MySQLDsn', + 'MariaDBDsn', + 'ClickHouseDsn', + 'SnowflakeDsn', +] + + +@_dataclasses.dataclass +class UrlConstraints: + """Url constraints. + + Attributes: + max_length: The maximum length of the url. Defaults to `None`. + allowed_schemes: The allowed schemes. Defaults to `None`. + host_required: Whether the host is required. Defaults to `None`. + default_host: The default host. Defaults to `None`. + default_port: The default port. Defaults to `None`. + default_path: The default path. Defaults to `None`. + preserve_empty_path: Whether to preserve empty URL paths. Defaults to `None`. + """ + + max_length: int | None = None + allowed_schemes: list[str] | None = None + host_required: bool | None = None + default_host: str | None = None + default_port: int | None = None + default_path: str | None = None + preserve_empty_path: bool | None = None + + def __hash__(self) -> int: + return hash( + ( + self.max_length, + tuple(self.allowed_schemes) if self.allowed_schemes is not None else None, + self.host_required, + self.default_host, + self.default_port, + self.default_path, + self.preserve_empty_path, + ) + ) + + @property + def defined_constraints(self) -> dict[str, Any]: + """Fetch a key / value mapping of constraints to values that are not None. Used for core schema updates.""" + return {field.name: value for field in fields(self) if (value := getattr(self, field.name)) is not None} + + def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source) + + # for function-wrap schemas, url constraints is applied to the inner schema + # because when we generate schemas for urls, we wrap a core_schema.url_schema() with a function-wrap schema + # that helps with validation on initialization, see _BaseUrl and _BaseMultiHostUrl below. + schema_to_mutate = schema['schema'] if schema['type'] == 'function-wrap' else schema + if (annotated_type := schema_to_mutate['type']) not in ('url', 'multi-host-url'): + raise PydanticUserError( + f"'UrlConstraints' cannot annotate '{annotated_type}'.", code='invalid-annotated-type' + ) + for constraint_key, constraint_value in self.defined_constraints.items(): + schema_to_mutate[constraint_key] = constraint_value + return schema + + +class _BaseUrl: + _constraints: ClassVar[UrlConstraints] = UrlConstraints() + _url: _CoreUrl + + def __init__(self, url: str | _CoreUrl | _BaseUrl) -> None: + self._url = _build_type_adapter(self.__class__).validate_python(url)._url + + @property + def scheme(self) -> str: + """The scheme part of the URL. + + e.g. `https` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.scheme + + @property + def username(self) -> str | None: + """The username part of the URL, or `None`. + + e.g. `user` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.username + + @property + def password(self) -> str | None: + """The password part of the URL, or `None`. + + e.g. `pass` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.password + + @property + def host(self) -> str | None: + """The host part of the URL, or `None`. + + If the URL must be punycode encoded, this is the encoded host, e.g if the input URL is `https://£££.com`, + `host` will be `xn--9aaa.com` + """ + return self._url.host + + def unicode_host(self) -> str | None: + """The host part of the URL as a unicode string, or `None`. + + e.g. `host` in `https://user:pass@host:port/path?query#fragment` + + If the URL must be punycode encoded, this is the decoded host, e.g if the input URL is `https://£££.com`, + `unicode_host()` will be `£££.com` + """ + return self._url.unicode_host() + + @property + def port(self) -> int | None: + """The port part of the URL, or `None`. + + e.g. `port` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.port + + @property + def path(self) -> str | None: + """The path part of the URL, or `None`. + + e.g. `/path` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.path + + @property + def query(self) -> str | None: + """The query part of the URL, or `None`. + + e.g. `query` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.query + + def query_params(self) -> list[tuple[str, str]]: + """The query part of the URL as a list of key-value pairs. + + e.g. `[('foo', 'bar')]` in `https://user:pass@host:port/path?foo=bar#fragment` + """ + return self._url.query_params() + + @property + def fragment(self) -> str | None: + """The fragment part of the URL, or `None`. + + e.g. `fragment` in `https://user:pass@host:port/path?query#fragment` + """ + return self._url.fragment + + def unicode_string(self) -> str: + """The URL as a unicode string, unlike `__str__()` this will not punycode encode the host. + + If the URL must be punycode encoded, this is the decoded string, e.g if the input URL is `https://£££.com`, + `unicode_string()` will be `https://£££.com` + """ + return self._url.unicode_string() + + def encoded_string(self) -> str: + """The URL's encoded string representation via __str__(). + + This returns the punycode-encoded host version of the URL as a string. + """ + return str(self) + + def __str__(self) -> str: + """The URL as a string, this will punycode encode the host if required.""" + return str(self._url) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({str(self._url)!r})' + + def __deepcopy__(self, memo: dict) -> Self: + return self.__class__(self._url) + + def __eq__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url == other._url + + def __lt__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url < other._url + + def __gt__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url > other._url + + def __le__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url <= other._url + + def __ge__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url >= other._url + + def __hash__(self) -> int: + return hash(self._url) + + def __len__(self) -> int: + return len(str(self._url)) + + @classmethod + def build( + cls, + *, + scheme: str, + username: str | None = None, + password: str | None = None, + host: str, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ) -> Self: + """Build a new `Url` instance from its component parts. + + Args: + scheme: The scheme part of the URL. + username: The username part of the URL, or omit for no username. + password: The password part of the URL, or omit for no password. + host: The host part of the URL. + port: The port part of the URL, or omit for no port. + path: The path part of the URL, or omit for no path. + query: The query part of the URL, or omit for no query. + fragment: The fragment part of the URL, or omit for no fragment. + + Returns: + An instance of URL + """ + return cls( + _CoreUrl.build( + scheme=scheme, + username=username, + password=password, + host=host, + port=port, + path=path, + query=query, + fragment=fragment, + ) + ) + + @classmethod + def serialize_url(cls, url: Any, info: core_schema.SerializationInfo) -> str | Self: + if not isinstance(url, cls): + raise PydanticSerializationUnexpectedValue( + f"Expected `{cls}` but got `{type(url)}` with value `'{url}'` - serialized value may not be as expected." + ) + if info.mode == 'json': + return str(url) + return url + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[_BaseUrl], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + def wrap_val(v, h): + if isinstance(v, source): + return v + if isinstance(v, _BaseUrl): + v = str(v) + core_url = h(v) + instance = source.__new__(source) + instance._url = core_url + return instance + + return core_schema.no_info_wrap_validator_function( + wrap_val, + schema=core_schema.url_schema(**cls._constraints.defined_constraints), + serialization=core_schema.plain_serializer_function_ser_schema( + cls.serialize_url, info_arg=True, when_used='always' + ), + ) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + # we use the url schema for json schema generation, but we might have to extract it from + # the function-wrap schema we use as a tool for validation on initialization + inner_schema = core_schema['schema'] if core_schema['type'] == 'function-wrap' else core_schema + return handler(inner_schema) + + __pydantic_serializer__ = SchemaSerializer(core_schema.any_schema(serialization=core_schema.to_string_ser_schema())) + + +class _BaseMultiHostUrl: + _constraints: ClassVar[UrlConstraints] = UrlConstraints() + _url: _CoreMultiHostUrl + + def __init__(self, url: str | _CoreMultiHostUrl | _BaseMultiHostUrl) -> None: + self._url = _build_type_adapter(self.__class__).validate_python(url)._url + + @property + def scheme(self) -> str: + """The scheme part of the URL. + + e.g. `https` in `https://foo.com,bar.com/path?query#fragment` + """ + return self._url.scheme + + @property + def path(self) -> str | None: + """The path part of the URL, or `None`. + + e.g. `/path` in `https://foo.com,bar.com/path?query#fragment` + """ + return self._url.path + + @property + def query(self) -> str | None: + """The query part of the URL, or `None`. + + e.g. `query` in `https://foo.com,bar.com/path?query#fragment` + """ + return self._url.query + + def query_params(self) -> list[tuple[str, str]]: + """The query part of the URL as a list of key-value pairs. + + e.g. `[('foo', 'bar')]` in `https://foo.com,bar.com/path?foo=bar#fragment` + """ + return self._url.query_params() + + @property + def fragment(self) -> str | None: + """The fragment part of the URL, or `None`. + + e.g. `fragment` in `https://foo.com,bar.com/path?query#fragment` + """ + return self._url.fragment + + def hosts(self) -> list[MultiHostHost]: + '''The hosts of the `MultiHostUrl` as [`MultiHostHost`][pydantic_core.MultiHostHost] typed dicts. + + ```python + from pydantic_core import MultiHostUrl + + mhu = MultiHostUrl('https://foo.com:123,foo:bar@bar.com/path') + print(mhu.hosts()) + """ + [ + {'username': None, 'password': None, 'host': 'foo.com', 'port': 123}, + {'username': 'foo', 'password': 'bar', 'host': 'bar.com', 'port': 443} + ] + ``` + Returns: + A list of dicts, each representing a host. + ''' + return self._url.hosts() + + def encoded_string(self) -> str: + """The URL's encoded string representation via __str__(). + + This returns the punycode-encoded host version of the URL as a string. + """ + return str(self) + + def unicode_string(self) -> str: + """The URL as a unicode string, unlike `__str__()` this will not punycode encode the hosts.""" + return self._url.unicode_string() + + def __str__(self) -> str: + """The URL as a string, this will punycode encode the host if required.""" + return str(self._url) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({str(self._url)!r})' + + def __deepcopy__(self, memo: dict) -> Self: + return self.__class__(self._url) + + def __eq__(self, other: Any) -> bool: + return self.__class__ is other.__class__ and self._url == other._url + + def __hash__(self) -> int: + return hash(self._url) + + def __len__(self) -> int: + return len(str(self._url)) + + @classmethod + def build( + cls, + *, + scheme: str, + hosts: list[MultiHostHost] | None = None, + username: str | None = None, + password: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ) -> Self: + """Build a new `MultiHostUrl` instance from its component parts. + + This method takes either `hosts` - a list of `MultiHostHost` typed dicts, or the individual components + `username`, `password`, `host` and `port`. + + Args: + scheme: The scheme part of the URL. + hosts: Multiple hosts to build the URL from. + username: The username part of the URL. + password: The password part of the URL. + host: The host part of the URL. + port: The port part of the URL. + path: The path part of the URL. + query: The query part of the URL, or omit for no query. + fragment: The fragment part of the URL, or omit for no fragment. + + Returns: + An instance of `MultiHostUrl` + """ + return cls( + _CoreMultiHostUrl.build( + scheme=scheme, + hosts=hosts, + username=username, + password=password, + host=host, + port=port, + path=path, + query=query, + fragment=fragment, + ) + ) + + @classmethod + def serialize_url(cls, url: Any, info: core_schema.SerializationInfo) -> str | Self: + if not isinstance(url, cls): + raise PydanticSerializationUnexpectedValue( + f"Expected `{cls}` but got `{type(url)}` with value `'{url}'` - serialized value may not be as expected." + ) + if info.mode == 'json': + return str(url) + return url + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[_BaseMultiHostUrl], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + def wrap_val(v, h): + if isinstance(v, source): + return v + if isinstance(v, _BaseMultiHostUrl): + v = str(v) + core_url = h(v) + instance = source.__new__(source) + instance._url = core_url + return instance + + return core_schema.no_info_wrap_validator_function( + wrap_val, + schema=core_schema.multi_host_url_schema(**cls._constraints.defined_constraints), + serialization=core_schema.plain_serializer_function_ser_schema( + cls.serialize_url, info_arg=True, when_used='always' + ), + ) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + # we use the url schema for json schema generation, but we might have to extract it from + # the function-wrap schema we use as a tool for validation on initialization + inner_schema = core_schema['schema'] if core_schema['type'] == 'function-wrap' else core_schema + return handler(inner_schema) + + __pydantic_serializer__ = SchemaSerializer(core_schema.any_schema(serialization=core_schema.to_string_ser_schema())) + + +@lru_cache +def _build_type_adapter(cls: type[_BaseUrl | _BaseMultiHostUrl]) -> TypeAdapter: + return TypeAdapter(cls) + + +class AnyUrl(_BaseUrl): + """Base type for all URLs. + + * Any scheme allowed + * Top-level domain (TLD) not required + * Host not required + + Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`, + the types export the following properties: + + - `scheme`: the URL scheme (`http`), always set. + - `host`: the URL host (`example.com`). + - `username`: optional username if included (`samuel`). + - `password`: optional password if included (`pass`). + - `port`: optional port (`8000`). + - `path`: optional path (`/the/path/`). + - `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`). + - `fragment`: optional fragment (`fragment=is;this=bit`). + """ + + +# Note: all single host urls inherit from `AnyUrl` to preserve compatibility with pre-v2.10 code +# Where urls were annotated variants of `AnyUrl`, which was an alias to `pydantic_core.Url` + + +class AnyHttpUrl(AnyUrl): + """A type that will accept any http or https URL. + + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['http', 'https']) + + +class HttpUrl(AnyUrl): + """A type that will accept any http or https URL. + + * TLD not required + * Host not required + * Max length 2083 + + ```python + from pydantic import BaseModel, HttpUrl, ValidationError + + class MyModel(BaseModel): + url: HttpUrl + + m = MyModel(url='http://www.example.com') # (1)! + print(m.url) + #> http://www.example.com/ + + try: + MyModel(url='ftp://invalid.url') + except ValidationError as e: + print(e) + ''' + 1 validation error for MyModel + url + URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str] + ''' + + try: + MyModel(url='not a url') + except ValidationError as e: + print(e) + ''' + 1 validation error for MyModel + url + Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str] + ''' + ``` + + 1. Note: mypy would prefer `m = MyModel(url=HttpUrl('http://www.example.com'))`, but Pydantic will convert the string to an HttpUrl instance anyway. + + "International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via + [punycode](https://en.wikipedia.org/wiki/Punycode) (see + [this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important): + + ```python + from pydantic import BaseModel, HttpUrl + + class MyModel(BaseModel): + url: HttpUrl + + m1 = MyModel(url='http://puny£code.com') + print(m1.url) + #> http://xn--punycode-eja.com/ + m2 = MyModel(url='https://www.аррӏе.com/') + print(m2.url) + #> https://www.xn--80ak6aa92e.com/ + m3 = MyModel(url='https://www.example.珠宝/') + print(m3.url) + #> https://www.example.xn--pbt977c/ + ``` + + + !!! warning "Underscores in Hostnames" + In Pydantic, underscores are allowed in all parts of a domain except the TLD. + Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can. + + To explain this; consider the following two cases: + + - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore. + - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain. + + Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore + underscores are allowed, but you can always do further validation in a validator if desired. + + Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good + (or at least big) company. + """ + + _constraints = UrlConstraints(max_length=2083, allowed_schemes=['http', 'https']) + + +class AnyWebsocketUrl(AnyUrl): + """A type that will accept any ws or wss URL. + + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['ws', 'wss']) + + +class WebsocketUrl(AnyUrl): + """A type that will accept any ws or wss URL. + + * TLD not required + * Host not required + * Max length 2083 + """ + + _constraints = UrlConstraints(max_length=2083, allowed_schemes=['ws', 'wss']) + + +class FileUrl(AnyUrl): + """A type that will accept any file URL. + + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['file']) + + +class FtpUrl(AnyUrl): + """A type that will accept ftp URL. + + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['ftp']) + + +class PostgresDsn(_BaseMultiHostUrl): + """A type that will accept any Postgres DSN. + + * User info required + * TLD not required + * Host required + * Supports multiple hosts + + If further validation is required, these properties can be used by validators to enforce specific behaviour: + + ```python + from pydantic import ( + BaseModel, + HttpUrl, + PostgresDsn, + ValidationError, + field_validator, + ) + + class MyModel(BaseModel): + url: HttpUrl + + m = MyModel(url='http://www.example.com') + + # the repr() method for a url will display all properties of the url + print(repr(m.url)) + #> HttpUrl('http://www.example.com/') + print(m.url.scheme) + #> http + print(m.url.host) + #> www.example.com + print(m.url.port) + #> 80 + + class MyDatabaseModel(BaseModel): + db: PostgresDsn + + @field_validator('db') + def check_db_name(cls, v): + assert v.path and len(v.path) > 1, 'database must be provided' + return v + + m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar') + print(m.db) + #> postgres://user:pass@localhost:5432/foobar + + try: + MyDatabaseModel(db='postgres://user:pass@localhost:5432') + except ValidationError as e: + print(e) + ''' + 1 validation error for MyDatabaseModel + db + Assertion failed, database must be provided + assert (None) + + where None = PostgresDsn('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str] + ''' + ``` + """ + + _constraints = UrlConstraints( + host_required=True, + allowed_schemes=[ + 'postgres', + 'postgresql', + 'postgresql+asyncpg', + 'postgresql+pg8000', + 'postgresql+psycopg', + 'postgresql+psycopg2', + 'postgresql+psycopg2cffi', + 'postgresql+py-postgresql', + 'postgresql+pygresql', + ], + ) + + @property + def host(self) -> str: + """The required URL host.""" + return self._url.host # pyright: ignore[reportAttributeAccessIssue] + + +class CockroachDsn(AnyUrl): + """A type that will accept any Cockroach DSN. + + * User info required + * TLD not required + * Host required + """ + + _constraints = UrlConstraints( + host_required=True, + allowed_schemes=[ + 'cockroachdb', + 'cockroachdb+psycopg2', + 'cockroachdb+asyncpg', + ], + ) + + @property + def host(self) -> str: + """The required URL host.""" + return self._url.host # pyright: ignore[reportReturnType] + + +class AmqpDsn(AnyUrl): + """A type that will accept any AMQP DSN. + + * User info required + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['amqp', 'amqps']) + + +class RedisDsn(AnyUrl): + """A type that will accept any Redis DSN. + + * User info required + * TLD not required + * Host required (e.g., `rediss://:pass@localhost`) + """ + + _constraints = UrlConstraints( + allowed_schemes=['redis', 'rediss'], + default_host='localhost', + default_port=6379, + default_path='/0', + host_required=True, + ) + + @property + def host(self) -> str: + """The required URL host.""" + return self._url.host # pyright: ignore[reportReturnType] + + +class MongoDsn(_BaseMultiHostUrl): + """A type that will accept any MongoDB DSN. + + * User info not required + * Database name not required + * Port not required + * User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`). + + !!! warning + If a port isn't specified, the default MongoDB port `27017` will be used. If this behavior is + undesirable, you can use the following: + + ```python + from typing import Annotated + + from pydantic_core import MultiHostUrl + + from pydantic import UrlConstraints + + MongoDsnNoDefaultPort = Annotated[ + MultiHostUrl, + UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv']), + ] + ``` + """ + + _constraints = UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017) + + +class KafkaDsn(AnyUrl): + """A type that will accept any Kafka DSN. + + * User info required + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints(allowed_schemes=['kafka'], default_host='localhost', default_port=9092) + + +class NatsDsn(_BaseMultiHostUrl): + """A type that will accept any NATS DSN. + + NATS is a connective technology built for the ever increasingly hyper-connected world. + It is a single technology that enables applications to securely communicate across + any combination of cloud vendors, on-premise, edge, web and mobile, and devices. + More: https://nats.io + """ + + _constraints = UrlConstraints( + allowed_schemes=['nats', 'tls', 'ws', 'wss'], default_host='localhost', default_port=4222 + ) + + +class MySQLDsn(AnyUrl): + """A type that will accept any MySQL DSN. + + * User info required + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints( + allowed_schemes=[ + 'mysql', + 'mysql+mysqlconnector', + 'mysql+aiomysql', + 'mysql+asyncmy', + 'mysql+mysqldb', + 'mysql+pymysql', + 'mysql+cymysql', + 'mysql+pyodbc', + ], + default_port=3306, + host_required=True, + ) + + +class MariaDBDsn(AnyUrl): + """A type that will accept any MariaDB DSN. + + * User info required + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints( + allowed_schemes=['mariadb', 'mariadb+mariadbconnector', 'mariadb+pymysql'], + default_port=3306, + ) + + +class ClickHouseDsn(AnyUrl): + """A type that will accept any ClickHouse DSN. + + * User info required + * TLD not required + * Host not required + """ + + _constraints = UrlConstraints( + allowed_schemes=[ + 'clickhouse+native', + 'clickhouse+asynch', + 'clickhouse+http', + 'clickhouse', + 'clickhouses', + 'clickhousedb', + ], + default_host='localhost', + default_port=9000, + ) + + +class SnowflakeDsn(AnyUrl): + """A type that will accept any Snowflake DSN. + + * User info required + * TLD not required + * Host required + """ + + _constraints = UrlConstraints( + allowed_schemes=['snowflake'], + host_required=True, + ) + + @property + def host(self) -> str: + """The required URL host.""" + return self._url.host # pyright: ignore[reportReturnType] + + +def import_email_validator() -> None: + global email_validator + try: + import email_validator + except ImportError as e: + raise ImportError("email-validator is not installed, run `pip install 'pydantic[email]'`") from e + if not version('email-validator').partition('.')[0] == '2': + raise ImportError('email-validator version >= 2.0 required, run pip install -U email-validator') + + +if TYPE_CHECKING: + EmailStr = Annotated[str, ...] +else: + + class EmailStr: + """ + Info: + To use this type, you need to install the optional + [`email-validator`](https://github.com/JoshData/python-email-validator) package: + + ```bash + pip install email-validator + ``` + + Validate email addresses. + + ```python + from pydantic import BaseModel, EmailStr + + class Model(BaseModel): + email: EmailStr + + print(Model(email='contact@mail.com')) + #> email='contact@mail.com' + ``` + """ # noqa: D212 + + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + import_email_validator() + return core_schema.no_info_after_validator_function(cls._validate, core_schema.str_schema()) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + field_schema.update(type='string', format='email') + return field_schema + + @classmethod + def _validate(cls, input_value: str, /) -> str: + return validate_email(input_value)[1] + + +class NameEmail(_repr.Representation): + """ + Info: + To use this type, you need to install the optional + [`email-validator`](https://github.com/JoshData/python-email-validator) package: + + ```bash + pip install email-validator + ``` + + Validate a name and email address combination, as specified by + [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4). + + The `NameEmail` has two properties: `name` and `email`. + In case the `name` is not provided, it's inferred from the email address. + + ```python + from pydantic import BaseModel, NameEmail + + class User(BaseModel): + email: NameEmail + + user = User(email='Fred Bloggs ') + print(user.email) + #> Fred Bloggs + print(user.email.name) + #> Fred Bloggs + + user = User(email='fred.bloggs@example.com') + print(user.email) + #> fred.bloggs + print(user.email.name) + #> fred.bloggs + ``` + """ # noqa: D212 + + __slots__ = 'name', 'email' + + def __init__(self, name: str, email: str): + self.name = name + self.email = email + + def __eq__(self, other: Any) -> bool: + return isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + field_schema.update(type='string', format='name-email') + return field_schema + + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + import_email_validator() + + return core_schema.no_info_after_validator_function( + cls._validate, + core_schema.json_or_python_schema( + json_schema=core_schema.str_schema(), + python_schema=core_schema.union_schema( + [core_schema.is_instance_schema(cls), core_schema.str_schema()], + custom_error_type='name_email_type', + custom_error_message='Input is not a valid NameEmail', + ), + serialization=core_schema.to_string_ser_schema(), + ), + ) + + @classmethod + def _validate(cls, input_value: Self | str, /) -> Self: + if isinstance(input_value, str): + name, email = validate_email(input_value) + return cls(name, email) + else: + return input_value + + def __str__(self) -> str: + if '@' in self.name: + return f'"{self.name}" <{self.email}>' + + return f'{self.name} <{self.email}>' + + +IPvAnyAddressType: TypeAlias = 'IPv4Address | IPv6Address' +IPvAnyInterfaceType: TypeAlias = 'IPv4Interface | IPv6Interface' +IPvAnyNetworkType: TypeAlias = 'IPv4Network | IPv6Network' + +if TYPE_CHECKING: + IPvAnyAddress = IPvAnyAddressType + IPvAnyInterface = IPvAnyInterfaceType + IPvAnyNetwork = IPvAnyNetworkType +else: + + class IPvAnyAddress: + """Validate an IPv4 or IPv6 address. + + ```python + from pydantic import BaseModel + from pydantic.networks import IPvAnyAddress + + class IpModel(BaseModel): + ip: IPvAnyAddress + + print(IpModel(ip='127.0.0.1')) + #> ip=IPv4Address('127.0.0.1') + + try: + IpModel(ip='http://www.example.com') + except ValueError as e: + print(e.errors()) + ''' + [ + { + 'type': 'ip_any_address', + 'loc': ('ip',), + 'msg': 'value is not a valid IPv4 or IPv6 address', + 'input': 'http://www.example.com', + } + ] + ''' + ``` + """ + + __slots__ = () + + def __new__(cls, value: Any) -> IPvAnyAddressType: + """Validate an IPv4 or IPv6 address.""" + try: + return IPv4Address(value) + except ValueError: + pass + + try: + return IPv6Address(value) + except ValueError: + raise PydanticCustomError('ip_any_address', 'value is not a valid IPv4 or IPv6 address') + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanyaddress') + return field_schema + + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) + + @classmethod + def _validate(cls, input_value: Any, /) -> IPvAnyAddressType: + return cls(input_value) # type: ignore[return-value] + + class IPvAnyInterface: + """Validate an IPv4 or IPv6 interface.""" + + __slots__ = () + + def __new__(cls, value: NetworkType) -> IPvAnyInterfaceType: + """Validate an IPv4 or IPv6 interface.""" + try: + return IPv4Interface(value) + except ValueError: + pass + + try: + return IPv6Interface(value) + except ValueError: + raise PydanticCustomError('ip_any_interface', 'value is not a valid IPv4 or IPv6 interface') + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanyinterface') + return field_schema + + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) + + @classmethod + def _validate(cls, input_value: NetworkType, /) -> IPvAnyInterfaceType: + return cls(input_value) # type: ignore[return-value] + + class IPvAnyNetwork: + """Validate an IPv4 or IPv6 network.""" + + __slots__ = () + + def __new__(cls, value: NetworkType) -> IPvAnyNetworkType: + """Validate an IPv4 or IPv6 network.""" + # Assume IP Network is defined with a default value for `strict` argument. + # Define your own class if you want to specify network address check strictness. + try: + return IPv4Network(value) + except ValueError: + pass + + try: + return IPv6Network(value) + except ValueError: + raise PydanticCustomError('ip_any_network', 'value is not a valid IPv4 or IPv6 network') + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanynetwork') + return field_schema + + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) + + @classmethod + def _validate(cls, input_value: NetworkType, /) -> IPvAnyNetworkType: + return cls(input_value) # type: ignore[return-value] + + +def _build_pretty_email_regex() -> re.Pattern[str]: + name_chars = r'[\w!#$%&\'*+\-/=?^_`{|}~]' + unquoted_name_group = rf'((?:{name_chars}+\s+)*{name_chars}+)' + quoted_name_group = r'"((?:[^"]|\")+)"' + email_group = r'<(.+)>' + return re.compile(rf'\s*(?:{unquoted_name_group}|{quoted_name_group})?\s*{email_group}\s*') + + +pretty_email_regex = _build_pretty_email_regex() + +MAX_EMAIL_LENGTH = 2048 +"""Maximum length for an email. +A somewhat arbitrary but very generous number compared to what is allowed by most implementations. +""" + + +def validate_email(value: str) -> tuple[str, str]: + """Email address validation using [email-validator](https://pypi.org/project/email-validator/). + + Returns: + A tuple containing the local part of the email (or the name for "pretty" email addresses) + and the normalized email. + + Raises: + PydanticCustomError: If the email is invalid. + + Note: + Note that: + + * Raw IP address (literal) domain parts are not allowed. + * `"John Doe "` style "pretty" email addresses are processed. + * Spaces are striped from the beginning and end of addresses, but no error is raised. + """ + if email_validator is None: + import_email_validator() + + if len(value) > MAX_EMAIL_LENGTH: + raise PydanticCustomError( + 'value_error', + 'value is not a valid email address: {reason}', + {'reason': f'Length must not exceed {MAX_EMAIL_LENGTH} characters'}, + ) + + m = pretty_email_regex.fullmatch(value) + name: str | None = None + if m: + unquoted_name, quoted_name, value = m.groups() + name = unquoted_name or quoted_name + + email = value.strip() + + try: + parts = email_validator.validate_email(email, check_deliverability=False) + except email_validator.EmailNotValidError as e: + raise PydanticCustomError( + 'value_error', 'value is not a valid email address: {reason}', {'reason': str(e.args[0])} + ) from e + + email = parts.normalized + assert email is not None + name = name or parts.local_part + return name, email + + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py new file mode 100644 index 0000000..68b7f04 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/parse.py @@ -0,0 +1,5 @@ +"""The `parse` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py new file mode 100644 index 0000000..840d20a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py @@ -0,0 +1,193 @@ +"""!!! abstract "Usage Documentation" + [Build a Plugin](../concepts/plugins.md#build-a-plugin) + +Plugin interface for Pydantic plugins, and related types. +""" + +from __future__ import annotations + +from typing import Any, Callable, Literal, NamedTuple + +from pydantic_core import CoreConfig, CoreSchema, ValidationError +from typing_extensions import Protocol, TypeAlias + +from pydantic.config import ExtraValues + +__all__ = ( + 'PydanticPluginProtocol', + 'BaseValidateHandlerProtocol', + 'ValidatePythonHandlerProtocol', + 'ValidateJsonHandlerProtocol', + 'ValidateStringsHandlerProtocol', + 'NewSchemaReturns', + 'SchemaTypePath', + 'SchemaKind', +) + +NewSchemaReturns: TypeAlias = 'tuple[ValidatePythonHandlerProtocol | None, ValidateJsonHandlerProtocol | None, ValidateStringsHandlerProtocol | None]' + + +class SchemaTypePath(NamedTuple): + """Path defining where `schema_type` was defined, or where `TypeAdapter` was called.""" + + module: str + name: str + + +SchemaKind: TypeAlias = Literal['BaseModel', 'TypeAdapter', 'dataclass', 'create_model', 'validate_call'] + + +class PydanticPluginProtocol(Protocol): + """Protocol defining the interface for Pydantic plugins.""" + + def new_schema_validator( + self, + schema: CoreSchema, + schema_type: Any, + schema_type_path: SchemaTypePath, + schema_kind: SchemaKind, + config: CoreConfig | None, + plugin_settings: dict[str, object], + ) -> tuple[ + ValidatePythonHandlerProtocol | None, ValidateJsonHandlerProtocol | None, ValidateStringsHandlerProtocol | None + ]: + """This method is called for each plugin every time a new [`SchemaValidator`][pydantic_core.SchemaValidator] + is created. + + It should return an event handler for each of the three validation methods, or `None` if the plugin does not + implement that method. + + Args: + schema: The schema to validate against. + schema_type: The original type which the schema was created from, e.g. the model class. + schema_type_path: Path defining where `schema_type` was defined, or where `TypeAdapter` was called. + schema_kind: The kind of schema to validate against. + config: The config to use for validation. + plugin_settings: Any plugin settings. + + Returns: + A tuple of optional event handlers for each of the three validation methods - + `validate_python`, `validate_json`, `validate_strings`. + """ + raise NotImplementedError('Pydantic plugins should implement `new_schema_validator`.') + + +class BaseValidateHandlerProtocol(Protocol): + """Base class for plugin callbacks protocols. + + You shouldn't implement this protocol directly, instead use one of the subclasses with adds the correctly + typed `on_error` method. + """ + + on_enter: Callable[..., None] + """`on_enter` is changed to be more specific on all subclasses""" + + def on_success(self, result: Any) -> None: + """Callback to be notified of successful validation. + + Args: + result: The result of the validation. + """ + return + + def on_error(self, error: ValidationError) -> None: + """Callback to be notified of validation errors. + + Args: + error: The validation error. + """ + return + + def on_exception(self, exception: Exception) -> None: + """Callback to be notified of validation exceptions. + + Args: + exception: The exception raised during validation. + """ + return + + +class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol): + """Event handler for `SchemaValidator.validate_python`.""" + + def on_enter( + self, + input: Any, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + self_instance: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> None: + """Callback to be notified of validation start, and create an instance of the event handler. + + Args: + input: The input to be validated. + strict: Whether to validate the object in strict mode. + extra: Whether to ignore, allow, or forbid extra data during model validation. + from_attributes: Whether to validate objects as inputs by extracting attributes. + context: The context to use for validation, this is passed to functional validators. + self_instance: An instance of a model to set attributes on from validation, this is used when running + validation from the `__init__` method of a model. + by_alias: Whether to use the field's alias to match the input data to an attribute. + by_name: Whether to use the field's name to match the input data to an attribute. + """ + + +class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol): + """Event handler for `SchemaValidator.validate_json`.""" + + def on_enter( + self, + input: str | bytes | bytearray, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + self_instance: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> None: + """Callback to be notified of validation start, and create an instance of the event handler. + + Args: + input: The JSON data to be validated. + strict: Whether to validate the object in strict mode. + extra: Whether to ignore, allow, or forbid extra data during model validation. + context: The context to use for validation, this is passed to functional validators. + self_instance: An instance of a model to set attributes on from validation, this is used when running + validation from the `__init__` method of a model. + by_alias: Whether to use the field's alias to match the input data to an attribute. + by_name: Whether to use the field's name to match the input data to an attribute. + """ + + +StringInput: TypeAlias = 'dict[str, StringInput]' + + +class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol): + """Event handler for `SchemaValidator.validate_strings`.""" + + def on_enter( + self, + input: StringInput, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> None: + """Callback to be notified of validation start, and create an instance of the event handler. + + Args: + input: The string data to be validated. + strict: Whether to validate the object in strict mode. + extra: Whether to ignore, allow, or forbid extra data during model validation. + context: The context to use for validation, this is passed to functional validators. + by_alias: Whether to use the field's alias to match the input data to an attribute. + by_name: Whether to use the field's name to match the input data to an attribute. + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py new file mode 100644 index 0000000..e8226fa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import importlib.metadata as importlib_metadata +import os +import warnings +from collections.abc import Iterable +from typing import TYPE_CHECKING, Final + +if TYPE_CHECKING: + from . import PydanticPluginProtocol + + +PYDANTIC_ENTRY_POINT_GROUP: Final[str] = 'pydantic' + +# cache of plugins +_plugins: dict[str, PydanticPluginProtocol] | None = None +# return no plugins while loading plugins to avoid recursion and errors while importing plugins +# this means that if plugins use pydantic +_loading_plugins: bool = False + + +def get_plugins() -> Iterable[PydanticPluginProtocol]: + """Load plugins for Pydantic. + + Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402 + """ + disabled_plugins = os.getenv('PYDANTIC_DISABLE_PLUGINS') + global _plugins, _loading_plugins + if _loading_plugins: + # this happens when plugins themselves use pydantic, we return no plugins + return () + elif disabled_plugins in ('__all__', '1', 'true'): + return () + elif _plugins is None: + _plugins = {} + # set _loading_plugins so any plugins that use pydantic don't themselves use plugins + _loading_plugins = True + try: + for dist in importlib_metadata.distributions(): + for entry_point in dist.entry_points: + if entry_point.group != PYDANTIC_ENTRY_POINT_GROUP: + continue + if entry_point.value in _plugins: + continue + if disabled_plugins is not None and entry_point.name in disabled_plugins.split(','): + continue + try: + _plugins[entry_point.value] = entry_point.load() + except (ImportError, AttributeError) as e: + warnings.warn( + f'{e.__class__.__name__} while loading the `{entry_point.name}` Pydantic plugin, ' + f'this plugin will not be installed.\n\n{e!r}', + stacklevel=2, + ) + finally: + _loading_plugins = False + + return _plugins.values() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py new file mode 100644 index 0000000..6ca5563 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py @@ -0,0 +1,152 @@ +"""Pluggable schema validator for pydantic.""" + +from __future__ import annotations + +import functools +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar + +from pydantic_core import CoreConfig, CoreSchema, SchemaValidator, ValidationError +from typing_extensions import ParamSpec + +if TYPE_CHECKING: + from . import BaseValidateHandlerProtocol, PydanticPluginProtocol, SchemaKind, SchemaTypePath + + +P = ParamSpec('P') +R = TypeVar('R') +Event = Literal['on_validate_python', 'on_validate_json', 'on_validate_strings'] +events: list[Event] = list(Event.__args__) # type: ignore + + +def create_schema_validator( + schema: CoreSchema, + schema_type: Any, + schema_type_module: str, + schema_type_name: str, + schema_kind: SchemaKind, + config: CoreConfig | None = None, + plugin_settings: dict[str, Any] | None = None, + _use_prebuilt: bool = True, +) -> SchemaValidator | PluggableSchemaValidator: + """Create a `SchemaValidator` or `PluggableSchemaValidator` if plugins are installed. + + Returns: + If plugins are installed then return `PluggableSchemaValidator`, otherwise return `SchemaValidator`. + """ + from . import SchemaTypePath + from ._loader import get_plugins + + plugins = get_plugins() + if plugins: + return PluggableSchemaValidator( + schema, + schema_type, + SchemaTypePath(schema_type_module, schema_type_name), + schema_kind, + config, + plugins, + plugin_settings or {}, + _use_prebuilt=_use_prebuilt, + ) + else: + return SchemaValidator(schema, config, _use_prebuilt=_use_prebuilt) + + +class PluggableSchemaValidator: + """Pluggable schema validator.""" + + __slots__ = '_schema_validator', 'validate_json', 'validate_python', 'validate_strings' + + def __init__( + self, + schema: CoreSchema, + schema_type: Any, + schema_type_path: SchemaTypePath, + schema_kind: SchemaKind, + config: CoreConfig | None, + plugins: Iterable[PydanticPluginProtocol], + plugin_settings: dict[str, Any], + _use_prebuilt: bool = True, + ) -> None: + self._schema_validator = SchemaValidator(schema, config, _use_prebuilt=_use_prebuilt) + + python_event_handlers: list[BaseValidateHandlerProtocol] = [] + json_event_handlers: list[BaseValidateHandlerProtocol] = [] + strings_event_handlers: list[BaseValidateHandlerProtocol] = [] + for plugin in plugins: + try: + p, j, s = plugin.new_schema_validator( + schema, schema_type, schema_type_path, schema_kind, config, plugin_settings + ) + except TypeError as e: # pragma: no cover + raise TypeError(f'Error using plugin `{plugin.__module__}:{plugin.__class__.__name__}`: {e}') from e + if p is not None: + python_event_handlers.append(p) + if j is not None: + json_event_handlers.append(j) + if s is not None: + strings_event_handlers.append(s) + + self.validate_python = build_wrapper(self._schema_validator.validate_python, python_event_handlers) + self.validate_json = build_wrapper(self._schema_validator.validate_json, json_event_handlers) + self.validate_strings = build_wrapper(self._schema_validator.validate_strings, strings_event_handlers) + + # Used to allow reuse of validators in pydantic-core (see pydantic-core/src/validators/prebuilt.rs): + @property + def __pydantic_schema_validator__(self) -> SchemaValidator: + """The underlying pydantic-core `SchemaValidator`. + + This is a private attribute, not meant to be used outside Pydantic. + """ + return self._schema_validator + + def __getattr__(self, name: str) -> Any: + return getattr(self._schema_validator, name) + + +def build_wrapper(func: Callable[P, R], event_handlers: list[BaseValidateHandlerProtocol]) -> Callable[P, R]: + if not event_handlers: + return func + else: + on_enters = tuple(h.on_enter for h in event_handlers if filter_handlers(h, 'on_enter')) + on_successes = tuple(h.on_success for h in event_handlers if filter_handlers(h, 'on_success')) + on_errors = tuple(h.on_error for h in event_handlers if filter_handlers(h, 'on_error')) + on_exceptions = tuple(h.on_exception for h in event_handlers if filter_handlers(h, 'on_exception')) + + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + for on_enter_handler in on_enters: + on_enter_handler(*args, **kwargs) + + try: + result = func(*args, **kwargs) + except ValidationError as error: + for on_error_handler in on_errors: + on_error_handler(error) + raise + except Exception as exception: + for on_exception_handler in on_exceptions: + on_exception_handler(exception) + raise + else: + for on_success_handler in on_successes: + on_success_handler(result) + return result + + return wrapper + + +def filter_handlers(handler_cls: BaseValidateHandlerProtocol, method_name: str) -> bool: + """Filter out handler methods which are not implemented by the plugin directly - e.g. those that are missing + or are inherited from the protocol. + """ + handler = getattr(handler_cls, method_name, None) + if handler is None: + return False + elif handler.__module__ == 'pydantic.plugin': + # this is the original handler, from the protocol due to runtime inheritance + # we don't want to call it + return False + else: + return True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py new file mode 100644 index 0000000..d290043 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py @@ -0,0 +1,157 @@ +"""RootModel class and type definitions.""" + +from __future__ import annotations as _annotations + +from copy import copy, deepcopy +from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar + +from pydantic_core import PydanticUndefined +from typing_extensions import Self, dataclass_transform + +from . import PydanticUserError +from ._internal import _model_construction, _repr +from .main import BaseModel, _object_setattr + +if TYPE_CHECKING: + from .fields import Field as PydanticModelField + from .fields import PrivateAttr as PydanticModelPrivateAttr + + # dataclass_transform could be applied to RootModel directly, but `ModelMetaclass`'s dataclass_transform + # takes priority (at least with pyright). We trick type checkers into thinking we apply dataclass_transform + # on a new metaclass. + @dataclass_transform(kw_only_default=False, field_specifiers=(PydanticModelField, PydanticModelPrivateAttr)) + class _RootModelMetaclass(_model_construction.ModelMetaclass): ... +else: + _RootModelMetaclass = _model_construction.ModelMetaclass + +__all__ = ('RootModel',) + +RootModelRootType = TypeVar('RootModelRootType') + + +class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetaclass): + """!!! abstract "Usage Documentation" + [`RootModel` and Custom Root Types](../concepts/models.md#rootmodel-and-custom-root-types) + + A Pydantic `BaseModel` for the root object of the model. + + Attributes: + root: The root object of the model. + __pydantic_root_model__: Whether the model is a RootModel. + __pydantic_private__: Private fields in the model. + __pydantic_extra__: Extra fields in the model. + + """ + + __pydantic_root_model__ = True + __pydantic_private__ = None + __pydantic_extra__ = None + + root: RootModelRootType + + def __init_subclass__(cls, **kwargs): + extra = cls.model_config.get('extra') + if extra is not None: + raise PydanticUserError( + "`RootModel` does not support setting `model_config['extra']`", code='root-model-extra' + ) + super().__init_subclass__(**kwargs) + + def __init__(self, /, root: RootModelRootType = PydanticUndefined, **data) -> None: # type: ignore + __tracebackhide__ = True + if data: + if root is not PydanticUndefined: + raise ValueError( + '"RootModel.__init__" accepts either a single positional argument or arbitrary keyword arguments' + ) + root = data # type: ignore + self.__pydantic_validator__.validate_python(root, self_instance=self) + + __init__.__pydantic_base_init__ = True # pyright: ignore[reportFunctionMemberAccess] + + @classmethod + def model_construct(cls, root: RootModelRootType, _fields_set: set[str] | None = None) -> Self: # type: ignore + """Create a new model using the provided root object and update fields set. + + Args: + root: The root object of the model. + _fields_set: The set of fields to be updated. + + Returns: + The new model. + + Raises: + NotImplemented: If the model is not a subclass of `RootModel`. + """ + return super().model_construct(root=root, _fields_set=_fields_set) + + def __getstate__(self) -> dict[Any, Any]: + return { + '__dict__': self.__dict__, + '__pydantic_fields_set__': self.__pydantic_fields_set__, + } + + def __setstate__(self, state: dict[Any, Any]) -> None: + _object_setattr(self, '__pydantic_fields_set__', state['__pydantic_fields_set__']) + _object_setattr(self, '__dict__', state['__dict__']) + + def __copy__(self) -> Self: + """Returns a shallow copy of the model.""" + cls = type(self) + m = cls.__new__(cls) + new_dict = copy(self.__dict__) + new_dict['root'] = copy(self.__dict__['root']) + _object_setattr(m, '__dict__', new_dict) + _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) + return m + + def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Self: + """Returns a deep copy of the model.""" + cls = type(self) + m = cls.__new__(cls) + _object_setattr(m, '__dict__', deepcopy(self.__dict__, memo=memo)) + # This next line doesn't need a deepcopy because __pydantic_fields_set__ is a set[str], + # and attempting a deepcopy would be marginally slower. + _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) + return m + + if TYPE_CHECKING: + + def model_dump( # type: ignore + self, + *, + mode: Literal['json', 'python'] | str = 'python', + include: Any = None, + exclude: Any = None, + context: dict[str, Any] | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + serialize_as_any: bool = False, + ) -> Any: + """This method is included just to get a more accurate return type for type checkers. + It is included in this `if TYPE_CHECKING:` block since no override is actually necessary. + + See the documentation of `BaseModel.model_dump` for more details about the arguments. + + Generally, this method will have a return type of `RootModelRootType`, assuming that `RootModelRootType` is + not a `BaseModel` subclass. If `RootModelRootType` is a `BaseModel` subclass, then the return + type will likely be `dict[str, Any]`, as `model_dump` calls are recursive. The return type could + even be something different, in the case of a custom serializer. + Thus, `Any` is used here to catch all of these cases. + """ + ... + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, RootModel): + return NotImplemented + return self.__pydantic_fields__['root'].annotation == other.__pydantic_fields__[ + 'root' + ].annotation and super().__eq__(other) + + def __repr_args__(self) -> _repr.ReprArgs: + yield 'root', self.root diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py new file mode 100644 index 0000000..a3245a6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/schema.py @@ -0,0 +1,5 @@ +"""The `schema` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py new file mode 100644 index 0000000..fdc68c4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/tools.py @@ -0,0 +1,5 @@ +"""The `tools` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py new file mode 100644 index 0000000..d962305 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py @@ -0,0 +1,801 @@ +"""Type adapter specification.""" + +from __future__ import annotations as _annotations + +import sys +import types +from collections.abc import Callable, Iterable +from dataclasses import is_dataclass +from types import FrameType +from typing import ( + Any, + Generic, + Literal, + TypeVar, + cast, + final, + overload, +) + +from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator, Some +from typing_extensions import ParamSpec, is_typeddict + +from pydantic.errors import PydanticUserError +from pydantic.main import BaseModel, IncEx + +from ._internal import _config, _generate_schema, _mock_val_ser, _namespace_utils, _repr, _typing_extra, _utils +from .config import ConfigDict, ExtraValues +from .errors import PydanticUndefinedAnnotation +from .json_schema import ( + DEFAULT_REF_TEMPLATE, + GenerateJsonSchema, + JsonSchemaKeyT, + JsonSchemaMode, + JsonSchemaValue, +) +from .plugin._schema_validator import PluggableSchemaValidator, create_schema_validator + +T = TypeVar('T') +R = TypeVar('R') +P = ParamSpec('P') +TypeAdapterT = TypeVar('TypeAdapterT', bound='TypeAdapter') + + +def _getattr_no_parents(obj: Any, attribute: str) -> Any: + """Returns the attribute value without attempting to look up attributes from parent types.""" + if hasattr(obj, '__dict__'): + try: + return obj.__dict__[attribute] + except KeyError: + pass + + slots = getattr(obj, '__slots__', None) + if slots is not None and attribute in slots: + return getattr(obj, attribute) + else: + raise AttributeError(attribute) + + +def _type_has_config(type_: Any) -> bool: + """Returns whether the type has config.""" + type_ = _typing_extra.annotated_type(type_) or type_ + try: + return issubclass(type_, BaseModel) or is_dataclass(type_) or is_typeddict(type_) + except TypeError: + # type is not a class + return False + + +@final +class TypeAdapter(Generic[T]): + """!!! abstract "Usage Documentation" + [`TypeAdapter`](../concepts/type_adapter.md) + + Type adapters provide a flexible way to perform validation and serialization based on a Python type. + + A `TypeAdapter` instance exposes some of the functionality from `BaseModel` instance methods + for types that do not have such methods (such as dataclasses, primitive types, and more). + + **Note:** `TypeAdapter` instances are not types, and cannot be used as type annotations for fields. + + Args: + type: The type associated with the `TypeAdapter`. + config: Configuration for the `TypeAdapter`, should be a dictionary conforming to + [`ConfigDict`][pydantic.config.ConfigDict]. + + !!! note + You cannot provide a configuration when instantiating a `TypeAdapter` if the type you're using + has its own config that cannot be overridden (ex: `BaseModel`, `TypedDict`, and `dataclass`). A + [`type-adapter-config-unused`](../errors/usage_errors.md#type-adapter-config-unused) error will + be raised in this case. + _parent_depth: Depth at which to search for the [parent frame][frame-objects]. This frame is used when + resolving forward annotations during schema building, by looking for the globals and locals of this + frame. Defaults to 2, which will result in the frame where the `TypeAdapter` was instantiated. + + !!! note + This parameter is named with an underscore to suggest its private nature and discourage use. + It may be deprecated in a minor version, so we only recommend using it if you're comfortable + with potential change in behavior/support. It's default value is 2 because internally, + the `TypeAdapter` class makes another call to fetch the frame. + module: The module that passes to plugin if provided. + + Attributes: + core_schema: The core schema for the type. + validator: The schema validator for the type. + serializer: The schema serializer for the type. + pydantic_complete: Whether the core schema for the type is successfully built. + + ??? tip "Compatibility with `mypy`" + Depending on the type used, `mypy` might raise an error when instantiating a `TypeAdapter`. As a workaround, you can explicitly + annotate your variable: + + ```py + from typing import Union + + from pydantic import TypeAdapter + + ta: TypeAdapter[Union[str, int]] = TypeAdapter(Union[str, int]) # type: ignore[arg-type] + ``` + + ??? info "Namespace management nuances and implementation details" + + Here, we collect some notes on namespace management, and subtle differences from `BaseModel`: + + `BaseModel` uses its own `__module__` to find out where it was defined + and then looks for symbols to resolve forward references in those globals. + On the other hand, `TypeAdapter` can be initialized with arbitrary objects, + which may not be types and thus do not have a `__module__` available. + So instead we look at the globals in our parent stack frame. + + It is expected that the `ns_resolver` passed to this function will have the correct + namespace for the type we're adapting. See the source code for `TypeAdapter.__init__` + and `TypeAdapter.rebuild` for various ways to construct this namespace. + + This works for the case where this function is called in a module that + has the target of forward references in its scope, but + does not always work for more complex cases. + + For example, take the following: + + ```python {title="a.py"} + IntList = list[int] + OuterDict = dict[str, 'IntList'] + ``` + + ```python {test="skip" title="b.py"} + from a import OuterDict + + from pydantic import TypeAdapter + + IntList = int # replaces the symbol the forward reference is looking for + v = TypeAdapter(OuterDict) + v({'x': 1}) # should fail but doesn't + ``` + + If `OuterDict` were a `BaseModel`, this would work because it would resolve + the forward reference within the `a.py` namespace. + But `TypeAdapter(OuterDict)` can't determine what module `OuterDict` came from. + + In other words, the assumption that _all_ forward references exist in the + module we are being called from is not technically always true. + Although most of the time it is and it works fine for recursive models and such, + `BaseModel`'s behavior isn't perfect either and _can_ break in similar ways, + so there is no right or wrong between the two. + + But at the very least this behavior is _subtly_ different from `BaseModel`'s. + """ + + core_schema: CoreSchema + validator: SchemaValidator | PluggableSchemaValidator + serializer: SchemaSerializer + pydantic_complete: bool + + @overload + def __init__( + self, + type: type[T], + *, + config: ConfigDict | None = ..., + _parent_depth: int = ..., + module: str | None = ..., + ) -> None: ... + + # This second overload is for unsupported special forms (such as Annotated, Union, etc.) + # Currently there is no way to type this correctly + # See https://github.com/python/typing/pull/1618 + @overload + def __init__( + self, + type: Any, + *, + config: ConfigDict | None = ..., + _parent_depth: int = ..., + module: str | None = ..., + ) -> None: ... + + def __init__( + self, + type: Any, + *, + config: ConfigDict | None = None, + _parent_depth: int = 2, + module: str | None = None, + ) -> None: + if _type_has_config(type) and config is not None: + raise PydanticUserError( + 'Cannot use `config` when the type is a BaseModel, dataclass or TypedDict.' + ' These types can have their own config and setting the config via the `config`' + ' parameter to TypeAdapter will not override it, thus the `config` you passed to' + ' TypeAdapter becomes meaningless, which is probably not what you want.', + code='type-adapter-config-unused', + ) + + self._type = type + self._config = config + self._parent_depth = _parent_depth + self.pydantic_complete = False + + parent_frame = self._fetch_parent_frame() + if isinstance(type, types.FunctionType): + # Special case functions, which are *not* pushed to the `NsResolver` stack and without this special case + # would only have access to the parent namespace where the `TypeAdapter` was instantiated (if the function is defined + # in another module, we need to look at that module's globals). + if parent_frame is not None: + # `f_locals` is the namespace where the type adapter was instantiated (~ to `f_globals` if at the module level): + parent_ns = parent_frame.f_locals + else: # pragma: no cover + parent_ns = None + globalns, localns = _namespace_utils.ns_for_function( + type, + parent_namespace=parent_ns, + ) + parent_namespace = None + else: + if parent_frame is not None: + globalns = parent_frame.f_globals + # Do not provide a local ns if the type adapter happens to be instantiated at the module level: + localns = parent_frame.f_locals if parent_frame.f_locals is not globalns else {} + else: # pragma: no cover + globalns = {} + localns = {} + parent_namespace = localns + + self._module_name = module or cast(str, globalns.get('__name__', '')) + self._init_core_attrs( + ns_resolver=_namespace_utils.NsResolver( + namespaces_tuple=_namespace_utils.NamespacesTuple(locals=localns, globals=globalns), + parent_namespace=parent_namespace, + ), + force=False, + ) + + def _fetch_parent_frame(self) -> FrameType | None: + frame = sys._getframe(self._parent_depth) + if frame.f_globals.get('__name__') == 'typing': + # Because `TypeAdapter` is generic, explicitly parametrizing the class results + # in a `typing._GenericAlias` instance, which proxies instantiation calls to the + # "real" `TypeAdapter` class and thus adding an extra frame to the call. To avoid + # pulling anything from the `typing` module, use the correct frame (the one before): + return frame.f_back + + return frame + + def _init_core_attrs( + self, ns_resolver: _namespace_utils.NsResolver, force: bool, raise_errors: bool = False + ) -> bool: + """Initialize the core schema, validator, and serializer for the type. + + Args: + ns_resolver: The namespace resolver to use when building the core schema for the adapted type. + force: Whether to force the construction of the core schema, validator, and serializer. + If `force` is set to `False` and `_defer_build` is `True`, the core schema, validator, and serializer will be set to mocks. + raise_errors: Whether to raise errors if initializing any of the core attrs fails. + + Returns: + `True` if the core schema, validator, and serializer were successfully initialized, otherwise `False`. + + Raises: + PydanticUndefinedAnnotation: If `PydanticUndefinedAnnotation` occurs in`__get_pydantic_core_schema__` + and `raise_errors=True`. + """ + if not force and self._defer_build: + _mock_val_ser.set_type_adapter_mocks(self) + self.pydantic_complete = False + return False + + try: + self.core_schema = _getattr_no_parents(self._type, '__pydantic_core_schema__') + self.validator = _getattr_no_parents(self._type, '__pydantic_validator__') + self.serializer = _getattr_no_parents(self._type, '__pydantic_serializer__') + + # TODO: we don't go through the rebuild logic here directly because we don't want + # to repeat all of the namespace fetching logic that we've already done + # so we simply skip to the block below that does the actual schema generation + if ( + isinstance(self.core_schema, _mock_val_ser.MockCoreSchema) + or isinstance(self.validator, _mock_val_ser.MockValSer) + or isinstance(self.serializer, _mock_val_ser.MockValSer) + ): + raise AttributeError() + except AttributeError: + config_wrapper = _config.ConfigWrapper(self._config) + + schema_generator = _generate_schema.GenerateSchema(config_wrapper, ns_resolver=ns_resolver) + + try: + core_schema = schema_generator.generate_schema(self._type) + except PydanticUndefinedAnnotation: + if raise_errors: + raise + _mock_val_ser.set_type_adapter_mocks(self) + return False + + try: + self.core_schema = schema_generator.clean_schema(core_schema) + except _generate_schema.InvalidSchemaError: + _mock_val_ser.set_type_adapter_mocks(self) + return False + + core_config = config_wrapper.core_config(None) + + self.validator = create_schema_validator( + schema=self.core_schema, + schema_type=self._type, + schema_type_module=self._module_name, + schema_type_name=str(self._type), + schema_kind='TypeAdapter', + config=core_config, + plugin_settings=config_wrapper.plugin_settings, + ) + self.serializer = SchemaSerializer(self.core_schema, core_config) + + self.pydantic_complete = True + return True + + @property + def _defer_build(self) -> bool: + config = self._config if self._config is not None else self._model_config + if config: + return config.get('defer_build') is True + return False + + @property + def _model_config(self) -> ConfigDict | None: + type_: Any = _typing_extra.annotated_type(self._type) or self._type # Eg FastAPI heavily uses Annotated + if _utils.lenient_issubclass(type_, BaseModel): + return type_.model_config + return getattr(type_, '__pydantic_config__', None) + + def __repr__(self) -> str: + return f'TypeAdapter({_repr.display_as_type(self._type)})' + + def rebuild( + self, + *, + force: bool = False, + raise_errors: bool = True, + _parent_namespace_depth: int = 2, + _types_namespace: _namespace_utils.MappingNamespace | None = None, + ) -> bool | None: + """Try to rebuild the pydantic-core schema for the adapter's type. + + This may be necessary when one of the annotations is a ForwardRef which could not be resolved during + the initial attempt to build the schema, and automatic rebuilding fails. + + Args: + force: Whether to force the rebuilding of the type adapter's schema, defaults to `False`. + raise_errors: Whether to raise errors, defaults to `True`. + _parent_namespace_depth: Depth at which to search for the [parent frame][frame-objects]. This + frame is used when resolving forward annotations during schema rebuilding, by looking for + the locals of this frame. Defaults to 2, which will result in the frame where the method + was called. + _types_namespace: An explicit types namespace to use, instead of using the local namespace + from the parent frame. Defaults to `None`. + + Returns: + Returns `None` if the schema is already "complete" and rebuilding was not required. + If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. + """ + if not force and self.pydantic_complete: + return None + + if _types_namespace is not None: + rebuild_ns = _types_namespace + elif _parent_namespace_depth > 0: + rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} + else: + rebuild_ns = {} + + # we have to manually fetch globals here because there's no type on the stack of the NsResolver + # and so we skip the globalns = get_module_ns_of(typ) call that would normally happen + globalns = sys._getframe(max(_parent_namespace_depth - 1, 1)).f_globals + ns_resolver = _namespace_utils.NsResolver( + namespaces_tuple=_namespace_utils.NamespacesTuple(locals=rebuild_ns, globals=globalns), + parent_namespace=rebuild_ns, + ) + return self._init_core_attrs(ns_resolver=ns_resolver, force=True, raise_errors=raise_errors) + + def validate_python( + self, + object: Any, + /, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> T: + """Validate a Python object against the model. + + Args: + object: The Python object to validate against the model. + strict: Whether to strictly check types. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + from_attributes: Whether to extract data from object attributes. + context: Additional context to pass to the validator. + experimental_allow_partial: **Experimental** whether to enable + [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. + * False / 'off': Default behavior, no partial validation. + * True / 'on': Enable partial validation. + * 'trailing-strings': Enable partial validation and allow trailing strings in the input. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + !!! note + When using `TypeAdapter` with a Pydantic `dataclass`, the use of the `from_attributes` + argument is not supported. + + Returns: + The validated object. + """ + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return self.validator.validate_python( + object, + strict=strict, + extra=extra, + from_attributes=from_attributes, + context=context, + allow_partial=experimental_allow_partial, + by_alias=by_alias, + by_name=by_name, + ) + + def validate_json( + self, + data: str | bytes | bytearray, + /, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> T: + """!!! abstract "Usage Documentation" + [JSON Parsing](../concepts/json.md#json-parsing) + + Validate a JSON string or bytes against the model. + + Args: + data: The JSON data to validate against the model. + strict: Whether to strictly check types. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + context: Additional context to use during validation. + experimental_allow_partial: **Experimental** whether to enable + [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. + * False / 'off': Default behavior, no partial validation. + * True / 'on': Enable partial validation. + * 'trailing-strings': Enable partial validation and allow trailing strings in the input. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Returns: + The validated object. + """ + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return self.validator.validate_json( + data, + strict=strict, + extra=extra, + context=context, + allow_partial=experimental_allow_partial, + by_alias=by_alias, + by_name=by_name, + ) + + def validate_strings( + self, + obj: Any, + /, + *, + strict: bool | None = None, + extra: ExtraValues | None = None, + context: Any | None = None, + experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> T: + """Validate object contains string data against the model. + + Args: + obj: The object contains string data to validate. + strict: Whether to strictly check types. + extra: Whether to ignore, allow, or forbid extra data during model validation. + See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + context: Additional context to use during validation. + experimental_allow_partial: **Experimental** whether to enable + [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. + * False / 'off': Default behavior, no partial validation. + * True / 'on': Enable partial validation. + * 'trailing-strings': Enable partial validation and allow trailing strings in the input. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Returns: + The validated object. + """ + if by_alias is False and by_name is not True: + raise PydanticUserError( + 'At least one of `by_alias` or `by_name` must be set to True.', + code='validate-by-alias-and-name-false', + ) + + return self.validator.validate_strings( + obj, + strict=strict, + extra=extra, + context=context, + allow_partial=experimental_allow_partial, + by_alias=by_alias, + by_name=by_name, + ) + + def get_default_value(self, *, strict: bool | None = None, context: Any | None = None) -> Some[T] | None: + """Get the default value for the wrapped type. + + Args: + strict: Whether to strictly check types. + context: Additional context to pass to the validator. + + Returns: + The default value wrapped in a `Some` if there is one or None if not. + """ + return self.validator.get_default_value(strict=strict, context=context) + + def dump_python( + self, + instance: T, + /, + *, + mode: Literal['json', 'python'] = 'python', + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, + ) -> Any: + """Dump an instance of the adapted type to a Python object. + + Args: + instance: The Python object to serialize. + mode: The output format. + include: Fields to include in the output. + exclude: Fields to exclude from the output. + by_alias: Whether to use alias names for field names. + exclude_unset: Whether to exclude unset fields. + exclude_defaults: Whether to exclude fields with default values. + exclude_none: Whether to exclude fields with None values. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: Whether to output the serialized data in a way that is compatible with deserialization. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: Additional context to pass to the serializer. + + Returns: + The serialized object. + """ + return self.serializer.to_python( + instance, + mode=mode, + by_alias=by_alias, + include=include, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + exclude_computed_fields=exclude_computed_fields, + round_trip=round_trip, + warnings=warnings, + fallback=fallback, + serialize_as_any=serialize_as_any, + polymorphic_serialization=polymorphic_serialization, + context=context, + ) + + def dump_json( + self, + instance: T, + /, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, + ) -> bytes: + """!!! abstract "Usage Documentation" + [JSON Serialization](../concepts/json.md#json-serialization) + + Serialize an instance of the adapted type to JSON. + + Args: + instance: The instance to be serialized. + indent: Number of spaces for JSON indentation. + ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. + If `False` (the default), these characters will be output as-is. + include: Fields to include. + exclude: Fields to exclude. + by_alias: Whether to use alias names for field names. + exclude_unset: Whether to exclude unset fields. + exclude_defaults: Whether to exclude fields with default values. + exclude_none: Whether to exclude fields with a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: Whether to serialize and deserialize the instance to ensure round-tripping. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: Additional context to pass to the serializer. + + Returns: + The JSON representation of the given instance as bytes. + """ + return self.serializer.to_json( + instance, + indent=indent, + ensure_ascii=ensure_ascii, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + exclude_computed_fields=exclude_computed_fields, + round_trip=round_trip, + warnings=warnings, + fallback=fallback, + serialize_as_any=serialize_as_any, + polymorphic_serialization=polymorphic_serialization, + context=context, + ) + + def json_schema( + self, + *, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, + mode: JsonSchemaMode = 'validation', + ) -> dict[str, Any]: + """Generate a JSON schema for the adapted type. + + Args: + by_alias: Whether to use alias names for field names. + ref_template: The format string used for generating $ref strings. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + schema_generator: To override the logic used to generate the JSON schema, as a subclass of + `GenerateJsonSchema` with your desired modifications + mode: The mode in which to generate the schema. + schema_generator: The generator class used for creating the schema. + mode: The mode to use for schema generation. + + Returns: + The JSON schema for the model as a dictionary. + """ + schema_generator_instance = schema_generator( + by_alias=by_alias, ref_template=ref_template, union_format=union_format + ) + if isinstance(self.core_schema, _mock_val_ser.MockCoreSchema): + self.core_schema.rebuild() + assert not isinstance(self.core_schema, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it' + return schema_generator_instance.generate(self.core_schema, mode=mode) + + @staticmethod + def json_schemas( + inputs: Iterable[tuple[JsonSchemaKeyT, JsonSchemaMode, TypeAdapter[Any]]], + /, + *, + by_alias: bool = True, + title: str | None = None, + description: str | None = None, + ref_template: str = DEFAULT_REF_TEMPLATE, + union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', + schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, + ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]: + """Generate a JSON schema including definitions from multiple type adapters. + + Args: + inputs: Inputs to schema generation. The first two items will form the keys of the (first) + output mapping; the type adapters will provide the core schemas that get converted into + definitions in the output JSON schema. + by_alias: Whether to use alias names. + title: The title for the schema. + description: The description for the schema. + ref_template: The format string used for generating $ref strings. + union_format: The format to use when combining schemas from unions together. Can be one of: + + - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) + keyword to combine schemas (the default). + - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) + keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive + type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to + `any_of`. + schema_generator: The generator class used for creating the schema. + + Returns: + A tuple where: + + - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and + whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have + JsonRef references to definitions that are defined in the second returned element.) + - The second element is a JSON schema containing all definitions referenced in the first returned + element, along with the optional title and description keys. + + """ + schema_generator_instance = schema_generator( + by_alias=by_alias, ref_template=ref_template, union_format=union_format + ) + + inputs_ = [] + for key, mode, adapter in inputs: + # This is the same pattern we follow for model json schemas - we attempt a core schema rebuild if we detect a mock + if isinstance(adapter.core_schema, _mock_val_ser.MockCoreSchema): + adapter.core_schema.rebuild() + assert not isinstance(adapter.core_schema, _mock_val_ser.MockCoreSchema), ( + 'this is a bug! please report it' + ) + inputs_.append((key, mode, adapter.core_schema)) + + json_schemas_map, definitions = schema_generator_instance.generate_definitions(inputs_) + + json_schema: dict[str, Any] = {} + if definitions: + json_schema['$defs'] = definitions + if title: + json_schema['title'] = title + if description: + json_schema['description'] = description + + return json_schemas_map, json_schema diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py new file mode 100644 index 0000000..467bc82 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/types.py @@ -0,0 +1,3310 @@ +"""The types module contains custom types used by pydantic.""" + +from __future__ import annotations as _annotations + +import base64 +import dataclasses as _dataclasses +import re +from collections.abc import Hashable, Iterator +from datetime import date, datetime +from decimal import Decimal +from enum import Enum +from pathlib import Path +from re import Pattern +from types import ModuleType +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Callable, + ClassVar, + Generic, + Literal, + TypeVar, + Union, + cast, +) +from uuid import UUID + +import annotated_types +from annotated_types import BaseMetadata, MaxLen, MinLen +from pydantic_core import CoreSchema, PydanticCustomError, SchemaSerializer, core_schema +from typing_extensions import Protocol, TypeAlias, TypeAliasType, deprecated, get_args, get_origin + +from ._internal import _fields, _internal_dataclass, _utils, _validators +from ._migration import getattr_migration +from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler +from .errors import PydanticUserError +from .json_schema import JsonSchemaValue +from .warnings import PydanticDeprecatedSince20 + +if TYPE_CHECKING: + from ._internal._core_metadata import CoreMetadata + +__all__ = ( + 'Strict', + 'StrictStr', + 'SocketPath', + 'conbytes', + 'conlist', + 'conset', + 'confrozenset', + 'constr', + 'ImportString', + 'conint', + 'PositiveInt', + 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', + 'confloat', + 'PositiveFloat', + 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', + 'FiniteFloat', + 'condecimal', + 'UUID1', + 'UUID3', + 'UUID4', + 'UUID5', + 'UUID6', + 'UUID7', + 'UUID8', + 'FilePath', + 'DirectoryPath', + 'NewPath', + 'Json', + 'Secret', + 'SecretStr', + 'SecretBytes', + 'StrictBool', + 'StrictBytes', + 'StrictInt', + 'StrictFloat', + 'PaymentCardNumber', + 'ByteSize', + 'PastDate', + 'FutureDate', + 'PastDatetime', + 'FutureDatetime', + 'condate', + 'AwareDatetime', + 'NaiveDatetime', + 'AllowInfNan', + 'EncoderProtocol', + 'EncodedBytes', + 'EncodedStr', + 'Base64Encoder', + 'Base64Bytes', + 'Base64Str', + 'Base64UrlBytes', + 'Base64UrlStr', + 'GetPydanticSchema', + 'StringConstraints', + 'Tag', + 'Discriminator', + 'JsonValue', + 'OnErrorOmit', + 'FailFast', +) + + +T = TypeVar('T') + + +@_dataclasses.dataclass +class Strict(_fields.PydanticMetadata, BaseMetadata): + """!!! abstract "Usage Documentation" + [Strict Mode with `Annotated` `Strict`](../concepts/strict_mode.md#strict-mode-with-annotated-strict) + + A field metadata class to indicate that a field should be validated in strict mode. + Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. + + Attributes: + strict: Whether to validate the field in strict mode. + + Example: + ```python + from typing import Annotated + + from pydantic.types import Strict + + StrictBool = Annotated[bool, Strict()] + ``` + """ + + strict: bool = True + + def __hash__(self) -> int: + return hash(self.strict) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BOOLEAN TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +StrictBool = Annotated[bool, Strict()] +"""A boolean that must be either ``True`` or ``False``.""" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INTEGER TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def conint( + *, + strict: bool | None = None, + gt: int | None = None, + ge: int | None = None, + lt: int | None = None, + le: int | None = None, + multiple_of: int | None = None, +) -> type[int]: + """ + !!! warning "Discouraged" + This function is **discouraged** in favor of using + [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) with + [`Field`][pydantic.fields.Field] instead. + + This function will be **deprecated** in Pydantic 3.0. + + The reason is that `conint` returns a type, which doesn't play well with static analysis tools. + + === ":x: Don't do this" + ```python + from pydantic import BaseModel, conint + + class Foo(BaseModel): + bar: conint(strict=True, gt=0) + ``` + + === ":white_check_mark: Do this" + ```python + from typing import Annotated + + from pydantic import BaseModel, Field + + class Foo(BaseModel): + bar: Annotated[int, Field(strict=True, gt=0)] + ``` + + A wrapper around `int` that allows for additional constraints. + + Args: + strict: Whether to validate the integer in strict mode. Defaults to `None`. + gt: The value must be greater than this. + ge: The value must be greater than or equal to this. + lt: The value must be less than this. + le: The value must be less than or equal to this. + multiple_of: The value must be a multiple of this. + + Returns: + The wrapped integer type. + + ```python + from pydantic import BaseModel, ValidationError, conint + + class ConstrainedExample(BaseModel): + constrained_int: conint(gt=1) + + m = ConstrainedExample(constrained_int=2) + print(repr(m)) + #> ConstrainedExample(constrained_int=2) + + try: + ConstrainedExample(constrained_int=0) + except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('constrained_int',), + 'msg': 'Input should be greater than 1', + 'input': 0, + 'ctx': {'gt': 1}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than', + } + ] + ''' + ``` + + """ # noqa: D212 + return Annotated[ # pyright: ignore[reportReturnType] + int, + Strict(strict) if strict is not None else None, + annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), + annotated_types.MultipleOf(multiple_of) if multiple_of is not None else None, + ] + + +PositiveInt = Annotated[int, annotated_types.Gt(0)] +"""An integer that must be greater than zero. + +```python +from pydantic import BaseModel, PositiveInt, ValidationError + +class Model(BaseModel): + positive_int: PositiveInt + +m = Model(positive_int=1) +print(repr(m)) +#> Model(positive_int=1) + +try: + Model(positive_int=-1) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('positive_int',), + 'msg': 'Input should be greater than 0', + 'input': -1, + 'ctx': {'gt': 0}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than', + } + ] + ''' +``` +""" +NegativeInt = Annotated[int, annotated_types.Lt(0)] +"""An integer that must be less than zero. + +```python +from pydantic import BaseModel, NegativeInt, ValidationError + +class Model(BaseModel): + negative_int: NegativeInt + +m = Model(negative_int=-1) +print(repr(m)) +#> Model(negative_int=-1) + +try: + Model(negative_int=1) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'less_than', + 'loc': ('negative_int',), + 'msg': 'Input should be less than 0', + 'input': 1, + 'ctx': {'lt': 0}, + 'url': 'https://errors.pydantic.dev/2/v/less_than', + } + ] + ''' +``` +""" +NonPositiveInt = Annotated[int, annotated_types.Le(0)] +"""An integer that must be less than or equal to zero. + +```python +from pydantic import BaseModel, NonPositiveInt, ValidationError + +class Model(BaseModel): + non_positive_int: NonPositiveInt + +m = Model(non_positive_int=0) +print(repr(m)) +#> Model(non_positive_int=0) + +try: + Model(non_positive_int=1) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'less_than_equal', + 'loc': ('non_positive_int',), + 'msg': 'Input should be less than or equal to 0', + 'input': 1, + 'ctx': {'le': 0}, + 'url': 'https://errors.pydantic.dev/2/v/less_than_equal', + } + ] + ''' +``` +""" +NonNegativeInt = Annotated[int, annotated_types.Ge(0)] +"""An integer that must be greater than or equal to zero. + +```python +from pydantic import BaseModel, NonNegativeInt, ValidationError + +class Model(BaseModel): + non_negative_int: NonNegativeInt + +m = Model(non_negative_int=0) +print(repr(m)) +#> Model(non_negative_int=0) + +try: + Model(non_negative_int=-1) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than_equal', + 'loc': ('non_negative_int',), + 'msg': 'Input should be greater than or equal to 0', + 'input': -1, + 'ctx': {'ge': 0}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than_equal', + } + ] + ''' +``` +""" +StrictInt = Annotated[int, Strict()] +"""An integer that must be validated in strict mode. + +```python +from pydantic import BaseModel, StrictInt, ValidationError + +class StrictIntModel(BaseModel): + strict_int: StrictInt + +try: + StrictIntModel(strict_int=3.14159) +except ValidationError as e: + print(e) + ''' + 1 validation error for StrictIntModel + strict_int + Input should be a valid integer [type=int_type, input_value=3.14159, input_type=float] + ''' +``` +""" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOAT TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +@_dataclasses.dataclass +class AllowInfNan(_fields.PydanticMetadata): + """A field metadata class to indicate that a field should allow `-inf`, `inf`, and `nan`. + + Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. + + Attributes: + allow_inf_nan: Whether to allow `-inf`, `inf`, and `nan`. Defaults to `True`. + + Example: + ```python + from typing import Annotated + + from pydantic.types import AllowInfNan + + LaxFloat = Annotated[float, AllowInfNan()] + ``` + """ + + allow_inf_nan: bool = True + + def __hash__(self) -> int: + return hash(self.allow_inf_nan) + + +def confloat( + *, + strict: bool | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + multiple_of: float | None = None, + allow_inf_nan: bool | None = None, +) -> type[float]: + """ + !!! warning "Discouraged" + This function is **discouraged** in favor of using + [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) with + [`Field`][pydantic.fields.Field] instead. + + This function will be **deprecated** in Pydantic 3.0. + + The reason is that `confloat` returns a type, which doesn't play well with static analysis tools. + + === ":x: Don't do this" + ```python + from pydantic import BaseModel, confloat + + class Foo(BaseModel): + bar: confloat(strict=True, gt=0) + ``` + + === ":white_check_mark: Do this" + ```python + from typing import Annotated + + from pydantic import BaseModel, Field + + class Foo(BaseModel): + bar: Annotated[float, Field(strict=True, gt=0)] + ``` + + A wrapper around `float` that allows for additional constraints. + + Args: + strict: Whether to validate the float in strict mode. + gt: The value must be greater than this. + ge: The value must be greater than or equal to this. + lt: The value must be less than this. + le: The value must be less than or equal to this. + multiple_of: The value must be a multiple of this. + allow_inf_nan: Whether to allow `-inf`, `inf`, and `nan`. + + Returns: + The wrapped float type. + + ```python + from pydantic import BaseModel, ValidationError, confloat + + class ConstrainedExample(BaseModel): + constrained_float: confloat(gt=1.0) + + m = ConstrainedExample(constrained_float=1.1) + print(repr(m)) + #> ConstrainedExample(constrained_float=1.1) + + try: + ConstrainedExample(constrained_float=0.9) + except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('constrained_float',), + 'msg': 'Input should be greater than 1', + 'input': 0.9, + 'ctx': {'gt': 1.0}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than', + } + ] + ''' + ``` + """ # noqa: D212 + return Annotated[ # pyright: ignore[reportReturnType] + float, + Strict(strict) if strict is not None else None, + annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), + annotated_types.MultipleOf(multiple_of) if multiple_of is not None else None, + AllowInfNan(allow_inf_nan) if allow_inf_nan is not None else None, + ] + + +PositiveFloat = Annotated[float, annotated_types.Gt(0)] +"""A float that must be greater than zero. + +```python +from pydantic import BaseModel, PositiveFloat, ValidationError + +class Model(BaseModel): + positive_float: PositiveFloat + +m = Model(positive_float=1.0) +print(repr(m)) +#> Model(positive_float=1.0) + +try: + Model(positive_float=-1.0) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('positive_float',), + 'msg': 'Input should be greater than 0', + 'input': -1.0, + 'ctx': {'gt': 0.0}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than', + } + ] + ''' +``` +""" +NegativeFloat = Annotated[float, annotated_types.Lt(0)] +"""A float that must be less than zero. + +```python +from pydantic import BaseModel, NegativeFloat, ValidationError + +class Model(BaseModel): + negative_float: NegativeFloat + +m = Model(negative_float=-1.0) +print(repr(m)) +#> Model(negative_float=-1.0) + +try: + Model(negative_float=1.0) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'less_than', + 'loc': ('negative_float',), + 'msg': 'Input should be less than 0', + 'input': 1.0, + 'ctx': {'lt': 0.0}, + 'url': 'https://errors.pydantic.dev/2/v/less_than', + } + ] + ''' +``` +""" +NonPositiveFloat = Annotated[float, annotated_types.Le(0)] +"""A float that must be less than or equal to zero. + +```python +from pydantic import BaseModel, NonPositiveFloat, ValidationError + +class Model(BaseModel): + non_positive_float: NonPositiveFloat + +m = Model(non_positive_float=0.0) +print(repr(m)) +#> Model(non_positive_float=0.0) + +try: + Model(non_positive_float=1.0) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'less_than_equal', + 'loc': ('non_positive_float',), + 'msg': 'Input should be less than or equal to 0', + 'input': 1.0, + 'ctx': {'le': 0.0}, + 'url': 'https://errors.pydantic.dev/2/v/less_than_equal', + } + ] + ''' +``` +""" +NonNegativeFloat = Annotated[float, annotated_types.Ge(0)] +"""A float that must be greater than or equal to zero. + +```python +from pydantic import BaseModel, NonNegativeFloat, ValidationError + +class Model(BaseModel): + non_negative_float: NonNegativeFloat + +m = Model(non_negative_float=0.0) +print(repr(m)) +#> Model(non_negative_float=0.0) + +try: + Model(non_negative_float=-1.0) +except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than_equal', + 'loc': ('non_negative_float',), + 'msg': 'Input should be greater than or equal to 0', + 'input': -1.0, + 'ctx': {'ge': 0.0}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than_equal', + } + ] + ''' +``` +""" +StrictFloat = Annotated[float, Strict(True)] +"""A float that must be validated in strict mode. + +```python +from pydantic import BaseModel, StrictFloat, ValidationError + +class StrictFloatModel(BaseModel): + strict_float: StrictFloat + +try: + StrictFloatModel(strict_float='1.0') +except ValidationError as e: + print(e) + ''' + 1 validation error for StrictFloatModel + strict_float + Input should be a valid number [type=float_type, input_value='1.0', input_type=str] + ''' +``` +""" +FiniteFloat = Annotated[float, AllowInfNan(False)] +"""A float that must be finite (not ``-inf``, ``inf``, or ``nan``). + +```python +from pydantic import BaseModel, FiniteFloat + +class Model(BaseModel): + finite: FiniteFloat + +m = Model(finite=1.0) +print(m) +#> finite=1.0 +``` +""" + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTES TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def conbytes( + *, + min_length: int | None = None, + max_length: int | None = None, + strict: bool | None = None, +) -> type[bytes]: + """A wrapper around `bytes` that allows for additional constraints. + + Args: + min_length: The minimum length of the bytes. + max_length: The maximum length of the bytes. + strict: Whether to validate the bytes in strict mode. + + Returns: + The wrapped bytes type. + """ + return Annotated[ # pyright: ignore[reportReturnType] + bytes, + Strict(strict) if strict is not None else None, + annotated_types.Len(min_length or 0, max_length), + ] + + +StrictBytes = Annotated[bytes, Strict()] +"""A bytes that must be validated in strict mode.""" + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ STRING TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +@_dataclasses.dataclass(frozen=True) +class StringConstraints(annotated_types.GroupedMetadata): + """!!! abstract "Usage Documentation" + [String types](./standard_library_types.md#strings) + + A field metadata class to apply constraints to `str` types. + Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. + + Attributes: + strip_whitespace: Whether to remove leading and trailing whitespace. + to_upper: Whether to convert the string to uppercase. + to_lower: Whether to convert the string to lowercase. + strict: Whether to validate the string in strict mode. + min_length: The minimum length of the string. + max_length: The maximum length of the string. + pattern: A regex pattern that the string must match. + ascii_only: Whether the string should contain only ASCII characters. + + Example: + ```python + from typing import Annotated + + from pydantic.types import StringConstraints + + ConstrainedStr = Annotated[str, StringConstraints(min_length=1, max_length=10)] + ``` + """ + + strip_whitespace: bool | None = None + to_upper: bool | None = None + to_lower: bool | None = None + strict: bool | None = None + min_length: int | None = None + max_length: int | None = None + pattern: str | Pattern[str] | None = None + ascii_only: bool | None = None + + def __iter__(self) -> Iterator[BaseMetadata]: + if self.min_length is not None: + yield MinLen(self.min_length) + if self.max_length is not None: + yield MaxLen(self.max_length) + if self.strict is not None: + yield Strict(self.strict) + if ( + self.strip_whitespace is not None + or self.pattern is not None + or self.to_lower is not None + or self.to_upper is not None + or self.ascii_only is not None + ): + yield _fields.pydantic_general_metadata( + strip_whitespace=self.strip_whitespace, + to_upper=self.to_upper, + to_lower=self.to_lower, + pattern=self.pattern, + ascii_only=self.ascii_only, + ) + + +def constr( + *, + strip_whitespace: bool | None = None, + to_upper: bool | None = None, + to_lower: bool | None = None, + strict: bool | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | Pattern[str] | None = None, + ascii_only: bool | None = None, +) -> type[str]: + """ + !!! warning "Discouraged" + This function is **discouraged** in favor of using + [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) with + [`StringConstraints`][pydantic.types.StringConstraints] instead. + + This function will be **deprecated** in Pydantic 3.0. + + The reason is that `constr` returns a type, which doesn't play well with static analysis tools. + + === ":x: Don't do this" + ```python + from pydantic import BaseModel, constr + + class Foo(BaseModel): + bar: constr(strip_whitespace=True, to_upper=True, pattern=r'^[A-Z]+$') + ``` + + === ":white_check_mark: Do this" + ```python + from typing import Annotated + + from pydantic import BaseModel, StringConstraints + + class Foo(BaseModel): + bar: Annotated[ + str, + StringConstraints( + strip_whitespace=True, to_upper=True, pattern=r'^[A-Z]+$' + ), + ] + ``` + + A wrapper around `str` that allows for additional constraints. + + ```python + from pydantic import BaseModel, constr + + class Foo(BaseModel): + bar: constr(strip_whitespace=True, to_upper=True) + + foo = Foo(bar=' hello ') + print(foo) + #> bar='HELLO' + ``` + + Args: + strip_whitespace: Whether to remove leading and trailing whitespace. + to_upper: Whether to turn all characters to uppercase. + to_lower: Whether to turn all characters to lowercase. + strict: Whether to validate the string in strict mode. + min_length: The minimum length of the string. + max_length: The maximum length of the string. + pattern: A regex pattern to validate the string against. + ascii_only: Whether the string should contain only ASCII characters. + + Returns: + The wrapped string type. + """ # noqa: D212 + return Annotated[ # pyright: ignore[reportReturnType] + str, + StringConstraints( + strip_whitespace=strip_whitespace, + to_upper=to_upper, + to_lower=to_lower, + strict=strict, + min_length=min_length, + max_length=max_length, + pattern=pattern, + ascii_only=ascii_only, + ), + ] + + +StrictStr = Annotated[str, Strict()] +"""A string that must be validated in strict mode.""" + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ COLLECTION TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +HashableItemType = TypeVar('HashableItemType', bound=Hashable) + + +def conset( + item_type: type[HashableItemType], *, min_length: int | None = None, max_length: int | None = None +) -> type[set[HashableItemType]]: + """A wrapper around `typing.Set` that allows for additional constraints. + + Args: + item_type: The type of the items in the set. + min_length: The minimum length of the set. + max_length: The maximum length of the set. + + Returns: + The wrapped set type. + """ + return Annotated[set[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + + +def confrozenset( + item_type: type[HashableItemType], *, min_length: int | None = None, max_length: int | None = None +) -> type[frozenset[HashableItemType]]: + """A wrapper around `typing.FrozenSet` that allows for additional constraints. + + Args: + item_type: The type of the items in the frozenset. + min_length: The minimum length of the frozenset. + max_length: The maximum length of the frozenset. + + Returns: + The wrapped frozenset type. + """ + return Annotated[frozenset[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + + +AnyItemType = TypeVar('AnyItemType') + + +def conlist( + item_type: type[AnyItemType], + *, + min_length: int | None = None, + max_length: int | None = None, + unique_items: bool | None = None, +) -> type[list[AnyItemType]]: + """A wrapper around [`list`][] that adds validation. + + Args: + item_type: The type of the items in the list. + min_length: The minimum length of the list. Defaults to None. + max_length: The maximum length of the list. Defaults to None. + unique_items: Whether the items in the list must be unique. Defaults to None. + !!! warning Deprecated + The `unique_items` parameter is deprecated, use `Set` instead. + See [this issue](https://github.com/pydantic/pydantic-core/issues/296) for more details. + + Returns: + The wrapped list type. + """ + if unique_items is not None: + raise PydanticUserError( + ( + '`unique_items` is removed, use `Set` instead' + '(this feature is discussed in https://github.com/pydantic/pydantic-core/issues/296)' + ), + code='removed-kwargs', + ) + return Annotated[list[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT STRING TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +AnyType = TypeVar('AnyType') +if TYPE_CHECKING: + ImportString = Annotated[AnyType, ...] +else: + + class ImportString: + """A type that can be used to import a Python object from a string. + + `ImportString` expects a string and loads the Python object importable at that dotted path. + Attributes of modules may be separated from the module by `:` or `.`, e.g. if `'math:cos'` is provided, + the resulting field value would be the function `cos`. If a `.` is used and both an attribute and submodule + are present at the same path, the module will be preferred. + + On model instantiation, pointers will be evaluated and imported. There is + some nuance to this behavior, demonstrated in the examples below. + + ```python + import math + + from pydantic import BaseModel, Field, ImportString, ValidationError + + class ImportThings(BaseModel): + obj: ImportString + + # A string value will cause an automatic import + my_cos = ImportThings(obj='math.cos') + + # You can use the imported function as you would expect + cos_of_0 = my_cos.obj(0) + assert cos_of_0 == 1 + + # A string whose value cannot be imported will raise an error + try: + ImportThings(obj='foo.bar') + except ValidationError as e: + print(e) + ''' + 1 validation error for ImportThings + obj + Invalid python path: No module named 'foo' [type=import_error, input_value='foo.bar', input_type=str] + ''' + + # Actual python objects can be assigned as well + my_cos = ImportThings(obj=math.cos) + my_cos_2 = ImportThings(obj='math.cos') + my_cos_3 = ImportThings(obj='math:cos') + assert my_cos == my_cos_2 == my_cos_3 + + # You can set default field value either as Python object: + class ImportThingsDefaultPyObj(BaseModel): + obj: ImportString = math.cos + + # or as a string value (but only if used with `validate_default=True`) + class ImportThingsDefaultString(BaseModel): + obj: ImportString = Field(default='math.cos', validate_default=True) + + my_cos_default1 = ImportThingsDefaultPyObj() + my_cos_default2 = ImportThingsDefaultString() + assert my_cos_default1.obj == my_cos_default2.obj == math.cos + + # note: this will not work! + class ImportThingsMissingValidateDefault(BaseModel): + obj: ImportString = 'math.cos' + + my_cos_default3 = ImportThingsMissingValidateDefault() + assert my_cos_default3.obj == 'math.cos' # just string, not evaluated + ``` + + Serializing an `ImportString` type to json is also possible. + + ```python + from pydantic import BaseModel, ImportString + + class ImportThings(BaseModel): + obj: ImportString + + # Create an instance + m = ImportThings(obj='math.cos') + print(m) + #> obj= + print(m.model_dump_json()) + #> {"obj":"math.cos"} + ``` + """ + + @classmethod + def __class_getitem__(cls, item: AnyType) -> AnyType: + return Annotated[item, cls()] + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + serializer = core_schema.plain_serializer_function_ser_schema(cls._serialize, when_used='json') + if cls is source: + # Treat bare usage of ImportString (`schema is None`) as the same as ImportString[Any] + return core_schema.no_info_plain_validator_function( + function=_validators.import_string, serialization=serializer + ) + else: + return core_schema.no_info_before_validator_function( + function=_validators.import_string, schema=handler(source), serialization=serializer + ) + + @classmethod + def __get_pydantic_json_schema__(cls, cs: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + return handler(core_schema.str_schema()) + + @staticmethod + def _serialize(v: Any) -> str: + if isinstance(v, ModuleType): + return v.__name__ + elif hasattr(v, '__module__') and hasattr(v, '__name__'): + return f'{v.__module__}.{v.__name__}' + # Handle special cases for sys.XXX streams + # if we see more of these, we should consider a more general solution + elif hasattr(v, 'name'): + if v.name == '': + return 'sys.stdout' + elif v.name == '': + return 'sys.stdin' + elif v.name == '': + return 'sys.stderr' + return v + + def __repr__(self) -> str: + return 'ImportString' + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DECIMAL TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def condecimal( + *, + strict: bool | None = None, + gt: int | Decimal | None = None, + ge: int | Decimal | None = None, + lt: int | Decimal | None = None, + le: int | Decimal | None = None, + multiple_of: int | Decimal | None = None, + max_digits: int | None = None, + decimal_places: int | None = None, + allow_inf_nan: bool | None = None, +) -> type[Decimal]: + """ + !!! warning "Discouraged" + This function is **discouraged** in favor of using + [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) with + [`Field`][pydantic.fields.Field] instead. + + This function will be **deprecated** in Pydantic 3.0. + + The reason is that `condecimal` returns a type, which doesn't play well with static analysis tools. + + === ":x: Don't do this" + ```python + from pydantic import BaseModel, condecimal + + class Foo(BaseModel): + bar: condecimal(strict=True, allow_inf_nan=True) + ``` + + === ":white_check_mark: Do this" + ```python + from decimal import Decimal + from typing import Annotated + + from pydantic import BaseModel, Field + + class Foo(BaseModel): + bar: Annotated[Decimal, Field(strict=True, allow_inf_nan=True)] + ``` + + A wrapper around Decimal that adds validation. + + Args: + strict: Whether to validate the value in strict mode. Defaults to `None`. + gt: The value must be greater than this. Defaults to `None`. + ge: The value must be greater than or equal to this. Defaults to `None`. + lt: The value must be less than this. Defaults to `None`. + le: The value must be less than or equal to this. Defaults to `None`. + multiple_of: The value must be a multiple of this. Defaults to `None`. + max_digits: The maximum number of digits. Defaults to `None`. + decimal_places: The number of decimal places. Defaults to `None`. + allow_inf_nan: Whether to allow infinity and NaN. Defaults to `None`. + + ```python + from decimal import Decimal + + from pydantic import BaseModel, ValidationError, condecimal + + class ConstrainedExample(BaseModel): + constrained_decimal: condecimal(gt=Decimal('1.0')) + + m = ConstrainedExample(constrained_decimal=Decimal('1.1')) + print(repr(m)) + #> ConstrainedExample(constrained_decimal=Decimal('1.1')) + + try: + ConstrainedExample(constrained_decimal=Decimal('0.9')) + except ValidationError as e: + print(e.errors()) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('constrained_decimal',), + 'msg': 'Input should be greater than 1.0', + 'input': Decimal('0.9'), + 'ctx': {'gt': Decimal('1.0')}, + 'url': 'https://errors.pydantic.dev/2/v/greater_than', + } + ] + ''' + ``` + """ # noqa: D212 + return Annotated[ # pyright: ignore[reportReturnType] + Decimal, + Strict(strict) if strict is not None else None, + annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), + annotated_types.MultipleOf(multiple_of) if multiple_of is not None else None, + _fields.pydantic_general_metadata(max_digits=max_digits, decimal_places=decimal_places), + AllowInfNan(allow_inf_nan) if allow_inf_nan is not None else None, + ] + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UUID TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true) +class UuidVersion: + """A field metadata class to indicate a [UUID](https://docs.python.org/3/library/uuid.html) version. + + Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. + + Attributes: + uuid_version: The version of the UUID. Must be one of 1, 3, 4, 5, 6, 7 or 8. + + Example: + ```python + from typing import Annotated + from uuid import UUID + + from pydantic.types import UuidVersion + + UUID1 = Annotated[UUID, UuidVersion(1)] + ``` + """ + + uuid_version: Literal[1, 3, 4, 5, 6, 7, 8] + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + field_schema.pop('anyOf', None) # remove the bytes/str union + field_schema.update(type='string', format=f'uuid{self.uuid_version}') + return field_schema + + def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source) + _check_annotated_type(schema['type'], 'uuid', self.__class__.__name__) + schema['version'] = self.uuid_version # type: ignore + return schema + + def __hash__(self) -> int: + return hash(self.uuid_version) + + +UUID1 = Annotated[UUID, UuidVersion(1)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 1. + +```python +import uuid + +from pydantic import UUID1, BaseModel + +class Model(BaseModel): + uuid1: UUID1 + +Model(uuid1=uuid.uuid1()) +``` +""" +UUID3 = Annotated[UUID, UuidVersion(3)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 3. + +```python +import uuid + +from pydantic import UUID3, BaseModel + +class Model(BaseModel): + uuid3: UUID3 + +Model(uuid3=uuid.uuid3(uuid.NAMESPACE_DNS, 'pydantic.org')) +``` +""" +UUID4 = Annotated[UUID, UuidVersion(4)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 4. + +```python +import uuid + +from pydantic import UUID4, BaseModel + +class Model(BaseModel): + uuid4: UUID4 + +Model(uuid4=uuid.uuid4()) +``` +""" +UUID5 = Annotated[UUID, UuidVersion(5)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 5. + +```python +import uuid + +from pydantic import UUID5, BaseModel + +class Model(BaseModel): + uuid5: UUID5 + +Model(uuid5=uuid.uuid5(uuid.NAMESPACE_DNS, 'pydantic.org')) +``` +""" +UUID6 = Annotated[UUID, UuidVersion(6)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 6. + +```python +import uuid + +from pydantic import UUID6, BaseModel + +class Model(BaseModel): + uuid6: UUID6 + +Model(uuid6=uuid.UUID('1efea953-c2d6-6790-aa0a-69db8c87df97')) +``` +""" +UUID7 = Annotated[UUID, UuidVersion(7)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 7. + +```python +import uuid + +from pydantic import UUID7, BaseModel + +class Model(BaseModel): + uuid7: UUID7 + +Model(uuid7=uuid.UUID('0194fdcb-1c47-7a09-b52c-561154de0b4a')) +``` +""" +UUID8 = Annotated[UUID, UuidVersion(8)] +"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 8. + +```python +import uuid + +from pydantic import UUID8, BaseModel + +class Model(BaseModel): + uuid8: UUID8 + +Model(uuid8=uuid.UUID('81a0b92e-6078-8551-9c81-8ccb666bdab8')) +``` +""" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PATH TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +@_dataclasses.dataclass +class PathType: + path_type: Literal['file', 'dir', 'new', 'socket'] + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + format_conversion = {'file': 'file-path', 'dir': 'directory-path'} + field_schema.update(format=format_conversion.get(self.path_type, 'path'), type='string') + return field_schema + + def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + function_lookup = { + 'file': cast(core_schema.WithInfoValidatorFunction, self.validate_file), + 'dir': cast(core_schema.WithInfoValidatorFunction, self.validate_directory), + 'new': cast(core_schema.WithInfoValidatorFunction, self.validate_new), + 'socket': cast(core_schema.WithInfoValidatorFunction, self.validate_socket), + } + + return core_schema.with_info_after_validator_function( + function_lookup[self.path_type], + handler(source), + ) + + @staticmethod + def validate_file(path: Path, _: core_schema.ValidationInfo) -> Path: + if path.is_file(): + return path + else: + raise PydanticCustomError('path_not_file', 'Path does not point to a file') + + @staticmethod + def validate_socket(path: Path, _: core_schema.ValidationInfo) -> Path: + if path.is_socket(): + return path + else: + raise PydanticCustomError('path_not_socket', 'Path does not point to a socket') + + @staticmethod + def validate_directory(path: Path, _: core_schema.ValidationInfo) -> Path: + if path.is_dir(): + return path + else: + raise PydanticCustomError('path_not_directory', 'Path does not point to a directory') + + @staticmethod + def validate_new(path: Path, _: core_schema.ValidationInfo) -> Path: + if path.exists(): + raise PydanticCustomError('path_exists', 'Path already exists') + elif not path.parent.exists(): + raise PydanticCustomError('parent_does_not_exist', 'Parent directory does not exist') + else: + return path + + def __hash__(self) -> int: + return hash(self.path_type) + + +FilePath = Annotated[Path, PathType('file')] +"""A path that must point to a file. + +```python +from pathlib import Path + +from pydantic import BaseModel, FilePath, ValidationError + +class Model(BaseModel): + f: FilePath + +path = Path('text.txt') +path.touch() +m = Model(f='text.txt') +print(m.model_dump()) +#> {'f': PosixPath('text.txt')} +path.unlink() + +path = Path('directory') +path.mkdir(exist_ok=True) +try: + Model(f='directory') # directory +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + f + Path does not point to a file [type=path_not_file, input_value='directory', input_type=str] + ''' +path.rmdir() + +try: + Model(f='not-exists-file') +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + f + Path does not point to a file [type=path_not_file, input_value='not-exists-file', input_type=str] + ''' +``` +""" +DirectoryPath = Annotated[Path, PathType('dir')] +"""A path that must point to a directory. + +```python +from pathlib import Path + +from pydantic import BaseModel, DirectoryPath, ValidationError + +class Model(BaseModel): + f: DirectoryPath + +path = Path('directory/') +path.mkdir() +m = Model(f='directory/') +print(m.model_dump()) +#> {'f': PosixPath('directory')} +path.rmdir() + +path = Path('file.txt') +path.touch() +try: + Model(f='file.txt') # file +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + f + Path does not point to a directory [type=path_not_directory, input_value='file.txt', input_type=str] + ''' +path.unlink() + +try: + Model(f='not-exists-directory') +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + f + Path does not point to a directory [type=path_not_directory, input_value='not-exists-directory', input_type=str] + ''' +``` +""" +NewPath = Annotated[Path, PathType('new')] +"""A path for a new file or directory that must not already exist. The parent directory must already exist.""" + +SocketPath = Annotated[Path, PathType('socket')] +"""A path to an existing socket file""" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + # Json[list[str]] will be recognized by type checkers as list[str] + Json = Annotated[AnyType, ...] + +else: + + class Json: + """A special type wrapper which loads JSON before parsing. + + You can use the `Json` data type to make Pydantic first load a raw JSON string before + validating the loaded data into the parametrized type: + + ```python + from typing import Any + + from pydantic import BaseModel, Json, ValidationError + + class AnyJsonModel(BaseModel): + json_obj: Json[Any] + + class ConstrainedJsonModel(BaseModel): + json_obj: Json[list[int]] + + print(AnyJsonModel(json_obj='{"b": 1}')) + #> json_obj={'b': 1} + print(ConstrainedJsonModel(json_obj='[1, 2, 3]')) + #> json_obj=[1, 2, 3] + + try: + ConstrainedJsonModel(json_obj=12) + except ValidationError as e: + print(e) + ''' + 1 validation error for ConstrainedJsonModel + json_obj + JSON input should be string, bytes or bytearray [type=json_type, input_value=12, input_type=int] + ''' + + try: + ConstrainedJsonModel(json_obj='[a, b]') + except ValidationError as e: + print(e) + ''' + 1 validation error for ConstrainedJsonModel + json_obj + Invalid JSON: expected value at line 1 column 2 [type=json_invalid, input_value='[a, b]', input_type=str] + ''' + + try: + ConstrainedJsonModel(json_obj='["a", "b"]') + except ValidationError as e: + print(e) + ''' + 2 validation errors for ConstrainedJsonModel + json_obj.0 + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] + json_obj.1 + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='b', input_type=str] + ''' + ``` + + When you dump the model using `model_dump` or `model_dump_json`, the dumped value will be the result of validation, + not the original JSON string. However, you can use the argument `round_trip=True` to get the original JSON string back: + + ```python + from pydantic import BaseModel, Json + + class ConstrainedJsonModel(BaseModel): + json_obj: Json[list[int]] + + print(ConstrainedJsonModel(json_obj='[1, 2, 3]').model_dump_json()) + #> {"json_obj":[1,2,3]} + print( + ConstrainedJsonModel(json_obj='[1, 2, 3]').model_dump_json(round_trip=True) + ) + #> {"json_obj":"[1,2,3]"} + ``` + """ + + @classmethod + def __class_getitem__(cls, item: AnyType) -> AnyType: + return Annotated[item, cls()] + + @classmethod + def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + if cls is source: + return core_schema.json_schema(None) + else: + return core_schema.json_schema(handler(source)) + + def __repr__(self) -> str: + return 'Json' + + def __hash__(self) -> int: + return hash(type(self)) + + def __eq__(self, other: Any) -> bool: + return type(other) is type(self) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SECRET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# The `Secret` class being conceptually immutable, make the type variable covariant: +SecretType = TypeVar('SecretType', covariant=True) + + +class _SecretBase(Generic[SecretType]): + def __init__(self, secret_value: SecretType) -> None: + self._secret_value: SecretType = secret_value + + def get_secret_value(self) -> SecretType: + """Get the secret value. + + Returns: + The secret value. + """ + return self._secret_value + + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) and self.get_secret_value() == other.get_secret_value() + + def __hash__(self) -> int: + return hash(self.get_secret_value()) + + def __str__(self) -> str: + return str(self._display()) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self._display()!r})' + + def _display(self) -> str | bytes: + raise NotImplementedError + + +def _serialize_secret(value: Secret[SecretType], info: core_schema.SerializationInfo) -> str | Secret[SecretType]: + if info.mode == 'json': + return str(value) + else: + return value + + +class Secret(_SecretBase[SecretType]): + """A generic base class used for defining a field with sensitive information that you do not want to be visible in logging or tracebacks. + + You may either directly parametrize `Secret` with a type, or subclass from `Secret` with a parametrized type. The benefit of subclassing + is that you can define a custom `_display` method, which will be used for `repr()` and `str()` methods. The examples below demonstrate both + ways of using `Secret` to create a new secret type. + + 1. Directly parametrizing `Secret` with a type: + + ```python + from pydantic import BaseModel, Secret + + SecretBool = Secret[bool] + + class Model(BaseModel): + secret_bool: SecretBool + + m = Model(secret_bool=True) + print(m.model_dump()) + #> {'secret_bool': Secret('**********')} + + print(m.model_dump_json()) + #> {"secret_bool":"**********"} + + print(m.secret_bool.get_secret_value()) + #> True + ``` + + 2. Subclassing from parametrized `Secret`: + + ```python + from datetime import date + + from pydantic import BaseModel, Secret + + class SecretDate(Secret[date]): + def _display(self) -> str: + return '****/**/**' + + class Model(BaseModel): + secret_date: SecretDate + + m = Model(secret_date=date(2022, 1, 1)) + print(m.model_dump()) + #> {'secret_date': SecretDate('****/**/**')} + + print(m.model_dump_json()) + #> {"secret_date":"****/**/**"} + + print(m.secret_date.get_secret_value()) + #> 2022-01-01 + ``` + + The value returned by the `_display` method will be used for `repr()` and `str()`. + + You can enforce constraints on the underlying type through annotations: + For example: + + ```python + from typing import Annotated + + from pydantic import BaseModel, Field, Secret, ValidationError + + SecretPosInt = Secret[Annotated[int, Field(gt=0, strict=True)]] + + class Model(BaseModel): + sensitive_int: SecretPosInt + + m = Model(sensitive_int=42) + print(m.model_dump()) + #> {'sensitive_int': Secret('**********')} + + try: + m = Model(sensitive_int=-42) # (1)! + except ValidationError as exc_info: + print(exc_info.errors(include_url=False, include_input=False)) + ''' + [ + { + 'type': 'greater_than', + 'loc': ('sensitive_int',), + 'msg': 'Input should be greater than 0', + 'ctx': {'gt': 0}, + } + ] + ''' + + try: + m = Model(sensitive_int='42') # (2)! + except ValidationError as exc_info: + print(exc_info.errors(include_url=False, include_input=False)) + ''' + [ + { + 'type': 'int_type', + 'loc': ('sensitive_int',), + 'msg': 'Input should be a valid integer', + } + ] + ''' + ``` + + 1. The input value is not greater than 0, so it raises a validation error. + 2. The input value is not an integer, so it raises a validation error because the `SecretPosInt` type has strict mode enabled. + """ + + def _display(self) -> str | bytes: + return '**********' if self.get_secret_value() else '' + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + inner_type = None + # if origin_type is Secret, then cls is a GenericAlias, and we can extract the inner type directly + origin_type = get_origin(source) + if origin_type is not None: + inner_type = get_args(source)[0] + # otherwise, we need to get the inner type from the base class + else: + bases = getattr(cls, '__orig_bases__', getattr(cls, '__bases__', [])) + for base in bases: + if get_origin(base) is Secret: + inner_type = get_args(base)[0] + if bases == [] or inner_type is None: + raise TypeError( + f"Can't get secret type from {cls.__name__}. " + 'Please use Secret[], or subclass from Secret[] instead.' + ) + + inner_schema = handler.generate_schema(inner_type) # type: ignore + + def validate_secret_value(value, handler) -> Secret[SecretType]: + if isinstance(value, Secret): + value = value.get_secret_value() + validated_inner = handler(value) + return cls(validated_inner) + + return core_schema.json_or_python_schema( + python_schema=core_schema.no_info_wrap_validator_function( + validate_secret_value, + inner_schema, + ), + json_schema=core_schema.no_info_after_validator_function(lambda x: cls(x), inner_schema), + serialization=core_schema.plain_serializer_function_ser_schema( + _serialize_secret, + info_arg=True, + when_used='always', + ), + ) + + __pydantic_serializer__ = SchemaSerializer( + core_schema.any_schema( + serialization=core_schema.plain_serializer_function_ser_schema( + _serialize_secret, + info_arg=True, + when_used='always', + ) + ) + ) + + +def _secret_display(value: SecretType) -> str: # type: ignore + return '**********' if value else '' + + +def _serialize_secret_field( + value: _SecretField[SecretType], info: core_schema.SerializationInfo +) -> str | _SecretField[SecretType]: + if info.mode == 'json': + # we want the output to always be string without the `b'` prefix for bytes, + # hence we just use `secret_display` + return _secret_display(value.get_secret_value()) + else: + return value + + +class _SecretField(_SecretBase[SecretType]): + _inner_schema: ClassVar[CoreSchema] + _error_kind: ClassVar[str] + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + def get_json_schema(_core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + json_schema = handler(cls._inner_schema) + _utils.update_not_none( + json_schema, + type='string', + writeOnly=True, + format='password', + ) + return json_schema + + def get_secret_schema(strict: bool) -> CoreSchema: + inner_schema = {**cls._inner_schema, 'strict': strict} + json_schema = core_schema.no_info_after_validator_function( + source, # construct the type + inner_schema, # pyright: ignore[reportArgumentType] + ) + return core_schema.json_or_python_schema( + python_schema=core_schema.union_schema( + [ + core_schema.is_instance_schema(source), + json_schema, + ], + custom_error_type=cls._error_kind, + ), + json_schema=json_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + _serialize_secret_field, + info_arg=True, + when_used='always', + ), + ) + + return core_schema.lax_or_strict_schema( + lax_schema=get_secret_schema(strict=False), + strict_schema=get_secret_schema(strict=True), + metadata={'pydantic_js_functions': [get_json_schema]}, + ) + + __pydantic_serializer__ = SchemaSerializer( + core_schema.any_schema( + serialization=core_schema.plain_serializer_function_ser_schema( + _serialize_secret_field, + info_arg=True, + when_used='always', + ) + ) + ) + + +class SecretStr(_SecretField[str]): + """A string used for storing sensitive information that you do not want to be visible in logging or tracebacks. + + When the secret value is nonempty, it is displayed as `'**********'` instead of the underlying value in + calls to `repr()` and `str()`. If the value _is_ empty, it is displayed as `''`. + + ```python + from pydantic import BaseModel, SecretStr + + class User(BaseModel): + username: str + password: SecretStr + + user = User(username='scolvin', password='password1') + + print(user) + #> username='scolvin' password=SecretStr('**********') + print(user.password.get_secret_value()) + #> password1 + print((SecretStr('password'), SecretStr(''))) + #> (SecretStr('**********'), SecretStr('')) + ``` + + As seen above, by default, [`SecretStr`][pydantic.types.SecretStr] (and [`SecretBytes`][pydantic.types.SecretBytes]) + will be serialized as `**********` when serializing to json. + + You can use the [`field_serializer`][pydantic.functional_serializers.field_serializer] to dump the + secret as plain-text when serializing to json. + + ```python + from pydantic import BaseModel, SecretBytes, SecretStr, field_serializer + + class Model(BaseModel): + password: SecretStr + password_bytes: SecretBytes + + @field_serializer('password', 'password_bytes', when_used='json') + def dump_secret(self, v): + return v.get_secret_value() + + model = Model(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes') + print(model) + #> password=SecretStr('**********') password_bytes=SecretBytes(b'**********') + print(model.password) + #> ********** + print(model.model_dump()) + ''' + { + 'password': SecretStr('**********'), + 'password_bytes': SecretBytes(b'**********'), + } + ''' + print(model.model_dump_json()) + #> {"password":"IAmSensitive","password_bytes":"IAmSensitiveBytes"} + ``` + """ + + _inner_schema: ClassVar[CoreSchema] = core_schema.str_schema() + _error_kind: ClassVar[str] = 'string_type' + + def __len__(self) -> int: + return len(self._secret_value) + + def _display(self) -> str: + return _secret_display(self._secret_value) + + +class SecretBytes(_SecretField[bytes]): + """A bytes used for storing sensitive information that you do not want to be visible in logging or tracebacks. + + It displays `b'**********'` instead of the string value on `repr()` and `str()` calls. + When the secret value is nonempty, it is displayed as `b'**********'` instead of the underlying value in + calls to `repr()` and `str()`. If the value _is_ empty, it is displayed as `b''`. + + ```python + from pydantic import BaseModel, SecretBytes + + class User(BaseModel): + username: str + password: SecretBytes + + user = User(username='scolvin', password=b'password1') + #> username='scolvin' password=SecretBytes(b'**********') + print(user.password.get_secret_value()) + #> b'password1' + print((SecretBytes(b'password'), SecretBytes(b''))) + #> (SecretBytes(b'**********'), SecretBytes(b'')) + ``` + """ + + _inner_schema: ClassVar[CoreSchema] = core_schema.bytes_schema() + _error_kind: ClassVar[str] = 'bytes_type' + + def __len__(self) -> int: + return len(self._secret_value) + + def _display(self) -> bytes: + return _secret_display(self._secret_value).encode() + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PAYMENT CARD TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class PaymentCardBrand(str, Enum): + amex = 'American Express' + mastercard = 'Mastercard' + visa = 'Visa' + other = 'other' + + def __str__(self) -> str: + return self.value + + +@deprecated( + 'The `PaymentCardNumber` class is deprecated, use `pydantic_extra_types` instead. ' + 'See https://docs.pydantic.dev/latest/api/pydantic_extra_types_payment/#pydantic_extra_types.payment.PaymentCardNumber.', + category=PydanticDeprecatedSince20, +) +class PaymentCardNumber(str): + """Based on: https://en.wikipedia.org/wiki/Payment_card_number.""" + + strip_whitespace: ClassVar[bool] = True + min_length: ClassVar[int] = 12 + max_length: ClassVar[int] = 19 + bin: str + last4: str + brand: PaymentCardBrand + + def __init__(self, card_number: str): + self.validate_digits(card_number) + + card_number = self.validate_luhn_check_digit(card_number) + + self.bin = card_number[:6] + self.last4 = card_number[-4:] + self.brand = self.validate_brand(card_number) + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + return core_schema.with_info_after_validator_function( + cls.validate, + core_schema.str_schema( + min_length=cls.min_length, max_length=cls.max_length, strip_whitespace=cls.strip_whitespace + ), + ) + + @classmethod + def validate(cls, input_value: str, /, _: core_schema.ValidationInfo) -> PaymentCardNumber: + """Validate the card number and return a `PaymentCardNumber` instance.""" + return cls(input_value) + + @property + def masked(self) -> str: + """Mask all but the last 4 digits of the card number. + + Returns: + A masked card number string. + """ + num_masked = len(self) - 10 # len(bin) + len(last4) == 10 + return f'{self.bin}{"*" * num_masked}{self.last4}' + + @classmethod + def validate_digits(cls, card_number: str) -> None: + """Validate that the card number is all digits.""" + if not card_number.isdigit(): + raise PydanticCustomError('payment_card_number_digits', 'Card number is not all digits') + + @classmethod + def validate_luhn_check_digit(cls, card_number: str) -> str: + """Based on: https://en.wikipedia.org/wiki/Luhn_algorithm.""" + sum_ = int(card_number[-1]) + length = len(card_number) + parity = length % 2 + for i in range(length - 1): + digit = int(card_number[i]) + if i % 2 == parity: + digit *= 2 + if digit > 9: + digit -= 9 + sum_ += digit + valid = sum_ % 10 == 0 + if not valid: + raise PydanticCustomError('payment_card_number_luhn', 'Card number is not luhn valid') + return card_number + + @staticmethod + def validate_brand(card_number: str) -> PaymentCardBrand: + """Validate length based on BIN for major brands: + https://en.wikipedia.org/wiki/Payment_card_number#Issuer_identification_number_(IIN). + """ + if card_number[0] == '4': + brand = PaymentCardBrand.visa + elif 51 <= int(card_number[:2]) <= 55: + brand = PaymentCardBrand.mastercard + elif card_number[:2] in {'34', '37'}: + brand = PaymentCardBrand.amex + else: + brand = PaymentCardBrand.other + + required_length: None | int | str = None + if brand in PaymentCardBrand.mastercard: + required_length = 16 + valid = len(card_number) == required_length + elif brand == PaymentCardBrand.visa: + required_length = '13, 16 or 19' + valid = len(card_number) in {13, 16, 19} + elif brand == PaymentCardBrand.amex: + required_length = 15 + valid = len(card_number) == required_length + else: + valid = True + + if not valid: + raise PydanticCustomError( + 'payment_card_number_brand', + 'Length for a {brand} card must be {required_length}', + {'brand': brand, 'required_length': required_length}, + ) + return brand + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTE SIZE TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ByteSize(int): + """Converts a string representing a number of bytes with units (such as `'1KB'` or `'11.5MiB'`) into an integer. + + You can use the `ByteSize` data type to (case-insensitively) convert a string representation of a number of bytes into + an integer, and also to print out human-readable strings representing a number of bytes. + + In conformance with [IEC 80000-13 Standard](https://en.wikipedia.org/wiki/ISO/IEC_80000) we interpret `'1KB'` to mean 1000 bytes, + and `'1KiB'` to mean 1024 bytes. In general, including a middle `'i'` will cause the unit to be interpreted as a power of 2, + rather than a power of 10 (so, for example, `'1 MB'` is treated as `1_000_000` bytes, whereas `'1 MiB'` is treated as `1_048_576` bytes). + + !!! info + Note that `1b` will be parsed as "1 byte" and not "1 bit". + + ```python + from pydantic import BaseModel, ByteSize + + class MyModel(BaseModel): + size: ByteSize + + print(MyModel(size=52000).size) + #> 52000 + print(MyModel(size='3000 KiB').size) + #> 3072000 + + m = MyModel(size='50 PB') + print(m.size.human_readable()) + #> 44.4PiB + print(m.size.human_readable(decimal=True)) + #> 50.0PB + print(m.size.human_readable(separator=' ')) + #> 44.4 PiB + + print(m.size.to('TiB')) + #> 45474.73508864641 + ``` + """ + + byte_sizes = { + 'b': 1, + 'kb': 10**3, + 'mb': 10**6, + 'gb': 10**9, + 'tb': 10**12, + 'pb': 10**15, + 'eb': 10**18, + 'kib': 2**10, + 'mib': 2**20, + 'gib': 2**30, + 'tib': 2**40, + 'pib': 2**50, + 'eib': 2**60, + 'bit': 1 / 8, + 'kbit': 10**3 / 8, + 'mbit': 10**6 / 8, + 'gbit': 10**9 / 8, + 'tbit': 10**12 / 8, + 'pbit': 10**15 / 8, + 'ebit': 10**18 / 8, + 'kibit': 2**10 / 8, + 'mibit': 2**20 / 8, + 'gibit': 2**30 / 8, + 'tibit': 2**40 / 8, + 'pibit': 2**50 / 8, + 'eibit': 2**60 / 8, + } + byte_sizes.update({k.lower()[0]: v for k, v in byte_sizes.items() if 'i' not in k}) + + byte_string_pattern = r'^\s*(\d*\.?\d+)\s*(\w+)?' + byte_string_re = re.compile(byte_string_pattern, re.IGNORECASE) + + @classmethod + def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + return core_schema.with_info_after_validator_function( + function=cls._validate, + schema=core_schema.union_schema( + [ + core_schema.str_schema(pattern=cls.byte_string_pattern), + core_schema.int_schema(ge=0), + ], + custom_error_type='byte_size', + custom_error_message='could not parse value and unit from byte string', + ), + serialization=core_schema.plain_serializer_function_ser_schema( + int, return_schema=core_schema.int_schema(ge=0) + ), + ) + + @classmethod + def _validate(cls, input_value: Any, /, _: core_schema.ValidationInfo) -> ByteSize: + try: + return cls(int(input_value)) + except ValueError: + pass + + str_match = cls.byte_string_re.match(str(input_value)) + if str_match is None: + raise PydanticCustomError('byte_size', 'could not parse value and unit from byte string') + + scalar, unit = str_match.groups() + if unit is None: + unit = 'b' + + try: + unit_mult = cls.byte_sizes[unit.lower()] + except KeyError: + raise PydanticCustomError('byte_size_unit', 'could not interpret byte unit: {unit}', {'unit': unit}) + + return cls(int(float(scalar) * unit_mult)) + + def human_readable(self, decimal: bool = False, separator: str = '') -> str: + """Converts a byte size to a human readable string. + + Args: + decimal: If True, use decimal units (e.g. 1000 bytes per KB). If False, use binary units + (e.g. 1024 bytes per KiB). + separator: A string used to split the value and unit. Defaults to an empty string (''). + + Returns: + A human readable string representation of the byte size. + """ + if decimal: + divisor = 1000 + units = 'B', 'KB', 'MB', 'GB', 'TB', 'PB' + final_unit = 'EB' + else: + divisor = 1024 + units = 'B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB' + final_unit = 'EiB' + + num = float(self) + for unit in units: + if abs(num) < divisor: + if unit == 'B': + return f'{num:0.0f}{separator}{unit}' + else: + return f'{num:0.1f}{separator}{unit}' + num /= divisor + + return f'{num:0.1f}{separator}{final_unit}' + + def to(self, unit: str) -> float: + """Converts a byte size to another unit, including both byte and bit units. + + Args: + unit: The unit to convert to. Must be one of the following: B, KB, MB, GB, TB, PB, EB, + KiB, MiB, GiB, TiB, PiB, EiB (byte units) and + bit, kbit, mbit, gbit, tbit, pbit, ebit, + kibit, mibit, gibit, tibit, pibit, eibit (bit units). + + Returns: + The byte size in the new unit. + """ + try: + unit_div = self.byte_sizes[unit.lower()] + except KeyError: + raise PydanticCustomError('byte_size_unit', 'Could not interpret byte unit: {unit}', {'unit': unit}) + + return self / unit_div + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DATE TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def _check_annotated_type(annotated_type: str, expected_type: str, annotation: str) -> None: + if annotated_type != expected_type: + raise PydanticUserError(f"'{annotation}' cannot annotate '{annotated_type}'.", code='invalid-annotated-type') + + +if TYPE_CHECKING: + PastDate = Annotated[date, ...] + FutureDate = Annotated[date, ...] +else: + + class PastDate: + """A date in the past.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.date_schema(now_op='past') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'date', cls.__name__) + schema['now_op'] = 'past' + return schema + + def __repr__(self) -> str: + return 'PastDate' + + class FutureDate: + """A date in the future.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.date_schema(now_op='future') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'date', cls.__name__) + schema['now_op'] = 'future' + return schema + + def __repr__(self) -> str: + return 'FutureDate' + + +def condate( + *, + strict: bool | None = None, + gt: date | None = None, + ge: date | None = None, + lt: date | None = None, + le: date | None = None, +) -> type[date]: + """A wrapper for date that adds constraints. + + Args: + strict: Whether to validate the date value in strict mode. Defaults to `None`. + gt: The value must be greater than this. Defaults to `None`. + ge: The value must be greater than or equal to this. Defaults to `None`. + lt: The value must be less than this. Defaults to `None`. + le: The value must be less than or equal to this. Defaults to `None`. + + Returns: + A date type with the specified constraints. + """ + return Annotated[ # pyright: ignore[reportReturnType] + date, + Strict(strict) if strict is not None else None, + annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), + ] + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DATETIME TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + AwareDatetime = Annotated[datetime, ...] + NaiveDatetime = Annotated[datetime, ...] + PastDatetime = Annotated[datetime, ...] + FutureDatetime = Annotated[datetime, ...] + +else: + + class AwareDatetime: + """A datetime that requires timezone info.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.datetime_schema(tz_constraint='aware') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'datetime', cls.__name__) + schema['tz_constraint'] = 'aware' + return schema + + def __repr__(self) -> str: + return 'AwareDatetime' + + class NaiveDatetime: + """A datetime that doesn't require timezone info.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.datetime_schema(tz_constraint='naive') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'datetime', cls.__name__) + schema['tz_constraint'] = 'naive' + return schema + + def __repr__(self) -> str: + return 'NaiveDatetime' + + class PastDatetime: + """A datetime that must be in the past.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.datetime_schema(now_op='past') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'datetime', cls.__name__) + schema['now_op'] = 'past' + return schema + + def __repr__(self) -> str: + return 'PastDatetime' + + class FutureDatetime: + """A datetime that must be in the future.""" + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + if cls is source: + # used directly as a type + return core_schema.datetime_schema(now_op='future') + else: + schema = handler(source) + _check_annotated_type(schema['type'], 'datetime', cls.__name__) + schema['now_op'] = 'future' + return schema + + def __repr__(self) -> str: + return 'FutureDatetime' + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Encoded TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class EncoderProtocol(Protocol): + """Protocol for encoding and decoding data to and from bytes.""" + + @classmethod + def decode(cls, data: bytes) -> bytes: + """Decode the data using the encoder. + + Args: + data: The data to decode. + + Returns: + The decoded data. + """ + ... + + @classmethod + def encode(cls, value: bytes) -> bytes: + """Encode the data using the encoder. + + Args: + value: The data to encode. + + Returns: + The encoded data. + """ + ... + + @classmethod + def get_json_format(cls) -> str: + """Get the JSON format for the encoded data. + + Returns: + The JSON format for the encoded data. + """ + ... + + +class Base64Encoder(EncoderProtocol): + """Standard (non-URL-safe) Base64 encoder.""" + + @classmethod + def decode(cls, data: bytes) -> bytes: + """Decode the data from base64 encoded bytes to original bytes data. + + Args: + data: The data to decode. + + Returns: + The decoded data. + """ + try: + return base64.b64decode(data) + except ValueError as e: + raise PydanticCustomError('base64_decode', "Base64 decoding error: '{error}'", {'error': str(e)}) + + @classmethod + def encode(cls, value: bytes) -> bytes: + """Encode the data from bytes to a base64 encoded bytes. + + Args: + value: The data to encode. + + Returns: + The encoded data. + """ + return base64.b64encode(value) + + @classmethod + def get_json_format(cls) -> Literal['base64']: + """Get the JSON format for the encoded data. + + Returns: + The JSON format for the encoded data. + """ + return 'base64' + + +class Base64UrlEncoder(EncoderProtocol): + """URL-safe Base64 encoder.""" + + @classmethod + def decode(cls, data: bytes) -> bytes: + """Decode the data from base64 encoded bytes to original bytes data. + + Args: + data: The data to decode. + + Returns: + The decoded data. + """ + try: + return base64.urlsafe_b64decode(data) + except ValueError as e: + raise PydanticCustomError('base64_decode', "Base64 decoding error: '{error}'", {'error': str(e)}) + + @classmethod + def encode(cls, value: bytes) -> bytes: + """Encode the data from bytes to a base64 encoded bytes. + + Args: + value: The data to encode. + + Returns: + The encoded data. + """ + return base64.urlsafe_b64encode(value) + + @classmethod + def get_json_format(cls) -> Literal['base64url']: + """Get the JSON format for the encoded data. + + Returns: + The JSON format for the encoded data. + """ + return 'base64url' + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true) +class EncodedBytes: + """A bytes type that is encoded and decoded using the specified encoder. + + `EncodedBytes` needs an encoder that implements `EncoderProtocol` to operate. + + ```python + from typing import Annotated + + from pydantic import BaseModel, EncodedBytes, EncoderProtocol, ValidationError + + class MyEncoder(EncoderProtocol): + @classmethod + def decode(cls, data: bytes) -> bytes: + if data == b'**undecodable**': + raise ValueError('Cannot decode data') + return data[13:] + + @classmethod + def encode(cls, value: bytes) -> bytes: + return b'**encoded**: ' + value + + @classmethod + def get_json_format(cls) -> str: + return 'my-encoder' + + MyEncodedBytes = Annotated[bytes, EncodedBytes(encoder=MyEncoder)] + + class Model(BaseModel): + my_encoded_bytes: MyEncodedBytes + + # Initialize the model with encoded data + m = Model(my_encoded_bytes=b'**encoded**: some bytes') + + # Access decoded value + print(m.my_encoded_bytes) + #> b'some bytes' + + # Serialize into the encoded form + print(m.model_dump()) + #> {'my_encoded_bytes': b'**encoded**: some bytes'} + + # Validate encoded data + try: + Model(my_encoded_bytes=b'**undecodable**') + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + my_encoded_bytes + Value error, Cannot decode data [type=value_error, input_value=b'**undecodable**', input_type=bytes] + ''' + ``` + """ + + encoder: type[EncoderProtocol] + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + field_schema.update(type='string', format=self.encoder.get_json_format()) + return field_schema + + def __get_pydantic_core_schema__(self, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source) + _check_annotated_type(schema['type'], 'bytes', self.__class__.__name__) + return core_schema.with_info_after_validator_function( + function=self.decode, + schema=schema, + serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode), + ) + + def decode(self, data: bytes, _: core_schema.ValidationInfo) -> bytes: + """Decode the data using the specified encoder. + + Args: + data: The data to decode. + + Returns: + The decoded data. + """ + return self.encoder.decode(data) + + def encode(self, value: bytes) -> bytes: + """Encode the data using the specified encoder. + + Args: + value: The data to encode. + + Returns: + The encoded data. + """ + return self.encoder.encode(value) + + def __hash__(self) -> int: + return hash(self.encoder) + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true) +class EncodedStr: + """A str type that is encoded and decoded using the specified encoder. + + `EncodedStr` needs an encoder that implements `EncoderProtocol` to operate. + + ```python + from typing import Annotated + + from pydantic import BaseModel, EncodedStr, EncoderProtocol, ValidationError + + class MyEncoder(EncoderProtocol): + @classmethod + def decode(cls, data: bytes) -> bytes: + if data == b'**undecodable**': + raise ValueError('Cannot decode data') + return data[13:] + + @classmethod + def encode(cls, value: bytes) -> bytes: + return b'**encoded**: ' + value + + @classmethod + def get_json_format(cls) -> str: + return 'my-encoder' + + MyEncodedStr = Annotated[str, EncodedStr(encoder=MyEncoder)] + + class Model(BaseModel): + my_encoded_str: MyEncodedStr + + # Initialize the model with encoded data + m = Model(my_encoded_str='**encoded**: some str') + + # Access decoded value + print(m.my_encoded_str) + #> some str + + # Serialize into the encoded form + print(m.model_dump()) + #> {'my_encoded_str': '**encoded**: some str'} + + # Validate encoded data + try: + Model(my_encoded_str='**undecodable**') + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + my_encoded_str + Value error, Cannot decode data [type=value_error, input_value='**undecodable**', input_type=str] + ''' + ``` + """ + + encoder: type[EncoderProtocol] + + def __get_pydantic_json_schema__( + self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = handler(core_schema) + field_schema.update(type='string', format=self.encoder.get_json_format()) + return field_schema + + def __get_pydantic_core_schema__(self, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + schema = handler(source) + _check_annotated_type(schema['type'], 'str', self.__class__.__name__) + return core_schema.with_info_after_validator_function( + function=self.decode_str, + schema=schema, + serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode_str), + ) + + def decode_str(self, data: str, _: core_schema.ValidationInfo) -> str: + """Decode the data using the specified encoder. + + Args: + data: The data to decode. + + Returns: + The decoded data. + """ + return self.encoder.decode(data.encode()).decode() + + def encode_str(self, value: str) -> str: + """Encode the data using the specified encoder. + + Args: + value: The data to encode. + + Returns: + The encoded data. + """ + return self.encoder.encode(value.encode()).decode() # noqa: UP008 + + def __hash__(self) -> int: + return hash(self.encoder) + + +Base64Bytes = Annotated[bytes, EncodedBytes(encoder=Base64Encoder)] +"""A bytes type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. + +Note: + Under the hood, `Base64Bytes` uses the standard library [`base64.b64encode()`][base64.b64encode] and [`base64.b64decode()`][base64.b64decode] functions. + + As a result, attempting to decode url-safe base64 data using the `Base64Bytes` type may fail or produce an incorrect + decoding. + +/// version-changed | v2.10 +`Base64Bytes` now uses [`base64.b64encode()`][base64.b64encode] and [`base64.b64decode()`][base64.b64decode] +instead of [`base64.encodebytes()`][base64.encodebytes] and [`base64.decodebytes()`][base64.decodebytes]. + +These methods are considered legacy implementation. If you'd still like to use these legacy encoders/decoders, +you can achieve this by creating a custom annotated type, like follows: +```python +import base64 +from typing import Annotated, Literal + +from pydantic_core import PydanticCustomError + +from pydantic import EncodedBytes, EncoderProtocol + +class LegacyBase64Encoder(EncoderProtocol): + @classmethod + def decode(cls, data: bytes) -> bytes: + try: + return base64.decodebytes(data) + except ValueError as e: + raise PydanticCustomError( + 'base64_decode', + "Base64 decoding error: '{error}'", + {'error': str(e)}, + ) + + @classmethod + def encode(cls, value: bytes) -> bytes: + return base64.encodebytes(value) + + @classmethod + def get_json_format(cls) -> Literal['base64']: + return 'base64' + +LegacyBase64Bytes = Annotated[bytes, EncodedBytes(encoder=LegacyBase64Encoder)] +``` +/// + +```python +from pydantic import Base64Bytes, BaseModel, ValidationError + +class Model(BaseModel): + base64_bytes: Base64Bytes + +# Initialize the model with base64 data +m = Model(base64_bytes=b'VGhpcyBpcyB0aGUgd2F5') + +# Access decoded value +print(m.base64_bytes) +#> b'This is the way' + +# Serialize into the base64 form +print(m.model_dump()) +#> {'base64_bytes': b'VGhpcyBpcyB0aGUgd2F5'} + +# Validate base64 data +try: + print(Model(base64_bytes=b'undecodable').base64_bytes) +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + base64_bytes + Base64 decoding error: 'Incorrect padding' [type=base64_decode, input_value=b'undecodable', input_type=bytes] + ''' +``` +""" +Base64Str = Annotated[str, EncodedStr(encoder=Base64Encoder)] +"""A string type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. + +Note: + Under the hood, `Base64Str` uses the standard library [`base64.b64encode()`][base64.b64encode] and [`base64.b64decode()`][base64.b64decode] functions. + + As a result, attempting to decode url-safe base64 data using the `Base64Str` type may fail or produce an incorrect + decoding. + +/// version-changed | v2.10 +`Base64Str` now uses [`base64.b64encode()`][base64.b64encode] and [`base64.b64decode()`][base64.b64decode] +instead of [`base64.encodebytes()`][base64.encodebytes] and [`base64.decodebytes()`][base64.decodebytes]. + +These methods are considered legacy implementation. See the documentation about the [`Base64Bytes`][pydantic.types.Base64Bytes] type +for more information on how to replicate the old behavior with the legacy encoders/decoders. +/// + + +```python +from pydantic import Base64Str, BaseModel, ValidationError + +class Model(BaseModel): + base64_str: Base64Str + +# Initialize the model with base64 data +m = Model(base64_str='VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y') + +# Access decoded value +print(m.base64_str) +#> These aren't the droids you're looking for + +# Serialize into the base64 form +print(m.model_dump()) +#> {'base64_str': 'VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y'} + +# Validate base64 data +try: + print(Model(base64_str='undecodable').base64_str) +except ValidationError as e: + print(e) + ''' + 1 validation error for Model + base64_str + Base64 decoding error: 'Incorrect padding' [type=base64_decode, input_value='undecodable', input_type=str] + ''' +``` +""" +Base64UrlBytes = Annotated[bytes, EncodedBytes(encoder=Base64UrlEncoder)] +"""A bytes type that is encoded and decoded using the URL-safe base64 encoder. + +Note: + Under the hood, `Base64UrlBytes` use standard library `base64.urlsafe_b64encode` and `base64.urlsafe_b64decode` + functions. + + As a result, the `Base64UrlBytes` type can be used to faithfully decode "vanilla" base64 data + (using `'+'` and `'/'`). + +```python +from pydantic import Base64UrlBytes, BaseModel + +class Model(BaseModel): + base64url_bytes: Base64UrlBytes + +# Initialize the model with base64 data +m = Model(base64url_bytes=b'SHc_dHc-TXc==') +print(m) +#> base64url_bytes=b'Hw?tw>Mw' +``` +""" +Base64UrlStr = Annotated[str, EncodedStr(encoder=Base64UrlEncoder)] +"""A str type that is encoded and decoded using the URL-safe base64 encoder. + +Note: + Under the hood, `Base64UrlStr` use standard library `base64.urlsafe_b64encode` and `base64.urlsafe_b64decode` + functions. + + As a result, the `Base64UrlStr` type can be used to faithfully decode "vanilla" base64 data (using `'+'` and `'/'`). + +```python +from pydantic import Base64UrlStr, BaseModel + +class Model(BaseModel): + base64url_str: Base64UrlStr + +# Initialize the model with base64 data +m = Model(base64url_str='SHc_dHc-TXc==') +print(m) +#> base64url_str='Hw?tw>Mw' +``` +""" + + +__getattr__ = getattr_migration(__name__) + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true) +class GetPydanticSchema: + """!!! abstract "Usage Documentation" + [Using `GetPydanticSchema` to Reduce Boilerplate](../concepts/types.md#using-getpydanticschema-to-reduce-boilerplate) + + A convenience class for creating an annotation that provides pydantic custom type hooks. + + This class is intended to eliminate the need to create a custom "marker" which defines the + `__get_pydantic_core_schema__` and `__get_pydantic_json_schema__` custom hook methods. + + For example, to have a field treated by type checkers as `int`, but by pydantic as `Any`, you can do: + ```python + from typing import Annotated, Any + + from pydantic import BaseModel, GetPydanticSchema + + HandleAsAny = GetPydanticSchema(lambda _s, h: h(Any)) + + class Model(BaseModel): + x: Annotated[int, HandleAsAny] # pydantic sees `x: Any` + + print(repr(Model(x='abc').x)) + #> 'abc' + ``` + """ + + get_pydantic_core_schema: Callable[[Any, GetCoreSchemaHandler], CoreSchema] | None = None + get_pydantic_json_schema: Callable[[Any, GetJsonSchemaHandler], JsonSchemaValue] | None = None + + # Note: we may want to consider adding a convenience staticmethod `def for_type(type_: Any) -> GetPydanticSchema:` + # which returns `GetPydanticSchema(lambda _s, h: h(type_))` + + if not TYPE_CHECKING: + # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access + + def __getattr__(self, item: str) -> Any: + """Use this rather than defining `__get_pydantic_core_schema__` etc. to reduce the number of nested calls.""" + if item == '__get_pydantic_core_schema__' and self.get_pydantic_core_schema: + return self.get_pydantic_core_schema + elif item == '__get_pydantic_json_schema__' and self.get_pydantic_json_schema: + return self.get_pydantic_json_schema + else: + return object.__getattribute__(self, item) + + __hash__ = object.__hash__ + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) +class Tag: + """Provides a way to specify the expected tag to use for a case of a (callable) discriminated union. + + Also provides a way to label a union case in error messages. + + When using a callable `Discriminator`, attach a `Tag` to each case in the `Union` to specify the tag that + should be used to identify that case. For example, in the below example, the `Tag` is used to specify that + if `get_discriminator_value` returns `'apple'`, the input should be validated as an `ApplePie`, and if it + returns `'pumpkin'`, the input should be validated as a `PumpkinPie`. + + The primary role of the `Tag` here is to map the return value from the callable `Discriminator` function to + the appropriate member of the `Union` in question. + + ```python + from typing import Annotated, Any, Literal, Union + + from pydantic import BaseModel, Discriminator, Tag + + class Pie(BaseModel): + time_to_cook: int + num_ingredients: int + + class ApplePie(Pie): + fruit: Literal['apple'] = 'apple' + + class PumpkinPie(Pie): + filling: Literal['pumpkin'] = 'pumpkin' + + def get_discriminator_value(v: Any) -> str: + if isinstance(v, dict): + return v.get('fruit', v.get('filling')) + return getattr(v, 'fruit', getattr(v, 'filling', None)) + + class ThanksgivingDinner(BaseModel): + dessert: Annotated[ + Union[ + Annotated[ApplePie, Tag('apple')], + Annotated[PumpkinPie, Tag('pumpkin')], + ], + Discriminator(get_discriminator_value), + ] + + apple_variation = ThanksgivingDinner.model_validate( + {'dessert': {'fruit': 'apple', 'time_to_cook': 60, 'num_ingredients': 8}} + ) + print(repr(apple_variation)) + ''' + ThanksgivingDinner(dessert=ApplePie(time_to_cook=60, num_ingredients=8, fruit='apple')) + ''' + + pumpkin_variation = ThanksgivingDinner.model_validate( + { + 'dessert': { + 'filling': 'pumpkin', + 'time_to_cook': 40, + 'num_ingredients': 6, + } + } + ) + print(repr(pumpkin_variation)) + ''' + ThanksgivingDinner(dessert=PumpkinPie(time_to_cook=40, num_ingredients=6, filling='pumpkin')) + ''' + ``` + + !!! note + You must specify a `Tag` for every case in a `Tag` that is associated with a + callable `Discriminator`. Failing to do so will result in a `PydanticUserError` with code + [`callable-discriminator-no-tag`](../errors/usage_errors.md#callable-discriminator-no-tag). + + See the [Discriminated Unions] concepts docs for more details on how to use `Tag`s. + + [Discriminated Unions]: ../concepts/unions.md#discriminated-unions + """ + + tag: str + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + schema = handler(source_type) + metadata = cast('CoreMetadata', schema.setdefault('metadata', {})) + metadata['pydantic_internal_union_tag_key'] = self.tag + return schema + + +@_dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) +class Discriminator: + """!!! abstract "Usage Documentation" + [Discriminated Unions with `Callable` `Discriminator`](../concepts/unions.md#discriminated-unions-with-callable-discriminator) + + Provides a way to use a custom callable as the way to extract the value of a union discriminator. + + This allows you to get validation behavior like you'd get from `Field(discriminator=)`, + but without needing to have a single shared field across all the union choices. This also makes it + possible to handle unions of models and primitive types with discriminated-union-style validation errors. + Finally, this allows you to use a custom callable as the way to identify which member of a union a value + belongs to, while still seeing all the performance benefits of a discriminated union. + + Consider this example, which is much more performant with the use of `Discriminator` and thus a `TaggedUnion` + than it would be as a normal `Union`. + + ```python + from typing import Annotated, Any, Literal, Union + + from pydantic import BaseModel, Discriminator, Tag + + class Pie(BaseModel): + time_to_cook: int + num_ingredients: int + + class ApplePie(Pie): + fruit: Literal['apple'] = 'apple' + + class PumpkinPie(Pie): + filling: Literal['pumpkin'] = 'pumpkin' + + def get_discriminator_value(v: Any) -> str: + if isinstance(v, dict): + return v.get('fruit', v.get('filling')) + return getattr(v, 'fruit', getattr(v, 'filling', None)) + + class ThanksgivingDinner(BaseModel): + dessert: Annotated[ + Union[ + Annotated[ApplePie, Tag('apple')], + Annotated[PumpkinPie, Tag('pumpkin')], + ], + Discriminator(get_discriminator_value), + ] + + apple_variation = ThanksgivingDinner.model_validate( + {'dessert': {'fruit': 'apple', 'time_to_cook': 60, 'num_ingredients': 8}} + ) + print(repr(apple_variation)) + ''' + ThanksgivingDinner(dessert=ApplePie(time_to_cook=60, num_ingredients=8, fruit='apple')) + ''' + + pumpkin_variation = ThanksgivingDinner.model_validate( + { + 'dessert': { + 'filling': 'pumpkin', + 'time_to_cook': 40, + 'num_ingredients': 6, + } + } + ) + print(repr(pumpkin_variation)) + ''' + ThanksgivingDinner(dessert=PumpkinPie(time_to_cook=40, num_ingredients=6, filling='pumpkin')) + ''' + ``` + + See the [Discriminated Unions] concepts docs for more details on how to use `Discriminator`s. + + [Discriminated Unions]: ../concepts/unions.md#discriminated-unions + """ + + discriminator: str | Callable[[Any], Hashable] + """The callable or field name for discriminating the type in a tagged union. + + A `Callable` discriminator must extract the value of the discriminator from the input. + A `str` discriminator must be the name of a field to discriminate against. + """ + custom_error_type: str | None = None + """Type to use in [custom errors](../errors/errors.md) replacing the standard discriminated union + validation errors. + """ + custom_error_message: str | None = None + """Message to use in custom errors.""" + custom_error_context: dict[str, int | str | float] | None = None + """Context to use in custom errors.""" + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + if isinstance(self.discriminator, str): + from pydantic import Field + + return handler(Annotated[source_type, Field(discriminator=self.discriminator)]) + else: + original_schema = handler(source_type) + return self._convert_schema(original_schema, handler) + + def _convert_schema( + self, original_schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler | None = None + ) -> core_schema.TaggedUnionSchema: + if handler is not None and original_schema['type'] == 'definition-ref': + # Same logic as `_ApplyInferredDiscriminator._apply_to_root()` + try: + def_schema = handler.resolve_ref_schema(original_schema) + except LookupError: # pragma: no cover + from pydantic._internal._discriminated_union import MissingDefinitionForUnionRef + + raise MissingDefinitionForUnionRef(original_schema['schema_ref']) + + # If using a referenceable union as discriminated (e.g. `type Pet = Cat | Dog; field: Pet = Field(discriminator=...)`): + if def_schema['type'] == 'union': + original_schema = def_schema.copy() + original_schema.pop('ref') + + if original_schema['type'] != 'union': + # This likely indicates that the schema was a single-item union that was simplified. + # In this case, we do the same thing we do in + # `pydantic._internal._discriminated_union._ApplyInferredDiscriminator._apply_to_root`, namely, + # package the generated schema back into a single-item union. + original_schema = core_schema.union_schema([original_schema]) + + tagged_union_choices = {} + for choice in original_schema['choices']: + tag = None + if isinstance(choice, tuple): + choice, tag = choice + metadata = cast('CoreMetadata | None', choice.get('metadata')) + if metadata is not None: + tag = metadata.get('pydantic_internal_union_tag_key') or tag + if tag is None: + # `handler` is None when this method is called from `apply_discriminator()` (deferred discriminators) + if handler is not None and choice['type'] == 'definition-ref': + # If choice was built from a PEP 695 type alias, try to resolve the def: + try: + choice = handler.resolve_ref_schema(choice) + except LookupError: + pass + else: + metadata = cast('CoreMetadata | None', choice.get('metadata')) + if metadata is not None: + tag = metadata.get('pydantic_internal_union_tag_key') + + if tag is None: + raise PydanticUserError( + f'`Tag` not provided for choice {choice} used with `Discriminator`', + code='callable-discriminator-no-tag', + ) + tagged_union_choices[tag] = choice + + # Have to do these verbose checks to ensure falsy values ('' and {}) don't get ignored + custom_error_type = self.custom_error_type + if custom_error_type is None: + custom_error_type = original_schema.get('custom_error_type') + + custom_error_message = self.custom_error_message + if custom_error_message is None: + custom_error_message = original_schema.get('custom_error_message') + + custom_error_context = self.custom_error_context + if custom_error_context is None: + custom_error_context = original_schema.get('custom_error_context') + + custom_error_type = original_schema.get('custom_error_type') if custom_error_type is None else custom_error_type + return core_schema.tagged_union_schema( + tagged_union_choices, + self.discriminator, + custom_error_type=custom_error_type, + custom_error_message=custom_error_message, + custom_error_context=custom_error_context, + strict=original_schema.get('strict'), + ref=original_schema.get('ref'), + metadata=original_schema.get('metadata'), + serialization=original_schema.get('serialization'), + ) + + +_JSON_TYPES = {int, float, str, bool, list, dict, type(None)} + + +def _get_type_name(x: Any) -> str: + type_ = type(x) + if type_ in _JSON_TYPES: + return type_.__name__ + + # Handle proper subclasses; note we don't need to handle None or bool here + if isinstance(x, int): + return 'int' + if isinstance(x, float): + return 'float' + if isinstance(x, str): + return 'str' + if isinstance(x, list): + return 'list' + if isinstance(x, dict): + return 'dict' + + # Fail by returning the type's actual name + return getattr(type_, '__name__', '') + + +class _AllowAnyJson: + @classmethod + def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + python_schema = handler(source_type) + return core_schema.json_or_python_schema(json_schema=core_schema.any_schema(), python_schema=python_schema) + + +if TYPE_CHECKING: + # This seems to only be necessary for mypy + JsonValue: TypeAlias = Union[ + list['JsonValue'], + dict[str, 'JsonValue'], + str, + bool, + int, + float, + None, + ] + """A `JsonValue` is used to represent a value that can be serialized to JSON. + + It may be one of: + + * `list['JsonValue']` + * `dict[str, 'JsonValue']` + * `str` + * `bool` + * `int` + * `float` + * `None` + + The following example demonstrates how to use `JsonValue` to validate JSON data, + and what kind of errors to expect when input data is not json serializable. + + ```python + import json + + from pydantic import BaseModel, JsonValue, ValidationError + + class Model(BaseModel): + j: JsonValue + + valid_json_data = {'j': {'a': {'b': {'c': 1, 'd': [2, None]}}}} + invalid_json_data = {'j': {'a': {'b': ...}}} + + print(repr(Model.model_validate(valid_json_data))) + #> Model(j={'a': {'b': {'c': 1, 'd': [2, None]}}}) + print(repr(Model.model_validate_json(json.dumps(valid_json_data)))) + #> Model(j={'a': {'b': {'c': 1, 'd': [2, None]}}}) + + try: + Model.model_validate(invalid_json_data) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + j.dict.a.dict.b + input was not a valid JSON value [type=invalid-json-value, input_value=Ellipsis, input_type=ellipsis] + ''' + ``` + """ + +else: + JsonValue = TypeAliasType( + 'JsonValue', + Annotated[ + Union[ + Annotated[list['JsonValue'], Tag('list')], + Annotated[dict[str, 'JsonValue'], Tag('dict')], + Annotated[str, Tag('str')], + Annotated[bool, Tag('bool')], + Annotated[int, Tag('int')], + Annotated[float, Tag('float')], + Annotated[None, Tag('NoneType')], + ], + Discriminator( + _get_type_name, + custom_error_type='invalid-json-value', + custom_error_message='input was not a valid JSON value', + ), + _AllowAnyJson, + ], + ) + + +class _OnErrorOmit: + @classmethod + def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + # there is no actual default value here but we use with_default_schema since it already has the on_error + # behavior implemented and it would be no more efficient to implement it on every other validator + # or as a standalone validator + return core_schema.with_default_schema(schema=handler(source_type), on_error='omit') + + +OnErrorOmit = Annotated[T, _OnErrorOmit] +""" +When used as an item in a list, the key type in a dict, optional values of a TypedDict, etc. +this annotation omits the item from the iteration if there is any error validating it. +That is, instead of a [`ValidationError`][pydantic_core.ValidationError] being propagated up and the entire iterable being discarded +any invalid items are discarded and the valid ones are returned. +""" + + +@_dataclasses.dataclass +class FailFast(_fields.PydanticMetadata, BaseMetadata): + """A `FailFast` annotation can be used to specify that validation should stop at the first error. + + This can be useful when you want to validate a large amount of data and you only need to know if it's valid or not. + + You might want to enable this setting if you want to validate your data faster (basically, if you use this, + validation will be more performant with the caveat that you get less information). + + ```python + from typing import Annotated + + from pydantic import BaseModel, FailFast, ValidationError + + class Model(BaseModel): + x: Annotated[list[int], FailFast()] + + # This will raise a single error for the first invalid value and stop validation + try: + obj = Model(x=[1, 2, 'a', 4, 5, 'b', 7, 8, 9, 'c']) + except ValidationError as e: + print(e) + ''' + 1 validation error for Model + x.2 + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] + ''' + ``` + """ + + fail_fast: bool = True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py new file mode 100644 index 0000000..0bda22d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/typing.py @@ -0,0 +1,5 @@ +"""`typing` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py new file mode 100644 index 0000000..8d1e2a8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/utils.py @@ -0,0 +1,5 @@ +"""The `utils` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py new file mode 100644 index 0000000..6ad3f46 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py @@ -0,0 +1,131 @@ +# flake8: noqa +from pydantic.v1 import dataclasses +from pydantic.v1.annotated_types import create_model_from_namedtuple, create_model_from_typeddict +from pydantic.v1.class_validators import root_validator, validator +from pydantic.v1.config import BaseConfig, ConfigDict, Extra +from pydantic.v1.decorator import validate_arguments +from pydantic.v1.env_settings import BaseSettings +from pydantic.v1.error_wrappers import ValidationError +from pydantic.v1.errors import * +from pydantic.v1.fields import Field, PrivateAttr, Required +from pydantic.v1.main import * +from pydantic.v1.networks import * +from pydantic.v1.parse import Protocol +from pydantic.v1.tools import * +from pydantic.v1.types import * +from pydantic.v1.version import VERSION, compiled + +__version__ = VERSION + +# WARNING __all__ from pydantic.errors is not included here, it will be removed as an export here in v2 +# please use "from pydantic.v1.errors import ..." instead +__all__ = [ + # annotated types utils + 'create_model_from_namedtuple', + 'create_model_from_typeddict', + # dataclasses + 'dataclasses', + # class_validators + 'root_validator', + 'validator', + # config + 'BaseConfig', + 'ConfigDict', + 'Extra', + # decorator + 'validate_arguments', + # env_settings + 'BaseSettings', + # error_wrappers + 'ValidationError', + # fields + 'Field', + 'Required', + # main + 'BaseModel', + 'create_model', + 'validate_model', + # network + 'AnyUrl', + 'AnyHttpUrl', + 'FileUrl', + 'HttpUrl', + 'stricturl', + 'EmailStr', + 'NameEmail', + 'IPvAnyAddress', + 'IPvAnyInterface', + 'IPvAnyNetwork', + 'PostgresDsn', + 'CockroachDsn', + 'AmqpDsn', + 'RedisDsn', + 'MongoDsn', + 'KafkaDsn', + 'validate_email', + # parse + 'Protocol', + # tools + 'parse_file_as', + 'parse_obj_as', + 'parse_raw_as', + 'schema_of', + 'schema_json_of', + # types + 'NoneStr', + 'NoneBytes', + 'StrBytes', + 'NoneStrBytes', + 'StrictStr', + 'ConstrainedBytes', + 'conbytes', + 'ConstrainedList', + 'conlist', + 'ConstrainedSet', + 'conset', + 'ConstrainedFrozenSet', + 'confrozenset', + 'ConstrainedStr', + 'constr', + 'PyObject', + 'ConstrainedInt', + 'conint', + 'PositiveInt', + 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', + 'ConstrainedFloat', + 'confloat', + 'PositiveFloat', + 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', + 'FiniteFloat', + 'ConstrainedDecimal', + 'condecimal', + 'ConstrainedDate', + 'condate', + 'UUID1', + 'UUID3', + 'UUID4', + 'UUID5', + 'FilePath', + 'DirectoryPath', + 'Json', + 'JsonWrapper', + 'SecretField', + 'SecretStr', + 'SecretBytes', + 'StrictBool', + 'StrictBytes', + 'StrictInt', + 'StrictFloat', + 'PaymentCardNumber', + 'PrivateAttr', + 'ByteSize', + 'PastDate', + 'FutureDate', + # version + 'compiled', + 'VERSION', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py new file mode 100644 index 0000000..b62234d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py @@ -0,0 +1,391 @@ +""" +Register Hypothesis strategies for Pydantic custom types. + +This enables fully-automatic generation of test data for most Pydantic classes. + +Note that this module has *no* runtime impact on Pydantic itself; instead it +is registered as a setuptools entry point and Hypothesis will import it if +Pydantic is installed. See also: + +https://hypothesis.readthedocs.io/en/latest/strategies.html#registering-strategies-via-setuptools-entry-points +https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.register_type_strategy +https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov +https://docs.pydantic.dev/usage/types/#pydantic-types + +Note that because our motivation is to *improve user experience*, the strategies +are always sound (never generate invalid data) but sacrifice completeness for +maintainability (ie may be unable to generate some tricky but valid data). + +Finally, this module makes liberal use of `# type: ignore[]` pragmas. +This is because Hypothesis annotates `register_type_strategy()` with +`(T, SearchStrategy[T])`, but in most cases we register e.g. `ConstrainedInt` +to generate instances of the builtin `int` type which match the constraints. +""" + +import contextlib +import datetime +import ipaddress +import json +import math +from fractions import Fraction +from typing import Callable, Dict, Type, Union, cast, overload + +import hypothesis.strategies as st + +import pydantic +import pydantic.color +import pydantic.types +from pydantic.v1.utils import lenient_issubclass + +# FilePath and DirectoryPath are explicitly unsupported, as we'd have to create +# them on-disk, and that's unsafe in general without being told *where* to do so. +# +# URLs are unsupported because it's easy for users to define their own strategy for +# "normal" URLs, and hard for us to define a general strategy which includes "weird" +# URLs but doesn't also have unpredictable performance problems. +# +# conlist() and conset() are unsupported for now, because the workarounds for +# Cython and Hypothesis to handle parametrized generic types are incompatible. +# We are rethinking Hypothesis compatibility in Pydantic v2. + +# Emails +try: + import email_validator +except ImportError: # pragma: no cover + pass +else: + + def is_valid_email(s: str) -> bool: + # Hypothesis' st.emails() occasionally generates emails like 0@A0--0.ac + # that are invalid according to email-validator, so we filter those out. + try: + email_validator.validate_email(s, check_deliverability=False) + return True + except email_validator.EmailNotValidError: # pragma: no cover + return False + + # Note that these strategies deliberately stay away from any tricky Unicode + # or other encoding issues; we're just trying to generate *something* valid. + st.register_type_strategy(pydantic.EmailStr, st.emails().filter(is_valid_email)) # type: ignore[arg-type] + st.register_type_strategy( + pydantic.NameEmail, + st.builds( + '{} <{}>'.format, # type: ignore[arg-type] + st.from_regex('[A-Za-z0-9_]+( [A-Za-z0-9_]+){0,5}', fullmatch=True), + st.emails().filter(is_valid_email), + ), + ) + +# PyObject - dotted names, in this case taken from the math module. +st.register_type_strategy( + pydantic.PyObject, # type: ignore[arg-type] + st.sampled_from( + [cast(pydantic.PyObject, f'math.{name}') for name in sorted(vars(math)) if not name.startswith('_')] + ), +) + +# CSS3 Colors; as name, hex, rgb(a) tuples or strings, or hsl strings +_color_regexes = ( + '|'.join( + ( + pydantic.color.r_hex_short, + pydantic.color.r_hex_long, + pydantic.color.r_rgb, + pydantic.color.r_rgba, + pydantic.color.r_hsl, + pydantic.color.r_hsla, + ) + ) + # Use more precise regex patterns to avoid value-out-of-range errors + .replace(pydantic.color._r_sl, r'(?:(\d\d?(?:\.\d+)?|100(?:\.0+)?)%)') + .replace(pydantic.color._r_alpha, r'(?:(0(?:\.\d+)?|1(?:\.0+)?|\.\d+|\d{1,2}%))') + .replace(pydantic.color._r_255, r'(?:((?:\d|\d\d|[01]\d\d|2[0-4]\d|25[0-4])(?:\.\d+)?|255(?:\.0+)?))') +) +st.register_type_strategy( + pydantic.color.Color, + st.one_of( + st.sampled_from(sorted(pydantic.color.COLORS_BY_NAME)), + st.tuples( + st.integers(0, 255), + st.integers(0, 255), + st.integers(0, 255), + st.none() | st.floats(0, 1) | st.floats(0, 100).map('{}%'.format), + ), + st.from_regex(_color_regexes, fullmatch=True), + ), +) + + +# Card numbers, valid according to the Luhn algorithm + + +def add_luhn_digit(card_number: str) -> str: + # See https://en.wikipedia.org/wiki/Luhn_algorithm + for digit in '0123456789': + with contextlib.suppress(Exception): + pydantic.PaymentCardNumber.validate_luhn_check_digit(card_number + digit) + return card_number + digit + raise AssertionError('Unreachable') # pragma: no cover + + +card_patterns = ( + # Note that these patterns omit the Luhn check digit; that's added by the function above + '4[0-9]{14}', # Visa + '5[12345][0-9]{13}', # Mastercard + '3[47][0-9]{12}', # American Express + '[0-26-9][0-9]{10,17}', # other (incomplete to avoid overlap) +) +st.register_type_strategy( + pydantic.PaymentCardNumber, + st.from_regex('|'.join(card_patterns), fullmatch=True).map(add_luhn_digit), # type: ignore[arg-type] +) + +# UUIDs +st.register_type_strategy(pydantic.UUID1, st.uuids(version=1)) +st.register_type_strategy(pydantic.UUID3, st.uuids(version=3)) +st.register_type_strategy(pydantic.UUID4, st.uuids(version=4)) +st.register_type_strategy(pydantic.UUID5, st.uuids(version=5)) + +# Secrets +st.register_type_strategy(pydantic.SecretBytes, st.binary().map(pydantic.SecretBytes)) +st.register_type_strategy(pydantic.SecretStr, st.text().map(pydantic.SecretStr)) + +# IP addresses, networks, and interfaces +st.register_type_strategy(pydantic.IPvAnyAddress, st.ip_addresses()) # type: ignore[arg-type] +st.register_type_strategy( + pydantic.IPvAnyInterface, + st.from_type(ipaddress.IPv4Interface) | st.from_type(ipaddress.IPv6Interface), # type: ignore[arg-type] +) +st.register_type_strategy( + pydantic.IPvAnyNetwork, + st.from_type(ipaddress.IPv4Network) | st.from_type(ipaddress.IPv6Network), # type: ignore[arg-type] +) + +# We hook into the con***() functions and the ConstrainedNumberMeta metaclass, +# so here we only have to register subclasses for other constrained types which +# don't go via those mechanisms. Then there are the registration hooks below. +st.register_type_strategy(pydantic.StrictBool, st.booleans()) +st.register_type_strategy(pydantic.StrictStr, st.text()) + + +# FutureDate, PastDate +st.register_type_strategy(pydantic.FutureDate, st.dates(min_value=datetime.date.today() + datetime.timedelta(days=1))) +st.register_type_strategy(pydantic.PastDate, st.dates(max_value=datetime.date.today() - datetime.timedelta(days=1))) + + +# Constrained-type resolver functions +# +# For these ones, we actually want to inspect the type in order to work out a +# satisfying strategy. First up, the machinery for tracking resolver functions: + +RESOLVERS: Dict[type, Callable[[type], st.SearchStrategy]] = {} # type: ignore[type-arg] + + +@overload +def _registered(typ: Type[pydantic.types.T]) -> Type[pydantic.types.T]: + pass + + +@overload +def _registered(typ: pydantic.types.ConstrainedNumberMeta) -> pydantic.types.ConstrainedNumberMeta: + pass + + +def _registered( + typ: Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta] +) -> Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]: + # This function replaces the version in `pydantic.types`, in order to + # effect the registration of new constrained types so that Hypothesis + # can generate valid examples. + pydantic.types._DEFINED_TYPES.add(typ) + for supertype, resolver in RESOLVERS.items(): + if issubclass(typ, supertype): + st.register_type_strategy(typ, resolver(typ)) # type: ignore + return typ + raise NotImplementedError(f'Unknown type {typ!r} has no resolver to register') # pragma: no cover + + +def resolves( + typ: Union[type, pydantic.types.ConstrainedNumberMeta] +) -> Callable[[Callable[..., st.SearchStrategy]], Callable[..., st.SearchStrategy]]: # type: ignore[type-arg] + def inner(f): # type: ignore + assert f not in RESOLVERS + RESOLVERS[typ] = f + return f + + return inner + + +# Type-to-strategy resolver functions + + +@resolves(pydantic.JsonWrapper) +def resolve_json(cls): # type: ignore[no-untyped-def] + try: + inner = st.none() if cls.inner_type is None else st.from_type(cls.inner_type) + except Exception: # pragma: no cover + finite = st.floats(allow_infinity=False, allow_nan=False) + inner = st.recursive( + base=st.one_of(st.none(), st.booleans(), st.integers(), finite, st.text()), + extend=lambda x: st.lists(x) | st.dictionaries(st.text(), x), # type: ignore + ) + inner_type = getattr(cls, 'inner_type', None) + return st.builds( + cls.inner_type.json if lenient_issubclass(inner_type, pydantic.BaseModel) else json.dumps, + inner, + ensure_ascii=st.booleans(), + indent=st.none() | st.integers(0, 16), + sort_keys=st.booleans(), + ) + + +@resolves(pydantic.ConstrainedBytes) +def resolve_conbytes(cls): # type: ignore[no-untyped-def] # pragma: no cover + min_size = cls.min_length or 0 + max_size = cls.max_length + if not cls.strip_whitespace: + return st.binary(min_size=min_size, max_size=max_size) + # Fun with regex to ensure we neither start nor end with whitespace + repeats = '{{{},{}}}'.format( + min_size - 2 if min_size > 2 else 0, + max_size - 2 if (max_size or 0) > 2 else '', + ) + if min_size >= 2: + pattern = rf'\W.{repeats}\W' + elif min_size == 1: + pattern = rf'\W(.{repeats}\W)?' + else: + assert min_size == 0 + pattern = rf'(\W(.{repeats}\W)?)?' + return st.from_regex(pattern.encode(), fullmatch=True) + + +@resolves(pydantic.ConstrainedDecimal) +def resolve_condecimal(cls): # type: ignore[no-untyped-def] + min_value = cls.ge + max_value = cls.le + if cls.gt is not None: + assert min_value is None, 'Set `gt` or `ge`, but not both' + min_value = cls.gt + if cls.lt is not None: + assert max_value is None, 'Set `lt` or `le`, but not both' + max_value = cls.lt + s = st.decimals(min_value, max_value, allow_nan=False, places=cls.decimal_places) + if cls.lt is not None: + s = s.filter(lambda d: d < cls.lt) + if cls.gt is not None: + s = s.filter(lambda d: cls.gt < d) + return s + + +@resolves(pydantic.ConstrainedFloat) +def resolve_confloat(cls): # type: ignore[no-untyped-def] + min_value = cls.ge + max_value = cls.le + exclude_min = False + exclude_max = False + + if cls.gt is not None: + assert min_value is None, 'Set `gt` or `ge`, but not both' + min_value = cls.gt + exclude_min = True + if cls.lt is not None: + assert max_value is None, 'Set `lt` or `le`, but not both' + max_value = cls.lt + exclude_max = True + + if cls.multiple_of is None: + return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False) + + if min_value is not None: + min_value = math.ceil(min_value / cls.multiple_of) + if exclude_min: + min_value = min_value + 1 + if max_value is not None: + assert max_value >= cls.multiple_of, 'Cannot build model with max value smaller than multiple of' + max_value = math.floor(max_value / cls.multiple_of) + if exclude_max: + max_value = max_value - 1 + + return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) + + +@resolves(pydantic.ConstrainedInt) +def resolve_conint(cls): # type: ignore[no-untyped-def] + min_value = cls.ge + max_value = cls.le + if cls.gt is not None: + assert min_value is None, 'Set `gt` or `ge`, but not both' + min_value = cls.gt + 1 + if cls.lt is not None: + assert max_value is None, 'Set `lt` or `le`, but not both' + max_value = cls.lt - 1 + + if cls.multiple_of is None or cls.multiple_of == 1: + return st.integers(min_value, max_value) + + # These adjustments and the .map handle integer-valued multiples, while the + # .filter handles trickier cases as for confloat. + if min_value is not None: + min_value = math.ceil(Fraction(min_value) / Fraction(cls.multiple_of)) + if max_value is not None: + max_value = math.floor(Fraction(max_value) / Fraction(cls.multiple_of)) + return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) + + +@resolves(pydantic.ConstrainedDate) +def resolve_condate(cls): # type: ignore[no-untyped-def] + if cls.ge is not None: + assert cls.gt is None, 'Set `gt` or `ge`, but not both' + min_value = cls.ge + elif cls.gt is not None: + min_value = cls.gt + datetime.timedelta(days=1) + else: + min_value = datetime.date.min + if cls.le is not None: + assert cls.lt is None, 'Set `lt` or `le`, but not both' + max_value = cls.le + elif cls.lt is not None: + max_value = cls.lt - datetime.timedelta(days=1) + else: + max_value = datetime.date.max + return st.dates(min_value, max_value) + + +@resolves(pydantic.ConstrainedStr) +def resolve_constr(cls): # type: ignore[no-untyped-def] # pragma: no cover + min_size = cls.min_length or 0 + max_size = cls.max_length + + if cls.regex is None and not cls.strip_whitespace: + return st.text(min_size=min_size, max_size=max_size) + + if cls.regex is not None: + strategy = st.from_regex(cls.regex) + if cls.strip_whitespace: + strategy = strategy.filter(lambda s: s == s.strip()) + elif cls.strip_whitespace: + repeats = '{{{},{}}}'.format( + min_size - 2 if min_size > 2 else 0, + max_size - 2 if (max_size or 0) > 2 else '', + ) + if min_size >= 2: + strategy = st.from_regex(rf'\W.{repeats}\W') + elif min_size == 1: + strategy = st.from_regex(rf'\W(.{repeats}\W)?') + else: + assert min_size == 0 + strategy = st.from_regex(rf'(\W(.{repeats}\W)?)?') + + if min_size == 0 and max_size is None: + return strategy + elif max_size is None: + return strategy.filter(lambda s: min_size <= len(s)) + return strategy.filter(lambda s: min_size <= len(s) <= max_size) + + +# Finally, register all previously-defined types, and patch in our new function +for typ in list(pydantic.types._DEFINED_TYPES): + _registered(typ) +pydantic.types._registered = _registered +st.register_type_strategy(pydantic.Json, resolve_json) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py new file mode 100644 index 0000000..d9eaaaf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py @@ -0,0 +1,72 @@ +import sys +from typing import TYPE_CHECKING, Any, Dict, FrozenSet, NamedTuple, Type + +from pydantic.v1.fields import Required +from pydantic.v1.main import BaseModel, create_model +from pydantic.v1.typing import is_typeddict, is_typeddict_special + +if TYPE_CHECKING: + from typing_extensions import TypedDict + +if sys.version_info < (3, 11): + + def is_legacy_typeddict(typeddict_cls: Type['TypedDict']) -> bool: # type: ignore[valid-type] + return is_typeddict(typeddict_cls) and type(typeddict_cls).__module__ == 'typing' + +else: + + def is_legacy_typeddict(_: Any) -> Any: + return False + + +def create_model_from_typeddict( + # Mypy bug: `Type[TypedDict]` is resolved as `Any` https://github.com/python/mypy/issues/11030 + typeddict_cls: Type['TypedDict'], # type: ignore[valid-type] + **kwargs: Any, +) -> Type['BaseModel']: + """ + Create a `BaseModel` based on the fields of a `TypedDict`. + Since `typing.TypedDict` in Python 3.8 does not store runtime information about optional keys, + we raise an error if this happens (see https://bugs.python.org/issue38834). + """ + field_definitions: Dict[str, Any] + + # Best case scenario: with python 3.9+ or when `TypedDict` is imported from `typing_extensions` + if not hasattr(typeddict_cls, '__required_keys__'): + raise TypeError( + 'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.9.2. ' + 'Without it, there is no way to differentiate required and optional fields when subclassed.' + ) + + if is_legacy_typeddict(typeddict_cls) and any( + is_typeddict_special(t) for t in typeddict_cls.__annotations__.values() + ): + raise TypeError( + 'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.11. ' + 'Without it, there is no way to reflect Required/NotRequired keys.' + ) + + required_keys: FrozenSet[str] = typeddict_cls.__required_keys__ # type: ignore[attr-defined] + field_definitions = { + field_name: (field_type, Required if field_name in required_keys else None) + for field_name, field_type in typeddict_cls.__annotations__.items() + } + + return create_model(typeddict_cls.__name__, **kwargs, **field_definitions) + + +def create_model_from_namedtuple(namedtuple_cls: Type['NamedTuple'], **kwargs: Any) -> Type['BaseModel']: + """ + Create a `BaseModel` based on the fields of a named tuple. + A named tuple can be created with `typing.NamedTuple` and declared annotations + but also with `collections.namedtuple`, in this case we consider all fields + to have type `Any`. + """ + # With python 3.10+, `__annotations__` always exists but can be empty hence the `getattr... or...` logic + namedtuple_annotations: Dict[str, Type[Any]] = getattr(namedtuple_cls, '__annotations__', None) or { + k: Any for k in namedtuple_cls._fields + } + field_definitions: Dict[str, Any] = { + field_name: (field_type, Required) for field_name, field_type in namedtuple_annotations.items() + } + return create_model(namedtuple_cls.__name__, **kwargs, **field_definitions) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py new file mode 100644 index 0000000..2f68fc8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py @@ -0,0 +1,361 @@ +import warnings +from collections import ChainMap +from functools import partial, partialmethod, wraps +from itertools import chain +from types import FunctionType +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, Union, overload + +from pydantic.v1.errors import ConfigError +from pydantic.v1.typing import AnyCallable +from pydantic.v1.utils import ROOT_KEY, in_ipython + +if TYPE_CHECKING: + from pydantic.v1.typing import AnyClassMethod + + +class Validator: + __slots__ = 'func', 'pre', 'each_item', 'always', 'check_fields', 'skip_on_failure' + + def __init__( + self, + func: AnyCallable, + pre: bool = False, + each_item: bool = False, + always: bool = False, + check_fields: bool = False, + skip_on_failure: bool = False, + ): + self.func = func + self.pre = pre + self.each_item = each_item + self.always = always + self.check_fields = check_fields + self.skip_on_failure = skip_on_failure + + +if TYPE_CHECKING: + from inspect import Signature + + from pydantic.v1.config import BaseConfig + from pydantic.v1.fields import ModelField + from pydantic.v1.types import ModelOrDc + + ValidatorCallable = Callable[[Optional[ModelOrDc], Any, Dict[str, Any], ModelField, Type[BaseConfig]], Any] + ValidatorsList = List[ValidatorCallable] + ValidatorListDict = Dict[str, List[Validator]] + +_FUNCS: Set[str] = set() +VALIDATOR_CONFIG_KEY = '__validator_config__' +ROOT_VALIDATOR_CONFIG_KEY = '__root_validator_config__' + + +def validator( + *fields: str, + pre: bool = False, + each_item: bool = False, + always: bool = False, + check_fields: bool = True, + whole: Optional[bool] = None, + allow_reuse: bool = False, +) -> Callable[[AnyCallable], 'AnyClassMethod']: + """ + Decorate methods on the class indicating that they should be used to validate fields + :param fields: which field(s) the method should be called on + :param pre: whether or not this validator should be called before the standard validators (else after) + :param each_item: for complex objects (sets, lists etc.) whether to validate individual elements rather than the + whole object + :param always: whether this method and other validators should be called even if the value is missing + :param check_fields: whether to check that the fields actually exist on the model + :param allow_reuse: whether to track and raise an error if another validator refers to the decorated function + """ + if not fields: + raise ConfigError('validator with no fields specified') + elif isinstance(fields[0], FunctionType): + raise ConfigError( + "validators should be used with fields and keyword arguments, not bare. " # noqa: Q000 + "E.g. usage should be `@validator('', ...)`" + ) + elif not all(isinstance(field, str) for field in fields): + raise ConfigError( + "validator fields should be passed as separate string args. " # noqa: Q000 + "E.g. usage should be `@validator('', '', ...)`" + ) + + if whole is not None: + warnings.warn( + 'The "whole" keyword argument is deprecated, use "each_item" (inverse meaning, default False) instead', + DeprecationWarning, + ) + assert each_item is False, '"each_item" and "whole" conflict, remove "whole"' + each_item = not whole + + def dec(f: AnyCallable) -> 'AnyClassMethod': + f_cls = _prepare_validator(f, allow_reuse) + setattr( + f_cls, + VALIDATOR_CONFIG_KEY, + ( + fields, + Validator(func=f_cls.__func__, pre=pre, each_item=each_item, always=always, check_fields=check_fields), + ), + ) + return f_cls + + return dec + + +@overload +def root_validator(_func: AnyCallable) -> 'AnyClassMethod': + ... + + +@overload +def root_validator( + *, pre: bool = False, allow_reuse: bool = False, skip_on_failure: bool = False +) -> Callable[[AnyCallable], 'AnyClassMethod']: + ... + + +def root_validator( + _func: Optional[AnyCallable] = None, *, pre: bool = False, allow_reuse: bool = False, skip_on_failure: bool = False +) -> Union['AnyClassMethod', Callable[[AnyCallable], 'AnyClassMethod']]: + """ + Decorate methods on a model indicating that they should be used to validate (and perhaps modify) data either + before or after standard model parsing/validation is performed. + """ + if _func: + f_cls = _prepare_validator(_func, allow_reuse) + setattr( + f_cls, ROOT_VALIDATOR_CONFIG_KEY, Validator(func=f_cls.__func__, pre=pre, skip_on_failure=skip_on_failure) + ) + return f_cls + + def dec(f: AnyCallable) -> 'AnyClassMethod': + f_cls = _prepare_validator(f, allow_reuse) + setattr( + f_cls, ROOT_VALIDATOR_CONFIG_KEY, Validator(func=f_cls.__func__, pre=pre, skip_on_failure=skip_on_failure) + ) + return f_cls + + return dec + + +def _prepare_validator(function: AnyCallable, allow_reuse: bool) -> 'AnyClassMethod': + """ + Avoid validators with duplicated names since without this, validators can be overwritten silently + which generally isn't the intended behaviour, don't run in ipython (see #312) or if allow_reuse is False. + """ + f_cls = function if isinstance(function, classmethod) else classmethod(function) + if not in_ipython() and not allow_reuse: + ref = ( + getattr(f_cls.__func__, '__module__', '') + + '.' + + getattr(f_cls.__func__, '__qualname__', f'') + ) + if ref in _FUNCS: + raise ConfigError(f'duplicate validator function "{ref}"; if this is intended, set `allow_reuse=True`') + _FUNCS.add(ref) + return f_cls + + +class ValidatorGroup: + def __init__(self, validators: 'ValidatorListDict') -> None: + self.validators = validators + self.used_validators = {'*'} + + def get_validators(self, name: str) -> Optional[Dict[str, Validator]]: + self.used_validators.add(name) + validators = self.validators.get(name, []) + if name != ROOT_KEY: + validators += self.validators.get('*', []) + if validators: + return {getattr(v.func, '__name__', f''): v for v in validators} + else: + return None + + def check_for_unused(self) -> None: + unused_validators = set( + chain.from_iterable( + ( + getattr(v.func, '__name__', f'') + for v in self.validators[f] + if v.check_fields + ) + for f in (self.validators.keys() - self.used_validators) + ) + ) + if unused_validators: + fn = ', '.join(unused_validators) + raise ConfigError( + f"Validators defined with incorrect fields: {fn} " # noqa: Q000 + f"(use check_fields=False if you're inheriting from the model and intended this)" + ) + + +def extract_validators(namespace: Dict[str, Any]) -> Dict[str, List[Validator]]: + validators: Dict[str, List[Validator]] = {} + for var_name, value in namespace.items(): + validator_config = getattr(value, VALIDATOR_CONFIG_KEY, None) + if validator_config: + fields, v = validator_config + for field in fields: + if field in validators: + validators[field].append(v) + else: + validators[field] = [v] + return validators + + +def extract_root_validators(namespace: Dict[str, Any]) -> Tuple[List[AnyCallable], List[Tuple[bool, AnyCallable]]]: + from inspect import signature + + pre_validators: List[AnyCallable] = [] + post_validators: List[Tuple[bool, AnyCallable]] = [] + for name, value in namespace.items(): + validator_config: Optional[Validator] = getattr(value, ROOT_VALIDATOR_CONFIG_KEY, None) + if validator_config: + sig = signature(validator_config.func) + args = list(sig.parameters.keys()) + if args[0] == 'self': + raise ConfigError( + f'Invalid signature for root validator {name}: {sig}, "self" not permitted as first argument, ' + f'should be: (cls, values).' + ) + if len(args) != 2: + raise ConfigError(f'Invalid signature for root validator {name}: {sig}, should be: (cls, values).') + # check function signature + if validator_config.pre: + pre_validators.append(validator_config.func) + else: + post_validators.append((validator_config.skip_on_failure, validator_config.func)) + return pre_validators, post_validators + + +def inherit_validators(base_validators: 'ValidatorListDict', validators: 'ValidatorListDict') -> 'ValidatorListDict': + for field, field_validators in base_validators.items(): + if field not in validators: + validators[field] = [] + validators[field] += field_validators + return validators + + +def make_generic_validator(validator: AnyCallable) -> 'ValidatorCallable': + """ + Make a generic function which calls a validator with the right arguments. + + Unfortunately other approaches (eg. return a partial of a function that builds the arguments) is slow, + hence this laborious way of doing things. + + It's done like this so validators don't all need **kwargs in their signature, eg. any combination of + the arguments "values", "fields" and/or "config" are permitted. + """ + from inspect import signature + + if not isinstance(validator, (partial, partialmethod)): + # This should be the default case, so overhead is reduced + sig = signature(validator) + args = list(sig.parameters.keys()) + else: + # Fix the generated argument lists of partial methods + sig = signature(validator.func) + args = [ + k + for k in signature(validator.func).parameters.keys() + if k not in validator.args | validator.keywords.keys() + ] + + first_arg = args.pop(0) + if first_arg == 'self': + raise ConfigError( + f'Invalid signature for validator {validator}: {sig}, "self" not permitted as first argument, ' + f'should be: (cls, value, values, config, field), "values", "config" and "field" are all optional.' + ) + elif first_arg == 'cls': + # assume the second argument is value + return wraps(validator)(_generic_validator_cls(validator, sig, set(args[1:]))) + else: + # assume the first argument was value which has already been removed + return wraps(validator)(_generic_validator_basic(validator, sig, set(args))) + + +def prep_validators(v_funcs: Iterable[AnyCallable]) -> 'ValidatorsList': + return [make_generic_validator(f) for f in v_funcs if f] + + +all_kwargs = {'values', 'field', 'config'} + + +def _generic_validator_cls(validator: AnyCallable, sig: 'Signature', args: Set[str]) -> 'ValidatorCallable': + # assume the first argument is value + has_kwargs = False + if 'kwargs' in args: + has_kwargs = True + args -= {'kwargs'} + + if not args.issubset(all_kwargs): + raise ConfigError( + f'Invalid signature for validator {validator}: {sig}, should be: ' + f'(cls, value, values, config, field), "values", "config" and "field" are all optional.' + ) + + if has_kwargs: + return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field, config=config) + elif args == set(): + return lambda cls, v, values, field, config: validator(cls, v) + elif args == {'values'}: + return lambda cls, v, values, field, config: validator(cls, v, values=values) + elif args == {'field'}: + return lambda cls, v, values, field, config: validator(cls, v, field=field) + elif args == {'config'}: + return lambda cls, v, values, field, config: validator(cls, v, config=config) + elif args == {'values', 'field'}: + return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field) + elif args == {'values', 'config'}: + return lambda cls, v, values, field, config: validator(cls, v, values=values, config=config) + elif args == {'field', 'config'}: + return lambda cls, v, values, field, config: validator(cls, v, field=field, config=config) + else: + # args == {'values', 'field', 'config'} + return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field, config=config) + + +def _generic_validator_basic(validator: AnyCallable, sig: 'Signature', args: Set[str]) -> 'ValidatorCallable': + has_kwargs = False + if 'kwargs' in args: + has_kwargs = True + args -= {'kwargs'} + + if not args.issubset(all_kwargs): + raise ConfigError( + f'Invalid signature for validator {validator}: {sig}, should be: ' + f'(value, values, config, field), "values", "config" and "field" are all optional.' + ) + + if has_kwargs: + return lambda cls, v, values, field, config: validator(v, values=values, field=field, config=config) + elif args == set(): + return lambda cls, v, values, field, config: validator(v) + elif args == {'values'}: + return lambda cls, v, values, field, config: validator(v, values=values) + elif args == {'field'}: + return lambda cls, v, values, field, config: validator(v, field=field) + elif args == {'config'}: + return lambda cls, v, values, field, config: validator(v, config=config) + elif args == {'values', 'field'}: + return lambda cls, v, values, field, config: validator(v, values=values, field=field) + elif args == {'values', 'config'}: + return lambda cls, v, values, field, config: validator(v, values=values, config=config) + elif args == {'field', 'config'}: + return lambda cls, v, values, field, config: validator(v, field=field, config=config) + else: + # args == {'values', 'field', 'config'} + return lambda cls, v, values, field, config: validator(v, values=values, field=field, config=config) + + +def gather_all_validators(type_: 'ModelOrDc') -> Dict[str, 'AnyClassMethod']: + all_attributes = ChainMap(*[cls.__dict__ for cls in type_.__mro__]) # type: ignore[arg-type,var-annotated] + return { + k: v + for k, v in all_attributes.items() + if hasattr(v, VALIDATOR_CONFIG_KEY) or hasattr(v, ROOT_VALIDATOR_CONFIG_KEY) + } diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py new file mode 100644 index 0000000..b0bbf78 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py @@ -0,0 +1,494 @@ +""" +Color definitions are used as per CSS3 specification: +http://www.w3.org/TR/css3-color/#svg-color + +A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`. + +In these cases the LAST color when sorted alphabetically takes preferences, +eg. Color((0, 255, 255)).as_named() == 'cyan' because "cyan" comes after "aqua". +""" +import math +import re +from colorsys import hls_to_rgb, rgb_to_hls +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union, cast + +from pydantic.v1.errors import ColorError +from pydantic.v1.utils import Representation, almost_equal_floats + +if TYPE_CHECKING: + from pydantic.v1.typing import CallableGenerator, ReprArgs + +ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]] +ColorType = Union[ColorTuple, str] +HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]] + + +class RGBA: + """ + Internal use only as a representation of a color. + """ + + __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' + + def __init__(self, r: float, g: float, b: float, alpha: Optional[float]): + self.r = r + self.g = g + self.b = b + self.alpha = alpha + + self._tuple: Tuple[float, float, float, Optional[float]] = (r, g, b, alpha) + + def __getitem__(self, item: Any) -> Any: + return self._tuple[item] + + +# these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached +r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*' +r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*' +_r_255 = r'(\d{1,3}(?:\.\d+)?)' +_r_comma = r'\s*,\s*' +r_rgb = fr'\s*rgb\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}\)\s*' +_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)' +r_rgba = fr'\s*rgba\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_alpha}\s*\)\s*' +_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?' +_r_sl = r'(\d{1,3}(?:\.\d+)?)%' +r_hsl = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}\s*\)\s*' +r_hsla = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}{_r_comma}{_r_alpha}\s*\)\s*' + +# colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used +repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'} +rads = 2 * math.pi + + +class Color(Representation): + __slots__ = '_original', '_rgba' + + def __init__(self, value: ColorType) -> None: + self._rgba: RGBA + self._original: ColorType + if isinstance(value, (tuple, list)): + self._rgba = parse_tuple(value) + elif isinstance(value, str): + self._rgba = parse_str(value) + elif isinstance(value, Color): + self._rgba = value._rgba + value = value._original + else: + raise ColorError(reason='value must be a tuple, list or string') + + # if we've got here value must be a valid color + self._original = value + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='color') + + def original(self) -> ColorType: + """ + Original value passed to Color + """ + return self._original + + def as_named(self, *, fallback: bool = False) -> str: + if self._rgba.alpha is None: + rgb = cast(Tuple[int, int, int], self.as_rgb_tuple()) + try: + return COLORS_BY_VALUE[rgb] + except KeyError as e: + if fallback: + return self.as_hex() + else: + raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e + else: + return self.as_hex() + + def as_hex(self) -> str: + """ + Hex string representing the color can be 3, 4, 6 or 8 characters depending on whether the string + a "short" representation of the color is possible and whether there's an alpha channel. + """ + values = [float_to_255(c) for c in self._rgba[:3]] + if self._rgba.alpha is not None: + values.append(float_to_255(self._rgba.alpha)) + + as_hex = ''.join(f'{v:02x}' for v in values) + if all(c in repeat_colors for c in values): + as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2)) + return '#' + as_hex + + def as_rgb(self) -> str: + """ + Color as an rgb(, , ) or rgba(, , , ) string. + """ + if self._rgba.alpha is None: + return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})' + else: + return ( + f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, ' + f'{round(self._alpha_float(), 2)})' + ) + + def as_rgb_tuple(self, *, alpha: Optional[bool] = None) -> ColorTuple: + """ + Color as an RGB or RGBA tuple; red, green and blue are in the range 0 to 255, alpha if included is + in the range 0 to 1. + + :param alpha: whether to include the alpha channel, options are + None - (default) include alpha only if it's set (e.g. not None) + True - always include alpha, + False - always omit alpha, + """ + r, g, b = (float_to_255(c) for c in self._rgba[:3]) + if alpha is None: + if self._rgba.alpha is None: + return r, g, b + else: + return r, g, b, self._alpha_float() + elif alpha: + return r, g, b, self._alpha_float() + else: + # alpha is False + return r, g, b + + def as_hsl(self) -> str: + """ + Color as an hsl(, , ) or hsl(, , , ) string. + """ + if self._rgba.alpha is None: + h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore + return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})' + else: + h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore + return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})' + + def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple: + """ + Color as an HSL or HSLA tuple, e.g. hue, saturation, lightness and optionally alpha; all elements are in + the range 0 to 1. + + NOTE: this is HSL as used in HTML and most other places, not HLS as used in python's colorsys. + + :param alpha: whether to include the alpha channel, options are + None - (default) include alpha only if it's set (e.g. not None) + True - always include alpha, + False - always omit alpha, + """ + h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) + if alpha is None: + if self._rgba.alpha is None: + return h, s, l + else: + return h, s, l, self._alpha_float() + if alpha: + return h, s, l, self._alpha_float() + else: + # alpha is False + return h, s, l + + def _alpha_float(self) -> float: + return 1 if self._rgba.alpha is None else self._rgba.alpha + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls + + def __str__(self) -> str: + return self.as_named(fallback=True) + + def __repr_args__(self) -> 'ReprArgs': + return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] # type: ignore + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() + + def __hash__(self) -> int: + return hash(self.as_rgb_tuple()) + + +def parse_tuple(value: Tuple[Any, ...]) -> RGBA: + """ + Parse a tuple or list as a color. + """ + if len(value) == 3: + r, g, b = (parse_color_value(v) for v in value) + return RGBA(r, g, b, None) + elif len(value) == 4: + r, g, b = (parse_color_value(v) for v in value[:3]) + return RGBA(r, g, b, parse_float_alpha(value[3])) + else: + raise ColorError(reason='tuples must have length 3 or 4') + + +def parse_str(value: str) -> RGBA: + """ + Parse a string to an RGBA tuple, trying the following formats (in this order): + * named color, see COLORS_BY_NAME below + * hex short eg. `fff` (prefix can be `#`, `0x` or nothing) + * hex long eg. `ffffff` (prefix can be `#`, `0x` or nothing) + * `rgb(, , ) ` + * `rgba(, , , )` + """ + value_lower = value.lower() + try: + r, g, b = COLORS_BY_NAME[value_lower] + except KeyError: + pass + else: + return ints_to_rgba(r, g, b, None) + + m = re.fullmatch(r_hex_short, value_lower) + if m: + *rgb, a = m.groups() + r, g, b = (int(v * 2, 16) for v in rgb) + if a: + alpha: Optional[float] = int(a * 2, 16) / 255 + else: + alpha = None + return ints_to_rgba(r, g, b, alpha) + + m = re.fullmatch(r_hex_long, value_lower) + if m: + *rgb, a = m.groups() + r, g, b = (int(v, 16) for v in rgb) + if a: + alpha = int(a, 16) / 255 + else: + alpha = None + return ints_to_rgba(r, g, b, alpha) + + m = re.fullmatch(r_rgb, value_lower) + if m: + return ints_to_rgba(*m.groups(), None) # type: ignore + + m = re.fullmatch(r_rgba, value_lower) + if m: + return ints_to_rgba(*m.groups()) # type: ignore + + m = re.fullmatch(r_hsl, value_lower) + if m: + h, h_units, s, l_ = m.groups() + return parse_hsl(h, h_units, s, l_) + + m = re.fullmatch(r_hsla, value_lower) + if m: + h, h_units, s, l_, a = m.groups() + return parse_hsl(h, h_units, s, l_, parse_float_alpha(a)) + + raise ColorError(reason='string not recognised as a valid color') + + +def ints_to_rgba(r: Union[int, str], g: Union[int, str], b: Union[int, str], alpha: Optional[float]) -> RGBA: + return RGBA(parse_color_value(r), parse_color_value(g), parse_color_value(b), parse_float_alpha(alpha)) + + +def parse_color_value(value: Union[int, str], max_val: int = 255) -> float: + """ + Parse a value checking it's a valid int in the range 0 to max_val and divide by max_val to give a number + in the range 0 to 1 + """ + try: + color = float(value) + except ValueError: + raise ColorError(reason='color values must be a valid number') + if 0 <= color <= max_val: + return color / max_val + else: + raise ColorError(reason=f'color values must be in the range 0 to {max_val}') + + +def parse_float_alpha(value: Union[None, str, float, int]) -> Optional[float]: + """ + Parse a value checking it's a valid float in the range 0 to 1 + """ + if value is None: + return None + try: + if isinstance(value, str) and value.endswith('%'): + alpha = float(value[:-1]) / 100 + else: + alpha = float(value) + except ValueError: + raise ColorError(reason='alpha values must be a valid float') + + if almost_equal_floats(alpha, 1): + return None + elif 0 <= alpha <= 1: + return alpha + else: + raise ColorError(reason='alpha values must be in the range 0 to 1') + + +def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: Optional[float] = None) -> RGBA: + """ + Parse raw hue, saturation, lightness and alpha values and convert to RGBA. + """ + s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) + + h_value = float(h) + if h_units in {None, 'deg'}: + h_value = h_value % 360 / 360 + elif h_units == 'rad': + h_value = h_value % rads / rads + else: + # turns + h_value = h_value % 1 + + r, g, b = hls_to_rgb(h_value, l_value, s_value) + return RGBA(r, g, b, alpha) + + +def float_to_255(c: float) -> int: + return int(round(c * 255)) + + +COLORS_BY_NAME = { + 'aliceblue': (240, 248, 255), + 'antiquewhite': (250, 235, 215), + 'aqua': (0, 255, 255), + 'aquamarine': (127, 255, 212), + 'azure': (240, 255, 255), + 'beige': (245, 245, 220), + 'bisque': (255, 228, 196), + 'black': (0, 0, 0), + 'blanchedalmond': (255, 235, 205), + 'blue': (0, 0, 255), + 'blueviolet': (138, 43, 226), + 'brown': (165, 42, 42), + 'burlywood': (222, 184, 135), + 'cadetblue': (95, 158, 160), + 'chartreuse': (127, 255, 0), + 'chocolate': (210, 105, 30), + 'coral': (255, 127, 80), + 'cornflowerblue': (100, 149, 237), + 'cornsilk': (255, 248, 220), + 'crimson': (220, 20, 60), + 'cyan': (0, 255, 255), + 'darkblue': (0, 0, 139), + 'darkcyan': (0, 139, 139), + 'darkgoldenrod': (184, 134, 11), + 'darkgray': (169, 169, 169), + 'darkgreen': (0, 100, 0), + 'darkgrey': (169, 169, 169), + 'darkkhaki': (189, 183, 107), + 'darkmagenta': (139, 0, 139), + 'darkolivegreen': (85, 107, 47), + 'darkorange': (255, 140, 0), + 'darkorchid': (153, 50, 204), + 'darkred': (139, 0, 0), + 'darksalmon': (233, 150, 122), + 'darkseagreen': (143, 188, 143), + 'darkslateblue': (72, 61, 139), + 'darkslategray': (47, 79, 79), + 'darkslategrey': (47, 79, 79), + 'darkturquoise': (0, 206, 209), + 'darkviolet': (148, 0, 211), + 'deeppink': (255, 20, 147), + 'deepskyblue': (0, 191, 255), + 'dimgray': (105, 105, 105), + 'dimgrey': (105, 105, 105), + 'dodgerblue': (30, 144, 255), + 'firebrick': (178, 34, 34), + 'floralwhite': (255, 250, 240), + 'forestgreen': (34, 139, 34), + 'fuchsia': (255, 0, 255), + 'gainsboro': (220, 220, 220), + 'ghostwhite': (248, 248, 255), + 'gold': (255, 215, 0), + 'goldenrod': (218, 165, 32), + 'gray': (128, 128, 128), + 'green': (0, 128, 0), + 'greenyellow': (173, 255, 47), + 'grey': (128, 128, 128), + 'honeydew': (240, 255, 240), + 'hotpink': (255, 105, 180), + 'indianred': (205, 92, 92), + 'indigo': (75, 0, 130), + 'ivory': (255, 255, 240), + 'khaki': (240, 230, 140), + 'lavender': (230, 230, 250), + 'lavenderblush': (255, 240, 245), + 'lawngreen': (124, 252, 0), + 'lemonchiffon': (255, 250, 205), + 'lightblue': (173, 216, 230), + 'lightcoral': (240, 128, 128), + 'lightcyan': (224, 255, 255), + 'lightgoldenrodyellow': (250, 250, 210), + 'lightgray': (211, 211, 211), + 'lightgreen': (144, 238, 144), + 'lightgrey': (211, 211, 211), + 'lightpink': (255, 182, 193), + 'lightsalmon': (255, 160, 122), + 'lightseagreen': (32, 178, 170), + 'lightskyblue': (135, 206, 250), + 'lightslategray': (119, 136, 153), + 'lightslategrey': (119, 136, 153), + 'lightsteelblue': (176, 196, 222), + 'lightyellow': (255, 255, 224), + 'lime': (0, 255, 0), + 'limegreen': (50, 205, 50), + 'linen': (250, 240, 230), + 'magenta': (255, 0, 255), + 'maroon': (128, 0, 0), + 'mediumaquamarine': (102, 205, 170), + 'mediumblue': (0, 0, 205), + 'mediumorchid': (186, 85, 211), + 'mediumpurple': (147, 112, 219), + 'mediumseagreen': (60, 179, 113), + 'mediumslateblue': (123, 104, 238), + 'mediumspringgreen': (0, 250, 154), + 'mediumturquoise': (72, 209, 204), + 'mediumvioletred': (199, 21, 133), + 'midnightblue': (25, 25, 112), + 'mintcream': (245, 255, 250), + 'mistyrose': (255, 228, 225), + 'moccasin': (255, 228, 181), + 'navajowhite': (255, 222, 173), + 'navy': (0, 0, 128), + 'oldlace': (253, 245, 230), + 'olive': (128, 128, 0), + 'olivedrab': (107, 142, 35), + 'orange': (255, 165, 0), + 'orangered': (255, 69, 0), + 'orchid': (218, 112, 214), + 'palegoldenrod': (238, 232, 170), + 'palegreen': (152, 251, 152), + 'paleturquoise': (175, 238, 238), + 'palevioletred': (219, 112, 147), + 'papayawhip': (255, 239, 213), + 'peachpuff': (255, 218, 185), + 'peru': (205, 133, 63), + 'pink': (255, 192, 203), + 'plum': (221, 160, 221), + 'powderblue': (176, 224, 230), + 'purple': (128, 0, 128), + 'red': (255, 0, 0), + 'rosybrown': (188, 143, 143), + 'royalblue': (65, 105, 225), + 'saddlebrown': (139, 69, 19), + 'salmon': (250, 128, 114), + 'sandybrown': (244, 164, 96), + 'seagreen': (46, 139, 87), + 'seashell': (255, 245, 238), + 'sienna': (160, 82, 45), + 'silver': (192, 192, 192), + 'skyblue': (135, 206, 235), + 'slateblue': (106, 90, 205), + 'slategray': (112, 128, 144), + 'slategrey': (112, 128, 144), + 'snow': (255, 250, 250), + 'springgreen': (0, 255, 127), + 'steelblue': (70, 130, 180), + 'tan': (210, 180, 140), + 'teal': (0, 128, 128), + 'thistle': (216, 191, 216), + 'tomato': (255, 99, 71), + 'turquoise': (64, 224, 208), + 'violet': (238, 130, 238), + 'wheat': (245, 222, 179), + 'white': (255, 255, 255), + 'whitesmoke': (245, 245, 245), + 'yellow': (255, 255, 0), + 'yellowgreen': (154, 205, 50), +} + +COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py new file mode 100644 index 0000000..18f7c99 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py @@ -0,0 +1,191 @@ +import json +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Dict, ForwardRef, Optional, Tuple, Type, Union + +from typing_extensions import Literal, Protocol + +from pydantic.v1.typing import AnyArgTCallable, AnyCallable +from pydantic.v1.utils import GetterDict +from pydantic.v1.version import compiled + +if TYPE_CHECKING: + from typing import overload + + from pydantic.v1.fields import ModelField + from pydantic.v1.main import BaseModel + + ConfigType = Type['BaseConfig'] + + class SchemaExtraCallable(Protocol): + @overload + def __call__(self, schema: Dict[str, Any]) -> None: + pass + + @overload + def __call__(self, schema: Dict[str, Any], model_class: Type[BaseModel]) -> None: + pass + +else: + SchemaExtraCallable = Callable[..., None] + +__all__ = 'BaseConfig', 'ConfigDict', 'get_config', 'Extra', 'inherit_config', 'prepare_config' + + +class Extra(str, Enum): + allow = 'allow' + ignore = 'ignore' + forbid = 'forbid' + + +# https://github.com/cython/cython/issues/4003 +# Fixed in Cython 3 and Pydantic v1 won't support Cython 3. +# Pydantic v2 doesn't depend on Cython at all. +if not compiled: + from typing_extensions import TypedDict + + class ConfigDict(TypedDict, total=False): + title: Optional[str] + anystr_lower: bool + anystr_strip_whitespace: bool + min_anystr_length: int + max_anystr_length: Optional[int] + validate_all: bool + extra: Extra + allow_mutation: bool + frozen: bool + allow_population_by_field_name: bool + use_enum_values: bool + fields: Dict[str, Union[str, Dict[str, str]]] + validate_assignment: bool + error_msg_templates: Dict[str, str] + arbitrary_types_allowed: bool + orm_mode: bool + getter_dict: Type[GetterDict] + alias_generator: Optional[Callable[[str], str]] + keep_untouched: Tuple[type, ...] + schema_extra: Union[Dict[str, object], 'SchemaExtraCallable'] + json_loads: Callable[[str], object] + json_dumps: AnyArgTCallable[str] + json_encoders: Dict[Type[object], AnyCallable] + underscore_attrs_are_private: bool + allow_inf_nan: bool + copy_on_model_validation: Literal['none', 'deep', 'shallow'] + # whether dataclass `__post_init__` should be run after validation + post_init_call: Literal['before_validation', 'after_validation'] + +else: + ConfigDict = dict # type: ignore + + +class BaseConfig: + title: Optional[str] = None + anystr_lower: bool = False + anystr_upper: bool = False + anystr_strip_whitespace: bool = False + min_anystr_length: int = 0 + max_anystr_length: Optional[int] = None + validate_all: bool = False + extra: Extra = Extra.ignore + allow_mutation: bool = True + frozen: bool = False + allow_population_by_field_name: bool = False + use_enum_values: bool = False + fields: Dict[str, Union[str, Dict[str, str]]] = {} + validate_assignment: bool = False + error_msg_templates: Dict[str, str] = {} + arbitrary_types_allowed: bool = False + orm_mode: bool = False + getter_dict: Type[GetterDict] = GetterDict + alias_generator: Optional[Callable[[str], str]] = None + keep_untouched: Tuple[type, ...] = () + schema_extra: Union[Dict[str, Any], 'SchemaExtraCallable'] = {} + json_loads: Callable[[str], Any] = json.loads + json_dumps: Callable[..., str] = json.dumps + json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable] = {} + underscore_attrs_are_private: bool = False + allow_inf_nan: bool = True + + # whether inherited models as fields should be reconstructed as base model, + # and whether such a copy should be shallow or deep + copy_on_model_validation: Literal['none', 'deep', 'shallow'] = 'shallow' + + # whether `Union` should check all allowed types before even trying to coerce + smart_union: bool = False + # whether dataclass `__post_init__` should be run before or after validation + post_init_call: Literal['before_validation', 'after_validation'] = 'before_validation' + + @classmethod + def get_field_info(cls, name: str) -> Dict[str, Any]: + """ + Get properties of FieldInfo from the `fields` property of the config class. + """ + + fields_value = cls.fields.get(name) + + if isinstance(fields_value, str): + field_info: Dict[str, Any] = {'alias': fields_value} + elif isinstance(fields_value, dict): + field_info = fields_value + else: + field_info = {} + + if 'alias' in field_info: + field_info.setdefault('alias_priority', 2) + + if field_info.get('alias_priority', 0) <= 1 and cls.alias_generator: + alias = cls.alias_generator(name) + if not isinstance(alias, str): + raise TypeError(f'Config.alias_generator must return str, not {alias.__class__}') + field_info.update(alias=alias, alias_priority=1) + return field_info + + @classmethod + def prepare_field(cls, field: 'ModelField') -> None: + """ + Optional hook to check or modify fields during model creation. + """ + pass + + +def get_config(config: Union[ConfigDict, Type[object], None]) -> Type[BaseConfig]: + if config is None: + return BaseConfig + + else: + config_dict = ( + config + if isinstance(config, dict) + else {k: getattr(config, k) for k in dir(config) if not k.startswith('__')} + ) + + class Config(BaseConfig): + ... + + for k, v in config_dict.items(): + setattr(Config, k, v) + return Config + + +def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType', **namespace: Any) -> 'ConfigType': + if not self_config: + base_classes: Tuple['ConfigType', ...] = (parent_config,) + elif self_config == parent_config: + base_classes = (self_config,) + else: + base_classes = self_config, parent_config + + namespace['json_encoders'] = { + **getattr(parent_config, 'json_encoders', {}), + **getattr(self_config, 'json_encoders', {}), + **namespace.get('json_encoders', {}), + } + + return type('Config', base_classes, namespace) + + +def prepare_config(config: Type[BaseConfig], cls_name: str) -> None: + if not isinstance(config.extra, Extra): + try: + config.extra = Extra(config.extra) + except ValueError: + raise ValueError(f'"{cls_name}": {config.extra} is not a valid value for "extra"') diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py new file mode 100644 index 0000000..bd16702 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py @@ -0,0 +1,500 @@ +""" +The main purpose is to enhance stdlib dataclasses by adding validation +A pydantic dataclass can be generated from scratch or from a stdlib one. + +Behind the scene, a pydantic dataclass is just like a regular one on which we attach +a `BaseModel` and magic methods to trigger the validation of the data. +`__init__` and `__post_init__` are hence overridden and have extra logic to be +able to validate input data. + +When a pydantic dataclass is generated from scratch, it's just a plain dataclass +with validation triggered at initialization + +The tricky part if for stdlib dataclasses that are converted after into pydantic ones e.g. + +```py +@dataclasses.dataclass +class M: + x: int + +ValidatedM = pydantic.dataclasses.dataclass(M) +``` + +We indeed still want to support equality, hashing, repr, ... as if it was the stdlib one! + +```py +assert isinstance(ValidatedM(x=1), M) +assert ValidatedM(x=1) == M(x=1) +``` + +This means we **don't want to create a new dataclass that inherits from it** +The trick is to create a wrapper around `M` that will act as a proxy to trigger +validation without altering default `M` behaviour. +""" +import copy +import dataclasses +import sys +from contextlib import contextmanager +from functools import wraps + +try: + from functools import cached_property +except ImportError: + # cached_property available only for python3.8+ + pass + +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Generator, Optional, Type, TypeVar, Union, overload + +from typing_extensions import dataclass_transform + +from pydantic.v1.class_validators import gather_all_validators +from pydantic.v1.config import BaseConfig, ConfigDict, Extra, get_config +from pydantic.v1.error_wrappers import ValidationError +from pydantic.v1.errors import DataclassTypeError +from pydantic.v1.fields import Field, FieldInfo, Required, Undefined +from pydantic.v1.main import create_model, validate_model +from pydantic.v1.utils import ClassAttribute + +if TYPE_CHECKING: + from pydantic.v1.main import BaseModel + from pydantic.v1.typing import CallableGenerator, NoArgAnyCallable + + DataclassT = TypeVar('DataclassT', bound='Dataclass') + + DataclassClassOrWrapper = Union[Type['Dataclass'], 'DataclassProxy'] + + class Dataclass: + # stdlib attributes + __dataclass_fields__: ClassVar[Dict[str, Any]] + __dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams` + __post_init__: ClassVar[Callable[..., None]] + + # Added by pydantic + __pydantic_run_validation__: ClassVar[bool] + __post_init_post_parse__: ClassVar[Callable[..., None]] + __pydantic_initialised__: ClassVar[bool] + __pydantic_model__: ClassVar[Type[BaseModel]] + __pydantic_validate_values__: ClassVar[Callable[['Dataclass'], None]] + __pydantic_has_field_info_default__: ClassVar[bool] # whether a `pydantic.Field` is used as default value + + def __init__(self, *args: object, **kwargs: object) -> None: + pass + + @classmethod + def __get_validators__(cls: Type['Dataclass']) -> 'CallableGenerator': + pass + + @classmethod + def __validate__(cls: Type['DataclassT'], v: Any) -> 'DataclassT': + pass + + +__all__ = [ + 'dataclass', + 'set_validation', + 'create_pydantic_model_from_dataclass', + 'is_builtin_dataclass', + 'make_dataclass_validator', +] + +_T = TypeVar('_T') + +if sys.version_info >= (3, 10): + + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) + @overload + def dataclass( + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: Union[ConfigDict, Type[object], None] = None, + validate_on_init: Optional[bool] = None, + use_proxy: Optional[bool] = None, + kw_only: bool = ..., + ) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']: + ... + + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) + @overload + def dataclass( + _cls: Type[_T], + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: Union[ConfigDict, Type[object], None] = None, + validate_on_init: Optional[bool] = None, + use_proxy: Optional[bool] = None, + kw_only: bool = ..., + ) -> 'DataclassClassOrWrapper': + ... + +else: + + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) + @overload + def dataclass( + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: Union[ConfigDict, Type[object], None] = None, + validate_on_init: Optional[bool] = None, + use_proxy: Optional[bool] = None, + ) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']: + ... + + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) + @overload + def dataclass( + _cls: Type[_T], + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: Union[ConfigDict, Type[object], None] = None, + validate_on_init: Optional[bool] = None, + use_proxy: Optional[bool] = None, + ) -> 'DataclassClassOrWrapper': + ... + + +@dataclass_transform(field_specifiers=(dataclasses.field, Field)) +def dataclass( + _cls: Optional[Type[_T]] = None, + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + config: Union[ConfigDict, Type[object], None] = None, + validate_on_init: Optional[bool] = None, + use_proxy: Optional[bool] = None, + kw_only: bool = False, +) -> Union[Callable[[Type[_T]], 'DataclassClassOrWrapper'], 'DataclassClassOrWrapper']: + """ + Like the python standard lib dataclasses but with type validation. + The result is either a pydantic dataclass that will validate input data + or a wrapper that will trigger validation around a stdlib dataclass + to avoid modifying it directly + """ + the_config = get_config(config) + + def wrap(cls: Type[Any]) -> 'DataclassClassOrWrapper': + should_use_proxy = ( + use_proxy + if use_proxy is not None + else ( + is_builtin_dataclass(cls) + and (cls.__bases__[0] is object or set(dir(cls)) == set(dir(cls.__bases__[0]))) + ) + ) + if should_use_proxy: + dc_cls_doc = '' + dc_cls = DataclassProxy(cls) + default_validate_on_init = False + else: + dc_cls_doc = cls.__doc__ or '' # needs to be done before generating dataclass + if sys.version_info >= (3, 10): + dc_cls = dataclasses.dataclass( + cls, + init=init, + repr=repr, + eq=eq, + order=order, + unsafe_hash=unsafe_hash, + frozen=frozen, + kw_only=kw_only, + ) + else: + dc_cls = dataclasses.dataclass( # type: ignore + cls, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen + ) + default_validate_on_init = True + + should_validate_on_init = default_validate_on_init if validate_on_init is None else validate_on_init + _add_pydantic_validation_attributes(cls, the_config, should_validate_on_init, dc_cls_doc) + dc_cls.__pydantic_model__.__try_update_forward_refs__(**{cls.__name__: cls}) + return dc_cls + + if _cls is None: + return wrap + + return wrap(_cls) + + +@contextmanager +def set_validation(cls: Type['DataclassT'], value: bool) -> Generator[Type['DataclassT'], None, None]: + original_run_validation = cls.__pydantic_run_validation__ + try: + cls.__pydantic_run_validation__ = value + yield cls + finally: + cls.__pydantic_run_validation__ = original_run_validation + + +class DataclassProxy: + __slots__ = '__dataclass__' + + def __init__(self, dc_cls: Type['Dataclass']) -> None: + object.__setattr__(self, '__dataclass__', dc_cls) + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + with set_validation(self.__dataclass__, True): + return self.__dataclass__(*args, **kwargs) + + def __getattr__(self, name: str) -> Any: + return getattr(self.__dataclass__, name) + + def __setattr__(self, __name: str, __value: Any) -> None: + return setattr(self.__dataclass__, __name, __value) + + def __instancecheck__(self, instance: Any) -> bool: + return isinstance(instance, self.__dataclass__) + + def __copy__(self) -> 'DataclassProxy': + return DataclassProxy(copy.copy(self.__dataclass__)) + + def __deepcopy__(self, memo: Any) -> 'DataclassProxy': + return DataclassProxy(copy.deepcopy(self.__dataclass__, memo)) + + +def _add_pydantic_validation_attributes( # noqa: C901 (ignore complexity) + dc_cls: Type['Dataclass'], + config: Type[BaseConfig], + validate_on_init: bool, + dc_cls_doc: str, +) -> None: + """ + We need to replace the right method. If no `__post_init__` has been set in the stdlib dataclass + it won't even exist (code is generated on the fly by `dataclasses`) + By default, we run validation after `__init__` or `__post_init__` if defined + """ + init = dc_cls.__init__ + + @wraps(init) + def handle_extra_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None: + if config.extra == Extra.ignore: + init(self, *args, **{k: v for k, v in kwargs.items() if k in self.__dataclass_fields__}) + + elif config.extra == Extra.allow: + for k, v in kwargs.items(): + self.__dict__.setdefault(k, v) + init(self, *args, **{k: v for k, v in kwargs.items() if k in self.__dataclass_fields__}) + + else: + init(self, *args, **kwargs) + + if hasattr(dc_cls, '__post_init__'): + try: + post_init = dc_cls.__post_init__.__wrapped__ # type: ignore[attr-defined] + except AttributeError: + post_init = dc_cls.__post_init__ + + @wraps(post_init) + def new_post_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None: + if config.post_init_call == 'before_validation': + post_init(self, *args, **kwargs) + + if self.__class__.__pydantic_run_validation__: + self.__pydantic_validate_values__() + if hasattr(self, '__post_init_post_parse__'): + self.__post_init_post_parse__(*args, **kwargs) + + if config.post_init_call == 'after_validation': + post_init(self, *args, **kwargs) + + setattr(dc_cls, '__init__', handle_extra_init) + setattr(dc_cls, '__post_init__', new_post_init) + + else: + + @wraps(init) + def new_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None: + handle_extra_init(self, *args, **kwargs) + + if self.__class__.__pydantic_run_validation__: + self.__pydantic_validate_values__() + + if hasattr(self, '__post_init_post_parse__'): + # We need to find again the initvars. To do that we use `__dataclass_fields__` instead of + # public method `dataclasses.fields` + + # get all initvars and their default values + initvars_and_values: Dict[str, Any] = {} + for i, f in enumerate(self.__class__.__dataclass_fields__.values()): + if f._field_type is dataclasses._FIELD_INITVAR: # type: ignore[attr-defined] + try: + # set arg value by default + initvars_and_values[f.name] = args[i] + except IndexError: + initvars_and_values[f.name] = kwargs.get(f.name, f.default) + + self.__post_init_post_parse__(**initvars_and_values) + + setattr(dc_cls, '__init__', new_init) + + setattr(dc_cls, '__pydantic_run_validation__', ClassAttribute('__pydantic_run_validation__', validate_on_init)) + setattr(dc_cls, '__pydantic_initialised__', False) + setattr(dc_cls, '__pydantic_model__', create_pydantic_model_from_dataclass(dc_cls, config, dc_cls_doc)) + setattr(dc_cls, '__pydantic_validate_values__', _dataclass_validate_values) + setattr(dc_cls, '__validate__', classmethod(_validate_dataclass)) + setattr(dc_cls, '__get_validators__', classmethod(_get_validators)) + + if dc_cls.__pydantic_model__.__config__.validate_assignment and not dc_cls.__dataclass_params__.frozen: + setattr(dc_cls, '__setattr__', _dataclass_validate_assignment_setattr) + + +def _get_validators(cls: 'DataclassClassOrWrapper') -> 'CallableGenerator': + yield cls.__validate__ + + +def _validate_dataclass(cls: Type['DataclassT'], v: Any) -> 'DataclassT': + with set_validation(cls, True): + if isinstance(v, cls): + v.__pydantic_validate_values__() + return v + elif isinstance(v, (list, tuple)): + return cls(*v) + elif isinstance(v, dict): + return cls(**v) + else: + raise DataclassTypeError(class_name=cls.__name__) + + +def create_pydantic_model_from_dataclass( + dc_cls: Type['Dataclass'], + config: Type[Any] = BaseConfig, + dc_cls_doc: Optional[str] = None, +) -> Type['BaseModel']: + field_definitions: Dict[str, Any] = {} + for field in dataclasses.fields(dc_cls): + default: Any = Undefined + default_factory: Optional['NoArgAnyCallable'] = None + field_info: FieldInfo + + if field.default is not dataclasses.MISSING: + default = field.default + elif field.default_factory is not dataclasses.MISSING: + default_factory = field.default_factory + else: + default = Required + + if isinstance(default, FieldInfo): + field_info = default + dc_cls.__pydantic_has_field_info_default__ = True + else: + field_info = Field(default=default, default_factory=default_factory, **field.metadata) + + field_definitions[field.name] = (field.type, field_info) + + validators = gather_all_validators(dc_cls) + model: Type['BaseModel'] = create_model( + dc_cls.__name__, + __config__=config, + __module__=dc_cls.__module__, + __validators__=validators, + __cls_kwargs__={'__resolve_forward_refs__': False}, + **field_definitions, + ) + model.__doc__ = dc_cls_doc if dc_cls_doc is not None else dc_cls.__doc__ or '' + return model + + +if sys.version_info >= (3, 8): + + def _is_field_cached_property(obj: 'Dataclass', k: str) -> bool: + return isinstance(getattr(type(obj), k, None), cached_property) + +else: + + def _is_field_cached_property(obj: 'Dataclass', k: str) -> bool: + return False + + +def _dataclass_validate_values(self: 'Dataclass') -> None: + # validation errors can occur if this function is called twice on an already initialised dataclass. + # for example if Extra.forbid is enabled, it would consider __pydantic_initialised__ an invalid extra property + if getattr(self, '__pydantic_initialised__'): + return + if getattr(self, '__pydantic_has_field_info_default__', False): + # We need to remove `FieldInfo` values since they are not valid as input + # It's ok to do that because they are obviously the default values! + input_data = { + k: v + for k, v in self.__dict__.items() + if not (isinstance(v, FieldInfo) or _is_field_cached_property(self, k)) + } + else: + input_data = {k: v for k, v in self.__dict__.items() if not _is_field_cached_property(self, k)} + d, _, validation_error = validate_model(self.__pydantic_model__, input_data, cls=self.__class__) + if validation_error: + raise validation_error + self.__dict__.update(d) + object.__setattr__(self, '__pydantic_initialised__', True) + + +def _dataclass_validate_assignment_setattr(self: 'Dataclass', name: str, value: Any) -> None: + if self.__pydantic_initialised__: + d = dict(self.__dict__) + d.pop(name, None) + known_field = self.__pydantic_model__.__fields__.get(name, None) + if known_field: + value, error_ = known_field.validate(value, d, loc=name, cls=self.__class__) + if error_: + raise ValidationError([error_], self.__class__) + + object.__setattr__(self, name, value) + + +def is_builtin_dataclass(_cls: Type[Any]) -> bool: + """ + Whether a class is a stdlib dataclass + (useful to discriminated a pydantic dataclass that is actually a wrapper around a stdlib dataclass) + + we check that + - `_cls` is a dataclass + - `_cls` is not a processed pydantic dataclass (with a basemodel attached) + - `_cls` is not a pydantic dataclass inheriting directly from a stdlib dataclass + e.g. + ``` + @dataclasses.dataclass + class A: + x: int + + @pydantic.dataclasses.dataclass + class B(A): + y: int + ``` + In this case, when we first check `B`, we make an extra check and look at the annotations ('y'), + which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x') + """ + return ( + dataclasses.is_dataclass(_cls) + and not hasattr(_cls, '__pydantic_model__') + and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {}))) + ) + + +def make_dataclass_validator(dc_cls: Type['Dataclass'], config: Type[BaseConfig]) -> 'CallableGenerator': + """ + Create a pydantic.dataclass from a builtin dataclass to add type validation + and yield the validators + It retrieves the parameters of the dataclass and forwards them to the newly created dataclass + """ + yield from _get_validators(dataclass(dc_cls, config=config, use_proxy=True)) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py new file mode 100644 index 0000000..a7598fc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py @@ -0,0 +1,248 @@ +""" +Functions to parse datetime objects. + +We're using regular expressions rather than time.strptime because: +- They provide both validation and parsing. +- They're more flexible for datetimes. +- The date/datetime/time constructors produce friendlier error messages. + +Stolen from https://raw.githubusercontent.com/django/django/main/django/utils/dateparse.py at +9718fa2e8abe430c3526a9278dd976443d4ae3c6 + +Changed to: +* use standard python datetime types not django.utils.timezone +* raise ValueError when regex doesn't match rather than returning None +* support parsing unix timestamps for dates and datetimes +""" +import re +from datetime import date, datetime, time, timedelta, timezone +from typing import Dict, Optional, Type, Union + +from pydantic.v1 import errors + +date_expr = r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' +time_expr = ( + r'(?P\d{1,2}):(?P\d{1,2})' + r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + r'(?PZ|[+-]\d{2}(?::?\d{2})?)?$' +) + +date_re = re.compile(f'{date_expr}$') +time_re = re.compile(time_expr) +datetime_re = re.compile(f'{date_expr}[T ]{time_expr}') + +standard_duration_re = re.compile( + r'^' + r'(?:(?P-?\d+) (days?, )?)?' + r'((?:(?P-?\d+):)(?=\d+:\d+))?' + r'(?:(?P-?\d+):)?' + r'(?P-?\d+)' + r'(?:\.(?P\d{1,6})\d{0,6})?' + r'$' +) + +# Support the sections of ISO 8601 date representation that are accepted by timedelta +iso8601_duration_re = re.compile( + r'^(?P[-+]?)' + r'P' + r'(?:(?P\d+(.\d+)?)D)?' + r'(?:T' + r'(?:(?P\d+(.\d+)?)H)?' + r'(?:(?P\d+(.\d+)?)M)?' + r'(?:(?P\d+(.\d+)?)S)?' + r')?' + r'$' +) + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) +StrBytesIntFloat = Union[str, bytes, int, float] + + +def get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f'invalid type; expected {native_expected_type}, string, bytes, int or float') + + +def from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str], error: Type[Exception]) -> Union[None, int, timezone]: + if value == 'Z': + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == '-': + offset = -offset + try: + return timezone(timedelta(minutes=offset)) + except ValueError: + raise error() + else: + return None + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = get_numeric(value, 'date') + if number is not None: + return from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + match = date_re.match(value) # type: ignore + if match is None: + raise errors.DateError() + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise errors.DateError() + + +def parse_time(value: Union[time, StrBytesIntFloat]) -> time: + """ + Parse a time/string and return a datetime.time. + + Raise ValueError if the input is well formatted but not a valid time. + Raise ValueError if the input isn't well formatted, in particular if it contains an offset. + """ + if isinstance(value, time): + return value + + number = get_numeric(value, 'time') + if number is not None: + if number >= 86400: + # doesn't make sense since the time time loop back around to 0 + raise errors.TimeError() + return (datetime.min + timedelta(seconds=number)).time() + + if isinstance(value, bytes): + value = value.decode() + + match = time_re.match(value) # type: ignore + if match is None: + raise errors.TimeError() + + kw = match.groupdict() + if kw['microsecond']: + kw['microsecond'] = kw['microsecond'].ljust(6, '0') + + tzinfo = _parse_timezone(kw.pop('tzinfo'), errors.TimeError) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_['tzinfo'] = tzinfo + + try: + return time(**kw_) # type: ignore + except ValueError: + raise errors.TimeError() + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = get_numeric(value, 'datetime') + if number is not None: + return from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + match = datetime_re.match(value) # type: ignore + if match is None: + raise errors.DateTimeError() + + kw = match.groupdict() + if kw['microsecond']: + kw['microsecond'] = kw['microsecond'].ljust(6, '0') + + tzinfo = _parse_timezone(kw.pop('tzinfo'), errors.DateTimeError) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_['tzinfo'] = tzinfo + + try: + return datetime(**kw_) # type: ignore + except ValueError: + raise errors.DateTimeError() + + +def parse_duration(value: StrBytesIntFloat) -> timedelta: + """ + Parse a duration int/float/string and return a datetime.timedelta. + + The preferred format for durations in Django is '%d %H:%M:%S.%f'. + + Also supports ISO 8601 representation. + """ + if isinstance(value, timedelta): + return value + + if isinstance(value, (int, float)): + # below code requires a string + value = f'{value:f}' + elif isinstance(value, bytes): + value = value.decode() + + try: + match = standard_duration_re.match(value) or iso8601_duration_re.match(value) + except TypeError: + raise TypeError('invalid type; expected timedelta, string, bytes, int or float') + + if not match: + raise errors.DurationError() + + kw = match.groupdict() + sign = -1 if kw.pop('sign', '+') == '-' else 1 + if kw.get('microseconds'): + kw['microseconds'] = kw['microseconds'].ljust(6, '0') + + if kw.get('seconds') and kw.get('microseconds') and kw['seconds'].startswith('-'): + kw['microseconds'] = '-' + kw['microseconds'] + + kw_ = {k: float(v) for k, v in kw.items() if v is not None} + + return sign * timedelta(**kw_) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py new file mode 100644 index 0000000..2c7c2c2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py @@ -0,0 +1,264 @@ +from functools import wraps +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, TypeVar, Union, overload + +from pydantic.v1 import validator +from pydantic.v1.config import Extra +from pydantic.v1.errors import ConfigError +from pydantic.v1.main import BaseModel, create_model +from pydantic.v1.typing import get_all_type_hints +from pydantic.v1.utils import to_camel + +__all__ = ('validate_arguments',) + +if TYPE_CHECKING: + from pydantic.v1.typing import AnyCallable + + AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable) + ConfigType = Union[None, Type[Any], Dict[str, Any]] + + +@overload +def validate_arguments(func: None = None, *, config: 'ConfigType' = None) -> Callable[['AnyCallableT'], 'AnyCallableT']: + ... + + +@overload +def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT': + ... + + +def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'ConfigType' = None) -> Any: + """ + Decorator to validate the arguments passed to a function. + """ + + def validate(_func: 'AnyCallable') -> 'AnyCallable': + vd = ValidatedFunction(_func, config) + + @wraps(_func) + def wrapper_function(*args: Any, **kwargs: Any) -> Any: + return vd.call(*args, **kwargs) + + wrapper_function.vd = vd # type: ignore + wrapper_function.validate = vd.init_model_instance # type: ignore + wrapper_function.raw_function = vd.raw_function # type: ignore + wrapper_function.model = vd.model # type: ignore + return wrapper_function + + if func: + return validate(func) + else: + return validate + + +ALT_V_ARGS = 'v__args' +ALT_V_KWARGS = 'v__kwargs' +V_POSITIONAL_ONLY_NAME = 'v__positional_only' +V_DUPLICATE_KWARGS = 'v__duplicate_kwargs' + + +class ValidatedFunction: + def __init__(self, function: 'AnyCallableT', config: 'ConfigType'): # noqa C901 + from inspect import Parameter, signature + + parameters: Mapping[str, Parameter] = signature(function).parameters + + if parameters.keys() & {ALT_V_ARGS, ALT_V_KWARGS, V_POSITIONAL_ONLY_NAME, V_DUPLICATE_KWARGS}: + raise ConfigError( + f'"{ALT_V_ARGS}", "{ALT_V_KWARGS}", "{V_POSITIONAL_ONLY_NAME}" and "{V_DUPLICATE_KWARGS}" ' + f'are not permitted as argument names when using the "{validate_arguments.__name__}" decorator' + ) + + self.raw_function = function + self.arg_mapping: Dict[int, str] = {} + self.positional_only_args = set() + self.v_args_name = 'args' + self.v_kwargs_name = 'kwargs' + + type_hints = get_all_type_hints(function) + takes_args = False + takes_kwargs = False + fields: Dict[str, Tuple[Any, Any]] = {} + for i, (name, p) in enumerate(parameters.items()): + if p.annotation is p.empty: + annotation = Any + else: + annotation = type_hints[name] + + default = ... if p.default is p.empty else p.default + if p.kind == Parameter.POSITIONAL_ONLY: + self.arg_mapping[i] = name + fields[name] = annotation, default + fields[V_POSITIONAL_ONLY_NAME] = List[str], None + self.positional_only_args.add(name) + elif p.kind == Parameter.POSITIONAL_OR_KEYWORD: + self.arg_mapping[i] = name + fields[name] = annotation, default + fields[V_DUPLICATE_KWARGS] = List[str], None + elif p.kind == Parameter.KEYWORD_ONLY: + fields[name] = annotation, default + elif p.kind == Parameter.VAR_POSITIONAL: + self.v_args_name = name + fields[name] = Tuple[annotation, ...], None + takes_args = True + else: + assert p.kind == Parameter.VAR_KEYWORD, p.kind + self.v_kwargs_name = name + fields[name] = Dict[str, annotation], None # type: ignore + takes_kwargs = True + + # these checks avoid a clash between "args" and a field with that name + if not takes_args and self.v_args_name in fields: + self.v_args_name = ALT_V_ARGS + + # same with "kwargs" + if not takes_kwargs and self.v_kwargs_name in fields: + self.v_kwargs_name = ALT_V_KWARGS + + if not takes_args: + # we add the field so validation below can raise the correct exception + fields[self.v_args_name] = List[Any], None + + if not takes_kwargs: + # same with kwargs + fields[self.v_kwargs_name] = Dict[Any, Any], None + + self.create_model(fields, takes_args, takes_kwargs, config) + + def init_model_instance(self, *args: Any, **kwargs: Any) -> BaseModel: + values = self.build_values(args, kwargs) + return self.model(**values) + + def call(self, *args: Any, **kwargs: Any) -> Any: + m = self.init_model_instance(*args, **kwargs) + return self.execute(m) + + def build_values(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Dict[str, Any]: + values: Dict[str, Any] = {} + if args: + arg_iter = enumerate(args) + while True: + try: + i, a = next(arg_iter) + except StopIteration: + break + arg_name = self.arg_mapping.get(i) + if arg_name is not None: + values[arg_name] = a + else: + values[self.v_args_name] = [a] + [a for _, a in arg_iter] + break + + var_kwargs: Dict[str, Any] = {} + wrong_positional_args = [] + duplicate_kwargs = [] + fields_alias = [ + field.alias + for name, field in self.model.__fields__.items() + if name not in (self.v_args_name, self.v_kwargs_name) + ] + non_var_fields = set(self.model.__fields__) - {self.v_args_name, self.v_kwargs_name} + for k, v in kwargs.items(): + if k in non_var_fields or k in fields_alias: + if k in self.positional_only_args: + wrong_positional_args.append(k) + if k in values: + duplicate_kwargs.append(k) + values[k] = v + else: + var_kwargs[k] = v + + if var_kwargs: + values[self.v_kwargs_name] = var_kwargs + if wrong_positional_args: + values[V_POSITIONAL_ONLY_NAME] = wrong_positional_args + if duplicate_kwargs: + values[V_DUPLICATE_KWARGS] = duplicate_kwargs + return values + + def execute(self, m: BaseModel) -> Any: + d = {k: v for k, v in m._iter() if k in m.__fields_set__ or m.__fields__[k].default_factory} + var_kwargs = d.pop(self.v_kwargs_name, {}) + + if self.v_args_name in d: + args_: List[Any] = [] + in_kwargs = False + kwargs = {} + for name, value in d.items(): + if in_kwargs: + kwargs[name] = value + elif name == self.v_args_name: + args_ += value + in_kwargs = True + else: + args_.append(value) + return self.raw_function(*args_, **kwargs, **var_kwargs) + elif self.positional_only_args: + args_ = [] + kwargs = {} + for name, value in d.items(): + if name in self.positional_only_args: + args_.append(value) + else: + kwargs[name] = value + return self.raw_function(*args_, **kwargs, **var_kwargs) + else: + return self.raw_function(**d, **var_kwargs) + + def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None: + pos_args = len(self.arg_mapping) + + class CustomConfig: + pass + + if not TYPE_CHECKING: # pragma: no branch + if isinstance(config, dict): + CustomConfig = type('Config', (), config) # noqa: F811 + elif config is not None: + CustomConfig = config # noqa: F811 + + if hasattr(CustomConfig, 'fields') or hasattr(CustomConfig, 'alias_generator'): + raise ConfigError( + 'Setting the "fields" and "alias_generator" property on custom Config for ' + '@validate_arguments is not yet supported, please remove.' + ) + + class DecoratorBaseModel(BaseModel): + @validator(self.v_args_name, check_fields=False, allow_reuse=True) + def check_args(cls, v: Optional[List[Any]]) -> Optional[List[Any]]: + if takes_args or v is None: + return v + + raise TypeError(f'{pos_args} positional arguments expected but {pos_args + len(v)} given') + + @validator(self.v_kwargs_name, check_fields=False, allow_reuse=True) + def check_kwargs(cls, v: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + if takes_kwargs or v is None: + return v + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v.keys())) + raise TypeError(f'unexpected keyword argument{plural}: {keys}') + + @validator(V_POSITIONAL_ONLY_NAME, check_fields=False, allow_reuse=True) + def check_positional_only(cls, v: Optional[List[str]]) -> None: + if v is None: + return + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v)) + raise TypeError(f'positional-only argument{plural} passed as keyword argument{plural}: {keys}') + + @validator(V_DUPLICATE_KWARGS, check_fields=False, allow_reuse=True) + def check_duplicate_kwargs(cls, v: Optional[List[str]]) -> None: + if v is None: + return + + plural = '' if len(v) == 1 else 's' + keys = ', '.join(map(repr, v)) + raise TypeError(f'multiple values for argument{plural}: {keys}') + + class Config(CustomConfig): + extra = getattr(CustomConfig, 'extra', Extra.forbid) + + self.model = create_model(to_camel(self.raw_function.__name__), __base__=DecoratorBaseModel, **fields) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py new file mode 100644 index 0000000..5f6f217 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py @@ -0,0 +1,350 @@ +import os +import warnings +from pathlib import Path +from typing import AbstractSet, Any, Callable, ClassVar, Dict, List, Mapping, Optional, Tuple, Type, Union + +from pydantic.v1.config import BaseConfig, Extra +from pydantic.v1.fields import ModelField +from pydantic.v1.main import BaseModel +from pydantic.v1.types import JsonWrapper +from pydantic.v1.typing import StrPath, display_as_type, get_origin, is_union +from pydantic.v1.utils import deep_update, lenient_issubclass, path_type, sequence_like + +env_file_sentinel = str(object()) + +SettingsSourceCallable = Callable[['BaseSettings'], Dict[str, Any]] +DotenvType = Union[StrPath, List[StrPath], Tuple[StrPath, ...]] + + +class SettingsError(ValueError): + pass + + +class BaseSettings(BaseModel): + """ + Base class for settings, allowing values to be overridden by environment variables. + + This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose), + Heroku and any 12 factor app design. + """ + + def __init__( + __pydantic_self__, + _env_file: Optional[DotenvType] = env_file_sentinel, + _env_file_encoding: Optional[str] = None, + _env_nested_delimiter: Optional[str] = None, + _secrets_dir: Optional[StrPath] = None, + **values: Any, + ) -> None: + # Uses something other than `self` the first arg to allow "self" as a settable attribute + super().__init__( + **__pydantic_self__._build_values( + values, + _env_file=_env_file, + _env_file_encoding=_env_file_encoding, + _env_nested_delimiter=_env_nested_delimiter, + _secrets_dir=_secrets_dir, + ) + ) + + def _build_values( + self, + init_kwargs: Dict[str, Any], + _env_file: Optional[DotenvType] = None, + _env_file_encoding: Optional[str] = None, + _env_nested_delimiter: Optional[str] = None, + _secrets_dir: Optional[StrPath] = None, + ) -> Dict[str, Any]: + # Configure built-in sources + init_settings = InitSettingsSource(init_kwargs=init_kwargs) + env_settings = EnvSettingsSource( + env_file=(_env_file if _env_file != env_file_sentinel else self.__config__.env_file), + env_file_encoding=( + _env_file_encoding if _env_file_encoding is not None else self.__config__.env_file_encoding + ), + env_nested_delimiter=( + _env_nested_delimiter if _env_nested_delimiter is not None else self.__config__.env_nested_delimiter + ), + env_prefix_len=len(self.__config__.env_prefix), + ) + file_secret_settings = SecretsSettingsSource(secrets_dir=_secrets_dir or self.__config__.secrets_dir) + # Provide a hook to set built-in sources priority and add / remove sources + sources = self.__config__.customise_sources( + init_settings=init_settings, env_settings=env_settings, file_secret_settings=file_secret_settings + ) + if sources: + return deep_update(*reversed([source(self) for source in sources])) + else: + # no one should mean to do this, but I think returning an empty dict is marginally preferable + # to an informative error and much better than a confusing error + return {} + + class Config(BaseConfig): + env_prefix: str = '' + env_file: Optional[DotenvType] = None + env_file_encoding: Optional[str] = None + env_nested_delimiter: Optional[str] = None + secrets_dir: Optional[StrPath] = None + validate_all: bool = True + extra: Extra = Extra.forbid + arbitrary_types_allowed: bool = True + case_sensitive: bool = False + + @classmethod + def prepare_field(cls, field: ModelField) -> None: + env_names: Union[List[str], AbstractSet[str]] + field_info_from_config = cls.get_field_info(field.name) + + env = field_info_from_config.get('env') or field.field_info.extra.get('env') + if env is None: + if field.has_alias: + warnings.warn( + 'aliases are no longer used by BaseSettings to define which environment variables to read. ' + 'Instead use the "env" field setting. ' + 'See https://pydantic-docs.helpmanual.io/usage/settings/#environment-variable-names', + FutureWarning, + ) + env_names = {cls.env_prefix + field.name} + elif isinstance(env, str): + env_names = {env} + elif isinstance(env, (set, frozenset)): + env_names = env + elif sequence_like(env): + env_names = list(env) + else: + raise TypeError(f'invalid field env: {env!r} ({display_as_type(env)}); should be string, list or set') + + if not cls.case_sensitive: + env_names = env_names.__class__(n.lower() for n in env_names) + field.field_info.extra['env_names'] = env_names + + @classmethod + def customise_sources( + cls, + init_settings: SettingsSourceCallable, + env_settings: SettingsSourceCallable, + file_secret_settings: SettingsSourceCallable, + ) -> Tuple[SettingsSourceCallable, ...]: + return init_settings, env_settings, file_secret_settings + + @classmethod + def parse_env_var(cls, field_name: str, raw_val: str) -> Any: + return cls.json_loads(raw_val) + + # populated by the metaclass using the Config class defined above, annotated here to help IDEs only + __config__: ClassVar[Type[Config]] + + +class InitSettingsSource: + __slots__ = ('init_kwargs',) + + def __init__(self, init_kwargs: Dict[str, Any]): + self.init_kwargs = init_kwargs + + def __call__(self, settings: BaseSettings) -> Dict[str, Any]: + return self.init_kwargs + + def __repr__(self) -> str: + return f'InitSettingsSource(init_kwargs={self.init_kwargs!r})' + + +class EnvSettingsSource: + __slots__ = ('env_file', 'env_file_encoding', 'env_nested_delimiter', 'env_prefix_len') + + def __init__( + self, + env_file: Optional[DotenvType], + env_file_encoding: Optional[str], + env_nested_delimiter: Optional[str] = None, + env_prefix_len: int = 0, + ): + self.env_file: Optional[DotenvType] = env_file + self.env_file_encoding: Optional[str] = env_file_encoding + self.env_nested_delimiter: Optional[str] = env_nested_delimiter + self.env_prefix_len: int = env_prefix_len + + def __call__(self, settings: BaseSettings) -> Dict[str, Any]: # noqa C901 + """ + Build environment variables suitable for passing to the Model. + """ + d: Dict[str, Any] = {} + + if settings.__config__.case_sensitive: + env_vars: Mapping[str, Optional[str]] = os.environ + else: + env_vars = {k.lower(): v for k, v in os.environ.items()} + + dotenv_vars = self._read_env_files(settings.__config__.case_sensitive) + if dotenv_vars: + env_vars = {**dotenv_vars, **env_vars} + + for field in settings.__fields__.values(): + env_val: Optional[str] = None + for env_name in field.field_info.extra['env_names']: + env_val = env_vars.get(env_name) + if env_val is not None: + break + + is_complex, allow_parse_failure = self.field_is_complex(field) + if is_complex: + if env_val is None: + # field is complex but no value found so far, try explode_env_vars + env_val_built = self.explode_env_vars(field, env_vars) + if env_val_built: + d[field.alias] = env_val_built + else: + # field is complex and there's a value, decode that as JSON, then add explode_env_vars + try: + env_val = settings.__config__.parse_env_var(field.name, env_val) + except ValueError as e: + if not allow_parse_failure: + raise SettingsError(f'error parsing env var "{env_name}"') from e + + if isinstance(env_val, dict): + d[field.alias] = deep_update(env_val, self.explode_env_vars(field, env_vars)) + else: + d[field.alias] = env_val + elif env_val is not None: + # simplest case, field is not complex, we only need to add the value if it was found + d[field.alias] = env_val + + return d + + def _read_env_files(self, case_sensitive: bool) -> Dict[str, Optional[str]]: + env_files = self.env_file + if env_files is None: + return {} + + if isinstance(env_files, (str, os.PathLike)): + env_files = [env_files] + + dotenv_vars = {} + for env_file in env_files: + env_path = Path(env_file).expanduser() + if env_path.is_file(): + dotenv_vars.update( + read_env_file(env_path, encoding=self.env_file_encoding, case_sensitive=case_sensitive) + ) + + return dotenv_vars + + def field_is_complex(self, field: ModelField) -> Tuple[bool, bool]: + """ + Find out if a field is complex, and if so whether JSON errors should be ignored + """ + if lenient_issubclass(field.annotation, JsonWrapper): + return False, False + + if field.is_complex(): + allow_parse_failure = False + elif is_union(get_origin(field.type_)) and field.sub_fields and any(f.is_complex() for f in field.sub_fields): + allow_parse_failure = True + else: + return False, False + + return True, allow_parse_failure + + def explode_env_vars(self, field: ModelField, env_vars: Mapping[str, Optional[str]]) -> Dict[str, Any]: + """ + Process env_vars and extract the values of keys containing env_nested_delimiter into nested dictionaries. + + This is applied to a single field, hence filtering by env_var prefix. + """ + prefixes = [f'{env_name}{self.env_nested_delimiter}' for env_name in field.field_info.extra['env_names']] + result: Dict[str, Any] = {} + for env_name, env_val in env_vars.items(): + if not any(env_name.startswith(prefix) for prefix in prefixes): + continue + # we remove the prefix before splitting in case the prefix has characters in common with the delimiter + env_name_without_prefix = env_name[self.env_prefix_len :] + _, *keys, last_key = env_name_without_prefix.split(self.env_nested_delimiter) + env_var = result + for key in keys: + env_var = env_var.setdefault(key, {}) + env_var[last_key] = env_val + + return result + + def __repr__(self) -> str: + return ( + f'EnvSettingsSource(env_file={self.env_file!r}, env_file_encoding={self.env_file_encoding!r}, ' + f'env_nested_delimiter={self.env_nested_delimiter!r})' + ) + + +class SecretsSettingsSource: + __slots__ = ('secrets_dir',) + + def __init__(self, secrets_dir: Optional[StrPath]): + self.secrets_dir: Optional[StrPath] = secrets_dir + + def __call__(self, settings: BaseSettings) -> Dict[str, Any]: + """ + Build fields from "secrets" files. + """ + secrets: Dict[str, Optional[str]] = {} + + if self.secrets_dir is None: + return secrets + + secrets_path = Path(self.secrets_dir).expanduser() + + if not secrets_path.exists(): + warnings.warn(f'directory "{secrets_path}" does not exist') + return secrets + + if not secrets_path.is_dir(): + raise SettingsError(f'secrets_dir must reference a directory, not a {path_type(secrets_path)}') + + for field in settings.__fields__.values(): + for env_name in field.field_info.extra['env_names']: + path = find_case_path(secrets_path, env_name, settings.__config__.case_sensitive) + if not path: + # path does not exist, we currently don't return a warning for this + continue + + if path.is_file(): + secret_value = path.read_text().strip() + if field.is_complex(): + try: + secret_value = settings.__config__.parse_env_var(field.name, secret_value) + except ValueError as e: + raise SettingsError(f'error parsing env var "{env_name}"') from e + + secrets[field.alias] = secret_value + else: + warnings.warn( + f'attempted to load secret file "{path}" but found a {path_type(path)} instead.', + stacklevel=4, + ) + return secrets + + def __repr__(self) -> str: + return f'SecretsSettingsSource(secrets_dir={self.secrets_dir!r})' + + +def read_env_file( + file_path: StrPath, *, encoding: str = None, case_sensitive: bool = False +) -> Dict[str, Optional[str]]: + try: + from dotenv import dotenv_values + except ImportError as e: + raise ImportError('python-dotenv is not installed, run `pip install pydantic[dotenv]`') from e + + file_vars: Dict[str, Optional[str]] = dotenv_values(file_path, encoding=encoding or 'utf8') + if not case_sensitive: + return {k.lower(): v for k, v in file_vars.items()} + else: + return file_vars + + +def find_case_path(dir_path: Path, file_name: str, case_sensitive: bool) -> Optional[Path]: + """ + Find a file within path's directory matching filename, optionally ignoring case. + """ + for f in dir_path.iterdir(): + if f.name == file_name: + return f + elif not case_sensitive and f.name.lower() == file_name.lower(): + return f + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py new file mode 100644 index 0000000..bc7f263 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py @@ -0,0 +1,161 @@ +import json +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union + +from pydantic.v1.json import pydantic_encoder +from pydantic.v1.utils import Representation + +if TYPE_CHECKING: + from typing_extensions import TypedDict + + from pydantic.v1.config import BaseConfig + from pydantic.v1.types import ModelOrDc + from pydantic.v1.typing import ReprArgs + + Loc = Tuple[Union[int, str], ...] + + class _ErrorDictRequired(TypedDict): + loc: Loc + msg: str + type: str + + class ErrorDict(_ErrorDictRequired, total=False): + ctx: Dict[str, Any] + + +__all__ = 'ErrorWrapper', 'ValidationError' + + +class ErrorWrapper(Representation): + __slots__ = 'exc', '_loc' + + def __init__(self, exc: Exception, loc: Union[str, 'Loc']) -> None: + self.exc = exc + self._loc = loc + + def loc_tuple(self) -> 'Loc': + if isinstance(self._loc, tuple): + return self._loc + else: + return (self._loc,) + + def __repr_args__(self) -> 'ReprArgs': + return [('exc', self.exc), ('loc', self.loc_tuple())] + + +# ErrorList is something like Union[List[Union[List[ErrorWrapper], ErrorWrapper]], ErrorWrapper] +# but recursive, therefore just use: +ErrorList = Union[Sequence[Any], ErrorWrapper] + + +class ValidationError(Representation, ValueError): + __slots__ = 'raw_errors', 'model', '_error_cache' + + def __init__(self, errors: Sequence[ErrorList], model: 'ModelOrDc') -> None: + self.raw_errors = errors + self.model = model + self._error_cache: Optional[List['ErrorDict']] = None + + def errors(self) -> List['ErrorDict']: + if self._error_cache is None: + try: + config = self.model.__config__ # type: ignore + except AttributeError: + config = self.model.__pydantic_model__.__config__ # type: ignore + self._error_cache = list(flatten_errors(self.raw_errors, config)) + return self._error_cache + + def json(self, *, indent: Union[None, int, str] = 2) -> str: + return json.dumps(self.errors(), indent=indent, default=pydantic_encoder) + + def __str__(self) -> str: + errors = self.errors() + no_errors = len(errors) + return ( + f'{no_errors} validation error{"" if no_errors == 1 else "s"} for {self.model.__name__}\n' + f'{display_errors(errors)}' + ) + + def __repr_args__(self) -> 'ReprArgs': + return [('model', self.model.__name__), ('errors', self.errors())] + + +def display_errors(errors: List['ErrorDict']) -> str: + return '\n'.join(f'{_display_error_loc(e)}\n {e["msg"]} ({_display_error_type_and_ctx(e)})' for e in errors) + + +def _display_error_loc(error: 'ErrorDict') -> str: + return ' -> '.join(str(e) for e in error['loc']) + + +def _display_error_type_and_ctx(error: 'ErrorDict') -> str: + t = 'type=' + error['type'] + ctx = error.get('ctx') + if ctx: + return t + ''.join(f'; {k}={v}' for k, v in ctx.items()) + else: + return t + + +def flatten_errors( + errors: Sequence[Any], config: Type['BaseConfig'], loc: Optional['Loc'] = None +) -> Generator['ErrorDict', None, None]: + for error in errors: + if isinstance(error, ErrorWrapper): + if loc: + error_loc = loc + error.loc_tuple() + else: + error_loc = error.loc_tuple() + + if isinstance(error.exc, ValidationError): + yield from flatten_errors(error.exc.raw_errors, config, error_loc) + else: + yield error_dict(error.exc, config, error_loc) + elif isinstance(error, list): + yield from flatten_errors(error, config, loc=loc) + else: + raise RuntimeError(f'Unknown error object: {error}') + + +def error_dict(exc: Exception, config: Type['BaseConfig'], loc: 'Loc') -> 'ErrorDict': + type_ = get_exc_type(exc.__class__) + msg_template = config.error_msg_templates.get(type_) or getattr(exc, 'msg_template', None) + ctx = exc.__dict__ + if msg_template: + msg = msg_template.format(**ctx) + else: + msg = str(exc) + + d: 'ErrorDict' = {'loc': loc, 'msg': msg, 'type': type_} + + if ctx: + d['ctx'] = ctx + + return d + + +_EXC_TYPE_CACHE: Dict[Type[Exception], str] = {} + + +def get_exc_type(cls: Type[Exception]) -> str: + # slightly more efficient than using lru_cache since we don't need to worry about the cache filling up + try: + return _EXC_TYPE_CACHE[cls] + except KeyError: + r = _get_exc_type(cls) + _EXC_TYPE_CACHE[cls] = r + return r + + +def _get_exc_type(cls: Type[Exception]) -> str: + if issubclass(cls, AssertionError): + return 'assertion_error' + + base_name = 'type_error' if issubclass(cls, TypeError) else 'value_error' + if cls in (TypeError, ValueError): + # just TypeError or ValueError, no extra code + return base_name + + # if it's not a TypeError or ValueError, we just take the lowercase of the exception name + # no chaining or snake case logic, use "code" for more complex error types. + code = getattr(cls, 'code', None) or cls.__name__.replace('Error', '').lower() + return base_name + '.' + code diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py new file mode 100644 index 0000000..6e86442 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py @@ -0,0 +1,646 @@ +from decimal import Decimal +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable, Sequence, Set, Tuple, Type, Union + +from pydantic.v1.typing import display_as_type + +if TYPE_CHECKING: + from pydantic.v1.typing import DictStrAny + +# explicitly state exports to avoid "from pydantic.v1.errors import *" also importing Decimal, Path etc. +__all__ = ( + 'PydanticTypeError', + 'PydanticValueError', + 'ConfigError', + 'MissingError', + 'ExtraError', + 'NoneIsNotAllowedError', + 'NoneIsAllowedError', + 'WrongConstantError', + 'NotNoneError', + 'BoolError', + 'BytesError', + 'DictError', + 'EmailError', + 'UrlError', + 'UrlSchemeError', + 'UrlSchemePermittedError', + 'UrlUserInfoError', + 'UrlHostError', + 'UrlHostTldError', + 'UrlPortError', + 'UrlExtraError', + 'EnumError', + 'IntEnumError', + 'EnumMemberError', + 'IntegerError', + 'FloatError', + 'PathError', + 'PathNotExistsError', + 'PathNotAFileError', + 'PathNotADirectoryError', + 'PyObjectError', + 'SequenceError', + 'ListError', + 'SetError', + 'FrozenSetError', + 'TupleError', + 'TupleLengthError', + 'ListMinLengthError', + 'ListMaxLengthError', + 'ListUniqueItemsError', + 'SetMinLengthError', + 'SetMaxLengthError', + 'FrozenSetMinLengthError', + 'FrozenSetMaxLengthError', + 'AnyStrMinLengthError', + 'AnyStrMaxLengthError', + 'StrError', + 'StrRegexError', + 'NumberNotGtError', + 'NumberNotGeError', + 'NumberNotLtError', + 'NumberNotLeError', + 'NumberNotMultipleError', + 'DecimalError', + 'DecimalIsNotFiniteError', + 'DecimalMaxDigitsError', + 'DecimalMaxPlacesError', + 'DecimalWholeDigitsError', + 'DateTimeError', + 'DateError', + 'DateNotInThePastError', + 'DateNotInTheFutureError', + 'TimeError', + 'DurationError', + 'HashableError', + 'UUIDError', + 'UUIDVersionError', + 'ArbitraryTypeError', + 'ClassError', + 'SubclassError', + 'JsonError', + 'JsonTypeError', + 'PatternError', + 'DataclassTypeError', + 'CallableError', + 'IPvAnyAddressError', + 'IPvAnyInterfaceError', + 'IPvAnyNetworkError', + 'IPv4AddressError', + 'IPv6AddressError', + 'IPv4NetworkError', + 'IPv6NetworkError', + 'IPv4InterfaceError', + 'IPv6InterfaceError', + 'ColorError', + 'StrictBoolError', + 'NotDigitError', + 'LuhnValidationError', + 'InvalidLengthForBrand', + 'InvalidByteSize', + 'InvalidByteSizeUnit', + 'MissingDiscriminator', + 'InvalidDiscriminator', +) + + +def cls_kwargs(cls: Type['PydanticErrorMixin'], ctx: 'DictStrAny') -> 'PydanticErrorMixin': + """ + For built-in exceptions like ValueError or TypeError, we need to implement + __reduce__ to override the default behaviour (instead of __getstate__/__setstate__) + By default pickle protocol 2 calls `cls.__new__(cls, *args)`. + Since we only use kwargs, we need a little constructor to change that. + Note: the callable can't be a lambda as pickle looks in the namespace to find it + """ + return cls(**ctx) + + +class PydanticErrorMixin: + code: str + msg_template: str + + def __init__(self, **ctx: Any) -> None: + self.__dict__ = ctx + + def __str__(self) -> str: + return self.msg_template.format(**self.__dict__) + + def __reduce__(self) -> Tuple[Callable[..., 'PydanticErrorMixin'], Tuple[Type['PydanticErrorMixin'], 'DictStrAny']]: + return cls_kwargs, (self.__class__, self.__dict__) + + +class PydanticTypeError(PydanticErrorMixin, TypeError): + pass + + +class PydanticValueError(PydanticErrorMixin, ValueError): + pass + + +class ConfigError(RuntimeError): + pass + + +class MissingError(PydanticValueError): + msg_template = 'field required' + + +class ExtraError(PydanticValueError): + msg_template = 'extra fields not permitted' + + +class NoneIsNotAllowedError(PydanticTypeError): + code = 'none.not_allowed' + msg_template = 'none is not an allowed value' + + +class NoneIsAllowedError(PydanticTypeError): + code = 'none.allowed' + msg_template = 'value is not none' + + +class WrongConstantError(PydanticValueError): + code = 'const' + + def __str__(self) -> str: + permitted = ', '.join(repr(v) for v in self.permitted) # type: ignore + return f'unexpected value; permitted: {permitted}' + + +class NotNoneError(PydanticTypeError): + code = 'not_none' + msg_template = 'value is not None' + + +class BoolError(PydanticTypeError): + msg_template = 'value could not be parsed to a boolean' + + +class BytesError(PydanticTypeError): + msg_template = 'byte type expected' + + +class DictError(PydanticTypeError): + msg_template = 'value is not a valid dict' + + +class EmailError(PydanticValueError): + msg_template = 'value is not a valid email address' + + +class UrlError(PydanticValueError): + code = 'url' + + +class UrlSchemeError(UrlError): + code = 'url.scheme' + msg_template = 'invalid or missing URL scheme' + + +class UrlSchemePermittedError(UrlError): + code = 'url.scheme' + msg_template = 'URL scheme not permitted' + + def __init__(self, allowed_schemes: Set[str]): + super().__init__(allowed_schemes=allowed_schemes) + + +class UrlUserInfoError(UrlError): + code = 'url.userinfo' + msg_template = 'userinfo required in URL but missing' + + +class UrlHostError(UrlError): + code = 'url.host' + msg_template = 'URL host invalid' + + +class UrlHostTldError(UrlError): + code = 'url.host' + msg_template = 'URL host invalid, top level domain required' + + +class UrlPortError(UrlError): + code = 'url.port' + msg_template = 'URL port invalid, port cannot exceed 65535' + + +class UrlExtraError(UrlError): + code = 'url.extra' + msg_template = 'URL invalid, extra characters found after valid URL: {extra!r}' + + +class EnumMemberError(PydanticTypeError): + code = 'enum' + + def __str__(self) -> str: + permitted = ', '.join(repr(v.value) for v in self.enum_values) # type: ignore + return f'value is not a valid enumeration member; permitted: {permitted}' + + +class IntegerError(PydanticTypeError): + msg_template = 'value is not a valid integer' + + +class FloatError(PydanticTypeError): + msg_template = 'value is not a valid float' + + +class PathError(PydanticTypeError): + msg_template = 'value is not a valid path' + + +class _PathValueError(PydanticValueError): + def __init__(self, *, path: Path) -> None: + super().__init__(path=str(path)) + + +class PathNotExistsError(_PathValueError): + code = 'path.not_exists' + msg_template = 'file or directory at path "{path}" does not exist' + + +class PathNotAFileError(_PathValueError): + code = 'path.not_a_file' + msg_template = 'path "{path}" does not point to a file' + + +class PathNotADirectoryError(_PathValueError): + code = 'path.not_a_directory' + msg_template = 'path "{path}" does not point to a directory' + + +class PyObjectError(PydanticTypeError): + msg_template = 'ensure this value contains valid import path or valid callable: {error_message}' + + +class SequenceError(PydanticTypeError): + msg_template = 'value is not a valid sequence' + + +class IterableError(PydanticTypeError): + msg_template = 'value is not a valid iterable' + + +class ListError(PydanticTypeError): + msg_template = 'value is not a valid list' + + +class SetError(PydanticTypeError): + msg_template = 'value is not a valid set' + + +class FrozenSetError(PydanticTypeError): + msg_template = 'value is not a valid frozenset' + + +class DequeError(PydanticTypeError): + msg_template = 'value is not a valid deque' + + +class TupleError(PydanticTypeError): + msg_template = 'value is not a valid tuple' + + +class TupleLengthError(PydanticValueError): + code = 'tuple.length' + msg_template = 'wrong tuple length {actual_length}, expected {expected_length}' + + def __init__(self, *, actual_length: int, expected_length: int) -> None: + super().__init__(actual_length=actual_length, expected_length=expected_length) + + +class ListMinLengthError(PydanticValueError): + code = 'list.min_items' + msg_template = 'ensure this value has at least {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class ListMaxLengthError(PydanticValueError): + code = 'list.max_items' + msg_template = 'ensure this value has at most {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class ListUniqueItemsError(PydanticValueError): + code = 'list.unique_items' + msg_template = 'the list has duplicated items' + + +class SetMinLengthError(PydanticValueError): + code = 'set.min_items' + msg_template = 'ensure this value has at least {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class SetMaxLengthError(PydanticValueError): + code = 'set.max_items' + msg_template = 'ensure this value has at most {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class FrozenSetMinLengthError(PydanticValueError): + code = 'frozenset.min_items' + msg_template = 'ensure this value has at least {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class FrozenSetMaxLengthError(PydanticValueError): + code = 'frozenset.max_items' + msg_template = 'ensure this value has at most {limit_value} items' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class AnyStrMinLengthError(PydanticValueError): + code = 'any_str.min_length' + msg_template = 'ensure this value has at least {limit_value} characters' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class AnyStrMaxLengthError(PydanticValueError): + code = 'any_str.max_length' + msg_template = 'ensure this value has at most {limit_value} characters' + + def __init__(self, *, limit_value: int) -> None: + super().__init__(limit_value=limit_value) + + +class StrError(PydanticTypeError): + msg_template = 'str type expected' + + +class StrRegexError(PydanticValueError): + code = 'str.regex' + msg_template = 'string does not match regex "{pattern}"' + + def __init__(self, *, pattern: str) -> None: + super().__init__(pattern=pattern) + + +class _NumberBoundError(PydanticValueError): + def __init__(self, *, limit_value: Union[int, float, Decimal]) -> None: + super().__init__(limit_value=limit_value) + + +class NumberNotGtError(_NumberBoundError): + code = 'number.not_gt' + msg_template = 'ensure this value is greater than {limit_value}' + + +class NumberNotGeError(_NumberBoundError): + code = 'number.not_ge' + msg_template = 'ensure this value is greater than or equal to {limit_value}' + + +class NumberNotLtError(_NumberBoundError): + code = 'number.not_lt' + msg_template = 'ensure this value is less than {limit_value}' + + +class NumberNotLeError(_NumberBoundError): + code = 'number.not_le' + msg_template = 'ensure this value is less than or equal to {limit_value}' + + +class NumberNotFiniteError(PydanticValueError): + code = 'number.not_finite_number' + msg_template = 'ensure this value is a finite number' + + +class NumberNotMultipleError(PydanticValueError): + code = 'number.not_multiple' + msg_template = 'ensure this value is a multiple of {multiple_of}' + + def __init__(self, *, multiple_of: Union[int, float, Decimal]) -> None: + super().__init__(multiple_of=multiple_of) + + +class DecimalError(PydanticTypeError): + msg_template = 'value is not a valid decimal' + + +class DecimalIsNotFiniteError(PydanticValueError): + code = 'decimal.not_finite' + msg_template = 'value is not a valid decimal' + + +class DecimalMaxDigitsError(PydanticValueError): + code = 'decimal.max_digits' + msg_template = 'ensure that there are no more than {max_digits} digits in total' + + def __init__(self, *, max_digits: int) -> None: + super().__init__(max_digits=max_digits) + + +class DecimalMaxPlacesError(PydanticValueError): + code = 'decimal.max_places' + msg_template = 'ensure that there are no more than {decimal_places} decimal places' + + def __init__(self, *, decimal_places: int) -> None: + super().__init__(decimal_places=decimal_places) + + +class DecimalWholeDigitsError(PydanticValueError): + code = 'decimal.whole_digits' + msg_template = 'ensure that there are no more than {whole_digits} digits before the decimal point' + + def __init__(self, *, whole_digits: int) -> None: + super().__init__(whole_digits=whole_digits) + + +class DateTimeError(PydanticValueError): + msg_template = 'invalid datetime format' + + +class DateError(PydanticValueError): + msg_template = 'invalid date format' + + +class DateNotInThePastError(PydanticValueError): + code = 'date.not_in_the_past' + msg_template = 'date is not in the past' + + +class DateNotInTheFutureError(PydanticValueError): + code = 'date.not_in_the_future' + msg_template = 'date is not in the future' + + +class TimeError(PydanticValueError): + msg_template = 'invalid time format' + + +class DurationError(PydanticValueError): + msg_template = 'invalid duration format' + + +class HashableError(PydanticTypeError): + msg_template = 'value is not a valid hashable' + + +class UUIDError(PydanticTypeError): + msg_template = 'value is not a valid uuid' + + +class UUIDVersionError(PydanticValueError): + code = 'uuid.version' + msg_template = 'uuid version {required_version} expected' + + def __init__(self, *, required_version: int) -> None: + super().__init__(required_version=required_version) + + +class ArbitraryTypeError(PydanticTypeError): + code = 'arbitrary_type' + msg_template = 'instance of {expected_arbitrary_type} expected' + + def __init__(self, *, expected_arbitrary_type: Type[Any]) -> None: + super().__init__(expected_arbitrary_type=display_as_type(expected_arbitrary_type)) + + +class ClassError(PydanticTypeError): + code = 'class' + msg_template = 'a class is expected' + + +class SubclassError(PydanticTypeError): + code = 'subclass' + msg_template = 'subclass of {expected_class} expected' + + def __init__(self, *, expected_class: Type[Any]) -> None: + super().__init__(expected_class=display_as_type(expected_class)) + + +class JsonError(PydanticValueError): + msg_template = 'Invalid JSON' + + +class JsonTypeError(PydanticTypeError): + code = 'json' + msg_template = 'JSON object must be str, bytes or bytearray' + + +class PatternError(PydanticValueError): + code = 'regex_pattern' + msg_template = 'Invalid regular expression' + + +class DataclassTypeError(PydanticTypeError): + code = 'dataclass' + msg_template = 'instance of {class_name}, tuple or dict expected' + + +class CallableError(PydanticTypeError): + msg_template = '{value} is not callable' + + +class EnumError(PydanticTypeError): + code = 'enum_instance' + msg_template = '{value} is not a valid Enum instance' + + +class IntEnumError(PydanticTypeError): + code = 'int_enum_instance' + msg_template = '{value} is not a valid IntEnum instance' + + +class IPvAnyAddressError(PydanticValueError): + msg_template = 'value is not a valid IPv4 or IPv6 address' + + +class IPvAnyInterfaceError(PydanticValueError): + msg_template = 'value is not a valid IPv4 or IPv6 interface' + + +class IPvAnyNetworkError(PydanticValueError): + msg_template = 'value is not a valid IPv4 or IPv6 network' + + +class IPv4AddressError(PydanticValueError): + msg_template = 'value is not a valid IPv4 address' + + +class IPv6AddressError(PydanticValueError): + msg_template = 'value is not a valid IPv6 address' + + +class IPv4NetworkError(PydanticValueError): + msg_template = 'value is not a valid IPv4 network' + + +class IPv6NetworkError(PydanticValueError): + msg_template = 'value is not a valid IPv6 network' + + +class IPv4InterfaceError(PydanticValueError): + msg_template = 'value is not a valid IPv4 interface' + + +class IPv6InterfaceError(PydanticValueError): + msg_template = 'value is not a valid IPv6 interface' + + +class ColorError(PydanticValueError): + msg_template = 'value is not a valid color: {reason}' + + +class StrictBoolError(PydanticValueError): + msg_template = 'value is not a valid boolean' + + +class NotDigitError(PydanticValueError): + code = 'payment_card_number.digits' + msg_template = 'card number is not all digits' + + +class LuhnValidationError(PydanticValueError): + code = 'payment_card_number.luhn_check' + msg_template = 'card number is not luhn valid' + + +class InvalidLengthForBrand(PydanticValueError): + code = 'payment_card_number.invalid_length_for_brand' + msg_template = 'Length for a {brand} card must be {required_length}' + + +class InvalidByteSize(PydanticValueError): + msg_template = 'could not parse value and unit from byte string' + + +class InvalidByteSizeUnit(PydanticValueError): + msg_template = 'could not interpret byte unit: {unit}' + + +class MissingDiscriminator(PydanticValueError): + code = 'discriminated_union.missing_discriminator' + msg_template = 'Discriminator {discriminator_key!r} is missing in value' + + +class InvalidDiscriminator(PydanticValueError): + code = 'discriminated_union.invalid_discriminator' + msg_template = ( + 'No match for discriminator {discriminator_key!r} and value {discriminator_value!r} ' + '(allowed values: {allowed_values})' + ) + + def __init__(self, *, discriminator_key: str, discriminator_value: Any, allowed_values: Sequence[Any]) -> None: + super().__init__( + discriminator_key=discriminator_key, + discriminator_value=discriminator_value, + allowed_values=', '.join(map(repr, allowed_values)), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py new file mode 100644 index 0000000..002b60c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py @@ -0,0 +1,1253 @@ +import copy +import re +from collections import Counter as CollectionCounter, defaultdict, deque +from collections.abc import Callable, Hashable as CollectionsHashable, Iterable as CollectionsIterable +from typing import ( + TYPE_CHECKING, + Any, + Counter, + DefaultDict, + Deque, + Dict, + ForwardRef, + FrozenSet, + Generator, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Sequence, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +from typing_extensions import Annotated, Final + +from pydantic.v1 import errors as errors_ +from pydantic.v1.class_validators import Validator, make_generic_validator, prep_validators +from pydantic.v1.error_wrappers import ErrorWrapper +from pydantic.v1.errors import ConfigError, InvalidDiscriminator, MissingDiscriminator, NoneIsNotAllowedError +from pydantic.v1.types import Json, JsonWrapper +from pydantic.v1.typing import ( + NoArgAnyCallable, + convert_generics, + display_as_type, + get_args, + get_origin, + is_finalvar, + is_literal_type, + is_new_type, + is_none_type, + is_typeddict, + is_typeddict_special, + is_union, + new_type_supertype, +) +from pydantic.v1.utils import ( + PyObjectStr, + Representation, + ValueItems, + get_discriminator_alias_and_values, + get_unique_discriminator_alias, + lenient_isinstance, + lenient_issubclass, + sequence_like, + smart_deepcopy, +) +from pydantic.v1.validators import constant_validator, dict_validator, find_validators, validate_json + +Required: Any = Ellipsis + +T = TypeVar('T') + + +class UndefinedType: + def __repr__(self) -> str: + return 'PydanticUndefined' + + def __copy__(self: T) -> T: + return self + + def __reduce__(self) -> str: + return 'Undefined' + + def __deepcopy__(self: T, _: Any) -> T: + return self + + +Undefined = UndefinedType() + +if TYPE_CHECKING: + from pydantic.v1.class_validators import ValidatorsList + from pydantic.v1.config import BaseConfig + from pydantic.v1.error_wrappers import ErrorList + from pydantic.v1.types import ModelOrDc + from pydantic.v1.typing import AbstractSetIntStr, MappingIntStrAny, ReprArgs + + ValidateReturn = Tuple[Optional[Any], Optional[ErrorList]] + LocStr = Union[Tuple[Union[int, str], ...], str] + BoolUndefined = Union[bool, UndefinedType] + + +class FieldInfo(Representation): + """ + Captures extra information about a field. + """ + + __slots__ = ( + 'default', + 'default_factory', + 'alias', + 'alias_priority', + 'title', + 'description', + 'exclude', + 'include', + 'const', + 'gt', + 'ge', + 'lt', + 'le', + 'multiple_of', + 'allow_inf_nan', + 'max_digits', + 'decimal_places', + 'min_items', + 'max_items', + 'unique_items', + 'min_length', + 'max_length', + 'allow_mutation', + 'repr', + 'regex', + 'discriminator', + 'extra', + ) + + # field constraints with the default value, it's also used in update_from_config below + __field_constraints__ = { + 'min_length': None, + 'max_length': None, + 'regex': None, + 'gt': None, + 'lt': None, + 'ge': None, + 'le': None, + 'multiple_of': None, + 'allow_inf_nan': None, + 'max_digits': None, + 'decimal_places': None, + 'min_items': None, + 'max_items': None, + 'unique_items': None, + 'allow_mutation': True, + } + + def __init__(self, default: Any = Undefined, **kwargs: Any) -> None: + self.default = default + self.default_factory = kwargs.pop('default_factory', None) + self.alias = kwargs.pop('alias', None) + self.alias_priority = kwargs.pop('alias_priority', 2 if self.alias is not None else None) + self.title = kwargs.pop('title', None) + self.description = kwargs.pop('description', None) + self.exclude = kwargs.pop('exclude', None) + self.include = kwargs.pop('include', None) + self.const = kwargs.pop('const', None) + self.gt = kwargs.pop('gt', None) + self.ge = kwargs.pop('ge', None) + self.lt = kwargs.pop('lt', None) + self.le = kwargs.pop('le', None) + self.multiple_of = kwargs.pop('multiple_of', None) + self.allow_inf_nan = kwargs.pop('allow_inf_nan', None) + self.max_digits = kwargs.pop('max_digits', None) + self.decimal_places = kwargs.pop('decimal_places', None) + self.min_items = kwargs.pop('min_items', None) + self.max_items = kwargs.pop('max_items', None) + self.unique_items = kwargs.pop('unique_items', None) + self.min_length = kwargs.pop('min_length', None) + self.max_length = kwargs.pop('max_length', None) + self.allow_mutation = kwargs.pop('allow_mutation', True) + self.regex = kwargs.pop('regex', None) + self.discriminator = kwargs.pop('discriminator', None) + self.repr = kwargs.pop('repr', True) + self.extra = kwargs + + def __repr_args__(self) -> 'ReprArgs': + field_defaults_to_hide: Dict[str, Any] = { + 'repr': True, + **self.__field_constraints__, + } + + attrs = ((s, getattr(self, s)) for s in self.__slots__) + return [(a, v) for a, v in attrs if v != field_defaults_to_hide.get(a, None)] + + def get_constraints(self) -> Set[str]: + """ + Gets the constraints set on the field by comparing the constraint value with its default value + + :return: the constraints set on field_info + """ + return {attr for attr, default in self.__field_constraints__.items() if getattr(self, attr) != default} + + def update_from_config(self, from_config: Dict[str, Any]) -> None: + """ + Update this FieldInfo based on a dict from get_field_info, only fields which have not been set are dated. + """ + for attr_name, value in from_config.items(): + try: + current_value = getattr(self, attr_name) + except AttributeError: + # attr_name is not an attribute of FieldInfo, it should therefore be added to extra + # (except if extra already has this value!) + self.extra.setdefault(attr_name, value) + else: + if current_value is self.__field_constraints__.get(attr_name, None): + setattr(self, attr_name, value) + elif attr_name == 'exclude': + self.exclude = ValueItems.merge(value, current_value) + elif attr_name == 'include': + self.include = ValueItems.merge(value, current_value, intersect=True) + + def _validate(self) -> None: + if self.default is not Undefined and self.default_factory is not None: + raise ValueError('cannot specify both default and default_factory') + + +def Field( + default: Any = Undefined, + *, + default_factory: Optional[NoArgAnyCallable] = None, + alias: Optional[str] = None, + title: Optional[str] = None, + description: Optional[str] = None, + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny', Any]] = None, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny', Any]] = None, + const: Optional[bool] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + multiple_of: Optional[float] = None, + allow_inf_nan: Optional[bool] = None, + max_digits: Optional[int] = None, + decimal_places: Optional[int] = None, + min_items: Optional[int] = None, + max_items: Optional[int] = None, + unique_items: Optional[bool] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + allow_mutation: bool = True, + regex: Optional[str] = None, + discriminator: Optional[str] = None, + repr: bool = True, + **extra: Any, +) -> Any: + """ + Used to provide extra information about a field, either for the model schema or complex validation. Some arguments + apply only to number fields (``int``, ``float``, ``Decimal``) and some apply only to ``str``. + + :param default: since this is replacing the field’s default, its first argument is used + to set the default, use ellipsis (``...``) to indicate the field is required + :param default_factory: callable that will be called when a default value is needed for this field + If both `default` and `default_factory` are set, an error is raised. + :param alias: the public name of the field + :param title: can be any string, used in the schema + :param description: can be any string, used in the schema + :param exclude: exclude this field while dumping. + Takes same values as the ``include`` and ``exclude`` arguments on the ``.dict`` method. + :param include: include this field while dumping. + Takes same values as the ``include`` and ``exclude`` arguments on the ``.dict`` method. + :param const: this field is required and *must* take it's default value + :param gt: only applies to numbers, requires the field to be "greater than". The schema + will have an ``exclusiveMinimum`` validation keyword + :param ge: only applies to numbers, requires the field to be "greater than or equal to". The + schema will have a ``minimum`` validation keyword + :param lt: only applies to numbers, requires the field to be "less than". The schema + will have an ``exclusiveMaximum`` validation keyword + :param le: only applies to numbers, requires the field to be "less than or equal to". The + schema will have a ``maximum`` validation keyword + :param multiple_of: only applies to numbers, requires the field to be "a multiple of". The + schema will have a ``multipleOf`` validation keyword + :param allow_inf_nan: only applies to numbers, allows the field to be NaN or infinity (+inf or -inf), + which is a valid Python float. Default True, set to False for compatibility with JSON. + :param max_digits: only applies to Decimals, requires the field to have a maximum number + of digits within the decimal. It does not include a zero before the decimal point or trailing decimal zeroes. + :param decimal_places: only applies to Decimals, requires the field to have at most a number of decimal places + allowed. It does not include trailing decimal zeroes. + :param min_items: only applies to lists, requires the field to have a minimum number of + elements. The schema will have a ``minItems`` validation keyword + :param max_items: only applies to lists, requires the field to have a maximum number of + elements. The schema will have a ``maxItems`` validation keyword + :param unique_items: only applies to lists, requires the field not to have duplicated + elements. The schema will have a ``uniqueItems`` validation keyword + :param min_length: only applies to strings, requires the field to have a minimum length. The + schema will have a ``minLength`` validation keyword + :param max_length: only applies to strings, requires the field to have a maximum length. The + schema will have a ``maxLength`` validation keyword + :param allow_mutation: a boolean which defaults to True. When False, the field raises a TypeError if the field is + assigned on an instance. The BaseModel Config must set validate_assignment to True + :param regex: only applies to strings, requires the field match against a regular expression + pattern string. The schema will have a ``pattern`` validation keyword + :param discriminator: only useful with a (discriminated a.k.a. tagged) `Union` of sub models with a common field. + The `discriminator` is the name of this common field to shorten validation and improve generated schema + :param repr: show this field in the representation + :param **extra: any additional keyword arguments will be added as is to the schema + """ + field_info = FieldInfo( + default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + exclude=exclude, + include=include, + const=const, + gt=gt, + ge=ge, + lt=lt, + le=le, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + min_items=min_items, + max_items=max_items, + unique_items=unique_items, + min_length=min_length, + max_length=max_length, + allow_mutation=allow_mutation, + regex=regex, + discriminator=discriminator, + repr=repr, + **extra, + ) + field_info._validate() + return field_info + + +# used to be an enum but changed to int's for small performance improvement as less access overhead +SHAPE_SINGLETON = 1 +SHAPE_LIST = 2 +SHAPE_SET = 3 +SHAPE_MAPPING = 4 +SHAPE_TUPLE = 5 +SHAPE_TUPLE_ELLIPSIS = 6 +SHAPE_SEQUENCE = 7 +SHAPE_FROZENSET = 8 +SHAPE_ITERABLE = 9 +SHAPE_GENERIC = 10 +SHAPE_DEQUE = 11 +SHAPE_DICT = 12 +SHAPE_DEFAULTDICT = 13 +SHAPE_COUNTER = 14 +SHAPE_NAME_LOOKUP = { + SHAPE_LIST: 'List[{}]', + SHAPE_SET: 'Set[{}]', + SHAPE_TUPLE_ELLIPSIS: 'Tuple[{}, ...]', + SHAPE_SEQUENCE: 'Sequence[{}]', + SHAPE_FROZENSET: 'FrozenSet[{}]', + SHAPE_ITERABLE: 'Iterable[{}]', + SHAPE_DEQUE: 'Deque[{}]', + SHAPE_DICT: 'Dict[{}]', + SHAPE_DEFAULTDICT: 'DefaultDict[{}]', + SHAPE_COUNTER: 'Counter[{}]', +} + +MAPPING_LIKE_SHAPES: Set[int] = {SHAPE_DEFAULTDICT, SHAPE_DICT, SHAPE_MAPPING, SHAPE_COUNTER} + + +class ModelField(Representation): + __slots__ = ( + 'type_', + 'outer_type_', + 'annotation', + 'sub_fields', + 'sub_fields_mapping', + 'key_field', + 'validators', + 'pre_validators', + 'post_validators', + 'default', + 'default_factory', + 'required', + 'final', + 'model_config', + 'name', + 'alias', + 'has_alias', + 'field_info', + 'discriminator_key', + 'discriminator_alias', + 'validate_always', + 'allow_none', + 'shape', + 'class_validators', + 'parse_json', + ) + + def __init__( + self, + *, + name: str, + type_: Type[Any], + class_validators: Optional[Dict[str, Validator]], + model_config: Type['BaseConfig'], + default: Any = None, + default_factory: Optional[NoArgAnyCallable] = None, + required: 'BoolUndefined' = Undefined, + final: bool = False, + alias: Optional[str] = None, + field_info: Optional[FieldInfo] = None, + ) -> None: + self.name: str = name + self.has_alias: bool = alias is not None + self.alias: str = alias if alias is not None else name + self.annotation = type_ + self.type_: Any = convert_generics(type_) + self.outer_type_: Any = type_ + self.class_validators = class_validators or {} + self.default: Any = default + self.default_factory: Optional[NoArgAnyCallable] = default_factory + self.required: 'BoolUndefined' = required + self.final: bool = final + self.model_config = model_config + self.field_info: FieldInfo = field_info or FieldInfo(default) + self.discriminator_key: Optional[str] = self.field_info.discriminator + self.discriminator_alias: Optional[str] = self.discriminator_key + + self.allow_none: bool = False + self.validate_always: bool = False + self.sub_fields: Optional[List[ModelField]] = None + self.sub_fields_mapping: Optional[Dict[str, 'ModelField']] = None # used for discriminated union + self.key_field: Optional[ModelField] = None + self.validators: 'ValidatorsList' = [] + self.pre_validators: Optional['ValidatorsList'] = None + self.post_validators: Optional['ValidatorsList'] = None + self.parse_json: bool = False + self.shape: int = SHAPE_SINGLETON + self.model_config.prepare_field(self) + self.prepare() + + def get_default(self) -> Any: + return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory() + + @staticmethod + def _get_field_info( + field_name: str, annotation: Any, value: Any, config: Type['BaseConfig'] + ) -> Tuple[FieldInfo, Any]: + """ + Get a FieldInfo from a root typing.Annotated annotation, value, or config default. + + The FieldInfo may be set in typing.Annotated or the value, but not both. If neither contain + a FieldInfo, a new one will be created using the config. + + :param field_name: name of the field for use in error messages + :param annotation: a type hint such as `str` or `Annotated[str, Field(..., min_length=5)]` + :param value: the field's assigned value + :param config: the model's config object + :return: the FieldInfo contained in the `annotation`, the value, or a new one from the config. + """ + field_info_from_config = config.get_field_info(field_name) + + field_info = None + if get_origin(annotation) is Annotated: + field_infos = [arg for arg in get_args(annotation)[1:] if isinstance(arg, FieldInfo)] + if len(field_infos) > 1: + raise ValueError(f'cannot specify multiple `Annotated` `Field`s for {field_name!r}') + field_info = next(iter(field_infos), None) + if field_info is not None: + field_info = copy.copy(field_info) + field_info.update_from_config(field_info_from_config) + if field_info.default not in (Undefined, Required): + raise ValueError(f'`Field` default cannot be set in `Annotated` for {field_name!r}') + if value is not Undefined and value is not Required: + # check also `Required` because of `validate_arguments` that sets `...` as default value + field_info.default = value + + if isinstance(value, FieldInfo): + if field_info is not None: + raise ValueError(f'cannot specify `Annotated` and value `Field`s together for {field_name!r}') + field_info = value + field_info.update_from_config(field_info_from_config) + elif field_info is None: + field_info = FieldInfo(value, **field_info_from_config) + value = None if field_info.default_factory is not None else field_info.default + field_info._validate() + return field_info, value + + @classmethod + def infer( + cls, + *, + name: str, + value: Any, + annotation: Any, + class_validators: Optional[Dict[str, Validator]], + config: Type['BaseConfig'], + ) -> 'ModelField': + from pydantic.v1.schema import get_annotation_from_field_info + + field_info, value = cls._get_field_info(name, annotation, value, config) + required: 'BoolUndefined' = Undefined + if value is Required: + required = True + value = None + elif value is not Undefined: + required = False + annotation = get_annotation_from_field_info(annotation, field_info, name, config.validate_assignment) + + return cls( + name=name, + type_=annotation, + alias=field_info.alias, + class_validators=class_validators, + default=value, + default_factory=field_info.default_factory, + required=required, + model_config=config, + field_info=field_info, + ) + + def set_config(self, config: Type['BaseConfig']) -> None: + self.model_config = config + info_from_config = config.get_field_info(self.name) + config.prepare_field(self) + new_alias = info_from_config.get('alias') + new_alias_priority = info_from_config.get('alias_priority') or 0 + if new_alias and new_alias_priority >= (self.field_info.alias_priority or 0): + self.field_info.alias = new_alias + self.field_info.alias_priority = new_alias_priority + self.alias = new_alias + new_exclude = info_from_config.get('exclude') + if new_exclude is not None: + self.field_info.exclude = ValueItems.merge(self.field_info.exclude, new_exclude) + new_include = info_from_config.get('include') + if new_include is not None: + self.field_info.include = ValueItems.merge(self.field_info.include, new_include, intersect=True) + + @property + def alt_alias(self) -> bool: + return self.name != self.alias + + def prepare(self) -> None: + """ + Prepare the field but inspecting self.default, self.type_ etc. + + Note: this method is **not** idempotent (because _type_analysis is not idempotent), + e.g. calling it it multiple times may modify the field and configure it incorrectly. + """ + self._set_default_and_type() + if self.type_.__class__ is ForwardRef or self.type_.__class__ is DeferredType: + # self.type_ is currently a ForwardRef and there's nothing we can do now, + # user will need to call model.update_forward_refs() + return + + self._type_analysis() + if self.required is Undefined: + self.required = True + if self.default is Undefined and self.default_factory is None: + self.default = None + self.populate_validators() + + def _set_default_and_type(self) -> None: + """ + Set the default value, infer the type if needed and check if `None` value is valid. + """ + if self.default_factory is not None: + if self.type_ is Undefined: + raise errors_.ConfigError( + f'you need to set the type of field {self.name!r} when using `default_factory`' + ) + return + + default_value = self.get_default() + + if default_value is not None and self.type_ is Undefined: + self.type_ = default_value.__class__ + self.outer_type_ = self.type_ + self.annotation = self.type_ + + if self.type_ is Undefined: + raise errors_.ConfigError(f'unable to infer type for attribute "{self.name}"') + + if self.required is False and default_value is None: + self.allow_none = True + + def _type_analysis(self) -> None: # noqa: C901 (ignore complexity) + # typing interface is horrible, we have to do some ugly checks + if lenient_issubclass(self.type_, JsonWrapper): + self.type_ = self.type_.inner_type + self.parse_json = True + elif lenient_issubclass(self.type_, Json): + self.type_ = Any + self.parse_json = True + elif isinstance(self.type_, TypeVar): + if self.type_.__bound__: + self.type_ = self.type_.__bound__ + elif self.type_.__constraints__: + self.type_ = Union[self.type_.__constraints__] + else: + self.type_ = Any + elif is_new_type(self.type_): + self.type_ = new_type_supertype(self.type_) + + if self.type_ is Any or self.type_ is object: + if self.required is Undefined: + self.required = False + self.allow_none = True + return + elif self.type_ is Pattern or self.type_ is re.Pattern: + # python 3.7 only, Pattern is a typing object but without sub fields + return + elif is_literal_type(self.type_): + return + elif is_typeddict(self.type_): + return + + if is_finalvar(self.type_): + self.final = True + + if self.type_ is Final: + self.type_ = Any + else: + self.type_ = get_args(self.type_)[0] + + self._type_analysis() + return + + origin = get_origin(self.type_) + + if origin is Annotated or is_typeddict_special(origin): + self.type_ = get_args(self.type_)[0] + self._type_analysis() + return + + if self.discriminator_key is not None and not is_union(origin): + raise TypeError('`discriminator` can only be used with `Union` type with more than one variant') + + # add extra check for `collections.abc.Hashable` for python 3.10+ where origin is not `None` + if origin is None or origin is CollectionsHashable: + # field is not "typing" object eg. Union, Dict, List etc. + # allow None for virtual superclasses of NoneType, e.g. Hashable + if isinstance(self.type_, type) and isinstance(None, self.type_): + self.allow_none = True + return + elif origin is Callable: + return + elif is_union(origin): + types_ = [] + for type_ in get_args(self.type_): + if is_none_type(type_) or type_ is Any or type_ is object: + if self.required is Undefined: + self.required = False + self.allow_none = True + if is_none_type(type_): + continue + types_.append(type_) + + if len(types_) == 1: + # Optional[] + self.type_ = types_[0] + # this is the one case where the "outer type" isn't just the original type + self.outer_type_ = self.type_ + # re-run to correctly interpret the new self.type_ + self._type_analysis() + else: + self.sub_fields = [self._create_sub_type(t, f'{self.name}_{display_as_type(t)}') for t in types_] + + if self.discriminator_key is not None: + self.prepare_discriminated_union_sub_fields() + return + elif issubclass(origin, Tuple): # type: ignore + # origin == Tuple without item type + args = get_args(self.type_) + if not args: # plain tuple + self.type_ = Any + self.shape = SHAPE_TUPLE_ELLIPSIS + elif len(args) == 2 and args[1] is Ellipsis: # e.g. Tuple[int, ...] + self.type_ = args[0] + self.shape = SHAPE_TUPLE_ELLIPSIS + self.sub_fields = [self._create_sub_type(args[0], f'{self.name}_0')] + elif args == ((),): # Tuple[()] means empty tuple + self.shape = SHAPE_TUPLE + self.type_ = Any + self.sub_fields = [] + else: + self.shape = SHAPE_TUPLE + self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(args)] + return + elif issubclass(origin, List): + # Create self validators + get_validators = getattr(self.type_, '__get_validators__', None) + if get_validators: + self.class_validators.update( + {f'list_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())} + ) + + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_LIST + elif issubclass(origin, Set): + # Create self validators + get_validators = getattr(self.type_, '__get_validators__', None) + if get_validators: + self.class_validators.update( + {f'set_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())} + ) + + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_SET + elif issubclass(origin, FrozenSet): + # Create self validators + get_validators = getattr(self.type_, '__get_validators__', None) + if get_validators: + self.class_validators.update( + {f'frozenset_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())} + ) + + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_FROZENSET + elif issubclass(origin, Deque): + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_DEQUE + elif issubclass(origin, Sequence): + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_SEQUENCE + # priority to most common mapping: dict + elif origin is dict or origin is Dict: + self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True) + self.type_ = get_args(self.type_)[1] + self.shape = SHAPE_DICT + elif issubclass(origin, DefaultDict): + self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True) + self.type_ = get_args(self.type_)[1] + self.shape = SHAPE_DEFAULTDICT + elif issubclass(origin, Counter): + self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True) + self.type_ = int + self.shape = SHAPE_COUNTER + elif issubclass(origin, Mapping): + self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True) + self.type_ = get_args(self.type_)[1] + self.shape = SHAPE_MAPPING + # Equality check as almost everything inherits form Iterable, including str + # check for Iterable and CollectionsIterable, as it could receive one even when declared with the other + elif origin in {Iterable, CollectionsIterable}: + self.type_ = get_args(self.type_)[0] + self.shape = SHAPE_ITERABLE + self.sub_fields = [self._create_sub_type(self.type_, f'{self.name}_type')] + elif issubclass(origin, Type): # type: ignore + return + elif hasattr(origin, '__get_validators__') or self.model_config.arbitrary_types_allowed: + # Is a Pydantic-compatible generic that handles itself + # or we have arbitrary_types_allowed = True + self.shape = SHAPE_GENERIC + self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(get_args(self.type_))] + self.type_ = origin + return + else: + raise TypeError(f'Fields of type "{origin}" are not supported.') + + # type_ has been refined eg. as the type of a List and sub_fields needs to be populated + self.sub_fields = [self._create_sub_type(self.type_, '_' + self.name)] + + def prepare_discriminated_union_sub_fields(self) -> None: + """ + Prepare the mapping -> and update `sub_fields` + Note that this process can be aborted if a `ForwardRef` is encountered + """ + assert self.discriminator_key is not None + + if self.type_.__class__ is DeferredType: + return + + assert self.sub_fields is not None + sub_fields_mapping: Dict[str, 'ModelField'] = {} + all_aliases: Set[str] = set() + + for sub_field in self.sub_fields: + t = sub_field.type_ + if t.__class__ is ForwardRef: + # Stopping everything...will need to call `update_forward_refs` + return + + alias, discriminator_values = get_discriminator_alias_and_values(t, self.discriminator_key) + all_aliases.add(alias) + for discriminator_value in discriminator_values: + sub_fields_mapping[discriminator_value] = sub_field + + self.sub_fields_mapping = sub_fields_mapping + self.discriminator_alias = get_unique_discriminator_alias(all_aliases, self.discriminator_key) + + def _create_sub_type(self, type_: Type[Any], name: str, *, for_keys: bool = False) -> 'ModelField': + if for_keys: + class_validators = None + else: + # validators for sub items should not have `each_item` as we want to check only the first sublevel + class_validators = { + k: Validator( + func=v.func, + pre=v.pre, + each_item=False, + always=v.always, + check_fields=v.check_fields, + skip_on_failure=v.skip_on_failure, + ) + for k, v in self.class_validators.items() + if v.each_item + } + + field_info, _ = self._get_field_info(name, type_, None, self.model_config) + + return self.__class__( + type_=type_, + name=name, + class_validators=class_validators, + model_config=self.model_config, + field_info=field_info, + ) + + def populate_validators(self) -> None: + """ + Prepare self.pre_validators, self.validators, and self.post_validators based on self.type_'s __get_validators__ + and class validators. This method should be idempotent, e.g. it should be safe to call multiple times + without mis-configuring the field. + """ + self.validate_always = getattr(self.type_, 'validate_always', False) or any( + v.always for v in self.class_validators.values() + ) + + class_validators_ = self.class_validators.values() + if not self.sub_fields or self.shape == SHAPE_GENERIC: + get_validators = getattr(self.type_, '__get_validators__', None) + v_funcs = ( + *[v.func for v in class_validators_ if v.each_item and v.pre], + *(get_validators() if get_validators else list(find_validators(self.type_, self.model_config))), + *[v.func for v in class_validators_ if v.each_item and not v.pre], + ) + self.validators = prep_validators(v_funcs) + + self.pre_validators = [] + self.post_validators = [] + + if self.field_info and self.field_info.const: + self.post_validators.append(make_generic_validator(constant_validator)) + + if class_validators_: + self.pre_validators += prep_validators(v.func for v in class_validators_ if not v.each_item and v.pre) + self.post_validators += prep_validators(v.func for v in class_validators_ if not v.each_item and not v.pre) + + if self.parse_json: + self.pre_validators.append(make_generic_validator(validate_json)) + + self.pre_validators = self.pre_validators or None + self.post_validators = self.post_validators or None + + def validate( + self, v: Any, values: Dict[str, Any], *, loc: 'LocStr', cls: Optional['ModelOrDc'] = None + ) -> 'ValidateReturn': + assert self.type_.__class__ is not DeferredType + + if self.type_.__class__ is ForwardRef: + assert cls is not None + raise ConfigError( + f'field "{self.name}" not yet prepared so type is still a ForwardRef, ' + f'you might need to call {cls.__name__}.update_forward_refs().' + ) + + errors: Optional['ErrorList'] + if self.pre_validators: + v, errors = self._apply_validators(v, values, loc, cls, self.pre_validators) + if errors: + return v, errors + + if v is None: + if is_none_type(self.type_): + # keep validating + pass + elif self.allow_none: + if self.post_validators: + return self._apply_validators(v, values, loc, cls, self.post_validators) + else: + return None, None + else: + return v, ErrorWrapper(NoneIsNotAllowedError(), loc) + + if self.shape == SHAPE_SINGLETON: + v, errors = self._validate_singleton(v, values, loc, cls) + elif self.shape in MAPPING_LIKE_SHAPES: + v, errors = self._validate_mapping_like(v, values, loc, cls) + elif self.shape == SHAPE_TUPLE: + v, errors = self._validate_tuple(v, values, loc, cls) + elif self.shape == SHAPE_ITERABLE: + v, errors = self._validate_iterable(v, values, loc, cls) + elif self.shape == SHAPE_GENERIC: + v, errors = self._apply_validators(v, values, loc, cls, self.validators) + else: + # sequence, list, set, generator, tuple with ellipsis, frozen set + v, errors = self._validate_sequence_like(v, values, loc, cls) + + if not errors and self.post_validators: + v, errors = self._apply_validators(v, values, loc, cls, self.post_validators) + return v, errors + + def _validate_sequence_like( # noqa: C901 (ignore complexity) + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + """ + Validate sequence-like containers: lists, tuples, sets and generators + Note that large if-else blocks are necessary to enable Cython + optimization, which is why we disable the complexity check above. + """ + if not sequence_like(v): + e: errors_.PydanticTypeError + if self.shape == SHAPE_LIST: + e = errors_.ListError() + elif self.shape in (SHAPE_TUPLE, SHAPE_TUPLE_ELLIPSIS): + e = errors_.TupleError() + elif self.shape == SHAPE_SET: + e = errors_.SetError() + elif self.shape == SHAPE_FROZENSET: + e = errors_.FrozenSetError() + else: + e = errors_.SequenceError() + return v, ErrorWrapper(e, loc) + + loc = loc if isinstance(loc, tuple) else (loc,) + result = [] + errors: List[ErrorList] = [] + for i, v_ in enumerate(v): + v_loc = *loc, i + r, ee = self._validate_singleton(v_, values, v_loc, cls) + if ee: + errors.append(ee) + else: + result.append(r) + + if errors: + return v, errors + + converted: Union[List[Any], Set[Any], FrozenSet[Any], Tuple[Any, ...], Iterator[Any], Deque[Any]] = result + + if self.shape == SHAPE_SET: + converted = set(result) + elif self.shape == SHAPE_FROZENSET: + converted = frozenset(result) + elif self.shape == SHAPE_TUPLE_ELLIPSIS: + converted = tuple(result) + elif self.shape == SHAPE_DEQUE: + converted = deque(result, maxlen=getattr(v, 'maxlen', None)) + elif self.shape == SHAPE_SEQUENCE: + if isinstance(v, tuple): + converted = tuple(result) + elif isinstance(v, set): + converted = set(result) + elif isinstance(v, Generator): + converted = iter(result) + elif isinstance(v, deque): + converted = deque(result, maxlen=getattr(v, 'maxlen', None)) + return converted, None + + def _validate_iterable( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + """ + Validate Iterables. + + This intentionally doesn't validate values to allow infinite generators. + """ + + try: + iterable = iter(v) + except TypeError: + return v, ErrorWrapper(errors_.IterableError(), loc) + return iterable, None + + def _validate_tuple( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + e: Optional[Exception] = None + if not sequence_like(v): + e = errors_.TupleError() + else: + actual_length, expected_length = len(v), len(self.sub_fields) # type: ignore + if actual_length != expected_length: + e = errors_.TupleLengthError(actual_length=actual_length, expected_length=expected_length) + + if e: + return v, ErrorWrapper(e, loc) + + loc = loc if isinstance(loc, tuple) else (loc,) + result = [] + errors: List[ErrorList] = [] + for i, (v_, field) in enumerate(zip(v, self.sub_fields)): # type: ignore + v_loc = *loc, i + r, ee = field.validate(v_, values, loc=v_loc, cls=cls) + if ee: + errors.append(ee) + else: + result.append(r) + + if errors: + return v, errors + else: + return tuple(result), None + + def _validate_mapping_like( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + try: + v_iter = dict_validator(v) + except TypeError as exc: + return v, ErrorWrapper(exc, loc) + + loc = loc if isinstance(loc, tuple) else (loc,) + result, errors = {}, [] + for k, v_ in v_iter.items(): + v_loc = *loc, '__key__' + key_result, key_errors = self.key_field.validate(k, values, loc=v_loc, cls=cls) # type: ignore + if key_errors: + errors.append(key_errors) + continue + + v_loc = *loc, k + value_result, value_errors = self._validate_singleton(v_, values, v_loc, cls) + if value_errors: + errors.append(value_errors) + continue + + result[key_result] = value_result + if errors: + return v, errors + elif self.shape == SHAPE_DICT: + return result, None + elif self.shape == SHAPE_DEFAULTDICT: + return defaultdict(self.type_, result), None + elif self.shape == SHAPE_COUNTER: + return CollectionCounter(result), None + else: + return self._get_mapping_value(v, result), None + + def _get_mapping_value(self, original: T, converted: Dict[Any, Any]) -> Union[T, Dict[Any, Any]]: + """ + When type is `Mapping[KT, KV]` (or another unsupported mapping), we try to avoid + coercing to `dict` unwillingly. + """ + original_cls = original.__class__ + + if original_cls == dict or original_cls == Dict: + return converted + elif original_cls in {defaultdict, DefaultDict}: + return defaultdict(self.type_, converted) + else: + try: + # Counter, OrderedDict, UserDict, ... + return original_cls(converted) # type: ignore + except TypeError: + raise RuntimeError(f'Could not convert dictionary to {original_cls.__name__!r}') from None + + def _validate_singleton( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + if self.sub_fields: + if self.discriminator_key is not None: + return self._validate_discriminated_union(v, values, loc, cls) + + errors = [] + + if self.model_config.smart_union and is_union(get_origin(self.type_)): + # 1st pass: check if the value is an exact instance of one of the Union types + # (e.g. to avoid coercing a bool into an int) + for field in self.sub_fields: + if v.__class__ is field.outer_type_: + return v, None + + # 2nd pass: check if the value is an instance of any subclass of the Union types + for field in self.sub_fields: + # This whole logic will be improved later on to support more complex `isinstance` checks + # It will probably be done once a strict mode is added and be something like: + # ``` + # value, error = field.validate(v, values, strict=True) + # if error is None: + # return value, None + # ``` + try: + if isinstance(v, field.outer_type_): + return v, None + except TypeError: + # compound type + if lenient_isinstance(v, get_origin(field.outer_type_)): + value, error = field.validate(v, values, loc=loc, cls=cls) + if not error: + return value, None + + # 1st pass by default or 3rd pass with `smart_union` enabled: + # check if the value can be coerced into one of the Union types + for field in self.sub_fields: + value, error = field.validate(v, values, loc=loc, cls=cls) + if error: + errors.append(error) + else: + return value, None + return v, errors + else: + return self._apply_validators(v, values, loc, cls, self.validators) + + def _validate_discriminated_union( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'] + ) -> 'ValidateReturn': + assert self.discriminator_key is not None + assert self.discriminator_alias is not None + + try: + try: + discriminator_value = v[self.discriminator_alias] + except KeyError: + if self.model_config.allow_population_by_field_name: + discriminator_value = v[self.discriminator_key] + else: + raise + except KeyError: + return v, ErrorWrapper(MissingDiscriminator(discriminator_key=self.discriminator_key), loc) + except TypeError: + try: + # BaseModel or dataclass + discriminator_value = getattr(v, self.discriminator_key) + except (AttributeError, TypeError): + return v, ErrorWrapper(MissingDiscriminator(discriminator_key=self.discriminator_key), loc) + + if self.sub_fields_mapping is None: + assert cls is not None + raise ConfigError( + f'field "{self.name}" not yet prepared so type is still a ForwardRef, ' + f'you might need to call {cls.__name__}.update_forward_refs().' + ) + + try: + sub_field = self.sub_fields_mapping[discriminator_value] + except (KeyError, TypeError): + # KeyError: `discriminator_value` is not in the dictionary. + # TypeError: `discriminator_value` is unhashable. + assert self.sub_fields_mapping is not None + return v, ErrorWrapper( + InvalidDiscriminator( + discriminator_key=self.discriminator_key, + discriminator_value=discriminator_value, + allowed_values=list(self.sub_fields_mapping), + ), + loc, + ) + else: + if not isinstance(loc, tuple): + loc = (loc,) + return sub_field.validate(v, values, loc=(*loc, display_as_type(sub_field.type_)), cls=cls) + + def _apply_validators( + self, v: Any, values: Dict[str, Any], loc: 'LocStr', cls: Optional['ModelOrDc'], validators: 'ValidatorsList' + ) -> 'ValidateReturn': + for validator in validators: + try: + v = validator(cls, v, values, self, self.model_config) + except (ValueError, TypeError, AssertionError) as exc: + return v, ErrorWrapper(exc, loc) + return v, None + + def is_complex(self) -> bool: + """ + Whether the field is "complex" eg. env variables should be parsed as JSON. + """ + from pydantic.v1.main import BaseModel + + return ( + self.shape != SHAPE_SINGLETON + or hasattr(self.type_, '__pydantic_model__') + or lenient_issubclass(self.type_, (BaseModel, list, set, frozenset, dict)) + ) + + def _type_display(self) -> PyObjectStr: + t = display_as_type(self.type_) + + if self.shape in MAPPING_LIKE_SHAPES: + t = f'Mapping[{display_as_type(self.key_field.type_)}, {t}]' # type: ignore + elif self.shape == SHAPE_TUPLE: + t = 'Tuple[{}]'.format(', '.join(display_as_type(f.type_) for f in self.sub_fields)) # type: ignore + elif self.shape == SHAPE_GENERIC: + assert self.sub_fields + t = '{}[{}]'.format( + display_as_type(self.type_), ', '.join(display_as_type(f.type_) for f in self.sub_fields) + ) + elif self.shape != SHAPE_SINGLETON: + t = SHAPE_NAME_LOOKUP[self.shape].format(t) + + if self.allow_none and (self.shape != SHAPE_SINGLETON or not self.sub_fields): + t = f'Optional[{t}]' + return PyObjectStr(t) + + def __repr_args__(self) -> 'ReprArgs': + args = [('name', self.name), ('type', self._type_display()), ('required', self.required)] + + if not self.required: + if self.default_factory is not None: + args.append(('default_factory', f'')) + else: + args.append(('default', self.default)) + + if self.alt_alias: + args.append(('alias', self.alias)) + return args + + +class ModelPrivateAttr(Representation): + __slots__ = ('default', 'default_factory') + + def __init__(self, default: Any = Undefined, *, default_factory: Optional[NoArgAnyCallable] = None) -> None: + self.default = default + self.default_factory = default_factory + + def get_default(self) -> Any: + return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory() + + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) and (self.default, self.default_factory) == ( + other.default, + other.default_factory, + ) + + +def PrivateAttr( + default: Any = Undefined, + *, + default_factory: Optional[NoArgAnyCallable] = None, +) -> Any: + """ + Indicates that attribute is only used internally and never mixed with regular fields. + + Types or values of private attrs are not checked by pydantic and it's up to you to keep them relevant. + + Private attrs are stored in model __slots__. + + :param default: the attribute’s default value + :param default_factory: callable that will be called when a default value is needed for this attribute + If both `default` and `default_factory` are set, an error is raised. + """ + if default is not Undefined and default_factory is not None: + raise ValueError('cannot specify both default and default_factory') + + return ModelPrivateAttr( + default, + default_factory=default_factory, + ) + + +class DeferredType: + """ + Used to postpone field preparation, while creating recursive generic models. + """ + + +def is_finalvar_with_default_val(type_: Type[Any], val: Any) -> bool: + return is_finalvar(type_) and val is not Undefined and not isinstance(val, FieldInfo) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py new file mode 100644 index 0000000..fa1dcec --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py @@ -0,0 +1,400 @@ +import functools +import operator +import sys +import types +import typing +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Dict, + ForwardRef, + Generic, + Iterator, + List, + Mapping, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) +from weakref import WeakKeyDictionary, WeakValueDictionary + +from typing_extensions import Annotated, Literal as ExtLiteral + +from pydantic.v1.class_validators import gather_all_validators +from pydantic.v1.fields import DeferredType +from pydantic.v1.main import BaseModel, create_model +from pydantic.v1.types import JsonWrapper +from pydantic.v1.typing import display_as_type, get_all_type_hints, get_args, get_origin, typing_base +from pydantic.v1.utils import all_identical, lenient_issubclass + +if sys.version_info >= (3, 8): + from typing import Literal + +GenericModelT = TypeVar('GenericModelT', bound='GenericModel') +TypeVarType = Any # since mypy doesn't allow the use of TypeVar as a type + +CacheKey = Tuple[Type[Any], Any, Tuple[Any, ...]] +Parametrization = Mapping[TypeVarType, Type[Any]] + +# weak dictionaries allow the dynamically created parametrized versions of generic models to get collected +# once they are no longer referenced by the caller. +if sys.version_info >= (3, 9): # Typing for weak dictionaries available at 3.9 + GenericTypesCache = WeakValueDictionary[CacheKey, Type[BaseModel]] + AssignedParameters = WeakKeyDictionary[Type[BaseModel], Parametrization] +else: + GenericTypesCache = WeakValueDictionary + AssignedParameters = WeakKeyDictionary + +# _generic_types_cache is a Mapping from __class_getitem__ arguments to the parametrized version of generic models. +# This ensures multiple calls of e.g. A[B] return always the same class. +_generic_types_cache = GenericTypesCache() + +# _assigned_parameters is a Mapping from parametrized version of generic models to assigned types of parametrizations +# as captured during construction of the class (not instances). +# E.g., for generic model `Model[A, B]`, when parametrized model `Model[int, str]` is created, +# `Model[int, str]`: {A: int, B: str}` will be stored in `_assigned_parameters`. +# (This information is only otherwise available after creation from the class name string). +_assigned_parameters = AssignedParameters() + + +class GenericModel(BaseModel): + __slots__ = () + __concrete__: ClassVar[bool] = False + + if TYPE_CHECKING: + # Putting this in a TYPE_CHECKING block allows us to replace `if Generic not in cls.__bases__` with + # `not hasattr(cls, "__parameters__")`. This means we don't need to force non-concrete subclasses of + # `GenericModel` to also inherit from `Generic`, which would require changes to the use of `create_model` below. + __parameters__: ClassVar[Tuple[TypeVarType, ...]] + + # Setting the return type as Type[Any] instead of Type[BaseModel] prevents PyCharm warnings + def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]: + """Instantiates a new class from a generic class `cls` and type variables `params`. + + :param params: Tuple of types the class . Given a generic class + `Model` with 2 type variables and a concrete model `Model[str, int]`, + the value `(str, int)` would be passed to `params`. + :return: New model class inheriting from `cls` with instantiated + types described by `params`. If no parameters are given, `cls` is + returned as is. + + """ + + def _cache_key(_params: Any) -> CacheKey: + args = get_args(_params) + # python returns a list for Callables, which is not hashable + if len(args) == 2 and isinstance(args[0], list): + args = (tuple(args[0]), args[1]) + return cls, _params, args + + cached = _generic_types_cache.get(_cache_key(params)) + if cached is not None: + return cached + if cls.__concrete__ and Generic not in cls.__bases__: + raise TypeError('Cannot parameterize a concrete instantiation of a generic model') + if not isinstance(params, tuple): + params = (params,) + if cls is GenericModel and any(isinstance(param, TypeVar) for param in params): + raise TypeError('Type parameters should be placed on typing.Generic, not GenericModel') + if not hasattr(cls, '__parameters__'): + raise TypeError(f'Type {cls.__name__} must inherit from typing.Generic before being parameterized') + + check_parameters_count(cls, params) + # Build map from generic typevars to passed params + typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params)) + if all_identical(typevars_map.keys(), typevars_map.values()) and typevars_map: + return cls # if arguments are equal to parameters it's the same object + + # Create new model with original model as parent inserting fields with DeferredType. + model_name = cls.__concrete_name__(params) + validators = gather_all_validators(cls) + + type_hints = get_all_type_hints(cls).items() + instance_type_hints = {k: v for k, v in type_hints if get_origin(v) is not ClassVar} + + fields = {k: (DeferredType(), cls.__fields__[k].field_info) for k in instance_type_hints if k in cls.__fields__} + + model_module, called_globally = get_caller_frame_info() + created_model = cast( + Type[GenericModel], # casting ensures mypy is aware of the __concrete__ and __parameters__ attributes + create_model( + model_name, + __module__=model_module or cls.__module__, + __base__=(cls,) + tuple(cls.__parameterized_bases__(typevars_map)), + __config__=None, + __validators__=validators, + __cls_kwargs__=None, + **fields, + ), + ) + + _assigned_parameters[created_model] = typevars_map + + if called_globally: # create global reference and therefore allow pickling + object_by_reference = None + reference_name = model_name + reference_module_globals = sys.modules[created_model.__module__].__dict__ + while object_by_reference is not created_model: + object_by_reference = reference_module_globals.setdefault(reference_name, created_model) + reference_name += '_' + + created_model.Config = cls.Config + + # Find any typevars that are still present in the model. + # If none are left, the model is fully "concrete", otherwise the new + # class is a generic class as well taking the found typevars as + # parameters. + new_params = tuple( + {param: None for param in iter_contained_typevars(typevars_map.values())} + ) # use dict as ordered set + created_model.__concrete__ = not new_params + if new_params: + created_model.__parameters__ = new_params + + # Save created model in cache so we don't end up creating duplicate + # models that should be identical. + _generic_types_cache[_cache_key(params)] = created_model + if len(params) == 1: + _generic_types_cache[_cache_key(params[0])] = created_model + + # Recursively walk class type hints and replace generic typevars + # with concrete types that were passed. + _prepare_model_fields(created_model, fields, instance_type_hints, typevars_map) + + return created_model + + @classmethod + def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str: + """Compute class name for child classes. + + :param params: Tuple of types the class . Given a generic class + `Model` with 2 type variables and a concrete model `Model[str, int]`, + the value `(str, int)` would be passed to `params`. + :return: String representing a the new class where `params` are + passed to `cls` as type variables. + + This method can be overridden to achieve a custom naming scheme for GenericModels. + """ + param_names = [display_as_type(param) for param in params] + params_component = ', '.join(param_names) + return f'{cls.__name__}[{params_component}]' + + @classmethod + def __parameterized_bases__(cls, typevars_map: Parametrization) -> Iterator[Type[Any]]: + """ + Returns unbound bases of cls parameterised to given type variables + + :param typevars_map: Dictionary of type applications for binding subclasses. + Given a generic class `Model` with 2 type variables [S, T] + and a concrete model `Model[str, int]`, + the value `{S: str, T: int}` would be passed to `typevars_map`. + :return: an iterator of generic sub classes, parameterised by `typevars_map` + and other assigned parameters of `cls` + + e.g.: + ``` + class A(GenericModel, Generic[T]): + ... + + class B(A[V], Generic[V]): + ... + + assert A[int] in B.__parameterized_bases__({V: int}) + ``` + """ + + def build_base_model( + base_model: Type[GenericModel], mapped_types: Parametrization + ) -> Iterator[Type[GenericModel]]: + base_parameters = tuple(mapped_types[param] for param in base_model.__parameters__) + parameterized_base = base_model.__class_getitem__(base_parameters) + if parameterized_base is base_model or parameterized_base is cls: + # Avoid duplication in MRO + return + yield parameterized_base + + for base_model in cls.__bases__: + if not issubclass(base_model, GenericModel): + # not a class that can be meaningfully parameterized + continue + elif not getattr(base_model, '__parameters__', None): + # base_model is "GenericModel" (and has no __parameters__) + # or + # base_model is already concrete, and will be included transitively via cls. + continue + elif cls in _assigned_parameters: + if base_model in _assigned_parameters: + # cls is partially parameterised but not from base_model + # e.g. cls = B[S], base_model = A[S] + # B[S][int] should subclass A[int], (and will be transitively via B[int]) + # but it's not viable to consistently subclass types with arbitrary construction + # So don't attempt to include A[S][int] + continue + else: # base_model not in _assigned_parameters: + # cls is partially parameterized, base_model is original generic + # e.g. cls = B[str, T], base_model = B[S, T] + # Need to determine the mapping for the base_model parameters + mapped_types: Parametrization = { + key: typevars_map.get(value, value) for key, value in _assigned_parameters[cls].items() + } + yield from build_base_model(base_model, mapped_types) + else: + # cls is base generic, so base_class has a distinct base + # can construct the Parameterised base model using typevars_map directly + yield from build_base_model(base_model, typevars_map) + + +def replace_types(type_: Any, type_map: Mapping[Any, Any]) -> Any: + """Return type with all occurrences of `type_map` keys recursively replaced with their values. + + :param type_: Any type, class or generic alias + :param type_map: Mapping from `TypeVar` instance to concrete types. + :return: New type representing the basic structure of `type_` with all + `typevar_map` keys recursively replaced. + + >>> replace_types(Tuple[str, Union[List[str], float]], {str: int}) + Tuple[int, Union[List[int], float]] + + """ + if not type_map: + return type_ + + type_args = get_args(type_) + origin_type = get_origin(type_) + + if origin_type is Annotated: + annotated_type, *annotations = type_args + return Annotated[replace_types(annotated_type, type_map), tuple(annotations)] + + if (origin_type is ExtLiteral) or (sys.version_info >= (3, 8) and origin_type is Literal): + return type_map.get(type_, type_) + # Having type args is a good indicator that this is a typing module + # class instantiation or a generic alias of some sort. + if type_args: + resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args) + if all_identical(type_args, resolved_type_args): + # If all arguments are the same, there is no need to modify the + # type or create a new object at all + return type_ + if ( + origin_type is not None + and isinstance(type_, typing_base) + and not isinstance(origin_type, typing_base) + and getattr(type_, '_name', None) is not None + ): + # In python < 3.9 generic aliases don't exist so any of these like `list`, + # `type` or `collections.abc.Callable` need to be translated. + # See: https://www.python.org/dev/peps/pep-0585 + origin_type = getattr(typing, type_._name) + assert origin_type is not None + # PEP-604 syntax (Ex.: list | str) is represented with a types.UnionType object that does not have __getitem__. + # We also cannot use isinstance() since we have to compare types. + if sys.version_info >= (3, 10) and origin_type is types.UnionType: # noqa: E721 + return functools.reduce(operator.or_, resolved_type_args) + return origin_type[resolved_type_args] + + # We handle pydantic generic models separately as they don't have the same + # semantics as "typing" classes or generic aliases + if not origin_type and lenient_issubclass(type_, GenericModel) and not type_.__concrete__: + type_args = type_.__parameters__ + resolved_type_args = tuple(replace_types(t, type_map) for t in type_args) + if all_identical(type_args, resolved_type_args): + return type_ + return type_[resolved_type_args] + + # Handle special case for typehints that can have lists as arguments. + # `typing.Callable[[int, str], int]` is an example for this. + if isinstance(type_, (List, list)): + resolved_list = list(replace_types(element, type_map) for element in type_) + if all_identical(type_, resolved_list): + return type_ + return resolved_list + + # For JsonWrapperValue, need to handle its inner type to allow correct parsing + # of generic Json arguments like Json[T] + if not origin_type and lenient_issubclass(type_, JsonWrapper): + type_.inner_type = replace_types(type_.inner_type, type_map) + return type_ + + # If all else fails, we try to resolve the type directly and otherwise just + # return the input with no modifications. + new_type = type_map.get(type_, type_) + # Convert string to ForwardRef + if isinstance(new_type, str): + return ForwardRef(new_type) + else: + return new_type + + +def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...]) -> None: + actual = len(parameters) + expected = len(cls.__parameters__) + if actual != expected: + description = 'many' if actual > expected else 'few' + raise TypeError(f'Too {description} parameters for {cls.__name__}; actual {actual}, expected {expected}') + + +DictValues: Type[Any] = {}.values().__class__ + + +def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]: + """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found.""" + if isinstance(v, TypeVar): + yield v + elif hasattr(v, '__parameters__') and not get_origin(v) and lenient_issubclass(v, GenericModel): + yield from v.__parameters__ + elif isinstance(v, (DictValues, list)): + for var in v: + yield from iter_contained_typevars(var) + else: + args = get_args(v) + for arg in args: + yield from iter_contained_typevars(arg) + + +def get_caller_frame_info() -> Tuple[Optional[str], bool]: + """ + Used inside a function to check whether it was called globally + + Will only work against non-compiled code, therefore used only in pydantic.generics + + :returns Tuple[module_name, called_globally] + """ + try: + previous_caller_frame = sys._getframe(2) + except ValueError as e: + raise RuntimeError('This function must be used inside another function') from e + except AttributeError: # sys module does not have _getframe function, so there's nothing we can do about it + return None, False + frame_globals = previous_caller_frame.f_globals + return frame_globals.get('__name__'), previous_caller_frame.f_locals is frame_globals + + +def _prepare_model_fields( + created_model: Type[GenericModel], + fields: Mapping[str, Any], + instance_type_hints: Mapping[str, type], + typevars_map: Mapping[Any, type], +) -> None: + """ + Replace DeferredType fields with concrete type hints and prepare them. + """ + + for key, field in created_model.__fields__.items(): + if key not in fields: + assert field.type_.__class__ is not DeferredType + # https://github.com/nedbat/coveragepy/issues/198 + continue # pragma: no cover + + assert field.type_.__class__ is DeferredType, field.type_.__class__ + + field_type_hint = instance_type_hints[key] + concrete_type = replace_types(field_type_hint, typevars_map) + field.type_ = concrete_type + field.outer_type_ = concrete_type + field.prepare() + created_model.__annotations__[key] = concrete_type diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py new file mode 100644 index 0000000..41d0d5f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py @@ -0,0 +1,112 @@ +import datetime +from collections import deque +from decimal import Decimal +from enum import Enum +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from pathlib import Path +from re import Pattern +from types import GeneratorType +from typing import Any, Callable, Dict, Type, Union +from uuid import UUID + +from pydantic.v1.color import Color +from pydantic.v1.networks import NameEmail +from pydantic.v1.types import SecretBytes, SecretStr + +__all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat' + + +def isoformat(o: Union[datetime.date, datetime.time]) -> str: + return o.isoformat() + + +def decimal_encoder(dec_value: Decimal) -> Union[int, float]: + """ + Encodes a Decimal as int of there's no exponent, otherwise float + + This is useful when we use ConstrainedDecimal to represent Numeric(x,0) + where a integer (but not int typed) is used. Encoding this as a float + results in failed round-tripping between encode and parse. + Our Id type is a prime example of this. + + >>> decimal_encoder(Decimal("1.0")) + 1.0 + + >>> decimal_encoder(Decimal("1")) + 1 + """ + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + else: + return float(dec_value) + + +ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { + bytes: lambda o: o.decode(), + Color: str, + datetime.date: isoformat, + datetime.datetime: isoformat, + datetime.time: isoformat, + datetime.timedelta: lambda td: td.total_seconds(), + Decimal: decimal_encoder, + Enum: lambda o: o.value, + frozenset: list, + deque: list, + GeneratorType: list, + IPv4Address: str, + IPv4Interface: str, + IPv4Network: str, + IPv6Address: str, + IPv6Interface: str, + IPv6Network: str, + NameEmail: str, + Path: str, + Pattern: lambda o: o.pattern, + SecretBytes: str, + SecretStr: str, + set: list, + UUID: str, +} + + +def pydantic_encoder(obj: Any) -> Any: + from dataclasses import asdict, is_dataclass + + from pydantic.v1.main import BaseModel + + if isinstance(obj, BaseModel): + return obj.dict() + elif is_dataclass(obj): + return asdict(obj) + + # Check the class type and its superclasses for a matching encoder + for base in obj.__class__.__mro__[:-1]: + try: + encoder = ENCODERS_BY_TYPE[base] + except KeyError: + continue + return encoder(obj) + else: # We have exited the for loop without finding a suitable encoder + raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable") + + +def custom_pydantic_encoder(type_encoders: Dict[Any, Callable[[Type[Any]], Any]], obj: Any) -> Any: + # Check the class type and its superclasses for a matching encoder + for base in obj.__class__.__mro__[:-1]: + try: + encoder = type_encoders[base] + except KeyError: + continue + + return encoder(obj) + else: # We have exited the for loop without finding a suitable encoder + return pydantic_encoder(obj) + + +def timedelta_isoformat(td: datetime.timedelta) -> str: + """ + ISO 8601 encoding for Python timedelta object. + """ + minutes, seconds = divmod(td.seconds, 60) + hours, minutes = divmod(minutes, 60) + return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py new file mode 100644 index 0000000..36e9c44 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py @@ -0,0 +1,1130 @@ +import sys +import warnings +from abc import ABCMeta +from copy import deepcopy +from enum import Enum +from functools import partial +from pathlib import Path +from types import FunctionType, prepare_class, resolve_bases +from typing import ( + TYPE_CHECKING, + AbstractSet, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + no_type_check, + overload, +) + +from typing_extensions import dataclass_transform + +from pydantic.v1.class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators +from pydantic.v1.config import BaseConfig, Extra, inherit_config, prepare_config +from pydantic.v1.error_wrappers import ErrorWrapper, ValidationError +from pydantic.v1.errors import ConfigError, DictError, ExtraError, MissingError +from pydantic.v1.fields import ( + MAPPING_LIKE_SHAPES, + Field, + ModelField, + ModelPrivateAttr, + PrivateAttr, + Undefined, + is_finalvar_with_default_val, +) +from pydantic.v1.json import custom_pydantic_encoder, pydantic_encoder +from pydantic.v1.parse import Protocol, load_file, load_str_bytes +from pydantic.v1.schema import default_ref_template, model_schema +from pydantic.v1.types import PyObject, StrBytes +from pydantic.v1.typing import ( + AnyCallable, + get_args, + get_origin, + is_classvar, + is_namedtuple, + is_union, + resolve_annotations, + update_model_forward_refs, +) +from pydantic.v1.utils import ( + DUNDER_ATTRIBUTES, + ROOT_KEY, + ClassAttribute, + GetterDict, + Representation, + ValueItems, + generate_model_signature, + is_valid_field, + is_valid_private_name, + lenient_issubclass, + sequence_like, + smart_deepcopy, + unique_list, + validate_field_name, +) + +if TYPE_CHECKING: + from inspect import Signature + + from pydantic.v1.class_validators import ValidatorListDict + from pydantic.v1.types import ModelOrDc + from pydantic.v1.typing import ( + AbstractSetIntStr, + AnyClassMethod, + CallableGenerator, + DictAny, + DictStrAny, + MappingIntStrAny, + ReprArgs, + SetStr, + TupleGenerator, + ) + + Model = TypeVar('Model', bound='BaseModel') + +__all__ = 'BaseModel', 'create_model', 'validate_model' + +_T = TypeVar('_T') + + +def validate_custom_root_type(fields: Dict[str, ModelField]) -> None: + if len(fields) > 1: + raise ValueError(f'{ROOT_KEY} cannot be mixed with other fields') + + +def generate_hash_function(frozen: bool) -> Optional[Callable[[Any], int]]: + def hash_function(self_: Any) -> int: + return hash(self_.__class__) + hash(tuple(self_.__dict__.values())) + + return hash_function if frozen else None + + +# If a field is of type `Callable`, its default value should be a function and cannot to ignored. +ANNOTATED_FIELD_UNTOUCHED_TYPES: Tuple[Any, ...] = (property, type, classmethod, staticmethod) +# When creating a `BaseModel` instance, we bypass all the methods, properties... added to the model +UNTOUCHED_TYPES: Tuple[Any, ...] = (FunctionType,) + ANNOTATED_FIELD_UNTOUCHED_TYPES +# Note `ModelMetaclass` refers to `BaseModel`, but is also used to *create* `BaseModel`, so we need to add this extra +# (somewhat hacky) boolean to keep track of whether we've created the `BaseModel` class yet, and therefore whether it's +# safe to refer to it. If it *hasn't* been created, we assume that the `__new__` call we're in the middle of is for +# the `BaseModel` class, since that's defined immediately after the metaclass. +_is_base_model_class_defined = False + + +@dataclass_transform(kw_only_default=True, field_specifiers=(Field,)) +class ModelMetaclass(ABCMeta): + @no_type_check # noqa C901 + def __new__(mcs, name, bases, namespace, **kwargs): # noqa C901 + fields: Dict[str, ModelField] = {} + config = BaseConfig + validators: 'ValidatorListDict' = {} + + pre_root_validators, post_root_validators = [], [] + private_attributes: Dict[str, ModelPrivateAttr] = {} + base_private_attributes: Dict[str, ModelPrivateAttr] = {} + slots: SetStr = namespace.get('__slots__', ()) + slots = {slots} if isinstance(slots, str) else set(slots) + class_vars: SetStr = set() + hash_func: Optional[Callable[[Any], int]] = None + + for base in reversed(bases): + if _is_base_model_class_defined and issubclass(base, BaseModel) and base != BaseModel: + fields.update(smart_deepcopy(base.__fields__)) + config = inherit_config(base.__config__, config) + validators = inherit_validators(base.__validators__, validators) + pre_root_validators += base.__pre_root_validators__ + post_root_validators += base.__post_root_validators__ + base_private_attributes.update(base.__private_attributes__) + class_vars.update(base.__class_vars__) + hash_func = base.__hash__ + + resolve_forward_refs = kwargs.pop('__resolve_forward_refs__', True) + allowed_config_kwargs: SetStr = { + key + for key in dir(config) + if not (key.startswith('__') and key.endswith('__')) # skip dunder methods and attributes + } + config_kwargs = {key: kwargs.pop(key) for key in kwargs.keys() & allowed_config_kwargs} + config_from_namespace = namespace.get('Config') + if config_kwargs and config_from_namespace: + raise TypeError('Specifying config in two places is ambiguous, use either Config attribute or class kwargs') + config = inherit_config(config_from_namespace, config, **config_kwargs) + + validators = inherit_validators(extract_validators(namespace), validators) + vg = ValidatorGroup(validators) + + for f in fields.values(): + f.set_config(config) + extra_validators = vg.get_validators(f.name) + if extra_validators: + f.class_validators.update(extra_validators) + # re-run prepare to add extra validators + f.populate_validators() + + prepare_config(config, name) + + untouched_types = ANNOTATED_FIELD_UNTOUCHED_TYPES + + def is_untouched(v: Any) -> bool: + return isinstance(v, untouched_types) or v.__class__.__name__ == 'cython_function_or_method' + + if (namespace.get('__module__'), namespace.get('__qualname__')) != ('pydantic.main', 'BaseModel'): + if sys.version_info >= (3, 14): + if '__annotations__' in namespace: + # `from __future__ import annotations` was used in the model's module + raw_annotations = namespace['__annotations__'] + else: + # See https://docs.python.org/3/library/annotationlib.html#using-annotations-in-a-metaclass: + from annotationlib import Format, call_annotate_function, get_annotate_from_class_namespace + + annotate = get_annotate_from_class_namespace(namespace) + if annotate is not None: + raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF) + else: + raw_annotations = {} + else: + raw_annotations = namespace.get('__annotations__', {}) + + annotations = resolve_annotations(raw_annotations, namespace.get('__module__', None)) + # annotation only fields need to come first in fields + for ann_name, ann_type in annotations.items(): + if is_classvar(ann_type): + class_vars.add(ann_name) + elif is_finalvar_with_default_val(ann_type, namespace.get(ann_name, Undefined)): + class_vars.add(ann_name) + elif is_valid_field(ann_name): + validate_field_name(bases, ann_name) + value = namespace.get(ann_name, Undefined) + allowed_types = get_args(ann_type) if is_union(get_origin(ann_type)) else (ann_type,) + if ( + is_untouched(value) + and ann_type != PyObject + and not any( + lenient_issubclass(get_origin(allowed_type), Type) for allowed_type in allowed_types + ) + ): + continue + fields[ann_name] = ModelField.infer( + name=ann_name, + value=value, + annotation=ann_type, + class_validators=vg.get_validators(ann_name), + config=config, + ) + elif ann_name not in namespace and config.underscore_attrs_are_private: + private_attributes[ann_name] = PrivateAttr() + + untouched_types = UNTOUCHED_TYPES + config.keep_untouched + for var_name, value in namespace.items(): + can_be_changed = var_name not in class_vars and not is_untouched(value) + if isinstance(value, ModelPrivateAttr): + if not is_valid_private_name(var_name): + raise NameError( + f'Private attributes "{var_name}" must not be a valid field name; ' + f'Use sunder or dunder names, e. g. "_{var_name}" or "__{var_name}__"' + ) + private_attributes[var_name] = value + elif config.underscore_attrs_are_private and is_valid_private_name(var_name) and can_be_changed: + private_attributes[var_name] = PrivateAttr(default=value) + elif is_valid_field(var_name) and var_name not in annotations and can_be_changed: + validate_field_name(bases, var_name) + inferred = ModelField.infer( + name=var_name, + value=value, + annotation=annotations.get(var_name, Undefined), + class_validators=vg.get_validators(var_name), + config=config, + ) + if var_name in fields: + if lenient_issubclass(inferred.type_, fields[var_name].type_): + inferred.type_ = fields[var_name].type_ + else: + raise TypeError( + f'The type of {name}.{var_name} differs from the new default value; ' + f'if you wish to change the type of this field, please use a type annotation' + ) + fields[var_name] = inferred + + _custom_root_type = ROOT_KEY in fields + if _custom_root_type: + validate_custom_root_type(fields) + vg.check_for_unused() + if config.json_encoders: + json_encoder = partial(custom_pydantic_encoder, config.json_encoders) + else: + json_encoder = pydantic_encoder + pre_rv_new, post_rv_new = extract_root_validators(namespace) + + if hash_func is None: + hash_func = generate_hash_function(config.frozen) + + exclude_from_namespace = fields | private_attributes.keys() | {'__slots__'} + new_namespace = { + '__config__': config, + '__fields__': fields, + '__exclude_fields__': { + name: field.field_info.exclude for name, field in fields.items() if field.field_info.exclude is not None + } + or None, + '__include_fields__': { + name: field.field_info.include for name, field in fields.items() if field.field_info.include is not None + } + or None, + '__validators__': vg.validators, + '__pre_root_validators__': unique_list( + pre_root_validators + pre_rv_new, + name_factory=lambda v: v.__name__, + ), + '__post_root_validators__': unique_list( + post_root_validators + post_rv_new, + name_factory=lambda skip_on_failure_and_v: skip_on_failure_and_v[1].__name__, + ), + '__schema_cache__': {}, + '__json_encoder__': staticmethod(json_encoder), + '__custom_root_type__': _custom_root_type, + '__private_attributes__': {**base_private_attributes, **private_attributes}, + '__slots__': slots | private_attributes.keys(), + '__hash__': hash_func, + '__class_vars__': class_vars, + **{n: v for n, v in namespace.items() if n not in exclude_from_namespace}, + } + + cls = super().__new__(mcs, name, bases, new_namespace, **kwargs) + # set __signature__ attr only for model class, but not for its instances + cls.__signature__ = ClassAttribute('__signature__', generate_model_signature(cls.__init__, fields, config)) + + if not _is_base_model_class_defined: + # Cython does not understand the `if TYPE_CHECKING:` condition in the + # BaseModel's body (where annotations are set), so clear them manually: + getattr(cls, '__annotations__', {}).clear() + + if resolve_forward_refs: + cls.__try_update_forward_refs__() + + # preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487 + # for attributes not in `new_namespace` (e.g. private attributes) + for name, obj in namespace.items(): + if name not in new_namespace: + set_name = getattr(obj, '__set_name__', None) + if callable(set_name): + set_name(cls, name) + + return cls + + def __instancecheck__(self, instance: Any) -> bool: + """ + Avoid calling ABC _abc_subclasscheck unless we're pretty sure. + + See #3829 and python/cpython#92810 + """ + return hasattr(instance, '__post_root_validators__') and super().__instancecheck__(instance) + + +object_setattr = object.__setattr__ + + +class BaseModel(Representation, metaclass=ModelMetaclass): + if TYPE_CHECKING: + # populated by the metaclass, defined here to help IDEs only + __fields__: ClassVar[Dict[str, ModelField]] = {} + __include_fields__: ClassVar[Optional[Mapping[str, Any]]] = None + __exclude_fields__: ClassVar[Optional[Mapping[str, Any]]] = None + __validators__: ClassVar[Dict[str, AnyCallable]] = {} + __pre_root_validators__: ClassVar[List[AnyCallable]] + __post_root_validators__: ClassVar[List[Tuple[bool, AnyCallable]]] + __config__: ClassVar[Type[BaseConfig]] = BaseConfig + __json_encoder__: ClassVar[Callable[[Any], Any]] = lambda x: x + __schema_cache__: ClassVar['DictAny'] = {} + __custom_root_type__: ClassVar[bool] = False + __signature__: ClassVar['Signature'] + __private_attributes__: ClassVar[Dict[str, ModelPrivateAttr]] + __class_vars__: ClassVar[SetStr] + __fields_set__: ClassVar[SetStr] = set() + + Config = BaseConfig + __slots__ = ('__dict__', '__fields_set__') + __doc__ = '' # Null out the Representation docstring + + def __init__(__pydantic_self__, **data: Any) -> None: + """ + Create a new model by parsing and validating input data from keyword arguments. + + Raises ValidationError if the input data cannot be parsed to form a valid model. + """ + # Uses something other than `self` the first arg to allow "self" as a settable attribute + values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data) + if validation_error: + raise validation_error + try: + object_setattr(__pydantic_self__, '__dict__', values) + except TypeError as e: + raise TypeError( + 'Model values must be a dict; you may not have returned a dictionary from a root validator' + ) from e + object_setattr(__pydantic_self__, '__fields_set__', fields_set) + __pydantic_self__._init_private_attributes() + + @no_type_check + def __setattr__(self, name, value): # noqa: C901 (ignore complexity) + if name in self.__private_attributes__ or name in DUNDER_ATTRIBUTES: + return object_setattr(self, name, value) + + if self.__config__.extra is not Extra.allow and name not in self.__fields__: + raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"') + elif not self.__config__.allow_mutation or self.__config__.frozen: + raise TypeError(f'"{self.__class__.__name__}" is immutable and does not support item assignment') + elif name in self.__fields__ and self.__fields__[name].final: + raise TypeError( + f'"{self.__class__.__name__}" object "{name}" field is final and does not support reassignment' + ) + elif self.__config__.validate_assignment: + new_values = {**self.__dict__, name: value} + + for validator in self.__pre_root_validators__: + try: + new_values = validator(self.__class__, new_values) + except (ValueError, TypeError, AssertionError) as exc: + raise ValidationError([ErrorWrapper(exc, loc=ROOT_KEY)], self.__class__) + + known_field = self.__fields__.get(name, None) + if known_field: + # We want to + # - make sure validators are called without the current value for this field inside `values` + # - keep other values (e.g. submodels) untouched (using `BaseModel.dict()` will change them into dicts) + # - keep the order of the fields + if not known_field.field_info.allow_mutation: + raise TypeError(f'"{known_field.name}" has allow_mutation set to False and cannot be assigned') + dict_without_original_value = {k: v for k, v in self.__dict__.items() if k != name} + value, error_ = known_field.validate(value, dict_without_original_value, loc=name, cls=self.__class__) + if error_: + raise ValidationError([error_], self.__class__) + else: + new_values[name] = value + + errors = [] + for skip_on_failure, validator in self.__post_root_validators__: + if skip_on_failure and errors: + continue + try: + new_values = validator(self.__class__, new_values) + except (ValueError, TypeError, AssertionError) as exc: + errors.append(ErrorWrapper(exc, loc=ROOT_KEY)) + if errors: + raise ValidationError(errors, self.__class__) + + # update the whole __dict__ as other values than just `value` + # may be changed (e.g. with `root_validator`) + object_setattr(self, '__dict__', new_values) + else: + self.__dict__[name] = value + + self.__fields_set__.add(name) + + def __getstate__(self) -> 'DictAny': + private_attrs = ((k, getattr(self, k, Undefined)) for k in self.__private_attributes__) + return { + '__dict__': self.__dict__, + '__fields_set__': self.__fields_set__, + '__private_attribute_values__': {k: v for k, v in private_attrs if v is not Undefined}, + } + + def __setstate__(self, state: 'DictAny') -> None: + object_setattr(self, '__dict__', state['__dict__']) + object_setattr(self, '__fields_set__', state['__fields_set__']) + for name, value in state.get('__private_attribute_values__', {}).items(): + object_setattr(self, name, value) + + def _init_private_attributes(self) -> None: + for name, private_attr in self.__private_attributes__.items(): + default = private_attr.get_default() + if default is not Undefined: + object_setattr(self, name, default) + + def dict( + self, + *, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + by_alias: bool = False, + skip_defaults: Optional[bool] = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> 'DictStrAny': + """ + Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + """ + if skip_defaults is not None: + warnings.warn( + f'{self.__class__.__name__}.dict(): "skip_defaults" is deprecated and replaced by "exclude_unset"', + DeprecationWarning, + ) + exclude_unset = skip_defaults + + return dict( + self._iter( + to_dict=True, + by_alias=by_alias, + include=include, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + ) + + def json( + self, + *, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + by_alias: bool = False, + skip_defaults: Optional[bool] = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + encoder: Optional[Callable[[Any], Any]] = None, + models_as_dict: bool = True, + **dumps_kwargs: Any, + ) -> str: + """ + Generate a JSON representation of the model, `include` and `exclude` arguments as per `dict()`. + + `encoder` is an optional function to supply as `default` to json.dumps(), other arguments as per `json.dumps()`. + """ + if skip_defaults is not None: + warnings.warn( + f'{self.__class__.__name__}.json(): "skip_defaults" is deprecated and replaced by "exclude_unset"', + DeprecationWarning, + ) + exclude_unset = skip_defaults + encoder = cast(Callable[[Any], Any], encoder or self.__json_encoder__) + + # We don't directly call `self.dict()`, which does exactly this with `to_dict=True` + # because we want to be able to keep raw `BaseModel` instances and not as `dict`. + # This allows users to write custom JSON encoders for given `BaseModel` classes. + data = dict( + self._iter( + to_dict=models_as_dict, + by_alias=by_alias, + include=include, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + ) + if self.__custom_root_type__: + data = data[ROOT_KEY] + return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs) + + @classmethod + def _enforce_dict_if_root(cls, obj: Any) -> Any: + if cls.__custom_root_type__ and ( + not (isinstance(obj, dict) and obj.keys() == {ROOT_KEY}) + and not (isinstance(obj, BaseModel) and obj.__fields__.keys() == {ROOT_KEY}) + or cls.__fields__[ROOT_KEY].shape in MAPPING_LIKE_SHAPES + ): + return {ROOT_KEY: obj} + else: + return obj + + @classmethod + def parse_obj(cls: Type['Model'], obj: Any) -> 'Model': + obj = cls._enforce_dict_if_root(obj) + if not isinstance(obj, dict): + try: + obj = dict(obj) + except (TypeError, ValueError) as e: + exc = TypeError(f'{cls.__name__} expected dict not {obj.__class__.__name__}') + raise ValidationError([ErrorWrapper(exc, loc=ROOT_KEY)], cls) from e + return cls(**obj) + + @classmethod + def parse_raw( + cls: Type['Model'], + b: StrBytes, + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + ) -> 'Model': + try: + obj = load_str_bytes( + b, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + json_loads=cls.__config__.json_loads, + ) + except (ValueError, TypeError, UnicodeDecodeError) as e: + raise ValidationError([ErrorWrapper(e, loc=ROOT_KEY)], cls) + return cls.parse_obj(obj) + + @classmethod + def parse_file( + cls: Type['Model'], + path: Union[str, Path], + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + ) -> 'Model': + obj = load_file( + path, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + json_loads=cls.__config__.json_loads, + ) + return cls.parse_obj(obj) + + @classmethod + def from_orm(cls: Type['Model'], obj: Any) -> 'Model': + if not cls.__config__.orm_mode: + raise ConfigError('You must have the config attribute orm_mode=True to use from_orm') + obj = {ROOT_KEY: obj} if cls.__custom_root_type__ else cls._decompose_class(obj) + m = cls.__new__(cls) + values, fields_set, validation_error = validate_model(cls, obj) + if validation_error: + raise validation_error + object_setattr(m, '__dict__', values) + object_setattr(m, '__fields_set__', fields_set) + m._init_private_attributes() + return m + + @classmethod + def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, **values: Any) -> 'Model': + """ + Creates a new model setting __dict__ and __fields_set__ from trusted or pre-validated data. + Default values are respected, but no other validation is performed. + Behaves as if `Config.extra = 'allow'` was set since it adds all passed values + """ + m = cls.__new__(cls) + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if field.alt_alias and field.alias in values: + fields_values[name] = values[field.alias] + elif name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() + fields_values.update(values) + object_setattr(m, '__dict__', fields_values) + if _fields_set is None: + _fields_set = set(values.keys()) + object_setattr(m, '__fields_set__', _fields_set) + m._init_private_attributes() + return m + + def _copy_and_set_values(self: 'Model', values: 'DictStrAny', fields_set: 'SetStr', *, deep: bool) -> 'Model': + if deep: + # chances of having empty dict here are quite low for using smart_deepcopy + values = deepcopy(values) + + cls = self.__class__ + m = cls.__new__(cls) + object_setattr(m, '__dict__', values) + object_setattr(m, '__fields_set__', fields_set) + for name in self.__private_attributes__: + value = getattr(self, name, Undefined) + if value is not Undefined: + if deep: + value = deepcopy(value) + object_setattr(m, name, value) + + return m + + def copy( + self: 'Model', + *, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + update: Optional['DictStrAny'] = None, + deep: bool = False, + ) -> 'Model': + """ + Duplicate a model, optionally choose which fields to include, exclude and change. + + :param include: fields to include in new model + :param exclude: fields to exclude from new model, as with values this takes precedence over include + :param update: values to change/add in the new model. Note: the data is not validated before creating + the new model: you should trust this data + :param deep: set to `True` to make a deep copy of the model + :return: new model instance + """ + + values = dict( + self._iter(to_dict=False, by_alias=False, include=include, exclude=exclude, exclude_unset=False), + **(update or {}), + ) + + # new `__fields_set__` can have unset optional fields with a set value in `update` kwarg + if update: + fields_set = self.__fields_set__ | update.keys() + else: + fields_set = set(self.__fields_set__) + + return self._copy_and_set_values(values, fields_set, deep=deep) + + @classmethod + def schema(cls, by_alias: bool = True, ref_template: str = default_ref_template) -> 'DictStrAny': + cached = cls.__schema_cache__.get((by_alias, ref_template)) + if cached is not None: + return cached + s = model_schema(cls, by_alias=by_alias, ref_template=ref_template) + cls.__schema_cache__[(by_alias, ref_template)] = s + return s + + @classmethod + def schema_json( + cls, *, by_alias: bool = True, ref_template: str = default_ref_template, **dumps_kwargs: Any + ) -> str: + from pydantic.v1.json import pydantic_encoder + + return cls.__config__.json_dumps( + cls.schema(by_alias=by_alias, ref_template=ref_template), default=pydantic_encoder, **dumps_kwargs + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls: Type['Model'], value: Any) -> 'Model': + if isinstance(value, cls): + copy_on_model_validation = cls.__config__.copy_on_model_validation + # whether to deep or shallow copy the model on validation, None means do not copy + deep_copy: Optional[bool] = None + if copy_on_model_validation not in {'deep', 'shallow', 'none'}: + # Warn about deprecated behavior + warnings.warn( + "`copy_on_model_validation` should be a string: 'deep', 'shallow' or 'none'", DeprecationWarning + ) + if copy_on_model_validation: + deep_copy = False + + if copy_on_model_validation == 'shallow': + # shallow copy + deep_copy = False + elif copy_on_model_validation == 'deep': + # deep copy + deep_copy = True + + if deep_copy is None: + return value + else: + return value._copy_and_set_values(value.__dict__, value.__fields_set__, deep=deep_copy) + + value = cls._enforce_dict_if_root(value) + + if isinstance(value, dict): + return cls(**value) + elif cls.__config__.orm_mode: + return cls.from_orm(value) + else: + try: + value_as_dict = dict(value) + except (TypeError, ValueError) as e: + raise DictError() from e + return cls(**value_as_dict) + + @classmethod + def _decompose_class(cls: Type['Model'], obj: Any) -> GetterDict: + if isinstance(obj, GetterDict): + return obj + return cls.__config__.getter_dict(obj) + + @classmethod + @no_type_check + def _get_value( + cls, + v: Any, + to_dict: bool, + by_alias: bool, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']], + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']], + exclude_unset: bool, + exclude_defaults: bool, + exclude_none: bool, + ) -> Any: + if isinstance(v, BaseModel): + if to_dict: + v_dict = v.dict( + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=include, + exclude=exclude, + exclude_none=exclude_none, + ) + if ROOT_KEY in v_dict: + return v_dict[ROOT_KEY] + return v_dict + else: + return v.copy(include=include, exclude=exclude) + + value_exclude = ValueItems(v, exclude) if exclude else None + value_include = ValueItems(v, include) if include else None + + if isinstance(v, dict): + return { + k_: cls._get_value( + v_, + to_dict=to_dict, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=value_include and value_include.for_element(k_), + exclude=value_exclude and value_exclude.for_element(k_), + exclude_none=exclude_none, + ) + for k_, v_ in v.items() + if (not value_exclude or not value_exclude.is_excluded(k_)) + and (not value_include or value_include.is_included(k_)) + } + + elif sequence_like(v): + seq_args = ( + cls._get_value( + v_, + to_dict=to_dict, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + include=value_include and value_include.for_element(i), + exclude=value_exclude and value_exclude.for_element(i), + exclude_none=exclude_none, + ) + for i, v_ in enumerate(v) + if (not value_exclude or not value_exclude.is_excluded(i)) + and (not value_include or value_include.is_included(i)) + ) + + return v.__class__(*seq_args) if is_namedtuple(v.__class__) else v.__class__(seq_args) + + elif isinstance(v, Enum) and getattr(cls.Config, 'use_enum_values', False): + return v.value + + else: + return v + + @classmethod + def __try_update_forward_refs__(cls, **localns: Any) -> None: + """ + Same as update_forward_refs but will not raise exception + when forward references are not defined. + """ + update_model_forward_refs(cls, cls.__fields__.values(), cls.__config__.json_encoders, localns, (NameError,)) + + @classmethod + def update_forward_refs(cls, **localns: Any) -> None: + """ + Try to update ForwardRefs on fields based on this Model, globalns and localns. + """ + update_model_forward_refs(cls, cls.__fields__.values(), cls.__config__.json_encoders, localns) + + def __iter__(self) -> 'TupleGenerator': + """ + so `dict(model)` works + """ + yield from self.__dict__.items() + + def _iter( + self, + to_dict: bool = False, + by_alias: bool = False, + include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> 'TupleGenerator': + # Merge field set excludes with explicit exclude parameter with explicit overriding field set options. + # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case. + if exclude is not None or self.__exclude_fields__ is not None: + exclude = ValueItems.merge(self.__exclude_fields__, exclude) + + if include is not None or self.__include_fields__ is not None: + include = ValueItems.merge(self.__include_fields__, include, intersect=True) + + allowed_keys = self._calculate_keys( + include=include, exclude=exclude, exclude_unset=exclude_unset # type: ignore + ) + if allowed_keys is None and not (to_dict or by_alias or exclude_unset or exclude_defaults or exclude_none): + # huge boost for plain _iter() + yield from self.__dict__.items() + return + + value_exclude = ValueItems(self, exclude) if exclude is not None else None + value_include = ValueItems(self, include) if include is not None else None + + for field_key, v in self.__dict__.items(): + if (allowed_keys is not None and field_key not in allowed_keys) or (exclude_none and v is None): + continue + + if exclude_defaults: + model_field = self.__fields__.get(field_key) + if not getattr(model_field, 'required', True) and getattr(model_field, 'default', _missing) == v: + continue + + if by_alias and field_key in self.__fields__: + dict_key = self.__fields__[field_key].alias + else: + dict_key = field_key + + if to_dict or value_include or value_exclude: + v = self._get_value( + v, + to_dict=to_dict, + by_alias=by_alias, + include=value_include and value_include.for_element(field_key), + exclude=value_exclude and value_exclude.for_element(field_key), + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + yield dict_key, v + + def _calculate_keys( + self, + include: Optional['MappingIntStrAny'], + exclude: Optional['MappingIntStrAny'], + exclude_unset: bool, + update: Optional['DictStrAny'] = None, + ) -> Optional[AbstractSet[str]]: + if include is None and exclude is None and exclude_unset is False: + return None + + keys: AbstractSet[str] + if exclude_unset: + keys = self.__fields_set__.copy() + else: + keys = self.__dict__.keys() + + if include is not None: + keys &= include.keys() + + if update: + keys -= update.keys() + + if exclude: + keys -= {k for k, v in exclude.items() if ValueItems.is_true(v)} + + return keys + + def __eq__(self, other: Any) -> bool: + if isinstance(other, BaseModel): + return self.dict() == other.dict() + else: + return self.dict() == other + + def __repr_args__(self) -> 'ReprArgs': + return [ + (k, v) + for k, v in self.__dict__.items() + if k not in DUNDER_ATTRIBUTES and (k not in self.__fields__ or self.__fields__[k].field_info.repr) + ] + + +_is_base_model_class_defined = True + + +@overload +def create_model( + __model_name: str, + *, + __config__: Optional[Type[BaseConfig]] = None, + __base__: None = None, + __module__: str = __name__, + __validators__: Dict[str, 'AnyClassMethod'] = None, + __cls_kwargs__: Dict[str, Any] = None, + **field_definitions: Any, +) -> Type['BaseModel']: + ... + + +@overload +def create_model( + __model_name: str, + *, + __config__: Optional[Type[BaseConfig]] = None, + __base__: Union[Type['Model'], Tuple[Type['Model'], ...]], + __module__: str = __name__, + __validators__: Dict[str, 'AnyClassMethod'] = None, + __cls_kwargs__: Dict[str, Any] = None, + **field_definitions: Any, +) -> Type['Model']: + ... + + +def create_model( + __model_name: str, + *, + __config__: Optional[Type[BaseConfig]] = None, + __base__: Union[None, Type['Model'], Tuple[Type['Model'], ...]] = None, + __module__: str = __name__, + __validators__: Dict[str, 'AnyClassMethod'] = None, + __cls_kwargs__: Dict[str, Any] = None, + __slots__: Optional[Tuple[str, ...]] = None, + **field_definitions: Any, +) -> Type['Model']: + """ + Dynamically create a model. + :param __model_name: name of the created model + :param __config__: config class to use for the new model + :param __base__: base class for the new model to inherit from + :param __module__: module of the created model + :param __validators__: a dict of method names and @validator class methods + :param __cls_kwargs__: a dict for class creation + :param __slots__: Deprecated, `__slots__` should not be passed to `create_model` + :param field_definitions: fields of the model (or extra fields if a base is supplied) + in the format `=(, )` or `=, e.g. + `foobar=(str, ...)` or `foobar=123`, or, for complex use-cases, in the format + `=` or `=(, )`, e.g. + `foo=Field(datetime, default_factory=datetime.utcnow, alias='bar')` or + `foo=(str, FieldInfo(title='Foo'))` + """ + if __slots__ is not None: + # __slots__ will be ignored from here on + warnings.warn('__slots__ should not be passed to create_model', RuntimeWarning) + + if __base__ is not None: + if __config__ is not None: + raise ConfigError('to avoid confusion __config__ and __base__ cannot be used together') + if not isinstance(__base__, tuple): + __base__ = (__base__,) + else: + __base__ = (cast(Type['Model'], BaseModel),) + + __cls_kwargs__ = __cls_kwargs__ or {} + + fields = {} + annotations = {} + + for f_name, f_def in field_definitions.items(): + if not is_valid_field(f_name): + warnings.warn(f'fields may not start with an underscore, ignoring "{f_name}"', RuntimeWarning) + if isinstance(f_def, tuple): + try: + f_annotation, f_value = f_def + except ValueError as e: + raise ConfigError( + 'field definitions should either be a tuple of (, ) or just a ' + 'default value, unfortunately this means tuples as ' + 'default values are not allowed' + ) from e + else: + f_annotation, f_value = None, f_def + + if f_annotation: + annotations[f_name] = f_annotation + fields[f_name] = f_value + + namespace: 'DictStrAny' = {'__annotations__': annotations, '__module__': __module__} + if __validators__: + namespace.update(__validators__) + namespace.update(fields) + if __config__: + namespace['Config'] = inherit_config(__config__, BaseConfig) + resolved_bases = resolve_bases(__base__) + meta, ns, kwds = prepare_class(__model_name, resolved_bases, kwds=__cls_kwargs__) + if resolved_bases is not __base__: + ns['__orig_bases__'] = __base__ + namespace.update(ns) + return meta(__model_name, resolved_bases, namespace, **kwds) + + +_missing = object() + + +def validate_model( # noqa: C901 (ignore complexity) + model: Type[BaseModel], input_data: 'DictStrAny', cls: 'ModelOrDc' = None +) -> Tuple['DictStrAny', 'SetStr', Optional[ValidationError]]: + """ + validate data against a model. + """ + values = {} + errors = [] + # input_data names, possibly alias + names_used = set() + # field names, never aliases + fields_set = set() + config = model.__config__ + check_extra = config.extra is not Extra.ignore + cls_ = cls or model + + for validator in model.__pre_root_validators__: + try: + input_data = validator(cls_, input_data) + except (ValueError, TypeError, AssertionError) as exc: + return {}, set(), ValidationError([ErrorWrapper(exc, loc=ROOT_KEY)], cls_) + + for name, field in model.__fields__.items(): + value = input_data.get(field.alias, _missing) + using_name = False + if value is _missing and config.allow_population_by_field_name and field.alt_alias: + value = input_data.get(field.name, _missing) + using_name = True + + if value is _missing: + if field.required: + errors.append(ErrorWrapper(MissingError(), loc=field.alias)) + continue + + value = field.get_default() + + if not config.validate_all and not field.validate_always: + values[name] = value + continue + else: + fields_set.add(name) + if check_extra: + names_used.add(field.name if using_name else field.alias) + + v_, errors_ = field.validate(value, values, loc=field.alias, cls=cls_) + if isinstance(errors_, ErrorWrapper): + errors.append(errors_) + elif isinstance(errors_, list): + errors.extend(errors_) + else: + values[name] = v_ + + if check_extra: + if isinstance(input_data, GetterDict): + extra = input_data.extra_keys() - names_used + else: + extra = input_data.keys() - names_used + if extra: + fields_set |= extra + if config.extra is Extra.allow: + for f in extra: + values[f] = input_data[f] + else: + for f in sorted(extra): + errors.append(ErrorWrapper(ExtraError(), loc=f)) + + for skip_on_failure, validator in model.__post_root_validators__: + if skip_on_failure and errors: + continue + try: + values = validator(cls_, values) + except (ValueError, TypeError, AssertionError) as exc: + errors.append(ErrorWrapper(exc, loc=ROOT_KEY)) + + if errors: + return values, fields_set, ValidationError(errors, cls_) + else: + return values, fields_set, None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py new file mode 100644 index 0000000..0a77569 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py @@ -0,0 +1,949 @@ +import sys +from configparser import ConfigParser +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType, Union + +from mypy.errorcodes import ErrorCode +from mypy.nodes import ( + ARG_NAMED, + ARG_NAMED_OPT, + ARG_OPT, + ARG_POS, + ARG_STAR2, + MDEF, + Argument, + AssignmentStmt, + Block, + CallExpr, + ClassDef, + Context, + Decorator, + EllipsisExpr, + FuncBase, + FuncDef, + JsonDict, + MemberExpr, + NameExpr, + PassStmt, + PlaceholderNode, + RefExpr, + StrExpr, + SymbolNode, + SymbolTableNode, + TempNode, + TypeInfo, + TypeVarExpr, + Var, +) +from mypy.options import Options +from mypy.plugin import ( + CheckerPluginInterface, + ClassDefContext, + FunctionContext, + MethodContext, + Plugin, + ReportConfigContext, + SemanticAnalyzerPluginInterface, +) +from mypy.plugins import dataclasses +from mypy.semanal import set_callable_name # type: ignore +from mypy.server.trigger import make_wildcard_trigger +from mypy.types import ( + AnyType, + CallableType, + Instance, + NoneType, + Overloaded, + ProperType, + Type, + TypeOfAny, + TypeType, + TypeVarId, + TypeVarType, + UnionType, + get_proper_type, +) +from mypy.typevars import fill_typevars +from mypy.util import get_unique_redefinition_name +from mypy.version import __version__ as mypy_version + +from pydantic.v1.utils import is_valid_field + +try: + from mypy.types import TypeVarDef # type: ignore[attr-defined] +except ImportError: # pragma: no cover + # Backward-compatible with TypeVarDef from Mypy 0.910. + from mypy.types import TypeVarType as TypeVarDef + +CONFIGFILE_KEY = 'pydantic-mypy' +METADATA_KEY = 'pydantic-mypy-metadata' +_NAMESPACE = __name__[:-5] # 'pydantic' in 1.10.X, 'pydantic.v1' in v2.X +BASEMODEL_FULLNAME = f'{_NAMESPACE}.main.BaseModel' +BASESETTINGS_FULLNAME = f'{_NAMESPACE}.env_settings.BaseSettings' +MODEL_METACLASS_FULLNAME = f'{_NAMESPACE}.main.ModelMetaclass' +FIELD_FULLNAME = f'{_NAMESPACE}.fields.Field' +DATACLASS_FULLNAME = f'{_NAMESPACE}.dataclasses.dataclass' + + +def parse_mypy_version(version: str) -> Tuple[int, ...]: + return tuple(map(int, version.partition('+')[0].split('.'))) + + +MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version) +BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0, 930) else '__builtins__' + +# Increment version if plugin changes and mypy caches should be invalidated +__version__ = 2 + + +def plugin(version: str) -> 'TypingType[Plugin]': + """ + `version` is the mypy version string + + We might want to use this to print a warning if the mypy version being used is + newer, or especially older, than we expect (or need). + """ + return PydanticPlugin + + +class PydanticPlugin(Plugin): + def __init__(self, options: Options) -> None: + self.plugin_config = PydanticPluginConfig(options) + self._plugin_data = self.plugin_config.to_data() + super().__init__(options) + + def get_base_class_hook(self, fullname: str) -> 'Optional[Callable[[ClassDefContext], None]]': + sym = self.lookup_fully_qualified(fullname) + if sym and isinstance(sym.node, TypeInfo): # pragma: no branch + # No branching may occur if the mypy cache has not been cleared + if any(get_fullname(base) == BASEMODEL_FULLNAME for base in sym.node.mro): + return self._pydantic_model_class_maker_callback + return None + + def get_metaclass_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: + if fullname == MODEL_METACLASS_FULLNAME: + return self._pydantic_model_metaclass_marker_callback + return None + + def get_function_hook(self, fullname: str) -> 'Optional[Callable[[FunctionContext], Type]]': + sym = self.lookup_fully_qualified(fullname) + if sym and sym.fullname == FIELD_FULLNAME: + return self._pydantic_field_callback + return None + + def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]: + if fullname.endswith('.from_orm'): + return from_orm_callback + return None + + def get_class_decorator_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: + """Mark pydantic.dataclasses as dataclass. + + Mypy version 1.1.1 added support for `@dataclass_transform` decorator. + """ + if fullname == DATACLASS_FULLNAME and MYPY_VERSION_TUPLE < (1, 1): + return dataclasses.dataclass_class_maker_callback # type: ignore[return-value] + return None + + def report_config_data(self, ctx: ReportConfigContext) -> Dict[str, Any]: + """Return all plugin config data. + + Used by mypy to determine if cache needs to be discarded. + """ + return self._plugin_data + + def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None: + transformer = PydanticModelTransformer(ctx, self.plugin_config) + transformer.transform() + + def _pydantic_model_metaclass_marker_callback(self, ctx: ClassDefContext) -> None: + """Reset dataclass_transform_spec attribute of ModelMetaclass. + + Let the plugin handle it. This behavior can be disabled + if 'debug_dataclass_transform' is set to True', for testing purposes. + """ + if self.plugin_config.debug_dataclass_transform: + return + info_metaclass = ctx.cls.info.declared_metaclass + assert info_metaclass, "callback not passed from 'get_metaclass_hook'" + if getattr(info_metaclass.type, 'dataclass_transform_spec', None): + info_metaclass.type.dataclass_transform_spec = None # type: ignore[attr-defined] + + def _pydantic_field_callback(self, ctx: FunctionContext) -> 'Type': + """ + Extract the type of the `default` argument from the Field function, and use it as the return type. + + In particular: + * Check whether the default and default_factory argument is specified. + * Output an error if both are specified. + * Retrieve the type of the argument which is specified, and use it as return type for the function. + """ + default_any_type = ctx.default_return_type + + assert ctx.callee_arg_names[0] == 'default', '"default" is no longer first argument in Field()' + assert ctx.callee_arg_names[1] == 'default_factory', '"default_factory" is no longer second argument in Field()' + default_args = ctx.args[0] + default_factory_args = ctx.args[1] + + if default_args and default_factory_args: + error_default_and_default_factory_specified(ctx.api, ctx.context) + return default_any_type + + if default_args: + default_type = ctx.arg_types[0][0] + default_arg = default_args[0] + + # Fallback to default Any type if the field is required + if not isinstance(default_arg, EllipsisExpr): + return default_type + + elif default_factory_args: + default_factory_type = ctx.arg_types[1][0] + + # Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter + # Pydantic calls the default factory without any argument, so we retrieve the first item + if isinstance(default_factory_type, Overloaded): + if MYPY_VERSION_TUPLE > (0, 910): + default_factory_type = default_factory_type.items[0] + else: + # Mypy0.910 exposes the items of overloaded types in a function + default_factory_type = default_factory_type.items()[0] # type: ignore[operator] + + if isinstance(default_factory_type, CallableType): + ret_type = get_proper_type(default_factory_type.ret_type) + if ( + isinstance(ret_type, Instance) + and ret_type.args + and all(isinstance(arg, TypeVarType) for arg in ret_type.args) + ): + # Looks like the default factory is a type like `list` or `dict`, replace all args with `Any` + ret_type = ret_type.copy_modified(args=[default_any_type] * len(ret_type.args)) + return ret_type + + return default_any_type + + +class PydanticPluginConfig: + __slots__ = ( + 'init_forbid_extra', + 'init_typed', + 'warn_required_dynamic_aliases', + 'warn_untyped_fields', + 'debug_dataclass_transform', + ) + init_forbid_extra: bool + init_typed: bool + warn_required_dynamic_aliases: bool + warn_untyped_fields: bool + debug_dataclass_transform: bool # undocumented + + def __init__(self, options: Options) -> None: + if options.config_file is None: # pragma: no cover + return + + toml_config = parse_toml(options.config_file) + if toml_config is not None: + config = toml_config.get('tool', {}).get('pydantic-mypy', {}) + for key in self.__slots__: + setting = config.get(key, False) + if not isinstance(setting, bool): + raise ValueError(f'Configuration value must be a boolean for key: {key}') + setattr(self, key, setting) + else: + plugin_config = ConfigParser() + plugin_config.read(options.config_file) + for key in self.__slots__: + setting = plugin_config.getboolean(CONFIGFILE_KEY, key, fallback=False) + setattr(self, key, setting) + + def to_data(self) -> Dict[str, Any]: + return {key: getattr(self, key) for key in self.__slots__} + + +def from_orm_callback(ctx: MethodContext) -> Type: + """ + Raise an error if orm_mode is not enabled + """ + model_type: Instance + ctx_type = ctx.type + if isinstance(ctx_type, TypeType): + ctx_type = ctx_type.item + if isinstance(ctx_type, CallableType) and isinstance(ctx_type.ret_type, Instance): + model_type = ctx_type.ret_type # called on the class + elif isinstance(ctx_type, Instance): + model_type = ctx_type # called on an instance (unusual, but still valid) + else: # pragma: no cover + detail = f'ctx.type: {ctx_type} (of type {ctx_type.__class__.__name__})' + error_unexpected_behavior(detail, ctx.api, ctx.context) + return ctx.default_return_type + pydantic_metadata = model_type.type.metadata.get(METADATA_KEY) + if pydantic_metadata is None: + return ctx.default_return_type + orm_mode = pydantic_metadata.get('config', {}).get('orm_mode') + if orm_mode is not True: + error_from_orm(get_name(model_type.type), ctx.api, ctx.context) + return ctx.default_return_type + + +class PydanticModelTransformer: + tracked_config_fields: Set[str] = { + 'extra', + 'allow_mutation', + 'frozen', + 'orm_mode', + 'allow_population_by_field_name', + 'alias_generator', + } + + def __init__(self, ctx: ClassDefContext, plugin_config: PydanticPluginConfig) -> None: + self._ctx = ctx + self.plugin_config = plugin_config + + def transform(self) -> None: + """ + Configures the BaseModel subclass according to the plugin settings. + + In particular: + * determines the model config and fields, + * adds a fields-aware signature for the initializer and construct methods + * freezes the class if allow_mutation = False or frozen = True + * stores the fields, config, and if the class is settings in the mypy metadata for access by subclasses + """ + ctx = self._ctx + info = ctx.cls.info + + self.adjust_validator_signatures() + config = self.collect_config() + fields = self.collect_fields(config) + is_settings = any(get_fullname(base) == BASESETTINGS_FULLNAME for base in info.mro[:-1]) + self.add_initializer(fields, config, is_settings) + self.add_construct_method(fields) + self.set_frozen(fields, frozen=config.allow_mutation is False or config.frozen is True) + info.metadata[METADATA_KEY] = { + 'fields': {field.name: field.serialize() for field in fields}, + 'config': config.set_values_dict(), + } + + def adjust_validator_signatures(self) -> None: + """When we decorate a function `f` with `pydantic.validator(...), mypy sees + `f` as a regular method taking a `self` instance, even though pydantic + internally wraps `f` with `classmethod` if necessary. + + Teach mypy this by marking any function whose outermost decorator is a + `validator()` call as a classmethod. + """ + for name, sym in self._ctx.cls.info.names.items(): + if isinstance(sym.node, Decorator): + first_dec = sym.node.original_decorators[0] + if ( + isinstance(first_dec, CallExpr) + and isinstance(first_dec.callee, NameExpr) + and first_dec.callee.fullname == f'{_NAMESPACE}.class_validators.validator' + ): + sym.node.func.is_class = True + + def collect_config(self) -> 'ModelConfigData': + """ + Collects the values of the config attributes that are used by the plugin, accounting for parent classes. + """ + ctx = self._ctx + cls = ctx.cls + config = ModelConfigData() + for stmt in cls.defs.body: + if not isinstance(stmt, ClassDef): + continue + if stmt.name == 'Config': + for substmt in stmt.defs.body: + if not isinstance(substmt, AssignmentStmt): + continue + config.update(self.get_config_update(substmt)) + if ( + config.has_alias_generator + and not config.allow_population_by_field_name + and self.plugin_config.warn_required_dynamic_aliases + ): + error_required_dynamic_aliases(ctx.api, stmt) + for info in cls.info.mro[1:]: # 0 is the current class + if METADATA_KEY not in info.metadata: + continue + + # Each class depends on the set of fields in its ancestors + ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) + for name, value in info.metadata[METADATA_KEY]['config'].items(): + config.setdefault(name, value) + return config + + def collect_fields(self, model_config: 'ModelConfigData') -> List['PydanticModelField']: + """ + Collects the fields for the model, accounting for parent classes + """ + # First, collect fields belonging to the current class. + ctx = self._ctx + cls = self._ctx.cls + fields = [] # type: List[PydanticModelField] + known_fields = set() # type: Set[str] + for stmt in cls.defs.body: + if not isinstance(stmt, AssignmentStmt): # `and stmt.new_syntax` to require annotation + continue + + lhs = stmt.lvalues[0] + if not isinstance(lhs, NameExpr) or not is_valid_field(lhs.name): + continue + + if not stmt.new_syntax and self.plugin_config.warn_untyped_fields: + error_untyped_fields(ctx.api, stmt) + + # if lhs.name == '__config__': # BaseConfig not well handled; I'm not sure why yet + # continue + + sym = cls.info.names.get(lhs.name) + if sym is None: # pragma: no cover + # This is likely due to a star import (see the dataclasses plugin for a more detailed explanation) + # This is the same logic used in the dataclasses plugin + continue + + node = sym.node + if isinstance(node, PlaceholderNode): # pragma: no cover + # See the PlaceholderNode docstring for more detail about how this can occur + # Basically, it is an edge case when dealing with complex import logic + # This is the same logic used in the dataclasses plugin + continue + if not isinstance(node, Var): # pragma: no cover + # Don't know if this edge case still happens with the `is_valid_field` check above + # but better safe than sorry + continue + + # x: ClassVar[int] is ignored by dataclasses. + if node.is_classvar: + continue + + is_required = self.get_is_required(cls, stmt, lhs) + alias, has_dynamic_alias = self.get_alias_info(stmt) + if ( + has_dynamic_alias + and not model_config.allow_population_by_field_name + and self.plugin_config.warn_required_dynamic_aliases + ): + error_required_dynamic_aliases(ctx.api, stmt) + fields.append( + PydanticModelField( + name=lhs.name, + is_required=is_required, + alias=alias, + has_dynamic_alias=has_dynamic_alias, + line=stmt.line, + column=stmt.column, + ) + ) + known_fields.add(lhs.name) + all_fields = fields.copy() + for info in cls.info.mro[1:]: # 0 is the current class, -2 is BaseModel, -1 is object + if METADATA_KEY not in info.metadata: + continue + + superclass_fields = [] + # Each class depends on the set of fields in its ancestors + ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) + + for name, data in info.metadata[METADATA_KEY]['fields'].items(): + if name not in known_fields: + field = PydanticModelField.deserialize(info, data) + known_fields.add(name) + superclass_fields.append(field) + else: + (field,) = (a for a in all_fields if a.name == name) + all_fields.remove(field) + superclass_fields.append(field) + all_fields = superclass_fields + all_fields + return all_fields + + def add_initializer(self, fields: List['PydanticModelField'], config: 'ModelConfigData', is_settings: bool) -> None: + """ + Adds a fields-aware `__init__` method to the class. + + The added `__init__` will be annotated with types vs. all `Any` depending on the plugin settings. + """ + ctx = self._ctx + typed = self.plugin_config.init_typed + use_alias = config.allow_population_by_field_name is not True + force_all_optional = is_settings or bool( + config.has_alias_generator and not config.allow_population_by_field_name + ) + init_arguments = self.get_field_arguments( + fields, typed=typed, force_all_optional=force_all_optional, use_alias=use_alias + ) + if not self.should_init_forbid_extra(fields, config): + var = Var('kwargs') + init_arguments.append(Argument(var, AnyType(TypeOfAny.explicit), None, ARG_STAR2)) + + if '__init__' not in ctx.cls.info.names: + add_method(ctx, '__init__', init_arguments, NoneType()) + + def add_construct_method(self, fields: List['PydanticModelField']) -> None: + """ + Adds a fully typed `construct` classmethod to the class. + + Similar to the fields-aware __init__ method, but always uses the field names (not aliases), + and does not treat settings fields as optional. + """ + ctx = self._ctx + set_str = ctx.api.named_type(f'{BUILTINS_NAME}.set', [ctx.api.named_type(f'{BUILTINS_NAME}.str')]) + optional_set_str = UnionType([set_str, NoneType()]) + fields_set_argument = Argument(Var('_fields_set', optional_set_str), optional_set_str, None, ARG_OPT) + construct_arguments = self.get_field_arguments(fields, typed=True, force_all_optional=False, use_alias=False) + construct_arguments = [fields_set_argument] + construct_arguments + + obj_type = ctx.api.named_type(f'{BUILTINS_NAME}.object') + self_tvar_name = '_PydanticBaseModel' # Make sure it does not conflict with other names in the class + tvar_fullname = ctx.cls.fullname + '.' + self_tvar_name + if MYPY_VERSION_TUPLE >= (1, 4): + tvd = TypeVarType( + self_tvar_name, + tvar_fullname, + ( + TypeVarId(-1, namespace=ctx.cls.fullname + '.construct') + if MYPY_VERSION_TUPLE >= (1, 11) + else TypeVarId(-1) + ), + [], + obj_type, + AnyType(TypeOfAny.from_omitted_generics), # type: ignore[arg-type] + ) + self_tvar_expr = TypeVarExpr( + self_tvar_name, + tvar_fullname, + [], + obj_type, + AnyType(TypeOfAny.from_omitted_generics), # type: ignore[arg-type] + ) + else: + tvd = TypeVarDef(self_tvar_name, tvar_fullname, -1, [], obj_type) + self_tvar_expr = TypeVarExpr(self_tvar_name, tvar_fullname, [], obj_type) + ctx.cls.info.names[self_tvar_name] = SymbolTableNode(MDEF, self_tvar_expr) + + # Backward-compatible with TypeVarDef from Mypy 0.910. + if isinstance(tvd, TypeVarType): + self_type = tvd + else: + self_type = TypeVarType(tvd) + + add_method( + ctx, + 'construct', + construct_arguments, + return_type=self_type, + self_type=self_type, + tvar_def=tvd, + is_classmethod=True, + ) + + def set_frozen(self, fields: List['PydanticModelField'], frozen: bool) -> None: + """ + Marks all fields as properties so that attempts to set them trigger mypy errors. + + This is the same approach used by the attrs and dataclasses plugins. + """ + ctx = self._ctx + info = ctx.cls.info + for field in fields: + sym_node = info.names.get(field.name) + if sym_node is not None: + var = sym_node.node + if isinstance(var, Var): + var.is_property = frozen + elif isinstance(var, PlaceholderNode) and not ctx.api.final_iteration: + # See https://github.com/pydantic/pydantic/issues/5191 to hit this branch for test coverage + ctx.api.defer() + else: # pragma: no cover + # I don't know whether it's possible to hit this branch, but I've added it for safety + try: + var_str = str(var) + except TypeError: + # This happens for PlaceholderNode; perhaps it will happen for other types in the future.. + var_str = repr(var) + detail = f'sym_node.node: {var_str} (of type {var.__class__})' + error_unexpected_behavior(detail, ctx.api, ctx.cls) + else: + var = field.to_var(info, use_alias=False) + var.info = info + var.is_property = frozen + var._fullname = get_fullname(info) + '.' + get_name(var) + info.names[get_name(var)] = SymbolTableNode(MDEF, var) + + def get_config_update(self, substmt: AssignmentStmt) -> Optional['ModelConfigData']: + """ + Determines the config update due to a single statement in the Config class definition. + + Warns if a tracked config attribute is set to a value the plugin doesn't know how to interpret (e.g., an int) + """ + lhs = substmt.lvalues[0] + if not (isinstance(lhs, NameExpr) and lhs.name in self.tracked_config_fields): + return None + if lhs.name == 'extra': + if isinstance(substmt.rvalue, StrExpr): + forbid_extra = substmt.rvalue.value == 'forbid' + elif isinstance(substmt.rvalue, MemberExpr): + forbid_extra = substmt.rvalue.name == 'forbid' + else: + error_invalid_config_value(lhs.name, self._ctx.api, substmt) + return None + return ModelConfigData(forbid_extra=forbid_extra) + if lhs.name == 'alias_generator': + has_alias_generator = True + if isinstance(substmt.rvalue, NameExpr) and substmt.rvalue.fullname == 'builtins.None': + has_alias_generator = False + return ModelConfigData(has_alias_generator=has_alias_generator) + if isinstance(substmt.rvalue, NameExpr) and substmt.rvalue.fullname in ('builtins.True', 'builtins.False'): + return ModelConfigData(**{lhs.name: substmt.rvalue.fullname == 'builtins.True'}) + error_invalid_config_value(lhs.name, self._ctx.api, substmt) + return None + + @staticmethod + def get_is_required(cls: ClassDef, stmt: AssignmentStmt, lhs: NameExpr) -> bool: + """ + Returns a boolean indicating whether the field defined in `stmt` is a required field. + """ + expr = stmt.rvalue + if isinstance(expr, TempNode): + # TempNode means annotation-only, so only non-required if Optional + value_type = get_proper_type(cls.info[lhs.name].type) + return not PydanticModelTransformer.type_has_implicit_default(value_type) + if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME: + # The "default value" is a call to `Field`; at this point, the field is + # only required if default is Ellipsis (i.e., `field_name: Annotation = Field(...)`) or if default_factory + # is specified. + for arg, name in zip(expr.args, expr.arg_names): + # If name is None, then this arg is the default because it is the only positional argument. + if name is None or name == 'default': + return arg.__class__ is EllipsisExpr + if name == 'default_factory': + return False + # In this case, default and default_factory are not specified, so we need to look at the annotation + value_type = get_proper_type(cls.info[lhs.name].type) + return not PydanticModelTransformer.type_has_implicit_default(value_type) + # Only required if the "default value" is Ellipsis (i.e., `field_name: Annotation = ...`) + return isinstance(expr, EllipsisExpr) + + @staticmethod + def type_has_implicit_default(type_: Optional[ProperType]) -> bool: + """ + Returns True if the passed type will be given an implicit default value. + + In pydantic v1, this is the case for Optional types and Any (with default value None). + """ + if isinstance(type_, AnyType): + # Annotated as Any + return True + if isinstance(type_, UnionType) and any( + isinstance(item, NoneType) or isinstance(item, AnyType) for item in type_.items + ): + # Annotated as Optional, or otherwise having NoneType or AnyType in the union + return True + return False + + @staticmethod + def get_alias_info(stmt: AssignmentStmt) -> Tuple[Optional[str], bool]: + """ + Returns a pair (alias, has_dynamic_alias), extracted from the declaration of the field defined in `stmt`. + + `has_dynamic_alias` is True if and only if an alias is provided, but not as a string literal. + If `has_dynamic_alias` is True, `alias` will be None. + """ + expr = stmt.rvalue + if isinstance(expr, TempNode): + # TempNode means annotation-only + return None, False + + if not ( + isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME + ): + # Assigned value is not a call to pydantic.fields.Field + return None, False + + for i, arg_name in enumerate(expr.arg_names): + if arg_name != 'alias': + continue + arg = expr.args[i] + if isinstance(arg, StrExpr): + return arg.value, False + else: + return None, True + return None, False + + def get_field_arguments( + self, fields: List['PydanticModelField'], typed: bool, force_all_optional: bool, use_alias: bool + ) -> List[Argument]: + """ + Helper function used during the construction of the `__init__` and `construct` method signatures. + + Returns a list of mypy Argument instances for use in the generated signatures. + """ + info = self._ctx.cls.info + arguments = [ + field.to_argument(info, typed=typed, force_optional=force_all_optional, use_alias=use_alias) + for field in fields + if not (use_alias and field.has_dynamic_alias) + ] + return arguments + + def should_init_forbid_extra(self, fields: List['PydanticModelField'], config: 'ModelConfigData') -> bool: + """ + Indicates whether the generated `__init__` should get a `**kwargs` at the end of its signature + + We disallow arbitrary kwargs if the extra config setting is "forbid", or if the plugin config says to, + *unless* a required dynamic alias is present (since then we can't determine a valid signature). + """ + if not config.allow_population_by_field_name: + if self.is_dynamic_alias_present(fields, bool(config.has_alias_generator)): + return False + if config.forbid_extra: + return True + return self.plugin_config.init_forbid_extra + + @staticmethod + def is_dynamic_alias_present(fields: List['PydanticModelField'], has_alias_generator: bool) -> bool: + """ + Returns whether any fields on the model have a "dynamic alias", i.e., an alias that cannot be + determined during static analysis. + """ + for field in fields: + if field.has_dynamic_alias: + return True + if has_alias_generator: + for field in fields: + if field.alias is None: + return True + return False + + +class PydanticModelField: + def __init__( + self, name: str, is_required: bool, alias: Optional[str], has_dynamic_alias: bool, line: int, column: int + ): + self.name = name + self.is_required = is_required + self.alias = alias + self.has_dynamic_alias = has_dynamic_alias + self.line = line + self.column = column + + def to_var(self, info: TypeInfo, use_alias: bool) -> Var: + name = self.name + if use_alias and self.alias is not None: + name = self.alias + return Var(name, info[self.name].type) + + def to_argument(self, info: TypeInfo, typed: bool, force_optional: bool, use_alias: bool) -> Argument: + if typed and info[self.name].type is not None: + type_annotation = info[self.name].type + else: + type_annotation = AnyType(TypeOfAny.explicit) + return Argument( + variable=self.to_var(info, use_alias), + type_annotation=type_annotation, + initializer=None, + kind=ARG_NAMED_OPT if force_optional or not self.is_required else ARG_NAMED, + ) + + def serialize(self) -> JsonDict: + return self.__dict__ + + @classmethod + def deserialize(cls, info: TypeInfo, data: JsonDict) -> 'PydanticModelField': + return cls(**data) + + +class ModelConfigData: + def __init__( + self, + forbid_extra: Optional[bool] = None, + allow_mutation: Optional[bool] = None, + frozen: Optional[bool] = None, + orm_mode: Optional[bool] = None, + allow_population_by_field_name: Optional[bool] = None, + has_alias_generator: Optional[bool] = None, + ): + self.forbid_extra = forbid_extra + self.allow_mutation = allow_mutation + self.frozen = frozen + self.orm_mode = orm_mode + self.allow_population_by_field_name = allow_population_by_field_name + self.has_alias_generator = has_alias_generator + + def set_values_dict(self) -> Dict[str, Any]: + return {k: v for k, v in self.__dict__.items() if v is not None} + + def update(self, config: Optional['ModelConfigData']) -> None: + if config is None: + return + for k, v in config.set_values_dict().items(): + setattr(self, k, v) + + def setdefault(self, key: str, value: Any) -> None: + if getattr(self, key) is None: + setattr(self, key, value) + + +ERROR_ORM = ErrorCode('pydantic-orm', 'Invalid from_orm call', 'Pydantic') +ERROR_CONFIG = ErrorCode('pydantic-config', 'Invalid config value', 'Pydantic') +ERROR_ALIAS = ErrorCode('pydantic-alias', 'Dynamic alias disallowed', 'Pydantic') +ERROR_UNEXPECTED = ErrorCode('pydantic-unexpected', 'Unexpected behavior', 'Pydantic') +ERROR_UNTYPED = ErrorCode('pydantic-field', 'Untyped field disallowed', 'Pydantic') +ERROR_FIELD_DEFAULTS = ErrorCode('pydantic-field', 'Invalid Field defaults', 'Pydantic') + + +def error_from_orm(model_name: str, api: CheckerPluginInterface, context: Context) -> None: + api.fail(f'"{model_name}" does not have orm_mode=True', context, code=ERROR_ORM) + + +def error_invalid_config_value(name: str, api: SemanticAnalyzerPluginInterface, context: Context) -> None: + api.fail(f'Invalid value for "Config.{name}"', context, code=ERROR_CONFIG) + + +def error_required_dynamic_aliases(api: SemanticAnalyzerPluginInterface, context: Context) -> None: + api.fail('Required dynamic aliases disallowed', context, code=ERROR_ALIAS) + + +def error_unexpected_behavior( + detail: str, api: Union[CheckerPluginInterface, SemanticAnalyzerPluginInterface], context: Context +) -> None: # pragma: no cover + # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path + link = 'https://github.com/pydantic/pydantic/issues/new/choose' + full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n' + full_message += f'Please consider reporting this bug at {link} so we can try to fix it!' + api.fail(full_message, context, code=ERROR_UNEXPECTED) + + +def error_untyped_fields(api: SemanticAnalyzerPluginInterface, context: Context) -> None: + api.fail('Untyped fields disallowed', context, code=ERROR_UNTYPED) + + +def error_default_and_default_factory_specified(api: CheckerPluginInterface, context: Context) -> None: + api.fail('Field default and default_factory cannot be specified together', context, code=ERROR_FIELD_DEFAULTS) + + +def add_method( + ctx: ClassDefContext, + name: str, + args: List[Argument], + return_type: Type, + self_type: Optional[Type] = None, + tvar_def: Optional[TypeVarDef] = None, + is_classmethod: bool = False, + is_new: bool = False, + # is_staticmethod: bool = False, +) -> None: + """ + Adds a new method to a class. + + This can be dropped if/when https://github.com/python/mypy/issues/7301 is merged + """ + info = ctx.cls.info + + # First remove any previously generated methods with the same name + # to avoid clashes and problems in the semantic analyzer. + if name in info.names: + sym = info.names[name] + if sym.plugin_generated and isinstance(sym.node, FuncDef): + ctx.cls.defs.body.remove(sym.node) # pragma: no cover + + self_type = self_type or fill_typevars(info) + if is_classmethod or is_new: + first = [Argument(Var('_cls'), TypeType.make_normalized(self_type), None, ARG_POS)] + # elif is_staticmethod: + # first = [] + else: + self_type = self_type or fill_typevars(info) + first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)] + args = first + args + arg_types, arg_names, arg_kinds = [], [], [] + for arg in args: + assert arg.type_annotation, 'All arguments must be fully typed.' + arg_types.append(arg.type_annotation) + arg_names.append(get_name(arg.variable)) + arg_kinds.append(arg.kind) + + function_type = ctx.api.named_type(f'{BUILTINS_NAME}.function') + signature = CallableType( + arg_types, arg_kinds, arg_names, return_type, function_type, variables=[tvar_def] if tvar_def else None + ) + + func = FuncDef(name, args, Block([PassStmt()])) + func.info = info + func.type = set_callable_name(signature, func) + func.is_class = is_classmethod + # func.is_static = is_staticmethod + func._fullname = get_fullname(info) + '.' + name + func.line = info.line + + # NOTE: we would like the plugin generated node to dominate, but we still + # need to keep any existing definitions so they get semantically analyzed. + if name in info.names: + # Get a nice unique name instead. + r_name = get_unique_redefinition_name(name, info.names) + info.names[r_name] = info.names[name] + + if is_classmethod: # or is_staticmethod: + func.is_decorated = True + v = Var(name, func.type) + v.info = info + v._fullname = func._fullname + # if is_classmethod: + v.is_classmethod = True + dec = Decorator(func, [NameExpr('classmethod')], v) + # else: + # v.is_staticmethod = True + # dec = Decorator(func, [NameExpr('staticmethod')], v) + + dec.line = info.line + sym = SymbolTableNode(MDEF, dec) + else: + sym = SymbolTableNode(MDEF, func) + sym.plugin_generated = True + + info.names[name] = sym + info.defn.defs.body.append(func) + + +def get_fullname(x: Union[FuncBase, SymbolNode]) -> str: + """ + Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped. + """ + fn = x.fullname + if callable(fn): # pragma: no cover + return fn() + return fn + + +def get_name(x: Union[FuncBase, SymbolNode]) -> str: + """ + Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped. + """ + fn = x.name + if callable(fn): # pragma: no cover + return fn() + return fn + + +def parse_toml(config_file: str) -> Optional[Dict[str, Any]]: + if not config_file.endswith('.toml'): + return None + + read_mode = 'rb' + if sys.version_info >= (3, 11): + import tomllib as toml_ + else: + try: + import tomli as toml_ + except ImportError: + # older versions of mypy have toml as a dependency, not tomli + read_mode = 'r' + try: + import toml as toml_ # type: ignore[no-redef] + except ImportError: # pragma: no cover + import warnings + + warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.') + return None + + with open(config_file, read_mode) as rf: + return toml_.load(rf) # type: ignore[arg-type] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py new file mode 100644 index 0000000..ba07b74 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py @@ -0,0 +1,747 @@ +import re +from ipaddress import ( + IPv4Address, + IPv4Interface, + IPv4Network, + IPv6Address, + IPv6Interface, + IPv6Network, + _BaseAddress, + _BaseNetwork, +) +from typing import ( + TYPE_CHECKING, + Any, + Collection, + Dict, + Generator, + List, + Match, + Optional, + Pattern, + Set, + Tuple, + Type, + Union, + cast, + no_type_check, +) + +from pydantic.v1 import errors +from pydantic.v1.utils import Representation, update_not_none +from pydantic.v1.validators import constr_length_validator, str_validator + +if TYPE_CHECKING: + import email_validator + from typing_extensions import TypedDict + + from pydantic.v1.config import BaseConfig + from pydantic.v1.fields import ModelField + from pydantic.v1.typing import AnyCallable + + CallableGenerator = Generator[AnyCallable, None, None] + + class Parts(TypedDict, total=False): + scheme: str + user: Optional[str] + password: Optional[str] + ipv4: Optional[str] + ipv6: Optional[str] + domain: Optional[str] + port: Optional[str] + path: Optional[str] + query: Optional[str] + fragment: Optional[str] + + class HostParts(TypedDict, total=False): + host: str + tld: Optional[str] + host_type: Optional[str] + port: Optional[str] + rebuild: bool + +else: + email_validator = None + + class Parts(dict): + pass + + +NetworkType = Union[str, bytes, int, Tuple[Union[str, bytes, int], Union[str, int]]] + +__all__ = [ + 'AnyUrl', + 'AnyHttpUrl', + 'FileUrl', + 'HttpUrl', + 'stricturl', + 'EmailStr', + 'NameEmail', + 'IPvAnyAddress', + 'IPvAnyInterface', + 'IPvAnyNetwork', + 'PostgresDsn', + 'CockroachDsn', + 'AmqpDsn', + 'RedisDsn', + 'MongoDsn', + 'KafkaDsn', + 'validate_email', +] + +_url_regex_cache = None +_multi_host_url_regex_cache = None +_ascii_domain_regex_cache = None +_int_domain_regex_cache = None +_host_regex_cache = None + +_host_regex = ( + r'(?:' + r'(?P(?:\d{1,3}\.){3}\d{1,3})(?=$|[/:#?])|' # ipv4 + r'(?P\[[A-F0-9]*:[A-F0-9:]+\])(?=$|[/:#?])|' # ipv6 + r'(?P[^\s/:?#]+)' # domain, validation occurs later + r')?' + r'(?::(?P\d+))?' # port +) +_scheme_regex = r'(?:(?P[a-z][a-z0-9+\-.]+)://)?' # scheme https://tools.ietf.org/html/rfc3986#appendix-A +_user_info_regex = r'(?:(?P[^\s:/]*)(?::(?P[^\s/]*))?@)?' +_path_regex = r'(?P/[^\s?#]*)?' +_query_regex = r'(?:\?(?P[^\s#]*))?' +_fragment_regex = r'(?:#(?P[^\s#]*))?' + + +def url_regex() -> Pattern[str]: + global _url_regex_cache + if _url_regex_cache is None: + _url_regex_cache = re.compile( + rf'{_scheme_regex}{_user_info_regex}{_host_regex}{_path_regex}{_query_regex}{_fragment_regex}', + re.IGNORECASE, + ) + return _url_regex_cache + + +def multi_host_url_regex() -> Pattern[str]: + """ + Compiled multi host url regex. + + Additionally to `url_regex` it allows to match multiple hosts. + E.g. host1.db.net,host2.db.net + """ + global _multi_host_url_regex_cache + if _multi_host_url_regex_cache is None: + _multi_host_url_regex_cache = re.compile( + rf'{_scheme_regex}{_user_info_regex}' + r'(?P([^/]*))' # validation occurs later + rf'{_path_regex}{_query_regex}{_fragment_regex}', + re.IGNORECASE, + ) + return _multi_host_url_regex_cache + + +def ascii_domain_regex() -> Pattern[str]: + global _ascii_domain_regex_cache + if _ascii_domain_regex_cache is None: + ascii_chunk = r'[_0-9a-z](?:[-_0-9a-z]{0,61}[_0-9a-z])?' + ascii_domain_ending = r'(?P\.[a-z]{2,63})?\.?' + _ascii_domain_regex_cache = re.compile( + fr'(?:{ascii_chunk}\.)*?{ascii_chunk}{ascii_domain_ending}', re.IGNORECASE + ) + return _ascii_domain_regex_cache + + +def int_domain_regex() -> Pattern[str]: + global _int_domain_regex_cache + if _int_domain_regex_cache is None: + int_chunk = r'[_0-9a-\U00040000](?:[-_0-9a-\U00040000]{0,61}[_0-9a-\U00040000])?' + int_domain_ending = r'(?P(\.[^\W\d_]{2,63})|(\.(?:xn--)[_0-9a-z-]{2,63}))?\.?' + _int_domain_regex_cache = re.compile(fr'(?:{int_chunk}\.)*?{int_chunk}{int_domain_ending}', re.IGNORECASE) + return _int_domain_regex_cache + + +def host_regex() -> Pattern[str]: + global _host_regex_cache + if _host_regex_cache is None: + _host_regex_cache = re.compile( + _host_regex, + re.IGNORECASE, + ) + return _host_regex_cache + + +class AnyUrl(str): + strip_whitespace = True + min_length = 1 + max_length = 2**16 + allowed_schemes: Optional[Collection[str]] = None + tld_required: bool = False + user_required: bool = False + host_required: bool = True + hidden_parts: Set[str] = set() + + __slots__ = ('scheme', 'user', 'password', 'host', 'tld', 'host_type', 'port', 'path', 'query', 'fragment') + + @no_type_check + def __new__(cls, url: Optional[str], **kwargs) -> object: + return str.__new__(cls, cls.build(**kwargs) if url is None else url) + + def __init__( + self, + url: str, + *, + scheme: str, + user: Optional[str] = None, + password: Optional[str] = None, + host: Optional[str] = None, + tld: Optional[str] = None, + host_type: str = 'domain', + port: Optional[str] = None, + path: Optional[str] = None, + query: Optional[str] = None, + fragment: Optional[str] = None, + ) -> None: + str.__init__(url) + self.scheme = scheme + self.user = user + self.password = password + self.host = host + self.tld = tld + self.host_type = host_type + self.port = port + self.path = path + self.query = query + self.fragment = fragment + + @classmethod + def build( + cls, + *, + scheme: str, + user: Optional[str] = None, + password: Optional[str] = None, + host: str, + port: Optional[str] = None, + path: Optional[str] = None, + query: Optional[str] = None, + fragment: Optional[str] = None, + **_kwargs: str, + ) -> str: + parts = Parts( + scheme=scheme, + user=user, + password=password, + host=host, + port=port, + path=path, + query=query, + fragment=fragment, + **_kwargs, # type: ignore[misc] + ) + + url = scheme + '://' + if user: + url += user + if password: + url += ':' + password + if user or password: + url += '@' + url += host + if port and ('port' not in cls.hidden_parts or cls.get_default_parts(parts).get('port') != port): + url += ':' + port + if path: + url += path + if query: + url += '?' + query + if fragment: + url += '#' + fragment + return url + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, minLength=cls.min_length, maxLength=cls.max_length, format='uri') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: Any, field: 'ModelField', config: 'BaseConfig') -> 'AnyUrl': + if value.__class__ == cls: + return value + value = str_validator(value) + if cls.strip_whitespace: + value = value.strip() + url: str = cast(str, constr_length_validator(value, field, config)) + + m = cls._match_url(url) + # the regex should always match, if it doesn't please report with details of the URL tried + assert m, 'URL regex failed unexpectedly' + + original_parts = cast('Parts', m.groupdict()) + parts = cls.apply_default_parts(original_parts) + parts = cls.validate_parts(parts) + + if m.end() != len(url): + raise errors.UrlExtraError(extra=url[m.end() :]) + + return cls._build_url(m, url, parts) + + @classmethod + def _build_url(cls, m: Match[str], url: str, parts: 'Parts') -> 'AnyUrl': + """ + Validate hosts and build the AnyUrl object. Split from `validate` so this method + can be altered in `MultiHostDsn`. + """ + host, tld, host_type, rebuild = cls.validate_host(parts) + + return cls( + None if rebuild else url, + scheme=parts['scheme'], + user=parts['user'], + password=parts['password'], + host=host, + tld=tld, + host_type=host_type, + port=parts['port'], + path=parts['path'], + query=parts['query'], + fragment=parts['fragment'], + ) + + @staticmethod + def _match_url(url: str) -> Optional[Match[str]]: + return url_regex().match(url) + + @staticmethod + def _validate_port(port: Optional[str]) -> None: + if port is not None and int(port) > 65_535: + raise errors.UrlPortError() + + @classmethod + def validate_parts(cls, parts: 'Parts', validate_port: bool = True) -> 'Parts': + """ + A method used to validate parts of a URL. + Could be overridden to set default values for parts if missing + """ + scheme = parts['scheme'] + if scheme is None: + raise errors.UrlSchemeError() + + if cls.allowed_schemes and scheme.lower() not in cls.allowed_schemes: + raise errors.UrlSchemePermittedError(set(cls.allowed_schemes)) + + if validate_port: + cls._validate_port(parts['port']) + + user = parts['user'] + if cls.user_required and user is None: + raise errors.UrlUserInfoError() + + return parts + + @classmethod + def validate_host(cls, parts: 'Parts') -> Tuple[str, Optional[str], str, bool]: + tld, host_type, rebuild = None, None, False + for f in ('domain', 'ipv4', 'ipv6'): + host = parts[f] # type: ignore[literal-required] + if host: + host_type = f + break + + if host is None: + if cls.host_required: + raise errors.UrlHostError() + elif host_type == 'domain': + is_international = False + d = ascii_domain_regex().fullmatch(host) + if d is None: + d = int_domain_regex().fullmatch(host) + if d is None: + raise errors.UrlHostError() + is_international = True + + tld = d.group('tld') + if tld is None and not is_international: + d = int_domain_regex().fullmatch(host) + assert d is not None + tld = d.group('tld') + is_international = True + + if tld is not None: + tld = tld[1:] + elif cls.tld_required: + raise errors.UrlHostTldError() + + if is_international: + host_type = 'int_domain' + rebuild = True + host = host.encode('idna').decode('ascii') + if tld is not None: + tld = tld.encode('idna').decode('ascii') + + return host, tld, host_type, rebuild # type: ignore + + @staticmethod + def get_default_parts(parts: 'Parts') -> 'Parts': + return {} + + @classmethod + def apply_default_parts(cls, parts: 'Parts') -> 'Parts': + for key, value in cls.get_default_parts(parts).items(): + if not parts[key]: # type: ignore[literal-required] + parts[key] = value # type: ignore[literal-required] + return parts + + def __repr__(self) -> str: + extra = ', '.join(f'{n}={getattr(self, n)!r}' for n in self.__slots__ if getattr(self, n) is not None) + return f'{self.__class__.__name__}({super().__repr__()}, {extra})' + + +class AnyHttpUrl(AnyUrl): + allowed_schemes = {'http', 'https'} + + __slots__ = () + + +class HttpUrl(AnyHttpUrl): + tld_required = True + # https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + max_length = 2083 + hidden_parts = {'port'} + + @staticmethod + def get_default_parts(parts: 'Parts') -> 'Parts': + return {'port': '80' if parts['scheme'] == 'http' else '443'} + + +class FileUrl(AnyUrl): + allowed_schemes = {'file'} + host_required = False + + __slots__ = () + + +class MultiHostDsn(AnyUrl): + __slots__ = AnyUrl.__slots__ + ('hosts',) + + def __init__(self, *args: Any, hosts: Optional[List['HostParts']] = None, **kwargs: Any): + super().__init__(*args, **kwargs) + self.hosts = hosts + + @staticmethod + def _match_url(url: str) -> Optional[Match[str]]: + return multi_host_url_regex().match(url) + + @classmethod + def validate_parts(cls, parts: 'Parts', validate_port: bool = True) -> 'Parts': + return super().validate_parts(parts, validate_port=False) + + @classmethod + def _build_url(cls, m: Match[str], url: str, parts: 'Parts') -> 'MultiHostDsn': + hosts_parts: List['HostParts'] = [] + host_re = host_regex() + for host in m.groupdict()['hosts'].split(','): + d: Parts = host_re.match(host).groupdict() # type: ignore + host, tld, host_type, rebuild = cls.validate_host(d) + port = d.get('port') + cls._validate_port(port) + hosts_parts.append( + { + 'host': host, + 'host_type': host_type, + 'tld': tld, + 'rebuild': rebuild, + 'port': port, + } + ) + + if len(hosts_parts) > 1: + return cls( + None if any([hp['rebuild'] for hp in hosts_parts]) else url, + scheme=parts['scheme'], + user=parts['user'], + password=parts['password'], + path=parts['path'], + query=parts['query'], + fragment=parts['fragment'], + host_type=None, + hosts=hosts_parts, + ) + else: + # backwards compatibility with single host + host_part = hosts_parts[0] + return cls( + None if host_part['rebuild'] else url, + scheme=parts['scheme'], + user=parts['user'], + password=parts['password'], + host=host_part['host'], + tld=host_part['tld'], + host_type=host_part['host_type'], + port=host_part.get('port'), + path=parts['path'], + query=parts['query'], + fragment=parts['fragment'], + ) + + +class PostgresDsn(MultiHostDsn): + allowed_schemes = { + 'postgres', + 'postgresql', + 'postgresql+asyncpg', + 'postgresql+pg8000', + 'postgresql+psycopg', + 'postgresql+psycopg2', + 'postgresql+psycopg2cffi', + 'postgresql+py-postgresql', + 'postgresql+pygresql', + } + user_required = True + + __slots__ = () + + +class CockroachDsn(AnyUrl): + allowed_schemes = { + 'cockroachdb', + 'cockroachdb+psycopg2', + 'cockroachdb+asyncpg', + } + user_required = True + + +class AmqpDsn(AnyUrl): + allowed_schemes = {'amqp', 'amqps'} + host_required = False + + +class RedisDsn(AnyUrl): + __slots__ = () + allowed_schemes = {'redis', 'rediss'} + host_required = False + + @staticmethod + def get_default_parts(parts: 'Parts') -> 'Parts': + return { + 'domain': 'localhost' if not (parts['ipv4'] or parts['ipv6']) else '', + 'port': '6379', + 'path': '/0', + } + + +class MongoDsn(AnyUrl): + allowed_schemes = {'mongodb'} + + # TODO: Needed to generic "Parts" for "Replica Set", "Sharded Cluster", and other mongodb deployment modes + @staticmethod + def get_default_parts(parts: 'Parts') -> 'Parts': + return { + 'port': '27017', + } + + +class KafkaDsn(AnyUrl): + allowed_schemes = {'kafka'} + + @staticmethod + def get_default_parts(parts: 'Parts') -> 'Parts': + return { + 'domain': 'localhost', + 'port': '9092', + } + + +def stricturl( + *, + strip_whitespace: bool = True, + min_length: int = 1, + max_length: int = 2**16, + tld_required: bool = True, + host_required: bool = True, + allowed_schemes: Optional[Collection[str]] = None, +) -> Type[AnyUrl]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict( + strip_whitespace=strip_whitespace, + min_length=min_length, + max_length=max_length, + tld_required=tld_required, + host_required=host_required, + allowed_schemes=allowed_schemes, + ) + return type('UrlValue', (AnyUrl,), namespace) + + +def import_email_validator() -> None: + global email_validator + try: + import email_validator + except ImportError as e: + raise ImportError('email-validator is not installed, run `pip install pydantic[email]`') from e + + +class EmailStr(str): + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='email') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + # included here and below so the error happens straight away + import_email_validator() + + yield str_validator + yield cls.validate + + @classmethod + def validate(cls, value: Union[str]) -> str: + return validate_email(value)[1] + + +class NameEmail(Representation): + __slots__ = 'name', 'email' + + def __init__(self, name: str, email: str): + self.name = name + self.email = email + + def __eq__(self, other: Any) -> bool: + return isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email) + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='name-email') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + import_email_validator() + + yield cls.validate + + @classmethod + def validate(cls, value: Any) -> 'NameEmail': + if value.__class__ == cls: + return value + value = str_validator(value) + return cls(*validate_email(value)) + + def __str__(self) -> str: + return f'{self.name} <{self.email}>' + + +class IPvAnyAddress(_BaseAddress): + __slots__ = () + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='ipvanyaddress') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: Union[str, bytes, int]) -> Union[IPv4Address, IPv6Address]: + try: + return IPv4Address(value) + except ValueError: + pass + + try: + return IPv6Address(value) + except ValueError: + raise errors.IPvAnyAddressError() + + +class IPvAnyInterface(_BaseAddress): + __slots__ = () + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='ipvanyinterface') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: NetworkType) -> Union[IPv4Interface, IPv6Interface]: + try: + return IPv4Interface(value) + except ValueError: + pass + + try: + return IPv6Interface(value) + except ValueError: + raise errors.IPvAnyInterfaceError() + + +class IPvAnyNetwork(_BaseNetwork): # type: ignore + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='ipvanynetwork') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: NetworkType) -> Union[IPv4Network, IPv6Network]: + # Assume IP Network is defined with a default value for ``strict`` argument. + # Define your own class if you want to specify network address check strictness. + try: + return IPv4Network(value) + except ValueError: + pass + + try: + return IPv6Network(value) + except ValueError: + raise errors.IPvAnyNetworkError() + + +pretty_email_regex = re.compile(r'([\w ]*?) *<(.*)> *') +MAX_EMAIL_LENGTH = 2048 +"""Maximum length for an email. +A somewhat arbitrary but very generous number compared to what is allowed by most implementations. +""" + + +def validate_email(value: Union[str]) -> Tuple[str, str]: + """ + Email address validation using https://pypi.org/project/email-validator/ + Notes: + * raw ip address (literal) domain parts are not allowed. + * "John Doe " style "pretty" email addresses are processed + * spaces are striped from the beginning and end of addresses but no error is raised + """ + if email_validator is None: + import_email_validator() + + if len(value) > MAX_EMAIL_LENGTH: + raise errors.EmailError() + + m = pretty_email_regex.fullmatch(value) + name: Union[str, None] = None + if m: + name, value = m.groups() + email = value.strip() + try: + parts = email_validator.validate_email(email, check_deliverability=False) + except email_validator.EmailNotValidError as e: + raise errors.EmailError from e + + if hasattr(parts, 'normalized'): + # email-validator >= 2 + email = parts.normalized + assert email is not None + name = name or parts.local_part + return name, email + else: + # email-validator >1, <2 + at_index = email.index('@') + local_part = email[:at_index] # RFC 5321, local part must be case-sensitive. + global_part = email[at_index:].lower() + + return name or local_part, local_part + global_part diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py new file mode 100644 index 0000000..431d75a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py @@ -0,0 +1,66 @@ +import json +import pickle +from enum import Enum +from pathlib import Path +from typing import Any, Callable, Union + +from pydantic.v1.types import StrBytes + + +class Protocol(str, Enum): + json = 'json' + pickle = 'pickle' + + +def load_str_bytes( + b: StrBytes, + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, +) -> Any: + if proto is None and content_type: + if content_type.endswith(('json', 'javascript')): + pass + elif allow_pickle and content_type.endswith('pickle'): + proto = Protocol.pickle + else: + raise TypeError(f'Unknown content-type: {content_type}') + + proto = proto or Protocol.json + + if proto == Protocol.json: + if isinstance(b, bytes): + b = b.decode(encoding) + return json_loads(b) + elif proto == Protocol.pickle: + if not allow_pickle: + raise RuntimeError('Trying to decode with pickle with allow_pickle=False') + bb = b if isinstance(b, bytes) else b.encode() + return pickle.loads(bb) + else: + raise TypeError(f'Unknown protocol: {proto}') + + +def load_file( + path: Union[str, Path], + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, +) -> Any: + path = Path(path) + b = path.read_bytes() + if content_type is None: + if path.suffix in ('.js', '.json'): + proto = Protocol.json + elif path.suffix == '.pkl': + proto = Protocol.pickle + + return load_str_bytes( + b, proto=proto, content_type=content_type, encoding=encoding, allow_pickle=allow_pickle, json_loads=json_loads + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py new file mode 100644 index 0000000..a91fe2c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py @@ -0,0 +1,1163 @@ +import re +import warnings +from collections import defaultdict +from dataclasses import is_dataclass +from datetime import date, datetime, time, timedelta +from decimal import Decimal +from enum import Enum +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + ForwardRef, + FrozenSet, + Generic, + Iterable, + List, + Optional, + Pattern, + Sequence, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) +from uuid import UUID + +from typing_extensions import Annotated, Literal + +from pydantic.v1.fields import ( + MAPPING_LIKE_SHAPES, + SHAPE_DEQUE, + SHAPE_FROZENSET, + SHAPE_GENERIC, + SHAPE_ITERABLE, + SHAPE_LIST, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_SINGLETON, + SHAPE_TUPLE, + SHAPE_TUPLE_ELLIPSIS, + FieldInfo, + ModelField, +) +from pydantic.v1.json import pydantic_encoder +from pydantic.v1.networks import AnyUrl, EmailStr +from pydantic.v1.types import ( + ConstrainedDecimal, + ConstrainedFloat, + ConstrainedFrozenSet, + ConstrainedInt, + ConstrainedList, + ConstrainedSet, + ConstrainedStr, + SecretBytes, + SecretStr, + StrictBytes, + StrictStr, + conbytes, + condecimal, + confloat, + confrozenset, + conint, + conlist, + conset, + constr, +) +from pydantic.v1.typing import ( + all_literal_values, + get_args, + get_origin, + get_sub_types, + is_callable_type, + is_literal_type, + is_namedtuple, + is_none_type, + is_union, +) +from pydantic.v1.utils import ROOT_KEY, get_model, lenient_issubclass + +if TYPE_CHECKING: + from pydantic.v1.dataclasses import Dataclass + from pydantic.v1.main import BaseModel + +default_prefix = '#/definitions/' +default_ref_template = '#/definitions/{model}' + +TypeModelOrEnum = Union[Type['BaseModel'], Type[Enum]] +TypeModelSet = Set[TypeModelOrEnum] + + +def _apply_modify_schema( + modify_schema: Callable[..., None], field: Optional[ModelField], field_schema: Dict[str, Any] +) -> None: + from inspect import signature + + sig = signature(modify_schema) + args = set(sig.parameters.keys()) + if 'field' in args or 'kwargs' in args: + modify_schema(field_schema, field=field) + else: + modify_schema(field_schema) + + +def schema( + models: Sequence[Union[Type['BaseModel'], Type['Dataclass']]], + *, + by_alias: bool = True, + title: Optional[str] = None, + description: Optional[str] = None, + ref_prefix: Optional[str] = None, + ref_template: str = default_ref_template, +) -> Dict[str, Any]: + """ + Process a list of models and generate a single JSON Schema with all of them defined in the ``definitions`` + top-level JSON key, including their sub-models. + + :param models: a list of models to include in the generated JSON Schema + :param by_alias: generate the schemas using the aliases defined, if any + :param title: title for the generated schema that includes the definitions + :param description: description for the generated schema + :param ref_prefix: the JSON Pointer prefix for schema references with ``$ref``, if None, will be set to the + default of ``#/definitions/``. Update it if you want the schemas to reference the definitions somewhere + else, e.g. for OpenAPI use ``#/components/schemas/``. The resulting generated schemas will still be at the + top-level key ``definitions``, so you can extract them from there. But all the references will have the set + prefix. + :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful + for references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For + a sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``. + :return: dict with the JSON Schema with a ``definitions`` top-level key including the schema definitions for + the models and sub-models passed in ``models``. + """ + clean_models = [get_model(model) for model in models] + flat_models = get_flat_models_from_models(clean_models) + model_name_map = get_model_name_map(flat_models) + definitions = {} + output_schema: Dict[str, Any] = {} + if title: + output_schema['title'] = title + if description: + output_schema['description'] = description + for model in clean_models: + m_schema, m_definitions, m_nested_models = model_process_schema( + model, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + ) + definitions.update(m_definitions) + model_name = model_name_map[model] + definitions[model_name] = m_schema + if definitions: + output_schema['definitions'] = definitions + return output_schema + + +def model_schema( + model: Union[Type['BaseModel'], Type['Dataclass']], + by_alias: bool = True, + ref_prefix: Optional[str] = None, + ref_template: str = default_ref_template, +) -> Dict[str, Any]: + """ + Generate a JSON Schema for one model. With all the sub-models defined in the ``definitions`` top-level + JSON key. + + :param model: a Pydantic model (a class that inherits from BaseModel) + :param by_alias: generate the schemas using the aliases defined, if any + :param ref_prefix: the JSON Pointer prefix for schema references with ``$ref``, if None, will be set to the + default of ``#/definitions/``. Update it if you want the schemas to reference the definitions somewhere + else, e.g. for OpenAPI use ``#/components/schemas/``. The resulting generated schemas will still be at the + top-level key ``definitions``, so you can extract them from there. But all the references will have the set + prefix. + :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful for + references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For a + sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``. + :return: dict with the JSON Schema for the passed ``model`` + """ + model = get_model(model) + flat_models = get_flat_models_from_model(model) + model_name_map = get_model_name_map(flat_models) + model_name = model_name_map[model] + m_schema, m_definitions, nested_models = model_process_schema( + model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, ref_template=ref_template + ) + if model_name in nested_models: + # model_name is in Nested models, it has circular references + m_definitions[model_name] = m_schema + m_schema = get_schema_ref(model_name, ref_prefix, ref_template, False) + if m_definitions: + m_schema.update({'definitions': m_definitions}) + return m_schema + + +def get_field_info_schema(field: ModelField, schema_overrides: bool = False) -> Tuple[Dict[str, Any], bool]: + # If no title is explicitly set, we don't set title in the schema for enums. + # The behaviour is the same as `BaseModel` reference, where the default title + # is in the definitions part of the schema. + schema_: Dict[str, Any] = {} + if field.field_info.title or not lenient_issubclass(field.type_, Enum): + schema_['title'] = field.field_info.title or field.alias.title().replace('_', ' ') + + if field.field_info.title: + schema_overrides = True + + if field.field_info.description: + schema_['description'] = field.field_info.description + schema_overrides = True + + if not field.required and field.default is not None and not is_callable_type(field.outer_type_): + schema_['default'] = encode_default(field.default) + schema_overrides = True + + return schema_, schema_overrides + + +def field_schema( + field: ModelField, + *, + by_alias: bool = True, + model_name_map: Dict[TypeModelOrEnum, str], + ref_prefix: Optional[str] = None, + ref_template: str = default_ref_template, + known_models: Optional[TypeModelSet] = None, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + Process a Pydantic field and return a tuple with a JSON Schema for it as the first item. + Also return a dictionary of definitions with models as keys and their schemas as values. If the passed field + is a model and has sub-models, and those sub-models don't have overrides (as ``title``, ``default``, etc), they + will be included in the definitions and referenced in the schema instead of included recursively. + + :param field: a Pydantic ``ModelField`` + :param by_alias: use the defined alias (if any) in the returned schema + :param model_name_map: used to generate the JSON Schema references to other models included in the definitions + :param ref_prefix: the JSON Pointer prefix to use for references to other schemas, if None, the default of + #/definitions/ will be used + :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful for + references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For a + sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``. + :param known_models: used to solve circular references + :return: tuple of the schema for this field and additional definitions + """ + s, schema_overrides = get_field_info_schema(field) + + validation_schema = get_field_schema_validations(field) + if validation_schema: + s.update(validation_schema) + schema_overrides = True + + f_schema, f_definitions, f_nested_models = field_type_schema( + field, + by_alias=by_alias, + model_name_map=model_name_map, + schema_overrides=schema_overrides, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models or set(), + ) + + # $ref will only be returned when there are no schema_overrides + if '$ref' in f_schema: + return f_schema, f_definitions, f_nested_models + else: + s.update(f_schema) + return s, f_definitions, f_nested_models + + +numeric_types = (int, float, Decimal) +_str_types_attrs: Tuple[Tuple[str, Union[type, Tuple[type, ...]], str], ...] = ( + ('max_length', numeric_types, 'maxLength'), + ('min_length', numeric_types, 'minLength'), + ('regex', str, 'pattern'), +) + +_numeric_types_attrs: Tuple[Tuple[str, Union[type, Tuple[type, ...]], str], ...] = ( + ('gt', numeric_types, 'exclusiveMinimum'), + ('lt', numeric_types, 'exclusiveMaximum'), + ('ge', numeric_types, 'minimum'), + ('le', numeric_types, 'maximum'), + ('multiple_of', numeric_types, 'multipleOf'), +) + + +def get_field_schema_validations(field: ModelField) -> Dict[str, Any]: + """ + Get the JSON Schema validation keywords for a ``field`` with an annotation of + a Pydantic ``FieldInfo`` with validation arguments. + """ + f_schema: Dict[str, Any] = {} + + if lenient_issubclass(field.type_, Enum): + # schema is already updated by `enum_process_schema`; just update with field extra + if field.field_info.extra: + f_schema.update(field.field_info.extra) + return f_schema + + if lenient_issubclass(field.type_, (str, bytes)): + for attr_name, t, keyword in _str_types_attrs: + attr = getattr(field.field_info, attr_name, None) + if isinstance(attr, t): + f_schema[keyword] = attr + if lenient_issubclass(field.type_, numeric_types) and not issubclass(field.type_, bool): + for attr_name, t, keyword in _numeric_types_attrs: + attr = getattr(field.field_info, attr_name, None) + if isinstance(attr, t): + f_schema[keyword] = attr + if field.field_info is not None and field.field_info.const: + f_schema['const'] = field.default + if field.field_info.extra: + f_schema.update(field.field_info.extra) + modify_schema = getattr(field.outer_type_, '__modify_schema__', None) + if modify_schema: + _apply_modify_schema(modify_schema, field, f_schema) + return f_schema + + +def get_model_name_map(unique_models: TypeModelSet) -> Dict[TypeModelOrEnum, str]: + """ + Process a set of models and generate unique names for them to be used as keys in the JSON Schema + definitions. By default the names are the same as the class name. But if two models in different Python + modules have the same name (e.g. "users.Model" and "items.Model"), the generated names will be + based on the Python module path for those conflicting models to prevent name collisions. + + :param unique_models: a Python set of models + :return: dict mapping models to names + """ + name_model_map = {} + conflicting_names: Set[str] = set() + for model in unique_models: + model_name = normalize_name(model.__name__) + if model_name in conflicting_names: + model_name = get_long_model_name(model) + name_model_map[model_name] = model + elif model_name in name_model_map: + conflicting_names.add(model_name) + conflicting_model = name_model_map.pop(model_name) + name_model_map[get_long_model_name(conflicting_model)] = conflicting_model + name_model_map[get_long_model_name(model)] = model + else: + name_model_map[model_name] = model + return {v: k for k, v in name_model_map.items()} + + +def get_flat_models_from_model(model: Type['BaseModel'], known_models: Optional[TypeModelSet] = None) -> TypeModelSet: + """ + Take a single ``model`` and generate a set with itself and all the sub-models in the tree. I.e. if you pass + model ``Foo`` (subclass of Pydantic ``BaseModel``) as ``model``, and it has a field of type ``Bar`` (also + subclass of ``BaseModel``) and that model ``Bar`` has a field of type ``Baz`` (also subclass of ``BaseModel``), + the return value will be ``set([Foo, Bar, Baz])``. + + :param model: a Pydantic ``BaseModel`` subclass + :param known_models: used to solve circular references + :return: a set with the initial model and all its sub-models + """ + known_models = known_models or set() + flat_models: TypeModelSet = set() + flat_models.add(model) + known_models |= flat_models + fields = cast(Sequence[ModelField], model.__fields__.values()) + flat_models |= get_flat_models_from_fields(fields, known_models=known_models) + return flat_models + + +def get_flat_models_from_field(field: ModelField, known_models: TypeModelSet) -> TypeModelSet: + """ + Take a single Pydantic ``ModelField`` (from a model) that could have been declared as a subclass of BaseModel + (so, it could be a submodel), and generate a set with its model and all the sub-models in the tree. + I.e. if you pass a field that was declared to be of type ``Foo`` (subclass of BaseModel) as ``field``, and that + model ``Foo`` has a field of type ``Bar`` (also subclass of ``BaseModel``) and that model ``Bar`` has a field of + type ``Baz`` (also subclass of ``BaseModel``), the return value will be ``set([Foo, Bar, Baz])``. + + :param field: a Pydantic ``ModelField`` + :param known_models: used to solve circular references + :return: a set with the model used in the declaration for this field, if any, and all its sub-models + """ + from pydantic.v1.main import BaseModel + + flat_models: TypeModelSet = set() + + field_type = field.type_ + if lenient_issubclass(getattr(field_type, '__pydantic_model__', None), BaseModel): + field_type = field_type.__pydantic_model__ + + if field.sub_fields and not lenient_issubclass(field_type, BaseModel): + flat_models |= get_flat_models_from_fields(field.sub_fields, known_models=known_models) + elif lenient_issubclass(field_type, BaseModel) and field_type not in known_models: + flat_models |= get_flat_models_from_model(field_type, known_models=known_models) + elif lenient_issubclass(field_type, Enum): + flat_models.add(field_type) + return flat_models + + +def get_flat_models_from_fields(fields: Sequence[ModelField], known_models: TypeModelSet) -> TypeModelSet: + """ + Take a list of Pydantic ``ModelField``s (from a model) that could have been declared as subclasses of ``BaseModel`` + (so, any of them could be a submodel), and generate a set with their models and all the sub-models in the tree. + I.e. if you pass a the fields of a model ``Foo`` (subclass of ``BaseModel``) as ``fields``, and on of them has a + field of type ``Bar`` (also subclass of ``BaseModel``) and that model ``Bar`` has a field of type ``Baz`` (also + subclass of ``BaseModel``), the return value will be ``set([Foo, Bar, Baz])``. + + :param fields: a list of Pydantic ``ModelField``s + :param known_models: used to solve circular references + :return: a set with any model declared in the fields, and all their sub-models + """ + flat_models: TypeModelSet = set() + for field in fields: + flat_models |= get_flat_models_from_field(field, known_models=known_models) + return flat_models + + +def get_flat_models_from_models(models: Sequence[Type['BaseModel']]) -> TypeModelSet: + """ + Take a list of ``models`` and generate a set with them and all their sub-models in their trees. I.e. if you pass + a list of two models, ``Foo`` and ``Bar``, both subclasses of Pydantic ``BaseModel`` as models, and ``Bar`` has + a field of type ``Baz`` (also subclass of ``BaseModel``), the return value will be ``set([Foo, Bar, Baz])``. + """ + flat_models: TypeModelSet = set() + for model in models: + flat_models |= get_flat_models_from_model(model) + return flat_models + + +def get_long_model_name(model: TypeModelOrEnum) -> str: + return f'{model.__module__}__{model.__qualname__}'.replace('.', '__') + + +def field_type_schema( + field: ModelField, + *, + by_alias: bool, + model_name_map: Dict[TypeModelOrEnum, str], + ref_template: str, + schema_overrides: bool = False, + ref_prefix: Optional[str] = None, + known_models: TypeModelSet, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + Used by ``field_schema()``, you probably should be using that function. + + Take a single ``field`` and generate the schema for its type only, not including additional + information as title, etc. Also return additional schema definitions, from sub-models. + """ + from pydantic.v1.main import BaseModel # noqa: F811 + + definitions = {} + nested_models: Set[str] = set() + f_schema: Dict[str, Any] + if field.shape in { + SHAPE_LIST, + SHAPE_TUPLE_ELLIPSIS, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_FROZENSET, + SHAPE_ITERABLE, + SHAPE_DEQUE, + }: + items_schema, f_definitions, f_nested_models = field_singleton_schema( + field, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + definitions.update(f_definitions) + nested_models.update(f_nested_models) + f_schema = {'type': 'array', 'items': items_schema} + if field.shape in {SHAPE_SET, SHAPE_FROZENSET}: + f_schema['uniqueItems'] = True + + elif field.shape in MAPPING_LIKE_SHAPES: + f_schema = {'type': 'object'} + key_field = cast(ModelField, field.key_field) + regex = getattr(key_field.type_, 'regex', None) + items_schema, f_definitions, f_nested_models = field_singleton_schema( + field, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + definitions.update(f_definitions) + nested_models.update(f_nested_models) + if regex: + # Dict keys have a regex pattern + # items_schema might be a schema or empty dict, add it either way + f_schema['patternProperties'] = {ConstrainedStr._get_pattern(regex): items_schema} + if items_schema: + # The dict values are not simply Any, so they need a schema + f_schema['additionalProperties'] = items_schema + elif field.shape == SHAPE_TUPLE or (field.shape == SHAPE_GENERIC and not issubclass(field.type_, BaseModel)): + sub_schema = [] + sub_fields = cast(List[ModelField], field.sub_fields) + for sf in sub_fields: + sf_schema, sf_definitions, sf_nested_models = field_type_schema( + sf, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + definitions.update(sf_definitions) + nested_models.update(sf_nested_models) + sub_schema.append(sf_schema) + + sub_fields_len = len(sub_fields) + if field.shape == SHAPE_GENERIC: + all_of_schemas = sub_schema[0] if sub_fields_len == 1 else {'type': 'array', 'items': sub_schema} + f_schema = {'allOf': [all_of_schemas]} + else: + f_schema = { + 'type': 'array', + 'minItems': sub_fields_len, + 'maxItems': sub_fields_len, + } + if sub_fields_len >= 1: + f_schema['items'] = sub_schema + else: + assert field.shape in {SHAPE_SINGLETON, SHAPE_GENERIC}, field.shape + f_schema, f_definitions, f_nested_models = field_singleton_schema( + field, + by_alias=by_alias, + model_name_map=model_name_map, + schema_overrides=schema_overrides, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + definitions.update(f_definitions) + nested_models.update(f_nested_models) + + # check field type to avoid repeated calls to the same __modify_schema__ method + if field.type_ != field.outer_type_: + if field.shape == SHAPE_GENERIC: + field_type = field.type_ + else: + field_type = field.outer_type_ + modify_schema = getattr(field_type, '__modify_schema__', None) + if modify_schema: + _apply_modify_schema(modify_schema, field, f_schema) + return f_schema, definitions, nested_models + + +def model_process_schema( + model: TypeModelOrEnum, + *, + by_alias: bool = True, + model_name_map: Dict[TypeModelOrEnum, str], + ref_prefix: Optional[str] = None, + ref_template: str = default_ref_template, + known_models: Optional[TypeModelSet] = None, + field: Optional[ModelField] = None, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + Used by ``model_schema()``, you probably should be using that function. + + Take a single ``model`` and generate its schema. Also return additional schema definitions, from sub-models. The + sub-models of the returned schema will be referenced, but their definitions will not be included in the schema. All + the definitions are returned as the second value. + """ + from inspect import getdoc, signature + + known_models = known_models or set() + if lenient_issubclass(model, Enum): + model = cast(Type[Enum], model) + s = enum_process_schema(model, field=field) + return s, {}, set() + model = cast(Type['BaseModel'], model) + s = {'title': model.__config__.title or model.__name__} + doc = getdoc(model) + if doc: + s['description'] = doc + known_models.add(model) + m_schema, m_definitions, nested_models = model_type_schema( + model, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + s.update(m_schema) + schema_extra = model.__config__.schema_extra + if callable(schema_extra): + if len(signature(schema_extra).parameters) == 1: + schema_extra(s) + else: + schema_extra(s, model) + else: + s.update(schema_extra) + return s, m_definitions, nested_models + + +def model_type_schema( + model: Type['BaseModel'], + *, + by_alias: bool, + model_name_map: Dict[TypeModelOrEnum, str], + ref_template: str, + ref_prefix: Optional[str] = None, + known_models: TypeModelSet, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + You probably should be using ``model_schema()``, this function is indirectly used by that function. + + Take a single ``model`` and generate the schema for its type only, not including additional + information as title, etc. Also return additional schema definitions, from sub-models. + """ + properties = {} + required = [] + definitions: Dict[str, Any] = {} + nested_models: Set[str] = set() + for k, f in model.__fields__.items(): + try: + f_schema, f_definitions, f_nested_models = field_schema( + f, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + except SkipField as skip: + warnings.warn(skip.message, UserWarning) + continue + definitions.update(f_definitions) + nested_models.update(f_nested_models) + if by_alias: + properties[f.alias] = f_schema + if f.required: + required.append(f.alias) + else: + properties[k] = f_schema + if f.required: + required.append(k) + if ROOT_KEY in properties: + out_schema = properties[ROOT_KEY] + out_schema['title'] = model.__config__.title or model.__name__ + else: + out_schema = {'type': 'object', 'properties': properties} + if required: + out_schema['required'] = required + if model.__config__.extra == 'forbid': + out_schema['additionalProperties'] = False + return out_schema, definitions, nested_models + + +def enum_process_schema(enum: Type[Enum], *, field: Optional[ModelField] = None) -> Dict[str, Any]: + """ + Take a single `enum` and generate its schema. + + This is similar to the `model_process_schema` function, but applies to ``Enum`` objects. + """ + import inspect + + schema_: Dict[str, Any] = { + 'title': enum.__name__, + # Python assigns all enums a default docstring value of 'An enumeration', so + # all enums will have a description field even if not explicitly provided. + 'description': inspect.cleandoc(enum.__doc__ or 'An enumeration.'), + # Add enum values and the enum field type to the schema. + 'enum': [item.value for item in cast(Iterable[Enum], enum)], + } + + add_field_type_to_schema(enum, schema_) + + modify_schema = getattr(enum, '__modify_schema__', None) + if modify_schema: + _apply_modify_schema(modify_schema, field, schema_) + + return schema_ + + +def field_singleton_sub_fields_schema( + field: ModelField, + *, + by_alias: bool, + model_name_map: Dict[TypeModelOrEnum, str], + ref_template: str, + schema_overrides: bool = False, + ref_prefix: Optional[str] = None, + known_models: TypeModelSet, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + This function is indirectly used by ``field_schema()``, you probably should be using that function. + + Take a list of Pydantic ``ModelField`` from the declaration of a type with parameters, and generate their + schema. I.e., fields used as "type parameters", like ``str`` and ``int`` in ``Tuple[str, int]``. + """ + sub_fields = cast(List[ModelField], field.sub_fields) + definitions = {} + nested_models: Set[str] = set() + if len(sub_fields) == 1: + return field_type_schema( + sub_fields[0], + by_alias=by_alias, + model_name_map=model_name_map, + schema_overrides=schema_overrides, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + else: + s: Dict[str, Any] = {} + # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminator-object + field_has_discriminator: bool = field.discriminator_key is not None + if field_has_discriminator: + assert field.sub_fields_mapping is not None + + discriminator_models_refs: Dict[str, Union[str, Dict[str, Any]]] = {} + + for discriminator_value, sub_field in field.sub_fields_mapping.items(): + if isinstance(discriminator_value, Enum): + discriminator_value = str(discriminator_value.value) + # sub_field is either a `BaseModel` or directly an `Annotated` `Union` of many + if is_union(get_origin(sub_field.type_)): + sub_models = get_sub_types(sub_field.type_) + discriminator_models_refs[discriminator_value] = { + model_name_map[sub_model]: get_schema_ref( + model_name_map[sub_model], ref_prefix, ref_template, False + ) + for sub_model in sub_models + } + else: + sub_field_type = sub_field.type_ + if hasattr(sub_field_type, '__pydantic_model__'): + sub_field_type = sub_field_type.__pydantic_model__ + + discriminator_model_name = model_name_map[sub_field_type] + discriminator_model_ref = get_schema_ref(discriminator_model_name, ref_prefix, ref_template, False) + discriminator_models_refs[discriminator_value] = discriminator_model_ref['$ref'] + + s['discriminator'] = { + 'propertyName': field.discriminator_alias if by_alias else field.discriminator_key, + 'mapping': discriminator_models_refs, + } + + sub_field_schemas = [] + for sf in sub_fields: + sub_schema, sub_definitions, sub_nested_models = field_type_schema( + sf, + by_alias=by_alias, + model_name_map=model_name_map, + schema_overrides=schema_overrides, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + definitions.update(sub_definitions) + if schema_overrides and 'allOf' in sub_schema: + # if the sub_field is a referenced schema we only need the referenced + # object. Otherwise we will end up with several allOf inside anyOf/oneOf. + # See https://github.com/pydantic/pydantic/issues/1209 + sub_schema = sub_schema['allOf'][0] + + if sub_schema.keys() == {'discriminator', 'oneOf'}: + # we don't want discriminator information inside oneOf choices, this is dealt with elsewhere + sub_schema.pop('discriminator') + sub_field_schemas.append(sub_schema) + nested_models.update(sub_nested_models) + s['oneOf' if field_has_discriminator else 'anyOf'] = sub_field_schemas + return s, definitions, nested_models + + +# Order is important, e.g. subclasses of str must go before str +# this is used only for standard library types, custom types should use __modify_schema__ instead +field_class_to_schema: Tuple[Tuple[Any, Dict[str, Any]], ...] = ( + (Path, {'type': 'string', 'format': 'path'}), + (datetime, {'type': 'string', 'format': 'date-time'}), + (date, {'type': 'string', 'format': 'date'}), + (time, {'type': 'string', 'format': 'time'}), + (timedelta, {'type': 'number', 'format': 'time-delta'}), + (IPv4Network, {'type': 'string', 'format': 'ipv4network'}), + (IPv6Network, {'type': 'string', 'format': 'ipv6network'}), + (IPv4Interface, {'type': 'string', 'format': 'ipv4interface'}), + (IPv6Interface, {'type': 'string', 'format': 'ipv6interface'}), + (IPv4Address, {'type': 'string', 'format': 'ipv4'}), + (IPv6Address, {'type': 'string', 'format': 'ipv6'}), + (Pattern, {'type': 'string', 'format': 'regex'}), + (str, {'type': 'string'}), + (bytes, {'type': 'string', 'format': 'binary'}), + (bool, {'type': 'boolean'}), + (int, {'type': 'integer'}), + (float, {'type': 'number'}), + (Decimal, {'type': 'number'}), + (UUID, {'type': 'string', 'format': 'uuid'}), + (dict, {'type': 'object'}), + (list, {'type': 'array', 'items': {}}), + (tuple, {'type': 'array', 'items': {}}), + (set, {'type': 'array', 'items': {}, 'uniqueItems': True}), + (frozenset, {'type': 'array', 'items': {}, 'uniqueItems': True}), +) + +json_scheme = {'type': 'string', 'format': 'json-string'} + + +def add_field_type_to_schema(field_type: Any, schema_: Dict[str, Any]) -> None: + """ + Update the given `schema` with the type-specific metadata for the given `field_type`. + + This function looks through `field_class_to_schema` for a class that matches the given `field_type`, + and then modifies the given `schema` with the information from that type. + """ + for type_, t_schema in field_class_to_schema: + # Fallback for `typing.Pattern` and `re.Pattern` as they are not a valid class + if lenient_issubclass(field_type, type_) or field_type is type_ is Pattern: + schema_.update(t_schema) + break + + +def get_schema_ref(name: str, ref_prefix: Optional[str], ref_template: str, schema_overrides: bool) -> Dict[str, Any]: + if ref_prefix: + schema_ref = {'$ref': ref_prefix + name} + else: + schema_ref = {'$ref': ref_template.format(model=name)} + return {'allOf': [schema_ref]} if schema_overrides else schema_ref + + +def field_singleton_schema( # noqa: C901 (ignore complexity) + field: ModelField, + *, + by_alias: bool, + model_name_map: Dict[TypeModelOrEnum, str], + ref_template: str, + schema_overrides: bool = False, + ref_prefix: Optional[str] = None, + known_models: TypeModelSet, +) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]: + """ + This function is indirectly used by ``field_schema()``, you should probably be using that function. + + Take a single Pydantic ``ModelField``, and return its schema and any additional definitions from sub-models. + """ + from pydantic.v1.main import BaseModel + + definitions: Dict[str, Any] = {} + nested_models: Set[str] = set() + field_type = field.type_ + + # Recurse into this field if it contains sub_fields and is NOT a + # BaseModel OR that BaseModel is a const + if field.sub_fields and ( + (field.field_info and field.field_info.const) or not lenient_issubclass(field_type, BaseModel) + ): + return field_singleton_sub_fields_schema( + field, + by_alias=by_alias, + model_name_map=model_name_map, + schema_overrides=schema_overrides, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + if field_type is Any or field_type is object or field_type.__class__ == TypeVar or get_origin(field_type) is type: + return {}, definitions, nested_models # no restrictions + if is_none_type(field_type): + return {'type': 'null'}, definitions, nested_models + if is_callable_type(field_type): + raise SkipField(f'Callable {field.name} was excluded from schema since JSON schema has no equivalent type.') + f_schema: Dict[str, Any] = {} + if field.field_info is not None and field.field_info.const: + f_schema['const'] = field.default + + if is_literal_type(field_type): + values = tuple(x.value if isinstance(x, Enum) else x for x in all_literal_values(field_type)) + + if len({v.__class__ for v in values}) > 1: + return field_schema( + multitypes_literal_field_for_schema(values, field), + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + ) + + # All values have the same type + field_type = values[0].__class__ + f_schema['enum'] = list(values) + add_field_type_to_schema(field_type, f_schema) + elif lenient_issubclass(field_type, Enum): + enum_name = model_name_map[field_type] + f_schema, schema_overrides = get_field_info_schema(field, schema_overrides) + f_schema.update(get_schema_ref(enum_name, ref_prefix, ref_template, schema_overrides)) + definitions[enum_name] = enum_process_schema(field_type, field=field) + elif is_namedtuple(field_type): + sub_schema, *_ = model_process_schema( + field_type.__pydantic_model__, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + field=field, + ) + items_schemas = list(sub_schema['properties'].values()) + f_schema.update( + { + 'type': 'array', + 'items': items_schemas, + 'minItems': len(items_schemas), + 'maxItems': len(items_schemas), + } + ) + elif not hasattr(field_type, '__pydantic_model__'): + add_field_type_to_schema(field_type, f_schema) + + modify_schema = getattr(field_type, '__modify_schema__', None) + if modify_schema: + _apply_modify_schema(modify_schema, field, f_schema) + + if f_schema: + return f_schema, definitions, nested_models + + # Handle dataclass-based models + if lenient_issubclass(getattr(field_type, '__pydantic_model__', None), BaseModel): + field_type = field_type.__pydantic_model__ + + if issubclass(field_type, BaseModel): + model_name = model_name_map[field_type] + if field_type not in known_models: + sub_schema, sub_definitions, sub_nested_models = model_process_schema( + field_type, + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + ref_template=ref_template, + known_models=known_models, + field=field, + ) + definitions.update(sub_definitions) + definitions[model_name] = sub_schema + nested_models.update(sub_nested_models) + else: + nested_models.add(model_name) + schema_ref = get_schema_ref(model_name, ref_prefix, ref_template, schema_overrides) + return schema_ref, definitions, nested_models + + # For generics with no args + args = get_args(field_type) + if args is not None and not args and Generic in field_type.__bases__: + return f_schema, definitions, nested_models + + raise ValueError(f'Value not declarable with JSON Schema, field: {field}') + + +def multitypes_literal_field_for_schema(values: Tuple[Any, ...], field: ModelField) -> ModelField: + """ + To support `Literal` with values of different types, we split it into multiple `Literal` with same type + e.g. `Literal['qwe', 'asd', 1, 2]` becomes `Union[Literal['qwe', 'asd'], Literal[1, 2]]` + """ + literal_distinct_types = defaultdict(list) + for v in values: + literal_distinct_types[v.__class__].append(v) + distinct_literals = (Literal[tuple(same_type_values)] for same_type_values in literal_distinct_types.values()) + + return ModelField( + name=field.name, + type_=Union[tuple(distinct_literals)], # type: ignore + class_validators=field.class_validators, + model_config=field.model_config, + default=field.default, + required=field.required, + alias=field.alias, + field_info=field.field_info, + ) + + +def encode_default(dft: Any) -> Any: + from pydantic.v1.main import BaseModel + + if isinstance(dft, BaseModel) or is_dataclass(dft): + dft = cast('dict[str, Any]', pydantic_encoder(dft)) + + if isinstance(dft, dict): + return {encode_default(k): encode_default(v) for k, v in dft.items()} + elif isinstance(dft, Enum): + return dft.value + elif isinstance(dft, (int, float, str)): + return dft + elif isinstance(dft, (list, tuple)): + t = dft.__class__ + seq_args = (encode_default(v) for v in dft) + return t(*seq_args) if is_namedtuple(t) else t(seq_args) + elif dft is None: + return None + else: + return pydantic_encoder(dft) + + +_map_types_constraint: Dict[Any, Callable[..., type]] = {int: conint, float: confloat, Decimal: condecimal} + + +def get_annotation_from_field_info( + annotation: Any, field_info: FieldInfo, field_name: str, validate_assignment: bool = False +) -> Type[Any]: + """ + Get an annotation with validation implemented for numbers and strings based on the field_info. + :param annotation: an annotation from a field specification, as ``str``, ``ConstrainedStr`` + :param field_info: an instance of FieldInfo, possibly with declarations for validations and JSON Schema + :param field_name: name of the field for use in error messages + :param validate_assignment: default False, flag for BaseModel Config value of validate_assignment + :return: the same ``annotation`` if unmodified or a new annotation with validation in place + """ + constraints = field_info.get_constraints() + used_constraints: Set[str] = set() + if constraints: + annotation, used_constraints = get_annotation_with_constraints(annotation, field_info) + if validate_assignment: + used_constraints.add('allow_mutation') + + unused_constraints = constraints - used_constraints + if unused_constraints: + raise ValueError( + f'On field "{field_name}" the following field constraints are set but not enforced: ' + f'{", ".join(unused_constraints)}. ' + f'\nFor more details see https://docs.pydantic.dev/usage/schema/#unenforced-field-constraints' + ) + + return annotation + + +def get_annotation_with_constraints(annotation: Any, field_info: FieldInfo) -> Tuple[Type[Any], Set[str]]: # noqa: C901 + """ + Get an annotation with used constraints implemented for numbers and strings based on the field_info. + + :param annotation: an annotation from a field specification, as ``str``, ``ConstrainedStr`` + :param field_info: an instance of FieldInfo, possibly with declarations for validations and JSON Schema + :return: the same ``annotation`` if unmodified or a new annotation along with the used constraints. + """ + used_constraints: Set[str] = set() + + def go(type_: Any) -> Type[Any]: + if ( + is_literal_type(type_) + or isinstance(type_, ForwardRef) + or lenient_issubclass(type_, (ConstrainedList, ConstrainedSet, ConstrainedFrozenSet)) + ): + return type_ + origin = get_origin(type_) + if origin is not None: + args: Tuple[Any, ...] = get_args(type_) + if any(isinstance(a, ForwardRef) for a in args): + # forward refs cause infinite recursion below + return type_ + + if origin is Annotated: + return go(args[0]) + if is_union(origin): + return Union[tuple(go(a) for a in args)] # type: ignore + + if issubclass(origin, List) and ( + field_info.min_items is not None + or field_info.max_items is not None + or field_info.unique_items is not None + ): + used_constraints.update({'min_items', 'max_items', 'unique_items'}) + return conlist( + go(args[0]), + min_items=field_info.min_items, + max_items=field_info.max_items, + unique_items=field_info.unique_items, + ) + + if issubclass(origin, Set) and (field_info.min_items is not None or field_info.max_items is not None): + used_constraints.update({'min_items', 'max_items'}) + return conset(go(args[0]), min_items=field_info.min_items, max_items=field_info.max_items) + + if issubclass(origin, FrozenSet) and (field_info.min_items is not None or field_info.max_items is not None): + used_constraints.update({'min_items', 'max_items'}) + return confrozenset(go(args[0]), min_items=field_info.min_items, max_items=field_info.max_items) + + for t in (Tuple, List, Set, FrozenSet, Sequence): + if issubclass(origin, t): # type: ignore + return t[tuple(go(a) for a in args)] # type: ignore + + if issubclass(origin, Dict): + return Dict[args[0], go(args[1])] # type: ignore + + attrs: Optional[Tuple[str, ...]] = None + constraint_func: Optional[Callable[..., type]] = None + if isinstance(type_, type): + if issubclass(type_, (SecretStr, SecretBytes)): + attrs = ('max_length', 'min_length') + + def constraint_func(**kw: Any) -> Type[Any]: # noqa: F811 + return type(type_.__name__, (type_,), kw) + + elif issubclass(type_, str) and not issubclass(type_, (EmailStr, AnyUrl)): + attrs = ('max_length', 'min_length', 'regex') + if issubclass(type_, StrictStr): + + def constraint_func(**kw: Any) -> Type[Any]: + return type(type_.__name__, (type_,), kw) + + else: + constraint_func = constr + elif issubclass(type_, bytes): + attrs = ('max_length', 'min_length', 'regex') + if issubclass(type_, StrictBytes): + + def constraint_func(**kw: Any) -> Type[Any]: + return type(type_.__name__, (type_,), kw) + + else: + constraint_func = conbytes + elif issubclass(type_, numeric_types) and not issubclass( + type_, + ( + ConstrainedInt, + ConstrainedFloat, + ConstrainedDecimal, + ConstrainedList, + ConstrainedSet, + ConstrainedFrozenSet, + bool, + ), + ): + # Is numeric type + attrs = ('gt', 'lt', 'ge', 'le', 'multiple_of') + if issubclass(type_, float): + attrs += ('allow_inf_nan',) + if issubclass(type_, Decimal): + attrs += ('max_digits', 'decimal_places') + numeric_type = next(t for t in numeric_types if issubclass(type_, t)) # pragma: no branch + constraint_func = _map_types_constraint[numeric_type] + + if attrs: + used_constraints.update(set(attrs)) + kwargs = { + attr_name: attr + for attr_name, attr in ((attr_name, getattr(field_info, attr_name)) for attr_name in attrs) + if attr is not None + } + if kwargs: + constraint_func = cast(Callable[..., type], constraint_func) + return constraint_func(**kwargs) + return type_ + + return go(annotation), used_constraints + + +def normalize_name(name: str) -> str: + """ + Normalizes the given name. This can be applied to either a model *or* enum. + """ + return re.sub(r'[^a-zA-Z0-9.\-_]', '_', name) + + +class SkipField(Exception): + """ + Utility exception used to exclude fields from schema. + """ + + def __init__(self, message: str) -> None: + self.message = message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py new file mode 100644 index 0000000..6838a23 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py @@ -0,0 +1,92 @@ +import json +from functools import lru_cache +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable, Optional, Type, TypeVar, Union + +from pydantic.v1.parse import Protocol, load_file, load_str_bytes +from pydantic.v1.types import StrBytes +from pydantic.v1.typing import display_as_type + +__all__ = ('parse_file_as', 'parse_obj_as', 'parse_raw_as', 'schema_of', 'schema_json_of') + +NameFactory = Union[str, Callable[[Type[Any]], str]] + +if TYPE_CHECKING: + from pydantic.v1.typing import DictStrAny + + +def _generate_parsing_type_name(type_: Any) -> str: + return f'ParsingModel[{display_as_type(type_)}]' + + +@lru_cache(maxsize=2048) +def _get_parsing_type(type_: Any, *, type_name: Optional[NameFactory] = None) -> Any: + from pydantic.v1.main import create_model + + if type_name is None: + type_name = _generate_parsing_type_name + if not isinstance(type_name, str): + type_name = type_name(type_) + return create_model(type_name, __root__=(type_, ...)) + + +T = TypeVar('T') + + +def parse_obj_as(type_: Type[T], obj: Any, *, type_name: Optional[NameFactory] = None) -> T: + model_type = _get_parsing_type(type_, type_name=type_name) # type: ignore[arg-type] + return model_type(__root__=obj).__root__ + + +def parse_file_as( + type_: Type[T], + path: Union[str, Path], + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, + type_name: Optional[NameFactory] = None, +) -> T: + obj = load_file( + path, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + json_loads=json_loads, + ) + return parse_obj_as(type_, obj, type_name=type_name) + + +def parse_raw_as( + type_: Type[T], + b: StrBytes, + *, + content_type: str = None, + encoding: str = 'utf8', + proto: Protocol = None, + allow_pickle: bool = False, + json_loads: Callable[[str], Any] = json.loads, + type_name: Optional[NameFactory] = None, +) -> T: + obj = load_str_bytes( + b, + proto=proto, + content_type=content_type, + encoding=encoding, + allow_pickle=allow_pickle, + json_loads=json_loads, + ) + return parse_obj_as(type_, obj, type_name=type_name) + + +def schema_of(type_: Any, *, title: Optional[NameFactory] = None, **schema_kwargs: Any) -> 'DictStrAny': + """Generate a JSON schema (as dict) for the passed model or dynamically generated one""" + return _get_parsing_type(type_, type_name=title).schema(**schema_kwargs) + + +def schema_json_of(type_: Any, *, title: Optional[NameFactory] = None, **schema_json_kwargs: Any) -> str: + """Generate a JSON schema (as JSON) for the passed model or dynamically generated one""" + return _get_parsing_type(type_, type_name=title).schema_json(**schema_json_kwargs) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py new file mode 100644 index 0000000..e1840d9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py @@ -0,0 +1,1205 @@ +import abc +import math +import re +import warnings +from datetime import date +from decimal import Decimal, InvalidOperation +from enum import Enum +from pathlib import Path +from types import new_class +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + FrozenSet, + List, + Optional, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, + overload, +) +from uuid import UUID +from weakref import WeakSet + +from pydantic.v1 import errors +from pydantic.v1.datetime_parse import parse_date +from pydantic.v1.utils import import_string, update_not_none +from pydantic.v1.validators import ( + bytes_validator, + constr_length_validator, + constr_lower, + constr_strip_whitespace, + constr_upper, + decimal_validator, + float_finite_validator, + float_validator, + frozenset_validator, + int_validator, + list_validator, + number_multiple_validator, + number_size_validator, + path_exists_validator, + path_validator, + set_validator, + str_validator, + strict_bytes_validator, + strict_float_validator, + strict_int_validator, + strict_str_validator, +) + +__all__ = [ + 'NoneStr', + 'NoneBytes', + 'StrBytes', + 'NoneStrBytes', + 'StrictStr', + 'ConstrainedBytes', + 'conbytes', + 'ConstrainedList', + 'conlist', + 'ConstrainedSet', + 'conset', + 'ConstrainedFrozenSet', + 'confrozenset', + 'ConstrainedStr', + 'constr', + 'PyObject', + 'ConstrainedInt', + 'conint', + 'PositiveInt', + 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', + 'ConstrainedFloat', + 'confloat', + 'PositiveFloat', + 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', + 'FiniteFloat', + 'ConstrainedDecimal', + 'condecimal', + 'UUID1', + 'UUID3', + 'UUID4', + 'UUID5', + 'FilePath', + 'DirectoryPath', + 'Json', + 'JsonWrapper', + 'SecretField', + 'SecretStr', + 'SecretBytes', + 'StrictBool', + 'StrictBytes', + 'StrictInt', + 'StrictFloat', + 'PaymentCardNumber', + 'ByteSize', + 'PastDate', + 'FutureDate', + 'ConstrainedDate', + 'condate', +] + +NoneStr = Optional[str] +NoneBytes = Optional[bytes] +StrBytes = Union[str, bytes] +NoneStrBytes = Optional[StrBytes] +OptionalInt = Optional[int] +OptionalIntFloat = Union[OptionalInt, float] +OptionalIntFloatDecimal = Union[OptionalIntFloat, Decimal] +OptionalDate = Optional[date] +StrIntFloat = Union[str, int, float] + +if TYPE_CHECKING: + from typing_extensions import Annotated + + from pydantic.v1.dataclasses import Dataclass + from pydantic.v1.main import BaseModel + from pydantic.v1.typing import CallableGenerator + + ModelOrDc = Type[Union[BaseModel, Dataclass]] + +T = TypeVar('T') +_DEFINED_TYPES: 'WeakSet[type]' = WeakSet() + + +@overload +def _registered(typ: Type[T]) -> Type[T]: + pass + + +@overload +def _registered(typ: 'ConstrainedNumberMeta') -> 'ConstrainedNumberMeta': + pass + + +def _registered(typ: Union[Type[T], 'ConstrainedNumberMeta']) -> Union[Type[T], 'ConstrainedNumberMeta']: + # In order to generate valid examples of constrained types, Hypothesis needs + # to inspect the type object - so we keep a weakref to each contype object + # until it can be registered. When (or if) our Hypothesis plugin is loaded, + # it monkeypatches this function. + # If Hypothesis is never used, the total effect is to keep a weak reference + # which has minimal memory usage and doesn't even affect garbage collection. + _DEFINED_TYPES.add(typ) + return typ + + +class ConstrainedNumberMeta(type): + def __new__(cls, name: str, bases: Any, dct: Dict[str, Any]) -> 'ConstrainedInt': # type: ignore + new_cls = cast('ConstrainedInt', type.__new__(cls, name, bases, dct)) + + if new_cls.gt is not None and new_cls.ge is not None: + raise errors.ConfigError('bounds gt and ge cannot be specified at the same time') + if new_cls.lt is not None and new_cls.le is not None: + raise errors.ConfigError('bounds lt and le cannot be specified at the same time') + + return _registered(new_cls) # type: ignore + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BOOLEAN TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + StrictBool = bool +else: + + class StrictBool(int): + """ + StrictBool to allow for bools which are not type-coerced. + """ + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='boolean') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: Any) -> bool: + """ + Ensure that we only allow bools. + """ + if isinstance(value, bool): + return value + + raise errors.StrictBoolError() + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INTEGER TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ConstrainedInt(int, metaclass=ConstrainedNumberMeta): + strict: bool = False + gt: OptionalInt = None + ge: OptionalInt = None + lt: OptionalInt = None + le: OptionalInt = None + multiple_of: OptionalInt = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + exclusiveMinimum=cls.gt, + exclusiveMaximum=cls.lt, + minimum=cls.ge, + maximum=cls.le, + multipleOf=cls.multiple_of, + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield strict_int_validator if cls.strict else int_validator + yield number_size_validator + yield number_multiple_validator + + +def conint( + *, + strict: bool = False, + gt: Optional[int] = None, + ge: Optional[int] = None, + lt: Optional[int] = None, + le: Optional[int] = None, + multiple_of: Optional[int] = None, +) -> Type[int]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of) + return type('ConstrainedIntValue', (ConstrainedInt,), namespace) + + +if TYPE_CHECKING: + PositiveInt = int + NegativeInt = int + NonPositiveInt = int + NonNegativeInt = int + StrictInt = int +else: + + class PositiveInt(ConstrainedInt): + gt = 0 + + class NegativeInt(ConstrainedInt): + lt = 0 + + class NonPositiveInt(ConstrainedInt): + le = 0 + + class NonNegativeInt(ConstrainedInt): + ge = 0 + + class StrictInt(ConstrainedInt): + strict = True + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOAT TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ConstrainedFloat(float, metaclass=ConstrainedNumberMeta): + strict: bool = False + gt: OptionalIntFloat = None + ge: OptionalIntFloat = None + lt: OptionalIntFloat = None + le: OptionalIntFloat = None + multiple_of: OptionalIntFloat = None + allow_inf_nan: Optional[bool] = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + exclusiveMinimum=cls.gt, + exclusiveMaximum=cls.lt, + minimum=cls.ge, + maximum=cls.le, + multipleOf=cls.multiple_of, + ) + # Modify constraints to account for differences between IEEE floats and JSON + if field_schema.get('exclusiveMinimum') == -math.inf: + del field_schema['exclusiveMinimum'] + if field_schema.get('minimum') == -math.inf: + del field_schema['minimum'] + if field_schema.get('exclusiveMaximum') == math.inf: + del field_schema['exclusiveMaximum'] + if field_schema.get('maximum') == math.inf: + del field_schema['maximum'] + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield strict_float_validator if cls.strict else float_validator + yield number_size_validator + yield number_multiple_validator + yield float_finite_validator + + +def confloat( + *, + strict: bool = False, + gt: float = None, + ge: float = None, + lt: float = None, + le: float = None, + multiple_of: float = None, + allow_inf_nan: Optional[bool] = None, +) -> Type[float]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict(strict=strict, gt=gt, ge=ge, lt=lt, le=le, multiple_of=multiple_of, allow_inf_nan=allow_inf_nan) + return type('ConstrainedFloatValue', (ConstrainedFloat,), namespace) + + +if TYPE_CHECKING: + PositiveFloat = float + NegativeFloat = float + NonPositiveFloat = float + NonNegativeFloat = float + StrictFloat = float + FiniteFloat = float +else: + + class PositiveFloat(ConstrainedFloat): + gt = 0 + + class NegativeFloat(ConstrainedFloat): + lt = 0 + + class NonPositiveFloat(ConstrainedFloat): + le = 0 + + class NonNegativeFloat(ConstrainedFloat): + ge = 0 + + class StrictFloat(ConstrainedFloat): + strict = True + + class FiniteFloat(ConstrainedFloat): + allow_inf_nan = False + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTES TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ConstrainedBytes(bytes): + strip_whitespace = False + to_upper = False + to_lower = False + min_length: OptionalInt = None + max_length: OptionalInt = None + strict: bool = False + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, minLength=cls.min_length, maxLength=cls.max_length) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield strict_bytes_validator if cls.strict else bytes_validator + yield constr_strip_whitespace + yield constr_upper + yield constr_lower + yield constr_length_validator + + +def conbytes( + *, + strip_whitespace: bool = False, + to_upper: bool = False, + to_lower: bool = False, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + strict: bool = False, +) -> Type[bytes]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict( + strip_whitespace=strip_whitespace, + to_upper=to_upper, + to_lower=to_lower, + min_length=min_length, + max_length=max_length, + strict=strict, + ) + return _registered(type('ConstrainedBytesValue', (ConstrainedBytes,), namespace)) + + +if TYPE_CHECKING: + StrictBytes = bytes +else: + + class StrictBytes(ConstrainedBytes): + strict = True + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ STRING TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ConstrainedStr(str): + strip_whitespace = False + to_upper = False + to_lower = False + min_length: OptionalInt = None + max_length: OptionalInt = None + curtail_length: OptionalInt = None + regex: Optional[Union[str, Pattern[str]]] = None + strict = False + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + minLength=cls.min_length, + maxLength=cls.max_length, + pattern=cls.regex and cls._get_pattern(cls.regex), + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield strict_str_validator if cls.strict else str_validator + yield constr_strip_whitespace + yield constr_upper + yield constr_lower + yield constr_length_validator + yield cls.validate + + @classmethod + def validate(cls, value: Union[str]) -> Union[str]: + if cls.curtail_length and len(value) > cls.curtail_length: + value = value[: cls.curtail_length] + + if cls.regex: + if not re.match(cls.regex, value): + raise errors.StrRegexError(pattern=cls._get_pattern(cls.regex)) + + return value + + @staticmethod + def _get_pattern(regex: Union[str, Pattern[str]]) -> str: + return regex if isinstance(regex, str) else regex.pattern + + +def constr( + *, + strip_whitespace: bool = False, + to_upper: bool = False, + to_lower: bool = False, + strict: bool = False, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + curtail_length: Optional[int] = None, + regex: Optional[str] = None, +) -> Type[str]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict( + strip_whitespace=strip_whitespace, + to_upper=to_upper, + to_lower=to_lower, + strict=strict, + min_length=min_length, + max_length=max_length, + curtail_length=curtail_length, + regex=regex and re.compile(regex), + ) + return _registered(type('ConstrainedStrValue', (ConstrainedStr,), namespace)) + + +if TYPE_CHECKING: + StrictStr = str +else: + + class StrictStr(ConstrainedStr): + strict = True + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +# This types superclass should be Set[T], but cython chokes on that... +class ConstrainedSet(set): # type: ignore + # Needed for pydantic to detect that this is a set + __origin__ = set + __args__: Set[Type[T]] # type: ignore + + min_items: Optional[int] = None + max_items: Optional[int] = None + item_type: Type[T] # type: ignore + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.set_length_validator + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items) + + @classmethod + def set_length_validator(cls, v: 'Optional[Set[T]]') -> 'Optional[Set[T]]': + if v is None: + return None + + v = set_validator(v) + v_len = len(v) + + if cls.min_items is not None and v_len < cls.min_items: + raise errors.SetMinLengthError(limit_value=cls.min_items) + + if cls.max_items is not None and v_len > cls.max_items: + raise errors.SetMaxLengthError(limit_value=cls.max_items) + + return v + + +def conset(item_type: Type[T], *, min_items: Optional[int] = None, max_items: Optional[int] = None) -> Type[Set[T]]: + # __args__ is needed to conform to typing generics api + namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': [item_type]} + # We use new_class to be able to deal with Generic types + return new_class('ConstrainedSetValue', (ConstrainedSet,), {}, lambda ns: ns.update(namespace)) + + +# This types superclass should be FrozenSet[T], but cython chokes on that... +class ConstrainedFrozenSet(frozenset): # type: ignore + # Needed for pydantic to detect that this is a set + __origin__ = frozenset + __args__: FrozenSet[Type[T]] # type: ignore + + min_items: Optional[int] = None + max_items: Optional[int] = None + item_type: Type[T] # type: ignore + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.frozenset_length_validator + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items) + + @classmethod + def frozenset_length_validator(cls, v: 'Optional[FrozenSet[T]]') -> 'Optional[FrozenSet[T]]': + if v is None: + return None + + v = frozenset_validator(v) + v_len = len(v) + + if cls.min_items is not None and v_len < cls.min_items: + raise errors.FrozenSetMinLengthError(limit_value=cls.min_items) + + if cls.max_items is not None and v_len > cls.max_items: + raise errors.FrozenSetMaxLengthError(limit_value=cls.max_items) + + return v + + +def confrozenset( + item_type: Type[T], *, min_items: Optional[int] = None, max_items: Optional[int] = None +) -> Type[FrozenSet[T]]: + # __args__ is needed to conform to typing generics api + namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': [item_type]} + # We use new_class to be able to deal with Generic types + return new_class('ConstrainedFrozenSetValue', (ConstrainedFrozenSet,), {}, lambda ns: ns.update(namespace)) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LIST TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +# This types superclass should be List[T], but cython chokes on that... +class ConstrainedList(list): # type: ignore + # Needed for pydantic to detect that this is a list + __origin__ = list + __args__: Tuple[Type[T], ...] # type: ignore + + min_items: Optional[int] = None + max_items: Optional[int] = None + unique_items: Optional[bool] = None + item_type: Type[T] # type: ignore + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.list_length_validator + if cls.unique_items: + yield cls.unique_items_validator + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items, uniqueItems=cls.unique_items) + + @classmethod + def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]': + if v is None: + return None + + v = list_validator(v) + v_len = len(v) + + if cls.min_items is not None and v_len < cls.min_items: + raise errors.ListMinLengthError(limit_value=cls.min_items) + + if cls.max_items is not None and v_len > cls.max_items: + raise errors.ListMaxLengthError(limit_value=cls.max_items) + + return v + + @classmethod + def unique_items_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]': + if v is None: + return None + + for i, value in enumerate(v, start=1): + if value in v[i:]: + raise errors.ListUniqueItemsError() + + return v + + +def conlist( + item_type: Type[T], *, min_items: Optional[int] = None, max_items: Optional[int] = None, unique_items: bool = None +) -> Type[List[T]]: + # __args__ is needed to conform to typing generics api + namespace = dict( + min_items=min_items, max_items=max_items, unique_items=unique_items, item_type=item_type, __args__=(item_type,) + ) + # We use new_class to be able to deal with Generic types + return new_class('ConstrainedListValue', (ConstrainedList,), {}, lambda ns: ns.update(namespace)) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PYOBJECT TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +if TYPE_CHECKING: + PyObject = Callable[..., Any] +else: + + class PyObject: + validate_always = True + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, value: Any) -> Any: + if isinstance(value, Callable): + return value + + try: + value = str_validator(value) + except errors.StrError: + raise errors.PyObjectError(error_message='value is neither a valid import path not a valid callable') + + try: + return import_string(value) + except ImportError as e: + raise errors.PyObjectError(error_message=str(e)) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DECIMAL TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class ConstrainedDecimal(Decimal, metaclass=ConstrainedNumberMeta): + gt: OptionalIntFloatDecimal = None + ge: OptionalIntFloatDecimal = None + lt: OptionalIntFloatDecimal = None + le: OptionalIntFloatDecimal = None + max_digits: OptionalInt = None + decimal_places: OptionalInt = None + multiple_of: OptionalIntFloatDecimal = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + exclusiveMinimum=cls.gt, + exclusiveMaximum=cls.lt, + minimum=cls.ge, + maximum=cls.le, + multipleOf=cls.multiple_of, + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield decimal_validator + yield number_size_validator + yield number_multiple_validator + yield cls.validate + + @classmethod + def validate(cls, value: Decimal) -> Decimal: + try: + normalized_value = value.normalize() + except InvalidOperation: + normalized_value = value + digit_tuple, exponent = normalized_value.as_tuple()[1:] + if exponent in {'F', 'n', 'N'}: + raise errors.DecimalIsNotFiniteError() + + if exponent >= 0: + # A positive exponent adds that many trailing zeros. + digits = len(digit_tuple) + exponent + decimals = 0 + else: + # If the absolute value of the negative exponent is larger than the + # number of digits, then it's the same as the number of digits, + # because it'll consume all of the digits in digit_tuple and then + # add abs(exponent) - len(digit_tuple) leading zeros after the + # decimal point. + if abs(exponent) > len(digit_tuple): + digits = decimals = abs(exponent) + else: + digits = len(digit_tuple) + decimals = abs(exponent) + whole_digits = digits - decimals + + if cls.max_digits is not None and digits > cls.max_digits: + raise errors.DecimalMaxDigitsError(max_digits=cls.max_digits) + + if cls.decimal_places is not None and decimals > cls.decimal_places: + raise errors.DecimalMaxPlacesError(decimal_places=cls.decimal_places) + + if cls.max_digits is not None and cls.decimal_places is not None: + expected = cls.max_digits - cls.decimal_places + if whole_digits > expected: + raise errors.DecimalWholeDigitsError(whole_digits=expected) + + return value + + +def condecimal( + *, + gt: Decimal = None, + ge: Decimal = None, + lt: Decimal = None, + le: Decimal = None, + max_digits: Optional[int] = None, + decimal_places: Optional[int] = None, + multiple_of: Decimal = None, +) -> Type[Decimal]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict( + gt=gt, ge=ge, lt=lt, le=le, max_digits=max_digits, decimal_places=decimal_places, multiple_of=multiple_of + ) + return type('ConstrainedDecimalValue', (ConstrainedDecimal,), namespace) + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UUID TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + UUID1 = UUID + UUID3 = UUID + UUID4 = UUID + UUID5 = UUID +else: + + class UUID1(UUID): + _required_version = 1 + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format=f'uuid{cls._required_version}') + + class UUID3(UUID1): + _required_version = 3 + + class UUID4(UUID1): + _required_version = 4 + + class UUID5(UUID1): + _required_version = 5 + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PATH TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + FilePath = Path + DirectoryPath = Path +else: + + class FilePath(Path): + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(format='file-path') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield path_validator + yield path_exists_validator + yield cls.validate + + @classmethod + def validate(cls, value: Path) -> Path: + if not value.is_file(): + raise errors.PathNotAFileError(path=value) + + return value + + class DirectoryPath(Path): + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(format='directory-path') + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield path_validator + yield path_exists_validator + yield cls.validate + + @classmethod + def validate(cls, value: Path) -> Path: + if not value.is_dir(): + raise errors.PathNotADirectoryError(path=value) + + return value + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class JsonWrapper: + pass + + +class JsonMeta(type): + def __getitem__(self, t: Type[Any]) -> Type[JsonWrapper]: + if t is Any: + return Json # allow Json[Any] to replicate plain Json + return _registered(type('JsonWrapperValue', (JsonWrapper,), {'inner_type': t})) + + +if TYPE_CHECKING: + Json = Annotated[T, ...] # Json[list[str]] will be recognized by type checkers as list[str] + +else: + + class Json(metaclass=JsonMeta): + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update(type='string', format='json-string') + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SECRET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class SecretField(abc.ABC): + """ + Note: this should be implemented as a generic like `SecretField(ABC, Generic[T])`, + the `__init__()` should be part of the abstract class and the + `get_secret_value()` method should use the generic `T` type. + + However Cython doesn't support very well generics at the moment and + the generated code fails to be imported (see + https://github.com/cython/cython/issues/2753). + """ + + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) and self.get_secret_value() == other.get_secret_value() + + def __str__(self) -> str: + return '**********' if self.get_secret_value() else '' + + def __hash__(self) -> int: + return hash(self.get_secret_value()) + + @abc.abstractmethod + def get_secret_value(self) -> Any: # pragma: no cover + ... + + +class SecretStr(SecretField): + min_length: OptionalInt = None + max_length: OptionalInt = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + type='string', + writeOnly=True, + format='password', + minLength=cls.min_length, + maxLength=cls.max_length, + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + yield constr_length_validator + + @classmethod + def validate(cls, value: Any) -> 'SecretStr': + if isinstance(value, cls): + return value + value = str_validator(value) + return cls(value) + + def __init__(self, value: str): + self._secret_value = value + + def __repr__(self) -> str: + return f"SecretStr('{self}')" + + def __len__(self) -> int: + return len(self._secret_value) + + def display(self) -> str: + warnings.warn('`secret_str.display()` is deprecated, use `str(secret_str)` instead', DeprecationWarning) + return str(self) + + def get_secret_value(self) -> str: + return self._secret_value + + +class SecretBytes(SecretField): + min_length: OptionalInt = None + max_length: OptionalInt = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none( + field_schema, + type='string', + writeOnly=True, + format='password', + minLength=cls.min_length, + maxLength=cls.max_length, + ) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + yield constr_length_validator + + @classmethod + def validate(cls, value: Any) -> 'SecretBytes': + if isinstance(value, cls): + return value + value = bytes_validator(value) + return cls(value) + + def __init__(self, value: bytes): + self._secret_value = value + + def __repr__(self) -> str: + return f"SecretBytes(b'{self}')" + + def __len__(self) -> int: + return len(self._secret_value) + + def display(self) -> str: + warnings.warn('`secret_bytes.display()` is deprecated, use `str(secret_bytes)` instead', DeprecationWarning) + return str(self) + + def get_secret_value(self) -> bytes: + return self._secret_value + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PAYMENT CARD TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +class PaymentCardBrand(str, Enum): + # If you add another card type, please also add it to the + # Hypothesis strategy in `pydantic._hypothesis_plugin`. + amex = 'American Express' + mastercard = 'Mastercard' + visa = 'Visa' + other = 'other' + + def __str__(self) -> str: + return self.value + + +class PaymentCardNumber(str): + """ + Based on: https://en.wikipedia.org/wiki/Payment_card_number + """ + + strip_whitespace: ClassVar[bool] = True + min_length: ClassVar[int] = 12 + max_length: ClassVar[int] = 19 + bin: str + last4: str + brand: PaymentCardBrand + + def __init__(self, card_number: str): + self.bin = card_number[:6] + self.last4 = card_number[-4:] + self.brand = self._get_brand(card_number) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield str_validator + yield constr_strip_whitespace + yield constr_length_validator + yield cls.validate_digits + yield cls.validate_luhn_check_digit + yield cls + yield cls.validate_length_for_brand + + @property + def masked(self) -> str: + num_masked = len(self) - 10 # len(bin) + len(last4) == 10 + return f'{self.bin}{"*" * num_masked}{self.last4}' + + @classmethod + def validate_digits(cls, card_number: str) -> str: + if not card_number.isdigit(): + raise errors.NotDigitError + return card_number + + @classmethod + def validate_luhn_check_digit(cls, card_number: str) -> str: + """ + Based on: https://en.wikipedia.org/wiki/Luhn_algorithm + """ + sum_ = int(card_number[-1]) + length = len(card_number) + parity = length % 2 + for i in range(length - 1): + digit = int(card_number[i]) + if i % 2 == parity: + digit *= 2 + if digit > 9: + digit -= 9 + sum_ += digit + valid = sum_ % 10 == 0 + if not valid: + raise errors.LuhnValidationError + return card_number + + @classmethod + def validate_length_for_brand(cls, card_number: 'PaymentCardNumber') -> 'PaymentCardNumber': + """ + Validate length based on BIN for major brands: + https://en.wikipedia.org/wiki/Payment_card_number#Issuer_identification_number_(IIN) + """ + required_length: Union[None, int, str] = None + if card_number.brand in PaymentCardBrand.mastercard: + required_length = 16 + valid = len(card_number) == required_length + elif card_number.brand == PaymentCardBrand.visa: + required_length = '13, 16 or 19' + valid = len(card_number) in {13, 16, 19} + elif card_number.brand == PaymentCardBrand.amex: + required_length = 15 + valid = len(card_number) == required_length + else: + valid = True + if not valid: + raise errors.InvalidLengthForBrand(brand=card_number.brand, required_length=required_length) + return card_number + + @staticmethod + def _get_brand(card_number: str) -> PaymentCardBrand: + if card_number[0] == '4': + brand = PaymentCardBrand.visa + elif 51 <= int(card_number[:2]) <= 55: + brand = PaymentCardBrand.mastercard + elif card_number[:2] in {'34', '37'}: + brand = PaymentCardBrand.amex + else: + brand = PaymentCardBrand.other + return brand + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTE SIZE TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +BYTE_SIZES = { + 'b': 1, + 'kb': 10**3, + 'mb': 10**6, + 'gb': 10**9, + 'tb': 10**12, + 'pb': 10**15, + 'eb': 10**18, + 'kib': 2**10, + 'mib': 2**20, + 'gib': 2**30, + 'tib': 2**40, + 'pib': 2**50, + 'eib': 2**60, +} +BYTE_SIZES.update({k.lower()[0]: v for k, v in BYTE_SIZES.items() if 'i' not in k}) +byte_string_re = re.compile(r'^\s*(\d*\.?\d+)\s*(\w+)?', re.IGNORECASE) + + +class ByteSize(int): + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield cls.validate + + @classmethod + def validate(cls, v: StrIntFloat) -> 'ByteSize': + try: + return cls(int(v)) + except ValueError: + pass + + str_match = byte_string_re.match(str(v)) + if str_match is None: + raise errors.InvalidByteSize() + + scalar, unit = str_match.groups() + if unit is None: + unit = 'b' + + try: + unit_mult = BYTE_SIZES[unit.lower()] + except KeyError: + raise errors.InvalidByteSizeUnit(unit=unit) + + return cls(int(float(scalar) * unit_mult)) + + def human_readable(self, decimal: bool = False) -> str: + if decimal: + divisor = 1000 + units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + final_unit = 'EB' + else: + divisor = 1024 + units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'] + final_unit = 'EiB' + + num = float(self) + for unit in units: + if abs(num) < divisor: + return f'{num:0.1f}{unit}' + num /= divisor + + return f'{num:0.1f}{final_unit}' + + def to(self, unit: str) -> float: + try: + unit_div = BYTE_SIZES[unit.lower()] + except KeyError: + raise errors.InvalidByteSizeUnit(unit=unit) + + return self / unit_div + + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DATE TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if TYPE_CHECKING: + PastDate = date + FutureDate = date +else: + + class PastDate(date): + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield parse_date + yield cls.validate + + @classmethod + def validate(cls, value: date) -> date: + if value >= date.today(): + raise errors.DateNotInThePastError() + + return value + + class FutureDate(date): + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield parse_date + yield cls.validate + + @classmethod + def validate(cls, value: date) -> date: + if value <= date.today(): + raise errors.DateNotInTheFutureError() + + return value + + +class ConstrainedDate(date, metaclass=ConstrainedNumberMeta): + gt: OptionalDate = None + ge: OptionalDate = None + lt: OptionalDate = None + le: OptionalDate = None + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + update_not_none(field_schema, exclusiveMinimum=cls.gt, exclusiveMaximum=cls.lt, minimum=cls.ge, maximum=cls.le) + + @classmethod + def __get_validators__(cls) -> 'CallableGenerator': + yield parse_date + yield number_size_validator + + +def condate( + *, + gt: date = None, + ge: date = None, + lt: date = None, + le: date = None, +) -> Type[date]: + # use kwargs then define conf in a dict to aid with IDE type hinting + namespace = dict(gt=gt, ge=ge, lt=lt, le=le) + return type('ConstrainedDateValue', (ConstrainedDate,), namespace) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py new file mode 100644 index 0000000..c5c5979 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py @@ -0,0 +1,627 @@ +import functools +import operator +import sys +import typing +from collections.abc import Callable +from os import PathLike +from typing import ( # type: ignore + TYPE_CHECKING, + AbstractSet, + Any, + Callable as TypingCallable, + ClassVar, + Dict, + ForwardRef, + Generator, + Iterable, + List, + Mapping, + NewType, + Optional, + Sequence, + Set, + Tuple, + Type, + TypeVar, + Union, + _eval_type, + cast, + get_type_hints, +) + +from typing_extensions import ( + Annotated, + Final, + Literal, + NotRequired as TypedDictNotRequired, + Required as TypedDictRequired, +) + +try: + from typing import _TypingBase as typing_base # type: ignore +except ImportError: + from typing import _Final as typing_base # type: ignore + +try: + from typing import GenericAlias as TypingGenericAlias # type: ignore +except ImportError: + # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on) + TypingGenericAlias = () + +try: + from types import UnionType as TypesUnionType # type: ignore +except ImportError: + # python < 3.10 does not have UnionType (str | int, byte | bool and so on) + TypesUnionType = () + + +if sys.version_info < (3, 9): + + def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: + return type_._evaluate(globalns, localns) + +elif sys.version_info < (3, 12, 4): + + def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: + # Even though it is the right signature for python 3.9, mypy complains with + # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast... + # Python 3.13/3.12.4+ made `recursive_guard` a kwarg, so name it explicitly to avoid: + # TypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard' + return cast(Any, type_)._evaluate(globalns, localns, recursive_guard=set()) + +elif sys.version_info < (3, 14): + + def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: + # Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid + # warnings: + return cast(Any, type_)._evaluate(globalns, localns, type_params=(), recursive_guard=set()) + +else: + + def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: + # Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid + # warnings: + return typing.evaluate_forward_ref( + type_, + globals=globalns, + locals=localns, + type_params=(), + _recursive_guard=set(), + ) + + +if sys.version_info < (3, 9): + # Ensure we always get all the whole `Annotated` hint, not just the annotated type. + # For 3.7 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`, + # so it already returns the full annotation + get_all_type_hints = get_type_hints + +else: + + def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> Any: + return get_type_hints(obj, globalns, localns, include_extras=True) + + +_T = TypeVar('_T') + +AnyCallable = TypingCallable[..., Any] +NoArgAnyCallable = TypingCallable[[], Any] + +# workaround for https://github.com/python/mypy/issues/9496 +AnyArgTCallable = TypingCallable[..., _T] + + +# Annotated[...] is implemented by returning an instance of one of these classes, depending on +# python/typing_extensions version. +AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'} + + +LITERAL_TYPES: Set[Any] = {Literal} +if hasattr(typing, 'Literal'): + LITERAL_TYPES.add(typing.Literal) + + +if sys.version_info < (3, 8): + + def get_origin(t: Type[Any]) -> Optional[Type[Any]]: + if type(t).__name__ in AnnotatedTypeNames: + # weirdly this is a runtime requirement, as well as for mypy + return cast(Type[Any], Annotated) + return getattr(t, '__origin__', None) + +else: + from typing import get_origin as _typing_get_origin + + def get_origin(tp: Type[Any]) -> Optional[Type[Any]]: + """ + We can't directly use `typing.get_origin` since we need a fallback to support + custom generic classes like `ConstrainedList` + It should be useless once https://github.com/cython/cython/issues/3537 is + solved and https://github.com/pydantic/pydantic/pull/1753 is merged. + """ + if type(tp).__name__ in AnnotatedTypeNames: + return cast(Type[Any], Annotated) # mypy complains about _SpecialForm + return _typing_get_origin(tp) or getattr(tp, '__origin__', None) + + +if sys.version_info < (3, 8): + from typing import _GenericAlias + + def get_args(t: Type[Any]) -> Tuple[Any, ...]: + """Compatibility version of get_args for python 3.7. + + Mostly compatible with the python 3.8 `typing` module version + and able to handle almost all use cases. + """ + if type(t).__name__ in AnnotatedTypeNames: + return t.__args__ + t.__metadata__ + if isinstance(t, _GenericAlias): + res = t.__args__ + if t.__origin__ is Callable and res and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return getattr(t, '__args__', ()) + +else: + from typing import get_args as _typing_get_args + + def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: + """ + In python 3.9, `typing.Dict`, `typing.List`, ... + do have an empty `__args__` by default (instead of the generic ~T for example). + In order to still support `Dict` for example and consider it as `Dict[Any, Any]`, + we retrieve the `_nparams` value that tells us how many parameters it needs. + """ + if hasattr(tp, '_nparams'): + return (Any,) * tp._nparams + # Special case for `tuple[()]`, which used to return ((),) with `typing.Tuple` + # in python 3.10- but now returns () for `tuple` and `Tuple`. + # This will probably be clarified in pydantic v2 + try: + if tp == Tuple[()] or sys.version_info >= (3, 9) and tp == tuple[()]: # type: ignore[misc] + return ((),) + # there is a TypeError when compiled with cython + except TypeError: # pragma: no cover + pass + return () + + def get_args(tp: Type[Any]) -> Tuple[Any, ...]: + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if type(tp).__name__ in AnnotatedTypeNames: + return tp.__args__ + tp.__metadata__ + # the fallback is needed for the same reasons as `get_origin` (see above) + return _typing_get_args(tp) or getattr(tp, '__args__', ()) or _generic_get_args(tp) + + +if sys.version_info < (3, 9): + + def convert_generics(tp: Type[Any]) -> Type[Any]: + """Python 3.9 and older only supports generics from `typing` module. + They convert strings to ForwardRef automatically. + + Examples:: + typing.List['Hero'] == typing.List[ForwardRef('Hero')] + """ + return tp + +else: + + def convert_generics(tp: Type[Any]) -> Type[Any]: + """ + Recursively searches for `str` type hints and replaces them with ForwardRef. + + Examples:: + convert_generics(list['Hero']) == list[ForwardRef('Hero')] + convert_generics(dict['Hero', 'Team']) == dict[ForwardRef('Hero'), ForwardRef('Team')] + convert_generics(typing.Dict['Hero', 'Team']) == typing.Dict[ForwardRef('Hero'), ForwardRef('Team')] + convert_generics(list[str | 'Hero'] | int) == list[str | ForwardRef('Hero')] | int + """ + origin = get_origin(tp) + if not origin or not hasattr(tp, '__args__'): + return tp + + args = get_args(tp) + + # typing.Annotated needs special treatment + if origin is Annotated: + return Annotated[(convert_generics(args[0]), *args[1:])] # type: ignore + + # recursively replace `str` instances inside of `GenericAlias` with `ForwardRef(arg)` + converted = tuple( + ForwardRef(arg) if isinstance(arg, str) and isinstance(tp, TypingGenericAlias) else convert_generics(arg) + for arg in args + ) + + if converted == args: + return tp + elif isinstance(tp, TypingGenericAlias): + return TypingGenericAlias(origin, converted) + elif isinstance(tp, TypesUnionType): + # recreate types.UnionType (PEP604, Python >= 3.10) + return functools.reduce(operator.or_, converted) # type: ignore + else: + try: + setattr(tp, '__args__', converted) + except AttributeError: + pass + return tp + + +if sys.version_info < (3, 10): + + def is_union(tp: Optional[Type[Any]]) -> bool: + return tp is Union + + WithArgsTypes = (TypingGenericAlias,) + +else: + import types + import typing + + def is_union(tp: Optional[Type[Any]]) -> bool: + return tp is Union or tp is types.UnionType # noqa: E721 + + WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType) + + +StrPath = Union[str, PathLike] + + +if TYPE_CHECKING: + from pydantic.v1.fields import ModelField + + TupleGenerator = Generator[Tuple[str, Any], None, None] + DictStrAny = Dict[str, Any] + DictAny = Dict[Any, Any] + SetStr = Set[str] + ListStr = List[str] + IntStr = Union[int, str] + AbstractSetIntStr = AbstractSet[IntStr] + DictIntStrAny = Dict[IntStr, Any] + MappingIntStrAny = Mapping[IntStr, Any] + CallableGenerator = Generator[AnyCallable, None, None] + ReprArgs = Sequence[Tuple[Optional[str], Any]] + + MYPY = False + if MYPY: + AnyClassMethod = classmethod[Any] + else: + # classmethod[TargetType, CallableParamSpecType, CallableReturnType] + AnyClassMethod = classmethod[Any, Any, Any] + +__all__ = ( + 'AnyCallable', + 'NoArgAnyCallable', + 'NoneType', + 'is_none_type', + 'display_as_type', + 'resolve_annotations', + 'is_callable_type', + 'is_literal_type', + 'all_literal_values', + 'is_namedtuple', + 'is_typeddict', + 'is_typeddict_special', + 'is_new_type', + 'new_type_supertype', + 'is_classvar', + 'is_finalvar', + 'update_field_forward_refs', + 'update_model_forward_refs', + 'TupleGenerator', + 'DictStrAny', + 'DictAny', + 'SetStr', + 'ListStr', + 'IntStr', + 'AbstractSetIntStr', + 'DictIntStrAny', + 'CallableGenerator', + 'ReprArgs', + 'AnyClassMethod', + 'CallableGenerator', + 'WithArgsTypes', + 'get_args', + 'get_origin', + 'get_sub_types', + 'typing_base', + 'get_all_type_hints', + 'is_union', + 'StrPath', + 'MappingIntStrAny', +) + + +NoneType = None.__class__ + + +NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None]) + + +if sys.version_info < (3, 8): + # Even though this implementation is slower, we need it for python 3.7: + # In python 3.7 "Literal" is not a builtin type and uses a different + # mechanism. + # for this reason `Literal[None] is Literal[None]` evaluates to `False`, + # breaking the faster implementation used for the other python versions. + + def is_none_type(type_: Any) -> bool: + return type_ in NONE_TYPES + +elif sys.version_info[:2] == (3, 8): + + def is_none_type(type_: Any) -> bool: + for none_type in NONE_TYPES: + if type_ is none_type: + return True + # With python 3.8, specifically 3.8.10, Literal "is" check sare very flakey + # can change on very subtle changes like use of types in other modules, + # hopefully this check avoids that issue. + if is_literal_type(type_): # pragma: no cover + return all_literal_values(type_) == (None,) + return False + +else: + + def is_none_type(type_: Any) -> bool: + return type_ in NONE_TYPES + + +def display_as_type(v: Type[Any]) -> str: + if not isinstance(v, typing_base) and not isinstance(v, WithArgsTypes) and not isinstance(v, type): + v = v.__class__ + + if is_union(get_origin(v)): + return f'Union[{", ".join(map(display_as_type, get_args(v)))}]' + + if isinstance(v, WithArgsTypes): + # Generic alias are constructs like `list[int]` + return str(v).replace('typing.', '') + + try: + return v.__name__ + except AttributeError: + # happens with typing objects + return str(v).replace('typing.', '') + + +def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]: + """ + Partially taken from typing.get_type_hints. + + Resolve string or ForwardRef annotations into type objects if possible. + """ + base_globals: Optional[Dict[str, Any]] = None + if module_name: + try: + module = sys.modules[module_name] + except KeyError: + # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 + pass + else: + base_globals = module.__dict__ + + annotations = {} + for name, value in raw_annotations.items(): + if isinstance(value, str): + if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= (3, 10, 1): + value = ForwardRef(value, is_argument=False, is_class=True) + else: + value = ForwardRef(value, is_argument=False) + try: + if sys.version_info >= (3, 13): + value = _eval_type(value, base_globals, None, type_params=()) + else: + value = _eval_type(value, base_globals, None) + except NameError: + # this is ok, it can be fixed with update_forward_refs + pass + annotations[name] = value + return annotations + + +def is_callable_type(type_: Type[Any]) -> bool: + return type_ is Callable or get_origin(type_) is Callable + + +def is_literal_type(type_: Type[Any]) -> bool: + return Literal is not None and get_origin(type_) in LITERAL_TYPES + + +def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: + return get_args(type_) + + +def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: + """ + This method is used to retrieve all Literal values as + Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) + e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]` + """ + if not is_literal_type(type_): + return (type_,) + + values = literal_values(type_) + return tuple(x for value in values for x in all_literal_values(value)) + + +def is_namedtuple(type_: Type[Any]) -> bool: + """ + Check if a given class is a named tuple. + It can be either a `typing.NamedTuple` or `collections.namedtuple` + """ + from pydantic.v1.utils import lenient_issubclass + + return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') + + +def is_typeddict(type_: Type[Any]) -> bool: + """ + Check if a given class is a typed dict (from `typing` or `typing_extensions`) + In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict) + """ + from pydantic.v1.utils import lenient_issubclass + + return lenient_issubclass(type_, dict) and hasattr(type_, '__total__') + + +def _check_typeddict_special(type_: Any) -> bool: + return type_ is TypedDictRequired or type_ is TypedDictNotRequired + + +def is_typeddict_special(type_: Any) -> bool: + """ + Check if type is a TypedDict special form (Required or NotRequired). + """ + return _check_typeddict_special(type_) or _check_typeddict_special(get_origin(type_)) + + +test_type = NewType('test_type', str) + + +def is_new_type(type_: Type[Any]) -> bool: + """ + Check whether type_ was created using typing.NewType + """ + return isinstance(type_, test_type.__class__) and hasattr(type_, '__supertype__') # type: ignore + + +def new_type_supertype(type_: Type[Any]) -> Type[Any]: + while hasattr(type_, '__supertype__'): + type_ = type_.__supertype__ + return type_ + + +def _check_classvar(v: Optional[Type[Any]]) -> bool: + if v is None: + return False + + return v.__class__ == ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar' + + +def _check_finalvar(v: Optional[Type[Any]]) -> bool: + """ + Check if a given type is a `typing.Final` type. + """ + if v is None: + return False + + return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final') + + +def is_classvar(ann_type: Type[Any]) -> bool: + if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)): + return True + + # this is an ugly workaround for class vars that contain forward references and are therefore themselves + # forward references, see #3679 + if ann_type.__class__ == ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['): + return True + + return False + + +def is_finalvar(ann_type: Type[Any]) -> bool: + return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type)) + + +def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None: + """ + Try to update ForwardRefs on fields based on this ModelField, globalns and localns. + """ + prepare = False + if field.type_.__class__ == ForwardRef: + prepare = True + field.type_ = evaluate_forwardref(field.type_, globalns, localns or None) + if field.outer_type_.__class__ == ForwardRef: + prepare = True + field.outer_type_ = evaluate_forwardref(field.outer_type_, globalns, localns or None) + if prepare: + field.prepare() + + if field.sub_fields: + for sub_f in field.sub_fields: + update_field_forward_refs(sub_f, globalns=globalns, localns=localns) + + if field.discriminator_key is not None: + field.prepare_discriminated_union_sub_fields() + + +def update_model_forward_refs( + model: Type[Any], + fields: Iterable['ModelField'], + json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable], + localns: 'DictStrAny', + exc_to_suppress: Tuple[Type[BaseException], ...] = (), +) -> None: + """ + Try to update model fields ForwardRefs based on model and localns. + """ + if model.__module__ in sys.modules: + globalns = sys.modules[model.__module__].__dict__.copy() + else: + globalns = {} + + globalns.setdefault(model.__name__, model) + + for f in fields: + try: + update_field_forward_refs(f, globalns=globalns, localns=localns) + except exc_to_suppress: + pass + + for key in set(json_encoders.keys()): + if isinstance(key, str): + fr: ForwardRef = ForwardRef(key) + elif isinstance(key, ForwardRef): + fr = key + else: + continue + + try: + new_key = evaluate_forwardref(fr, globalns, localns or None) + except exc_to_suppress: # pragma: no cover + continue + + json_encoders[new_key] = json_encoders.pop(key) + + +def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]: + """ + Tries to get the class of a Type[T] annotation. Returns True if Type is used + without brackets. Otherwise returns None. + """ + if type_ is type: + return True + + if get_origin(type_) is None: + return None + + args = get_args(type_) + if not args or not isinstance(args[0], type): + return True + else: + return args[0] + + +def get_sub_types(tp: Any) -> List[Any]: + """ + Return all the types that are allowed by type `tp` + `tp` can be a `Union` of allowed types or an `Annotated` type + """ + origin = get_origin(tp) + if origin is Annotated: + return get_sub_types(get_args(tp)[0]) + elif is_union(origin): + return [x for t in get_args(tp) for x in get_sub_types(t)] + else: + return [tp] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py new file mode 100644 index 0000000..2094e84 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py @@ -0,0 +1,807 @@ +import keyword +import warnings +import weakref +from collections import OrderedDict, defaultdict, deque +from copy import deepcopy +from itertools import islice, zip_longest +from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType +from typing import ( + TYPE_CHECKING, + AbstractSet, + Any, + Callable, + Collection, + Dict, + Generator, + Iterable, + Iterator, + List, + Mapping, + NoReturn, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +from typing_extensions import Annotated + +from pydantic.v1.errors import ConfigError +from pydantic.v1.typing import ( + NoneType, + WithArgsTypes, + all_literal_values, + display_as_type, + get_args, + get_origin, + is_literal_type, + is_union, +) +from pydantic.v1.version import version_info + +if TYPE_CHECKING: + from inspect import Signature + from pathlib import Path + + from pydantic.v1.config import BaseConfig + from pydantic.v1.dataclasses import Dataclass + from pydantic.v1.fields import ModelField + from pydantic.v1.main import BaseModel + from pydantic.v1.typing import AbstractSetIntStr, DictIntStrAny, IntStr, MappingIntStrAny, ReprArgs + + RichReprResult = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]] + +__all__ = ( + 'import_string', + 'sequence_like', + 'validate_field_name', + 'lenient_isinstance', + 'lenient_issubclass', + 'in_ipython', + 'is_valid_identifier', + 'deep_update', + 'update_not_none', + 'almost_equal_floats', + 'get_model', + 'to_camel', + 'to_lower_camel', + 'is_valid_field', + 'smart_deepcopy', + 'PyObjectStr', + 'Representation', + 'GetterDict', + 'ValueItems', + 'version_info', # required here to match behaviour in v1.3 + 'ClassAttribute', + 'path_type', + 'ROOT_KEY', + 'get_unique_discriminator_alias', + 'get_discriminator_alias_and_values', + 'DUNDER_ATTRIBUTES', +) + +ROOT_KEY = '__root__' +# these are types that are returned unchanged by deepcopy +IMMUTABLE_NON_COLLECTIONS_TYPES: Set[Type[Any]] = { + int, + float, + complex, + str, + bool, + bytes, + type, + NoneType, + FunctionType, + BuiltinFunctionType, + LambdaType, + weakref.ref, + CodeType, + # note: including ModuleType will differ from behaviour of deepcopy by not producing error. + # It might be not a good idea in general, but considering that this function used only internally + # against default values of fields, this will allow to actually have a field with module as default value + ModuleType, + NotImplemented.__class__, + Ellipsis.__class__, +} + +# these are types that if empty, might be copied with simple copy() instead of deepcopy() +BUILTIN_COLLECTIONS: Set[Type[Any]] = { + list, + set, + tuple, + frozenset, + dict, + OrderedDict, + defaultdict, + deque, +} + + +def import_string(dotted_path: str) -> Any: + """ + Stolen approximately from django. Import a dotted module path and return the attribute/class designated by the + last name in the path. Raise ImportError if the import fails. + """ + from importlib import import_module + + try: + module_path, class_name = dotted_path.strip(' ').rsplit('.', 1) + except ValueError as e: + raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e + + module = import_module(module_path) + try: + return getattr(module, class_name) + except AttributeError as e: + raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e + + +def truncate(v: Union[str], *, max_len: int = 80) -> str: + """ + Truncate a value and add a unicode ellipsis (three dots) to the end if it was too long + """ + warnings.warn('`truncate` is no-longer used by pydantic and is deprecated', DeprecationWarning) + if isinstance(v, str) and len(v) > (max_len - 2): + # -3 so quote + string + … + quote has correct length + return (v[: (max_len - 3)] + '…').__repr__() + try: + v = v.__repr__() + except TypeError: + v = v.__class__.__repr__(v) # in case v is a type + if len(v) > max_len: + v = v[: max_len - 1] + '…' + return v + + +def sequence_like(v: Any) -> bool: + return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) + + +def validate_field_name(bases: Iterable[Type[Any]], field_name: str) -> None: + """ + Ensure that the field's name does not shadow an existing attribute of the model. + """ + for base in bases: + if getattr(base, field_name, None): + raise NameError( + f'Field name "{field_name}" shadows a BaseModel attribute; ' + f'use a different field name with "alias=\'{field_name}\'".' + ) + + +def lenient_isinstance(o: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool: + try: + return isinstance(o, class_or_tuple) # type: ignore[arg-type] + except TypeError: + return False + + +def lenient_issubclass(cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool: + try: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] + except TypeError: + if isinstance(cls, WithArgsTypes): + return False + raise # pragma: no cover + + +def in_ipython() -> bool: + """ + Check whether we're in an ipython environment, including jupyter notebooks. + """ + try: + eval('__IPYTHON__') + except NameError: + return False + else: # pragma: no cover + return True + + +def is_valid_identifier(identifier: str) -> bool: + """ + Checks that a string is a valid identifier and not a Python keyword. + :param identifier: The identifier to test. + :return: True if the identifier is valid. + """ + return identifier.isidentifier() and not keyword.iskeyword(identifier) + + +KeyType = TypeVar('KeyType') + + +def deep_update(mapping: Dict[KeyType, Any], *updating_mappings: Dict[KeyType, Any]) -> Dict[KeyType, Any]: + updated_mapping = mapping.copy() + for updating_mapping in updating_mappings: + for k, v in updating_mapping.items(): + if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict): + updated_mapping[k] = deep_update(updated_mapping[k], v) + else: + updated_mapping[k] = v + return updated_mapping + + +def update_not_none(mapping: Dict[Any, Any], **update: Any) -> None: + mapping.update({k: v for k, v in update.items() if v is not None}) + + +def almost_equal_floats(value_1: float, value_2: float, *, delta: float = 1e-8) -> bool: + """ + Return True if two floats are almost equal + """ + return abs(value_1 - value_2) <= delta + + +def generate_model_signature( + init: Callable[..., None], fields: Dict[str, 'ModelField'], config: Type['BaseConfig'] +) -> 'Signature': + """ + Generate signature for model based on its fields + """ + from inspect import Parameter, Signature, signature + + from pydantic.v1.config import Extra + + present_params = signature(init).parameters.values() + merged_params: Dict[str, Parameter] = {} + var_kw = None + use_var_kw = False + + for param in islice(present_params, 1, None): # skip self arg + if param.kind is param.VAR_KEYWORD: + var_kw = param + continue + merged_params[param.name] = param + + if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through + allow_names = config.allow_population_by_field_name + for field_name, field in fields.items(): + param_name = field.alias + if field_name in merged_params or param_name in merged_params: + continue + elif not is_valid_identifier(param_name): + if allow_names and is_valid_identifier(field_name): + param_name = field_name + else: + use_var_kw = True + continue + + # TODO: replace annotation with actual expected types once #1055 solved + kwargs = {'default': field.default} if not field.required else {} + merged_params[param_name] = Parameter( + param_name, Parameter.KEYWORD_ONLY, annotation=field.annotation, **kwargs + ) + + if config.extra is Extra.allow: + use_var_kw = True + + if var_kw and use_var_kw: + # Make sure the parameter for extra kwargs + # does not have the same name as a field + default_model_signature = [ + ('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD), + ('data', Parameter.VAR_KEYWORD), + ] + if [(p.name, p.kind) for p in present_params] == default_model_signature: + # if this is the standard model signature, use extra_data as the extra args name + var_kw_name = 'extra_data' + else: + # else start from var_kw + var_kw_name = var_kw.name + + # generate a name that's definitely unique + while var_kw_name in fields: + var_kw_name += '_' + merged_params[var_kw_name] = var_kw.replace(name=var_kw_name) + + return Signature(parameters=list(merged_params.values()), return_annotation=None) + + +def get_model(obj: Union[Type['BaseModel'], Type['Dataclass']]) -> Type['BaseModel']: + from pydantic.v1.main import BaseModel + + try: + model_cls = obj.__pydantic_model__ # type: ignore + except AttributeError: + model_cls = obj + + if not issubclass(model_cls, BaseModel): + raise TypeError('Unsupported type, must be either BaseModel or dataclass') + return model_cls + + +def to_camel(string: str) -> str: + return ''.join(word.capitalize() for word in string.split('_')) + + +def to_lower_camel(string: str) -> str: + if len(string) >= 1: + pascal_string = to_camel(string) + return pascal_string[0].lower() + pascal_string[1:] + return string.lower() + + +T = TypeVar('T') + + +def unique_list( + input_list: Union[List[T], Tuple[T, ...]], + *, + name_factory: Callable[[T], str] = str, +) -> List[T]: + """ + Make a list unique while maintaining order. + We update the list if another one with the same name is set + (e.g. root validator overridden in subclass) + """ + result: List[T] = [] + result_names: List[str] = [] + for v in input_list: + v_name = name_factory(v) + if v_name not in result_names: + result_names.append(v_name) + result.append(v) + else: + result[result_names.index(v_name)] = v + + return result + + +class PyObjectStr(str): + """ + String class where repr doesn't include quotes. Useful with Representation when you want to return a string + representation of something that valid (or pseudo-valid) python. + """ + + def __repr__(self) -> str: + return str(self) + + +class Representation: + """ + Mixin to provide __str__, __repr__, and __pretty__ methods. See #884 for more details. + + __pretty__ is used by [devtools](https://python-devtools.helpmanual.io/) to provide human readable representations + of objects. + """ + + __slots__: Tuple[str, ...] = tuple() + + def __repr_args__(self) -> 'ReprArgs': + """ + Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden. + + Can either return: + * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]` + * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]` + """ + attrs = ((s, getattr(self, s)) for s in self.__slots__) + return [(a, v) for a, v in attrs if v is not None] + + def __repr_name__(self) -> str: + """ + Name of the instance's class, used in __repr__. + """ + return self.__class__.__name__ + + def __repr_str__(self, join_str: str) -> str: + return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__()) + + def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]: + """ + Used by devtools (https://python-devtools.helpmanual.io/) to provide a human readable representations of objects + """ + yield self.__repr_name__() + '(' + yield 1 + for name, value in self.__repr_args__(): + if name is not None: + yield name + '=' + yield fmt(value) + yield ',' + yield 0 + yield -1 + yield ')' + + def __str__(self) -> str: + return self.__repr_str__(' ') + + def __repr__(self) -> str: + return f'{self.__repr_name__()}({self.__repr_str__(", ")})' + + def __rich_repr__(self) -> 'RichReprResult': + """Get fields for Rich library""" + for name, field_repr in self.__repr_args__(): + if name is None: + yield field_repr + else: + yield name, field_repr + + +class GetterDict(Representation): + """ + Hack to make object's smell just enough like dicts for validate_model. + + We can't inherit from Mapping[str, Any] because it upsets cython so we have to implement all methods ourselves. + """ + + __slots__ = ('_obj',) + + def __init__(self, obj: Any): + self._obj = obj + + def __getitem__(self, key: str) -> Any: + try: + return getattr(self._obj, key) + except AttributeError as e: + raise KeyError(key) from e + + def get(self, key: Any, default: Any = None) -> Any: + return getattr(self._obj, key, default) + + def extra_keys(self) -> Set[Any]: + """ + We don't want to get any other attributes of obj if the model didn't explicitly ask for them + """ + return set() + + def keys(self) -> List[Any]: + """ + Keys of the pseudo dictionary, uses a list not set so order information can be maintained like python + dictionaries. + """ + return list(self) + + def values(self) -> List[Any]: + return [self[k] for k in self] + + def items(self) -> Iterator[Tuple[str, Any]]: + for k in self: + yield k, self.get(k) + + def __iter__(self) -> Iterator[str]: + for name in dir(self._obj): + if not name.startswith('_'): + yield name + + def __len__(self) -> int: + return sum(1 for _ in self) + + def __contains__(self, item: Any) -> bool: + return item in self.keys() + + def __eq__(self, other: Any) -> bool: + return dict(self) == dict(other.items()) + + def __repr_args__(self) -> 'ReprArgs': + return [(None, dict(self))] + + def __repr_name__(self) -> str: + return f'GetterDict[{display_as_type(self._obj)}]' + + +class ValueItems(Representation): + """ + Class for more convenient calculation of excluded or included fields on values. + """ + + __slots__ = ('_items', '_type') + + def __init__(self, value: Any, items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> None: + items = self._coerce_items(items) + + if isinstance(value, (list, tuple)): + items = self._normalize_indexes(items, len(value)) + + self._items: 'MappingIntStrAny' = items + + def is_excluded(self, item: Any) -> bool: + """ + Check if item is fully excluded. + + :param item: key or index of a value + """ + return self.is_true(self._items.get(item)) + + def is_included(self, item: Any) -> bool: + """ + Check if value is contained in self._items + + :param item: key or index of value + """ + return item in self._items + + def for_element(self, e: 'IntStr') -> Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']]: + """ + :param e: key or index of element on value + :return: raw values for element if self._items is dict and contain needed element + """ + + item = self._items.get(e) + return item if not self.is_true(item) else None + + def _normalize_indexes(self, items: 'MappingIntStrAny', v_length: int) -> 'DictIntStrAny': + """ + :param items: dict or set of indexes which will be normalized + :param v_length: length of sequence indexes of which will be + + >>> self._normalize_indexes({0: True, -2: True, -1: True}, 4) + {0: True, 2: True, 3: True} + >>> self._normalize_indexes({'__all__': True}, 4) + {0: True, 1: True, 2: True, 3: True} + """ + + normalized_items: 'DictIntStrAny' = {} + all_items = None + for i, v in items.items(): + if not (isinstance(v, Mapping) or isinstance(v, AbstractSet) or self.is_true(v)): + raise TypeError(f'Unexpected type of exclude value for index "{i}" {v.__class__}') + if i == '__all__': + all_items = self._coerce_value(v) + continue + if not isinstance(i, int): + raise TypeError( + 'Excluding fields from a sequence of sub-models or dicts must be performed index-wise: ' + 'expected integer keys or keyword "__all__"' + ) + normalized_i = v_length + i if i < 0 else i + normalized_items[normalized_i] = self.merge(v, normalized_items.get(normalized_i)) + + if not all_items: + return normalized_items + if self.is_true(all_items): + for i in range(v_length): + normalized_items.setdefault(i, ...) + return normalized_items + for i in range(v_length): + normalized_item = normalized_items.setdefault(i, {}) + if not self.is_true(normalized_item): + normalized_items[i] = self.merge(all_items, normalized_item) + return normalized_items + + @classmethod + def merge(cls, base: Any, override: Any, intersect: bool = False) -> Any: + """ + Merge a ``base`` item with an ``override`` item. + + Both ``base`` and ``override`` are converted to dictionaries if possible. + Sets are converted to dictionaries with the sets entries as keys and + Ellipsis as values. + + Each key-value pair existing in ``base`` is merged with ``override``, + while the rest of the key-value pairs are updated recursively with this function. + + Merging takes place based on the "union" of keys if ``intersect`` is + set to ``False`` (default) and on the intersection of keys if + ``intersect`` is set to ``True``. + """ + override = cls._coerce_value(override) + base = cls._coerce_value(base) + if override is None: + return base + if cls.is_true(base) or base is None: + return override + if cls.is_true(override): + return base if intersect else override + + # intersection or union of keys while preserving ordering: + if intersect: + merge_keys = [k for k in base if k in override] + [k for k in override if k in base] + else: + merge_keys = list(base) + [k for k in override if k not in base] + + merged: 'DictIntStrAny' = {} + for k in merge_keys: + merged_item = cls.merge(base.get(k), override.get(k), intersect=intersect) + if merged_item is not None: + merged[k] = merged_item + + return merged + + @staticmethod + def _coerce_items(items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> 'MappingIntStrAny': + if isinstance(items, Mapping): + pass + elif isinstance(items, AbstractSet): + items = dict.fromkeys(items, ...) + else: + class_name = getattr(items, '__class__', '???') + assert_never( + items, + f'Unexpected type of exclude value {class_name}', + ) + return items + + @classmethod + def _coerce_value(cls, value: Any) -> Any: + if value is None or cls.is_true(value): + return value + return cls._coerce_items(value) + + @staticmethod + def is_true(v: Any) -> bool: + return v is True or v is ... + + def __repr_args__(self) -> 'ReprArgs': + return [(None, self._items)] + + +class ClassAttribute: + """ + Hide class attribute from its instances + """ + + __slots__ = ( + 'name', + 'value', + ) + + def __init__(self, name: str, value: Any) -> None: + self.name = name + self.value = value + + def __get__(self, instance: Any, owner: Type[Any]) -> None: + if instance is None: + return self.value + raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only') + + +path_types = { + 'is_dir': 'directory', + 'is_file': 'file', + 'is_mount': 'mount point', + 'is_symlink': 'symlink', + 'is_block_device': 'block device', + 'is_char_device': 'char device', + 'is_fifo': 'FIFO', + 'is_socket': 'socket', +} + + +def path_type(p: 'Path') -> str: + """ + Find out what sort of thing a path is. + """ + assert p.exists(), 'path does not exist' + for method, name in path_types.items(): + if getattr(p, method)(): + return name + + return 'unknown' + + +Obj = TypeVar('Obj') + + +def smart_deepcopy(obj: Obj) -> Obj: + """ + Return type as is for immutable built-in types + Use obj.copy() for built-in empty collections + Use copy.deepcopy() for non-empty collections and unknown objects + """ + + obj_type = obj.__class__ + if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES: + return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway + try: + if not obj and obj_type in BUILTIN_COLLECTIONS: + # faster way for empty collections, no need to copy its members + return obj if obj_type is tuple else obj.copy() # type: ignore # tuple doesn't have copy method + except (TypeError, ValueError, RuntimeError): + # do we really dare to catch ALL errors? Seems a bit risky + pass + + return deepcopy(obj) # slowest way when we actually might need a deepcopy + + +def is_valid_field(name: str) -> bool: + if not name.startswith('_'): + return True + return ROOT_KEY == name + + +DUNDER_ATTRIBUTES = { + '__annotations__', + '__classcell__', + '__doc__', + '__module__', + '__orig_bases__', + '__orig_class__', + '__qualname__', + '__firstlineno__', + '__static_attributes__', + '__classdictcell__', +} + + +def is_valid_private_name(name: str) -> bool: + return not is_valid_field(name) and name not in DUNDER_ATTRIBUTES + + +_EMPTY = object() + + +def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool: + """ + Check that the items of `left` are the same objects as those in `right`. + + >>> a, b = object(), object() + >>> all_identical([a, b, a], [a, b, a]) + True + >>> all_identical([a, b, [a]], [a, b, [a]]) # new list object, while "equal" is not "identical" + False + """ + for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY): + if left_item is not right_item: + return False + return True + + +def assert_never(obj: NoReturn, msg: str) -> NoReturn: + """ + Helper to make sure that we have covered all possible types. + + This is mostly useful for ``mypy``, docs: + https://mypy.readthedocs.io/en/latest/literal_types.html#exhaustive-checks + """ + raise TypeError(msg) + + +def get_unique_discriminator_alias(all_aliases: Collection[str], discriminator_key: str) -> str: + """Validate that all aliases are the same and if that's the case return the alias""" + unique_aliases = set(all_aliases) + if len(unique_aliases) > 1: + raise ConfigError( + f'Aliases for discriminator {discriminator_key!r} must be the same (got {", ".join(sorted(all_aliases))})' + ) + return unique_aliases.pop() + + +def get_discriminator_alias_and_values(tp: Any, discriminator_key: str) -> Tuple[str, Tuple[str, ...]]: + """ + Get alias and all valid values in the `Literal` type of the discriminator field + `tp` can be a `BaseModel` class or directly an `Annotated` `Union` of many. + """ + is_root_model = getattr(tp, '__custom_root_type__', False) + + if get_origin(tp) is Annotated: + tp = get_args(tp)[0] + + if hasattr(tp, '__pydantic_model__'): + tp = tp.__pydantic_model__ + + if is_union(get_origin(tp)): + alias, all_values = _get_union_alias_and_all_values(tp, discriminator_key) + return alias, tuple(v for values in all_values for v in values) + elif is_root_model: + union_type = tp.__fields__[ROOT_KEY].type_ + alias, all_values = _get_union_alias_and_all_values(union_type, discriminator_key) + + if len(set(all_values)) > 1: + raise ConfigError( + f'Field {discriminator_key!r} is not the same for all submodels of {display_as_type(tp)!r}' + ) + + return alias, all_values[0] + + else: + try: + t_discriminator_type = tp.__fields__[discriminator_key].type_ + except AttributeError as e: + raise TypeError(f'Type {tp.__name__!r} is not a valid `BaseModel` or `dataclass`') from e + except KeyError as e: + raise ConfigError(f'Model {tp.__name__!r} needs a discriminator field for key {discriminator_key!r}') from e + + if not is_literal_type(t_discriminator_type): + raise ConfigError(f'Field {discriminator_key!r} of model {tp.__name__!r} needs to be a `Literal`') + + return tp.__fields__[discriminator_key].alias, all_literal_values(t_discriminator_type) + + +def _get_union_alias_and_all_values( + union_type: Type[Any], discriminator_key: str +) -> Tuple[str, Tuple[Tuple[str, ...], ...]]: + zipped_aliases_values = [get_discriminator_alias_and_values(t, discriminator_key) for t in get_args(union_type)] + # unzip: [('alias_a',('v1', 'v2)), ('alias_b', ('v3',))] => [('alias_a', 'alias_b'), (('v1', 'v2'), ('v3',))] + all_aliases, all_values = zip(*zipped_aliases_values) + return get_unique_discriminator_alias(all_aliases, discriminator_key), all_values diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py new file mode 100644 index 0000000..c0940e8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py @@ -0,0 +1,768 @@ +import math +import re +from collections import OrderedDict, deque +from collections.abc import Hashable as CollectionsHashable +from datetime import date, datetime, time, timedelta +from decimal import Decimal, DecimalException +from enum import Enum, IntEnum +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Deque, + Dict, + ForwardRef, + FrozenSet, + Generator, + Hashable, + List, + NamedTuple, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, +) +from uuid import UUID +from warnings import warn + +from pydantic.v1 import errors +from pydantic.v1.datetime_parse import parse_date, parse_datetime, parse_duration, parse_time +from pydantic.v1.typing import ( + AnyCallable, + all_literal_values, + display_as_type, + get_class, + is_callable_type, + is_literal_type, + is_namedtuple, + is_none_type, + is_typeddict, +) +from pydantic.v1.utils import almost_equal_floats, lenient_issubclass, sequence_like + +if TYPE_CHECKING: + from typing_extensions import Literal, TypedDict + + from pydantic.v1.config import BaseConfig + from pydantic.v1.fields import ModelField + from pydantic.v1.types import ConstrainedDecimal, ConstrainedFloat, ConstrainedInt + + ConstrainedNumber = Union[ConstrainedDecimal, ConstrainedFloat, ConstrainedInt] + AnyOrderedDict = OrderedDict[Any, Any] + Number = Union[int, float, Decimal] + StrBytes = Union[str, bytes] + + +def str_validator(v: Any) -> Union[str]: + if isinstance(v, str): + if isinstance(v, Enum): + return v.value + else: + return v + elif isinstance(v, (float, int, Decimal)): + # is there anything else we want to add here? If you think so, create an issue. + return str(v) + elif isinstance(v, (bytes, bytearray)): + return v.decode() + else: + raise errors.StrError() + + +def strict_str_validator(v: Any) -> Union[str]: + if isinstance(v, str) and not isinstance(v, Enum): + return v + raise errors.StrError() + + +def bytes_validator(v: Any) -> Union[bytes]: + if isinstance(v, bytes): + return v + elif isinstance(v, bytearray): + return bytes(v) + elif isinstance(v, str): + return v.encode() + elif isinstance(v, (float, int, Decimal)): + return str(v).encode() + else: + raise errors.BytesError() + + +def strict_bytes_validator(v: Any) -> Union[bytes]: + if isinstance(v, bytes): + return v + elif isinstance(v, bytearray): + return bytes(v) + else: + raise errors.BytesError() + + +BOOL_FALSE = {0, '0', 'off', 'f', 'false', 'n', 'no'} +BOOL_TRUE = {1, '1', 'on', 't', 'true', 'y', 'yes'} + + +def bool_validator(v: Any) -> bool: + if v is True or v is False: + return v + if isinstance(v, bytes): + v = v.decode() + if isinstance(v, str): + v = v.lower() + try: + if v in BOOL_TRUE: + return True + if v in BOOL_FALSE: + return False + except TypeError: + raise errors.BoolError() + raise errors.BoolError() + + +# matches the default limit cpython, see https://github.com/python/cpython/pull/96500 +max_str_int = 4_300 + + +def int_validator(v: Any) -> int: + if isinstance(v, int) and not (v is True or v is False): + return v + + # see https://github.com/pydantic/pydantic/issues/1477 and in turn, https://github.com/python/cpython/issues/95778 + # this check should be unnecessary once patch releases are out for 3.7, 3.8, 3.9 and 3.10 + # but better to check here until then. + # NOTICE: this does not fully protect user from the DOS risk since the standard library JSON implementation + # (and other std lib modules like xml) use `int()` and are likely called before this, the best workaround is to + # 1. update to the latest patch release of python once released, 2. use a different JSON library like ujson + if isinstance(v, (str, bytes, bytearray)) and len(v) > max_str_int: + raise errors.IntegerError() + + try: + return int(v) + except (TypeError, ValueError, OverflowError): + raise errors.IntegerError() + + +def strict_int_validator(v: Any) -> int: + if isinstance(v, int) and not (v is True or v is False): + return v + raise errors.IntegerError() + + +def float_validator(v: Any) -> float: + if isinstance(v, float): + return v + + try: + return float(v) + except (TypeError, ValueError): + raise errors.FloatError() + + +def strict_float_validator(v: Any) -> float: + if isinstance(v, float): + return v + raise errors.FloatError() + + +def float_finite_validator(v: 'Number', field: 'ModelField', config: 'BaseConfig') -> 'Number': + allow_inf_nan = getattr(field.type_, 'allow_inf_nan', None) + if allow_inf_nan is None: + allow_inf_nan = config.allow_inf_nan + + if allow_inf_nan is False and (math.isnan(v) or math.isinf(v)): + raise errors.NumberNotFiniteError() + return v + + +def number_multiple_validator(v: 'Number', field: 'ModelField') -> 'Number': + field_type: ConstrainedNumber = field.type_ + if field_type.multiple_of is not None: + mod = float(v) / float(field_type.multiple_of) % 1 + if not almost_equal_floats(mod, 0.0) and not almost_equal_floats(mod, 1.0): + raise errors.NumberNotMultipleError(multiple_of=field_type.multiple_of) + return v + + +def number_size_validator(v: 'Number', field: 'ModelField') -> 'Number': + field_type: ConstrainedNumber = field.type_ + if field_type.gt is not None and not v > field_type.gt: + raise errors.NumberNotGtError(limit_value=field_type.gt) + elif field_type.ge is not None and not v >= field_type.ge: + raise errors.NumberNotGeError(limit_value=field_type.ge) + + if field_type.lt is not None and not v < field_type.lt: + raise errors.NumberNotLtError(limit_value=field_type.lt) + if field_type.le is not None and not v <= field_type.le: + raise errors.NumberNotLeError(limit_value=field_type.le) + + return v + + +def constant_validator(v: 'Any', field: 'ModelField') -> 'Any': + """Validate ``const`` fields. + + The value provided for a ``const`` field must be equal to the default value + of the field. This is to support the keyword of the same name in JSON + Schema. + """ + if v != field.default: + raise errors.WrongConstantError(given=v, permitted=[field.default]) + + return v + + +def anystr_length_validator(v: 'StrBytes', config: 'BaseConfig') -> 'StrBytes': + v_len = len(v) + + min_length = config.min_anystr_length + if v_len < min_length: + raise errors.AnyStrMinLengthError(limit_value=min_length) + + max_length = config.max_anystr_length + if max_length is not None and v_len > max_length: + raise errors.AnyStrMaxLengthError(limit_value=max_length) + + return v + + +def anystr_strip_whitespace(v: 'StrBytes') -> 'StrBytes': + return v.strip() + + +def anystr_upper(v: 'StrBytes') -> 'StrBytes': + return v.upper() + + +def anystr_lower(v: 'StrBytes') -> 'StrBytes': + return v.lower() + + +def ordered_dict_validator(v: Any) -> 'AnyOrderedDict': + if isinstance(v, OrderedDict): + return v + + try: + return OrderedDict(v) + except (TypeError, ValueError): + raise errors.DictError() + + +def dict_validator(v: Any) -> Dict[Any, Any]: + if isinstance(v, dict): + return v + + try: + return dict(v) + except (TypeError, ValueError): + raise errors.DictError() + + +def list_validator(v: Any) -> List[Any]: + if isinstance(v, list): + return v + elif sequence_like(v): + return list(v) + else: + raise errors.ListError() + + +def tuple_validator(v: Any) -> Tuple[Any, ...]: + if isinstance(v, tuple): + return v + elif sequence_like(v): + return tuple(v) + else: + raise errors.TupleError() + + +def set_validator(v: Any) -> Set[Any]: + if isinstance(v, set): + return v + elif sequence_like(v): + return set(v) + else: + raise errors.SetError() + + +def frozenset_validator(v: Any) -> FrozenSet[Any]: + if isinstance(v, frozenset): + return v + elif sequence_like(v): + return frozenset(v) + else: + raise errors.FrozenSetError() + + +def deque_validator(v: Any) -> Deque[Any]: + if isinstance(v, deque): + return v + elif sequence_like(v): + return deque(v) + else: + raise errors.DequeError() + + +def enum_member_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum: + try: + enum_v = field.type_(v) + except ValueError: + # field.type_ should be an enum, so will be iterable + raise errors.EnumMemberError(enum_values=list(field.type_)) + return enum_v.value if config.use_enum_values else enum_v + + +def uuid_validator(v: Any, field: 'ModelField') -> UUID: + try: + if isinstance(v, str): + v = UUID(v) + elif isinstance(v, (bytes, bytearray)): + try: + v = UUID(v.decode()) + except ValueError: + # 16 bytes in big-endian order as the bytes argument fail + # the above check + v = UUID(bytes=v) + except ValueError: + raise errors.UUIDError() + + if not isinstance(v, UUID): + raise errors.UUIDError() + + required_version = getattr(field.type_, '_required_version', None) + if required_version and v.version != required_version: + raise errors.UUIDVersionError(required_version=required_version) + + return v + + +def decimal_validator(v: Any) -> Decimal: + if isinstance(v, Decimal): + return v + elif isinstance(v, (bytes, bytearray)): + v = v.decode() + + v = str(v).strip() + + try: + v = Decimal(v) + except DecimalException: + raise errors.DecimalError() + + if not v.is_finite(): + raise errors.DecimalIsNotFiniteError() + + return v + + +def hashable_validator(v: Any) -> Hashable: + if isinstance(v, Hashable): + return v + + raise errors.HashableError() + + +def ip_v4_address_validator(v: Any) -> IPv4Address: + if isinstance(v, IPv4Address): + return v + + try: + return IPv4Address(v) + except ValueError: + raise errors.IPv4AddressError() + + +def ip_v6_address_validator(v: Any) -> IPv6Address: + if isinstance(v, IPv6Address): + return v + + try: + return IPv6Address(v) + except ValueError: + raise errors.IPv6AddressError() + + +def ip_v4_network_validator(v: Any) -> IPv4Network: + """ + Assume IPv4Network initialised with a default ``strict`` argument + + See more: + https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network + """ + if isinstance(v, IPv4Network): + return v + + try: + return IPv4Network(v) + except ValueError: + raise errors.IPv4NetworkError() + + +def ip_v6_network_validator(v: Any) -> IPv6Network: + """ + Assume IPv6Network initialised with a default ``strict`` argument + + See more: + https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network + """ + if isinstance(v, IPv6Network): + return v + + try: + return IPv6Network(v) + except ValueError: + raise errors.IPv6NetworkError() + + +def ip_v4_interface_validator(v: Any) -> IPv4Interface: + if isinstance(v, IPv4Interface): + return v + + try: + return IPv4Interface(v) + except ValueError: + raise errors.IPv4InterfaceError() + + +def ip_v6_interface_validator(v: Any) -> IPv6Interface: + if isinstance(v, IPv6Interface): + return v + + try: + return IPv6Interface(v) + except ValueError: + raise errors.IPv6InterfaceError() + + +def path_validator(v: Any) -> Path: + if isinstance(v, Path): + return v + + try: + return Path(v) + except TypeError: + raise errors.PathError() + + +def path_exists_validator(v: Any) -> Path: + if not v.exists(): + raise errors.PathNotExistsError(path=v) + + return v + + +def callable_validator(v: Any) -> AnyCallable: + """ + Perform a simple check if the value is callable. + + Note: complete matching of argument type hints and return types is not performed + """ + if callable(v): + return v + + raise errors.CallableError(value=v) + + +def enum_validator(v: Any) -> Enum: + if isinstance(v, Enum): + return v + + raise errors.EnumError(value=v) + + +def int_enum_validator(v: Any) -> IntEnum: + if isinstance(v, IntEnum): + return v + + raise errors.IntEnumError(value=v) + + +def make_literal_validator(type_: Any) -> Callable[[Any], Any]: + permitted_choices = all_literal_values(type_) + + # To have a O(1) complexity and still return one of the values set inside the `Literal`, + # we create a dict with the set values (a set causes some problems with the way intersection works). + # In some cases the set value and checked value can indeed be different (see `test_literal_validator_str_enum`) + allowed_choices = {v: v for v in permitted_choices} + + def literal_validator(v: Any) -> Any: + try: + return allowed_choices[v] + except (KeyError, TypeError): + raise errors.WrongConstantError(given=v, permitted=permitted_choices) + + return literal_validator + + +def constr_length_validator(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes': + v_len = len(v) + + min_length = field.type_.min_length if field.type_.min_length is not None else config.min_anystr_length + if v_len < min_length: + raise errors.AnyStrMinLengthError(limit_value=min_length) + + max_length = field.type_.max_length if field.type_.max_length is not None else config.max_anystr_length + if max_length is not None and v_len > max_length: + raise errors.AnyStrMaxLengthError(limit_value=max_length) + + return v + + +def constr_strip_whitespace(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes': + strip_whitespace = field.type_.strip_whitespace or config.anystr_strip_whitespace + if strip_whitespace: + v = v.strip() + + return v + + +def constr_upper(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes': + upper = field.type_.to_upper or config.anystr_upper + if upper: + v = v.upper() + + return v + + +def constr_lower(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes': + lower = field.type_.to_lower or config.anystr_lower + if lower: + v = v.lower() + return v + + +def validate_json(v: Any, config: 'BaseConfig') -> Any: + if v is None: + # pass None through to other validators + return v + try: + return config.json_loads(v) # type: ignore + except ValueError: + raise errors.JsonError() + except TypeError: + raise errors.JsonTypeError() + + +T = TypeVar('T') + + +def make_arbitrary_type_validator(type_: Type[T]) -> Callable[[T], T]: + def arbitrary_type_validator(v: Any) -> T: + if isinstance(v, type_): + return v + raise errors.ArbitraryTypeError(expected_arbitrary_type=type_) + + return arbitrary_type_validator + + +def make_class_validator(type_: Type[T]) -> Callable[[Any], Type[T]]: + def class_validator(v: Any) -> Type[T]: + if lenient_issubclass(v, type_): + return v + raise errors.SubclassError(expected_class=type_) + + return class_validator + + +def any_class_validator(v: Any) -> Type[T]: + if isinstance(v, type): + return v + raise errors.ClassError() + + +def none_validator(v: Any) -> 'Literal[None]': + if v is None: + return v + raise errors.NotNoneError() + + +def pattern_validator(v: Any) -> Pattern[str]: + if isinstance(v, Pattern): + return v + + str_value = str_validator(v) + + try: + return re.compile(str_value) + except re.error: + raise errors.PatternError() + + +NamedTupleT = TypeVar('NamedTupleT', bound=NamedTuple) + + +def make_namedtuple_validator( + namedtuple_cls: Type[NamedTupleT], config: Type['BaseConfig'] +) -> Callable[[Tuple[Any, ...]], NamedTupleT]: + from pydantic.v1.annotated_types import create_model_from_namedtuple + + NamedTupleModel = create_model_from_namedtuple( + namedtuple_cls, + __config__=config, + __module__=namedtuple_cls.__module__, + ) + namedtuple_cls.__pydantic_model__ = NamedTupleModel # type: ignore[attr-defined] + + def namedtuple_validator(values: Tuple[Any, ...]) -> NamedTupleT: + annotations = NamedTupleModel.__annotations__ + + if len(values) > len(annotations): + raise errors.ListMaxLengthError(limit_value=len(annotations)) + + dict_values: Dict[str, Any] = dict(zip(annotations, values)) + validated_dict_values: Dict[str, Any] = dict(NamedTupleModel(**dict_values)) + return namedtuple_cls(**validated_dict_values) + + return namedtuple_validator + + +def make_typeddict_validator( + typeddict_cls: Type['TypedDict'], config: Type['BaseConfig'] # type: ignore[valid-type] +) -> Callable[[Any], Dict[str, Any]]: + from pydantic.v1.annotated_types import create_model_from_typeddict + + TypedDictModel = create_model_from_typeddict( + typeddict_cls, + __config__=config, + __module__=typeddict_cls.__module__, + ) + typeddict_cls.__pydantic_model__ = TypedDictModel # type: ignore[attr-defined] + + def typeddict_validator(values: 'TypedDict') -> Dict[str, Any]: # type: ignore[valid-type] + return TypedDictModel.parse_obj(values).dict(exclude_unset=True) + + return typeddict_validator + + +class IfConfig: + def __init__(self, validator: AnyCallable, *config_attr_names: str, ignored_value: Any = False) -> None: + self.validator = validator + self.config_attr_names = config_attr_names + self.ignored_value = ignored_value + + def check(self, config: Type['BaseConfig']) -> bool: + return any(getattr(config, name) not in {None, self.ignored_value} for name in self.config_attr_names) + + +# order is important here, for example: bool is a subclass of int so has to come first, datetime before date same, +# IPv4Interface before IPv4Address, etc +_VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [ + (IntEnum, [int_validator, enum_member_validator]), + (Enum, [enum_member_validator]), + ( + str, + [ + str_validator, + IfConfig(anystr_strip_whitespace, 'anystr_strip_whitespace'), + IfConfig(anystr_upper, 'anystr_upper'), + IfConfig(anystr_lower, 'anystr_lower'), + IfConfig(anystr_length_validator, 'min_anystr_length', 'max_anystr_length'), + ], + ), + ( + bytes, + [ + bytes_validator, + IfConfig(anystr_strip_whitespace, 'anystr_strip_whitespace'), + IfConfig(anystr_upper, 'anystr_upper'), + IfConfig(anystr_lower, 'anystr_lower'), + IfConfig(anystr_length_validator, 'min_anystr_length', 'max_anystr_length'), + ], + ), + (bool, [bool_validator]), + (int, [int_validator]), + (float, [float_validator, IfConfig(float_finite_validator, 'allow_inf_nan', ignored_value=True)]), + (Path, [path_validator]), + (datetime, [parse_datetime]), + (date, [parse_date]), + (time, [parse_time]), + (timedelta, [parse_duration]), + (OrderedDict, [ordered_dict_validator]), + (dict, [dict_validator]), + (list, [list_validator]), + (tuple, [tuple_validator]), + (set, [set_validator]), + (frozenset, [frozenset_validator]), + (deque, [deque_validator]), + (UUID, [uuid_validator]), + (Decimal, [decimal_validator]), + (IPv4Interface, [ip_v4_interface_validator]), + (IPv6Interface, [ip_v6_interface_validator]), + (IPv4Address, [ip_v4_address_validator]), + (IPv6Address, [ip_v6_address_validator]), + (IPv4Network, [ip_v4_network_validator]), + (IPv6Network, [ip_v6_network_validator]), +] + + +def find_validators( # noqa: C901 (ignore complexity) + type_: Type[Any], config: Type['BaseConfig'] +) -> Generator[AnyCallable, None, None]: + from pydantic.v1.dataclasses import is_builtin_dataclass, make_dataclass_validator + + if type_ is Any or type_ is object: + return + type_type = type_.__class__ + if type_type == ForwardRef or type_type == TypeVar: + return + + if is_none_type(type_): + yield none_validator + return + if type_ is Pattern or type_ is re.Pattern: + yield pattern_validator + return + if type_ is Hashable or type_ is CollectionsHashable: + yield hashable_validator + return + if is_callable_type(type_): + yield callable_validator + return + if is_literal_type(type_): + yield make_literal_validator(type_) + return + if is_builtin_dataclass(type_): + yield from make_dataclass_validator(type_, config) + return + if type_ is Enum: + yield enum_validator + return + if type_ is IntEnum: + yield int_enum_validator + return + if is_namedtuple(type_): + yield tuple_validator + yield make_namedtuple_validator(type_, config) + return + if is_typeddict(type_): + yield make_typeddict_validator(type_, config) + return + + class_ = get_class(type_) + if class_ is not None: + if class_ is not Any and isinstance(class_, type): + yield make_class_validator(class_) + else: + yield any_class_validator + return + + for val_type, validators in _VALIDATORS: + try: + if issubclass(type_, val_type): + for v in validators: + if isinstance(v, IfConfig): + if v.check(config): + yield v.validator + else: + yield v + return + except TypeError: + raise RuntimeError(f'error checking inheritance of {type_!r} (type: {display_as_type(type_)})') + + if config.arbitrary_types_allowed: + yield make_arbitrary_type_validator(type_) + else: + if hasattr(type_, '__pydantic_core_schema__'): + warn(f'Mixing V1 and V2 models is not supported. `{type_.__name__}` is a V2 model.', UserWarning) + raise RuntimeError(f'no validator found for {type_}, see `arbitrary_types_allowed` in Config') diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py new file mode 100644 index 0000000..6ba1cb0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py @@ -0,0 +1,38 @@ +__all__ = 'compiled', 'VERSION', 'version_info' + +VERSION = '1.10.26' + +try: + import cython # type: ignore +except ImportError: + compiled: bool = False +else: # pragma: no cover + try: + compiled = cython.compiled + except AttributeError: + compiled = False + + +def version_info() -> str: + import platform + import sys + from importlib import import_module + from pathlib import Path + + optional_deps = [] + for p in ('devtools', 'dotenv', 'email-validator', 'typing-extensions'): + try: + import_module(p.replace('-', '_')) + except ImportError: + continue + optional_deps.append(p) + + info = { + 'pydantic version': VERSION, + 'pydantic compiled': compiled, + 'install path': Path(__file__).resolve().parent, + 'python version': sys.version, + 'platform': platform.platform(), + 'optional deps. installed': optional_deps, + } + return '\n'.join('{:>30} {}'.format(k + ':', str(v).replace('\n', ' ')) for k, v in info.items()) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py new file mode 100644 index 0000000..4bc8991 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py @@ -0,0 +1,116 @@ +"""Decorator for validating function calls.""" + +from __future__ import annotations as _annotations + +import inspect +from functools import partial +from types import BuiltinFunctionType +from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast, overload + +from ._internal import _generate_schema, _typing_extra, _validate_call +from .errors import PydanticUserError + +__all__ = ('validate_call',) + +if TYPE_CHECKING: + from .config import ConfigDict + + AnyCallableT = TypeVar('AnyCallableT', bound=Callable[..., Any]) + + +_INVALID_TYPE_ERROR_CODE = 'validate-call-type' + + +def _check_function_type(function: object) -> None: + """Check if the input function is a supported type for `validate_call`.""" + if isinstance(function, _generate_schema.VALIDATE_CALL_SUPPORTED_TYPES): + try: + _typing_extra.signature_no_eval(cast(_generate_schema.ValidateCallSupportedTypes, function)) + except (ValueError, TypeError): + raise PydanticUserError( + f"Input function `{function}` doesn't have a valid signature", code=_INVALID_TYPE_ERROR_CODE + ) + + if isinstance(function, partial): + try: + assert not isinstance(partial.func, partial), 'Partial of partial' + _check_function_type(function.func) + except PydanticUserError as e: + raise PydanticUserError( + f'Partial of `{function.func}` is invalid because the type of `{function.func}` is not supported by `validate_call`', + code=_INVALID_TYPE_ERROR_CODE, + ) from e + + return + + if isinstance(function, BuiltinFunctionType): + raise PydanticUserError(f'Input built-in function `{function}` is not supported', code=_INVALID_TYPE_ERROR_CODE) + if isinstance(function, (classmethod, staticmethod, property)): + name = type(function).__name__ + raise PydanticUserError( + f'The `@{name}` decorator should be applied after `@validate_call` (put `@{name}` on top)', + code=_INVALID_TYPE_ERROR_CODE, + ) + + if inspect.isclass(function): + raise PydanticUserError( + f'Unable to validate {function}: `validate_call` should be applied to functions, not classes (put `@validate_call` on top of `__init__` or `__new__` instead)', + code=_INVALID_TYPE_ERROR_CODE, + ) + if callable(function): + raise PydanticUserError( + f'Unable to validate {function}: `validate_call` should be applied to functions, not instances or other callables. Use `validate_call` explicitly on `__call__` instead.', + code=_INVALID_TYPE_ERROR_CODE, + ) + + raise PydanticUserError( + f'Unable to validate {function}: `validate_call` should be applied to one of the following: function, method, partial, or lambda', + code=_INVALID_TYPE_ERROR_CODE, + ) + + +@overload +def validate_call( + *, config: ConfigDict | None = None, validate_return: bool = False +) -> Callable[[AnyCallableT], AnyCallableT]: ... + + +@overload +def validate_call(func: AnyCallableT, /) -> AnyCallableT: ... + + +def validate_call( + func: AnyCallableT | None = None, + /, + *, + config: ConfigDict | None = None, + validate_return: bool = False, +) -> AnyCallableT | Callable[[AnyCallableT], AnyCallableT]: + """!!! abstract "Usage Documentation" + [Validation Decorator](../concepts/validation_decorator.md) + + Returns a decorated wrapper around the function that validates the arguments and, optionally, the return value. + + Usage may be either as a plain decorator `@validate_call` or with arguments `@validate_call(...)`. + + Args: + func: The function to be decorated. + config: The configuration dictionary. + validate_return: Whether to validate the return value. + + Returns: + The decorated function. + """ + parent_namespace = _typing_extra.parent_frame_namespace() + + def validate(function: AnyCallableT) -> AnyCallableT: + _check_function_type(function) + validate_call_wrapper = _validate_call.ValidateCallWrapper( + cast(_generate_schema.ValidateCallSupportedTypes, function), config, validate_return, parent_namespace + ) + return _validate_call.update_wrapper_attributes(function, validate_call_wrapper.__call__) # type: ignore + + if func is not None: + return validate(func) + else: + return validate diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py new file mode 100644 index 0000000..7921b04 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/validators.py @@ -0,0 +1,5 @@ +"""The `validators` module is a backport module from V1.""" + +from ._migration import getattr_migration + +__getattr__ = getattr_migration(__name__) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py new file mode 100644 index 0000000..560f34a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/version.py @@ -0,0 +1,113 @@ +"""The `version` module holds the version information for Pydantic.""" + +from __future__ import annotations as _annotations + +import sys + +from pydantic_core import __version__ as __pydantic_core_version__ + +__all__ = 'VERSION', 'version_info' + +VERSION = '2.13.5' +"""The version of Pydantic. + +This version specifier is guaranteed to be compliant with the [specification], +introduced by [PEP 440]. + +[specification]: https://packaging.python.org/en/latest/specifications/version-specifiers/ +[PEP 440]: https://peps.python.org/pep-0440/ +""" + +# Keep this in sync with the version constraint in the `pyproject.toml` dependencies: +_COMPATIBLE_PYDANTIC_CORE_VERSION = '2.46.5' + + +def version_short() -> str: + """Return the `major.minor` part of Pydantic version. + + It returns '2.1' if Pydantic version is '2.1.1'. + """ + return '.'.join(VERSION.split('.')[:2]) + + +def version_info() -> str: + """Return complete version information for Pydantic and its dependencies.""" + import importlib.metadata + import platform + from pathlib import Path + + import pydantic_core._pydantic_core as pdc + + from ._internal import _git as git + + # get data about packages that are closely related to pydantic, use pydantic or often conflict with pydantic + package_names = { + 'email-validator', + 'fastapi', + 'mypy', + 'pydantic-extra-types', + 'pydantic-settings', + 'pyright', + 'typing_extensions', + } + related_packages = [] + + for dist in importlib.metadata.distributions(): + name = dist.metadata['Name'] + if name in package_names: + related_packages.append(f'{name}-{dist.version}') + + pydantic_dir = Path(__file__).parents[1].resolve() + most_recent_commit = ( + git.git_revision(pydantic_dir) if git.is_git_repo(pydantic_dir) and git.have_git() else 'unknown' + ) + + info = { + 'pydantic version': VERSION, + 'pydantic-core version': __pydantic_core_version__, + 'pydantic-core build': getattr(pdc, 'build_info', None) or pdc.build_profile, # pyright: ignore[reportPrivateImportUsage] + 'python version': sys.version, + 'platform': platform.platform(), + 'related packages': ' '.join(related_packages), + 'commit': most_recent_commit, + } + return '\n'.join('{:>30} {}'.format(k + ':', str(v).replace('\n', ' ')) for k, v in info.items()) + + +def check_pydantic_core_version() -> bool: + """Check that the installed `pydantic-core` dependency is compatible.""" + return __pydantic_core_version__ == _COMPATIBLE_PYDANTIC_CORE_VERSION + + +def _ensure_pydantic_core_version() -> None: # pragma: no cover + if not check_pydantic_core_version(): + raise_error = True + # Do not raise the error if pydantic is installed in editable mode (i.e. in development): + if sys.version_info >= (3, 13): # origin property added in 3.13 + from importlib.metadata import distribution + + dist = distribution('pydantic') + if getattr(getattr(dist.origin, 'dir_info', None), 'editable', False): + raise_error = False + + if raise_error: + raise SystemError( + f'The installed pydantic-core version ({__pydantic_core_version__}) is incompatible ' + f'with the current pydantic version, which requires {_COMPATIBLE_PYDANTIC_CORE_VERSION}. ' + "If you encounter this error, make sure that you haven't upgraded pydantic-core manually." + ) + + +def parse_mypy_version(version: str) -> tuple[int, int, int]: + """Parse `mypy` string version to a 3-tuple of ints. + + It parses normal version like `1.11.0` and extra info followed by a `+` sign + like `1.11.0+dev.d6d9d8cd4f27c52edac1f537e236ec48a01e54cb.dirty`. + + Args: + version: The mypy version string. + + Returns: + A triple of ints, e.g. `(1, 11, 0)`. + """ + return tuple(map(int, version.partition('+')[0].split('.'))) # pyright: ignore[reportReturnType] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py new file mode 100644 index 0000000..02323b8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py @@ -0,0 +1,122 @@ +"""Pydantic-specific warnings.""" + +from __future__ import annotations as _annotations + +from .version import version_short + +__all__ = ( + 'PydanticDeprecatedSince20', + 'PydanticDeprecatedSince26', + 'PydanticDeprecatedSince29', + 'PydanticDeprecatedSince210', + 'PydanticDeprecatedSince211', + 'PydanticDeprecatedSince212', + 'PydanticDeprecationWarning', + 'PydanticExperimentalWarning', + 'ArbitraryTypeWarning', + 'UnsupportedFieldAttributeWarning', + 'TypedDictExtraConfigWarning', +) + + +class PydanticDeprecationWarning(DeprecationWarning): + """A Pydantic specific deprecation warning. + + This warning is raised when using deprecated functionality in Pydantic. It provides information on when the + deprecation was introduced and the expected version in which the corresponding functionality will be removed. + + Attributes: + message: Description of the warning. + since: Pydantic version in what the deprecation was introduced. + expected_removal: Pydantic version in what the corresponding functionality expected to be removed. + """ + + message: str + since: tuple[int, int] + expected_removal: tuple[int, int] + + def __init__( + self, message: str, *args: object, since: tuple[int, int], expected_removal: tuple[int, int] | None = None + ) -> None: + super().__init__(message, *args) + self.message = message.rstrip('.') + self.since = since + self.expected_removal = expected_removal if expected_removal is not None else (since[0] + 1, 0) + + def __str__(self) -> str: + message = ( + f'{self.message}. Deprecated in Pydantic V{self.since[0]}.{self.since[1]}' + f' to be removed in V{self.expected_removal[0]}.{self.expected_removal[1]}.' + ) + if self.since == (2, 0): + message += f' See Pydantic V2 Migration Guide at https://errors.pydantic.dev/{version_short()}/migration/' + return message + + +class PydanticDeprecatedSince20(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.0.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 0), expected_removal=(3, 0)) + + +class PydanticDeprecatedSince26(PydanticDeprecationWarning): # pragma: no cover + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.6.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 6), expected_removal=(3, 0)) + + +class PydanticDeprecatedSince29(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.9.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 9), expected_removal=(3, 0)) + + +class PydanticDeprecatedSince210(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.10.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 10), expected_removal=(3, 0)) + + +class PydanticDeprecatedSince211(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.11.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 11), expected_removal=(3, 0)) + + +class PydanticDeprecatedSince212(PydanticDeprecationWarning): + """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.12.""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(2, 12), expected_removal=(3, 0)) + + +class GenericBeforeBaseModelWarning(Warning): + pass + + +class PydanticExperimentalWarning(Warning): + """A Pydantic specific experimental functionality warning. + + It is raised to warn users that the functionality may change or be removed in future versions of Pydantic. + """ + + +class CoreSchemaGenerationWarning(UserWarning): + """A warning raised during core schema generation.""" + + +class ArbitraryTypeWarning(CoreSchemaGenerationWarning): + """A warning raised when Pydantic fails to generate a core schema for an arbitrary type.""" + + +class UnsupportedFieldAttributeWarning(CoreSchemaGenerationWarning): + """A warning raised when a `Field()` attribute isn't supported in the context it is used.""" + + +class TypedDictExtraConfigWarning(CoreSchemaGenerationWarning): + """A warning raised when the [`extra`][pydantic.ConfigDict.extra] configuration is incompatible with the `closed` or `extra_items` specification.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA new file mode 100644 index 0000000..d3f72db --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA @@ -0,0 +1,173 @@ +Metadata-Version: 2.4 +Name: pydantic_core +Version: 2.46.5 +Classifier: Development Status :: 3 - Alpha +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Implementation :: GraalPy +Classifier: Programming Language :: Rust +Classifier: Framework :: Pydantic +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS +Classifier: Typing :: Typed +Requires-Dist: typing-extensions>=4.14.1 +License-File: LICENSE +Summary: Core functionality for Pydantic validation and serialization +Home-Page: https://github.com/pydantic/pydantic +Author-email: Samuel Colvin , Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, David Montague , David Hewitt , Sydney Runkle , Victorien Plot +License-Expression: MIT +Requires-Python: >=3.9 +Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM +Project-URL: Funding, https://github.com/sponsors/samuelcolvin +Project-URL: Homepage, https://github.com/pydantic +Project-URL: Source, https://github.com/pydantic/pydantic/tree/main/pydantic-core + +# pydantic-core + +[![CI](https://github.com/pydantic/pydantic-core/workflows/ci/badge.svg?event=push)](https://github.com/pydantic/pydantic-core/actions?query=event%3Apush+branch%3Amain+workflow%3Aci) +[![Coverage](https://codecov.io/gh/pydantic/pydantic-core/branch/main/graph/badge.svg)](https://codecov.io/gh/pydantic/pydantic-core) +[![pypi](https://img.shields.io/pypi/v/pydantic-core.svg)](https://pypi.python.org/pypi/pydantic-core) +[![versions](https://img.shields.io/pypi/pyversions/pydantic-core.svg)](https://github.com/pydantic/pydantic-core) +[![license](https://img.shields.io/github/license/pydantic/pydantic-core.svg)](https://github.com/pydantic/pydantic-core/blob/main/LICENSE) + +This package provides the core functionality for [pydantic](https://docs.pydantic.dev) validation and serialization. + +Pydantic-core is currently around 17x faster than pydantic V1. +See [`tests/benchmarks/`](./tests/benchmarks/) for details. + +## Example of direct usage + +*NOTE: You should not need to use pydantic-core directly; instead, use pydantic, which in turn uses pydantic-core.* + +```py +from pydantic_core import SchemaValidator, ValidationError + + +v = SchemaValidator( + { + 'type': 'typed-dict', + 'fields': { + 'name': { + 'type': 'typed-dict-field', + 'schema': { + 'type': 'str', + }, + }, + 'age': { + 'type': 'typed-dict-field', + 'schema': { + 'type': 'int', + 'ge': 18, + }, + }, + 'is_developer': { + 'type': 'typed-dict-field', + 'schema': { + 'type': 'default', + 'schema': {'type': 'bool'}, + 'default': True, + }, + }, + }, + } +) + +r1 = v.validate_python({'name': 'Samuel', 'age': 35}) +assert r1 == {'name': 'Samuel', 'age': 35, 'is_developer': True} + +# pydantic-core can also validate JSON directly +r2 = v.validate_json('{"name": "Samuel", "age": 35}') +assert r1 == r2 + +try: + v.validate_python({'name': 'Samuel', 'age': 11}) +except ValidationError as e: + print(e) + """ + 1 validation error for model + age + Input should be greater than or equal to 18 + [type=greater_than_equal, context={ge: 18}, input_value=11, input_type=int] + """ +``` + +## Getting Started + +### Prerequisites + +You'll need: + +1. **[Rust](https://rustup.rs/)** - Rust stable (or nightly for coverage) +2. **[uv](https://docs.astral.sh/uv/getting-started/installation/)** - Fast Python package manager (will install Python 3.9+ automatically) +3. **[git](https://git-scm.com/)** - For version control +4. **[make](https://www.gnu.org/software/make/)** - For running development commands (or use `nmake` on Windows) + +### Quick Start + +```bash +# Clone the repository (or from your fork) +git clone git@github.com:pydantic/pydantic-core.git +cd pydantic-core + +# Install all dependencies using uv, setup pre-commit hooks, and build the development version +make install +``` + +Verify your installation by running: + +```bash +make +``` + +This runs a full development cycle: formatting, building, linting, and testing + +### Development Commands + +Run `make help` to see all available commands, or use these common ones: + +```bash +make build-dev # to build the package during development +make build-prod # to perform an optimised build for benchmarking +make test # to run the tests +make testcov # to run the tests and generate a coverage report +make lint # to run the linter +make format # to format python and rust code +make all # to run to run build-dev + format + lint + test +``` + +### Useful Resources + +* [`python/pydantic_core/_pydantic_core.pyi`](./python/pydantic_core/_pydantic_core.pyi) - Python API types +* [`python/pydantic_core/core_schema.py`](./python/pydantic_core/core_schema.py) - Core schema definitions +* [`tests/`](./tests) - Comprehensive usage examples + +## Profiling + +It's possible to profile the code using the [`flamegraph` utility from `flamegraph-rs`](https://github.com/flamegraph-rs/flamegraph). (Tested on Linux.) You can install this with `cargo install flamegraph`. + +Run `make build-profiling` to install a release build with debugging symbols included (needed for profiling). + +Once that is built, you can profile pytest benchmarks with (e.g.): + +```bash +flamegraph -- pytest tests/benchmarks/test_micro_benchmarks.py -k test_list_of_ints_core_py --benchmark-enable +``` + +The `flamegraph` command will produce an interactive SVG at `flamegraph.svg`. + +## Releasing + +TBC (needs to be integrated into `pydantic` repository release process). + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD new file mode 100644 index 0000000..03b668f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD @@ -0,0 +1,10 @@ +pydantic_core/__init__.py,sha256=nK1ikrdSVK9gapcKrpv_blrp8LCAic1jrK-jkbYHlNI,5115 +pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so,sha256=xjvMyU-oN2g8K8nwBvPb6H8CUokxNeBV4St7-CEyKK0,4692808 +pydantic_core/_pydantic_core.pyi,sha256=H_lG8iKiv3Bpfd3rixY7xzs67gE-s0eiAqJaSbnF8Ss,45932 +pydantic_core/core_schema.py,sha256=1PM31yf_kGpm_H7GLnNFRkIPAQGPL9zh6rf2SxohVxg,155574 +pydantic_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic_core-2.46.5.dist-info/METADATA,sha256=KXaMA4wLs1ZMCofvUO940YojckdL3vC6NWqMD9S9YCg,6573 +pydantic_core-2.46.5.dist-info/WHEEL,sha256=1L2Bvzb2XS9DJjp84sf2rCJj39NxyL0JQf57V8aeU1s,147 +pydantic_core-2.46.5.dist-info/licenses/LICENSE,sha256=Kv3TDVS01itvSIprzBVG6E7FBh8T9CCcA9ASNIeDeVo,1080 +pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json,sha256=FFtLdH9zlujVDKMsja1UgrntCXBOhgZ1-ptTG2Nvg_k,125376 +pydantic_core-2.46.5.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL new file mode 100644 index 0000000..f1fe4e4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: maturin (1.15.0) +Root-Is-Purelib: false +Tag: cp313-cp313-manylinux_2_17_x86_64 +Tag: cp313-cp313-manylinux2014_x86_64 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE new file mode 100644 index 0000000..0716871 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Samuel Colvin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json new file mode 100644 index 0000000..8cb0eb3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json @@ -0,0 +1,3961 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:fc8a7c1f-f0d2-4437-bdbb-a4e265dd881c", + "metadata": { + "timestamp": "2026-08-28T09:11:59.448685593Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.9" + } + ], + "component": { + "type": "library", + "bom-ref": "path+file:///home/runner/work/pydantic/pydantic/pydantic-core#2.46.5", + "name": "pydantic-core", + "version": "2.46.5", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/pydantic-core@2.46.5?download_url=file://.", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pydantic/pydantic" + }, + { + "type": "vcs", + "url": "https://github.com/pydantic/pydantic.git" + } + ], + "components": [ + { + "type": "library", + "bom-ref": "path+file:///home/runner/work/pydantic/pydantic/pydantic-core#2.46.5 bin-target-0", + "name": "_pydantic_core", + "version": "2.46.5", + "purl": "pkg:cargo/pydantic-core@2.46.5?download_url=file://.#src/lib.rs" + } + ] + }, + "properties": [ + { + "name": "cdx:rustc:sbom:target:all_targets", + "value": "true" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.12", + "author": "Tom Kaitchuck ", + "name": "ahash", + "version": "0.8.12", + "description": "A non-cryptographic hash function using AES-NI for high performance", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ahash@0.8.12", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ahash" + }, + { + "type": "vcs", + "url": "https://github.com/tkaitchuck/ahash" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3", + "author": "Andrew Gallant ", + "name": "aho-corasick", + "version": "1.1.3", + "description": "Fast multiple substring searching.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" + } + ], + "licenses": [ + { + "expression": "Unlicense OR MIT" + } + ], + "purl": "pkg:cargo/aho-corasick@1.1.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/BurntSushi/aho-corasick" + }, + { + "type": "vcs", + "url": "https://github.com/BurntSushi/aho-corasick" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "author": "Zakarum ", + "name": "allocator-api2", + "version": "0.2.21", + "description": "Mirror of Rust's allocator API", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/allocator-api2@0.2.21", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/allocator-api2" + }, + { + "type": "website", + "url": "https://github.com/zakarumych/allocator-api2" + }, + { + "type": "vcs", + "url": "https://github.com/zakarumych/allocator-api2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.3.0", + "author": "Josh Stone ", + "name": "autocfg", + "version": "1.3.0", + "description": "Automatic cfg for Rust compiler features", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/autocfg@1.3.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/autocfg/" + }, + { + "type": "vcs", + "url": "https://github.com/cuviper/autocfg" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "author": "Marshall Pierce ", + "name": "base64", + "version": "0.22.1", + "description": "encodes and decodes base64 as bytes or utf8", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/base64@0.22.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/base64" + }, + { + "type": "vcs", + "url": "https://github.com/marshallpierce/rust-base64" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.9.1", + "author": "The Rust Project Developers", + "name": "bitflags", + "version": "2.9.1", + "description": "A macro to generate structures which behave like bitflags. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bitflags@2.9.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitflags" + }, + { + "type": "website", + "url": "https://github.com/bitflags/bitflags" + }, + { + "type": "vcs", + "url": "https://github.com/bitflags/bitflags" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitvec@1.0.1", + "name": "bitvec", + "version": "1.0.1", + "description": "Addresses memory by bits, for packed collections and bitfields", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/bitvec@1.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitvec/latest/bitvec" + }, + { + "type": "website", + "url": "https://bitvecto-rs.github.io/bitvec" + }, + { + "type": "vcs", + "url": "https://github.com/bitvecto-rs/bitvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.19.0", + "author": "Nick Fitzgerald ", + "name": "bumpalo", + "version": "3.19.0", + "description": "A fast bump allocation arena for Rust.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bumpalo@3.19.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bumpalo" + }, + { + "type": "vcs", + "url": "https://github.com/fitzgen/bumpalo" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.0.101", + "author": "Alex Crichton ", + "name": "cc", + "version": "1.0.101", + "description": "A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cc@1.0.101", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cc" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/cc-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0", + "author": "Alex Crichton ", + "name": "cfg-if", + "version": "1.0.0", + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cfg-if@1.0.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cfg-if" + }, + { + "type": "website", + "url": "https://github.com/alexcrichton/cfg-if" + }, + { + "type": "vcs", + "url": "https://github.com/alexcrichton/cfg-if" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "author": "Jane Lusby ", + "name": "displaydoc", + "version": "0.2.5", + "description": "A derive macro for implementing the display Trait via a doc comment and string interpolation ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/displaydoc@0.2.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/displaydoc" + }, + { + "type": "website", + "url": "https://github.com/yaahc/displaydoc" + }, + { + "type": "vcs", + "url": "https://github.com/yaahc/displaydoc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#enum_dispatch@0.3.13", + "author": "Anton Lazarev ", + "name": "enum_dispatch", + "version": "0.3.13", + "description": "Near drop-in replacement for dynamic-dispatched method calls with up to 10x the speed", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/enum_dispatch@0.3.13", + "externalReferences": [ + { + "type": "vcs", + "url": "https://gitlab.com/antonok/enum_dispatch" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "name": "equivalent", + "version": "1.0.2", + "description": "Traits for key comparison in maps.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/equivalent@1.0.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/indexmap-rs/equivalent" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.2.0", + "author": "Orson Peters ", + "name": "foldhash", + "version": "0.2.0", + "description": "A fast, non-cryptographic, minimally DoS-resistant hashing algorithm.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + } + ], + "licenses": [ + { + "expression": "Zlib" + } + ], + "purl": "pkg:cargo/foldhash@0.2.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/orlp/foldhash" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.2", + "author": "The rust-url developers", + "name": "form_urlencoded", + "version": "1.2.2", + "description": "Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/form_urlencoded@1.2.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/servo/rust-url" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#funty@2.0.0", + "author": "myrrlyn ", + "name": "funty", + "version": "2.0.0", + "description": "Trait generalization over the primitive types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/funty@2.0.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/funty" + }, + { + "type": "vcs", + "url": "https://github.com/myrrlyn/funty" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.3", + "author": "The Rand Project Developers", + "name": "getrandom", + "version": "0.3.3", + "description": "A small cross-platform library for retrieving random data from system source", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/getrandom@0.3.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/getrandom" + }, + { + "type": "vcs", + "url": "https://github.com/rust-random/getrandom" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "author": "Amanieu d'Antras ", + "name": "hashbrown", + "version": "0.16.1", + "description": "A Rust port of Google's SwissTable hash map", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hashbrown@0.16.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/hashbrown" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3", + "author": "KokaKiwi ", + "name": "hex", + "version": "0.4.3", + "description": "Encoding and decoding data into/from hexadecimal representation.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hex@0.4.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/hex/" + }, + { + "type": "vcs", + "url": "https://github.com/KokaKiwi/rust-hex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_collections", + "version": "1.5.0", + "description": "Collection of API for use in ICU libraries.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_collections@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_locid", + "version": "1.5.0", + "description": "API for managing Unicode Language and Locale Identifiers", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_locid@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_locid_transform", + "version": "1.5.0", + "description": "API for Unicode Language and Locale Identifiers canonicalization", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_locid_transform@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_locid_transform_data", + "version": "1.5.0", + "description": "Data for the icu_locid_transform crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_locid_transform_data@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_normalizer", + "version": "1.5.0", + "description": "API for normalizing text into Unicode Normalization Forms", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_normalizer@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_normalizer_data", + "version": "1.5.0", + "description": "Data for the icu_normalizer crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_normalizer_data@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1", + "author": "The ICU4X Project Developers", + "name": "icu_properties", + "version": "1.5.1", + "description": "Definitions for Unicode properties", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_properties@1.5.1", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_properties_data", + "version": "1.5.0", + "description": "Data for the icu_properties crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_properties_data@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_provider", + "version": "1.5.0", + "description": "Trait and struct definitions for the ICU data provider", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_provider@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0", + "author": "The ICU4X Project Developers", + "name": "icu_provider_macros", + "version": "1.5.0", + "description": "Proc macros for ICU data providers", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/icu_provider_macros@1.5.0", + "externalReferences": [ + { + "type": "website", + "url": "https://icu4x.unicode.org" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#idna@1.1.0", + "author": "The rust-url developers", + "name": "idna", + "version": "1.1.0", + "description": "IDNA (Internationalizing Domain Names in Applications) and Punycode.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/idna@1.1.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/servo/rust-url/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0", + "author": "The rust-url developers", + "name": "idna_adapter", + "version": "1.2.0", + "description": "Back end adapter for idna", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/idna_adapter@1.2.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/idna_adapter/latest/idna_adapter/" + }, + { + "type": "website", + "url": "https://docs.rs/crate/idna_adapter/latest" + }, + { + "type": "vcs", + "url": "https://github.com/hsivonen/idna_adapter" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.11", + "author": "David Tolnay ", + "name": "itoa", + "version": "1.0.11", + "description": "Fast integer primitive to string conversion", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itoa@1.0.11", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itoa" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/itoa" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#jiter@0.14.0", + "author": "Samuel Colvin ", + "name": "jiter", + "version": "0.14.0", + "description": "Fast Iterable JSON parser", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b6f3b5d3f84b36f4ad09fd1da896d23d9852a1aa86556578dd0289f43dce311d" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/jiter@0.14.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pydantic/jiter/" + }, + { + "type": "vcs", + "url": "https://github.com/pydantic/jiter/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77", + "author": "The wasm-bindgen Developers", + "name": "js-sys", + "version": "0.3.77", + "description": "Bindings for all JS global objects and functions in all JS environments like Node.js and browsers, built on `#[wasm_bindgen]` using the `wasm-bindgen` crate. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/js-sys@0.3.77", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/js-sys" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/wasm-bindgen/" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-float@1.0.5", + "author": "Alex Huszagh ", + "name": "lexical-parse-float", + "version": "1.0.5", + "description": "Efficient parsing of floats from strings.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lexical-parse-float@1.0.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Alexhuszagh/rust-lexical" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-integer@1.0.5", + "author": "Alex Huszagh ", + "name": "lexical-parse-integer", + "version": "1.0.5", + "description": "Efficient parsing of integers from strings.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lexical-parse-integer@1.0.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Alexhuszagh/rust-lexical" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-util@1.0.6", + "author": "Alex Huszagh ", + "name": "lexical-util", + "version": "1.0.6", + "description": "Shared utilities for lexical creates.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lexical-util@1.0.6", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Alexhuszagh/rust-lexical" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.185", + "author": "The Rust Project Developers", + "name": "libc", + "version": "0.2.185", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.185", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.3", + "author": "The ICU4X Project Developers", + "name": "litemap", + "version": "0.7.3", + "description": "A key-value Map implementation based on a flat, sorted Vec.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/litemap@0.7.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/litemap" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.27", + "author": "The Rust Project Developers", + "name": "log", + "version": "0.4.27", + "description": "A lightweight logging facade for Rust ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/log@0.4.27", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/log" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/log" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.16.3", + "author": "Jerome Froelich ", + "name": "lru", + "version": "0.16.3", + "description": "A LRU cache implementation", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/lru@0.16.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/lru/" + }, + { + "type": "website", + "url": "https://github.com/jeromefroe/lru-rs" + }, + { + "type": "vcs", + "url": "https://github.com/jeromefroe/lru-rs.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4", + "author": "Andrew Gallant , bluss", + "name": "memchr", + "version": "2.7.4", + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + } + ], + "licenses": [ + { + "expression": "Unlicense OR MIT" + } + ], + "purl": "pkg:cargo/memchr@2.7.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/memchr/" + }, + { + "type": "website", + "url": "https://github.com/BurntSushi/memchr" + }, + { + "type": "vcs", + "url": "https://github.com/BurntSushi/memchr" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6", + "author": "The Rust Project Developers", + "name": "num-bigint", + "version": "0.4.6", + "description": "Big integer implementation for Rust", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/num-bigint@0.4.6", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/num-bigint" + }, + { + "type": "website", + "url": "https://github.com/rust-num/num-bigint" + }, + { + "type": "vcs", + "url": "https://github.com/rust-num/num-bigint" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46", + "author": "The Rust Project Developers", + "name": "num-integer", + "version": "0.1.46", + "description": "Integer traits and functions", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/num-integer@0.1.46", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/num-integer" + }, + { + "type": "website", + "url": "https://github.com/rust-num/num-integer" + }, + { + "type": "vcs", + "url": "https://github.com/rust-num/num-integer" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "author": "The Rust Project Developers", + "name": "num-traits", + "version": "0.2.19", + "description": "Numeric traits for generic mathematics", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/num-traits@0.2.19", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/num-traits" + }, + { + "type": "website", + "url": "https://github.com/rust-num/num-traits" + }, + { + "type": "vcs", + "url": "https://github.com/rust-num/num-traits" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "author": "Aleksey Kladov ", + "name": "once_cell", + "version": "1.21.3", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "author": "The rust-url developers", + "name": "percent-encoding", + "version": "2.3.2", + "description": "Percent encoding and decoding", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/percent-encoding@2.3.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/servo/rust-url/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.6.0", + "name": "portable-atomic", + "version": "1.6.0", + "description": "Portable atomic types including support for 128-bit atomics, atomic float, etc. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/portable-atomic@1.6.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/portable-atomic" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "author": "David Tolnay , Alex Crichton ", + "name": "proc-macro2", + "version": "1.0.86", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.86", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-build-config", + "version": "0.28.3", + "description": "Build configuration for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-build-config@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-ffi", + "version": "0.28.3", + "description": "Python-API bindings for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-ffi@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros-backend", + "version": "0.28.3", + "description": "Code generation for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros-backend@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros", + "version": "0.28.3", + "description": "Proc macros for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3", + "version": "0.28.3", + "description": "Bindings to Python interpreter", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3@0.28.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crate/pyo3/" + }, + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#python3-dll-a@0.2.14", + "author": "Sergey Kvachonok , messense , Adam Reichold ", + "name": "python3-dll-a", + "version": "0.2.14", + "description": "Standalone python3(y)(t).dll import library generator", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/python3-dll-a@0.2.14", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/PyO3/python3-dll-a" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "author": "David Tolnay ", + "name": "quote", + "version": "1.0.44", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#r-efi@5.2.0", + "name": "r-efi", + "version": "5.2.0", + "description": "UEFI Reference Specification Protocol Constants and Definitions", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0 OR LGPL-2.1-or-later" + } + ], + "purl": "pkg:cargo/r-efi@5.2.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/r-efi/r-efi/wiki" + }, + { + "type": "vcs", + "url": "https://github.com/r-efi/r-efi" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#radium@0.7.0", + "author": "Nika Layzell , myrrlyn ", + "name": "radium", + "version": "0.7.0", + "description": "Portable interfaces for maybe-atomic types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/radium@0.7.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/radium" + }, + { + "type": "website", + "url": "https://github.com/bitvecto-rs/radium" + }, + { + "type": "vcs", + "url": "https://github.com/bitvecto-rs/radium" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.13", + "author": "The Rust Project Developers, Andrew Gallant ", + "name": "regex-automata", + "version": "0.4.13", + "description": "Automata construction and matching using regular expressions.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/regex-automata@0.4.13", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/regex-automata" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/regex/tree/master/regex-automata" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/regex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5", + "author": "The Rust Project Developers, Andrew Gallant ", + "name": "regex-syntax", + "version": "0.8.5", + "description": "A regular expression parser.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/regex-syntax@0.8.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/regex-syntax" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/regex/tree/master/regex-syntax" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#regex@1.12.3", + "author": "The Rust Project Developers, Andrew Gallant ", + "name": "regex", + "version": "1.12.3", + "description": "An implementation of regular expressions for Rust. This implementation uses finite automata and guarantees linear time matching on all inputs. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/regex@1.12.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/regex" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/regex" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/regex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.17", + "author": "David Tolnay ", + "name": "rustversion", + "version": "1.0.17", + "description": "Conditional compilation according to rustc compiler version", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/rustversion@1.0.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustversion" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/rustversion" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "author": "Erick Tryzelaar , David Tolnay ", + "name": "serde", + "version": "1.0.228", + "description": "A generic serialization/deserialization framework", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "author": "Erick Tryzelaar , David Tolnay ", + "name": "serde_core", + "version": "1.0.228", + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_core@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_core" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "author": "Erick Tryzelaar , David Tolnay ", + "name": "serde_derive", + "version": "1.0.228", + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_derive@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://serde.rs/derive.html" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "author": "Erick Tryzelaar , David Tolnay ", + "name": "serde_json", + "version": "1.0.149", + "description": "A JSON serialization file format", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_json@1.0.149", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_json" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/json" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "author": "The Servo Project Developers", + "name": "smallvec", + "version": "1.15.1", + "description": "'Small vector' optimization: store up to a small number of items on the stack", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/smallvec@1.15.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/smallvec/" + }, + { + "type": "vcs", + "url": "https://github.com/servo/rust-smallvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#speedate@0.17.0", + "author": "Samuel Colvin ", + "name": "speedate", + "version": "0.17.0", + "description": "Fast and simple datetime, date, time and duration parsing", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "aba069c070b5e213f2a094deb7e5ed50ecb092be36102a4f4042e8d2056d060e" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/speedate@0.17.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pydantic/speedate/" + }, + { + "type": "vcs", + "url": "https://github.com/pydantic/speedate/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0", + "author": "Robert Grosse ", + "name": "stable_deref_trait", + "version": "1.2.0", + "description": "An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/stable_deref_trait@1.2.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/stable_deref_trait/1.2.0/stable_deref_trait" + }, + { + "type": "vcs", + "url": "https://github.com/storyyeller/stable_deref_trait" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0", + "author": "Nikolai Vazquez", + "name": "static_assertions", + "version": "1.1.0", + "description": "Compile-time assertions to ensure that invariants are met.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/static_assertions@1.1.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/static_assertions/" + }, + { + "type": "website", + "url": "https://github.com/nvzqz/static-assertions-rs" + }, + { + "type": "vcs", + "url": "https://github.com/nvzqz/static-assertions-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.27.2", + "author": "Peter Glotfelty ", + "name": "strum", + "version": "0.27.2", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum@0.27.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.27.2", + "author": "Peter Glotfelty ", + "name": "strum_macros", + "version": "0.27.2", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum_macros@0.27.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "author": "David Tolnay ", + "name": "syn", + "version": "2.0.82", + "description": "Parser for Rust source code", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.82", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1", + "author": "Nika Layzell ", + "name": "synstructure", + "version": "0.13.1", + "description": "Helper methods and macros for custom derives", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/synstructure@0.13.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/synstructure" + }, + { + "type": "vcs", + "url": "https://github.com/mystor/synstructure" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tap@1.0.1", + "author": "Elliott Linder , myrrlyn ", + "name": "tap", + "version": "1.0.1", + "description": "Generic extensions for tapping values in Rust", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tap@1.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/tap" + }, + { + "type": "website", + "url": "https://github.com/myrrlyn/tap" + }, + { + "type": "vcs", + "url": "https://github.com/myrrlyn/tap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.4", + "author": "Dan Gohman ", + "name": "target-lexicon", + "version": "0.13.4", + "description": "LLVM target triple types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/target-lexicon@0.13.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/target-lexicon/" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/target-lexicon" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "author": "The ICU4X Project Developers", + "name": "tinystr", + "version": "0.7.6", + "description": "A small ASCII-only bounded length string representation.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/tinystr@0.7.6", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12", + "author": "David Tolnay ", + "name": "unicode-ident", + "version": "1.0.12", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + } + ], + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-DFS-2016" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.12", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#url@2.5.8", + "author": "The rust-url developers", + "name": "url", + "version": "2.5.8", + "description": "URL library for Rust, based on the WHATWG URL Standard", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/url@2.5.8", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/url" + }, + { + "type": "vcs", + "url": "https://github.com/servo/rust-url" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5", + "author": "Henri Sivonen ", + "name": "utf16_iter", + "version": "1.0.5", + "description": "Iterator by char over potentially-invalid UTF-16 in &[u16]", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/utf16_iter@1.0.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/utf16_iter/" + }, + { + "type": "website", + "url": "https://docs.rs/utf16_iter/" + }, + { + "type": "vcs", + "url": "https://github.com/hsivonen/utf16_iter" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4", + "author": "Henri Sivonen ", + "name": "utf8_iter", + "version": "1.0.4", + "description": "Iterator by char over potentially-invalid UTF-8 in &[u8]", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/utf8_iter@1.0.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/utf8_iter/" + }, + { + "type": "website", + "url": "https://docs.rs/utf8_iter/" + }, + { + "type": "vcs", + "url": "https://github.com/hsivonen/utf8_iter" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#uuid@1.23.0", + "author": "Ashley Mannix, Dylan DPC, Hunar Roop Kahlon", + "name": "uuid", + "version": "1.23.0", + "description": "A library to generate and parse UUIDs.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/uuid@1.23.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/uuid" + }, + { + "type": "website", + "url": "https://github.com/uuid-rs/uuid" + }, + { + "type": "vcs", + "url": "https://github.com/uuid-rs/uuid" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5", + "author": "Sergio Benitez ", + "name": "version_check", + "version": "0.9.5", + "description": "Tiny crate to check the version of the installed/running rustc.", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/version_check@0.9.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/version_check/" + }, + { + "type": "vcs", + "url": "https://github.com/SergioBenitez/version_check" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasi@0.14.2+wasi-0.2.4", + "author": "The Cranelift Project Developers", + "name": "wasi", + "version": "0.14.2+wasi-0.2.4", + "description": "WASI API bindings for Rust", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/wasi@0.14.2+wasi-0.2.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasi" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/wasi-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100", + "author": "The wasm-bindgen Developers", + "name": "wasm-bindgen-backend", + "version": "0.2.100", + "description": "Backend code generation of the wasm-bindgen tool ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/wasm-bindgen-backend@0.2.100", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasm-bindgen-backend" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/wasm-bindgen/" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100", + "author": "The wasm-bindgen Developers", + "name": "wasm-bindgen-macro-support", + "version": "0.2.100", + "description": "The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/wasm-bindgen-macro-support@0.2.100", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasm-bindgen" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/wasm-bindgen/" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100", + "author": "The wasm-bindgen Developers", + "name": "wasm-bindgen-macro", + "version": "0.2.100", + "description": "Definition of the `#[wasm_bindgen]` attribute, an internal dependency ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/wasm-bindgen-macro@0.2.100", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasm-bindgen" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/wasm-bindgen/" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100", + "author": "The wasm-bindgen Developers", + "name": "wasm-bindgen-shared", + "version": "0.2.100", + "description": "Shared support between wasm-bindgen and wasm-bindgen cli, an internal dependency. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/wasm-bindgen-shared@0.2.100", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasm-bindgen-shared" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/wasm-bindgen/" + }, + { + "type": "other", + "url": "wasm_bindgen" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100", + "author": "The wasm-bindgen Developers", + "name": "wasm-bindgen", + "version": "0.2.100", + "description": "Easy support for interacting between JS and Rust. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/wasm-bindgen@0.2.100", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wasm-bindgen" + }, + { + "type": "website", + "url": "https://rustwasm.github.io/" + }, + { + "type": "vcs", + "url": "https://github.com/rustwasm/wasm-bindgen" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.39.0", + "name": "wit-bindgen-rt", + "version": "0.39.0", + "description": "Runtime support for the `wit-bindgen` crate ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/wit-bindgen-rt@0.39.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/bytecodealliance/wit-bindgen" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/wit-bindgen" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0", + "name": "write16", + "version": "1.0.0", + "description": "A UTF-16 analog of the Write trait", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/write16@1.0.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/write16/" + }, + { + "type": "website", + "url": "https://docs.rs/write16/" + }, + { + "type": "vcs", + "url": "https://github.com/hsivonen/write16" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5", + "author": "The ICU4X Project Developers", + "name": "writeable", + "version": "0.5.5", + "description": "A more efficient alternative to fmt::Display", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/writeable@0.5.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#wyz@0.5.1", + "author": "myrrlyn ", + "name": "wyz", + "version": "0.5.1", + "description": "myrrlyn’s utility collection", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/wyz@0.5.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/wyz" + }, + { + "type": "website", + "url": "https://myrrlyn.net/crates/wyz" + }, + { + "type": "vcs", + "url": "https://github.com/myrrlyn/wyz" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.4", + "author": "Manish Goregaokar ", + "name": "yoke-derive", + "version": "0.7.4", + "description": "Custom derive for the yoke crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/yoke-derive@0.7.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.4", + "author": "Manish Goregaokar ", + "name": "yoke", + "version": "0.7.4", + "description": "Abstraction allowing borrowed data to be carried along with the backing data it borrows from", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/yoke@0.7.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.8.25", + "author": "Joshua Liebow-Feeser , Jack Wrenn ", + "name": "zerocopy-derive", + "version": "0.8.25", + "description": "Custom derive for traits from the zerocopy crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" + } + ], + "licenses": [ + { + "expression": "BSD-2-Clause OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/zerocopy-derive@0.8.25", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/google/zerocopy" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.25", + "author": "Joshua Liebow-Feeser , Jack Wrenn ", + "name": "zerocopy", + "version": "0.8.25", + "description": "Zerocopy makes zero-cost memory manipulation effortless. We write \"unsafe\" so you don't have to.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" + } + ], + "licenses": [ + { + "expression": "BSD-2-Clause OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/zerocopy@0.8.25", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/google/zerocopy" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.4", + "author": "Manish Goregaokar ", + "name": "zerofrom-derive", + "version": "0.1.4", + "description": "Custom derive for the zerofrom crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/zerofrom-derive@0.1.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4", + "author": "Manish Goregaokar ", + "name": "zerofrom", + "version": "0.1.4", + "description": "ZeroFrom trait for constructing", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/zerofrom@0.1.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3", + "author": "Manish Goregaokar ", + "name": "zerovec-derive", + "version": "0.10.3", + "description": "Custom derive for the zerovec crate", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/zerovec-derive@0.10.3", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4", + "author": "The ICU4X Project Developers", + "name": "zerovec", + "version": "0.10.4", + "description": "Zero-copy vector backed by a byte array", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" + } + ], + "licenses": [ + { + "expression": "Unicode-3.0" + } + ], + "purl": "pkg:cargo/zerovec@0.10.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/unicode-org/icu4x" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.6", + "author": "David Tolnay ", + "name": "zmij", + "version": "1.0.6", + "description": "A double-to-string conversion algorithm based on Schubfach and yy", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/zmij@1.0.6", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/zmij" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/zmij" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///home/runner/work/pydantic/pydantic/pydantic-core#2.46.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.12", + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#enum_dispatch@0.3.13", + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3", + "registry+https://github.com/rust-lang/crates.io-index#idna@1.1.0", + "registry+https://github.com/rust-lang/crates.io-index#jiter@0.14.0", + "registry+https://github.com/rust-lang/crates.io-index#lru@0.16.3", + "registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#regex@1.12.3", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "registry+https://github.com/rust-lang/crates.io-index#speedate@0.17.0", + "registry+https://github.com/rust-lang/crates.io-index#strum@0.27.2", + "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.27.2", + "registry+https://github.com/rust-lang/crates.io-index#url@2.5.8", + "registry+https://github.com/rust-lang/crates.io-index#uuid@1.23.0", + "registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.12", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0", + "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.3", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5", + "registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.25" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.3.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.9.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitvec@1.0.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#funty@2.0.0", + "registry+https://github.com/rust-lang/crates.io-index#radium@0.7.0", + "registry+https://github.com/rust-lang/crates.io-index#tap@1.0.1", + "registry+https://github.com/rust-lang/crates.io-index#wyz@0.5.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.19.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.0.101" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#enum_dispatch@0.3.13", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.2.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#funty@2.0.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.185", + "registry+https://github.com/rust-lang/crates.io-index#r-efi@5.2.0", + "registry+https://github.com/rust-lang/crates.io-index#wasi@0.14.2+wasi-0.2.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.4", + "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.3", + "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1", + "registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5", + "registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0", + "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5", + "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.4", + "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#idna@1.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.11" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#jiter@0.14.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.12", + "registry+https://github.com/rust-lang/crates.io-index#bitvec@1.0.1", + "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-float@1.0.5", + "registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-float@1.0.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-integer@1.0.5", + "registry+https://github.com/rust-lang/crates.io-index#lexical-util@1.0.6", + "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-integer@1.0.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lexical-util@1.0.6", + "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lexical-util@1.0.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.185" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.3" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.27" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.16.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.3.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.6.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#python3-dll-a@0.2.14", + "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.185", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.185", + "registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.6.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#python3-dll-a@0.2.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.0.101" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#r-efi@5.2.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#radium@0.7.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.13", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4", + "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#regex@1.12.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4", + "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.13", + "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.17" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.11", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.6" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#speedate@0.17.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lexical-parse-float@1.0.5", + "registry+https://github.com/rust-lang/crates.io-index#strum@0.27.2", + "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.27.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.27.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.27.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.27.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tap@1.0.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5", + "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#url@2.5.8", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.2", + "registry+https://github.com/rust-lang/crates.io-index#idna@1.1.0", + "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#uuid@1.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasi@0.14.2+wasi-0.2.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.39.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.19.0", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.27", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.17", + "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.39.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.9.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#wyz@0.5.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#tap@1.0.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0", + "registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.4", + "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.8.25", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.25", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.8.25" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82", + "registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.86", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.82" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.4", + "registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.4", + "registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.6" + } + ] +} \ No newline at end of file diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py new file mode 100644 index 0000000..d5facd1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py @@ -0,0 +1,171 @@ +from __future__ import annotations + +import sys as _sys +from typing import Any as _Any + +from typing_extensions import Sentinel + +from ._pydantic_core import ( + ArgsKwargs, + MultiHostUrl, + PydanticCustomError, + PydanticKnownError, + PydanticOmit, + PydanticSerializationError, + PydanticSerializationUnexpectedValue, + PydanticUndefined, + PydanticUndefinedType, + PydanticUseDefault, + SchemaError, + SchemaSerializer, + SchemaValidator, + Some, + TzInfo, + Url, + ValidationError, + __version__, + from_json, + to_json, + to_jsonable_python, +) +from .core_schema import CoreConfig, CoreSchema, CoreSchemaType, ErrorType + +if _sys.version_info < (3, 11): + from typing_extensions import NotRequired as _NotRequired +else: + from typing import NotRequired as _NotRequired + +if _sys.version_info < (3, 12): + from typing_extensions import TypedDict as _TypedDict +else: + from typing import TypedDict as _TypedDict + +__all__ = [ + '__version__', + 'UNSET', + 'CoreConfig', + 'CoreSchema', + 'CoreSchemaType', + 'SchemaValidator', + 'SchemaSerializer', + 'Some', + 'Url', + 'MultiHostUrl', + 'ArgsKwargs', + 'PydanticUndefined', + 'PydanticUndefinedType', + 'SchemaError', + 'ErrorDetails', + 'InitErrorDetails', + 'ValidationError', + 'PydanticCustomError', + 'PydanticKnownError', + 'PydanticOmit', + 'PydanticUseDefault', + 'PydanticSerializationError', + 'PydanticSerializationUnexpectedValue', + 'TzInfo', + 'to_json', + 'from_json', + 'to_jsonable_python', +] + + +class ErrorDetails(_TypedDict): + type: str + """ + The type of error that occurred, this is an identifier designed for + programmatic use that will change rarely or never. + + `type` is unique for each error message, and can hence be used as an identifier to build custom error messages. + """ + loc: tuple[int | str, ...] + """Tuple of strings and ints identifying where in the schema the error occurred.""" + msg: str + """A human readable error message.""" + input: _Any + """The input data at this `loc` that caused the error.""" + ctx: _NotRequired[dict[str, _Any]] + """ + Values which are required to render the error message, and could hence be useful in rendering custom error messages. + Also useful for passing custom error data forward. + """ + url: _NotRequired[str] + """ + The documentation URL giving information about the error. No URL is available if + a [`PydanticCustomError`][pydantic_core.PydanticCustomError] is used. + """ + + +class InitErrorDetails(_TypedDict): + type: str | PydanticCustomError + """The type of error that occurred, this should be a "slug" identifier that changes rarely or never.""" + loc: _NotRequired[tuple[int | str, ...]] + """Tuple of strings and ints identifying where in the schema the error occurred.""" + input: _Any + """The input data at this `loc` that caused the error.""" + ctx: _NotRequired[dict[str, _Any]] + """ + Values which are required to render the error message, and could hence be useful in rendering custom error messages. + Also useful for passing custom error data forward. + """ + + +class ErrorTypeInfo(_TypedDict): + """ + Gives information about errors. + """ + + type: ErrorType + """The type of error that occurred, this should be a "slug" identifier that changes rarely or never.""" + message_template_python: str + """String template to render a human readable error message from using context, when the input is Python.""" + example_message_python: str + """Example of a human readable error message, when the input is Python.""" + message_template_json: _NotRequired[str] + """String template to render a human readable error message from using context, when the input is JSON data.""" + example_message_json: _NotRequired[str] + """Example of a human readable error message, when the input is JSON data.""" + example_context: dict[str, _Any] | None + """Example of context values.""" + + +class MultiHostHost(_TypedDict): + """ + A host part of a multi-host URL. + """ + + username: str | None + """The username part of this host, or `None`.""" + password: str | None + """The password part of this host, or `None`.""" + host: str | None + """The host part of this host, or `None`.""" + port: int | None + """The port part of this host, or `None`.""" + + +MISSING = Sentinel('MISSING') +"""A singleton indicating a field value was not provided during validation. + +This singleton can be used a default value, as an alternative to `None` when it has +an explicit meaning. During serialization, any field with `MISSING` as a value is excluded +from the output. + +Example: + ```python + from pydantic import BaseModel + + from pydantic_core import MISSING + + + class Configuration(BaseModel): + timeout: int | None | MISSING = MISSING + + + # configuration defaults, stored somewhere else: + defaults = {'timeout': 200} + + conf = Configuration.model_validate({...}) + timeout = conf.timeout if timeout.timeout is not MISSING else defaults['timeout'] +""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so new file mode 100644 index 0000000..87eb09c Binary files /dev/null and b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so differ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi new file mode 100644 index 0000000..122c86f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi @@ -0,0 +1,1056 @@ +import datetime +from collections.abc import Mapping +from typing import Any, Callable, Generic, Literal, TypeVar, final + +from _typeshed import SupportsAllComparisons +from typing_extensions import LiteralString, Self, TypeAlias + +from pydantic_core import ErrorDetails, ErrorTypeInfo, InitErrorDetails, MultiHostHost +from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType, ExtraBehavior + +__all__ = [ + '__version__', + 'build_profile', + 'build_info', + '_recursion_limit', + 'ArgsKwargs', + 'SchemaValidator', + 'SchemaSerializer', + 'Url', + 'MultiHostUrl', + 'SchemaError', + 'ValidationError', + 'PydanticCustomError', + 'PydanticKnownError', + 'PydanticOmit', + 'PydanticUseDefault', + 'PydanticSerializationError', + 'PydanticSerializationUnexpectedValue', + 'PydanticUndefined', + 'PydanticUndefinedType', + 'Some', + 'to_json', + 'from_json', + 'to_jsonable_python', + 'list_all_errors', + 'TzInfo', +] +__version__: str +build_profile: str +build_info: str +_recursion_limit: int + +_T = TypeVar('_T', default=Any, covariant=True) + +_StringInput: TypeAlias = 'dict[str, _StringInput]' + +@final +class Some(Generic[_T]): + """ + Similar to Rust's [`Option::Some`](https://doc.rust-lang.org/std/option/enum.Option.html) type, this + identifies a value as being present, and provides a way to access it. + + Generally used in a union with `None` to different between "some value which could be None" and no value. + """ + + __match_args__ = ('value',) + + @property + def value(self) -> _T: + """ + Returns the value wrapped by `Some`. + """ + @classmethod + def __class_getitem__(cls, item: Any, /) -> type[Self]: ... + +@final +class SchemaValidator: + """ + `SchemaValidator` is the Python wrapper for `pydantic-core`'s Rust validation logic, internally it owns one + `CombinedValidator` which may in turn own more `CombinedValidator`s which make up the full schema validator. + """ + + # note: pyo3 currently supports __new__, but not __init__, though we include __init__ stubs + # and docstrings here (and in the following classes) for documentation purposes + + def __init__(self, schema: CoreSchema, config: CoreConfig | None = None, _use_prebuilt: bool = True) -> None: + """Initializes the `SchemaValidator`. + + Arguments: + schema: The `CoreSchema` to use for validation. + config: Optionally a [`CoreConfig`][pydantic_core.core_schema.CoreConfig] to configure validation. + _use_prebuilt: Whether to use pre-built validators (False during rebuilds to avoid stale references). + """ + + def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None, _use_prebuilt: bool = True) -> Self: ... + @property + def title(self) -> str: + """ + The title of the schema, as used in the heading of [`ValidationError.__str__()`][pydantic_core.ValidationError]. + """ + def validate_python( + self, + input: Any, + *, + strict: bool | None = None, + extra: ExtraBehavior | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + self_instance: Any | None = None, + allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Any: + """ + Validate a Python object against the schema and return the validated object. + + Arguments: + input: The Python object to validate. + strict: Whether to validate the object in strict mode. + If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. + extra: Whether to ignore, allow, or forbid extra data during model validation. + If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. + from_attributes: Whether to validate objects as inputs to models by extracting attributes. + If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used. + context: The context to use for validation, this is passed to functional validators as + [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. + self_instance: An instance of a model set attributes on from validation, this is used when running + validation from the `__init__` method of a model. + allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences + and mappings are ignored. + `'trailing-strings'` means any final unfinished JSON string is included in the result. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Raises: + ValidationError: If validation fails. + Exception: Other error types maybe raised if internal errors occur. + + Returns: + The validated object. + """ + def isinstance_python( + self, + input: Any, + *, + strict: bool | None = None, + extra: ExtraBehavior | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + self_instance: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> bool: + """ + Similar to [`validate_python()`][pydantic_core.SchemaValidator.validate_python] but returns a boolean. + + Arguments match `validate_python()`. This method will not raise `ValidationError`s but will raise internal + errors. + + Returns: + `True` if validation succeeds, `False` if validation fails. + """ + def validate_json( + self, + input: str | bytes | bytearray, + *, + strict: bool | None = None, + extra: ExtraBehavior | None = None, + context: Any | None = None, + self_instance: Any | None = None, + allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Any: + """ + Validate JSON data directly against the schema and return the validated Python object. + + This method should be significantly faster than `validate_python(json.loads(json_data))` as it avoids the + need to create intermediate Python objects + + It also handles constructing the correct Python type even in strict mode, where + `validate_python(json.loads(json_data))` would fail validation. + + Arguments: + input: The JSON data to validate. + strict: Whether to validate the object in strict mode. + If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. + extra: Whether to ignore, allow, or forbid extra data during model validation. + If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. + context: The context to use for validation, this is passed to functional validators as + [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. + self_instance: An instance of a model set attributes on from validation. + allow_partial: Whether to allow partial validation; if `True` incomplete JSON will be parsed successfully + and errors in the last element of sequences and mappings are ignored. + `'trailing-strings'` means any final unfinished JSON string is included in the result. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Raises: + ValidationError: If validation fails or if the JSON data is invalid. + Exception: Other error types maybe raised if internal errors occur. + + Returns: + The validated Python object. + """ + def validate_strings( + self, + input: _StringInput, + *, + strict: bool | None = None, + extra: ExtraBehavior | None = None, + context: Any | None = None, + allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> Any: + """ + Validate a string against the schema and return the validated Python object. + + This is similar to `validate_json` but applies to scenarios where the input will be a string but not + JSON data, e.g. URL fragments, query parameters, etc. + + Arguments: + input: The input as a string, or bytes/bytearray if `strict=False`. + strict: Whether to validate the object in strict mode. + If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. + extra: Whether to ignore, allow, or forbid extra data during model validation. + If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. + context: The context to use for validation, this is passed to functional validators as + [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. + allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences + and mappings are ignored. + `'trailing-strings'` means any final unfinished JSON string is included in the result. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Raises: + ValidationError: If validation fails or if the JSON data is invalid. + Exception: Other error types maybe raised if internal errors occur. + + Returns: + The validated Python object. + """ + def validate_assignment( + self, + obj: Any, + field_name: str, + field_value: Any, + *, + strict: bool | None = None, + extra: ExtraBehavior | None = None, + from_attributes: bool | None = None, + context: Any | None = None, + by_alias: bool | None = None, + by_name: bool | None = None, + ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any] | None, set[str]]: + """ + Validate an assignment to a field on a model. + + Arguments: + obj: The model instance being assigned to. + field_name: The name of the field to validate assignment for. + field_value: The value to assign to the field. + strict: Whether to validate the object in strict mode. + If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. + extra: Whether to ignore, allow, or forbid extra data during model validation. + If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. + from_attributes: Whether to validate objects as inputs to models by extracting attributes. + If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used. + context: The context to use for validation, this is passed to functional validators as + [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. + by_alias: Whether to use the field's alias when validating against the provided input data. + by_name: Whether to use the field's name when validating against the provided input data. + + Raises: + ValidationError: If validation fails. + Exception: Other error types maybe raised if internal errors occur. + + Returns: + Either the model dict or a tuple of `(model_data, model_extra, fields_set)` + """ + def get_default_value(self, *, strict: bool | None = None, context: Any = None) -> Some | None: + """ + Get the default value for the schema, including running default value validation. + + Arguments: + strict: Whether to validate the default value in strict mode. + If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. + context: The context to use for validation, this is passed to functional validators as + [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. + + Raises: + ValidationError: If validation fails. + Exception: Other error types maybe raised if internal errors occur. + + Returns: + `None` if the schema has no default value, otherwise a [`Some`][pydantic_core.Some] containing the default. + """ + +# In reality, `bool` should be replaced by `Literal[True]` but mypy fails to correctly apply bidirectional type inference +# (e.g. when using `{'a': {'b': True}}`). +_IncEx: TypeAlias = set[int] | set[str] | Mapping[int, _IncEx | bool] | Mapping[str, _IncEx | bool] + +@final +class SchemaSerializer: + """ + `SchemaSerializer` is the Python wrapper for `pydantic-core`'s Rust serialization logic, internally it owns one + `CombinedSerializer` which may in turn own more `CombinedSerializer`s which make up the full schema serializer. + """ + + def __init__(self, schema: CoreSchema, config: CoreConfig | None = None, _use_prebuilt: bool = True) -> None: + """Initializes the `SchemaSerializer`. + + Arguments: + schema: The `CoreSchema` to use for serialization. + config: Optionally a [`CoreConfig`][pydantic_core.core_schema.CoreConfig] to to configure serialization. + _use_prebuilt: Whether to use pre-built validators (False during rebuilds to avoid stale references). + """ + + def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None, _use_prebuilt: bool = True) -> Self: ... + def to_python( + self, + value: Any, + *, + mode: str | None = None, + include: _IncEx | None = None, + exclude: _IncEx | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, + ) -> Any: + """ + Serialize/marshal a Python object to a Python object including transforming and filtering data. + + Arguments: + value: The Python object to serialize. + mode: The serialization mode to use, either `'python'` or `'json'`, defaults to `'python'`. In JSON mode, + all values are converted to JSON compatible types, e.g. `None`, `int`, `float`, `str`, `list`, `dict`. + include: A set of fields to include, if `None` all fields are included. + exclude: A set of fields to exclude, if `None` no fields are excluded. + by_alias: Whether to use the alias names of fields. + exclude_unset: Whether to exclude fields that are not set, + e.g. are not included in `__pydantic_fields_set__`. + exclude_defaults: Whether to exclude fields that are equal to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + round_trip: Whether to enable serialization and validation round-trip support. + warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered, + if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: The context to use for serialization, this is passed to functional serializers as + [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. + + Raises: + PydanticSerializationError: If serialization fails and no `fallback` function is provided. + + Returns: + The serialized Python object. + """ + def to_json( + self, + value: Any, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: _IncEx | None = None, + exclude: _IncEx | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal['none', 'warn', 'error'] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, + ) -> bytes: + """ + Serialize a Python object to JSON including transforming and filtering data. + + Arguments: + value: The Python object to serialize. + indent: If `None`, the JSON will be compact, otherwise it will be pretty-printed with the indent provided. + ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. + If `False` (the default), these characters will be output as-is. + include: A set of fields to include, if `None` all fields are included. + exclude: A set of fields to exclude, if `None` no fields are excluded. + by_alias: Whether to use the alias names of fields. + exclude_unset: Whether to exclude fields that are not set, + e.g. are not included in `__pydantic_fields_set__`. + exclude_defaults: Whether to exclude fields that are equal to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + round_trip: Whether to enable serialization and validation round-trip support. + warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered, + if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: The context to use for serialization, this is passed to functional serializers as + [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. + + Raises: + PydanticSerializationError: If serialization fails and no `fallback` function is provided. + + Returns: + JSON bytes. + """ + +def to_json( + value: Any, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: _IncEx | None = None, + exclude: _IncEx | None = None, + # Note: In Pydantic 2.11, the default value of `by_alias` on `SchemaSerializer` was changed from `True` to `None`, + # to be consistent with the Pydantic "dump" methods. However, the default of `True` was kept here for + # backwards compatibility. In Pydantic V3, `by_alias` is expected to default to `True` everywhere: + by_alias: bool = True, + exclude_none: bool = False, + round_trip: bool = False, + timedelta_mode: Literal['iso8601', 'float'] = 'iso8601', + temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601', + bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8', + inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants', + serialize_unknown: bool = False, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, +) -> bytes: + """ + Serialize a Python object to JSON including transforming and filtering data. + + This is effectively a standalone version of [`SchemaSerializer.to_json`][pydantic_core.SchemaSerializer.to_json]. + + Arguments: + value: The Python object to serialize. + indent: If `None`, the JSON will be compact, otherwise it will be pretty-printed with the indent provided. + ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. + If `False` (the default), these characters will be output as-is. + include: A set of fields to include, if `None` all fields are included. + exclude: A set of fields to exclude, if `None` no fields are excluded. + by_alias: Whether to use the alias names of fields. + exclude_none: Whether to exclude fields that have a value of `None`. + round_trip: Whether to enable serialization and validation round-trip support. + timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`. + temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`. + `iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float. + + bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`. + inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`. + serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails + `""` will be used. + fallback: A function to call when an unknown value is encountered, + if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: The context to use for serialization, this is passed to functional serializers as + [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. + + Raises: + PydanticSerializationError: If serialization fails and no `fallback` function is provided. + + Returns: + JSON bytes. + """ + +def from_json( + data: str | bytes | bytearray, + *, + allow_inf_nan: bool = True, + cache_strings: bool | Literal['all', 'keys', 'none'] = True, + allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, +) -> Any: + """ + Deserialize JSON data to a Python object. + + This is effectively a faster version of `json.loads()`, with some extra functionality. + + Arguments: + data: The JSON data to deserialize. + allow_inf_nan: Whether to allow `Infinity`, `-Infinity` and `NaN` values as `json.loads()` does by default. + cache_strings: Whether to cache strings to avoid constructing new Python objects, + this should have a significant impact on performance while increasing memory usage slightly, + `all/True` means cache all strings, `keys` means cache only dict keys, `none/False` means no caching. + allow_partial: Whether to allow partial deserialization, if `True` JSON data is returned if the end of the + input is reached before the full object is deserialized, e.g. `["aa", "bb", "c` would return `['aa', 'bb']`. + `'trailing-strings'` means any final unfinished JSON string is included in the result. + + Raises: + ValueError: If deserialization fails. + + Returns: + The deserialized Python object. + """ + +def to_jsonable_python( + value: Any, + *, + include: _IncEx | None = None, + exclude: _IncEx | None = None, + # Note: In Pydantic 2.11, the default value of `by_alias` on `SchemaSerializer` was changed from `True` to `None`, + # to be consistent with the Pydantic "dump" methods. However, the default of `True` was kept here for + # backwards compatibility. In Pydantic V3, `by_alias` is expected to default to `True` everywhere: + by_alias: bool = True, + exclude_none: bool = False, + round_trip: bool = False, + timedelta_mode: Literal['iso8601', 'float'] = 'iso8601', + temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601', + bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8', + inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants', + serialize_unknown: bool = False, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + polymorphic_serialization: bool | None = None, + context: Any | None = None, +) -> Any: + """ + Serialize/marshal a Python object to a JSON-serializable Python object including transforming and filtering data. + + This is effectively a standalone version of + [`SchemaSerializer.to_python(mode='json')`][pydantic_core.SchemaSerializer.to_python]. + + Args: + value: The Python object to serialize. + include: A set of fields to include, if `None` all fields are included. + exclude: A set of fields to exclude, if `None` no fields are excluded. + by_alias: Whether to use the alias names of fields. + exclude_none: Whether to exclude fields that have a value of `None`. + round_trip: Whether to enable serialization and validation round-trip support. + timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`. + temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`. + `iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float. + + bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`. + inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`. + serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails + `""` will be used. + fallback: A function to call when an unknown value is encountered, + if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + polymorphic_serialization: Whether to use model and dataclass polymorphic serialization for this call. + context: The context to use for serialization, this is passed to functional serializers as + [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. + + Raises: + PydanticSerializationError: If serialization fails and no `fallback` function is provided. + + Returns: + The serialized Python object. + """ + +class Url(SupportsAllComparisons): + """ + A URL type, internal logic uses the [url rust crate](https://docs.rs/url/latest/url/) originally developed + by Mozilla. + """ + + def __init__(self, url: str) -> None: ... + def __new__(cls, url: str) -> Self: ... + @property + def scheme(self) -> str: ... + @property + def username(self) -> str | None: ... + @property + def password(self) -> str | None: ... + @property + def host(self) -> str | None: ... + def unicode_host(self) -> str | None: ... + @property + def port(self) -> int | None: ... + @property + def path(self) -> str | None: ... + @property + def query(self) -> str | None: ... + def query_params(self) -> list[tuple[str, str]]: ... + @property + def fragment(self) -> str | None: ... + def unicode_string(self) -> str: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + def __deepcopy__(self, memo: dict) -> str: ... + @classmethod + def build( + cls, + *, + scheme: str, + username: str | None = None, + password: str | None = None, + host: str, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ) -> Self: ... + +class MultiHostUrl(SupportsAllComparisons): + """ + A URL type with support for multiple hosts, as used by some databases for DSNs, e.g. `https://foo.com,bar.com/path`. + + Internal URL logic uses the [url rust crate](https://docs.rs/url/latest/url/) originally developed + by Mozilla. + """ + + def __init__(self, url: str) -> None: ... + def __new__(cls, url: str) -> Self: ... + @property + def scheme(self) -> str: ... + @property + def path(self) -> str | None: ... + @property + def query(self) -> str | None: ... + def query_params(self) -> list[tuple[str, str]]: ... + @property + def fragment(self) -> str | None: ... + def hosts(self) -> list[MultiHostHost]: ... + def unicode_string(self) -> str: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + def __deepcopy__(self, memo: dict) -> Self: ... + @classmethod + def build( + cls, + *, + scheme: str, + hosts: list[MultiHostHost] | None = None, + username: str | None = None, + password: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ) -> Self: ... + +@final +class SchemaError(Exception): + """ + Information about errors that occur while building a [`SchemaValidator`][pydantic_core.SchemaValidator] + or [`SchemaSerializer`][pydantic_core.SchemaSerializer]. + """ + + def error_count(self) -> int: + """ + Returns: + The number of errors in the schema. + """ + def errors(self) -> list[ErrorDetails]: + """ + Returns: + A list of [`ErrorDetails`][pydantic_core.ErrorDetails] for each error in the schema. + """ + +class ValidationError(ValueError): + """ + `ValidationError` is the exception raised by `pydantic-core` when validation fails, it contains a list of errors + which detail why validation failed. + """ + @classmethod + def from_exception_data( + cls, + title: str, + line_errors: list[InitErrorDetails], + input_type: Literal['python', 'json'] = 'python', + hide_input: bool = False, + ) -> Self: + """ + Python constructor for a Validation Error. + + Arguments: + title: The title of the error, as used in the heading of `str(validation_error)` + line_errors: A list of [`InitErrorDetails`][pydantic_core.InitErrorDetails] which contain information + about errors that occurred during validation. + input_type: Whether the error is for a Python object or JSON. + hide_input: Whether to hide the input value in the error message. + """ + @property + def title(self) -> str: + """ + The title of the error, as used in the heading of `str(validation_error)`. + """ + def error_count(self) -> int: + """ + Returns: + The number of errors in the validation error. + """ + def errors( + self, *, include_url: bool = True, include_context: bool = True, include_input: bool = True + ) -> list[ErrorDetails]: + """ + Details about each error in the validation error. + + Args: + include_url: Whether to include a URL to documentation on the error each error. + include_context: Whether to include the context of each error. + include_input: Whether to include the input value of each error. + + Returns: + A list of [`ErrorDetails`][pydantic_core.ErrorDetails] for each error in the validation error. + """ + def json( + self, + *, + indent: int | None = None, + include_url: bool = True, + include_context: bool = True, + include_input: bool = True, + ) -> str: + """ + Same as [`errors()`][pydantic_core.ValidationError.errors] but returns a JSON string. + + Args: + indent: The number of spaces to indent the JSON by, or `None` for no indentation - compact JSON. + include_url: Whether to include a URL to documentation on the error each error. + include_context: Whether to include the context of each error. + include_input: Whether to include the input value of each error. + + Returns: + a JSON string. + """ + + def __repr__(self) -> str: + """ + A string representation of the validation error. + + Whether or not documentation URLs are included in the repr is controlled by the + environment variable `PYDANTIC_ERRORS_INCLUDE_URL` being set to `1` or + `true`; by default, URLs are shown. + + Due to implementation details, this environment variable can only be set once, + before the first validation error is created. + """ + +class PydanticCustomError(ValueError): + """A custom exception providing flexible error handling for Pydantic validators. + + You can raise this error in custom validators when you'd like flexibility in regards to the error type, message, and context. + + Example: + ```py + from pydantic_core import PydanticCustomError + + def custom_validator(v) -> None: + if v <= 10: + raise PydanticCustomError('custom_value_error', 'Value must be greater than {value}', {'value': 10, 'extra_context': 'extra_data'}) + return v + ``` + + Arguments: + error_type: The error type. + message_template: The message template. + context: The data to inject into the message template. + """ + + def __init__( + self, error_type: LiteralString, message_template: LiteralString, context: dict[str, Any] | None = None, / + ) -> None: ... + @property + def context(self) -> dict[str, Any] | None: + """Values which are required to render the error message, and could hence be useful in passing error data forward.""" + + @property + def type(self) -> str: + """The error type associated with the error. For consistency with Pydantic, this is typically a snake_case string.""" + + @property + def message_template(self) -> str: + """The message template associated with the error. This is a string that can be formatted with context variables in `{curly_braces}`.""" + + def message(self) -> str: + """The formatted message associated with the error. This presents as the message template with context variables appropriately injected.""" + +@final +class PydanticKnownError(ValueError): + """A helper class for raising exceptions that mimic Pydantic's built-in exceptions, with more flexibility in regards to context. + + Unlike [`PydanticCustomError`][pydantic_core.PydanticCustomError], the `error_type` argument must be a known `ErrorType`. + + Example: + ```py + from pydantic_core import PydanticKnownError + + def custom_validator(v) -> None: + if v <= 10: + raise PydanticKnownError('greater_than', {'gt': 10}) + return v + ``` + + Arguments: + error_type: The error type. + context: The data to inject into the message template. + """ + + def __init__(self, error_type: ErrorType, context: dict[str, Any] | None = None, /) -> None: ... + @property + def context(self) -> dict[str, Any] | None: + """Values which are required to render the error message, and could hence be useful in passing error data forward.""" + + @property + def type(self) -> ErrorType: + """The type of the error.""" + + @property + def message_template(self) -> str: + """The message template associated with the provided error type. This is a string that can be formatted with context variables in `{curly_braces}`.""" + + def message(self) -> str: + """The formatted message associated with the error. This presents as the message template with context variables appropriately injected.""" + +@final +class PydanticOmit(Exception): + """An exception to signal that a field should be omitted from a generated result. + + This could span from omitting a field from a JSON Schema to omitting a field from a serialized result. + Upcoming: more robust support for using PydanticOmit in custom serializers is still in development. + Right now, this is primarily used in the JSON Schema generation process. + + Example: + ```py + from typing import Callable + + from pydantic_core import PydanticOmit + + from pydantic import BaseModel + from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue + + + class MyGenerateJsonSchema(GenerateJsonSchema): + def handle_invalid_for_json_schema(self, schema, error_info) -> JsonSchemaValue: + raise PydanticOmit + + + class Predicate(BaseModel): + name: str = 'no-op' + func: Callable = lambda x: x + + + instance_example = Predicate() + + validation_schema = instance_example.model_json_schema(schema_generator=MyGenerateJsonSchema, mode='validation') + print(validation_schema) + ''' + {'properties': {'name': {'default': 'no-op', 'title': 'Name', 'type': 'string'}}, 'title': 'Predicate', 'type': 'object'} + ''' + ``` + + For a more in depth example / explanation, see the [customizing JSON schema](../concepts/json_schema.md#customizing-the-json-schema-generation-process) docs. + """ + + def __new__(cls) -> Self: ... + +@final +class PydanticUseDefault(Exception): + """An exception to signal that standard validation either failed or should be skipped, and the default value should be used instead. + + This warning can be raised in custom validation functions to redirect the flow of validation. + + Example: + ```py + from pydantic_core import PydanticUseDefault + from datetime import datetime + from pydantic import BaseModel, field_validator + + + class Event(BaseModel): + name: str = 'meeting' + time: datetime + + @field_validator('name', mode='plain') + def name_must_be_present(cls, v) -> str: + if not v or not isinstance(v, str): + raise PydanticUseDefault() + return v + + + event1 = Event(name='party', time=datetime(2024, 1, 1, 12, 0, 0)) + print(repr(event1)) + # > Event(name='party', time=datetime.datetime(2024, 1, 1, 12, 0)) + event2 = Event(time=datetime(2024, 1, 1, 12, 0, 0)) + print(repr(event2)) + # > Event(name='meeting', time=datetime.datetime(2024, 1, 1, 12, 0)) + ``` + + For an additional example, see the [validating partial json data](../concepts/json.md#partial-json-parsing) section of the Pydantic documentation. + """ + + def __new__(cls) -> Self: ... + +@final +class PydanticSerializationError(ValueError): + """An error raised when an issue occurs during serialization. + + In custom serializers, this error can be used to indicate that serialization has failed. + + Arguments: + message: The message associated with the error. + """ + + def __init__(self, message: str, /) -> None: ... + +@final +class PydanticSerializationUnexpectedValue(ValueError): + """An error raised when an unexpected value is encountered during serialization. + + This error is often caught and coerced into a warning, as `pydantic-core` generally makes a best attempt + at serializing values, in contrast with validation where errors are eagerly raised. + + Example: + ```py + from pydantic import BaseModel, field_serializer + from pydantic_core import PydanticSerializationUnexpectedValue + + class BasicPoint(BaseModel): + x: int + y: int + + @field_serializer('*') + def serialize(self, v): + if not isinstance(v, int): + raise PydanticSerializationUnexpectedValue(f'Expected type `int`, got {type(v)} with value {v}') + return v + + point = BasicPoint(x=1, y=2) + # some sort of mutation + point.x = 'a' + + print(point.model_dump()) + ''' + UserWarning: Pydantic serializer warnings: + PydanticSerializationUnexpectedValue(Expected type `int`, got with value a) + return self.__pydantic_serializer__.to_python( + {'x': 'a', 'y': 2} + ''' + ``` + + This is often used internally in `pydantic-core` when unexpected types are encountered during serialization, + but it can also be used by users in custom serializers, as seen above. + + Arguments: + message: The message associated with the unexpected value. + """ + + def __init__(self, message: str, /) -> None: ... + +@final +class ArgsKwargs: + """A construct used to store arguments and keyword arguments for a function call. + + This data structure is generally used to store information for core schemas associated with functions (like in an arguments schema). + This data structure is also currently used for some validation against dataclasses. + + Example: + ```py + from pydantic.dataclasses import dataclass + from pydantic import model_validator + + + @dataclass + class Model: + a: int + b: int + + @model_validator(mode="before") + @classmethod + def no_op_validator(cls, values): + print(values) + return values + + Model(1, b=2) + #> ArgsKwargs((1,), {"b": 2}) + + Model(1, 2) + #> ArgsKwargs((1, 2), {}) + + Model(a=1, b=2) + #> ArgsKwargs((), {"a": 1, "b": 2}) + ``` + """ + + def __init__(self, args: tuple[Any, ...], kwargs: dict[str, Any] | None = None) -> None: + """Initializes the `ArgsKwargs`. + + Arguments: + args: The arguments (inherently ordered) for a function call. + kwargs: The keyword arguments for a function call + """ + + def __new__(cls, args: tuple[Any, ...], kwargs: dict[str, Any] | None = None) -> Self: ... + @property + def args(self) -> tuple[Any, ...]: + """The arguments (inherently ordered) for a function call.""" + + @property + def kwargs(self) -> dict[str, Any] | None: + """The keyword arguments for a function call.""" + +@final +class PydanticUndefinedType: + """A type used as a sentinel for undefined values.""" + + def __copy__(self) -> Self: ... + def __deepcopy__(self, memo: Any) -> Self: ... + +PydanticUndefined: PydanticUndefinedType + +def list_all_errors() -> list[ErrorTypeInfo]: + """ + Get information about all built-in errors. + + Returns: + A list of `ErrorTypeInfo` typed dicts. + """ +@final +class TzInfo(datetime.tzinfo): + """An `pydantic-core` implementation of the abstract [`datetime.tzinfo`][] class.""" + + def __init__(self, seconds: float = 0.0) -> None: + """Initializes the `TzInfo`. + + Arguments: + seconds: The offset from UTC in seconds. Defaults to 0.0 (UTC). + """ + + def __new__(cls, seconds: float = 0.0) -> Self: ... + + # Docstrings for attributes sourced from the abstract base class, [`datetime.tzinfo`](https://docs.python.org/3/library/datetime.html#datetime.tzinfo). + + def tzname(self, dt: datetime.datetime | None) -> str | None: + """Return the time zone name corresponding to the [`datetime`][datetime.datetime] object _dt_, as a string. + + For more info, see [`tzinfo.tzname`][datetime.tzinfo.tzname]. + """ + + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: + """Return offset of local time from UTC, as a [`timedelta`][datetime.timedelta] object that is positive east of UTC. If local time is west of UTC, this should be negative. + + More info can be found at [`tzinfo.utcoffset`][datetime.tzinfo.utcoffset]. + """ + + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: + """Return the daylight saving time (DST) adjustment, as a [`timedelta`][datetime.timedelta] object or `None` if DST information isn’t known. + + More info can be found at[`tzinfo.dst`][datetime.tzinfo.dst].""" + + def fromutc(self, dt: datetime.datetime) -> datetime.datetime: + """Adjust the date and time data associated datetime object _dt_, returning an equivalent datetime in self’s local time. + + More info can be found at [`tzinfo.fromutc`][datetime.tzinfo.fromutc].""" + + def __deepcopy__(self, _memo: dict[Any, Any]) -> TzInfo: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py new file mode 100644 index 0000000..6ff202c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py @@ -0,0 +1,4461 @@ +""" +This module contains definitions to build schemas which `pydantic_core` can +validate and serialize. +""" + +from __future__ import annotations as _annotations + +import sys +import warnings +from collections.abc import Generator, Hashable, Mapping +from datetime import date, datetime, time, timedelta +from decimal import Decimal +from re import Pattern +from typing import TYPE_CHECKING, Any, Callable, Literal, Union + +from typing_extensions import TypeVar, deprecated + +if sys.version_info < (3, 12): + from typing_extensions import TypedDict +else: + from typing import TypedDict + +if sys.version_info < (3, 11): + from typing_extensions import Protocol, Required, TypeAlias +else: + from typing import Protocol, Required, TypeAlias + +if TYPE_CHECKING: + from pydantic_core import PydanticUndefined +else: + # The initial build of pydantic_core requires PydanticUndefined to generate + # the core schema; so we need to conditionally skip it. mypy doesn't like + # this at all, hence the TYPE_CHECKING branch above. + try: + from pydantic_core import PydanticUndefined + except ImportError: + PydanticUndefined = object() + + +ExtraBehavior = Literal['allow', 'forbid', 'ignore'] + + +class CoreConfig(TypedDict, total=False): + """ + Base class for schema configuration options. + + Attributes: + title: The name of the configuration. + strict: Whether the configuration should strictly adhere to specified rules. + extra_fields_behavior: The behavior for handling extra fields. + typed_dict_total: Whether the TypedDict should be considered total. Default is `True`. + from_attributes: Whether to use attributes for models, dataclasses, and tagged union keys. + loc_by_alias: Whether to use the used alias (or first alias for "field required" errors) instead of + `field_names` to construct error `loc`s. Default is `True`. + revalidate_instances: Whether instances of models and dataclasses should re-validate. Default is 'never'. + validate_default: Whether to validate default values during validation. Default is `False`. + str_max_length: The maximum length for string fields. + str_min_length: The minimum length for string fields. + str_strip_whitespace: Whether to strip whitespace from string fields. + str_to_lower: Whether to convert string fields to lowercase. + str_to_upper: Whether to convert string fields to uppercase. + allow_inf_nan: Whether to allow infinity and NaN values for float fields. Default is `True`. + ser_json_timedelta: The serialization option for `timedelta` values. Default is 'iso8601'. + Note that if ser_json_temporal is set, then this param will be ignored. + ser_json_temporal: The serialization option for datetime like values. Default is 'iso8601'. + The types this covers are datetime, date, time and timedelta. + If this is set, it will take precedence over ser_json_timedelta + ser_json_bytes: The serialization option for `bytes` values. Default is 'utf8'. + ser_json_inf_nan: The serialization option for infinity and NaN values + in float fields. Default is 'null'. + val_json_bytes: The validation option for `bytes` values, complementing ser_json_bytes. Default is 'utf8'. + hide_input_in_errors: Whether to hide input data from `ValidationError` representation. + validation_error_cause: Whether to add user-python excs to the __cause__ of a ValidationError. + Requires exceptiongroup backport pre Python 3.11. + coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). + regex_engine: The regex engine to use for regex pattern validation. Default is 'rust-regex'. See `StringSchema`. + cache_strings: Whether to cache strings. Default is `True`, `True` or `'all'` is required to cache strings + during general validation since validators don't know if they're in a key or a value. + validate_by_alias: Whether to use the field's alias when validating against the provided input data. Default is `True`. + validate_by_name: Whether to use the field's name when validating against the provided input data. Default is `False`. Replacement for `populate_by_name`. + serialize_by_alias: Whether to serialize by alias. Default is `False`, expected to change to `True` in V3. + polymorphic_serialization: Whether to enable polymorphic serialization for models and dataclasses. Default is `False`. + url_preserve_empty_path: Whether to preserve empty URL paths when validating values for a URL type. Defaults to `False`. + """ + + title: str + strict: bool + # settings related to typed dicts, model fields, dataclass fields + extra_fields_behavior: ExtraBehavior + typed_dict_total: bool # default: True + # used for models, dataclasses, and tagged union keys + from_attributes: bool + # whether to use the used alias (or first alias for "field required" errors) instead of field_names + # to construct error `loc`s, default True + loc_by_alias: bool + # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' + revalidate_instances: Literal['always', 'never', 'subclass-instances'] + # whether to validate default values during validation, default False + validate_default: bool + # used on typed-dicts and arguments + # fields related to string fields only + str_max_length: int + str_min_length: int + str_strip_whitespace: bool + str_to_lower: bool + str_to_upper: bool + # fields related to float fields only + allow_inf_nan: bool # default: True + # the config options are used to customise serialization to JSON + ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601' + ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] # default: 'iso8601' + ser_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8' + ser_json_inf_nan: Literal['null', 'constants', 'strings'] # default: 'null' + val_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8' + # used to hide input data from ValidationError repr + hide_input_in_errors: bool + validation_error_cause: bool # default: False + coerce_numbers_to_str: bool # default: False + regex_engine: Literal['rust-regex', 'python-re'] # default: 'rust-regex' + cache_strings: Union[bool, Literal['all', 'keys', 'none']] # default: 'True' + validate_by_alias: bool # default: True + validate_by_name: bool # default: False + serialize_by_alias: bool # default: False + polymorphic_serialization: bool # default: False + url_preserve_empty_path: bool # default: False + + +IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None' + +ContextT = TypeVar('ContextT', covariant=True, default='Any | None') + + +class SerializationInfo(Protocol[ContextT]): + """Extra data used during serialization.""" + + @property + def include(self) -> IncExCall: + """The `include` argument set during serialization.""" + ... + + @property + def exclude(self) -> IncExCall: + """The `exclude` argument set during serialization.""" + ... + + @property + def context(self) -> ContextT: + """The current serialization context.""" + ... + + @property + def mode(self) -> Literal['python', 'json'] | str: + """The serialization mode set during serialization.""" + ... + + @property + def by_alias(self) -> bool: + """The `by_alias` argument set during serialization.""" + ... + + @property + def exclude_unset(self) -> bool: + """The `exclude_unset` argument set during serialization.""" + ... + + @property + def exclude_defaults(self) -> bool: + """The `exclude_defaults` argument set during serialization.""" + ... + + @property + def exclude_none(self) -> bool: + """The `exclude_none` argument set during serialization.""" + ... + + @property + def exclude_computed_fields(self) -> bool: + """The `exclude_computed_fields` argument set during serialization.""" + ... + + @property + def serialize_as_any(self) -> bool: + """The `serialize_as_any` argument set during serialization.""" + ... + + @property + def polymorphic_serialization(self) -> bool | None: + """The `polymorphic_serialization` argument set during serialization, if any.""" + ... + + @property + def round_trip(self) -> bool: + """The `round_trip` argument set during serialization.""" + ... + + def mode_is_json(self) -> bool: ... + + def __str__(self) -> str: ... + + def __repr__(self) -> str: ... + + +class FieldSerializationInfo(SerializationInfo[ContextT], Protocol): + """Extra data used during field serialization.""" + + @property + def field_name(self) -> str: + """The name of the current field being serialized.""" + ... + + +class ValidationInfo(Protocol[ContextT]): + """Extra data used during validation.""" + + @property + def context(self) -> ContextT: + """The current validation context.""" + ... + + @property + def config(self) -> CoreConfig | None: + """The CoreConfig that applies to this validation.""" + ... + + @property + def mode(self) -> Literal['python', 'json']: + """The type of input data we are currently validating.""" + ... + + @property + def data(self) -> dict[str, Any]: + """The data being validated for this model.""" + ... + + @property + def field_name(self) -> str | None: + """ + The name of the current field being validated if this validator is + attached to a model field. + """ + ... + + +ExpectedSerializationTypes = Literal[ + 'none', + 'int', + 'bool', + 'float', + 'str', + 'bytes', + 'bytearray', + 'list', + 'tuple', + 'set', + 'frozenset', + 'generator', + 'dict', + 'datetime', + 'date', + 'time', + 'timedelta', + 'url', + 'multi-host-url', + 'json', + 'uuid', + 'any', +] + + +class SimpleSerSchema(TypedDict, total=False): + type: Required[ExpectedSerializationTypes] + + +def simple_ser_schema(type: ExpectedSerializationTypes) -> SimpleSerSchema: + """ + Returns a schema for serialization with a custom type. + + Args: + type: The type to use for serialization + """ + return SimpleSerSchema(type=type) + + +# (input_value: Any, /) -> Any +GeneralPlainNoInfoSerializerFunction = Callable[[Any], Any] +# (input_value: Any, info: FieldSerializationInfo, /) -> Any +GeneralPlainInfoSerializerFunction = Callable[[Any, SerializationInfo[Any]], Any] +# (model: Any, input_value: Any, /) -> Any +FieldPlainNoInfoSerializerFunction = Callable[[Any, Any], Any] +# (model: Any, input_value: Any, info: FieldSerializationInfo, /) -> Any +FieldPlainInfoSerializerFunction = Callable[[Any, Any, FieldSerializationInfo[Any]], Any] +SerializerFunction = Union[ + GeneralPlainNoInfoSerializerFunction, + GeneralPlainInfoSerializerFunction, + FieldPlainNoInfoSerializerFunction, + FieldPlainInfoSerializerFunction, +] + +WhenUsed = Literal['always', 'unless-none', 'json', 'json-unless-none'] +""" +Values have the following meanings: + +* `'always'` means always use +* `'unless-none'` means use unless the value is `None` +* `'json'` means use when serializing to JSON +* `'json-unless-none'` means use when serializing to JSON and the value is not `None` +""" + + +class PlainSerializerFunctionSerSchema(TypedDict, total=False): + type: Required[Literal['function-plain']] + function: Required[SerializerFunction] + is_field_serializer: bool # default False + info_arg: bool # default False + return_schema: CoreSchema # if omitted, AnySchema is used + when_used: WhenUsed # default: 'always' + + +def plain_serializer_function_ser_schema( + function: SerializerFunction, + *, + is_field_serializer: bool | None = None, + info_arg: bool | None = None, + return_schema: CoreSchema | None = None, + when_used: WhenUsed = 'always', +) -> PlainSerializerFunctionSerSchema: + """ + Returns a schema for serialization with a function, can be either a "general" or "field" function. + + Args: + function: The function to use for serialization + is_field_serializer: Whether the serializer is for a field, e.g. takes `model` as the first argument, + and `info` includes `field_name` + info_arg: Whether the function takes an `info` argument + return_schema: Schema to use for serializing return value + when_used: When the function should be called + """ + if when_used == 'always': + # just to avoid extra elements in schema, and to use the actual default defined in rust + when_used = None # type: ignore + return _dict_not_none( + type='function-plain', + function=function, + is_field_serializer=is_field_serializer, + info_arg=info_arg, + return_schema=return_schema, + when_used=when_used, + ) + + +class SerializerFunctionWrapHandler(Protocol): # pragma: no cover + def __call__(self, input_value: Any, index_key: int | str | None = None, /) -> Any: ... + + +# (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any +GeneralWrapNoInfoSerializerFunction = Callable[[Any, SerializerFunctionWrapHandler], Any] +# (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any +GeneralWrapInfoSerializerFunction = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any] +# (model: Any, input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any +FieldWrapNoInfoSerializerFunction = Callable[[Any, Any, SerializerFunctionWrapHandler], Any] +# (model: Any, input_value: Any, serializer: SerializerFunctionWrapHandler, info: FieldSerializationInfo, /) -> Any +FieldWrapInfoSerializerFunction = Callable[[Any, Any, SerializerFunctionWrapHandler, FieldSerializationInfo[Any]], Any] +WrapSerializerFunction = Union[ + GeneralWrapNoInfoSerializerFunction, + GeneralWrapInfoSerializerFunction, + FieldWrapNoInfoSerializerFunction, + FieldWrapInfoSerializerFunction, +] + + +class WrapSerializerFunctionSerSchema(TypedDict, total=False): + type: Required[Literal['function-wrap']] + function: Required[WrapSerializerFunction] + is_field_serializer: bool # default False + info_arg: bool # default False + schema: CoreSchema # if omitted, the schema on which this serializer is defined is used + return_schema: CoreSchema # if omitted, AnySchema is used + when_used: WhenUsed # default: 'always' + + +def wrap_serializer_function_ser_schema( + function: WrapSerializerFunction, + *, + is_field_serializer: bool | None = None, + info_arg: bool | None = None, + schema: CoreSchema | None = None, + return_schema: CoreSchema | None = None, + when_used: WhenUsed = 'always', +) -> WrapSerializerFunctionSerSchema: + """ + Returns a schema for serialization with a wrap function, can be either a "general" or "field" function. + + Args: + function: The function to use for serialization + is_field_serializer: Whether the serializer is for a field, e.g. takes `model` as the first argument, + and `info` includes `field_name` + info_arg: Whether the function takes an `info` argument + schema: The schema to use for the inner serialization + return_schema: Schema to use for serializing return value + when_used: When the function should be called + """ + if when_used == 'always': + # just to avoid extra elements in schema, and to use the actual default defined in rust + when_used = None # type: ignore + return _dict_not_none( + type='function-wrap', + function=function, + is_field_serializer=is_field_serializer, + info_arg=info_arg, + schema=schema, + return_schema=return_schema, + when_used=when_used, + ) + + +class FormatSerSchema(TypedDict, total=False): + type: Required[Literal['format']] + formatting_string: Required[str] + when_used: WhenUsed # default: 'json-unless-none' + + +def format_ser_schema(formatting_string: str, *, when_used: WhenUsed = 'json-unless-none') -> FormatSerSchema: + """ + Returns a schema for serialization using python's `format` method. + + Args: + formatting_string: String defining the format to use + when_used: Same meaning as for [general_function_plain_ser_schema], but with a different default + """ + if when_used == 'json-unless-none': + # just to avoid extra elements in schema, and to use the actual default defined in rust + when_used = None # type: ignore + return _dict_not_none(type='format', formatting_string=formatting_string, when_used=when_used) + + +class ToStringSerSchema(TypedDict, total=False): + type: Required[Literal['to-string']] + when_used: WhenUsed # default: 'json-unless-none' + + +def to_string_ser_schema(*, when_used: WhenUsed = 'json-unless-none') -> ToStringSerSchema: + """ + Returns a schema for serialization using python's `str()` / `__str__` method. + + Args: + when_used: Same meaning as for [general_function_plain_ser_schema], but with a different default + """ + s = dict(type='to-string') + if when_used != 'json-unless-none': + # just to avoid extra elements in schema, and to use the actual default defined in rust + s['when_used'] = when_used + return s # type: ignore + + +class ModelSerSchema(TypedDict, total=False): + type: Required[Literal['model']] + cls: Required[type[Any]] + schema: Required[CoreSchema] + + +def model_ser_schema(cls: type[Any], schema: CoreSchema) -> ModelSerSchema: + """ + Returns a schema for serialization using a model. + + Args: + cls: The expected class type, used to generate warnings if the wrong type is passed + schema: Internal schema to use to serialize the model dict + """ + return ModelSerSchema(type='model', cls=cls, schema=schema) + + +SerSchema = Union[ + SimpleSerSchema, + PlainSerializerFunctionSerSchema, + WrapSerializerFunctionSerSchema, + FormatSerSchema, + ToStringSerSchema, + ModelSerSchema, +] + + +class InvalidSchema(TypedDict, total=False): + type: Required[Literal['invalid']] + ref: str + metadata: dict[str, Any] + # note, we never plan to use this, but include it for type checking purposes to match + # all other CoreSchema union members + serialization: SerSchema + + +def invalid_schema(ref: str | None = None, metadata: dict[str, Any] | None = None) -> InvalidSchema: + """ + Returns an invalid schema, used to indicate that a schema is invalid. + + Args: + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + """ + + return _dict_not_none(type='invalid', ref=ref, metadata=metadata) + + +class ComputedField(TypedDict, total=False): + type: Required[Literal['computed-field']] + property_name: Required[str] + return_schema: Required[CoreSchema] + alias: str + serialization_exclude_if: Callable[[Any], bool] + metadata: dict[str, Any] + + +def computed_field( + property_name: str, + return_schema: CoreSchema, + *, + alias: str | None = None, + serialization_exclude_if: Callable[[Any], bool] | None = None, + metadata: dict[str, Any] | None = None, +) -> ComputedField: + """ + ComputedFields are properties of a model or dataclass that are included in serialization. + + Args: + property_name: The name of the property on the model or dataclass + return_schema: The schema used for the type returned by the computed field + alias: The name to use in the serialized output + metadata: Any other information you want to include with the schema, not used by pydantic-core + """ + return _dict_not_none( + type='computed-field', + property_name=property_name, + return_schema=return_schema, + alias=alias, + serialization_exclude_if=serialization_exclude_if, + metadata=metadata, + ) + + +class AnySchema(TypedDict, total=False): + type: Required[Literal['any']] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def any_schema( + *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None +) -> AnySchema: + """ + Returns a schema that matches any value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.any_schema() + v = SchemaValidator(schema) + assert v.validate_python(1) == 1 + ``` + + Args: + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='any', ref=ref, metadata=metadata, serialization=serialization) + + +class NoneSchema(TypedDict, total=False): + type: Required[Literal['none']] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def none_schema( + *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None +) -> NoneSchema: + """ + Returns a schema that matches a None value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.none_schema() + v = SchemaValidator(schema) + assert v.validate_python(None) is None + ``` + + Args: + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='none', ref=ref, metadata=metadata, serialization=serialization) + + +class BoolSchema(TypedDict, total=False): + type: Required[Literal['bool']] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def bool_schema( + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> BoolSchema: + """ + Returns a schema that matches a bool value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.bool_schema() + v = SchemaValidator(schema) + assert v.validate_python('True') is True + ``` + + Args: + strict: Whether the value should be a bool or a value that can be converted to a bool + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='bool', strict=strict, ref=ref, metadata=metadata, serialization=serialization) + + +class IntSchema(TypedDict, total=False): + type: Required[Literal['int']] + multiple_of: int + le: int + ge: int + lt: int + gt: int + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def int_schema( + *, + multiple_of: int | None = None, + le: int | None = None, + ge: int | None = None, + lt: int | None = None, + gt: int | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> IntSchema: + """ + Returns a schema that matches a int value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.int_schema(multiple_of=2, le=6, ge=2) + v = SchemaValidator(schema) + assert v.validate_python('4') == 4 + ``` + + Args: + multiple_of: The value must be a multiple of this number + le: The value must be less than or equal to this number + ge: The value must be greater than or equal to this number + lt: The value must be strictly less than this number + gt: The value must be strictly greater than this number + strict: Whether the value should be a int or a value that can be converted to a int + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='int', + multiple_of=multiple_of, + le=le, + ge=ge, + lt=lt, + gt=gt, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class FloatSchema(TypedDict, total=False): + type: Required[Literal['float']] + allow_inf_nan: bool # whether 'NaN', '+inf', '-inf' should be forbidden. default: True + multiple_of: float + le: float + ge: float + lt: float + gt: float + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def float_schema( + *, + allow_inf_nan: bool | None = None, + multiple_of: float | None = None, + le: float | None = None, + ge: float | None = None, + lt: float | None = None, + gt: float | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> FloatSchema: + """ + Returns a schema that matches a float value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.float_schema(le=0.8, ge=0.2) + v = SchemaValidator(schema) + assert v.validate_python('0.5') == 0.5 + ``` + + Args: + allow_inf_nan: Whether to allow inf and nan values + multiple_of: The value must be a multiple of this number + le: The value must be less than or equal to this number + ge: The value must be greater than or equal to this number + lt: The value must be strictly less than this number + gt: The value must be strictly greater than this number + strict: Whether the value should be a float or a value that can be converted to a float + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='float', + allow_inf_nan=allow_inf_nan, + multiple_of=multiple_of, + le=le, + ge=ge, + lt=lt, + gt=gt, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class DecimalSchema(TypedDict, total=False): + type: Required[Literal['decimal']] + allow_inf_nan: bool # whether 'NaN', '+inf', '-inf' should be forbidden. default: False + multiple_of: Decimal + le: Decimal + ge: Decimal + lt: Decimal + gt: Decimal + max_digits: int + decimal_places: int + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def decimal_schema( + *, + allow_inf_nan: bool | None = None, + multiple_of: Decimal | None = None, + le: Decimal | None = None, + ge: Decimal | None = None, + lt: Decimal | None = None, + gt: Decimal | None = None, + max_digits: int | None = None, + decimal_places: int | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> DecimalSchema: + """ + Returns a schema that matches a decimal value, e.g.: + + ```py + from decimal import Decimal + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.decimal_schema(le=0.8, ge=0.2) + v = SchemaValidator(schema) + assert v.validate_python('0.5') == Decimal('0.5') + ``` + + Args: + allow_inf_nan: Whether to allow inf and nan values + multiple_of: The value must be a multiple of this number + le: The value must be less than or equal to this number + ge: The value must be greater than or equal to this number + lt: The value must be strictly less than this number + gt: The value must be strictly greater than this number + max_digits: The maximum number of decimal digits allowed + decimal_places: The maximum number of decimal places allowed + strict: Whether the value should be a float or a value that can be converted to a float + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='decimal', + gt=gt, + ge=ge, + lt=lt, + le=le, + max_digits=max_digits, + decimal_places=decimal_places, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class ComplexSchema(TypedDict, total=False): + type: Required[Literal['complex']] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def complex_schema( + *, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ComplexSchema: + """ + Returns a schema that matches a complex value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.complex_schema() + v = SchemaValidator(schema) + assert v.validate_python('1+2j') == complex(1, 2) + assert v.validate_python(complex(1, 2)) == complex(1, 2) + ``` + + Args: + strict: Whether the value should be a complex object instance or a value that can be converted to a complex object + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='complex', + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class StringSchema(TypedDict, total=False): + type: Required[Literal['str']] + pattern: Union[str, Pattern[str]] + max_length: int + min_length: int + strip_whitespace: bool + to_lower: bool + to_upper: bool + regex_engine: Literal['rust-regex', 'python-re'] # default: 'rust-regex' + strict: bool + coerce_numbers_to_str: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def str_schema( + *, + pattern: str | Pattern[str] | None = None, + max_length: int | None = None, + min_length: int | None = None, + strip_whitespace: bool | None = None, + to_lower: bool | None = None, + to_upper: bool | None = None, + regex_engine: Literal['rust-regex', 'python-re'] | None = None, + strict: bool | None = None, + coerce_numbers_to_str: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> StringSchema: + """ + Returns a schema that matches a string value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.str_schema(max_length=10, min_length=2) + v = SchemaValidator(schema) + assert v.validate_python('hello') == 'hello' + ``` + + Args: + pattern: A regex pattern that the value must match + max_length: The value must be at most this length + min_length: The value must be at least this length + strip_whitespace: Whether to strip whitespace from the value + to_lower: Whether to convert the value to lowercase + to_upper: Whether to convert the value to uppercase + regex_engine: The regex engine to use for pattern validation. Default is 'rust-regex'. + - `rust-regex` uses the [`regex`](https://docs.rs/regex) Rust + crate, which is non-backtracking and therefore more DDoS + resistant, but does not support all regex features. + - `python-re` use the [`re`](https://docs.python.org/3/library/re.html) module, + which supports all regex features, but may be slower. + strict: Whether the value should be a string or a value that can be converted to a string + coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='str', + pattern=pattern, + max_length=max_length, + min_length=min_length, + strip_whitespace=strip_whitespace, + to_lower=to_lower, + to_upper=to_upper, + regex_engine=regex_engine, + strict=strict, + coerce_numbers_to_str=coerce_numbers_to_str, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class BytesSchema(TypedDict, total=False): + type: Required[Literal['bytes']] + max_length: int + min_length: int + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def bytes_schema( + *, + max_length: int | None = None, + min_length: int | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> BytesSchema: + """ + Returns a schema that matches a bytes value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.bytes_schema(max_length=10, min_length=2) + v = SchemaValidator(schema) + assert v.validate_python(b'hello') == b'hello' + ``` + + Args: + max_length: The value must be at most this length + min_length: The value must be at least this length + strict: Whether the value should be a bytes or a value that can be converted to a bytes + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='bytes', + max_length=max_length, + min_length=min_length, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class DateSchema(TypedDict, total=False): + type: Required[Literal['date']] + strict: bool + le: date + ge: date + lt: date + gt: date + now_op: Literal['past', 'future'] + # defaults to current local utc offset from `time.localtime().tm_gmtoff` + # value is restricted to -86_400 < offset < 86_400: + now_utc_offset: int + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def date_schema( + *, + strict: bool | None = None, + le: date | None = None, + ge: date | None = None, + lt: date | None = None, + gt: date | None = None, + now_op: Literal['past', 'future'] | None = None, + now_utc_offset: int | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> DateSchema: + """ + Returns a schema that matches a date value, e.g.: + + ```py + from datetime import date + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.date_schema(le=date(2020, 1, 1), ge=date(2019, 1, 1)) + v = SchemaValidator(schema) + assert v.validate_python(date(2019, 6, 1)) == date(2019, 6, 1) + ``` + + Args: + strict: Whether the value should be a date or a value that can be converted to a date + le: The value must be less than or equal to this date + ge: The value must be greater than or equal to this date + lt: The value must be strictly less than this date + gt: The value must be strictly greater than this date + now_op: The value must be in the past or future relative to the current date + now_utc_offset: The value must be in the past or future relative to the current date with this utc offset + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='date', + strict=strict, + le=le, + ge=ge, + lt=lt, + gt=gt, + now_op=now_op, + now_utc_offset=now_utc_offset, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class TimeSchema(TypedDict, total=False): + type: Required[Literal['time']] + strict: bool + le: time + ge: time + lt: time + gt: time + tz_constraint: Union[Literal['aware', 'naive'], int] + microseconds_precision: Literal['truncate', 'error'] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def time_schema( + *, + strict: bool | None = None, + le: time | None = None, + ge: time | None = None, + lt: time | None = None, + gt: time | None = None, + tz_constraint: Literal['aware', 'naive'] | int | None = None, + microseconds_precision: Literal['truncate', 'error'] = 'truncate', + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> TimeSchema: + """ + Returns a schema that matches a time value, e.g.: + + ```py + from datetime import time + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.time_schema(le=time(12, 0, 0), ge=time(6, 0, 0)) + v = SchemaValidator(schema) + assert v.validate_python(time(9, 0, 0)) == time(9, 0, 0) + ``` + + Args: + strict: Whether the value should be a time or a value that can be converted to a time + le: The value must be less than or equal to this time + ge: The value must be greater than or equal to this time + lt: The value must be strictly less than this time + gt: The value must be strictly greater than this time + tz_constraint: The value must be timezone aware or naive, or an int to indicate required tz offset + microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='time', + strict=strict, + le=le, + ge=ge, + lt=lt, + gt=gt, + tz_constraint=tz_constraint, + microseconds_precision=microseconds_precision, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class DatetimeSchema(TypedDict, total=False): + type: Required[Literal['datetime']] + strict: bool + le: datetime + ge: datetime + lt: datetime + gt: datetime + now_op: Literal['past', 'future'] + tz_constraint: Union[Literal['aware', 'naive'], int] + # defaults to current local utc offset from `time.localtime().tm_gmtoff` + # value is restricted to -86_400 < offset < 86_400 by bounds in generate_self_schema.py + now_utc_offset: int + microseconds_precision: Literal['truncate', 'error'] # default: 'truncate' + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def datetime_schema( + *, + strict: bool | None = None, + le: datetime | None = None, + ge: datetime | None = None, + lt: datetime | None = None, + gt: datetime | None = None, + now_op: Literal['past', 'future'] | None = None, + tz_constraint: Literal['aware', 'naive'] | int | None = None, + now_utc_offset: int | None = None, + microseconds_precision: Literal['truncate', 'error'] = 'truncate', + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> DatetimeSchema: + """ + Returns a schema that matches a datetime value, e.g.: + + ```py + from datetime import datetime + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.datetime_schema() + v = SchemaValidator(schema) + now = datetime.now() + assert v.validate_python(str(now)) == now + ``` + + Args: + strict: Whether the value should be a datetime or a value that can be converted to a datetime + le: The value must be less than or equal to this datetime + ge: The value must be greater than or equal to this datetime + lt: The value must be strictly less than this datetime + gt: The value must be strictly greater than this datetime + now_op: The value must be in the past or future relative to the current datetime + tz_constraint: The value must be timezone aware or naive, or an int to indicate required tz offset + TODO: use of a tzinfo where offset changes based on the datetime is not yet supported + now_utc_offset: The value must be in the past or future relative to the current datetime with this utc offset + microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='datetime', + strict=strict, + le=le, + ge=ge, + lt=lt, + gt=gt, + now_op=now_op, + tz_constraint=tz_constraint, + now_utc_offset=now_utc_offset, + microseconds_precision=microseconds_precision, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class TimedeltaSchema(TypedDict, total=False): + type: Required[Literal['timedelta']] + strict: bool + le: timedelta + ge: timedelta + lt: timedelta + gt: timedelta + microseconds_precision: Literal['truncate', 'error'] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def timedelta_schema( + *, + strict: bool | None = None, + le: timedelta | None = None, + ge: timedelta | None = None, + lt: timedelta | None = None, + gt: timedelta | None = None, + microseconds_precision: Literal['truncate', 'error'] = 'truncate', + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> TimedeltaSchema: + """ + Returns a schema that matches a timedelta value, e.g.: + + ```py + from datetime import timedelta + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.timedelta_schema(le=timedelta(days=1), ge=timedelta(days=0)) + v = SchemaValidator(schema) + assert v.validate_python(timedelta(hours=12)) == timedelta(hours=12) + ``` + + Args: + strict: Whether the value should be a timedelta or a value that can be converted to a timedelta + le: The value must be less than or equal to this timedelta + ge: The value must be greater than or equal to this timedelta + lt: The value must be strictly less than this timedelta + gt: The value must be strictly greater than this timedelta + microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='timedelta', + strict=strict, + le=le, + ge=ge, + lt=lt, + gt=gt, + microseconds_precision=microseconds_precision, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class LiteralSchema(TypedDict, total=False): + type: Required[Literal['literal']] + expected: Required[list[Any]] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def literal_schema( + expected: list[Any], + *, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> LiteralSchema: + """ + Returns a schema that matches a literal value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.literal_schema(['hello', 'world']) + v = SchemaValidator(schema) + assert v.validate_python('hello') == 'hello' + ``` + + Args: + expected: The value must be one of these values + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='literal', expected=expected, ref=ref, metadata=metadata, serialization=serialization) + + +class EnumSchema(TypedDict, total=False): + type: Required[Literal['enum']] + cls: Required[Any] + members: Required[list[Any]] + sub_type: Literal['str', 'int', 'float'] + missing: Callable[[Any], Any] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def enum_schema( + cls: Any, + members: list[Any], + *, + sub_type: Literal['str', 'int', 'float'] | None = None, + missing: Callable[[Any], Any] | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> EnumSchema: + """ + Returns a schema that matches an enum value, e.g.: + + ```py + from enum import Enum + from pydantic_core import SchemaValidator, core_schema + + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + schema = core_schema.enum_schema(Color, list(Color.__members__.values())) + v = SchemaValidator(schema) + assert v.validate_python(2) is Color.GREEN + ``` + + Args: + cls: The enum class + members: The members of the enum, generally `list(MyEnum.__members__.values())` + sub_type: The type of the enum, either 'str' or 'int' or None for plain enums + missing: A function to use when the value is not found in the enum, from `_missing_` + strict: Whether to use strict mode, defaults to False + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='enum', + cls=cls, + members=members, + sub_type=sub_type, + missing=missing, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class MissingSentinelSchema(TypedDict, total=False): + type: Required[Literal['missing-sentinel']] + metadata: dict[str, Any] + serialization: SerSchema + + +def missing_sentinel_schema( + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> MissingSentinelSchema: + """Returns a schema for the `MISSING` sentinel.""" + + return _dict_not_none( + type='missing-sentinel', + metadata=metadata, + serialization=serialization, + ) + + +# must match input/parse_json.rs::JsonType::try_from +JsonType = Literal['null', 'bool', 'int', 'float', 'str', 'list', 'dict'] + + +class IsInstanceSchema(TypedDict, total=False): + type: Required[Literal['is-instance']] + cls: Required[Any] + cls_repr: str + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def is_instance_schema( + cls: Any, + *, + cls_repr: str | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> IsInstanceSchema: + """ + Returns a schema that checks if a value is an instance of a class, equivalent to python's `isinstance` method, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + class A: + pass + + schema = core_schema.is_instance_schema(cls=A) + v = SchemaValidator(schema) + v.validate_python(A()) + ``` + + Args: + cls: The value must be an instance of this class + cls_repr: If provided this string is used in the validator name instead of `repr(cls)` + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='is-instance', cls=cls, cls_repr=cls_repr, ref=ref, metadata=metadata, serialization=serialization + ) + + +class IsSubclassSchema(TypedDict, total=False): + type: Required[Literal['is-subclass']] + cls: Required[type[Any]] + cls_repr: str + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def is_subclass_schema( + cls: type[Any], + *, + cls_repr: str | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> IsInstanceSchema: + """ + Returns a schema that checks if a value is a subtype of a class, equivalent to python's `issubclass` method, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + class A: + pass + + class B(A): + pass + + schema = core_schema.is_subclass_schema(cls=A) + v = SchemaValidator(schema) + v.validate_python(B) + ``` + + Args: + cls: The value must be a subclass of this class + cls_repr: If provided this string is used in the validator name instead of `repr(cls)` + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='is-subclass', cls=cls, cls_repr=cls_repr, ref=ref, metadata=metadata, serialization=serialization + ) + + +class CallableSchema(TypedDict, total=False): + type: Required[Literal['callable']] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def callable_schema( + *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None +) -> CallableSchema: + """ + Returns a schema that checks if a value is callable, equivalent to python's `callable` method, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.callable_schema() + v = SchemaValidator(schema) + v.validate_python(min) + ``` + + Args: + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='callable', ref=ref, metadata=metadata, serialization=serialization) + + +class UuidSchema(TypedDict, total=False): + type: Required[Literal['uuid']] + version: Literal[1, 3, 4, 5, 7] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def uuid_schema( + *, + version: Literal[1, 3, 4, 5, 6, 7, 8] | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> UuidSchema: + return _dict_not_none( + type='uuid', version=version, strict=strict, ref=ref, metadata=metadata, serialization=serialization + ) + + +class IncExSeqSerSchema(TypedDict, total=False): + type: Required[Literal['include-exclude-sequence']] + include: set[int] + exclude: set[int] + + +def filter_seq_schema(*, include: set[int] | None = None, exclude: set[int] | None = None) -> IncExSeqSerSchema: + return _dict_not_none(type='include-exclude-sequence', include=include, exclude=exclude) + + +IncExSeqOrElseSerSchema = Union[IncExSeqSerSchema, SerSchema] + + +class ListSchema(TypedDict, total=False): + type: Required[Literal['list']] + items_schema: CoreSchema + min_length: int + max_length: int + fail_fast: bool + strict: bool + ref: str + metadata: dict[str, Any] + serialization: IncExSeqOrElseSerSchema + + +def list_schema( + items_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + fail_fast: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: IncExSeqOrElseSerSchema | None = None, +) -> ListSchema: + """ + Returns a schema that matches a list value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.list_schema(core_schema.int_schema(), min_length=0, max_length=10) + v = SchemaValidator(schema) + assert v.validate_python(['4']) == [4] + ``` + + Args: + items_schema: The value must be a list of items that match this schema + min_length: The value must be a list with at least this many items + max_length: The value must be a list with at most this many items + fail_fast: Stop validation on the first error + strict: The value must be a list with exactly this many items + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='list', + items_schema=items_schema, + min_length=min_length, + max_length=max_length, + fail_fast=fail_fast, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +# @deprecated('tuple_positional_schema is deprecated. Use pydantic_core.core_schema.tuple_schema instead.') +def tuple_positional_schema( + items_schema: list[CoreSchema], + *, + extras_schema: CoreSchema | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: IncExSeqOrElseSerSchema | None = None, +) -> TupleSchema: + """ + Returns a schema that matches a tuple of schemas, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.tuple_positional_schema( + [core_schema.int_schema(), core_schema.str_schema()] + ) + v = SchemaValidator(schema) + assert v.validate_python((1, 'hello')) == (1, 'hello') + ``` + + Args: + items_schema: The value must be a tuple with items that match these schemas + extras_schema: The value must be a tuple with items that match this schema + This was inspired by JSON schema's `prefixItems` and `items` fields. + In python's `typing.Tuple`, you can't specify a type for "extra" items -- they must all be the same type + if the length is variable. So this field won't be set from a `typing.Tuple` annotation on a pydantic model. + strict: The value must be a tuple with exactly this many items + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + if extras_schema is not None: + variadic_item_index = len(items_schema) + items_schema = items_schema + [extras_schema] + else: + variadic_item_index = None + return tuple_schema( + items_schema=items_schema, + variadic_item_index=variadic_item_index, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +# @deprecated('tuple_variable_schema is deprecated. Use pydantic_core.core_schema.tuple_schema instead.') +def tuple_variable_schema( + items_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: IncExSeqOrElseSerSchema | None = None, +) -> TupleSchema: + """ + Returns a schema that matches a tuple of a given schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.tuple_variable_schema( + items_schema=core_schema.int_schema(), min_length=0, max_length=10 + ) + v = SchemaValidator(schema) + assert v.validate_python(('1', 2, 3)) == (1, 2, 3) + ``` + + Args: + items_schema: The value must be a tuple with items that match this schema + min_length: The value must be a tuple with at least this many items + max_length: The value must be a tuple with at most this many items + strict: The value must be a tuple with exactly this many items + ref: Optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return tuple_schema( + items_schema=[items_schema or any_schema()], + variadic_item_index=0, + min_length=min_length, + max_length=max_length, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class TupleSchema(TypedDict, total=False): + type: Required[Literal['tuple']] + items_schema: Required[list[CoreSchema]] + variadic_item_index: int + min_length: int + max_length: int + fail_fast: bool + strict: bool + ref: str + metadata: dict[str, Any] + serialization: IncExSeqOrElseSerSchema + + +def tuple_schema( + items_schema: list[CoreSchema], + *, + variadic_item_index: int | None = None, + min_length: int | None = None, + max_length: int | None = None, + fail_fast: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: IncExSeqOrElseSerSchema | None = None, +) -> TupleSchema: + """ + Returns a schema that matches a tuple of schemas, with an optional variadic item, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.tuple_schema( + [core_schema.int_schema(), core_schema.str_schema(), core_schema.float_schema()], + variadic_item_index=1, + ) + v = SchemaValidator(schema) + assert v.validate_python((1, 'hello', 'world', 1.5)) == (1, 'hello', 'world', 1.5) + ``` + + Args: + items_schema: The value must be a tuple with items that match these schemas + variadic_item_index: The index of the schema in `items_schema` to be treated as variadic (following PEP 646) + min_length: The value must be a tuple with at least this many items + max_length: The value must be a tuple with at most this many items + fail_fast: Stop validation on the first error + strict: The value must be a tuple with exactly this many items + ref: Optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='tuple', + items_schema=items_schema, + variadic_item_index=variadic_item_index, + min_length=min_length, + max_length=max_length, + fail_fast=fail_fast, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class SetSchema(TypedDict, total=False): + type: Required[Literal['set']] + items_schema: CoreSchema + min_length: int + max_length: int + fail_fast: bool + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def set_schema( + items_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + fail_fast: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> SetSchema: + """ + Returns a schema that matches a set of a given schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.set_schema( + items_schema=core_schema.int_schema(), min_length=0, max_length=10 + ) + v = SchemaValidator(schema) + assert v.validate_python({1, '2', 3}) == {1, 2, 3} + ``` + + Args: + items_schema: The value must be a set with items that match this schema + min_length: The value must be a set with at least this many items + max_length: The value must be a set with at most this many items + fail_fast: Stop validation on the first error + strict: The value must be a set with exactly this many items + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='set', + items_schema=items_schema, + min_length=min_length, + max_length=max_length, + fail_fast=fail_fast, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class FrozenSetSchema(TypedDict, total=False): + type: Required[Literal['frozenset']] + items_schema: CoreSchema + min_length: int + max_length: int + fail_fast: bool + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def frozenset_schema( + items_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + fail_fast: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> FrozenSetSchema: + """ + Returns a schema that matches a frozenset of a given schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.frozenset_schema( + items_schema=core_schema.int_schema(), min_length=0, max_length=10 + ) + v = SchemaValidator(schema) + assert v.validate_python(frozenset(range(3))) == frozenset({0, 1, 2}) + ``` + + Args: + items_schema: The value must be a frozenset with items that match this schema + min_length: The value must be a frozenset with at least this many items + max_length: The value must be a frozenset with at most this many items + fail_fast: Stop validation on the first error + strict: The value must be a frozenset with exactly this many items + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='frozenset', + items_schema=items_schema, + min_length=min_length, + max_length=max_length, + fail_fast=fail_fast, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class GeneratorSchema(TypedDict, total=False): + type: Required[Literal['generator']] + items_schema: CoreSchema + min_length: int + max_length: int + ref: str + metadata: dict[str, Any] + serialization: IncExSeqOrElseSerSchema + + +def generator_schema( + items_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: IncExSeqOrElseSerSchema | None = None, +) -> GeneratorSchema: + """ + Returns a schema that matches a generator value, e.g.: + + ```py + from typing import Iterator + from pydantic_core import SchemaValidator, core_schema + + def gen() -> Iterator[int]: + yield 1 + + schema = core_schema.generator_schema(items_schema=core_schema.int_schema()) + v = SchemaValidator(schema) + v.validate_python(gen()) + ``` + + Unlike other types, validated generators do not raise ValidationErrors eagerly, + but instead will raise a ValidationError when a violating value is actually read from the generator. + This is to ensure that "validated" generators retain the benefit of lazy evaluation. + + Args: + items_schema: The value must be a generator with items that match this schema + min_length: The value must be a generator that yields at least this many items + max_length: The value must be a generator that yields at most this many items + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='generator', + items_schema=items_schema, + min_length=min_length, + max_length=max_length, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +IncExDict = set[Union[int, str]] + + +class IncExDictSerSchema(TypedDict, total=False): + type: Required[Literal['include-exclude-dict']] + include: IncExDict + exclude: IncExDict + + +def filter_dict_schema(*, include: IncExDict | None = None, exclude: IncExDict | None = None) -> IncExDictSerSchema: + return _dict_not_none(type='include-exclude-dict', include=include, exclude=exclude) + + +IncExDictOrElseSerSchema = Union[IncExDictSerSchema, SerSchema] + + +class DictSchema(TypedDict, total=False): + type: Required[Literal['dict']] + keys_schema: CoreSchema # default: AnySchema + values_schema: CoreSchema # default: AnySchema + min_length: int + max_length: int + fail_fast: bool + strict: bool + ref: str + metadata: dict[str, Any] + serialization: IncExDictOrElseSerSchema + + +def dict_schema( + keys_schema: CoreSchema | None = None, + values_schema: CoreSchema | None = None, + *, + min_length: int | None = None, + max_length: int | None = None, + fail_fast: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> DictSchema: + """ + Returns a schema that matches a dict value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.dict_schema( + keys_schema=core_schema.str_schema(), values_schema=core_schema.int_schema() + ) + v = SchemaValidator(schema) + assert v.validate_python({'a': '1', 'b': 2}) == {'a': 1, 'b': 2} + ``` + + Args: + keys_schema: The value must be a dict with keys that match this schema + values_schema: The value must be a dict with values that match this schema + min_length: The value must be a dict with at least this many items + max_length: The value must be a dict with at most this many items + fail_fast: Stop validation on the first error + strict: Whether the keys and values should be validated with strict mode + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='dict', + keys_schema=keys_schema, + values_schema=values_schema, + min_length=min_length, + max_length=max_length, + fail_fast=fail_fast, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +# (input_value: Any, /) -> Any +NoInfoValidatorFunction = Callable[[Any], Any] + + +class NoInfoValidatorFunctionSchema(TypedDict): + type: Literal['no-info'] + function: NoInfoValidatorFunction + + +# (input_value: Any, info: ValidationInfo, /) -> Any +WithInfoValidatorFunction = Callable[[Any, ValidationInfo[Any]], Any] + + +class WithInfoValidatorFunctionSchema(TypedDict, total=False): + type: Required[Literal['with-info']] + function: Required[WithInfoValidatorFunction] + field_name: str # deprecated + + +ValidationFunction = Union[NoInfoValidatorFunctionSchema, WithInfoValidatorFunctionSchema] + + +class _ValidatorFunctionSchema(TypedDict, total=False): + function: Required[ValidationFunction] + schema: Required[CoreSchema] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +class BeforeValidatorFunctionSchema(_ValidatorFunctionSchema, total=False): + type: Required[Literal['function-before']] + json_schema_input_schema: CoreSchema + + +def no_info_before_validator_function( + function: NoInfoValidatorFunction, + schema: CoreSchema, + *, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> BeforeValidatorFunctionSchema: + """ + Returns a schema that calls a validator function before validating, no `info` argument is provided, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: bytes) -> str: + return v.decode() + 'world' + + func_schema = core_schema.no_info_before_validator_function( + function=fn, schema=core_schema.str_schema() + ) + schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) + + v = SchemaValidator(schema) + assert v.validate_python({'a': b'hello '}) == {'a': 'hello world'} + ``` + + Args: + function: The validator function to call + schema: The schema to validate the output of the validator function + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='function-before', + function={'type': 'no-info', 'function': function}, + schema=schema, + ref=ref, + json_schema_input_schema=json_schema_input_schema, + metadata=metadata, + serialization=serialization, + ) + + +def with_info_before_validator_function( + function: WithInfoValidatorFunction, + schema: CoreSchema, + *, + field_name: str | None = None, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> BeforeValidatorFunctionSchema: + """ + Returns a schema that calls a validator function before validation, the function is called with + an `info` argument, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: bytes, info: core_schema.ValidationInfo) -> str: + assert info.data is not None + assert info.field_name is not None + return v.decode() + 'world' + + func_schema = core_schema.with_info_before_validator_function( + function=fn, schema=core_schema.str_schema() + ) + schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) + + v = SchemaValidator(schema) + assert v.validate_python({'a': b'hello '}) == {'a': 'hello world'} + ``` + + Args: + function: The validator function to call + field_name: The name of the field this validator is applied to, if any (deprecated) + schema: The schema to validate the output of the validator function + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + if field_name is not None: + warnings.warn( + 'The `field_name` argument on `with_info_before_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', + DeprecationWarning, + stacklevel=2, + ) + + return _dict_not_none( + type='function-before', + function=_dict_not_none(type='with-info', function=function, field_name=field_name), + schema=schema, + ref=ref, + json_schema_input_schema=json_schema_input_schema, + metadata=metadata, + serialization=serialization, + ) + + +class AfterValidatorFunctionSchema(_ValidatorFunctionSchema, total=False): + type: Required[Literal['function-after']] + + +def no_info_after_validator_function( + function: NoInfoValidatorFunction, + schema: CoreSchema, + *, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> AfterValidatorFunctionSchema: + """ + Returns a schema that calls a validator function after validating, no `info` argument is provided, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str) -> str: + return v + 'world' + + func_schema = core_schema.no_info_after_validator_function(fn, core_schema.str_schema()) + schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) + + v = SchemaValidator(schema) + assert v.validate_python({'a': b'hello '}) == {'a': 'hello world'} + ``` + + Args: + function: The validator function to call after the schema is validated + schema: The schema to validate before the validator function + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='function-after', + function={'type': 'no-info', 'function': function}, + schema=schema, + ref=ref, + json_schema_input_schema=json_schema_input_schema, + metadata=metadata, + serialization=serialization, + ) + + +def with_info_after_validator_function( + function: WithInfoValidatorFunction, + schema: CoreSchema, + *, + field_name: str | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> AfterValidatorFunctionSchema: + """ + Returns a schema that calls a validator function after validation, the function is called with + an `info` argument, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str, info: core_schema.ValidationInfo) -> str: + assert info.data is not None + assert info.field_name is not None + return v + 'world' + + func_schema = core_schema.with_info_after_validator_function( + function=fn, schema=core_schema.str_schema() + ) + schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) + + v = SchemaValidator(schema) + assert v.validate_python({'a': b'hello '}) == {'a': 'hello world'} + ``` + + Args: + function: The validator function to call after the schema is validated + schema: The schema to validate before the validator function + field_name: The name of the field this validator is applied to, if any (deprecated) + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + if field_name is not None: + warnings.warn( + 'The `field_name` argument on `with_info_after_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', + DeprecationWarning, + stacklevel=2, + ) + + return _dict_not_none( + type='function-after', + function=_dict_not_none(type='with-info', function=function, field_name=field_name), + schema=schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class ValidatorFunctionWrapHandler(Protocol): + def __call__(self, input_value: Any, outer_location: str | int | None = None, /) -> Any: # pragma: no cover + ... + + +# (input_value: Any, validator: ValidatorFunctionWrapHandler, /) -> Any +NoInfoWrapValidatorFunction = Callable[[Any, ValidatorFunctionWrapHandler], Any] + + +class NoInfoWrapValidatorFunctionSchema(TypedDict): + type: Literal['no-info'] + function: NoInfoWrapValidatorFunction + + +# (input_value: Any, validator: ValidatorFunctionWrapHandler, info: ValidationInfo, /) -> Any +WithInfoWrapValidatorFunction = Callable[[Any, ValidatorFunctionWrapHandler, ValidationInfo[Any]], Any] + + +class WithInfoWrapValidatorFunctionSchema(TypedDict, total=False): + type: Required[Literal['with-info']] + function: Required[WithInfoWrapValidatorFunction] + field_name: str # deprecated + + +WrapValidatorFunction = Union[NoInfoWrapValidatorFunctionSchema, WithInfoWrapValidatorFunctionSchema] + + +class WrapValidatorFunctionSchema(TypedDict, total=False): + type: Required[Literal['function-wrap']] + function: Required[WrapValidatorFunction] + schema: Required[CoreSchema] + ref: str + json_schema_input_schema: CoreSchema + metadata: dict[str, Any] + serialization: SerSchema + + +def no_info_wrap_validator_function( + function: NoInfoWrapValidatorFunction, + schema: CoreSchema, + *, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> WrapValidatorFunctionSchema: + """ + Returns a schema which calls a function with a `validator` callable argument which can + optionally be used to call inner validation with the function logic, this is much like the + "onion" implementation of middleware in many popular web frameworks, no `info` argument is passed, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn( + v: str, + validator: core_schema.ValidatorFunctionWrapHandler, + ) -> str: + return validator(input_value=v) + 'world' + + schema = core_schema.no_info_wrap_validator_function( + function=fn, schema=core_schema.str_schema() + ) + v = SchemaValidator(schema) + assert v.validate_python('hello ') == 'hello world' + ``` + + Args: + function: The validator function to call + schema: The schema to validate the output of the validator function + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='function-wrap', + function={'type': 'no-info', 'function': function}, + schema=schema, + json_schema_input_schema=json_schema_input_schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +def with_info_wrap_validator_function( + function: WithInfoWrapValidatorFunction, + schema: CoreSchema, + *, + field_name: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> WrapValidatorFunctionSchema: + """ + Returns a schema which calls a function with a `validator` callable argument which can + optionally be used to call inner validation with the function logic, this is much like the + "onion" implementation of middleware in many popular web frameworks, an `info` argument is also passed, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn( + v: str, + validator: core_schema.ValidatorFunctionWrapHandler, + info: core_schema.ValidationInfo, + ) -> str: + return validator(input_value=v) + 'world' + + schema = core_schema.with_info_wrap_validator_function( + function=fn, schema=core_schema.str_schema() + ) + v = SchemaValidator(schema) + assert v.validate_python('hello ') == 'hello world' + ``` + + Args: + function: The validator function to call + schema: The schema to validate the output of the validator function + field_name: The name of the field this validator is applied to, if any (deprecated) + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + if field_name is not None: + warnings.warn( + 'The `field_name` argument on `with_info_wrap_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', + DeprecationWarning, + stacklevel=2, + ) + + return _dict_not_none( + type='function-wrap', + function=_dict_not_none(type='with-info', function=function, field_name=field_name), + schema=schema, + json_schema_input_schema=json_schema_input_schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class PlainValidatorFunctionSchema(TypedDict, total=False): + type: Required[Literal['function-plain']] + function: Required[ValidationFunction] + ref: str + json_schema_input_schema: CoreSchema + metadata: dict[str, Any] + serialization: SerSchema + + +def no_info_plain_validator_function( + function: NoInfoValidatorFunction, + *, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> PlainValidatorFunctionSchema: + """ + Returns a schema that uses the provided function for validation, no `info` argument is passed, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str) -> str: + assert 'hello' in v + return v + 'world' + + schema = core_schema.no_info_plain_validator_function(function=fn) + v = SchemaValidator(schema) + assert v.validate_python('hello ') == 'hello world' + ``` + + Args: + function: The validator function to call + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='function-plain', + function={'type': 'no-info', 'function': function}, + ref=ref, + json_schema_input_schema=json_schema_input_schema, + metadata=metadata, + serialization=serialization, + ) + + +def with_info_plain_validator_function( + function: WithInfoValidatorFunction, + *, + field_name: str | None = None, + ref: str | None = None, + json_schema_input_schema: CoreSchema | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> PlainValidatorFunctionSchema: + """ + Returns a schema that uses the provided function for validation, an `info` argument is passed, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str, info: core_schema.ValidationInfo) -> str: + assert 'hello' in v + return v + 'world' + + schema = core_schema.with_info_plain_validator_function(function=fn) + v = SchemaValidator(schema) + assert v.validate_python('hello ') == 'hello world' + ``` + + Args: + function: The validator function to call + field_name: The name of the field this validator is applied to, if any (deprecated) + ref: optional unique identifier of the schema, used to reference the schema in other places + json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + if field_name is not None: + warnings.warn( + 'The `field_name` argument on `with_info_plain_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', + DeprecationWarning, + stacklevel=2, + ) + + return _dict_not_none( + type='function-plain', + function=_dict_not_none(type='with-info', function=function, field_name=field_name), + ref=ref, + json_schema_input_schema=json_schema_input_schema, + metadata=metadata, + serialization=serialization, + ) + + +class WithDefaultSchema(TypedDict, total=False): + type: Required[Literal['default']] + schema: Required[CoreSchema] + default: Any + default_factory: Union[Callable[[], Any], Callable[[dict[str, Any]], Any]] + default_factory_takes_data: bool + on_error: Literal['raise', 'omit', 'default'] # default: 'raise' + validate_default: bool # default: False + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def with_default_schema( + schema: CoreSchema, + *, + default: Any = PydanticUndefined, + default_factory: Union[Callable[[], Any], Callable[[dict[str, Any]], Any], None] = None, + default_factory_takes_data: bool | None = None, + on_error: Literal['raise', 'omit', 'default'] | None = None, + validate_default: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> WithDefaultSchema: + """ + Returns a schema that adds a default value to the given schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.with_default_schema(core_schema.str_schema(), default='hello') + wrapper_schema = core_schema.typed_dict_schema( + {'a': core_schema.typed_dict_field(schema)} + ) + v = SchemaValidator(wrapper_schema) + assert v.validate_python({}) == v.validate_python({'a': 'hello'}) + ``` + + Args: + schema: The schema to add a default value to + default: The default value to use + default_factory: A callable that returns the default value to use + default_factory_takes_data: Whether the default factory takes a validated data argument + on_error: What to do if the schema validation fails. One of 'raise', 'omit', 'default' + validate_default: Whether the default value should be validated + strict: Whether the underlying schema should be validated with strict mode + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + s = _dict_not_none( + type='default', + schema=schema, + default_factory=default_factory, + default_factory_takes_data=default_factory_takes_data, + on_error=on_error, + validate_default=validate_default, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + if default is not PydanticUndefined: + s['default'] = default + return s + + +class NullableSchema(TypedDict, total=False): + type: Required[Literal['nullable']] + schema: Required[CoreSchema] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def nullable_schema( + schema: CoreSchema, + *, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> NullableSchema: + """ + Returns a schema that matches a nullable value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.nullable_schema(core_schema.str_schema()) + v = SchemaValidator(schema) + assert v.validate_python(None) is None + ``` + + Args: + schema: The schema to wrap + strict: Whether the underlying schema should be validated with strict mode + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='nullable', schema=schema, strict=strict, ref=ref, metadata=metadata, serialization=serialization + ) + + +class UnionSchema(TypedDict, total=False): + type: Required[Literal['union']] + choices: Required[list[Union[CoreSchema, tuple[CoreSchema, str]]]] + # default true, whether to automatically collapse unions with one element to the inner validator + auto_collapse: bool + custom_error_type: str + custom_error_message: str + custom_error_context: dict[str, Union[str, int, float]] + mode: Literal['smart', 'left_to_right'] # default: 'smart' + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def union_schema( + choices: list[CoreSchema | tuple[CoreSchema, str]], + *, + auto_collapse: bool | None = None, + custom_error_type: str | None = None, + custom_error_message: str | None = None, + custom_error_context: dict[str, str | int] | None = None, + mode: Literal['smart', 'left_to_right'] | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> UnionSchema: + """ + Returns a schema that matches a union value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.union_schema([core_schema.str_schema(), core_schema.int_schema()]) + v = SchemaValidator(schema) + assert v.validate_python('hello') == 'hello' + assert v.validate_python(1) == 1 + ``` + + Args: + choices: The schemas to match. If a tuple, the second item is used as the label for the case. + auto_collapse: whether to automatically collapse unions with one element to the inner validator, default true + custom_error_type: The custom error type to use if the validation fails + custom_error_message: The custom error message to use if the validation fails + custom_error_context: The custom error context to use if the validation fails + mode: How to select which choice to return + * `smart` (default) will try to return the choice which is the closest match to the input value + * `left_to_right` will return the first choice in `choices` which succeeds validation + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='union', + choices=choices, + auto_collapse=auto_collapse, + custom_error_type=custom_error_type, + custom_error_message=custom_error_message, + custom_error_context=custom_error_context, + mode=mode, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class TaggedUnionSchema(TypedDict, total=False): + type: Required[Literal['tagged-union']] + choices: Required[dict[Hashable, CoreSchema]] + discriminator: Required[Union[str, list[Union[str, int]], list[list[Union[str, int]]], Callable[[Any], Hashable]]] + custom_error_type: str + custom_error_message: str + custom_error_context: dict[str, Union[str, int, float]] + strict: bool + from_attributes: bool # default: True + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def tagged_union_schema( + choices: dict[Any, CoreSchema], + discriminator: str | list[str | int] | list[list[str | int]] | Callable[[Any], Any], + *, + custom_error_type: str | None = None, + custom_error_message: str | None = None, + custom_error_context: dict[str, int | str | float] | None = None, + strict: bool | None = None, + from_attributes: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> TaggedUnionSchema: + """ + Returns a schema that matches a tagged union value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + apple_schema = core_schema.typed_dict_schema( + { + 'foo': core_schema.typed_dict_field(core_schema.str_schema()), + 'bar': core_schema.typed_dict_field(core_schema.int_schema()), + } + ) + banana_schema = core_schema.typed_dict_schema( + { + 'foo': core_schema.typed_dict_field(core_schema.str_schema()), + 'spam': core_schema.typed_dict_field( + core_schema.list_schema(items_schema=core_schema.int_schema()) + ), + } + ) + schema = core_schema.tagged_union_schema( + choices={ + 'apple': apple_schema, + 'banana': banana_schema, + }, + discriminator='foo', + ) + v = SchemaValidator(schema) + assert v.validate_python({'foo': 'apple', 'bar': '123'}) == {'foo': 'apple', 'bar': 123} + assert v.validate_python({'foo': 'banana', 'spam': [1, 2, 3]}) == { + 'foo': 'banana', + 'spam': [1, 2, 3], + } + ``` + + Args: + choices: The schemas to match + When retrieving a schema from `choices` using the discriminator value, if the value is a str, + it should be fed back into the `choices` map until a schema is obtained + (This approach is to prevent multiple ownership of a single schema in Rust) + discriminator: The discriminator to use to determine the schema to use + * If `discriminator` is a str, it is the name of the attribute to use as the discriminator + * If `discriminator` is a list of int/str, it should be used as a "path" to access the discriminator + * If `discriminator` is a list of lists, each inner list is a path, and the first path that exists is used + * If `discriminator` is a callable, it should return the discriminator when called on the value to validate; + the callable can return `None` to indicate that there is no matching discriminator present on the input + custom_error_type: The custom error type to use if the validation fails + custom_error_message: The custom error message to use if the validation fails + custom_error_context: The custom error context to use if the validation fails + strict: Whether the underlying schemas should be validated with strict mode + from_attributes: Whether to use the attributes of the object to retrieve the discriminator value + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='tagged-union', + choices=choices, + discriminator=discriminator, + custom_error_type=custom_error_type, + custom_error_message=custom_error_message, + custom_error_context=custom_error_context, + strict=strict, + from_attributes=from_attributes, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class ChainSchema(TypedDict, total=False): + type: Required[Literal['chain']] + steps: Required[list[CoreSchema]] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def chain_schema( + steps: list[CoreSchema], + *, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ChainSchema: + """ + Returns a schema that chains the provided validation schemas, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str, info: core_schema.ValidationInfo) -> str: + assert 'hello' in v + return v + ' world' + + fn_schema = core_schema.with_info_plain_validator_function(function=fn) + schema = core_schema.chain_schema( + [fn_schema, fn_schema, fn_schema, core_schema.str_schema()] + ) + v = SchemaValidator(schema) + assert v.validate_python('hello') == 'hello world world world' + ``` + + Args: + steps: The schemas to chain + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='chain', steps=steps, ref=ref, metadata=metadata, serialization=serialization) + + +class LaxOrStrictSchema(TypedDict, total=False): + type: Required[Literal['lax-or-strict']] + lax_schema: Required[CoreSchema] + strict_schema: Required[CoreSchema] + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def lax_or_strict_schema( + lax_schema: CoreSchema, + strict_schema: CoreSchema, + *, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> LaxOrStrictSchema: + """ + Returns a schema that uses the lax or strict schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + def fn(v: str, info: core_schema.ValidationInfo) -> str: + assert 'hello' in v + return v + ' world' + + lax_schema = core_schema.int_schema(strict=False) + strict_schema = core_schema.int_schema(strict=True) + + schema = core_schema.lax_or_strict_schema( + lax_schema=lax_schema, strict_schema=strict_schema, strict=True + ) + v = SchemaValidator(schema) + assert v.validate_python(123) == 123 + + schema = core_schema.lax_or_strict_schema( + lax_schema=lax_schema, strict_schema=strict_schema, strict=False + ) + v = SchemaValidator(schema) + assert v.validate_python('123') == 123 + ``` + + Args: + lax_schema: The lax schema to use + strict_schema: The strict schema to use + strict: Whether the strict schema should be used + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='lax-or-strict', + lax_schema=lax_schema, + strict_schema=strict_schema, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class JsonOrPythonSchema(TypedDict, total=False): + type: Required[Literal['json-or-python']] + json_schema: Required[CoreSchema] + python_schema: Required[CoreSchema] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def json_or_python_schema( + json_schema: CoreSchema, + python_schema: CoreSchema, + *, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> JsonOrPythonSchema: + """ + Returns a schema that uses the Json or Python schema depending on the input: + + ```py + from pydantic_core import SchemaValidator, ValidationError, core_schema + + v = SchemaValidator( + core_schema.json_or_python_schema( + json_schema=core_schema.int_schema(), + python_schema=core_schema.int_schema(strict=True), + ) + ) + + assert v.validate_json('"123"') == 123 + + try: + v.validate_python('123') + except ValidationError: + pass + else: + raise AssertionError('Validation should have failed') + ``` + + Args: + json_schema: The schema to use for Json inputs + python_schema: The schema to use for Python inputs + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='json-or-python', + json_schema=json_schema, + python_schema=python_schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class TypedDictField(TypedDict, total=False): + type: Required[Literal['typed-dict-field']] + schema: Required[CoreSchema] + required: bool + validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + serialization_alias: str + serialization_exclude: bool # default: False + metadata: dict[str, Any] + serialization_exclude_if: Callable[[Any], bool] # default None + + +def typed_dict_field( + schema: CoreSchema, + *, + required: bool | None = None, + validation_alias: str | list[str | int] | list[list[str | int]] | None = None, + serialization_alias: str | None = None, + serialization_exclude: bool | None = None, + metadata: dict[str, Any] | None = None, + serialization_exclude_if: Callable[[Any], bool] | None = None, +) -> TypedDictField: + """ + Returns a schema that matches a typed dict field, e.g.: + + ```py + from pydantic_core import core_schema + + field = core_schema.typed_dict_field(schema=core_schema.int_schema(), required=True) + ``` + + Args: + schema: The schema to use for the field + required: Whether the field is required, otherwise uses the value from `total` on the typed dict + validation_alias: The alias(es) to use to find the field in the validation data + serialization_alias: The alias to use as a key when serializing + serialization_exclude: Whether to exclude the field when serializing + serialization_exclude_if: A callable that determines whether to exclude the field when serializing based on its value. + metadata: Any other information you want to include with the schema, not used by pydantic-core + """ + return _dict_not_none( + type='typed-dict-field', + schema=schema, + required=required, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + serialization_exclude=serialization_exclude, + serialization_exclude_if=serialization_exclude_if, + metadata=metadata, + ) + + +class TypedDictSchema(TypedDict, total=False): + type: Required[Literal['typed-dict']] + fields: Required[dict[str, TypedDictField]] + cls: type[Any] + cls_name: str + computed_fields: list[ComputedField] + strict: bool + extras_schema: CoreSchema + # all these values can be set via config, equivalent fields have `typed_dict_` prefix + extra_behavior: ExtraBehavior + total: bool # default: True + ref: str + metadata: dict[str, Any] + serialization: SerSchema + config: CoreConfig + + +def typed_dict_schema( + fields: dict[str, TypedDictField], + *, + cls: type[Any] | None = None, + cls_name: str | None = None, + computed_fields: list[ComputedField] | None = None, + strict: bool | None = None, + extras_schema: CoreSchema | None = None, + extra_behavior: ExtraBehavior | None = None, + total: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, + config: CoreConfig | None = None, +) -> TypedDictSchema: + """ + Returns a schema that matches a typed dict, e.g.: + + ```py + from typing_extensions import TypedDict + + from pydantic_core import SchemaValidator, core_schema + + class MyTypedDict(TypedDict): + a: str + + wrapper_schema = core_schema.typed_dict_schema( + {'a': core_schema.typed_dict_field(core_schema.str_schema())}, cls=MyTypedDict + ) + v = SchemaValidator(wrapper_schema) + assert v.validate_python({'a': 'hello'}) == {'a': 'hello'} + ``` + + Args: + fields: The fields to use for the typed dict + cls: The class to use for the typed dict + cls_name: The name to use in error locations. Falls back to `cls.__name__`, or the validator name if no class + is provided. + computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model + strict: Whether the typed dict is strict + extras_schema: The extra validator to use for the typed dict + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + extra_behavior: The extra behavior to use for the typed dict + total: Whether the typed dict is total, otherwise uses `typed_dict_total` from config + serialization: Custom serialization schema + """ + return _dict_not_none( + type='typed-dict', + fields=fields, + cls=cls, + cls_name=cls_name, + computed_fields=computed_fields, + strict=strict, + extras_schema=extras_schema, + extra_behavior=extra_behavior, + total=total, + ref=ref, + metadata=metadata, + serialization=serialization, + config=config, + ) + + +class ModelField(TypedDict, total=False): + type: Required[Literal['model-field']] + schema: Required[CoreSchema] + validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + serialization_alias: str + serialization_exclude: bool # default: False + serialization_exclude_if: Callable[[Any], bool] # default: None + frozen: bool + metadata: dict[str, Any] + + +def model_field( + schema: CoreSchema, + *, + validation_alias: str | list[str | int] | list[list[str | int]] | None = None, + serialization_alias: str | None = None, + serialization_exclude: bool | None = None, + serialization_exclude_if: Callable[[Any], bool] | None = None, + frozen: bool | None = None, + metadata: dict[str, Any] | None = None, +) -> ModelField: + """ + Returns a schema for a model field, e.g.: + + ```py + from pydantic_core import core_schema + + field = core_schema.model_field(schema=core_schema.int_schema()) + ``` + + Args: + schema: The schema to use for the field + validation_alias: The alias(es) to use to find the field in the validation data + serialization_alias: The alias to use as a key when serializing + serialization_exclude: Whether to exclude the field when serializing + serialization_exclude_if: A Callable that determines whether to exclude a field during serialization based on its value. + frozen: Whether the field is frozen + metadata: Any other information you want to include with the schema, not used by pydantic-core + """ + return _dict_not_none( + type='model-field', + schema=schema, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + serialization_exclude=serialization_exclude, + serialization_exclude_if=serialization_exclude_if, + frozen=frozen, + metadata=metadata, + ) + + +class ModelFieldsSchema(TypedDict, total=False): + type: Required[Literal['model-fields']] + fields: Required[dict[str, ModelField]] + model_name: str + computed_fields: list[ComputedField] + strict: bool + extras_schema: CoreSchema + extras_keys_schema: CoreSchema + extra_behavior: ExtraBehavior + from_attributes: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def model_fields_schema( + fields: dict[str, ModelField], + *, + model_name: str | None = None, + computed_fields: list[ComputedField] | None = None, + strict: bool | None = None, + extras_schema: CoreSchema | None = None, + extras_keys_schema: CoreSchema | None = None, + extra_behavior: ExtraBehavior | None = None, + from_attributes: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ModelFieldsSchema: + """ + Returns a schema that matches the fields of a Pydantic model, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + wrapper_schema = core_schema.model_fields_schema( + {'a': core_schema.model_field(core_schema.str_schema())} + ) + v = SchemaValidator(wrapper_schema) + print(v.validate_python({'a': 'hello'})) + #> ({'a': 'hello'}, None, {'a'}) + ``` + + Args: + fields: The fields of the model + model_name: The name of the model, used for error messages, defaults to "Model" + computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model + strict: Whether the model is strict + extras_schema: The schema to use when validating extra input data + extras_keys_schema: The schema to use when validating the keys of extra input data + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + extra_behavior: The extra behavior to use for the model fields + from_attributes: Whether the model fields should be populated from attributes + serialization: Custom serialization schema + """ + return _dict_not_none( + type='model-fields', + fields=fields, + model_name=model_name, + computed_fields=computed_fields, + strict=strict, + extras_schema=extras_schema, + extras_keys_schema=extras_keys_schema, + extra_behavior=extra_behavior, + from_attributes=from_attributes, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class ModelSchema(TypedDict, total=False): + type: Required[Literal['model']] + cls: Required[type[Any]] + generic_origin: type[Any] + schema: Required[CoreSchema] + custom_init: bool + root_model: bool + post_init: str + revalidate_instances: Literal['always', 'never', 'subclass-instances'] # default: 'never' + strict: bool + frozen: bool + extra_behavior: ExtraBehavior + config: CoreConfig + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def model_schema( + cls: type[Any], + schema: CoreSchema, + *, + generic_origin: type[Any] | None = None, + custom_init: bool | None = None, + root_model: bool | None = None, + post_init: str | None = None, + revalidate_instances: Literal['always', 'never', 'subclass-instances'] | None = None, + strict: bool | None = None, + frozen: bool | None = None, + extra_behavior: ExtraBehavior | None = None, + config: CoreConfig | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ModelSchema: + """ + A model schema generally contains a typed-dict schema. + It will run the typed dict validator, then create a new class + and set the dict and fields set returned from the typed dict validator + to `__dict__` and `__pydantic_fields_set__` respectively. + + Example: + + ```py + from pydantic_core import CoreConfig, SchemaValidator, core_schema + + class MyModel: + __slots__ = ( + '__dict__', + '__pydantic_fields_set__', + '__pydantic_extra__', + '__pydantic_private__', + ) + + schema = core_schema.model_schema( + cls=MyModel, + config=CoreConfig(str_max_length=5), + schema=core_schema.model_fields_schema( + fields={'a': core_schema.model_field(core_schema.str_schema())}, + ), + ) + v = SchemaValidator(schema) + assert v.isinstance_python({'a': 'hello'}) is True + assert v.isinstance_python({'a': 'too long'}) is False + ``` + + Args: + cls: The class to use for the model + schema: The schema to use for the model + generic_origin: The origin type used for this model, if it's a parametrized generic. Ex, + if this model schema represents `SomeModel[int]`, generic_origin is `SomeModel` + custom_init: Whether the model has a custom init method + root_model: Whether the model is a `RootModel` + post_init: The call after init to use for the model + revalidate_instances: whether instances of models and dataclasses (including subclass instances) + should re-validate defaults to config.revalidate_instances, else 'never' + strict: Whether the model is strict + frozen: Whether the model is frozen + extra_behavior: The extra behavior to use for the model, used in serialization + config: The config to use for the model + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='model', + cls=cls, + generic_origin=generic_origin, + schema=schema, + custom_init=custom_init, + root_model=root_model, + post_init=post_init, + revalidate_instances=revalidate_instances, + strict=strict, + frozen=frozen, + extra_behavior=extra_behavior, + config=config, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class DataclassField(TypedDict, total=False): + type: Required[Literal['dataclass-field']] + name: Required[str] + schema: Required[CoreSchema] + kw_only: bool # default: True + init: bool # default: True + init_only: bool # default: False + frozen: bool # default: False + validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + serialization_alias: str + serialization_exclude: bool # default: False + metadata: dict[str, Any] + serialization_exclude_if: Callable[[Any], bool] # default: None + + +def dataclass_field( + name: str, + schema: CoreSchema, + *, + kw_only: bool | None = None, + init: bool | None = None, + init_only: bool | None = None, + validation_alias: str | list[str | int] | list[list[str | int]] | None = None, + serialization_alias: str | None = None, + serialization_exclude: bool | None = None, + metadata: dict[str, Any] | None = None, + serialization_exclude_if: Callable[[Any], bool] | None = None, + frozen: bool | None = None, +) -> DataclassField: + """ + Returns a schema for a dataclass field, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + field = core_schema.dataclass_field( + name='a', schema=core_schema.str_schema(), kw_only=False + ) + schema = core_schema.dataclass_args_schema('Foobar', [field]) + v = SchemaValidator(schema) + assert v.validate_python({'a': 'hello'}) == ({'a': 'hello'}, None) + ``` + + Args: + name: The name to use for the argument parameter + schema: The schema to use for the argument parameter + kw_only: Whether the field can be set with a positional argument as well as a keyword argument + init: Whether the field should be validated during initialization + init_only: Whether the field should be omitted from `__dict__` and passed to `__post_init__` + validation_alias: The alias(es) to use to find the field in the validation data + serialization_alias: The alias to use as a key when serializing + serialization_exclude: Whether to exclude the field when serializing + serialization_exclude_if: A callable that determines whether to exclude the field when serializing based on its value. + metadata: Any other information you want to include with the schema, not used by pydantic-core + frozen: Whether the field is frozen + """ + return _dict_not_none( + type='dataclass-field', + name=name, + schema=schema, + kw_only=kw_only, + init=init, + init_only=init_only, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + serialization_exclude=serialization_exclude, + serialization_exclude_if=serialization_exclude_if, + metadata=metadata, + frozen=frozen, + ) + + +class DataclassArgsSchema(TypedDict, total=False): + type: Required[Literal['dataclass-args']] + dataclass_name: Required[str] + fields: Required[list[DataclassField]] + computed_fields: list[ComputedField] + collect_init_only: bool # default: False + ref: str + metadata: dict[str, Any] + serialization: SerSchema + extra_behavior: ExtraBehavior + + +def dataclass_args_schema( + dataclass_name: str, + fields: list[DataclassField], + *, + computed_fields: list[ComputedField] | None = None, + collect_init_only: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, + extra_behavior: ExtraBehavior | None = None, +) -> DataclassArgsSchema: + """ + Returns a schema for validating dataclass arguments, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + field_a = core_schema.dataclass_field( + name='a', schema=core_schema.str_schema(), kw_only=False + ) + field_b = core_schema.dataclass_field( + name='b', schema=core_schema.bool_schema(), kw_only=False + ) + schema = core_schema.dataclass_args_schema('Foobar', [field_a, field_b]) + v = SchemaValidator(schema) + assert v.validate_python({'a': 'hello', 'b': True}) == ({'a': 'hello', 'b': True}, None) + ``` + + Args: + dataclass_name: The name of the dataclass being validated + fields: The fields to use for the dataclass + computed_fields: Computed fields to use when serializing the dataclass + collect_init_only: Whether to collect init only fields into a dict to pass to `__post_init__` + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + extra_behavior: How to handle extra fields + """ + return _dict_not_none( + type='dataclass-args', + dataclass_name=dataclass_name, + fields=fields, + computed_fields=computed_fields, + collect_init_only=collect_init_only, + ref=ref, + metadata=metadata, + serialization=serialization, + extra_behavior=extra_behavior, + ) + + +class DataclassSchema(TypedDict, total=False): + type: Required[Literal['dataclass']] + cls: Required[type[Any]] + generic_origin: type[Any] + schema: Required[CoreSchema] + fields: Required[list[str]] + cls_name: str + post_init: bool # default: False + revalidate_instances: Literal['always', 'never', 'subclass-instances'] # default: 'never' + strict: bool # default: False + frozen: bool # default False + ref: str + metadata: dict[str, Any] + serialization: SerSchema + slots: bool + config: CoreConfig + + +def dataclass_schema( + cls: type[Any], + schema: CoreSchema, + fields: list[str], + *, + generic_origin: type[Any] | None = None, + cls_name: str | None = None, + post_init: bool | None = None, + revalidate_instances: Literal['always', 'never', 'subclass-instances'] | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, + frozen: bool | None = None, + slots: bool | None = None, + config: CoreConfig | None = None, +) -> DataclassSchema: + """ + Returns a schema for a dataclass. As with `ModelSchema`, this schema can only be used as a field within + another schema, not as the root type. + + Args: + cls: The dataclass type, used to perform subclass checks + schema: The schema to use for the dataclass fields + fields: Fields of the dataclass, this is used in serialization and in validation during re-validation + and while validating assignment + generic_origin: The origin type used for this dataclass, if it's a parametrized generic. Ex, + if this model schema represents `SomeDataclass[int]`, generic_origin is `SomeDataclass` + cls_name: The name to use in error locs, etc; this is useful for generics (default: `cls.__name__`) + post_init: Whether to call `__post_init__` after validation + revalidate_instances: whether instances of models and dataclasses (including subclass instances) + should re-validate defaults to config.revalidate_instances, else 'never' + strict: Whether to require an exact instance of `cls` + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + frozen: Whether the dataclass is frozen + slots: Whether `slots=True` on the dataclass, means each field is assigned independently, rather than + simply setting `__dict__`, default false + """ + return _dict_not_none( + type='dataclass', + cls=cls, + generic_origin=generic_origin, + fields=fields, + cls_name=cls_name, + schema=schema, + post_init=post_init, + revalidate_instances=revalidate_instances, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + frozen=frozen, + slots=slots, + config=config, + ) + + +class ArgumentsParameter(TypedDict, total=False): + name: Required[str] + schema: Required[CoreSchema] + mode: Literal['positional_only', 'positional_or_keyword', 'keyword_only'] # default positional_or_keyword + alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + + +def arguments_parameter( + name: str, + schema: CoreSchema, + *, + mode: Literal['positional_only', 'positional_or_keyword', 'keyword_only'] | None = None, + alias: str | list[str | int] | list[list[str | int]] | None = None, +) -> ArgumentsParameter: + """ + Returns a schema that matches an argument parameter, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + param = core_schema.arguments_parameter( + name='a', schema=core_schema.str_schema(), mode='positional_only' + ) + schema = core_schema.arguments_schema([param]) + v = SchemaValidator(schema) + assert v.validate_python(('hello',)) == (('hello',), {}) + ``` + + Args: + name: The name to use for the argument parameter + schema: The schema to use for the argument parameter + mode: The mode to use for the argument parameter + alias: The alias to use for the argument parameter + """ + return _dict_not_none(name=name, schema=schema, mode=mode, alias=alias) + + +VarKwargsMode: TypeAlias = Literal['uniform', 'unpacked-typed-dict'] + + +class ArgumentsSchema(TypedDict, total=False): + type: Required[Literal['arguments']] + arguments_schema: Required[list[ArgumentsParameter]] + validate_by_name: bool + validate_by_alias: bool + var_args_schema: CoreSchema + var_kwargs_mode: VarKwargsMode + var_kwargs_schema: CoreSchema + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def arguments_schema( + arguments: list[ArgumentsParameter], + *, + validate_by_name: bool | None = None, + validate_by_alias: bool | None = None, + var_args_schema: CoreSchema | None = None, + var_kwargs_mode: VarKwargsMode | None = None, + var_kwargs_schema: CoreSchema | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ArgumentsSchema: + """ + Returns a schema that matches an arguments schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + param_a = core_schema.arguments_parameter( + name='a', schema=core_schema.str_schema(), mode='positional_only' + ) + param_b = core_schema.arguments_parameter( + name='b', schema=core_schema.bool_schema(), mode='positional_only' + ) + schema = core_schema.arguments_schema([param_a, param_b]) + v = SchemaValidator(schema) + assert v.validate_python(('hello', True)) == (('hello', True), {}) + ``` + + Args: + arguments: The arguments to use for the arguments schema + validate_by_name: Whether to populate by the parameter names, defaults to `False`. + validate_by_alias: Whether to populate by the parameter aliases, defaults to `True`. + var_args_schema: The variable args schema to use for the arguments schema + var_kwargs_mode: The validation mode to use for variadic keyword arguments. If `'uniform'`, every value of the + keyword arguments will be validated against the `var_kwargs_schema` schema. If `'unpacked-typed-dict'`, + the `var_kwargs_schema` argument must be a [`typed_dict_schema`][pydantic_core.core_schema.typed_dict_schema] + var_kwargs_schema: The variable kwargs schema to use for the arguments schema + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='arguments', + arguments_schema=arguments, + validate_by_name=validate_by_name, + validate_by_alias=validate_by_alias, + var_args_schema=var_args_schema, + var_kwargs_mode=var_kwargs_mode, + var_kwargs_schema=var_kwargs_schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class ArgumentsV3Parameter(TypedDict, total=False): + name: Required[str] + schema: Required[CoreSchema] + mode: Literal[ + 'positional_only', + 'positional_or_keyword', + 'keyword_only', + 'var_args', + 'var_kwargs_uniform', + 'var_kwargs_unpacked_typed_dict', + ] # default positional_or_keyword + alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + + +def arguments_v3_parameter( + name: str, + schema: CoreSchema, + *, + mode: Literal[ + 'positional_only', + 'positional_or_keyword', + 'keyword_only', + 'var_args', + 'var_kwargs_uniform', + 'var_kwargs_unpacked_typed_dict', + ] + | None = None, + alias: str | list[str | int] | list[list[str | int]] | None = None, +) -> ArgumentsV3Parameter: + """ + Returns a schema that matches an argument parameter, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + param = core_schema.arguments_v3_parameter( + name='a', schema=core_schema.str_schema(), mode='positional_only' + ) + schema = core_schema.arguments_v3_schema([param]) + v = SchemaValidator(schema) + assert v.validate_python({'a': 'hello'}) == (('hello',), {}) + ``` + + Args: + name: The name to use for the argument parameter + schema: The schema to use for the argument parameter + mode: The mode to use for the argument parameter + alias: The alias to use for the argument parameter + """ + return _dict_not_none(name=name, schema=schema, mode=mode, alias=alias) + + +class ArgumentsV3Schema(TypedDict, total=False): + type: Required[Literal['arguments-v3']] + arguments_schema: Required[list[ArgumentsV3Parameter]] + validate_by_name: bool + validate_by_alias: bool + extra_behavior: Literal['forbid', 'ignore'] # 'allow' doesn't make sense here. + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def arguments_v3_schema( + arguments: list[ArgumentsV3Parameter], + *, + validate_by_name: bool | None = None, + validate_by_alias: bool | None = None, + extra_behavior: Literal['forbid', 'ignore'] | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> ArgumentsV3Schema: + """ + Returns a schema that matches an arguments schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + param_a = core_schema.arguments_v3_parameter( + name='a', schema=core_schema.str_schema(), mode='positional_only' + ) + param_b = core_schema.arguments_v3_parameter( + name='kwargs', schema=core_schema.bool_schema(), mode='var_kwargs_uniform' + ) + schema = core_schema.arguments_v3_schema([param_a, param_b]) + v = SchemaValidator(schema) + assert v.validate_python({'a': 'hi', 'kwargs': {'b': True}}) == (('hi',), {'b': True}) + ``` + + This schema is currently not used by other Pydantic components. In V3, it will most likely + become the default arguments schema for the `'call'` schema. + + Args: + arguments: The arguments to use for the arguments schema. + validate_by_name: Whether to populate by the parameter names, defaults to `False`. + validate_by_alias: Whether to populate by the parameter aliases, defaults to `True`. + extra_behavior: The extra behavior to use. + ref: optional unique identifier of the schema, used to reference the schema in other places. + metadata: Any other information you want to include with the schema, not used by pydantic-core. + serialization: Custom serialization schema. + """ + return _dict_not_none( + type='arguments-v3', + arguments_schema=arguments, + validate_by_name=validate_by_name, + validate_by_alias=validate_by_alias, + extra_behavior=extra_behavior, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class CallSchema(TypedDict, total=False): + type: Required[Literal['call']] + arguments_schema: Required[CoreSchema] + function: Required[Callable[..., Any]] + function_name: str # default function.__name__ + return_schema: CoreSchema + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def call_schema( + arguments: CoreSchema, + function: Callable[..., Any], + *, + function_name: str | None = None, + return_schema: CoreSchema | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> CallSchema: + """ + Returns a schema that matches an arguments schema, then calls a function, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + param_a = core_schema.arguments_parameter( + name='a', schema=core_schema.str_schema(), mode='positional_only' + ) + param_b = core_schema.arguments_parameter( + name='b', schema=core_schema.bool_schema(), mode='positional_only' + ) + args_schema = core_schema.arguments_schema([param_a, param_b]) + + schema = core_schema.call_schema( + arguments=args_schema, + function=lambda a, b: a + str(not b), + return_schema=core_schema.str_schema(), + ) + v = SchemaValidator(schema) + assert v.validate_python((('hello', True))) == 'helloFalse' + ``` + + Args: + arguments: The arguments to use for the arguments schema + function: The function to use for the call schema + function_name: The function name to use for the call schema, if not provided `function.__name__` is used + return_schema: The return schema to use for the call schema + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='call', + arguments_schema=arguments, + function=function, + function_name=function_name, + return_schema=return_schema, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class CustomErrorSchema(TypedDict, total=False): + type: Required[Literal['custom-error']] + schema: Required[CoreSchema] + custom_error_type: Required[str] + custom_error_message: str + custom_error_context: dict[str, Union[str, int, float]] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def custom_error_schema( + schema: CoreSchema, + custom_error_type: str, + *, + custom_error_message: str | None = None, + custom_error_context: dict[str, Any] | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> CustomErrorSchema: + """ + Returns a schema that matches a custom error value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.custom_error_schema( + schema=core_schema.int_schema(), + custom_error_type='MyError', + custom_error_message='Error msg', + ) + v = SchemaValidator(schema) + v.validate_python(1) + ``` + + Args: + schema: The schema to use for the custom error schema + custom_error_type: The custom error type to use for the custom error schema + custom_error_message: The custom error message to use for the custom error schema + custom_error_context: The custom error context to use for the custom error schema + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='custom-error', + schema=schema, + custom_error_type=custom_error_type, + custom_error_message=custom_error_message, + custom_error_context=custom_error_context, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class JsonSchema(TypedDict, total=False): + type: Required[Literal['json']] + schema: CoreSchema + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def json_schema( + schema: CoreSchema | None = None, + *, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> JsonSchema: + """ + Returns a schema that matches a JSON value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + dict_schema = core_schema.model_fields_schema( + { + 'field_a': core_schema.model_field(core_schema.str_schema()), + 'field_b': core_schema.model_field(core_schema.bool_schema()), + }, + ) + + class MyModel: + __slots__ = ( + '__dict__', + '__pydantic_fields_set__', + '__pydantic_extra__', + '__pydantic_private__', + ) + field_a: str + field_b: bool + + json_schema = core_schema.json_schema(schema=dict_schema) + schema = core_schema.model_schema(cls=MyModel, schema=json_schema) + v = SchemaValidator(schema) + m = v.validate_python('{"field_a": "hello", "field_b": true}') + assert isinstance(m, MyModel) + ``` + + Args: + schema: The schema to use for the JSON schema + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none(type='json', schema=schema, ref=ref, metadata=metadata, serialization=serialization) + + +class UrlSchema(TypedDict, total=False): + type: Required[Literal['url']] + max_length: int + allowed_schemes: list[str] + host_required: bool # default False + default_host: str + default_port: int + default_path: str + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def url_schema( + *, + max_length: int | None = None, + allowed_schemes: list[str] | None = None, + host_required: bool | None = None, + default_host: str | None = None, + default_port: int | None = None, + default_path: str | None = None, + preserve_empty_path: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> UrlSchema: + """ + Returns a schema that matches a URL value, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.url_schema() + v = SchemaValidator(schema) + print(v.validate_python('https://example.com')) + #> https://example.com/ + ``` + + Args: + max_length: The maximum length of the URL + allowed_schemes: The allowed URL schemes + host_required: Whether the URL must have a host + default_host: The default host to use if the URL does not have a host + default_port: The default port to use if the URL does not have a port + default_path: The default path to use if the URL does not have a path + preserve_empty_path: Whether to preserve an empty path or convert it to '/', default False + strict: Whether to use strict URL parsing + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='url', + max_length=max_length, + allowed_schemes=allowed_schemes, + host_required=host_required, + default_host=default_host, + default_port=default_port, + default_path=default_path, + preserve_empty_path=preserve_empty_path, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class MultiHostUrlSchema(TypedDict, total=False): + type: Required[Literal['multi-host-url']] + max_length: int + allowed_schemes: list[str] + host_required: bool # default False + default_host: str + default_port: int + default_path: str + strict: bool + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def multi_host_url_schema( + *, + max_length: int | None = None, + allowed_schemes: list[str] | None = None, + host_required: bool | None = None, + default_host: str | None = None, + default_port: int | None = None, + default_path: str | None = None, + preserve_empty_path: bool | None = None, + strict: bool | None = None, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> MultiHostUrlSchema: + """ + Returns a schema that matches a URL value with possibly multiple hosts, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.multi_host_url_schema() + v = SchemaValidator(schema) + print(v.validate_python('redis://localhost,0.0.0.0,127.0.0.1')) + #> redis://localhost,0.0.0.0,127.0.0.1 + ``` + + Args: + max_length: The maximum length of the URL + allowed_schemes: The allowed URL schemes + host_required: Whether the URL must have a host + default_host: The default host to use if the URL does not have a host + default_port: The default port to use if the URL does not have a port + default_path: The default path to use if the URL does not have a path + preserve_empty_path: Whether to preserve an empty path or convert it to '/', default False + strict: Whether to use strict URL parsing + ref: optional unique identifier of the schema, used to reference the schema in other places + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='multi-host-url', + max_length=max_length, + allowed_schemes=allowed_schemes, + host_required=host_required, + default_host=default_host, + default_port=default_port, + default_path=default_path, + preserve_empty_path=preserve_empty_path, + strict=strict, + ref=ref, + metadata=metadata, + serialization=serialization, + ) + + +class DefinitionsSchema(TypedDict, total=False): + type: Required[Literal['definitions']] + schema: Required[CoreSchema] + definitions: Required[list[CoreSchema]] + metadata: dict[str, Any] + serialization: SerSchema + + +def definitions_schema(schema: CoreSchema, definitions: list[CoreSchema]) -> DefinitionsSchema: + """ + Build a schema that contains both an inner schema and a list of definitions which can be used + within the inner schema. + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema = core_schema.definitions_schema( + core_schema.list_schema(core_schema.definition_reference_schema('foobar')), + [core_schema.int_schema(ref='foobar')], + ) + v = SchemaValidator(schema) + assert v.validate_python([1, 2, '3']) == [1, 2, 3] + ``` + + Args: + schema: The inner schema + definitions: List of definitions which can be referenced within inner schema + """ + return DefinitionsSchema(type='definitions', schema=schema, definitions=definitions) + + +class DefinitionReferenceSchema(TypedDict, total=False): + type: Required[Literal['definition-ref']] + schema_ref: Required[str] + ref: str + metadata: dict[str, Any] + serialization: SerSchema + + +def definition_reference_schema( + schema_ref: str, + ref: str | None = None, + metadata: dict[str, Any] | None = None, + serialization: SerSchema | None = None, +) -> DefinitionReferenceSchema: + """ + Returns a schema that points to a schema stored in "definitions", this is useful for nested recursive + models and also when you want to define validators separately from the main schema, e.g.: + + ```py + from pydantic_core import SchemaValidator, core_schema + + schema_definition = core_schema.definition_reference_schema('list-schema') + schema = core_schema.definitions_schema( + schema=schema_definition, + definitions=[ + core_schema.list_schema(items_schema=schema_definition, ref='list-schema'), + ], + ) + v = SchemaValidator(schema) + assert v.validate_python([()]) == [[]] + ``` + + Args: + schema_ref: The schema ref to use for the definition reference schema + metadata: Any other information you want to include with the schema, not used by pydantic-core + serialization: Custom serialization schema + """ + return _dict_not_none( + type='definition-ref', schema_ref=schema_ref, ref=ref, metadata=metadata, serialization=serialization + ) + + +MYPY = False +# See https://github.com/python/mypy/issues/14034 for details, in summary mypy is extremely slow to process this +# union which kills performance not just for pydantic, but even for code using pydantic +if not MYPY: + CoreSchema = Union[ + InvalidSchema, + AnySchema, + NoneSchema, + BoolSchema, + IntSchema, + FloatSchema, + DecimalSchema, + StringSchema, + BytesSchema, + DateSchema, + TimeSchema, + DatetimeSchema, + TimedeltaSchema, + LiteralSchema, + MissingSentinelSchema, + EnumSchema, + IsInstanceSchema, + IsSubclassSchema, + CallableSchema, + ListSchema, + TupleSchema, + SetSchema, + FrozenSetSchema, + GeneratorSchema, + DictSchema, + AfterValidatorFunctionSchema, + BeforeValidatorFunctionSchema, + WrapValidatorFunctionSchema, + PlainValidatorFunctionSchema, + WithDefaultSchema, + NullableSchema, + UnionSchema, + TaggedUnionSchema, + ChainSchema, + LaxOrStrictSchema, + JsonOrPythonSchema, + TypedDictSchema, + ModelFieldsSchema, + ModelSchema, + DataclassArgsSchema, + DataclassSchema, + ArgumentsSchema, + ArgumentsV3Schema, + CallSchema, + CustomErrorSchema, + JsonSchema, + UrlSchema, + MultiHostUrlSchema, + DefinitionsSchema, + DefinitionReferenceSchema, + UuidSchema, + ComplexSchema, + ] +elif False: + CoreSchema: TypeAlias = Mapping[str, Any] + + +# to update this, call `pytest -k test_core_schema_type_literal` and copy the output +CoreSchemaType = Literal[ + 'invalid', + 'any', + 'none', + 'bool', + 'int', + 'float', + 'decimal', + 'str', + 'bytes', + 'date', + 'time', + 'datetime', + 'timedelta', + 'literal', + 'missing-sentinel', + 'enum', + 'is-instance', + 'is-subclass', + 'callable', + 'list', + 'tuple', + 'set', + 'frozenset', + 'generator', + 'dict', + 'function-after', + 'function-before', + 'function-wrap', + 'function-plain', + 'default', + 'nullable', + 'union', + 'tagged-union', + 'chain', + 'lax-or-strict', + 'json-or-python', + 'typed-dict', + 'model-fields', + 'model', + 'dataclass-args', + 'dataclass', + 'arguments', + 'arguments-v3', + 'call', + 'custom-error', + 'json', + 'url', + 'multi-host-url', + 'definitions', + 'definition-ref', + 'uuid', + 'complex', +] + +CoreSchemaFieldType = Literal['model-field', 'dataclass-field', 'typed-dict-field', 'computed-field'] + + +# used in _pydantic_core.pyi::PydanticKnownError +# to update this, call `pytest -k test_all_errors` and copy the output +ErrorType = Literal[ + 'no_such_attribute', + 'json_invalid', + 'json_type', + 'needs_python_object', + 'recursion_loop', + 'missing', + 'frozen_field', + 'frozen_instance', + 'extra_forbidden', + 'invalid_key', + 'get_attribute_error', + 'model_type', + 'model_attributes_type', + 'dataclass_type', + 'dataclass_exact_type', + 'default_factory_not_called', + 'none_required', + 'greater_than', + 'greater_than_equal', + 'less_than', + 'less_than_equal', + 'multiple_of', + 'finite_number', + 'too_short', + 'too_long', + 'iterable_type', + 'iteration_error', + 'string_type', + 'string_sub_type', + 'string_unicode', + 'string_too_short', + 'string_too_long', + 'string_pattern_mismatch', + 'string_not_ascii', + 'enum', + 'dict_type', + 'mapping_type', + 'list_type', + 'tuple_type', + 'set_type', + 'set_item_not_hashable', + 'bool_type', + 'bool_parsing', + 'int_type', + 'int_parsing', + 'int_parsing_size', + 'int_from_float', + 'float_type', + 'float_parsing', + 'bytes_type', + 'bytes_too_short', + 'bytes_too_long', + 'bytes_invalid_encoding', + 'value_error', + 'assertion_error', + 'literal_error', + 'missing_sentinel_error', + 'date_type', + 'date_parsing', + 'date_from_datetime_parsing', + 'date_from_datetime_inexact', + 'date_past', + 'date_future', + 'time_type', + 'time_parsing', + 'datetime_type', + 'datetime_parsing', + 'datetime_object_invalid', + 'datetime_from_date_parsing', + 'datetime_past', + 'datetime_future', + 'timezone_naive', + 'timezone_aware', + 'timezone_offset', + 'time_delta_type', + 'time_delta_parsing', + 'frozen_set_type', + 'is_instance_of', + 'is_subclass_of', + 'callable_type', + 'union_tag_invalid', + 'union_tag_not_found', + 'arguments_type', + 'missing_argument', + 'unexpected_keyword_argument', + 'missing_keyword_only_argument', + 'unexpected_positional_argument', + 'missing_positional_only_argument', + 'multiple_argument_values', + 'url_type', + 'url_parsing', + 'url_syntax_violation', + 'url_too_long', + 'url_scheme', + 'uuid_type', + 'uuid_parsing', + 'uuid_version', + 'decimal_type', + 'decimal_parsing', + 'decimal_max_digits', + 'decimal_max_places', + 'decimal_whole_digits', + 'complex_type', + 'complex_str_parsing', +] + + +def _dict_not_none(**kwargs: Any) -> Any: + return {k: v for k, v in kwargs.items() if v is not None} + + +def iter_union_choices(union_schema: UnionSchema) -> Generator[CoreSchema]: + """Iterate over the choices of a `'union'` schema.""" + for choice in union_schema['choices']: + if isinstance(choice, tuple): + yield choice[0] + else: + yield choice + + +############################################################################### +# All this stuff is deprecated by #980 and will be removed eventually +# They're kept because some code external code will be using them + + +@deprecated('`field_before_validator_function` is deprecated, use `with_info_before_validator_function` instead.') +def field_before_validator_function(function: WithInfoValidatorFunction, field_name: str, schema: CoreSchema, **kwargs): + warnings.warn( + '`field_before_validator_function` is deprecated, use `with_info_before_validator_function` instead.', + DeprecationWarning, + ) + return with_info_before_validator_function(function, schema, field_name=field_name, **kwargs) + + +@deprecated('`general_before_validator_function` is deprecated, use `with_info_before_validator_function` instead.') +def general_before_validator_function(*args, **kwargs): + warnings.warn( + '`general_before_validator_function` is deprecated, use `with_info_before_validator_function` instead.', + DeprecationWarning, + ) + return with_info_before_validator_function(*args, **kwargs) + + +@deprecated('`field_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.') +def field_after_validator_function(function: WithInfoValidatorFunction, field_name: str, schema: CoreSchema, **kwargs): + warnings.warn( + '`field_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.', + DeprecationWarning, + ) + return with_info_after_validator_function(function, schema, field_name=field_name, **kwargs) + + +@deprecated('`general_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.') +def general_after_validator_function(*args, **kwargs): + warnings.warn( + '`general_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.', + DeprecationWarning, + ) + return with_info_after_validator_function(*args, **kwargs) + + +@deprecated('`field_wrap_validator_function` is deprecated, use `with_info_wrap_validator_function` instead.') +def field_wrap_validator_function( + function: WithInfoWrapValidatorFunction, field_name: str, schema: CoreSchema, **kwargs +): + warnings.warn( + '`field_wrap_validator_function` is deprecated, use `with_info_wrap_validator_function` instead.', + DeprecationWarning, + ) + return with_info_wrap_validator_function(function, schema, field_name=field_name, **kwargs) + + +@deprecated('`general_wrap_validator_function` is deprecated, use `with_info_wrap_validator_function` instead.') +def general_wrap_validator_function(*args, **kwargs): + warnings.warn( + '`general_wrap_validator_function` is deprecated, use `with_info_wrap_validator_function` instead.', + DeprecationWarning, + ) + return with_info_wrap_validator_function(*args, **kwargs) + + +@deprecated('`field_plain_validator_function` is deprecated, use `with_info_plain_validator_function` instead.') +def field_plain_validator_function(function: WithInfoValidatorFunction, field_name: str, **kwargs): + warnings.warn( + '`field_plain_validator_function` is deprecated, use `with_info_plain_validator_function` instead.', + DeprecationWarning, + ) + return with_info_plain_validator_function(function, field_name=field_name, **kwargs) + + +@deprecated('`general_plain_validator_function` is deprecated, use `with_info_plain_validator_function` instead.') +def general_plain_validator_function(*args, **kwargs): + warnings.warn( + '`general_plain_validator_function` is deprecated, use `with_info_plain_validator_function` instead.', + DeprecationWarning, + ) + return with_info_plain_validator_function(*args, **kwargs) + + +_deprecated_import_lookup = { + 'FieldValidationInfo': ValidationInfo, + 'FieldValidatorFunction': WithInfoValidatorFunction, + 'GeneralValidatorFunction': WithInfoValidatorFunction, + 'FieldWrapValidatorFunction': WithInfoWrapValidatorFunction, +} + +if TYPE_CHECKING: + FieldValidationInfo = ValidationInfo + + +def __getattr__(attr_name: str) -> object: + new_attr = _deprecated_import_lookup.get(attr_name) + if new_attr is None: + raise AttributeError(f"module 'pydantic_core' has no attribute '{attr_name}'") + else: + import warnings + + msg = f'`{attr_name}` is deprecated, use `{new_attr.__name__}` instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=1) + return new_attr diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA new file mode 100644 index 0000000..52926c2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA @@ -0,0 +1,70 @@ +Metadata-Version: 2.4 +Name: pydantic-settings +Version: 2.15.0 +Summary: Settings management using Pydantic +Project-URL: Homepage, https://github.com/pydantic/pydantic-settings +Project-URL: Funding, https://github.com/sponsors/samuelcolvin +Project-URL: Source, https://github.com/pydantic/pydantic-settings +Project-URL: Changelog, https://github.com/pydantic/pydantic-settings/releases +Project-URL: Documentation, https://docs.pydantic.dev/dev-v2/concepts/pydantic_settings/ +Author-email: Samuel Colvin , Eric Jolibois , Hasan Ramezani +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Framework :: Pydantic +Classifier: Framework :: Pydantic :: 2 +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.10 +Requires-Dist: pydantic>=2.7.0 +Requires-Dist: python-dotenv>=0.21.0 +Requires-Dist: typing-inspection>=0.4.0 +Provides-Extra: aws-secrets-manager +Requires-Dist: boto3>=1.35.0; extra == 'aws-secrets-manager' +Provides-Extra: azure-key-vault +Requires-Dist: azure-identity>=1.16.0; extra == 'azure-key-vault' +Requires-Dist: azure-keyvault-secrets>=4.8.0; extra == 'azure-key-vault' +Provides-Extra: gcp-secret-manager +Requires-Dist: google-cloud-secret-manager>=2.23.1; extra == 'gcp-secret-manager' +Provides-Extra: toml +Requires-Dist: tomli>=2.0.1; extra == 'toml' +Provides-Extra: yaml +Requires-Dist: pyyaml>=6.0.1; extra == 'yaml' +Description-Content-Type: text/markdown + +# pydantic-settings + +[![CI](https://github.com/pydantic/pydantic-settings/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/pydantic/pydantic-settings/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage](https://codecov.io/gh/pydantic/pydantic-settings/branch/main/graph/badge.svg)](https://codecov.io/gh/pydantic/pydantic-settings) +[![pypi](https://img.shields.io/pypi/v/pydantic-settings.svg)](https://pypi.python.org/pypi/pydantic-settings) +[![license](https://img.shields.io/github/license/pydantic/pydantic-settings.svg)](https://github.com/pydantic/pydantic-settings/blob/main/LICENSE) +[![downloads](https://static.pepy.tech/badge/pydantic-settings/month)](https://pepy.tech/project/pydantic-settings) +[![versions](https://img.shields.io/pypi/pyversions/pydantic-settings.svg)](https://github.com/pydantic/pydantic-settings) + +Settings management using Pydantic. + +See [documentation](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/) for more details. + +## Part of the Pydantic Stack + +The Pydantic Stack is everything you need to ship production-grade AI agents: + +- [Pydantic AI](https://pydantic.dev/pydantic-ai?utm_source=github&utm_medium=readme&utm_campaign=pydantic-settings) - Type-safe agent framework +- [Pydantic Logfire](https://pydantic.dev/logfire?utm_source=github&utm_medium=readme&utm_campaign=pydantic-settings) - AI-first, full-stack observability +- [Logfire AI Gateway](https://pydantic.dev/ai-gateway?utm_source=github&utm_medium=readme&utm_campaign=pydantic-settings) - Unified LLM proxy diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD new file mode 100644 index 0000000..ebd1905 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD @@ -0,0 +1,27 @@ +pydantic_settings/__init__.py,sha256=F24B0u2U_vPocmZwgqpN83JtwfFSAbsl3VePWAJCBbE,1781 +pydantic_settings/exceptions.py,sha256=o9WAppV9m_8334HP2LqxzbrVfM4WHV56Cqit57tgL4A,426 +pydantic_settings/main.py,sha256=HxKWdFB6duziUY4ezxTt5BS2Hlb-880pxsMO1H-GC_I,47390 +pydantic_settings/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic_settings/utils.py,sha256=ZPXGoD5MU3QLl6TlT_P2VqP11kMqb9rqp0nc3nsCLRc,1703 +pydantic_settings/version.py,sha256=jSdrSRDnJmdDSOi5UEA39d_r3Ov1jn5kIrqsZnD1-Po,19 +pydantic_settings/sources/__init__.py,sha256=sOdQuzLbpBvJWKWGUyIK_ZeLvJnAvFiFNARE_PVHXJ4,2404 +pydantic_settings/sources/base.py,sha256=5X-IrXve090QH-u39NRVcVB5E3hUsNuxqTOOA3h-_4Q,26340 +pydantic_settings/sources/types.py,sha256=6XH_-XSRVb78LbDFSDIAcfA7GqP2237ozUlwZqUjTaQ,2617 +pydantic_settings/sources/utils.py,sha256=1xhDF5cG-JZxYjUf_xDTZmioM-LOoRGWhBuk7v3ci3Q,14570 +pydantic_settings/sources/providers/__init__.py,sha256=gE6XLz6hbKpc_sSidAQ-UM001uWUXg7xGU-IRHrC6Ug,1281 +pydantic_settings/sources/providers/aws.py,sha256=jielh-5n_kl8b8gGOd3kq3Mr1UkwW5JjUCqqtcOzw4s,2729 +pydantic_settings/sources/providers/azure.py,sha256=ljAhpbQ2CENyBBm8StZBXVhEhZN4rDqBcRP4Ji8kdYA,5689 +pydantic_settings/sources/providers/cli.py,sha256=e_LHvvyQyRXmp4s2pLJdVDCvMsR46LlayMrSJAzFGO4,79106 +pydantic_settings/sources/providers/dotenv.py,sha256=oul0VHKUS0UFvr5eFcR5gPDIP77QHMpH6963RFDzuak,8328 +pydantic_settings/sources/providers/env.py,sha256=vyV26JsO5LhLOhPX86k6pEK1ZQetSjHQH5kIrNTQYV4,16235 +pydantic_settings/sources/providers/gcp.py,sha256=ykP5sLd6l95eyAh3oHz7Me0tnlqNZnYm_YRuwyxPrpM,15238 +pydantic_settings/sources/providers/json.py,sha256=9rohQV_nK0z-Xn2n6dLU63-Kx3O7rxSkaykkMVUdgos,1673 +pydantic_settings/sources/providers/nested_secrets.py,sha256=k6pntQ_8GGfpRzZTMNAt3F7RaAN64Xjkv08b2iNRkOM,9260 +pydantic_settings/sources/providers/pyproject.py,sha256=-NYo0pHE0LuebcCHu5e2x5uYPYjT5Cch9W5nD23wwmo,2153 +pydantic_settings/sources/providers/secrets.py,sha256=8c3tV9FyW36P5TTjiWU4sF7XIYObkLMgxj7a8WL-pns,5188 +pydantic_settings/sources/providers/toml.py,sha256=FCLO4KuuvhDPm4cjEy18DGPghg6AQzq8sHzwmYudTio,3351 +pydantic_settings/sources/providers/yaml.py,sha256=rKkIPx2DF-3-prmoniU13GrxRYFjsEx84EvTbLjiZD4,5726 +pydantic_settings-2.15.0.dist-info/METADATA,sha256=WsEuQI32Lx1MdkMUbZ3wSs50nx_lzyizt5S-Cyg0ym0,3885 +pydantic_settings-2.15.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87 +pydantic_settings-2.15.0.dist-info/licenses/LICENSE,sha256=6zVadT4CA0bTPYO_l2kTW4n8YQVorFMaAcKVvO5_2Zg,1103 +pydantic_settings-2.15.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL new file mode 100644 index 0000000..7401812 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.31.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..d90598f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Samuel Colvin and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py new file mode 100644 index 0000000..8cdd40c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py @@ -0,0 +1,70 @@ +from .exceptions import IncompleteFieldDefinitionWarning, SettingsError +from .main import BaseSettings, CliApp, SettingsConfigDict +from .sources import ( + CLI_SUPPRESS, + AWSSecretsManagerSettingsSource, + AzureKeyVaultSettingsSource, + CliDualFlag, + CliExplicitFlag, + CliImplicitFlag, + CliMutuallyExclusiveGroup, + CliPositionalArg, + CliSettingsSource, + CliSubCommand, + CliSuppress, + CliToggleFlag, + CliUnknownArgs, + DotEnvSettingsSource, + EnvSettingsSource, + ForceDecode, + GoogleSecretManagerSettingsSource, + InitSettingsSource, + JsonConfigSettingsSource, + NestedSecretsSettingsSource, + NoDecode, + PydanticBaseSettingsSource, + PyprojectTomlConfigSettingsSource, + SecretsSettingsSource, + TomlConfigSettingsSource, + YamlConfigSettingsSource, + get_subcommand, +) +from .version import VERSION + +__all__ = ( + 'CLI_SUPPRESS', + 'AWSSecretsManagerSettingsSource', + 'AzureKeyVaultSettingsSource', + 'BaseSettings', + 'CliApp', + 'CliDualFlag', + 'CliExplicitFlag', + 'CliImplicitFlag', + 'CliMutuallyExclusiveGroup', + 'CliPositionalArg', + 'CliSettingsSource', + 'CliSubCommand', + 'CliSuppress', + 'CliToggleFlag', + 'CliUnknownArgs', + 'DotEnvSettingsSource', + 'EnvSettingsSource', + 'ForceDecode', + 'GoogleSecretManagerSettingsSource', + 'IncompleteFieldDefinitionWarning', + 'InitSettingsSource', + 'JsonConfigSettingsSource', + 'NestedSecretsSettingsSource', + 'NoDecode', + 'PydanticBaseSettingsSource', + 'PyprojectTomlConfigSettingsSource', + 'SecretsSettingsSource', + 'SettingsConfigDict', + 'SettingsError', + 'TomlConfigSettingsSource', + 'YamlConfigSettingsSource', + '__version__', + 'get_subcommand', +) + +__version__ = VERSION diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py new file mode 100644 index 0000000..dab7c71 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py @@ -0,0 +1,10 @@ +class SettingsError(ValueError): + """Base exception for settings-related errors.""" + + +class IncompleteFieldDefinitionWarning(UserWarning): + """Warning emitted when a field with an incomplete definition is used during settings resolution. + + A field definition is incomplete when its annotation contains unresolved forward references, + in which case settings sources may fail to correctly resolve its value. + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py new file mode 100644 index 0000000..3366241 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py @@ -0,0 +1,978 @@ +from __future__ import annotations as _annotations + +import asyncio +import inspect +import logging +import re +import threading +import warnings +from argparse import Namespace +from collections.abc import Mapping +from types import SimpleNamespace +from typing import Any, ClassVar, Literal, TextIO, TypeVar, cast + +from pydantic import ConfigDict +from pydantic._internal._config import config_keys +from pydantic._internal._signature import _field_name_for_signature +from pydantic._internal._utils import deep_update, is_model_class +from pydantic.dataclasses import is_pydantic_dataclass +from pydantic.main import BaseModel + +from .exceptions import SettingsError +from .sources import ( + ENV_FILE_SENTINEL, + CliSettingsSource, + ConfigFileSourceType, + DefaultSettingsSource, + DotenvFiltering, + DotEnvSettingsSource, + DotenvType, + EnvPrefixTarget, + EnvSettingsSource, + InitSettingsSource, + JsonConfigSettingsSource, + PathType, + PydanticBaseSettingsSource, + PydanticModel, + PyprojectTomlConfigSettingsSource, + SecretsSettingsSource, + TomlConfigSettingsSource, + YamlConfigSettingsSource, + get_subcommand, +) +from .sources.utils import InitState, _get_alias_names, _warn_if_field_info_incomplete +from .utils import _settings_debug_enabled, logger + +T = TypeVar('T') + + +class SettingsConfigDict(ConfigDict, total=False): + case_sensitive: bool + nested_model_default_partial_update: bool | None + env_prefix: str + env_prefix_target: EnvPrefixTarget + env_file: DotenvType | None + env_file_encoding: str | None + dotenv_filtering: DotenvFiltering | None + env_ignore_empty: bool + env_nested_delimiter: str | None + env_nested_max_split: int | None + env_parse_none_str: str | None + env_parse_enums: bool | None + cli_prog_name: str | None + cli_parse_args: bool | list[str] | tuple[str, ...] | None + cli_parse_none_str: str | None + cli_hide_none_type: bool + cli_avoid_json: bool + cli_enforce_required: bool + cli_use_class_docs_for_groups: bool + cli_show_env_vars: bool + cli_exit_on_error: bool + cli_prefix: str + cli_flag_prefix_char: str + cli_implicit_flags: bool | Literal['dual', 'toggle'] | None + cli_ignore_unknown_args: bool | None + cli_kebab_case: bool | Literal['all', 'no_enums'] | None + cli_shortcuts: Mapping[str, str | list[str]] | None + secrets_dir: PathType | None + json_file: ConfigFileSourceType | None + json_file_encoding: str | None + yaml_file: ConfigFileSourceType | None + yaml_file_encoding: str | None + yaml_config_section: str | None + """ + Specifies the section in a YAML file from which to load the settings. + Supports dot-notation for nested paths (e.g., 'config.app.settings'). + If provided, the settings will be loaded from the specified section. + This is useful when the YAML file contains multiple configuration sections + and you only want to load a specific subset into your settings model. + """ + + pyproject_toml_depth: int + """ + Number of levels **up** from the current working directory to attempt to find a pyproject.toml + file. + + This is only used when a pyproject.toml file is not found in the current working directory. + """ + + pyproject_toml_table_header: tuple[str, ...] + """ + Header of the TOML table within a pyproject.toml file to use when filling variables. + This is supplied as a `tuple[str, ...]` instead of a `str` to accommodate for headers + containing a `.`. + + For example, `toml_table_header = ("tool", "my.tool", "foo")` can be used to fill variable + values from a table with header `[tool."my.tool".foo]`. + + To use the root table, exclude this config setting or provide an empty tuple. + """ + + toml_file: ConfigFileSourceType | None + + toml_table_header: tuple[str, ...] + """ + Header of the TOML table in a TOML file to use when filling variables. + Similar to `pyproject_toml_table_header`, but for generic TOML file handling. + + Default is to use the root table, i.e. no table header. + """ + enable_decoding: bool + + +# Extend `config_keys` by pydantic settings config keys to +# support setting config through class kwargs. +# Pydantic uses `config_keys` in `pydantic._internal._config.ConfigWrapper.for_model` +# to extract config keys from model kwargs, So, by adding pydantic settings keys to +# `config_keys`, they will be considered as valid config keys and will be collected +# by Pydantic. +config_keys |= set(SettingsConfigDict.__annotations__.keys()) + + +class BaseSettings(BaseModel): + """ + Base class for settings, allowing values to be overridden by environment variables. + + This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose), + Heroku and any 12 factor app design. + + All the below attributes can be set via `model_config`. + + Args: + _case_sensitive: Whether environment and CLI variable names should be read with case-sensitivity. + Defaults to `None`. + _nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields. + Defaults to `False`. + _env_prefix: Prefix for all environment variables. Defaults to `None`. + _env_prefix_target: Targets to which `_env_prefix` is applied. Default: `variable`. + _env_file: The env file(s) to load settings values from. Defaults to `Path('')`, which + means that the value from `model_config['env_file']` should be used. You can also pass + `None` to indicate that environment variables should not be loaded from an env file. + _env_file_encoding: The env file encoding, e.g. `'latin-1'`. Defaults to `None`. + _env_ignore_empty: Ignore environment variables where the value is an empty string. Default to `False`. + _env_nested_delimiter: The nested env values delimiter. Defaults to `None`. + _env_nested_max_split: The nested env values maximum nesting. Defaults to `None`, which means no limit. + _env_parse_none_str: The env string value that should be parsed (e.g. "null", "void", "None", etc.) + into `None` type(None). Defaults to `None` type(None), which means no parsing should occur. + _env_parse_enums: Parse enum field names to values. Defaults to `None.`, which means no parsing should occur. + _cli_prog_name: The CLI program name to display in help text. Defaults to `None` if _cli_parse_args is `None`. + Otherwise, defaults to sys.argv[0]. + _cli_parse_args: The list of CLI arguments to parse. Defaults to None. + If set to `True`, defaults to sys.argv[1:]. + _cli_settings_source: Override the default CLI settings source with a user defined instance. Defaults to None. + _cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into + `None` type(None). Defaults to _env_parse_none_str value if set. Otherwise, defaults to "null" if + _cli_avoid_json is `False`, and "None" if _cli_avoid_json is `True`. + _cli_hide_none_type: Hide `None` values in CLI help text. Defaults to `False`. + _cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to `False`. + _cli_enforce_required: Enforce required fields at the CLI. Defaults to `False`. + _cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions. + Defaults to `False`. + _cli_show_env_vars: Show resolved environment variable names in CLI help text. Defaults to `False`. + _cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs. + Defaults to `True`. + _cli_prefix: The root parser command line arguments prefix. Defaults to "". + _cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'. + _cli_implicit_flags: Controls how `bool` fields are exposed as CLI flags. + + - False (default): no implicit flags are generated; booleans must be set explicitly (e.g. --flag=true). + - True / 'dual': optional boolean fields generate both positive and negative forms (--flag and --no-flag). + - 'toggle': required boolean fields remain in 'dual' mode, while optional boolean fields generate a single + flag aligned with the default value (if default=False, expose --flag; if default=True, expose --no-flag). + _cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`. + _cli_kebab_case: CLI args use kebab case. Defaults to `False`. + _cli_shortcuts: Mapping of target field name to alias names. Defaults to `None`. + _secrets_dir: The secret files directory or a sequence of directories. Defaults to `None`. + _build_sources: Pre-initialized sources and init kwargs to use for building instantiation values. + Defaults to `None`. + """ + + # Note: when adding new parameters, make sure to use `object` instead of `Any` to avoid issues with the Mypy plugin + # when used with `--disallow-any-explicit`. If `Any` needs to be used as a generic parameter for variance (e.g. in `_build_sources`), + # make sure to update the Pydantic Mypy plugin accordingly. + def __init__( + __pydantic_self__, + _case_sensitive: bool | None = None, + _nested_model_default_partial_update: bool | None = None, + _env_prefix: str | None = None, + _env_prefix_target: EnvPrefixTarget | None = None, + _env_file: DotenvType | None = ENV_FILE_SENTINEL, + _env_file_encoding: str | None = None, + _env_ignore_empty: bool | None = None, + _env_nested_delimiter: str | None = None, + _env_nested_max_split: int | None = None, + _env_parse_none_str: str | None = None, + _env_parse_enums: bool | None = None, + _cli_prog_name: str | None = None, + _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, + _cli_settings_source: CliSettingsSource[Any] | None = None, + _cli_parse_none_str: str | None = None, + _cli_hide_none_type: bool | None = None, + _cli_avoid_json: bool | None = None, + _cli_enforce_required: bool | None = None, + _cli_use_class_docs_for_groups: bool | None = None, + _cli_show_env_vars: bool | None = None, + _cli_exit_on_error: bool | None = None, + _cli_prefix: str | None = None, + _cli_flag_prefix_char: str | None = None, + _cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None, + _cli_ignore_unknown_args: bool | None = None, + _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None, + _cli_shortcuts: Mapping[str, str | list[str]] | None = None, + _secrets_dir: PathType | None = None, + _build_sources: tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]] | None = None, + **values: Any, + ) -> None: + sources, init_kwargs = ( + _build_sources + if _build_sources is not None + else __pydantic_self__.__class__._settings_init_sources( + _case_sensitive=_case_sensitive, + _nested_model_default_partial_update=_nested_model_default_partial_update, + _env_prefix=_env_prefix, + _env_prefix_target=_env_prefix_target, + _env_file=_env_file, + _env_file_encoding=_env_file_encoding, + _env_ignore_empty=_env_ignore_empty, + _env_nested_delimiter=_env_nested_delimiter, + _env_nested_max_split=_env_nested_max_split, + _env_parse_none_str=_env_parse_none_str, + _env_parse_enums=_env_parse_enums, + _cli_prog_name=_cli_prog_name, + _cli_parse_args=_cli_parse_args, + _cli_settings_source=_cli_settings_source, + _cli_parse_none_str=_cli_parse_none_str, + _cli_hide_none_type=_cli_hide_none_type, + _cli_avoid_json=_cli_avoid_json, + _cli_enforce_required=_cli_enforce_required, + _cli_use_class_docs_for_groups=_cli_use_class_docs_for_groups, + _cli_show_env_vars=_cli_show_env_vars, + _cli_exit_on_error=_cli_exit_on_error, + _cli_prefix=_cli_prefix, + _cli_flag_prefix_char=_cli_flag_prefix_char, + _cli_implicit_flags=_cli_implicit_flags, + _cli_ignore_unknown_args=_cli_ignore_unknown_args, + _cli_kebab_case=_cli_kebab_case, + _cli_shortcuts=_cli_shortcuts, + _secrets_dir=_secrets_dir, + _init_kwargs=values, + ) + ) + + super().__init__(**__pydantic_self__.__class__._settings_build_values(sources, init_kwargs)) + + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + """ + Define the sources and their order for loading the settings values. + + Args: + settings_cls: The Settings class. + init_settings: The `InitSettingsSource` instance. + env_settings: The `EnvSettingsSource` instance. + dotenv_settings: The `DotEnvSettingsSource` instance. + file_secret_settings: The `SecretsSettingsSource` instance. + + Returns: + A tuple containing the sources and their order for loading the settings values. + """ + return init_settings, env_settings, dotenv_settings, file_secret_settings + + @classmethod + def _settings_init_sources( + cls, + _case_sensitive: bool | None = None, + _nested_model_default_partial_update: bool | None = None, + _env_prefix: str | None = None, + _env_prefix_target: EnvPrefixTarget | None = None, + _env_file: DotenvType | None = ENV_FILE_SENTINEL, + _env_file_encoding: str | None = None, + _env_ignore_empty: bool | None = None, + _env_nested_delimiter: str | None = None, + _env_nested_max_split: int | None = None, + _env_parse_none_str: str | None = None, + _env_parse_enums: bool | None = None, + _cli_prog_name: str | None = None, + _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, + _cli_settings_source: CliSettingsSource[Any] | None = None, + _cli_parse_none_str: str | None = None, + _cli_hide_none_type: bool | None = None, + _cli_avoid_json: bool | None = None, + _cli_enforce_required: bool | None = None, + _cli_use_class_docs_for_groups: bool | None = None, + _cli_show_env_vars: bool | None = None, + _cli_exit_on_error: bool | None = None, + _cli_prefix: str | None = None, + _cli_flag_prefix_char: str | None = None, + _cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None, + _cli_ignore_unknown_args: bool | None = None, + _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None, + _cli_shortcuts: Mapping[str, str | list[str]] | None = None, + _secrets_dir: PathType | None = None, + _init_kwargs: dict[str, Any] | None = None, + ) -> tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]]: + # Determine settings config values + case_sensitive = _case_sensitive if _case_sensitive is not None else cls.model_config.get('case_sensitive') + env_prefix = _env_prefix if _env_prefix is not None else cls.model_config.get('env_prefix') + env_prefix_target = ( + _env_prefix_target if _env_prefix_target is not None else cls.model_config.get('env_prefix_target') + ) + nested_model_default_partial_update = ( + _nested_model_default_partial_update + if _nested_model_default_partial_update is not None + else cls.model_config.get('nested_model_default_partial_update') + ) + env_file = _env_file if _env_file != ENV_FILE_SENTINEL else cls.model_config.get('env_file') + env_file_encoding = ( + _env_file_encoding if _env_file_encoding is not None else cls.model_config.get('env_file_encoding') + ) + env_ignore_empty = ( + _env_ignore_empty if _env_ignore_empty is not None else cls.model_config.get('env_ignore_empty') + ) + env_nested_delimiter = ( + _env_nested_delimiter if _env_nested_delimiter is not None else cls.model_config.get('env_nested_delimiter') + ) + env_nested_max_split = ( + _env_nested_max_split if _env_nested_max_split is not None else cls.model_config.get('env_nested_max_split') + ) + env_parse_none_str = ( + _env_parse_none_str if _env_parse_none_str is not None else cls.model_config.get('env_parse_none_str') + ) + env_parse_enums = _env_parse_enums if _env_parse_enums is not None else cls.model_config.get('env_parse_enums') + + cli_prog_name = _cli_prog_name if _cli_prog_name is not None else cls.model_config.get('cli_prog_name') + cli_parse_args = _cli_parse_args if _cli_parse_args is not None else cls.model_config.get('cli_parse_args') + cli_settings_source = ( + _cli_settings_source if _cli_settings_source is not None else cls.model_config.get('cli_settings_source') + ) + cli_parse_none_str = ( + _cli_parse_none_str if _cli_parse_none_str is not None else cls.model_config.get('cli_parse_none_str') + ) + cli_parse_none_str = cli_parse_none_str if not env_parse_none_str else env_parse_none_str + cli_hide_none_type = ( + _cli_hide_none_type if _cli_hide_none_type is not None else cls.model_config.get('cli_hide_none_type') + ) + cli_avoid_json = _cli_avoid_json if _cli_avoid_json is not None else cls.model_config.get('cli_avoid_json') + cli_enforce_required = ( + _cli_enforce_required if _cli_enforce_required is not None else cls.model_config.get('cli_enforce_required') + ) + cli_use_class_docs_for_groups = ( + _cli_use_class_docs_for_groups + if _cli_use_class_docs_for_groups is not None + else cls.model_config.get('cli_use_class_docs_for_groups') + ) + cli_show_env_vars = ( + _cli_show_env_vars if _cli_show_env_vars is not None else cls.model_config.get('cli_show_env_vars') + ) + cli_exit_on_error = ( + _cli_exit_on_error if _cli_exit_on_error is not None else cls.model_config.get('cli_exit_on_error') + ) + cli_prefix = _cli_prefix if _cli_prefix is not None else cls.model_config.get('cli_prefix') + cli_flag_prefix_char = ( + _cli_flag_prefix_char if _cli_flag_prefix_char is not None else cls.model_config.get('cli_flag_prefix_char') + ) + cli_implicit_flags = ( + _cli_implicit_flags if _cli_implicit_flags is not None else cls.model_config.get('cli_implicit_flags') + ) + cli_ignore_unknown_args = ( + _cli_ignore_unknown_args + if _cli_ignore_unknown_args is not None + else cls.model_config.get('cli_ignore_unknown_args') + ) + cli_kebab_case = _cli_kebab_case if _cli_kebab_case is not None else cls.model_config.get('cli_kebab_case') + cli_shortcuts = _cli_shortcuts if _cli_shortcuts is not None else cls.model_config.get('cli_shortcuts') + + secrets_dir = _secrets_dir if _secrets_dir is not None else cls.model_config.get('secrets_dir') + + # Configure built-in sources, sharing the same init state so that incomplete + # fields are only warned about once per settings resolution. + init_state: InitState = {'field_info_ids': set()} + default_settings = DefaultSettingsSource( + cls, nested_model_default_partial_update=nested_model_default_partial_update, _init_state=init_state + ) + init_settings = InitSettingsSource( + cls, + init_kwargs=_init_kwargs if _init_kwargs is not None else {}, + nested_model_default_partial_update=nested_model_default_partial_update, + _init_state=default_settings._init_state, + ) + env_settings = EnvSettingsSource( + cls, + case_sensitive=case_sensitive, + env_prefix=env_prefix, + env_prefix_target=env_prefix_target, + env_nested_delimiter=env_nested_delimiter, + env_nested_max_split=env_nested_max_split, + env_ignore_empty=env_ignore_empty, + env_parse_none_str=env_parse_none_str, + env_parse_enums=env_parse_enums, + _init_state=init_settings._init_state, + ) + dotenv_settings = DotEnvSettingsSource( + cls, + env_file=env_file, + env_file_encoding=env_file_encoding, + case_sensitive=case_sensitive, + env_prefix=env_prefix, + env_prefix_target=env_prefix_target, + env_nested_delimiter=env_nested_delimiter, + env_nested_max_split=env_nested_max_split, + env_ignore_empty=env_ignore_empty, + env_parse_none_str=env_parse_none_str, + env_parse_enums=env_parse_enums, + _init_state=env_settings._init_state, + ) + + file_secret_settings = SecretsSettingsSource( + cls, + secrets_dir=secrets_dir, + case_sensitive=case_sensitive, + env_prefix=env_prefix, + env_prefix_target=env_prefix_target, + _init_state=dotenv_settings._init_state, + ) + # Provide a hook to set built-in sources priority and add / remove sources + sources = cls.settings_customise_sources( + cls, + init_settings=init_settings, + env_settings=env_settings, + dotenv_settings=dotenv_settings, + file_secret_settings=file_secret_settings, + ) + (default_settings,) + custom_cli_sources = [source for source in sources if isinstance(source, CliSettingsSource)] + if not any(custom_cli_sources): + if isinstance(cli_settings_source, CliSettingsSource): + sources = (cli_settings_source,) + sources + elif cli_parse_args is not None: + cli_settings = CliSettingsSource[Any]( + cls, + _init_state=file_secret_settings._init_state, + cli_prog_name=cli_prog_name, + cli_parse_args=cli_parse_args, + cli_parse_none_str=cli_parse_none_str, + cli_hide_none_type=cli_hide_none_type, + cli_avoid_json=cli_avoid_json, + cli_enforce_required=cli_enforce_required, + cli_use_class_docs_for_groups=cli_use_class_docs_for_groups, + cli_show_env_vars=cli_show_env_vars, + cli_exit_on_error=cli_exit_on_error, + cli_prefix=cli_prefix, + cli_flag_prefix_char=cli_flag_prefix_char, + cli_implicit_flags=cli_implicit_flags, + cli_ignore_unknown_args=cli_ignore_unknown_args, + cli_kebab_case=cli_kebab_case, + cli_shortcuts=cli_shortcuts, + case_sensitive=case_sensitive, + _env_settings_source=env_settings, + ) + sources = (cli_settings,) + sources + # We ensure that if command line arguments haven't been parsed yet, we do so. + elif cli_parse_args not in (None, False) and not custom_cli_sources[0].env_vars: + custom_cli_sources[0](args=cli_parse_args) + + cls._settings_warn_unused_config_keys(sources, cls.model_config) + + return sources, _init_kwargs if _init_kwargs is not None else {} + + @classmethod + def _settings_build_values( + cls, sources: tuple[PydanticBaseSettingsSource, ...], init_kwargs: dict[str, Any] + ) -> dict[str, Any]: + if sources: + state: dict[str, Any] = {} + defaults: dict[str, Any] = {} + states: dict[str, dict[str, Any]] = {} + for source in sources: + if isinstance(source, PydanticBaseSettingsSource): + source._set_current_state(state) + source._set_settings_sources_data(states) + + source_name = source.__name__ if hasattr(source, '__name__') else type(source).__name__ + source_state = source() + + if isinstance(source, DefaultSettingsSource): + defaults = source_state + + states[source_name] = source_state + state = deep_update(source_state, state) + + # Strip any default values not explicitly set before returning final state + state = {key: val for key, val in state.items() if key not in defaults or defaults[key] != val} + + if _settings_debug_enabled() and logger.isEnabledFor(logging.DEBUG): + cls._settings_log_debug(states) + # The last source is the `DefaultSettingsSource` instance created in `_settings_init_sources()`, + # holding the init state shared by all built-in sources: + last_source = sources[-1] + init_state = last_source._init_state if isinstance(last_source, PydanticBaseSettingsSource) else InitState() + cls._settings_restore_init_kwarg_names(cls, init_kwargs, state, init_state) + + return state + else: + # no one should mean to do this, but I think returning an empty dict is marginally preferable + # to an informative error and much better than a confusing error + return {} + + @classmethod + def _settings_log_debug(cls, states: dict[str, dict[str, Any]]) -> None: + """Log the data collected by each settings source in priority order. + + Enabled by setting the ``PYDANTIC_SETTINGS_DEBUG`` environment variable to a truthy value. + This is emitted at ``DEBUG`` level on the ``pydantic_settings`` logger. + + Warning: the output may contain sensitive values loaded from the environment, dotenv + files, or secret files. Only enable it in a trusted debugging context. + """ + lines = [f'Resolving settings for {cls.__name__!r} (sources in priority order, highest first):'] + for name, source_state in states.items(): + lines.append(f' {name}: {source_state!r}') + logger.debug('\n'.join(lines)) + + @staticmethod + def _settings_restore_init_kwarg_names( + settings_cls: type[BaseSettings], + init_kwargs: dict[str, Any], + state: dict[str, Any], + init_state: InitState, + ) -> None: + """ + Restore the init_kwarg key names to the final merged state dictionary. + + This function renames keys in state to match the original init_kwargs key names, + preserving the merged values from the source priority order. + """ + if init_kwargs and state: + state_kwarg_names = set(state.keys()) + init_kwarg_names = set(init_kwargs.keys()) + for field_name, field_info in settings_cls.model_fields.items(): + _warn_if_field_info_incomplete(field_info, field_name, init_state) + alias_names, *_ = _get_alias_names(field_name, field_info) + matchable_names = set(alias_names) + include_name = settings_cls.model_config.get( + 'populate_by_name', False + ) or settings_cls.model_config.get('validate_by_name', False) + if include_name: + matchable_names.add(field_name) + init_kwarg_name = init_kwarg_names & matchable_names + state_kwarg_name = state_kwarg_names & matchable_names + if init_kwarg_name and state_kwarg_name: + # Use deterministic selection for both keys. + # Target key: the key from init_kwargs that should be used in the final state. + target_key = next(iter(init_kwarg_name)) + # Source key: prefer the alias (first in alias_names) if present in state, + # as InitSettingsSource normalizes to the preferred alias. + # This ensures we get the highest-priority value for this field. + source_key = None + for alias in alias_names: + if alias in state_kwarg_name: + source_key = alias + break + if source_key is None: + # Fall back to field_name if no alias found in state + source_key = field_name if field_name in state_kwarg_name else next(iter(state_kwarg_name)) + # Get the value from the source key and remove all matching keys + value = state.pop(source_key) + for key in state_kwarg_name - {source_key}: + state.pop(key, None) + state[target_key] = value + + @staticmethod + def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None: + """ + Warns if any values in model_config were set but the corresponding settings source has not been initialised. + + The list alternative sources and their config keys can be found here: + https://docs.pydantic.dev/latest/concepts/pydantic_settings/#other-settings-source + + Args: + sources: The tuple of configured sources + model_config: The model config to check for unused config keys + """ + + def warn_if_not_used(source_type: type[PydanticBaseSettingsSource], keys: tuple[str, ...]) -> None: + if not any(isinstance(source, source_type) for source in sources): + for key in keys: + if model_config.get(key) is not None: + warnings.warn( + f'Config key `{key}` is set in model_config but will be ignored because no ' + f'{source_type.__name__} source is configured. To use this config key, add a ' + f'{source_type.__name__} source to the settings sources via the ' + 'settings_customise_sources hook.', + UserWarning, + stacklevel=3, + ) + + warn_if_not_used(JsonConfigSettingsSource, ('json_file', 'json_file_encoding')) + warn_if_not_used(PyprojectTomlConfigSettingsSource, ('pyproject_toml_depth', 'pyproject_toml_table_header')) + warn_if_not_used(TomlConfigSettingsSource, ('toml_file', 'toml_table_header')) + warn_if_not_used(YamlConfigSettingsSource, ('yaml_file', 'yaml_file_encoding', 'yaml_config_section')) + + model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict( + extra='forbid', + arbitrary_types_allowed=True, + validate_default=True, + case_sensitive=False, + env_prefix='', + env_prefix_target='variable', + nested_model_default_partial_update=False, + env_file=None, + env_file_encoding=None, + env_ignore_empty=False, + env_nested_delimiter=None, + env_nested_max_split=None, + env_parse_none_str=None, + env_parse_enums=None, + cli_prog_name=None, + cli_parse_args=None, + cli_parse_none_str=None, + cli_hide_none_type=False, + cli_avoid_json=False, + cli_enforce_required=False, + cli_use_class_docs_for_groups=False, + cli_show_env_vars=False, + cli_exit_on_error=True, + cli_prefix='', + cli_flag_prefix_char='-', + cli_implicit_flags=False, + cli_ignore_unknown_args=False, + cli_kebab_case=False, + cli_shortcuts=None, + json_file=None, + json_file_encoding=None, + yaml_file=None, + yaml_file_encoding=None, + yaml_config_section=None, + toml_file=None, + secrets_dir=None, + protected_namespaces=('model_validate', 'model_dump', 'settings_customise_sources'), + enable_decoding=True, + ) + + +class CliApp: + """ + A utility class for running Pydantic `BaseSettings`, `BaseModel`, or `pydantic.dataclasses.dataclass` as + CLI applications. + """ + + _subcommand_stack: ClassVar[dict[int, tuple[CliSettingsSource[Any], Any, str]]] = {} + _ansi_color: ClassVar[re.Pattern[str]] = re.compile(r'\x1b\[[0-9;]*m') + + @staticmethod + def _get_base_settings_cls(model_cls: type[Any]) -> type[BaseSettings]: + if issubclass(model_cls, BaseSettings): + return model_cls + + class CliAppBaseSettings(BaseSettings, model_cls): # type: ignore + __doc__ = model_cls.__doc__ + model_config = SettingsConfigDict( + nested_model_default_partial_update=True, + case_sensitive=True, + cli_hide_none_type=True, + cli_avoid_json=True, + cli_enforce_required=True, + cli_implicit_flags=True, + cli_kebab_case=True, + ) + + return CliAppBaseSettings + + @staticmethod + def _run_cli_cmd(model: Any, cli_cmd_method_name: str, is_required: bool) -> Any: + command = getattr(type(model), cli_cmd_method_name, None) + if command is None: + if is_required: + raise SettingsError(f'Error: {type(model).__name__} class is missing {cli_cmd_method_name} entrypoint') + return model + + # If the method is asynchronous, we handle its execution based on the current event loop status. + if inspect.iscoroutinefunction(command): + # For asynchronous methods, we have two execution scenarios: + # 1. If no event loop is running in the current thread, run the coroutine directly with asyncio.run(). + # 2. If an event loop is already running in the current thread, run the coroutine in a separate thread to avoid conflicts. + try: + # Check if an event loop is currently running in this thread. + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + + if loop and loop.is_running(): + # We're in a context with an active event loop (e.g., Jupyter Notebook). + # Running asyncio.run() here would cause conflicts, so we use a separate thread. + exception_container = [] + + def run_coro() -> None: + try: + # Execute the coroutine in a new event loop in this separate thread. + asyncio.run(command(model)) + except Exception as e: + exception_container.append(e) + + thread = threading.Thread(target=run_coro) + thread.start() + thread.join() + if exception_container: + # Propagate exceptions from the separate thread. + raise exception_container[0] + else: + # No event loop is running; safe to run the coroutine directly. + asyncio.run(command(model)) + else: + # For synchronous methods, call them directly. + command(model) + + return model + + @staticmethod + def run( + model_cls: type[T], + cli_args: list[str] | Namespace | SimpleNamespace | dict[str, Any] | None = None, + cli_settings_source: CliSettingsSource[Any] | None = None, + cli_exit_on_error: bool | None = None, + cli_show_env_vars: bool | None = None, + cli_cmd_method_name: str = 'cli_cmd', + **model_init_data: Any, + ) -> T: + """ + Runs a Pydantic `BaseSettings`, `BaseModel`, or `pydantic.dataclasses.dataclass` as a CLI application. + Running a model as a CLI application requires the `cli_cmd` method to be defined in the model class. + + Args: + model_cls: The model class to run as a CLI application. + cli_args: The list of CLI arguments to parse. If `cli_settings_source` is specified, this may + also be a namespace or dictionary of pre-parsed CLI arguments. Defaults to `sys.argv[1:]`. + cli_settings_source: Override the default CLI settings source with a user defined instance. + Defaults to `None`. + cli_exit_on_error: Determines whether this function exits on error. If model is subclass of + `BaseSettings`, defaults to BaseSettings `cli_exit_on_error` value. Otherwise, defaults to + `True`. + cli_show_env_vars: Show resolved environment variable names in CLI help text. Defaults to `False`. + cli_cmd_method_name: The CLI command method name to run. Defaults to "cli_cmd". + model_init_data: The model init data. + + Returns: + The ran instance of model. + + Raises: + SettingsError: If model_cls is not subclass of `BaseModel` or `pydantic.dataclasses.dataclass`. + SettingsError: If model_cls does not have a `cli_cmd` entrypoint defined. + """ + + if not (is_pydantic_dataclass(model_cls) or is_model_class(model_cls)): + raise SettingsError( + f'Error: {model_cls.__name__} is not subclass of BaseModel or pydantic.dataclasses.dataclass' + ) + + cli_settings = None + cli_parse_args = True if cli_args is None else cli_args + if cli_settings_source is not None: + if isinstance(cli_parse_args, (Namespace, SimpleNamespace, dict)): + cli_settings = cli_settings_source(parsed_args=cli_parse_args) + else: + cli_settings = cli_settings_source(args=cli_parse_args) + elif isinstance(cli_parse_args, (Namespace, SimpleNamespace, dict)): + raise SettingsError('Error: `cli_args` must be list[str] or None when `cli_settings_source` is not used') + + if not issubclass(model_cls, BaseSettings): + base_settings_cls = CliApp._get_base_settings_cls(model_cls) + sources, init_kwargs = base_settings_cls._settings_init_sources( + _cli_parse_args=cli_parse_args, # type: ignore[arg-type] + _cli_exit_on_error=cli_exit_on_error, + _cli_show_env_vars=cli_show_env_vars, + _cli_settings_source=cli_settings, + _init_kwargs=model_init_data, + ) + model = base_settings_cls(**base_settings_cls._settings_build_values(sources, init_kwargs)) + model_init_data = {} + for field_name, field_info in base_settings_cls.model_fields.items(): + model_init_data[_field_name_for_signature(field_name, field_info)] = getattr(model, field_name) + command = model_cls(**model_init_data) + else: + sources, init_kwargs = model_cls._settings_init_sources( + _cli_parse_args=cli_parse_args, # type: ignore[arg-type] + _cli_exit_on_error=cli_exit_on_error, + _cli_show_env_vars=cli_show_env_vars, + _cli_settings_source=cli_settings, + _init_kwargs=model_init_data, + ) + command = model_cls(_build_sources=(sources, init_kwargs)) + + subcommand_dest = ':subcommand' + cli_settings_source = next(source for source in sources if isinstance(source, CliSettingsSource)) + CliApp._subcommand_stack[id(command)] = (cli_settings_source, cli_settings_source.root_parser, subcommand_dest) + try: + data_model = CliApp._run_cli_cmd(command, cli_cmd_method_name, is_required=False) + finally: + del CliApp._subcommand_stack[id(command)] + return data_model + + @staticmethod + def run_subcommand( + model: PydanticModel, + cli_exit_on_error: bool | None = None, + cli_show_env_vars: bool | None = None, + cli_cmd_method_name: str = 'cli_cmd', + ) -> PydanticModel: + """ + Runs the model subcommand. Running a model subcommand requires the `cli_cmd` method to be defined in + the nested model subcommand class. + + Args: + model: The model to run the subcommand from. + cli_exit_on_error: Determines whether this function exits with error if no subcommand is found. + Defaults to model_config `cli_exit_on_error` value if set. Otherwise, defaults to `True`. + cli_show_env_vars: Show resolved environment variable names in CLI help text. Defaults to `False`. + cli_cmd_method_name: The CLI command method name to run. Defaults to "cli_cmd". + + Returns: + The ran subcommand model. + + Raises: + SystemExit: When no subcommand is found and cli_exit_on_error=`True` (the default). + SettingsError: When no subcommand is found and cli_exit_on_error=`False`. + """ + + if id(model) in CliApp._subcommand_stack: + cli_settings_source, parser, subcommand_dest = CliApp._subcommand_stack[id(model)] + else: + cli_settings_source = CliSettingsSource[Any]( + CliApp._get_base_settings_cls(type(model)), + cli_show_env_vars=cli_show_env_vars, + ) + parser = cli_settings_source.root_parser + subcommand_dest = ':subcommand' + + cli_exit_on_error = cli_settings_source.cli_exit_on_error if cli_exit_on_error is None else cli_exit_on_error + + errors: list[SettingsError | SystemExit] = [] + subcommand = get_subcommand( + model, is_required=True, cli_exit_on_error=cli_exit_on_error, _suppress_errors=errors + ) + if errors: + err = errors[0] + if err.__context__ is None and err.__cause__ is None and cli_settings_source._format_help is not None: + error_message = f'{err}\n{cli_settings_source._format_help(parser)}' + raise type(err)(error_message) from None + else: + raise err + + subcommand_cls = cast(type[BaseModel], type(subcommand)) + subcommand_arg = cli_settings_source._parser_map[subcommand_dest][subcommand_cls] + subcommand_dest = f'{subcommand_arg.dest}.:subcommand' + subcommand_parser = subcommand_arg.parser + CliApp._subcommand_stack[id(subcommand)] = (cli_settings_source, subcommand_parser, subcommand_dest) + try: + data_model = CliApp._run_cli_cmd(subcommand, cli_cmd_method_name, is_required=True) + finally: + del CliApp._subcommand_stack[id(subcommand)] + return data_model + + @staticmethod + def serialize( + model: PydanticModel, + list_style: Literal['json', 'argparse', 'lazy'] = 'json', + dict_style: Literal['json', 'env'] = 'json', + positionals_first: bool = False, + ) -> list[str]: + """ + Serializes the CLI arguments for a Pydantic data model. + + Args: + model: The data model to serialize. + list_style: + Controls how list-valued fields are serialized on the command line. + - 'json' (default): + Lists are encoded as a single JSON array. + Example: `--tags '["a","b","c"]'` + - 'argparse': + Each list element becomes its own repeated flag, following + typical `argparse` conventions. + Example: `--tags a --tags b --tags c` + - 'lazy': + Lists are emitted as a single comma-separated string without JSON + quoting or escaping. + Example: `--tags a,b,c` + dict_style: + Controls how dictionary-valued fields are serialized. + - 'json' (default): + The entire dictionary is emitted as a single JSON object. + Example: `--config '{"host": "localhost", "port": 5432}'` + - 'env': + The dictionary is flattened into multiple CLI flags using + environment-variable-style assignment. + Example: `--config host=localhost --config port=5432` + positionals_first: Controls whether positional arguments should be serialized + first compared to optional arguments. Defaults to `False`. + + Returns: + The serialized CLI arguments for the data model. + """ + + base_settings_cls = CliApp._get_base_settings_cls(type(model)) + serialized_args = CliSettingsSource[Any](base_settings_cls)._serialized_args( + model, + list_style=list_style, + dict_style=dict_style, + positionals_first=positionals_first, + ) + return CliSettingsSource._flatten_serialized_args(serialized_args, positionals_first) + + @staticmethod + def format_help( + model: PydanticModel | type[T], + cli_settings_source: CliSettingsSource[Any] | None = None, + strip_ansi_color: bool = False, + ) -> str: + """ + Return a string containing a help message for a Pydantic model. + + Args: + model: The model or model class. + cli_settings_source: Override the default CLI settings source with a user defined instance. + Defaults to `None`. + strip_ansi_color: Strips ANSI color codes from the help message when set to `True`. + + Returns: + The help message string for the model. + """ + model_cls = model if isinstance(model, type) else type(model) + if cli_settings_source is None: + if not isinstance(model, type) and id(model) in CliApp._subcommand_stack: + cli_settings_source, *_ = CliApp._subcommand_stack[id(model)] + else: + cli_settings_source = CliSettingsSource(CliApp._get_base_settings_cls(model_cls)) + help_message = cli_settings_source._format_help(cli_settings_source.root_parser) + return help_message if not strip_ansi_color else CliApp._ansi_color.sub('', help_message) + + @staticmethod + def print_help( + model: PydanticModel | type[T], + cli_settings_source: CliSettingsSource[Any] | None = None, + file: TextIO | None = None, + strip_ansi_color: bool = False, + ) -> None: + """ + Print a help message for a Pydantic model. + + Args: + model: The model or model class. + cli_settings_source: Override the default CLI settings source with a user defined instance. + Defaults to `None`. + file: A text stream to which the help message is written. If `None`, the output is sent to sys.stdout. + strip_ansi_color: Strips ANSI color codes from the help message when set to `True`. + """ + print( + CliApp.format_help( + model, + cli_settings_source=cli_settings_source, + strip_ansi_color=strip_ansi_color, + ), + file=file, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py new file mode 100644 index 0000000..a9afe6e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py @@ -0,0 +1,88 @@ +"""Package for handling configuration sources in pydantic-settings.""" + +from .base import ( + ConfigFileSourceMixin, + DefaultSettingsSource, + InitSettingsSource, + PydanticBaseEnvSettingsSource, + PydanticBaseSettingsSource, + get_subcommand, +) +from .providers.aws import AWSSecretsManagerSettingsSource +from .providers.azure import AzureKeyVaultSettingsSource +from .providers.cli import ( + CLI_SUPPRESS, + CliDualFlag, + CliExplicitFlag, + CliImplicitFlag, + CliMutuallyExclusiveGroup, + CliPositionalArg, + CliSettingsSource, + CliSubCommand, + CliSuppress, + CliToggleFlag, + CliUnknownArgs, +) +from .providers.dotenv import DotEnvSettingsSource, read_env_file +from .providers.env import EnvSettingsSource +from .providers.gcp import GoogleSecretManagerSettingsSource +from .providers.json import JsonConfigSettingsSource +from .providers.nested_secrets import NestedSecretsSettingsSource +from .providers.pyproject import PyprojectTomlConfigSettingsSource +from .providers.secrets import SecretsSettingsSource +from .providers.toml import TomlConfigSettingsSource +from .providers.yaml import YamlConfigSettingsSource +from .types import ( + DEFAULT_PATH, + ENV_FILE_SENTINEL, + ConfigFileSourceType, + DotenvFiltering, + DotenvType, + EnvPrefixTarget, + ForceDecode, + NoDecode, + PathType, + PydanticModel, +) + +__all__ = [ + 'CLI_SUPPRESS', + 'DEFAULT_PATH', + 'ENV_FILE_SENTINEL', + 'AWSSecretsManagerSettingsSource', + 'AzureKeyVaultSettingsSource', + 'CliDualFlag', + 'CliExplicitFlag', + 'CliImplicitFlag', + 'CliMutuallyExclusiveGroup', + 'CliPositionalArg', + 'CliSettingsSource', + 'CliSubCommand', + 'CliSuppress', + 'CliToggleFlag', + 'CliUnknownArgs', + 'ConfigFileSourceMixin', + 'ConfigFileSourceType', + 'DefaultSettingsSource', + 'DotEnvSettingsSource', + 'DotenvFiltering', + 'DotenvType', + 'EnvPrefixTarget', + 'EnvSettingsSource', + 'ForceDecode', + 'GoogleSecretManagerSettingsSource', + 'InitSettingsSource', + 'JsonConfigSettingsSource', + 'NestedSecretsSettingsSource', + 'NoDecode', + 'PathType', + 'PydanticBaseEnvSettingsSource', + 'PydanticBaseSettingsSource', + 'PydanticModel', + 'PyprojectTomlConfigSettingsSource', + 'SecretsSettingsSource', + 'TomlConfigSettingsSource', + 'YamlConfigSettingsSource', + 'get_subcommand', + 'read_env_file', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py new file mode 100644 index 0000000..9d73d14 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py @@ -0,0 +1,639 @@ +"""Base classes and core functionality for pydantic-settings sources.""" + +from __future__ import annotations as _annotations + +import json +from abc import ABC, abstractmethod +from collections.abc import Sequence +from dataclasses import asdict, is_dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any, cast, get_args + +from pydantic import AliasChoices, AliasPath, BaseModel, TypeAdapter +from pydantic._internal._typing_extra import ( # type: ignore[attr-defined] + get_origin, +) +from pydantic._internal._utils import deep_update, is_model_class +from pydantic.fields import FieldInfo +from typing_inspection.introspection import is_union_origin + +from ..exceptions import SettingsError +from ..utils import _lenient_issubclass +from .types import ( + ConfigFileSourceType, + EnvNoneType, + EnvPrefixTarget, + ForceDecode, + NoDecode, + PydanticModel, + _CliSubCommand, +) +from .utils import ( + InitState, + _annotation_is_complex, + _get_alias_names, + _get_field_metadata, + _get_model_fields, + _resolve_type_alias, + _strip_annotated, + _union_is_complex, + _warn_if_field_info_incomplete, +) + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + from .types import Traversable + + +def get_subcommand( + model: PydanticModel, + is_required: bool = True, + cli_exit_on_error: bool | None = None, + _suppress_errors: list[SettingsError | SystemExit] | None = None, +) -> PydanticModel | None: + """ + Get the subcommand from a model. + + Args: + model: The model to get the subcommand from. + is_required: Determines whether a model must have subcommand set and raises error if not + found. Defaults to `True`. + cli_exit_on_error: Determines whether this function exits with error if no subcommand is found. + Defaults to model_config `cli_exit_on_error` value if set. Otherwise, defaults to `True`. + + Returns: + The subcommand model if found, otherwise `None`. + + Raises: + SystemExit: When no subcommand is found and is_required=`True` and cli_exit_on_error=`True` + (the default). + SettingsError: When no subcommand is found and is_required=`True` and + cli_exit_on_error=`False`. + """ + + model_cls = type(model) + if cli_exit_on_error is None and is_model_class(model_cls): + model_default = model_cls.model_config.get('cli_exit_on_error') + if isinstance(model_default, bool): + cli_exit_on_error = model_default + if cli_exit_on_error is None: + cli_exit_on_error = True + + subcommands: list[str] = [] + for field_name, field_info in _get_model_fields(model_cls).items(): + if _CliSubCommand in field_info.metadata: + if getattr(model, field_name) is not None: + return getattr(model, field_name) + subcommands.append(field_name) + # TODO: we could warn if no sub command was found and one of `field_info`s + # isn't complete. + + if is_required: + error_message = ( + f'Error: CLI subcommand is required {{{", ".join(subcommands)}}}' + if subcommands + else 'Error: CLI subcommand is required but no subcommands were found.' + ) + err = SystemExit(error_message) if cli_exit_on_error else SettingsError(error_message) + if _suppress_errors is None: + raise err + _suppress_errors.append(err) + + return None + + +class PydanticBaseSettingsSource(ABC): + """ + Abstract base class for settings sources, every settings source classes should inherit from it. + """ + + def __init__(self, settings_cls: type[BaseSettings], _init_state: InitState | None = None): + self._init_state: InitState = {} if _init_state is None else _init_state + self.settings_cls = settings_cls + self.config = settings_cls.model_config + self._current_state: dict[str, Any] = {} + self._settings_sources_data: dict[str, dict[str, Any]] = {} + + def _set_current_state(self, state: dict[str, Any]) -> None: + """ + Record the state of settings from the previous settings sources. This should + be called right before __call__. + """ + self._current_state = state + + def _set_settings_sources_data(self, states: dict[str, dict[str, Any]]) -> None: + """ + Record the state of settings from all previous settings sources. This should + be called right before __call__. + """ + self._settings_sources_data = states + + @property + def current_state(self) -> dict[str, Any]: + """ + The current state of the settings, populated by the previous settings sources. + """ + return self._current_state + + @property + def settings_sources_data(self) -> dict[str, dict[str, Any]]: + """ + The state of all previous settings sources. + """ + return self._settings_sources_data + + @abstractmethod + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + """ + Gets the value, the key for model creation, and a flag to determine whether value is complex. + + This is an abstract method that should be overridden in every settings source classes. + + Args: + field: The field. + field_name: The field name. + + Returns: + A tuple that contains the value, key and a flag to determine whether value is complex. + """ + + def field_is_complex(self, field: FieldInfo) -> bool: + """ + Checks whether a field is complex, in which case it will attempt to be parsed as JSON. + + Args: + field: The field. + + Returns: + Whether the field is complex. + """ + return _annotation_is_complex(field.annotation, field.metadata, self._init_state) + + def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any: + """ + Prepares the value of a field. + + Args: + field_name: The field name. + field: The field. + value: The value of the field that has to be prepared. + value_is_complex: A flag to determine whether value is complex. + + Returns: + The prepared value. + """ + if value is not None and (self.field_is_complex(field) or value_is_complex): + return self.decode_complex_value(field_name, field, value) + return value + + def decode_complex_value(self, field_name: str, field: FieldInfo, value: Any) -> Any: + """ + Decode the value for a complex field + + Args: + field_name: The field name. + field: The field. + value: The value of the field that has to be prepared. + + Returns: + The decoded value for further preparation + """ + if field and ( + NoDecode in _get_field_metadata(field) + or (self.config.get('enable_decoding') is False and ForceDecode not in field.metadata) + ): + return value + + return json.loads(value) + + @abstractmethod + def __call__(self) -> dict[str, Any]: + pass + + +class ConfigFileSourceMixin(ABC): + def _read_files(self, files: ConfigFileSourceType | None, deep_merge: bool = False) -> dict[str, Any]: + if files is None: + return {} + if not isinstance(files, Sequence) or isinstance(files, str): + files = [files] + vars: dict[str, Any] = {} + for file in files: + file_path: Path | Traversable = Path(file) if isinstance(file, str) else file + if isinstance(file_path, Path): + file_path = file_path.expanduser() + + if not file_path.is_file(): + continue + + updating_vars = self._read_file(file_path) + if deep_merge: + vars = deep_update(vars, updating_vars) + else: + vars.update(updating_vars) + return vars + + @abstractmethod + def _read_file(self, path: Path | Traversable) -> dict[str, Any]: + pass + + +def _unwrap_optional_annotation(annotation: Any) -> Any: + """Return the inner type of an ``Optional[T]`` annotation, otherwise the annotation unchanged.""" + if is_union_origin(get_origin(annotation)): + args = get_args(annotation) + if len(args) == 2 and type(None) in args: + for arg in args: + if arg is not type(None): + return arg + return annotation + + +def _has_discriminator(field_info: FieldInfo) -> bool: + """Check if a field uses a discriminated union (via Annotated or Field(discriminator=...)).""" + if field_info.discriminator is not None: + return True + from pydantic import Discriminator + + return any(isinstance(m, Discriminator) for m in field_info.metadata) + + +class DefaultSettingsSource(PydanticBaseSettingsSource): + """ + Source class for loading default object values. + + Args: + settings_cls: The Settings class. + nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields. + Defaults to `False`. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + nested_model_default_partial_update: bool | None = None, + _init_state: InitState | None = None, + ): + super().__init__(settings_cls, _init_state) + self.defaults: dict[str, Any] = {} + self.nested_model_default_partial_update = ( + nested_model_default_partial_update + if nested_model_default_partial_update is not None + else self.config.get('nested_model_default_partial_update', False) + ) + if self.nested_model_default_partial_update: + for field_name, field_info in settings_cls.model_fields.items(): + _warn_if_field_info_incomplete(field_info, field_name, self._init_state) + if _has_discriminator(field_info): + continue + alias_names, *_ = _get_alias_names(field_name, field_info) + preferred_alias = alias_names[0] + if is_dataclass(type(field_info.default)): + self.defaults[preferred_alias] = asdict(field_info.default) + elif is_model_class(type(field_info.default)): + self.defaults[preferred_alias] = field_info.default.model_dump() + + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + # Nothing to do here. Only implement the return statement to make mypy happy + return None, '', False + + def __call__(self) -> dict[str, Any]: + return self.defaults + + def __repr__(self) -> str: + return ( + f'{self.__class__.__name__}(nested_model_default_partial_update={self.nested_model_default_partial_update})' + ) + + +class InitSettingsSource(PydanticBaseSettingsSource): + """ + Source class for loading values provided during settings class initialization. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + init_kwargs: dict[str, Any], + nested_model_default_partial_update: bool | None = None, + _init_state: InitState | None = None, + ): + super().__init__(settings_cls, _init_state) + case_sensitive = self.config.get('case_sensitive', False) + include_name = self.config.get('populate_by_name', False) or self.config.get('validate_by_name', False) + + def normalize(name: str) -> str: + # When case_sensitive is False, matching is done on lower-cased names. + return name if case_sensitive else name.lower() + + self.init_kwargs = {} + init_kwarg_names = set(init_kwargs.keys()) + # Map each normalized name to all provided keys sharing it, so field aliases can + # be matched without losing the original key used to look up the value. A 1-to-many + # mapping is required because case_sensitive=False can collapse distinct keys + # (e.g. ``TeSt`` and ``TEST``) onto the same normalized name. + normalized_provided: dict[str, list[str]] = {} + for key in init_kwargs: + normalized_provided.setdefault(normalize(key), []).append(key) + for field_name, field_info in settings_cls.model_fields.items(): + _warn_if_field_info_incomplete(field_info, field_name, self._init_state) + # Canonical (case-preserving) alias names determine the output key, so the value + # still matches pydantic's validation aliases, which are always case-sensitive. + canonical_alias_names, *_ = _get_alias_names(field_name, field_info) + # Match alias names respect case_sensitive when matching against provided keys. + match_alias_names, *_ = _get_alias_names(field_name, field_info, case_sensitive=case_sensitive) + # When populate_by_name is True, allow using the field name as an input key, + # but normalize to the preferred alias to keep keys consistent across sources. + matchable_names = list(match_alias_names) + if include_name: + name = normalize(field_name) + if name not in matchable_names: + matchable_names.append(name) + # All provided keys matching this field, in matchable_names preference order + # (aliases first, then the field name). Only keys not yet consumed by an + # earlier field are considered. + matched_keys = [ + original + for name in matchable_names + if name in normalized_provided + for original in normalized_provided[name] + if original in init_kwarg_names + ] + if matched_keys: + preferred_alias = canonical_alias_names[0] if canonical_alias_names else field_name + # Prefer the first match in preference order; drop the remaining matches + # so they are not re-added as extras. + init_kwarg_names.difference_update(matched_keys) + self.init_kwargs[preferred_alias] = init_kwargs[matched_keys[0]] + # Include any remaining init kwargs (e.g., extras) unchanged + # Note: If populate_by_name is True and the provided key is the field name, but + # no alias exists, we keep it as-is so it can be processed as extra if allowed. + self.init_kwargs.update({key: val for key, val in init_kwargs.items() if key in init_kwarg_names}) + + self.nested_model_default_partial_update = ( + nested_model_default_partial_update + if nested_model_default_partial_update is not None + else self.config.get('nested_model_default_partial_update', False) + ) + + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + # Nothing to do here. Only implement the return statement to make mypy happy + return None, '', False + + def __call__(self) -> dict[str, Any]: + return ( + TypeAdapter(dict[str, Any]).dump_python(self.init_kwargs) + if self.nested_model_default_partial_update + else self.init_kwargs + ) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(init_kwargs={self.init_kwargs!r})' + + +class PydanticBaseEnvSettingsSource(PydanticBaseSettingsSource): + def __init__( + self, + settings_cls: type[BaseSettings], + case_sensitive: bool | None = None, + env_prefix: str | None = None, + env_prefix_target: EnvPrefixTarget | None = None, + env_ignore_empty: bool | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + _init_state: InitState | None = None, + ) -> None: + super().__init__(settings_cls, _init_state) + self.case_sensitive = case_sensitive if case_sensitive is not None else self.config.get('case_sensitive', False) + self.env_prefix = env_prefix if env_prefix is not None else self.config.get('env_prefix', '') + self.env_prefix_target = ( + env_prefix_target if env_prefix_target is not None else self.config.get('env_prefix_target', 'variable') + ) + self.env_ignore_empty = ( + env_ignore_empty if env_ignore_empty is not None else self.config.get('env_ignore_empty', False) + ) + self.env_parse_none_str = ( + env_parse_none_str if env_parse_none_str is not None else self.config.get('env_parse_none_str') + ) + self.env_parse_enums = env_parse_enums if env_parse_enums is not None else self.config.get('env_parse_enums') + + def _apply_case_sensitive(self, value: str) -> str: + return value.lower() if not self.case_sensitive else value + + def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[str, str, bool]]: + """ + Extracts field info. This info is used to get the value of field from environment variables. + + It returns a list of tuples, each tuple contains: + * field_key: The key of field that has to be used in model creation. + * env_name: The environment variable name of the field. + * value_is_complex: A flag to determine whether the value from environment variable + is complex and has to be parsed. + + Args: + field (FieldInfo): The field. + field_name (str): The field name. + + Returns: + list[tuple[str, str, bool]]: List of tuples, each tuple contains field_key, env_name, and value_is_complex. + """ + _warn_if_field_info_incomplete(field, field_name, self._init_state) + field_info: list[tuple[str, str, bool]] = [] + if isinstance(field.validation_alias, (AliasChoices, AliasPath)): + v_alias: str | list[str | int] | list[list[str | int]] | None = field.validation_alias.convert_to_aliases() + else: + v_alias = field.validation_alias + + if v_alias: + env_prefix = self.env_prefix if self.env_prefix_target in ('alias', 'all') else '' + if isinstance(v_alias, list): # AliasChoices, AliasPath + for alias in v_alias: + if isinstance(alias, str): # AliasPath + field_info.append((alias, self._apply_case_sensitive(env_prefix + alias), len(alias) > 1)) + elif isinstance(alias, list): # AliasChoices + first_arg = cast(str, alias[0]) # first item of an AliasChoices must be a str + field_info.append( + ( + first_arg, + self._apply_case_sensitive(env_prefix + first_arg), + len(alias) > 1, + ) + ) + else: # string validation alias + field_info.append((v_alias, self._apply_case_sensitive(env_prefix + v_alias), False)) + + if not v_alias or self.config.get('populate_by_name', False) or self.config.get('validate_by_name', False): + annotation = _strip_annotated(_resolve_type_alias(field.annotation)) + env_prefix = self.env_prefix if self.env_prefix_target in ('variable', 'all') else '' + if is_union_origin(get_origin(annotation)) and _union_is_complex( + annotation, field.metadata, self._init_state + ): + field_info.append((field_name, self._apply_case_sensitive(env_prefix + field_name), True)) + else: + field_info.append((field_name, self._apply_case_sensitive(env_prefix + field_name), False)) + + return field_info + + def _replace_field_names_case_insensitively(self, field: FieldInfo, field_values: dict[str, Any]) -> dict[str, Any]: + """ + Replace field names in values dict by looking in models fields insensitively. + + By having the following models: + + ```py + class SubSubSub(BaseModel): + VaL3: str + + class SubSub(BaseModel): + Val2: str + SUB_sub_SuB: SubSubSub + + class Sub(BaseModel): + VAL1: str + SUB_sub: SubSub + + class Settings(BaseSettings): + nested: Sub + + model_config = SettingsConfigDict(env_nested_delimiter='__') + ``` + + Then: + _replace_field_names_case_insensitively( + field, + {"val1": "v1", "sub_SUB": {"VAL2": "v2", "sub_SUB_sUb": {"vAl3": "v3"}}} + ) + Returns {'VAL1': 'v1', 'SUB_sub': {'Val2': 'v2', 'SUB_sub_SuB': {'VaL3': 'v3'}}} + """ + values: dict[str, Any] = {} + + for name, value in field_values.items(): + sub_model_field: FieldInfo | None = None + + annotation = _unwrap_optional_annotation(field.annotation) + + # This is here to make mypy happy + # Item "None" of "Optional[Type[Any]]" has no attribute "model_fields" + if not annotation or not hasattr(annotation, 'model_fields'): + values[name] = value + continue + else: + model_fields: dict[str, FieldInfo] = annotation.model_fields + + # Find field in sub model by looking in fields case insensitively + field_key: str | None = None + for sub_model_field_name, sub_model_field in model_fields.items(): + _warn_if_field_info_incomplete(sub_model_field, sub_model_field_name, self._init_state) + aliases, _ = _get_alias_names(sub_model_field_name, sub_model_field) + _search = (alias for alias in aliases if alias.lower() == name.lower()) + if field_key := next(_search, None): + break + + if not field_key: + values[name] = value + continue + + if ( + sub_model_field is not None + and _lenient_issubclass(_unwrap_optional_annotation(sub_model_field.annotation), BaseModel) + and isinstance(value, dict) + ): + values[field_key] = self._replace_field_names_case_insensitively(sub_model_field, value) + else: + values[field_key] = value + + return values + + def _replace_env_none_type_values(self, field_value: dict[str, Any]) -> dict[str, Any]: + """ + Recursively parse values that are of "None" type(EnvNoneType) to `None` type(None). + """ + values: dict[str, Any] = {} + + for key, value in field_value.items(): + if not isinstance(value, EnvNoneType): + values[key] = value if not isinstance(value, dict) else self._replace_env_none_type_values(value) + else: + values[key] = None + + return values + + def _get_resolved_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + """ + Gets the value, the preferred alias key for model creation, and a flag to determine whether value + is complex. + + Note: + In V3, this method should either be made public, or, this method should be removed and the + abstract method get_field_value should be updated to include a "use_preferred_alias" flag. + + Args: + field: The field. + field_name: The field name. + + Returns: + A tuple that contains the value, preferred key and a flag to determine whether value is complex. + """ + field_value, field_key, value_is_complex = self.get_field_value(field, field_name) + if not ( + value_is_complex + or ( + (self.config.get('populate_by_name', False) or self.config.get('validate_by_name', False)) + and (field_key == field_name) + ) + ): + field_infos = self._extract_field_info(field, field_name) + preferred_key, _, preferred_is_complex = field_infos[0] + # Only normalize to preferred_key when it's a simple string alias. + # When the preferred key comes from an AliasPath (complex entry), skip normalization + # to avoid using the AliasPath's first element as the key (see #766). + if not preferred_is_complex: + return field_value, preferred_key, value_is_complex + return field_value, field_key, value_is_complex + + def __call__(self) -> dict[str, Any]: + data: dict[str, Any] = {} + + for field_name, field in self.settings_cls.model_fields.items(): + _warn_if_field_info_incomplete(field, field_name, self._init_state) + try: + field_value, field_key, value_is_complex = self._get_resolved_field_value(field, field_name) + except Exception as e: + raise SettingsError( + f'error getting value for field "{field_name}" from source "{self.__class__.__name__}"' + ) from e + + try: + field_value = self.prepare_field_value(field_name, field, field_value, value_is_complex) + except ValueError as e: + raise SettingsError( + f'error parsing value for field "{field_name}" from source "{self.__class__.__name__}"' + ) from e + + if field_value is not None: + if self.env_parse_none_str is not None: + if isinstance(field_value, dict): + field_value = self._replace_env_none_type_values(field_value) + elif isinstance(field_value, EnvNoneType): + field_value = None + if ( + not self.case_sensitive + # and _lenient_issubclass(field.annotation, BaseModel) + and isinstance(field_value, dict) + ): + data[field_key] = self._replace_field_names_case_insensitively(field, field_value) + else: + data[field_key] = field_value + + return data + + +__all__ = [ + 'ConfigFileSourceMixin', + 'DefaultSettingsSource', + 'InitSettingsSource', + 'PydanticBaseEnvSettingsSource', + 'PydanticBaseSettingsSource', + 'SettingsError', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py new file mode 100644 index 0000000..b55a2ed --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py @@ -0,0 +1,45 @@ +"""Package containing individual source implementations.""" + +from .aws import AWSSecretsManagerSettingsSource +from .azure import AzureKeyVaultSettingsSource +from .cli import ( + CliDualFlag, + CliExplicitFlag, + CliImplicitFlag, + CliMutuallyExclusiveGroup, + CliPositionalArg, + CliSettingsSource, + CliSubCommand, + CliSuppress, + CliToggleFlag, +) +from .dotenv import DotEnvSettingsSource +from .env import EnvSettingsSource +from .gcp import GoogleSecretManagerSettingsSource +from .json import JsonConfigSettingsSource +from .pyproject import PyprojectTomlConfigSettingsSource +from .secrets import SecretsSettingsSource +from .toml import TomlConfigSettingsSource +from .yaml import YamlConfigSettingsSource + +__all__ = [ + 'AWSSecretsManagerSettingsSource', + 'AzureKeyVaultSettingsSource', + 'CliDualFlag', + 'CliExplicitFlag', + 'CliImplicitFlag', + 'CliMutuallyExclusiveGroup', + 'CliPositionalArg', + 'CliSettingsSource', + 'CliSubCommand', + 'CliSuppress', + 'CliToggleFlag', + 'DotEnvSettingsSource', + 'EnvSettingsSource', + 'GoogleSecretManagerSettingsSource', + 'JsonConfigSettingsSource', + 'PyprojectTomlConfigSettingsSource', + 'SecretsSettingsSource', + 'TomlConfigSettingsSource', + 'YamlConfigSettingsSource', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py new file mode 100644 index 0000000..848c2d0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py @@ -0,0 +1,87 @@ +from __future__ import annotations as _annotations # important for BaseSettings import to work + +import json +from collections.abc import Mapping +from typing import TYPE_CHECKING + +from ..utils import InitState, parse_env_vars +from .env import EnvSettingsSource + +if TYPE_CHECKING: + from types_boto3_secretsmanager.client import SecretsManagerClient + + from pydantic_settings.main import BaseSettings + + +boto3_client = None + + +def import_aws_secrets_manager() -> None: + global boto3_client + + try: + from boto3 import client as boto3_client + except ImportError as e: # pragma: no cover + raise ImportError( + 'AWS Secrets Manager dependencies are not installed, run `pip install pydantic-settings[aws-secrets-manager]`' + ) from e + + +class AWSSecretsManagerSettingsSource(EnvSettingsSource): + _secret_id: str + _secretsmanager_client: SecretsManagerClient + + def __init__( + self, + settings_cls: type[BaseSettings], + secret_id: str, + region_name: str | None = None, + endpoint_url: str | None = None, + case_sensitive: bool | None = True, + env_prefix: str | None = None, + env_nested_delimiter: str | None = '--', + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + version_id: str | None = None, + _init_state: InitState | None = None, + ) -> None: + import_aws_secrets_manager() + self._secretsmanager_client = boto3_client('secretsmanager', region_name=region_name, endpoint_url=endpoint_url) # type: ignore + self._secret_id = secret_id + self._version_id = version_id + super().__init__( + settings_cls, + case_sensitive=case_sensitive, + env_prefix=env_prefix, + env_nested_delimiter=env_nested_delimiter, + env_ignore_empty=False, + env_parse_none_str=env_parse_none_str, + env_parse_enums=env_parse_enums, + _init_state=_init_state, + ) + + def _load_env_vars(self) -> Mapping[str, str | None]: + request = {'SecretId': self._secret_id} + + if self._version_id: + request['VersionId'] = self._version_id + + response = self._secretsmanager_client.get_secret_value(**request) + + return parse_env_vars( + json.loads(response['SecretString']), + self.case_sensitive, + self.env_ignore_empty, + self.env_parse_none_str, + ) + + def __repr__(self) -> str: + return ( + f'{self.__class__.__name__}(secret_id={self._secret_id!r}, ' + f'env_nested_delimiter={self.env_nested_delimiter!r})' + ) + + +__all__ = [ + 'AWSSecretsManagerSettingsSource', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py new file mode 100644 index 0000000..7bda681 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py @@ -0,0 +1,162 @@ +"""Azure Key Vault settings source.""" + +from __future__ import annotations as _annotations + +from collections.abc import Iterator, Mapping +from typing import TYPE_CHECKING + +from pydantic.alias_generators import to_snake +from pydantic.fields import FieldInfo + +from ..utils import InitState +from .env import EnvSettingsSource + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + from azure.core.exceptions import ResourceNotFoundError + from azure.keyvault.secrets import SecretClient + + from pydantic_settings.main import BaseSettings +else: + TokenCredential = None + ResourceNotFoundError = None + SecretClient = None + + +def import_azure_key_vault() -> None: + global TokenCredential + global SecretClient + global ResourceNotFoundError + + try: + from azure.core.credentials import TokenCredential + from azure.core.exceptions import ResourceNotFoundError + from azure.keyvault.secrets import SecretClient + except ImportError as e: # pragma: no cover + raise ImportError( + 'Azure Key Vault dependencies are not installed, run `pip install pydantic-settings[azure-key-vault]`' + ) from e + + +class AzureKeyVaultMapping(Mapping[str, str | None]): + _loaded_secrets: dict[str, str | None] + _secret_client: SecretClient + _secret_names: list[str] + + def __init__( + self, + secret_client: SecretClient, + case_sensitive: bool, + snake_case_conversion: bool, + env_prefix: str | None, + ) -> None: + self._loaded_secrets = {} + self._secret_client = secret_client + self._case_sensitive = case_sensitive + self._snake_case_conversion = snake_case_conversion + self._env_prefix = env_prefix if env_prefix else '' + self._secret_map: dict[str, str] = self._load_remote() + + def _load_remote(self) -> dict[str, str]: + secret_names: Iterator[str] = ( + secret.name for secret in self._secret_client.list_properties_of_secrets() if secret.name and secret.enabled + ) + + if self._snake_case_conversion: + name_map: dict[str, str] = {} + for name in secret_names: + if name.startswith(self._env_prefix): + name_map[f'{self._env_prefix}{to_snake(name[len(self._env_prefix) :])}'] = name + else: + name_map[to_snake(name)] = name + return name_map + + if self._case_sensitive: + return {name: name for name in secret_names} + + return {name.lower(): name for name in secret_names} + + def __getitem__(self, key: str) -> str | None: + new_key = key + + if self._snake_case_conversion: + if key.startswith(self._env_prefix): + new_key = f'{self._env_prefix}{to_snake(key[len(self._env_prefix) :])}' + else: + new_key = to_snake(key) + + elif not self._case_sensitive: + new_key = key.lower() + + if new_key not in self._loaded_secrets: + if new_key in self._secret_map: + self._loaded_secrets[new_key] = self._secret_client.get_secret(self._secret_map[new_key]).value + else: + raise KeyError(key) + + return self._loaded_secrets[new_key] + + def __len__(self) -> int: + return len(self._secret_map) + + def __iter__(self) -> Iterator[str]: + return iter(self._secret_map.keys()) + + +class AzureKeyVaultSettingsSource(EnvSettingsSource): + _url: str + _credential: TokenCredential + + def __init__( + self, + settings_cls: type[BaseSettings], + url: str, + credential: TokenCredential, + dash_to_underscore: bool = False, + case_sensitive: bool | None = None, + snake_case_conversion: bool = False, + env_prefix: str | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + _init_state: InitState | None = None, + ) -> None: + import_azure_key_vault() + self._url = url + self._credential = credential + self._dash_to_underscore = dash_to_underscore + self._snake_case_conversion = snake_case_conversion + super().__init__( + settings_cls, + case_sensitive=True if snake_case_conversion else case_sensitive, + env_prefix=env_prefix, + env_nested_delimiter='__' if snake_case_conversion else '--', + env_ignore_empty=False, + env_parse_none_str=env_parse_none_str, + env_parse_enums=env_parse_enums, + _init_state=_init_state, + ) + + def _load_env_vars(self) -> Mapping[str, str | None]: + secret_client = SecretClient(vault_url=self._url, credential=self._credential) + return AzureKeyVaultMapping( + secret_client=secret_client, + case_sensitive=self.case_sensitive, + snake_case_conversion=self._snake_case_conversion, + env_prefix=self.env_prefix, + ) + + def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[str, str, bool]]: + if self._snake_case_conversion: + field_info = [(x[0], x[1], x[2]) for x in super()._extract_field_info(field, field_name)] + return field_info + + if self._dash_to_underscore: + return [(x[0], x[1].replace('_', '-'), x[2]) for x in super()._extract_field_info(field, field_name)] + + return super()._extract_field_info(field, field_name) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(url={self._url!r}, env_nested_delimiter={self.env_nested_delimiter!r})' + + +__all__ = ['AzureKeyVaultMapping', 'AzureKeyVaultSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py new file mode 100644 index 0000000..1064759 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py @@ -0,0 +1,1700 @@ +"""Command-line interface settings source.""" + +from __future__ import annotations as _annotations + +import copy +import json +import re +import shlex +import sys +import typing +from argparse import ( + SUPPRESS, + ArgumentParser, + BooleanOptionalAction, + Namespace, + RawDescriptionHelpFormatter, + _SubParsersAction, +) +from collections import defaultdict +from collections.abc import Callable, Mapping, Sequence +from enum import Enum +from functools import cached_property +from itertools import chain +from textwrap import dedent +from types import SimpleNamespace +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Generic, + Literal, + NoReturn, + TypeVar, + cast, + get_args, + get_origin, + overload, +) + +from pydantic import AliasChoices, AliasPath, BaseModel, Field, PrivateAttr, TypeAdapter +from pydantic._internal._repr import Representation +from pydantic._internal._utils import is_model_class +from pydantic.dataclasses import is_pydantic_dataclass +from pydantic.fields import FieldInfo +from pydantic_core import PydanticUndefined +from typing_inspection import typing_objects +from typing_inspection.introspection import is_union_origin + +from ...exceptions import SettingsError +from ...utils import _lenient_issubclass, _typing_base, _WithArgsTypes +from ..types import ( + ForceDecode, + NoDecode, + PydanticModel, + _CliDualFlag, + _CliExplicitFlag, + _CliImplicitFlag, + _CliPositionalArg, + _CliSubCommand, + _CliToggleFlag, + _CliUnknownArgs, +) +from ..utils import ( + InitState, + _annotation_contains_types, + _annotation_enum_val_to_name, + _get_alias_names, + _get_model_fields, + _is_function, + _strip_annotated, + parse_env_vars, +) +from .env import EnvSettingsSource + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + +class _CliInternalArgParser(ArgumentParser): + def __init__(self, cli_exit_on_error: bool = True, **kwargs: Any) -> None: + super().__init__(**kwargs) + self._cli_exit_on_error = cli_exit_on_error + + def error(self, message: str) -> NoReturn: + if not self._cli_exit_on_error: + raise SettingsError(f'error parsing CLI: {message}') + super().error(message) + + +class CliMutuallyExclusiveGroup(BaseModel): + pass + + +def _get_model_description(model_cls: type[Any]) -> str | None: + """Get model description from json_schema_extra or __doc__ fallback. + + ``json_schema_extra.description`` takes precedence over ``__doc__`` to + match pydantic's own behaviour. When neither is available (e.g. under + ``python -OO`` where docstrings are stripped), returns ``None``. + """ + config: Any = {} + if is_model_class(model_cls): + config = model_cls.model_config + elif is_pydantic_dataclass(model_cls): + config = getattr(model_cls, '__pydantic_config__', {}) + json_schema_extra = config.get('json_schema_extra') + if isinstance(json_schema_extra, dict): + desc = json_schema_extra.get('description') + if desc is not None: + return desc + elif callable(json_schema_extra): + try: + desc = None + if is_model_class(model_cls): + desc = model_cls.model_json_schema().get('description') + elif is_pydantic_dataclass(model_cls): + desc = TypeAdapter(model_cls).json_schema().get('description') + if desc is not None: + return desc + except Exception: + pass + if model_cls.__doc__ is not None: + return dedent(model_cls.__doc__) + return None + + +def _collect_sub_models(type_: Any, sub_models: list[type[BaseModel]]) -> None: + """Recursively collect BaseModel subclasses from possibly nested union types.""" + stripped = _strip_annotated(type_) + if is_model_class(stripped) or is_pydantic_dataclass(stripped): + sub_models.append(stripped) # type: ignore[arg-type] + elif is_union_origin(get_origin(stripped)): + for arg in get_args(stripped): + _collect_sub_models(arg, sub_models) + + +class _CliArg(BaseModel): + model: Any + parser: Any + field_name: str + arg_prefix: str + case_sensitive: bool + populate_by_name: bool + hide_none_type: bool + kebab_case: bool | Literal['all', 'no_enums'] | None + enable_decoding: bool | None + env_prefix_len: int + args: list[str] = [] + kwargs: dict[str, Any] = {} + + _alias_names: tuple[str, ...] = PrivateAttr(()) + _alias_paths: dict[str, int | None] = PrivateAttr({}) + _is_alias_path_only: bool = PrivateAttr(False) + _field_info: FieldInfo = PrivateAttr() + + def __init__( + self, + field_info: FieldInfo, + parser_map: defaultdict[str | FieldInfo, dict[int | None | str | type[BaseModel], _CliArg]], + **values: Any, + ) -> None: + super().__init__(**values) + self._field_info = field_info + self._alias_names, self._is_alias_path_only = _get_alias_names( + self.field_name, + self.field_info, + alias_path_args=self._alias_paths, + case_sensitive=self.case_sensitive, + populate_by_name=self.populate_by_name, + ) + + alias_path_dests = {f'{self.arg_prefix}{name}': index for name, index in self._alias_paths.items()} + if self.subcommand_dest: + for sub_model in self.sub_models: + subcommand_alias = self.subcommand_alias(sub_model) + parser_map[self.subcommand_dest][subcommand_alias] = self.model_copy(update={'args': [], 'kwargs': {}}) + parser_map[self.subcommand_dest][sub_model] = parser_map[self.subcommand_dest][subcommand_alias] + parser_map[self.field_info][subcommand_alias] = parser_map[self.subcommand_dest][subcommand_alias] + elif self.dest not in alias_path_dests: + parser_map[self.dest][None] = self + parser_map[self.field_info][None] = parser_map[self.dest][None] + for alias_path_dest, index in alias_path_dests.items(): + parser_map[alias_path_dest][index] = self.model_copy(update={'args': [], 'kwargs': {}}) + parser_map[self.field_info][index] = parser_map[alias_path_dest][index] + + @classmethod + def get_kebab_case(cls, name: str, kebab_case: bool | Literal['all', 'no_enums'] | None) -> str: + return name.replace('_', '-') if kebab_case not in (None, False) else name + + @classmethod + def get_enum_names( + cls, annotation: type[Any], kebab_case: bool | Literal['all', 'no_enums'] | None + ) -> tuple[str, ...]: + enum_names: tuple[str, ...] = () + annotation = _strip_annotated(annotation) + for type_ in get_args(annotation): + enum_names += cls.get_enum_names(type_, kebab_case) + if annotation and _lenient_issubclass(annotation, Enum): + enum_names += tuple(cls.get_kebab_case(name, kebab_case == 'all') for name in annotation.__members__) + return enum_names + + def subcommand_alias(self, sub_model: type[BaseModel]) -> str: + return self.get_kebab_case( + sub_model.__name__ if len(self.sub_models) > 1 else self.preferred_alias, self.kebab_case + ) + + @cached_property + def field_info(self) -> FieldInfo: + return self._field_info + + @cached_property + def subcommand_dest(self) -> str | None: + return f'{self.arg_prefix}:subcommand' if _CliSubCommand in self.field_info.metadata else None + + @cached_property + def dest(self) -> str: + if ( + not self.subcommand_dest + and self.arg_prefix + and self.field_info.validation_alias is not None + and not self.is_parser_submodel + ): + # Strip prefix if validation alias is set and value is not complex. + # Related https://github.com/pydantic/pydantic-settings/pull/25 + return f'{self.arg_prefix}{self.preferred_alias}'[self.env_prefix_len :] + return f'{self.arg_prefix}{self.preferred_alias}' + + @cached_property + def preferred_arg_name(self) -> str: + return self.args[0].replace('_', '-') if self.kebab_case else self.args[0] + + @cached_property + def sub_models(self) -> list[type[BaseModel]]: + field_types: tuple[Any, ...] = ( + (self.field_info.annotation,) + if not get_args(self.field_info.annotation) + else get_args(self.field_info.annotation) + ) + if self.hide_none_type: + field_types = tuple([type_ for type_ in field_types if type_ is not type(None)]) + + sub_models: list[type[BaseModel]] = [] + for type_ in field_types: + if _annotation_contains_types(type_, (_CliSubCommand,), is_include_origin=False): + raise SettingsError( + f'CliSubCommand is not outermost annotation for {self.model.__name__}.{self.field_name}' + ) + elif _annotation_contains_types(type_, (_CliPositionalArg,), is_include_origin=False): + raise SettingsError( + f'CliPositionalArg is not outermost annotation for {self.model.__name__}.{self.field_name}' + ) + _collect_sub_models(type_, sub_models) + return sub_models + + @cached_property + def alias_names(self) -> tuple[str, ...]: + return self._alias_names + + @cached_property + def alias_paths(self) -> dict[str, int | None]: + return self._alias_paths + + @cached_property + def preferred_alias(self) -> str: + return self._alias_names[0] + + @cached_property + def is_alias_path_only(self) -> bool: + return self._is_alias_path_only + + @cached_property + def is_append_action(self) -> bool: + return not self.subcommand_dest and _annotation_contains_types( + self.field_info.annotation, (list, set, dict, Sequence, Mapping), is_strip_annotated=True + ) + + @cached_property + def is_parser_submodel(self) -> bool: + return not self.subcommand_dest and bool(self.sub_models) and not self.is_append_action + + @cached_property + def is_no_decode(self) -> bool: + return self.field_info is not None and ( + NoDecode in self.field_info.metadata + or (self.enable_decoding is False and ForceDecode not in self.field_info.metadata) + ) + + +T = TypeVar('T') +CliSubCommand = Annotated[T | None, _CliSubCommand] +CliPositionalArg = Annotated[T, _CliPositionalArg] +_CliBoolFlag = TypeVar('_CliBoolFlag', bound=bool) +CliImplicitFlag = Annotated[_CliBoolFlag, _CliImplicitFlag] +CliExplicitFlag = Annotated[_CliBoolFlag, _CliExplicitFlag] +CliToggleFlag = Annotated[_CliBoolFlag, _CliToggleFlag] +CliDualFlag = Annotated[_CliBoolFlag, _CliDualFlag] +CLI_SUPPRESS = SUPPRESS +CliSuppress = Annotated[T, CLI_SUPPRESS] +CliUnknownArgs = Annotated[list[str], Field(default=[]), _CliUnknownArgs, NoDecode] + + +class CliSettingsSource(EnvSettingsSource, Generic[T]): + """ + Source class for loading settings values from CLI. + + Note: + A `CliSettingsSource` connects with a `root_parser` object by using the parser methods to add + `settings_cls` fields as command line arguments. The `CliSettingsSource` internal parser representation + is based upon the `argparse` parsing library, and therefore, requires the parser methods to support + the same attributes as their `argparse` library counterparts. + + Args: + cli_prog_name: The CLI program name to display in help text. Defaults to `None` if cli_parse_args is `None`. + Otherwise, defaults to sys.argv[0]. + cli_parse_args: The list of CLI arguments to parse. Defaults to None. + If set to `True`, defaults to sys.argv[1:]. + cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into `None` + type(None). Defaults to "null" if cli_avoid_json is `False`, and "None" if cli_avoid_json is `True`. + cli_hide_none_type: Hide `None` values in CLI help text. Defaults to `False`. + cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to `False`. + cli_enforce_required: Enforce required fields at the CLI. Defaults to `False`. + cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions. + Defaults to `False`. + cli_show_env_vars: Show resolved environment variable names in CLI help text. Defaults to `False`. + cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs. + Defaults to `True`. + cli_prefix: Prefix for command line arguments added under the root parser. Defaults to "". + cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'. + cli_implicit_flags: Controls how `bool` fields are exposed as CLI flags. + + - False (default): no implicit flags are generated; booleans must be set explicitly (e.g. --flag=true). + - True / 'dual': optional boolean fields generate both positive and negative forms (--flag and --no-flag). + - 'toggle': required boolean fields remain in 'dual' mode, while optional boolean fields generate a single + flag aligned with the default value (if default=False, expose --flag; if default=True, expose --no-flag). + cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`. + cli_kebab_case: CLI args use kebab case. Defaults to `False`. + cli_shortcuts: Mapping of target field name to alias names. Defaults to `None`. + case_sensitive: Whether CLI "--arg" names should be read with case-sensitivity. Defaults to `True`. + Note: Case-insensitive matching is only supported on the internal root parser and does not apply to CLI + subcommands. + root_parser: The root parser object. + parse_args_method: The root parser parse args method. Defaults to `argparse.ArgumentParser.parse_args`. + add_argument_method: The root parser add argument method. Defaults to `argparse.ArgumentParser.add_argument`. + add_argument_group_method: The root parser add argument group method. + Defaults to `argparse.ArgumentParser.add_argument_group`. + add_parser_method: The root parser add new parser (sub-command) method. + Defaults to `argparse._SubParsersAction.add_parser`. + add_subparsers_method: The root parser add subparsers (sub-commands) method. + Defaults to `argparse.ArgumentParser.add_subparsers`. + format_help_method: The root parser format help method. Defaults to `argparse.ArgumentParser.format_help`. + formatter_class: A class for customizing the root parser help text. Defaults to `argparse.RawDescriptionHelpFormatter`. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + cli_prog_name: str | None = None, + cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, + cli_parse_none_str: str | None = None, + cli_hide_none_type: bool | None = None, + cli_avoid_json: bool | None = None, + cli_enforce_required: bool | None = None, + cli_use_class_docs_for_groups: bool | None = None, + cli_show_env_vars: bool | None = None, + cli_exit_on_error: bool | None = None, + cli_prefix: str | None = None, + cli_flag_prefix_char: str | None = None, + cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None, + cli_ignore_unknown_args: bool | None = None, + cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None, + cli_shortcuts: Mapping[str, str | list[str]] | None = None, + case_sensitive: bool | None = True, + root_parser: Any = None, + parse_args_method: Callable[..., Any] | None = None, + add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument, + add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group, + add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser, + add_subparsers_method: Callable[..., Any] | None = ArgumentParser.add_subparsers, + format_help_method: Callable[..., Any] | None = ArgumentParser.format_help, + formatter_class: Any = RawDescriptionHelpFormatter, + _env_settings_source: EnvSettingsSource | None = None, + _init_state: InitState | None = None, + ) -> None: + self.cli_prog_name = ( + cli_prog_name if cli_prog_name is not None else settings_cls.model_config.get('cli_prog_name', sys.argv[0]) + ) + self.cli_hide_none_type = ( + cli_hide_none_type + if cli_hide_none_type is not None + else settings_cls.model_config.get('cli_hide_none_type', False) + ) + self.cli_avoid_json = ( + cli_avoid_json if cli_avoid_json is not None else settings_cls.model_config.get('cli_avoid_json', False) + ) + if not cli_parse_none_str: + cli_parse_none_str = 'None' if self.cli_avoid_json is True else 'null' + self.cli_parse_none_str = cli_parse_none_str + self.cli_enforce_required = ( + cli_enforce_required + if cli_enforce_required is not None + else settings_cls.model_config.get('cli_enforce_required', False) + ) + self.cli_use_class_docs_for_groups = ( + cli_use_class_docs_for_groups + if cli_use_class_docs_for_groups is not None + else settings_cls.model_config.get('cli_use_class_docs_for_groups', False) + ) + self.cli_show_env_vars = ( + cli_show_env_vars + if cli_show_env_vars is not None + else settings_cls.model_config.get('cli_show_env_vars', False) + ) + self.cli_exit_on_error = ( + cli_exit_on_error + if cli_exit_on_error is not None + else settings_cls.model_config.get('cli_exit_on_error', True) + ) + self.cli_prefix = cli_prefix if cli_prefix is not None else settings_cls.model_config.get('cli_prefix', '') + self.cli_flag_prefix_char = ( + cli_flag_prefix_char + if cli_flag_prefix_char is not None + else settings_cls.model_config.get('cli_flag_prefix_char', '-') + ) + self._cli_flag_prefix = self.cli_flag_prefix_char * 2 + if self.cli_prefix: + if cli_prefix.startswith('.') or cli_prefix.endswith('.') or not cli_prefix.replace('.', '').isidentifier(): # type: ignore + raise SettingsError(f'CLI settings source prefix is invalid: {cli_prefix}') + self.cli_prefix += '.' + self.cli_implicit_flags = ( + cli_implicit_flags + if cli_implicit_flags is not None + else settings_cls.model_config.get('cli_implicit_flags', False) + ) + self.cli_ignore_unknown_args = ( + cli_ignore_unknown_args + if cli_ignore_unknown_args is not None + else settings_cls.model_config.get('cli_ignore_unknown_args', False) + ) + self.cli_kebab_case = ( + cli_kebab_case if cli_kebab_case is not None else settings_cls.model_config.get('cli_kebab_case', False) + ) + self.cli_shortcuts = ( + cli_shortcuts if cli_shortcuts is not None else settings_cls.model_config.get('cli_shortcuts', None) + ) + self._env_settings_source: EnvSettingsSource | None = None + if self.cli_show_env_vars: + self._env_settings_source = _env_settings_source or EnvSettingsSource(settings_cls) + self._env_var_names: dict[str, tuple[str, ...]] = {} + + case_sensitive = case_sensitive if case_sensitive is not None else True + if not case_sensitive and root_parser is not None: + raise SettingsError('Case-insensitive matching is only supported on the internal root parser') + + super().__init__( + settings_cls, + env_nested_delimiter='.', + env_parse_none_str=self.cli_parse_none_str, + env_parse_enums=True, + env_prefix=self.cli_prefix, + case_sensitive=case_sensitive, + env_nested_max_split=0, + _init_state=_init_state, + ) + + root_parser = ( + _CliInternalArgParser( + cli_exit_on_error=self.cli_exit_on_error, + prog=self.cli_prog_name, + description=_get_model_description(settings_cls), + formatter_class=formatter_class, + prefix_chars=self.cli_flag_prefix_char, + allow_abbrev=False, + add_help=False, + ) + if root_parser is None + else root_parser + ) + self._connect_root_parser( + root_parser=root_parser, + parse_args_method=parse_args_method, + add_argument_method=add_argument_method, + add_argument_group_method=add_argument_group_method, + add_parser_method=add_parser_method, + add_subparsers_method=add_subparsers_method, + format_help_method=format_help_method, + formatter_class=formatter_class, + ) + + if cli_parse_args not in (None, False): + if cli_parse_args is True: + cli_parse_args = sys.argv[1:] + elif not isinstance(cli_parse_args, (list, tuple)): + raise SettingsError( + f'cli_parse_args must be a list or tuple of strings, received {type(cli_parse_args)}' + ) + self._load_env_vars(parsed_args=self._parse_args(self.root_parser, cli_parse_args)) + + @property + def env_var_names(self) -> Mapping[str, tuple[str, ...]]: + """Resolved environment variable names for CLI arguments.""" + return self._env_var_names.copy() + + @overload + def __call__(self) -> dict[str, Any]: ... + + @overload + def __call__(self, *, args: list[str] | tuple[str, ...] | bool) -> CliSettingsSource[T]: + """ + Parse and load the command line arguments list into the CLI settings source. + + Args: + args: + The command line arguments to parse and load. Defaults to `None`, which means do not parse + command line arguments. If set to `True`, defaults to sys.argv[1:]. If set to `False`, does + not parse command line arguments. + + Returns: + CliSettingsSource: The object instance itself. + """ + + @overload + def __call__(self, *, parsed_args: Namespace | SimpleNamespace | dict[str, Any]) -> CliSettingsSource[T]: + """ + Loads parsed command line arguments into the CLI settings source. + + Note: + The parsed args must be in `argparse.Namespace`, `SimpleNamespace`, or vars dictionary + (e.g., vars(argparse.Namespace)) format. + + Args: + parsed_args: The parsed args to load. + + Returns: + CliSettingsSource: The object instance itself. + """ + + def __call__( + self, + *, + args: list[str] | tuple[str, ...] | bool | None = None, + parsed_args: Namespace | SimpleNamespace | dict[str, list[str] | str] | None = None, + ) -> dict[str, Any] | CliSettingsSource[T]: + if args is not None and parsed_args is not None: + raise SettingsError('`args` and `parsed_args` are mutually exclusive') + elif args is not None: + if args is False: + return self._load_env_vars(parsed_args={}) + if args is True: + args = sys.argv[1:] + return self._load_env_vars(parsed_args=self._parse_args(self.root_parser, args)) + elif parsed_args is not None: + return self._load_env_vars(parsed_args=copy.copy(parsed_args)) + else: + return super().__call__() + + @overload + def _load_env_vars(self) -> Mapping[str, str | None]: ... + + @overload + def _load_env_vars(self, *, parsed_args: Namespace | SimpleNamespace | dict[str, Any]) -> CliSettingsSource[T]: + """ + Loads the parsed command line arguments into the CLI environment settings variables. + + Note: + The parsed args must be in `argparse.Namespace`, `SimpleNamespace`, or vars dictionary + (e.g., vars(argparse.Namespace)) format. + + Args: + parsed_args: The parsed args to load. + + Returns: + CliSettingsSource: The object instance itself. + """ + + def _load_env_vars( + self, *, parsed_args: Namespace | SimpleNamespace | dict[str, list[str] | str] | None = None + ) -> Mapping[str, str | None] | CliSettingsSource[T]: + if parsed_args is None: + return {} + + if isinstance(parsed_args, (Namespace, SimpleNamespace)): + parsed_args = vars(parsed_args) + + selected_subcommands = self._resolve_parsed_args(parsed_args) + for arg_dest, arg_map in self._parser_map.items(): + if isinstance(arg_dest, str) and arg_dest.endswith(':subcommand'): + for subcommand_dest in [arg.dest for arg in arg_map.values()]: + if subcommand_dest not in selected_subcommands: + parsed_args[subcommand_dest] = self.cli_parse_none_str + + parsed_args = { + key: val + for key, val in parsed_args.items() + if not key.endswith(':subcommand') and val is not PydanticUndefined + } + if selected_subcommands: + last_selected_subcommand = max(selected_subcommands, key=len) + if not any(field_name for field_name in parsed_args if f'{last_selected_subcommand}.' in field_name): + parsed_args[last_selected_subcommand] = '{}' + else: + last_selected_subcommand = '' + + # When using parse_known_args due to a subcommand's CliUnknownArgs, reject + # unknown args if the selected subcommand does not accept them. + if not self.cli_ignore_unknown_args and self._cli_unknown_args: + has_unknown = any(args for args in self._cli_unknown_args.values()) + if has_unknown: + selected_accepts_unknown = any( + dest.rsplit('.', 1)[0] in last_selected_subcommand for dest in self._cli_unknown_args + ) + if not selected_accepts_unknown: + unknown = next(args for args in self._cli_unknown_args.values() if args) + if isinstance(self.root_parser, ArgumentParser): + self.root_parser.error(f'unrecognized arguments: {" ".join(unknown)}') + raise SystemExit(2) + + parsed_args.update(self._cli_unknown_args) + + self.env_vars = parse_env_vars( + cast(Mapping[str, str], parsed_args), + self.case_sensitive, + self.env_ignore_empty, + self.cli_parse_none_str, + ) + + return self + + def _resolve_parsed_args(self, parsed_args: dict[str, list[str] | str]) -> list[str]: + selected_subcommands: list[str] = [] + for field_name, val in list(parsed_args.items()): + if isinstance(val, list): + if self._is_nested_alias_path_only_workaround(parsed_args, field_name, val): + # Workaround for nested alias path environment variables not being handled. + # See https://github.com/pydantic/pydantic-settings/issues/670 + continue + + cli_arg = self._parser_map.get(field_name, {}).get(None) + if cli_arg and cli_arg.is_no_decode: + parsed_args[field_name] = ','.join(val) + continue + + parsed_args[field_name] = self._merge_parsed_list(val, field_name) + elif field_name.endswith(':subcommand') and val is not None: + selected_subcommands.append(self._parser_map[field_name][val].dest) + elif self.cli_kebab_case == 'all' and isinstance(val, str): + snake_val = val.replace('-', '_') + cli_arg = self._parser_map.get(field_name, {}).get(None) + if ( + cli_arg + and cli_arg.field_info.annotation + and (snake_val in cli_arg.get_enum_names(cli_arg.field_info.annotation, False)) + ): + if '_' in val: + raise ValueError(f'Input should be kebab-case "{val.replace("_", "-")}", not "{val}"') + parsed_args[field_name] = snake_val + + return selected_subcommands + + def _is_nested_alias_path_only_workaround( + self, parsed_args: dict[str, list[str] | str], field_name: str, val: list[str] + ) -> bool: + """ + Workaround for nested alias path environment variables not being handled. + See https://github.com/pydantic/pydantic-settings/issues/670 + """ + known_arg = self._parser_map.get(field_name, {}).values() + if not known_arg: + return False + arg = next(iter(known_arg)) + if arg.is_alias_path_only and arg.arg_prefix.endswith('.'): + del parsed_args[field_name] + nested_dest = arg.arg_prefix[:-1] + nested_val = f'"{arg.preferred_alias}": {self._merge_parsed_list(val, field_name)}' + parsed_args[nested_dest] = ( + f'{{{nested_val}}}' + if nested_dest not in parsed_args + else f'{parsed_args[nested_dest][:-1]}, {nested_val}}}' + ) + return True + return False + + def _get_merge_parsed_list_types(self, parsed_list: list[str], field_name: str) -> tuple[type | None, type | None]: + merge_type = self._cli_dict_args.get(field_name, list) + if ( + merge_type is list + or not is_union_origin(get_origin(merge_type)) + or not any( + type_ + for type_ in get_args(merge_type) + if type_ is not type(None) and get_origin(type_) not in (dict, Mapping) + ) + ): + inferred_type = merge_type + else: + inferred_type = list if parsed_list and (len(parsed_list) > 1 or parsed_list[0].startswith('[')) else str + + return merge_type, inferred_type + + def _merged_list_to_str(self, merged_list: list[str], field_name: str) -> str: + decode_list: list[str] = [] + is_use_decode: bool | None = None + cli_arg_map = self._parser_map.get(field_name, {}) + try: + list_adapter: Any = TypeAdapter(next(iter(cli_arg_map.values())).field_info.annotation) + is_num_type_str = type(next(iter(list_adapter.validate_python(['1'])))) is str + except Exception: + is_num_type_str = None + for index, item in enumerate(merged_list): + cli_arg = cli_arg_map.get(index) + is_decode = cli_arg is None or not cli_arg.is_no_decode + if is_use_decode is None: + is_use_decode = is_decode + elif is_use_decode != is_decode: + raise SettingsError('Mixing Decode and NoDecode across different AliasPath fields is not allowed') + if is_use_decode: + item = item.replace('\\', '\\\\') + try: + unquoted_item = item[1:-1] if item.startswith('"') and item.endswith('"') else item + float(unquoted_item) + item = f'"{unquoted_item}"' if is_num_type_str else unquoted_item + except ValueError: + pass + elif item.startswith('"') and item.endswith('"'): + item = item[1:-1] + decode_list.append(item) + merged_list_str = ','.join(decode_list) + return f'[{merged_list_str}]' if is_use_decode else merged_list_str + + def _merge_parsed_list(self, parsed_list: list[str], field_name: str) -> str: + try: + merged_list: list[str] = [] + is_last_consumed_a_value = False + merge_type, inferred_type = self._get_merge_parsed_list_types(parsed_list, field_name) + for val in parsed_list: + if not isinstance(val, str): + # If val is not a string, it's from an external parser and we can ignore parsing the rest of the + # list. + break + val = val.strip() + if val.startswith('[') and val.endswith(']'): + val = val[1:-1].strip() + while val: + val = val.strip() + if val.startswith(','): + val = self._consume_comma(val, merged_list, is_last_consumed_a_value) + is_last_consumed_a_value = False + else: + if val.startswith(('{', '[')): + val = self._consume_object_or_array(val, merged_list) + else: + try: + val = self._consume_string_or_number(val, merged_list, merge_type) + except ValueError: + if merge_type is inferred_type: + raise + merge_type = inferred_type + val = self._consume_string_or_number(val, merged_list, merge_type) + is_last_consumed_a_value = True + if not is_last_consumed_a_value: + val = self._consume_comma(val, merged_list, is_last_consumed_a_value) + + if merge_type is str: + return merged_list[0] + elif merge_type is list: + return self._merged_list_to_str(merged_list, field_name) + else: + merged_dict: dict[str, str] = {} + for item in merged_list: + merged_dict.update(json.loads(item)) + return json.dumps(merged_dict) + except Exception as e: + raise SettingsError(f'Parsing error encountered for {field_name}: {e}') + + def _consume_comma(self, item: str, merged_list: list[str], is_last_consumed_a_value: bool) -> str: + if not is_last_consumed_a_value: + merged_list.append('""') + return item[1:] + + def _consume_object_or_array(self, item: str, merged_list: list[str]) -> str: + count = 1 + close_delim = '}' if item.startswith('{') else ']' + in_str = False + for consumed in range(1, len(item)): + if item[consumed] == '"' and item[consumed - 1] != '\\': + in_str = not in_str + elif in_str: + continue + elif item[consumed] in ('{', '['): + count += 1 + elif item[consumed] in ('}', ']'): + count -= 1 + if item[consumed] == close_delim and count == 0: + merged_list.append(item[: consumed + 1]) + return item[consumed + 1 :] + raise SettingsError(f'Missing end delimiter "{close_delim}"') + + def _consume_string_or_number(self, item: str, merged_list: list[str], merge_type: type[Any] | None) -> str: + consumed = 0 if merge_type is not str else len(item) + is_find_end_quote = False + while consumed < len(item): + if item[consumed] == '"' and (consumed == 0 or item[consumed - 1] != '\\'): + is_find_end_quote = not is_find_end_quote + if not is_find_end_quote and item[consumed] == ',': + break + consumed += 1 + if is_find_end_quote: + raise SettingsError('Mismatched quotes') + val_string = item[:consumed].strip() + if merge_type in (list, str): + try: + float(val_string) + except ValueError: + if val_string == self.cli_parse_none_str: + val_string = 'null' + if val_string not in ('true', 'false', 'null') and not val_string.startswith('"'): + val_string = f'"{val_string}"' + merged_list.append(val_string) + else: + key, val = (kv for kv in val_string.split('=', 1)) + if key.startswith('"') and not key.endswith('"') and not val.startswith('"') and val.endswith('"'): + raise ValueError(f'Dictionary key=val parameter is a quoted string: {val_string}') + key, val = key.strip('"'), val.strip('"') + merged_list.append(json.dumps({key: val})) + return item[consumed:] + + def _verify_cli_flag_annotations(self, model: type[BaseModel], field_name: str, field_info: FieldInfo) -> None: + if _CliImplicitFlag in field_info.metadata: + cli_flag_name = 'CliImplicitFlag' + elif _CliExplicitFlag in field_info.metadata: + cli_flag_name = 'CliExplicitFlag' + elif _CliToggleFlag in field_info.metadata: + cli_flag_name = 'CliToggleFlag' + if not isinstance(field_info.default, bool): + raise SettingsError( + f'{cli_flag_name} argument {model.__name__}.{field_name} must have a default bool value' + ) + elif _CliDualFlag in field_info.metadata: + cli_flag_name = 'CliDualFlag' + else: + return + + if field_info.annotation is not bool: + raise SettingsError(f'{cli_flag_name} argument {model.__name__}.{field_name} is not of type bool') + + def _sort_arg_fields(self, model: type[BaseModel]) -> list[tuple[str, FieldInfo]]: + positional_variadic_arg: list[tuple[str, FieldInfo]] = [] + positional_args, subcommand_args, optional_args = [], [], [] + for field_name, field_info in _get_model_fields(model).items(): + if _CliSubCommand in field_info.metadata: + if not field_info.is_required(): + raise SettingsError(f'subcommand argument {model.__name__}.{field_name} has a default value') + else: + alias_names, *_ = _get_alias_names(field_name, field_info) + if len(alias_names) > 1: + raise SettingsError(f'subcommand argument {model.__name__}.{field_name} has multiple aliases') + field_types = [type_ for type_ in get_args(field_info.annotation) if type_ is not type(None)] + for field_type in field_types: + if not (is_model_class(field_type) or is_pydantic_dataclass(field_type)): + raise SettingsError( + f'subcommand argument {model.__name__}.{field_name} has type not derived from BaseModel' + ) + subcommand_args.append((field_name, field_info)) + elif _CliPositionalArg in field_info.metadata: + alias_names, *_ = _get_alias_names(field_name, field_info) + if len(alias_names) > 1: + raise SettingsError(f'positional argument {model.__name__}.{field_name} has multiple aliases') + is_append_action = _annotation_contains_types( + field_info.annotation, (list, set, dict, Sequence, Mapping), is_strip_annotated=True + ) + if not is_append_action: + positional_args.append((field_name, field_info)) + else: + positional_variadic_arg.append((field_name, field_info)) + else: + self._verify_cli_flag_annotations(model, field_name, field_info) + optional_args.append((field_name, field_info)) + + if positional_variadic_arg: + if len(positional_variadic_arg) > 1: + field_names = ', '.join([name for name, info in positional_variadic_arg]) + raise SettingsError(f'{model.__name__} has multiple variadic positional arguments: {field_names}') + elif subcommand_args: + field_names = ', '.join([name for name, info in positional_variadic_arg + subcommand_args]) + raise SettingsError( + f'{model.__name__} has variadic positional arguments and subcommand arguments: {field_names}' + ) + + return positional_args + positional_variadic_arg + subcommand_args + optional_args + + @property + def root_parser(self) -> T: + """The connected root parser instance.""" + return self._root_parser + + def _connect_parser_method( + self, parser_method: Callable[..., Any] | None, method_name: str, *args: Any, **kwargs: Any + ) -> Callable[..., Any]: + if ( + parser_method is not None + and self.case_sensitive is False + and method_name == 'parse_args_method' + and isinstance(self._root_parser, _CliInternalArgParser) + ): + + def parse_args_insensitive_method( + root_parser: _CliInternalArgParser, + args: list[str] | tuple[str, ...] | None = None, + namespace: Namespace | None = None, + ) -> Any: + insensitive_args = [] + for arg in shlex.split(shlex.join(args)) if args else []: + flag_prefix = rf'\{self.cli_flag_prefix_char}{{1,2}}' + matched = re.match(rf'^({flag_prefix}[^\s=]+)(.*)', arg) + if matched: + arg = matched.group(1).lower() + matched.group(2) + insensitive_args.append(arg) + return parser_method(root_parser, insensitive_args, namespace) + + return parse_args_insensitive_method + + elif parser_method is None: + + def none_parser_method(*args: Any, **kwargs: Any) -> Any: + raise SettingsError( + f'cannot connect CLI settings source root parser: {method_name} is set to `None` but is needed for connecting' + ) + + return none_parser_method + + else: + return parser_method + + def _connect_group_method(self, add_argument_group_method: Callable[..., Any] | None) -> Callable[..., Any]: + add_argument_group = self._connect_parser_method(add_argument_group_method, 'add_argument_group_method') + + def add_group_method(parser: Any, **kwargs: Any) -> Any: + if not kwargs.pop('_is_cli_mutually_exclusive_group'): + kwargs.pop('required') + return add_argument_group(parser, **kwargs) + else: + main_group_kwargs = {arg: kwargs.pop(arg) for arg in ['title', 'description'] if arg in kwargs} + main_group_kwargs['title'] += ' (mutually exclusive)' + group = add_argument_group(parser, **main_group_kwargs) + if not hasattr(group, 'add_mutually_exclusive_group'): + raise SettingsError( + 'cannot connect CLI settings source root parser: ' + 'group object is missing add_mutually_exclusive_group but is needed for connecting' + ) + return group.add_mutually_exclusive_group(**kwargs) + + return add_group_method + + def _connect_root_parser( + self, + root_parser: T, + parse_args_method: Callable[..., Any] | None, + add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument, + add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group, + add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser, + add_subparsers_method: Callable[..., Any] | None = ArgumentParser.add_subparsers, + format_help_method: Callable[..., Any] | None = ArgumentParser.format_help, + formatter_class: Any = RawDescriptionHelpFormatter, + ) -> None: + self._cli_unknown_args: dict[str, list[str]] = {} + + def _parse_known_args(*args: Any, **kwargs: Any) -> Namespace: + args, unknown_args = ArgumentParser.parse_known_args(*args, **kwargs) + for dest in self._cli_unknown_args: + self._cli_unknown_args[dest] = unknown_args + return cast(Namespace, args) + + self._root_parser = root_parser + _is_default_parse_args = parse_args_method is None + if parse_args_method is None: + parse_args_method = _parse_known_args if self.cli_ignore_unknown_args else ArgumentParser.parse_args + self._parse_args = self._connect_parser_method(parse_args_method, 'parse_args_method') + self._add_argument = self._connect_parser_method(add_argument_method, 'add_argument_method') + self._add_group = self._connect_group_method(add_argument_group_method) + self._add_parser = self._connect_parser_method(add_parser_method, 'add_parser_method') + self._add_subparsers = self._connect_parser_method(add_subparsers_method, 'add_subparsers_method') + self._format_help = self._connect_parser_method(format_help_method, 'format_help_method') + self._formatter_class = formatter_class + self._cli_dict_args: dict[str, type[Any] | None] = {} + self._parser_map: defaultdict[str | FieldInfo, dict[int | None | str | type[BaseModel], _CliArg]] = defaultdict( + dict + ) + self._add_default_help() + self._add_parser_args( + parser=self.root_parser, + model=self.settings_cls, + added_args=[], + arg_prefix=self.env_prefix, + subcommand_prefix=self.env_prefix, + group=None, + alias_prefixes=[], + model_default=PydanticUndefined, + model_path=set(), + env_prefixes=self._root_env_prefixes(), + ) + + # If subcommands registered CliUnknownArgs fields but root does not have + # cli_ignore_unknown_args=True, upgrade to parse_known_args so that argparse + # does not error on unknown arguments destined for a subcommand. + if self._cli_unknown_args and not self.cli_ignore_unknown_args and _is_default_parse_args: + self._parse_args = self._connect_parser_method(_parse_known_args, 'parse_args_method') + + def _add_default_help(self) -> None: + if isinstance(self._root_parser, _CliInternalArgParser): + if not self.cli_prefix: + for field_name, field_info in _get_model_fields(self.settings_cls).items(): + alias_names, *_ = _get_alias_names(field_name, field_info, case_sensitive=self.case_sensitive) + if 'help' in alias_names: + return + + self._add_argument( + self.root_parser, + f'{self._cli_flag_prefix[:1]}h', + f'{self._cli_flag_prefix[:2]}help', + action='help', + default=SUPPRESS, + help='show this help message and exit', + ) + + def _add_parser_args( # noqa: C901 + self, + parser: Any, + model: type[BaseModel], + added_args: list[str], + arg_prefix: str, + subcommand_prefix: str, + group: Any, + alias_prefixes: list[str], + model_default: Any, + is_model_suppressed: bool = False, + discriminator_vals: dict[str, set[Any]] | None = None, + is_last_discriminator: bool = True, + model_path: set[type[BaseModel]] | None = None, + env_prefixes: tuple[tuple[str, bool], ...] = (), + ) -> ArgumentParser: + if discriminator_vals is None: + discriminator_vals = {} + if model_path is None: + model_path = set() + model_path = model_path | {model} + subparsers: Any = None + alias_path_args: dict[str, int | None] = {} + # Ignore model default if the default is a model and not a subclass of the current model. + model_default = ( + None + if ( + (is_model_class(type(model_default)) or is_pydantic_dataclass(type(model_default))) + and not issubclass(type(model_default), model) + ) + else model_default + ) + for field_name, field_info in self._sort_arg_fields(model): + arg = _CliArg( + parser=parser, + field_info=field_info, + parser_map=self._parser_map, + model=model, + field_name=field_name, + arg_prefix=arg_prefix, + case_sensitive=self.case_sensitive, + populate_by_name=self.config.get('populate_by_name', False) + or self.config.get('validate_by_name', False), + hide_none_type=self.cli_hide_none_type, + kebab_case=self.cli_kebab_case, + enable_decoding=self.config.get('enable_decoding'), + env_prefix_len=self.env_prefix_len, + ) + alias_path_args.update(arg.alias_paths) + + if arg.subcommand_dest: + for sub_model in arg.sub_models: + subcommand_alias = arg.subcommand_alias(sub_model) + subcommand_arg = self._parser_map[arg.subcommand_dest][subcommand_alias] + subcommand_arg.args = [subcommand_alias] + subcommand_arg.kwargs['allow_abbrev'] = False + subcommand_arg.kwargs['formatter_class'] = self._formatter_class + subcommand_arg.kwargs['description'] = _get_model_description(sub_model) + subcommand_arg.kwargs['help'] = None if len(arg.sub_models) > 1 else field_info.description + if self.cli_use_class_docs_for_groups: + subcommand_arg.kwargs['help'] = _get_model_description(sub_model) + + subparsers = ( + self._add_subparsers( + parser, + title='subcommands', + dest=f'{arg_prefix}:subcommand', + description=field_info.description if len(arg.sub_models) > 1 else None, + ) + if subparsers is None + else subparsers + ) + + if hasattr(subparsers, 'metavar'): + subparsers.metavar = ( + f'{subparsers.metavar[:-1]},{subcommand_alias}}}' + if subparsers.metavar + else f'{{{subcommand_alias}}}' + ) + + subcommand_arg.parser = self._add_parser(subparsers, *subcommand_arg.args, **subcommand_arg.kwargs) + self._add_parser_args( + parser=subcommand_arg.parser, + model=sub_model, + added_args=[], + arg_prefix=f'{arg.dest}.', + subcommand_prefix=f'{subcommand_prefix}{arg.preferred_alias}.', + group=None, + alias_prefixes=[], + model_default=PydanticUndefined, + model_path=model_path, + env_prefixes=self._nested_env_prefixes( + self._get_field_env_var_names(field_name, field_info, env_prefixes) + ), + ) + else: + flag_prefix: str = self._cli_flag_prefix + env_var_names = self._get_field_env_var_names(field_name, field_info, env_prefixes) + arg.kwargs['dest'] = arg.dest + arg.kwargs['default'] = CLI_SUPPRESS + arg.kwargs['help'] = self._help_format( + field_name, + field_info, + model_default, + is_model_suppressed, + env_var_names, + ) + arg.kwargs['metavar'] = self._metavar_format(field_info.annotation) + arg.kwargs['required'] = ( + self.cli_enforce_required and field_info.is_required() and model_default is PydanticUndefined + ) + + arg_names = self._get_arg_names( + arg, + subcommand_prefix, + alias_prefixes, + added_args, + discriminator_vals, + is_last_discriminator, + ) + if not arg_names or (arg.kwargs['dest'] in added_args): + continue + + self._convert_append_action(arg.kwargs, field_info, arg.is_append_action) + + if _CliPositionalArg in field_info.metadata: + arg_names, flag_prefix = self._convert_positional_arg( + arg.kwargs, field_info, arg.preferred_alias, model_default + ) + + self._convert_bool_flag(arg.kwargs, field_info, model_default) + + non_recursive_sub_models = [m for m in arg.sub_models if m not in model_path] + if ( + arg.is_parser_submodel + and not getattr(field_info.annotation, '__pydantic_root_model__', False) + and non_recursive_sub_models + ): + self._add_parser_submodels( + parser, + model, + non_recursive_sub_models, + added_args, + arg_prefix, + subcommand_prefix, + flag_prefix, + arg_names, + arg.kwargs, + field_name, + field_info, + arg.alias_names, + model_default=model_default, + is_model_suppressed=is_model_suppressed, + model_path=model_path, + env_var_names=env_var_names, + ) + elif _CliUnknownArgs in field_info.metadata: + self._cli_unknown_args[arg.kwargs['dest']] = [] + elif not arg.is_alias_path_only: + if isinstance(group, dict): + group = self._add_group(parser, **group) + context = parser if group is None else group + if arg.kwargs.get('action') == 'store_false': + flag_prefix += 'no-' + arg.args = [f'{flag_prefix[: 1 if len(name) == 1 else None]}{name}' for name in arg_names] + self._add_env_var_names(arg.dest, arg.kwargs['help'], env_var_names) + self._add_argument(context, *arg.args, **arg.kwargs) + added_args += list(arg_names) + + self._add_parser_alias_paths(parser, alias_path_args, added_args, arg_prefix, subcommand_prefix, group) + return parser + + def _convert_append_action(self, kwargs: dict[str, Any], field_info: FieldInfo, is_append_action: bool) -> None: + if is_append_action: + kwargs['action'] = 'append' + if _annotation_contains_types(field_info.annotation, (dict, Mapping), is_strip_annotated=True): + self._cli_dict_args[kwargs['dest']] = field_info.annotation + + def _convert_bool_flag(self, kwargs: dict[str, Any], field_info: FieldInfo, model_default: Any) -> None: + if kwargs['metavar'] == 'bool': + meta_bool_flags = [ + meta + for meta in field_info.metadata + if isinstance(meta, type) and issubclass(meta, _CliImplicitFlag | _CliExplicitFlag) + ] + if not meta_bool_flags and self.cli_implicit_flags: + meta_bool_flags = [_CliImplicitFlag] + if meta_bool_flags: + bool_flag = meta_bool_flags.pop() + if bool_flag is _CliImplicitFlag: + bool_flag = ( + _CliToggleFlag + if self.cli_implicit_flags == 'toggle' and isinstance(field_info.default, bool) + else _CliDualFlag + ) + if bool_flag is _CliDualFlag: + del kwargs['metavar'] + kwargs['action'] = BooleanOptionalAction + elif bool_flag is _CliToggleFlag: + del kwargs['metavar'] + kwargs['action'] = 'store_false' if field_info.default else 'store_true' + + def _convert_positional_arg( + self, kwargs: dict[str, Any], field_info: FieldInfo, preferred_alias: str, model_default: Any + ) -> tuple[list[str], str]: + flag_prefix = '' + arg_names = [kwargs['dest']] + kwargs['default'] = PydanticUndefined + kwargs['metavar'] = _CliArg.get_kebab_case(preferred_alias.upper(), self.cli_kebab_case) + + # Note: CLI positional args are always strictly required at the CLI. Therefore, use field_info.is_required in + # conjunction with model_default instead of the derived kwargs['required']. + is_required = field_info.is_required() and model_default is PydanticUndefined + if kwargs.get('action') == 'append': + del kwargs['action'] + kwargs['nargs'] = '+' if is_required else '*' + elif not is_required: + kwargs['nargs'] = '?' + + del kwargs['dest'] + del kwargs['required'] + return arg_names, flag_prefix + + def _get_arg_names( + self, + arg: _CliArg, + subcommand_prefix: str, + alias_prefixes: list[str], + added_args: list[str], + discriminator_vals: dict[str, set[Any]], + is_last_discriminator: bool, + ) -> list[str]: + arg_names: list[str] = [] + for prefix in [arg.arg_prefix] + alias_prefixes: + for name in arg.alias_names: + arg_name = _CliArg.get_kebab_case( + f'{prefix}{name}' + if subcommand_prefix == self.env_prefix + else f'{prefix.replace(subcommand_prefix, "", 1)}{name}', + self.cli_kebab_case, + ) + if arg_name not in added_args: + arg_names.append(arg_name) + + if self.cli_shortcuts: + for target, aliases in self.cli_shortcuts.items(): + if target in arg_names: + alias_list = [aliases] if isinstance(aliases, str) else aliases + arg_names.extend(alias for alias in alias_list if alias not in added_args) + + tags: set[Any] = set() + discriminators = discriminator_vals.get(arg.dest) + if discriminators is not None: + _annotation_contains_types( + arg.field_info.annotation, + (Literal,), + is_include_origin=True, + collect=tags, + ) + discriminators.update(chain.from_iterable(get_args(tag) for tag in tags)) + if not is_last_discriminator: + return [] + arg.kwargs['metavar'] = self._metavar_format(Literal[tuple(sorted(discriminators))]) + + return arg_names + + def _add_parser_submodels( + self, + parser: Any, + model: type[BaseModel], + sub_models: list[type[BaseModel]], + added_args: list[str], + arg_prefix: str, + subcommand_prefix: str, + flag_prefix: str, + arg_names: list[str], + kwargs: dict[str, Any], + field_name: str, + field_info: FieldInfo, + alias_names: tuple[str, ...], + model_default: Any, + is_model_suppressed: bool, + model_path: set[type[BaseModel]] | None = None, + env_var_names: tuple[str, ...] = (), + ) -> None: + if issubclass(model, CliMutuallyExclusiveGroup): + # Argparse has deprecated "calling add_argument_group() or add_mutually_exclusive_group() on a + # mutually exclusive group" (https://docs.python.org/3/library/argparse.html#mutual-exclusion). + # Since nested models result in a group add, raise an exception for nested models in a mutually + # exclusive group. + raise SettingsError('cannot have nested models in a CliMutuallyExclusiveGroup') + + model_group_kwargs: dict[str, Any] = {} + model_group_kwargs['title'] = f'{arg_names[0]} options' + model_group_kwargs['description'] = field_info.description + model_group_kwargs['required'] = kwargs['required'] + model_group_kwargs['_is_cli_mutually_exclusive_group'] = any( + issubclass(model, CliMutuallyExclusiveGroup) for model in sub_models + ) + if model_group_kwargs['_is_cli_mutually_exclusive_group'] and len(sub_models) > 1: + raise SettingsError('cannot use union with CliMutuallyExclusiveGroup') + if self.cli_use_class_docs_for_groups and len(sub_models) == 1: + model_group_kwargs['description'] = _get_model_description(sub_models[0]) + + if model_default is not PydanticUndefined: + if is_model_class(type(model_default)) or is_pydantic_dataclass(type(model_default)): + model_default = getattr(model_default, field_name) + else: + if field_info.default is not PydanticUndefined: + model_default = field_info.default + elif field_info.default_factory is not None: + model_default = field_info.default_factory + if model_default is None: + desc_header = f'default: {self.cli_parse_none_str} (undefined)' + if model_group_kwargs['description'] is not None: + model_group_kwargs['description'] = dedent(f'{desc_header}\n{model_group_kwargs["description"]}') + else: + model_group_kwargs['description'] = desc_header + + preferred_alias = alias_names[0] + is_model_suppressed = self._is_field_suppressed(field_info) or is_model_suppressed + if is_model_suppressed: + model_group_kwargs['description'] = CLI_SUPPRESS + added_args.append(arg_names[0]) + kwargs['required'] = False + kwargs['nargs'] = '?' + kwargs['const'] = '{}' + kwargs['help'] = ( + CLI_SUPPRESS + if is_model_suppressed or self.cli_avoid_json + else self._help_format_env_vars(f'set {arg_names[0]} from JSON string (default: {{}})', env_var_names) + ) + self._add_env_var_names(kwargs['dest'], kwargs['help'], env_var_names) + model_group = self._add_group(parser, **model_group_kwargs) + self._add_argument(model_group, *(f'{flag_prefix}{name}' for name in arg_names), **kwargs) + discriminator_vals: dict[str, set[Any]] = ( + {f'{arg_prefix}{preferred_alias}.{field_info.discriminator}': set()} if field_info.discriminator else {} + ) + for sub_model in sub_models: + self._add_parser_args( + parser=parser, + model=sub_model, + added_args=added_args, + arg_prefix=f'{arg_prefix}{preferred_alias}.', + subcommand_prefix=subcommand_prefix, + group=model_group, + alias_prefixes=[f'{arg_prefix}{name}.' for name in alias_names[1:]], + model_default=model_default, + is_model_suppressed=is_model_suppressed, + discriminator_vals=discriminator_vals, + is_last_discriminator=sub_model is sub_models[-1], + model_path=model_path, + env_prefixes=self._nested_env_prefixes(env_var_names), + ) + + def _add_parser_alias_paths( + self, + parser: Any, + alias_path_args: dict[str, int | None], + added_args: list[str], + arg_prefix: str, + subcommand_prefix: str, + group: Any, + ) -> None: + if alias_path_args: + context = parser + if group is not None: + context = self._add_group(parser, **group) if isinstance(group, dict) else group + for name, index in alias_path_args.items(): + arg_name = ( + f'{arg_prefix}{name}' + if subcommand_prefix == self.env_prefix + else f'{arg_prefix.replace(subcommand_prefix, "", 1)}{name}' + ) + kwargs: dict[str, Any] = {} + kwargs['default'] = CLI_SUPPRESS + kwargs['help'] = 'pydantic alias path' + kwargs['action'] = 'append' + kwargs['metavar'] = 'list' + if index is None: + kwargs['metavar'] = 'dict' + self._cli_dict_args[arg_name] = dict + args = [f'{self._cli_flag_prefix}{arg_name}'] + for arg in self._parser_map[arg_name].values(): + arg.args, arg.kwargs = args, kwargs + self._add_argument(context, *args, **kwargs) + added_args.append(arg_name) + + def _get_modified_args(self, obj: Any) -> tuple[str, ...]: + if not self.cli_hide_none_type: + return get_args(obj) + else: + return tuple([type_ for type_ in get_args(obj) if type_ is not type(None)]) + + def _metavar_format_choices(self, args: list[str], obj_qualname: str | None = None) -> str: + if 'JSON' in args: + args = args[: args.index('JSON') + 1] + [arg for arg in args[args.index('JSON') + 1 :] if arg != 'JSON'] + metavar = ','.join(args) + if obj_qualname: + return f'{obj_qualname}[{metavar}]' + else: + return metavar if len(args) == 1 else f'{{{metavar}}}' + + def _metavar_format_recurse(self, obj: Any) -> str: + """Pretty metavar representation of a type. Adapts logic from `pydantic._repr.display_as_type`.""" + obj = _strip_annotated(obj) + if _is_function(obj): + # If function is locally defined use __name__ instead of __qualname__ + return obj.__name__ if '' in obj.__qualname__ else obj.__qualname__ + elif obj is ...: + return '...' + elif isinstance(obj, Representation): + return repr(obj) + elif isinstance(obj, typing.ForwardRef) or typing_objects.is_typealiastype(obj): + return str(obj) + + if not isinstance(obj, (_typing_base, _WithArgsTypes, type)): + obj = obj.__class__ + + origin = get_origin(obj) + if is_union_origin(origin): + return self._metavar_format_choices(list(map(self._metavar_format_recurse, self._get_modified_args(obj)))) + elif typing_objects.is_literal(origin): + return self._metavar_format_choices(list(map(str, self._get_modified_args(obj)))) + elif _lenient_issubclass(obj, Enum): + return self._metavar_format_choices( + [_CliArg.get_kebab_case(name, self.cli_kebab_case == 'all') for name in obj.__members__] + ) + elif isinstance(obj, _WithArgsTypes): + return self._metavar_format_choices( + list(map(self._metavar_format_recurse, self._get_modified_args(obj))), + obj_qualname=obj.__qualname__ if hasattr(obj, '__qualname__') else str(obj), + ) + elif obj is type(None): + return self.cli_parse_none_str + elif is_model_class(obj) or is_pydantic_dataclass(obj): + return ( + self._metavar_format_recurse(_get_model_fields(obj)['root'].annotation) + if getattr(obj, '__pydantic_root_model__', False) + else 'JSON' + ) + elif isinstance(obj, type): + return obj.__qualname__ + else: + return repr(obj).replace('typing.', '').replace('typing_extensions.', '') + + def _metavar_format(self, obj: Any) -> str: + return self._metavar_format_recurse(obj).replace(', ', ',') + + def _root_env_prefixes(self) -> tuple[tuple[str, bool], ...]: + if self._env_settings_source is None: + return () + return ((self._env_settings_source.env_prefix, False),) + + def _nested_env_prefixes(self, env_var_names: tuple[str, ...]) -> tuple[tuple[str, bool], ...]: + if self._env_settings_source is None or not self._env_settings_source.env_nested_delimiter: + return () + return tuple( + (f'{env_name}{self._env_settings_source.env_nested_delimiter}', True) for env_name in env_var_names + ) + + def _display_env_var_name(self, env_name: str) -> str: + if self._env_settings_source is not None and not self._env_settings_source.case_sensitive: + return env_name.upper() + return env_name + + def _get_field_env_var_names( + self, + field_name: str, + field_info: FieldInfo, + env_prefixes: tuple[tuple[str, bool], ...], + ) -> tuple[str, ...]: + if ( + self._env_settings_source is None + or self._is_field_suppressed(field_info) + or _CliPositionalArg in field_info.metadata + or _CliUnknownArgs in field_info.metadata + ): + return () + + env_var_names: list[str] = [] + env_settings_source = copy.copy(self._env_settings_source) + for env_prefix, force_env_prefix in env_prefixes: + env_settings_source.env_prefix = env_prefix + normalized_env_prefix = env_settings_source._apply_case_sensitive(env_prefix) + for _, env_name, _ in env_settings_source._extract_field_info(field_info, field_name): + if force_env_prefix and normalized_env_prefix and not env_name.startswith(normalized_env_prefix): + env_name = f'{normalized_env_prefix}{env_name}' + display_env_name = self._display_env_var_name(env_name) + if display_env_name and display_env_name not in env_var_names: + env_var_names.append(display_env_name) + + return tuple(env_var_names) + + def _help_format_env_vars(self, help_text: str, env_var_names: tuple[str, ...]) -> str: + if not env_var_names: + return help_text + env_help = f'[env: {" | ".join(env_var_names)}]' + return f'{help_text} {env_help}' if help_text else env_help + + def _add_env_var_names(self, dest: str, help_text: str, env_var_names: tuple[str, ...]) -> None: + if env_var_names and help_text != CLI_SUPPRESS: + self._env_var_names[dest] = env_var_names + + def _help_format( + self, + field_name: str, + field_info: FieldInfo, + model_default: Any, + is_model_suppressed: bool, + env_var_names: tuple[str, ...] = (), + ) -> str: + _help = field_info.description if field_info.description else '' + if is_model_suppressed or self._is_field_suppressed(field_info): + return CLI_SUPPRESS + + if field_info.is_required() and model_default in (PydanticUndefined, None): + if _CliPositionalArg not in field_info.metadata: + ifdef = 'ifdef: ' if model_default is None else '' + _help += f' ({ifdef}required)' if _help else f'({ifdef}required)' + else: + default = f'(default: {self.cli_parse_none_str})' + if is_model_class(type(model_default)) or is_pydantic_dataclass(type(model_default)): + default = f'(default: {getattr(model_default, field_name)})' + elif model_default not in (PydanticUndefined, None) and _is_function(model_default): + default = f'(default factory: {self._metavar_format(model_default)})' + elif field_info.default not in (PydanticUndefined, None): + enum_name = _annotation_enum_val_to_name(field_info.annotation, field_info.default) + default = f'(default: {field_info.default if enum_name is None else enum_name})' + elif field_info.default_factory is not None: + default = f'(default factory: {self._metavar_format(field_info.default_factory)})' + + if _CliToggleFlag not in field_info.metadata: + _help += f' {default}' if _help else default + _help = self._help_format_env_vars(_help, env_var_names) + return _help.replace('%', '%%') if issubclass(type(self._root_parser), ArgumentParser) else _help + + def _is_field_suppressed(self, field_info: FieldInfo) -> bool: + _help = field_info.description if field_info.description else '' + return _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata + + def _update_alias_path_only_default( + self, arg_name: str, value: Any, field_info: FieldInfo, alias_path_only_defaults: dict[str, Any] + ) -> list[Any] | dict[str, Any]: + alias_path: AliasPath = next( + alias if isinstance(alias, AliasPath) else cast(AliasPath, alias.choices[0]) + for alias in (field_info.alias, field_info.validation_alias) + if isinstance(alias, (AliasPath, AliasChoices)) + ) + + alias_nested_paths: list[str] = alias_path.path[1:-1] # type: ignore + if not alias_nested_paths: + alias_path_only_defaults.setdefault(arg_name, []) + alias_default = alias_path_only_defaults[arg_name] + else: + alias_path_only_defaults.setdefault(arg_name, {}) + current_path = alias_path_only_defaults[arg_name] + + for nested_path in alias_nested_paths[:-1]: + current_path.setdefault(nested_path, {}) + current_path = current_path[nested_path] + current_path.setdefault(alias_nested_paths[-1], []) + alias_default = current_path[alias_nested_paths[-1]] + + alias_path_index = cast(int, alias_path.path[-1]) + alias_default.extend([''] * max(alias_path_index + 1 - len(alias_default), 0)) + alias_default[alias_path_index] = value + return alias_path_only_defaults[arg_name] + + def _coerce_value_styles( + self, + model_default: Any, + value: str | list[Any] | dict[str, Any], + list_style: Literal['json', 'argparse', 'lazy'] = 'json', + dict_style: Literal['json', 'env'] = 'json', + ) -> list[str | list[Any] | dict[str, Any]]: + values = [value] + if isinstance(value, str): + if isinstance(model_default, list): + if list_style == 'lazy': + values = [','.join(f'{v}' for v in json.loads(value))] + elif list_style == 'argparse': + values = [f'{v}' for v in json.loads(value)] + elif isinstance(model_default, dict) and dict_style == 'env': + values = [f'{k}={v}' for k, v in json.loads(value).items()] + return values + + @staticmethod + def _flatten_serialized_args( + serialized_args: dict[str, list[str]], + positionals_first: bool, + ) -> list[str]: + return ( + serialized_args['optional'] + serialized_args['positional'] + if not positionals_first + else serialized_args['positional'] + serialized_args['optional'] + ) + serialized_args['subcommand'] + + def _serialized_args( + self, + model: PydanticModel, + list_style: Literal['json', 'argparse', 'lazy'] = 'json', + dict_style: Literal['json', 'env'] = 'json', + positionals_first: bool = False, + _is_submodel: bool = False, + ) -> dict[str, list[str]]: + alias_path_only_defaults: dict[str, Any] = {} + optional_args: list[str | list[Any] | dict[str, Any]] = [] + positional_args: list[str | list[Any] | dict[str, Any]] = [] + subcommand_args: list[str] = [] + for field_name, field_info in _get_model_fields(type(model) if _is_submodel else self.settings_cls).items(): + model_default = getattr(model, field_name) + if field_info.default == model_default: + continue + if _CliSubCommand in field_info.metadata and model_default is None: + continue + arg = next(iter(self._parser_map[field_info].values())) + if arg.subcommand_dest: + subcommand_args.append(arg.subcommand_alias(type(model_default))) + sub_args = self._serialized_args( + model_default, + list_style=list_style, + dict_style=dict_style, + positionals_first=positionals_first, + _is_submodel=True, + ) + subcommand_args += self._flatten_serialized_args(sub_args, positionals_first) + continue + if is_model_class(type(model_default)) or is_pydantic_dataclass(type(model_default)): + sub_args = self._serialized_args( + model_default, + list_style=list_style, + dict_style=dict_style, + positionals_first=positionals_first, + _is_submodel=True, + ) + optional_args += sub_args['optional'] + positional_args += sub_args['positional'] + subcommand_args += sub_args['subcommand'] + continue + + matched = re.match(r'(-*)(.+)', arg.preferred_arg_name) + flag_chars, arg_name = matched.groups() if matched else ('', '') + value: str | list[Any] | dict[str, Any] = ( + json.dumps(model_default) if isinstance(model_default, (dict, list, set)) else str(model_default) + ) + + if arg.is_alias_path_only: + # For alias path only, we won't know the complete value until we've finished parsing the entire class. In + # this case, insert value as a non-string reference pointing to the relevant alias_path_only_defaults + # entry and convert into completed string value later. + value = self._update_alias_path_only_default(arg_name, value, field_info, alias_path_only_defaults) + + if _CliPositionalArg in field_info.metadata: + for value in model_default if isinstance(model_default, list) else [model_default]: + value = json.dumps(value) if isinstance(value, (dict, list, set)) else str(value) + positional_args.append(value) + continue + + # Note: prepend 'no-' for boolean optional action flag if model_default value is False and flag is not a short option + if arg.kwargs.get('action') == BooleanOptionalAction and model_default is False and flag_chars == '--': + flag_chars += 'no-' + + for coerced_value in self._coerce_value_styles( + model_default, value, list_style=list_style, dict_style=dict_style + ): + optional_args.append(f'{flag_chars}{arg_name}') + + # If implicit bool flag, do not add a value + if arg.kwargs.get('action') not in (BooleanOptionalAction, 'store_true', 'store_false'): + optional_args.append(coerced_value) + + return { + 'optional': [json.dumps(value) if not isinstance(value, str) else value for value in optional_args], + 'positional': [json.dumps(value) if not isinstance(value, str) else value for value in positional_args], + 'subcommand': subcommand_args, + } diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py new file mode 100644 index 0000000..d4b7848 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py @@ -0,0 +1,210 @@ +"""Dotenv file settings source.""" + +from __future__ import annotations as _annotations + +import logging +import os +import warnings +from collections.abc import Mapping +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from dotenv import dotenv_values +from pydantic._internal._typing_extra import ( # type: ignore[attr-defined] + get_origin, +) +from typing_inspection.introspection import is_union_origin + +from ...utils import _settings_debug_enabled, logger +from ..types import ENV_FILE_SENTINEL, DotenvFiltering, DotenvType, EnvPrefixTarget +from ..utils import ( + InitState, + _annotation_is_complex, + _union_is_complex, + parse_env_vars, +) +from .env import EnvSettingsSource + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + +class DotEnvSettingsSource(EnvSettingsSource): + """ + Source class for loading settings values from env files. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + env_file: DotenvType | None = ENV_FILE_SENTINEL, + env_file_encoding: str | None = None, + dotenv_filtering: DotenvFiltering | None = None, + case_sensitive: bool | None = None, + env_prefix: str | None = None, + env_prefix_target: EnvPrefixTarget | None = None, + env_nested_delimiter: str | None = None, + env_nested_max_split: int | None = None, + env_ignore_empty: bool | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + _init_state: InitState | None = None, + ) -> None: + self.env_file = env_file if env_file != ENV_FILE_SENTINEL else settings_cls.model_config.get('env_file') + self.env_file_encoding = ( + env_file_encoding if env_file_encoding is not None else settings_cls.model_config.get('env_file_encoding') + ) + self.dotenv_filtering = ( + dotenv_filtering if dotenv_filtering is not None else settings_cls.model_config.get('dotenv_filtering') + ) + super().__init__( + settings_cls, + case_sensitive, + env_prefix, + env_prefix_target, + env_nested_delimiter, + env_nested_max_split, + env_ignore_empty, + env_parse_none_str, + env_parse_enums, + _init_state, + ) + + def _load_env_vars(self) -> Mapping[str, str | None]: + return self._read_env_files() + + @staticmethod + def _static_read_env_file( + file_path: Path, + *, + encoding: str | None = None, + case_sensitive: bool = False, + ignore_empty: bool = False, + parse_none_str: str | None = None, + ) -> Mapping[str, str | None]: + file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8') + return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str) + + def _read_env_file( + self, + file_path: Path, + ) -> Mapping[str, str | None]: + return self._static_read_env_file( + file_path, + encoding=self.env_file_encoding, + case_sensitive=self.case_sensitive, + ignore_empty=self.env_ignore_empty, + parse_none_str=self.env_parse_none_str, + ) + + def _read_env_files(self) -> Mapping[str, str | None]: + env_files = self.env_file + if env_files is None: + return {} + + if isinstance(env_files, (str, os.PathLike)): + env_files = [env_files] + + debug = _settings_debug_enabled() and logger.isEnabledFor(logging.DEBUG) + + dotenv_vars: dict[str, str | None] = {} + for env_file in env_files: + env_path = Path(env_file).expanduser() + if env_path.is_file() or env_path.is_fifo(): + if debug: + logger.debug('Loading env file: %s', env_path.resolve()) + dotenv_vars.update(self._read_env_file(env_path)) + elif debug: + logger.debug('Env file not found, skipping: %s', env_path.resolve()) + + return dotenv_vars + + def __call__(self) -> dict[str, Any]: # noqa: C901 + data: dict[str, Any] = super().__call__() + if self.dotenv_filtering == 'only_existing': + # This case behaves like the EnvSettingsSource, only return existing fields + return data + if self.dotenv_filtering == 'match_prefix': + # In this case add all env vars that match the prefix, stripping the prefix. + prefix = self._apply_case_sensitive(self.env_prefix) + for env_name, env_value in self.env_vars.items(): + if env_name.startswith(prefix): + normalized_env_name = env_name[len(self.env_prefix) :] + if ( + self.env_nested_delimiter + and self.env_nested_delimiter in normalized_env_name + and normalized_env_name.partition(self.env_nested_delimiter)[0] in data + ): + continue + if normalized_env_name not in data: + data[normalized_env_name] = env_value + return data + + is_extra_allowed = self.config.get('extra') != 'forbid' + + # As `extra` config is allowed in dotenv settings source, We have to + # update data with extra env variables from dotenv file. + for env_name, env_value in self.env_vars.items(): + if not env_value or env_name in data or (self.env_prefix and env_name in self.settings_cls.model_fields): + continue + env_used = False + for field_name, field in self.settings_cls.model_fields.items(): + for _, field_env_name, _ in self._extract_field_info(field, field_name): + if env_name == field_env_name or ( + ( + _annotation_is_complex(field.annotation, field.metadata, self._init_state) + or ( + is_union_origin(get_origin(field.annotation)) + and _union_is_complex(field.annotation, field.metadata, self._init_state) + ) + ) + # A var only belongs to a complex field when it sits at the nested + # delimiter boundary (`db...`, matching what explode_env_vars + # consumes). A bare name-prefix match (e.g. `dbx_token` vs field `db`) + # must not claim the var, otherwise it is silently dropped instead of + # being kept as an extra. + and self.env_nested_delimiter + and env_name.startswith(f'{field_env_name}{self.env_nested_delimiter}') + ): + env_used = True + break + if env_used: + break + if not env_used: + if is_extra_allowed and env_name.startswith(self.env_prefix): + # env_prefix should be respected and removed from the env_name + normalized_env_name = env_name[len(self.env_prefix) :] + data[normalized_env_name] = env_value + else: + data[env_name] = env_value + return data + + def __repr__(self) -> str: + return ( + f'{self.__class__.__name__}(env_file={self.env_file!r}, env_file_encoding={self.env_file_encoding!r}, ' + f'env_nested_delimiter={self.env_nested_delimiter!r}, env_prefix_len={self.env_prefix_len!r})' + ) + + +def read_env_file( + file_path: Path, + *, + encoding: str | None = None, + case_sensitive: bool = False, + ignore_empty: bool = False, + parse_none_str: str | None = None, +) -> Mapping[str, str | None]: + warnings.warn( + 'read_env_file will be removed in the next version, use DotEnvSettingsSource._static_read_env_file if you must', + DeprecationWarning, + ) + return DotEnvSettingsSource._static_read_env_file( + file_path, + encoding=encoding, + case_sensitive=case_sensitive, + ignore_empty=ignore_empty, + parse_none_str=parse_none_str, + ) + + +__all__ = ['DotEnvSettingsSource', 'read_env_file'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py new file mode 100644 index 0000000..e6c3993 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py @@ -0,0 +1,382 @@ +from __future__ import annotations as _annotations + +import json +import os +from collections.abc import Mapping +from typing import ( + TYPE_CHECKING, + Any, + get_args, + get_origin, +) + +from pydantic import AliasChoices, AliasPath, Json, TypeAdapter, ValidationError +from pydantic._internal._utils import deep_update, is_model_class +from pydantic.dataclasses import is_pydantic_dataclass +from pydantic.fields import FieldInfo +from typing_inspection.introspection import is_union_origin + +from ...utils import _lenient_issubclass +from ..base import PydanticBaseEnvSettingsSource +from ..types import EnvNoneType, EnvPrefixTarget +from ..utils import ( + InitState, + _annotation_contains_types, + _annotation_enum_name_to_val, + _annotation_is_complex, + _get_model_fields, + _literal_has_numeric_enum, + _union_has_strict_types, + _union_is_complex, + parse_env_vars, +) + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + +def _environ_is_case_insensitive() -> bool: + """Whether ``os.environ`` is the OS-backed, case-insensitive mapping (i.e. on Windows). + + When ``os.environ`` has been replaced (e.g. patched to a plain ``dict`` in tests), the + requested case sensitivity is honored as-is. + """ + return os.name == 'nt' and isinstance(os.environ, os._Environ) + + +class EnvSettingsSource(PydanticBaseEnvSettingsSource): + """ + Source class for loading settings values from environment variables. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + case_sensitive: bool | None = None, + env_prefix: str | None = None, + env_prefix_target: EnvPrefixTarget | None = None, + env_nested_delimiter: str | None = None, + env_nested_max_split: int | None = None, + env_ignore_empty: bool | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + _init_state: InitState | None = None, + ) -> None: + super().__init__( + settings_cls, + case_sensitive, + env_prefix, + env_prefix_target, + env_ignore_empty, + env_parse_none_str, + env_parse_enums, + _init_state, + ) + self.env_nested_delimiter = ( + env_nested_delimiter if env_nested_delimiter is not None else self.config.get('env_nested_delimiter') + ) + self.env_nested_max_split = ( + env_nested_max_split if env_nested_max_split is not None else self.config.get('env_nested_max_split') + ) + self.maxsplit = (self.env_nested_max_split or 0) - 1 + self.env_prefix_len = len(self.env_prefix) + + self.env_vars = self._load_env_vars() + + def _load_env_vars(self) -> Mapping[str, str | None]: + # On Windows, ``os.environ`` is case-insensitive: variable names are normalized to + # upper-case and looked up regardless of case. Matching environment variables + # case-sensitively is therefore impossible, so we fall back to case-insensitive + # matching to avoid spurious "field required" errors (see #295). ``.env`` files do + # preserve case and are handled by ``DotEnvSettingsSource``, which overrides this method. + if self.case_sensitive and _environ_is_case_insensitive(): + self.case_sensitive = False + return parse_env_vars(os.environ, self.case_sensitive, self.env_ignore_empty, self.env_parse_none_str) + + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + """ + Gets the value for field from environment variables and a flag to determine whether value is complex. + + Args: + field: The field. + field_name: The field name. + + Returns: + A tuple that contains the value (`None` if not found), key, and + a flag to determine whether value is complex. + """ + + env_val: str | None = None + for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name): + env_val = self.env_vars.get(env_name) + if env_val is not None: + break + + return env_val, field_key, value_is_complex + + def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any: + """ + Prepare value for the field. + + * Extract value for nested field. + * Deserialize value to python object for complex field. + + Args: + field: The field. + field_name: The field name. + + Returns: + A tuple contains prepared value for the field. + + Raises: + ValuesError: When There is an error in deserializing value for complex field. + """ + is_complex, allow_parse_failure = self._field_is_complex(field) + if self.env_parse_enums: + enum_val = _annotation_enum_name_to_val(field.annotation, value) + value = value if enum_val is None else enum_val + + if is_complex or value_is_complex: + if isinstance(value, EnvNoneType): + return value + elif value is None: + # field is complex but no value found so far, try explode_env_vars + env_val_built = self.explode_env_vars(field_name, field, self.env_vars) + if env_val_built: + return env_val_built + else: + # field is complex and there's a value, decode that as JSON, then add explode_env_vars + try: + value = self.decode_complex_value(field_name, field, value) + except ValueError: + if not allow_parse_failure: + raise + + if isinstance(value, dict): + return deep_update(value, self.explode_env_vars(field_name, field, self.env_vars)) + else: + return value + elif value is not None: + # simplest case, field is not complex, we only need to add the value if it was found + return self._coerce_env_val_strict(field, value) + + def _matches_alias_path_head(self, field: FieldInfo | Any | None, key: str) -> bool: + """ + Whether ``key`` matches the first element of a multi-part ``AliasPath`` on ``field``. + + When a nested field is aliased via e.g. ``AliasPath('path', 0)``, the env value found + under ``path`` is a container that must be JSON-decoded before pydantic can navigate + into it (see #670). This mirrors the complexity that ``_extract_field_info`` already + reports for top-level ``AliasPath`` fields. + + Args: + field: The field (or raw type) matched for ``key``. + key: The (case-normalized) env name segment matched for the field. + + Returns: + Whether ``key`` is the head of a multi-element ``AliasPath``. + """ + if not isinstance(field, FieldInfo): + return False + v_alias = field.validation_alias + paths: list[tuple[str | int, ...]] = [] + if isinstance(v_alias, AliasPath): + paths.append(tuple(v_alias.path)) + elif isinstance(v_alias, AliasChoices): + paths.extend(tuple(choice.path) for choice in v_alias.choices if isinstance(choice, AliasPath)) + for path in paths: + head = path[0] + if len(path) > 1 and isinstance(head, str) and self._apply_case_sensitive(head) == key: + return True + return False + + def _field_is_complex(self, field: FieldInfo) -> tuple[bool, bool]: + """ + Find out if a field is complex, and if so whether JSON errors should be ignored + """ + if self.field_is_complex(field): + allow_parse_failure = False + elif is_union_origin(get_origin(field.annotation)) and _union_is_complex( + field.annotation, field.metadata, self._init_state + ): + allow_parse_failure = True + else: + return False, False + + return True, allow_parse_failure + + # Default value of `case_sensitive` is `None`, because we don't want to break existing behavior. + # We have to change the method to a non-static method and use + # `self.case_sensitive` instead in V3. + def next_field( + self, field: FieldInfo | Any | None, key: str, case_sensitive: bool | None = None + ) -> FieldInfo | None: + """ + Find the field in a sub model by key(env name) + + By having the following models: + + ```py + class SubSubModel(BaseSettings): + dvals: Dict + + class SubModel(BaseSettings): + vals: list[str] + sub_sub_model: SubSubModel + + class Cfg(BaseSettings): + sub_model: SubModel + ``` + + Then: + next_field(sub_model, 'vals') Returns the `vals` field of `SubModel` class + next_field(sub_model, 'sub_sub_model') Returns `sub_sub_model` field of `SubModel` class + + Args: + field: The field. + key: The key (env name). + case_sensitive: Whether to search for key case sensitively. + + Returns: + Field if it finds the next field otherwise `None`. + """ + if not field: + return None + + annotation = field.annotation if isinstance(field, FieldInfo) else field + for type_ in get_args(annotation): + type_has_key = self.next_field(type_, key, case_sensitive) + if type_has_key: + return type_has_key + if _lenient_issubclass(get_origin(annotation), dict): + # get value type if it's a dict + return get_args(annotation)[-1] + elif is_model_class(annotation) or is_pydantic_dataclass(annotation): # type: ignore[arg-type] + fields = _get_model_fields(annotation) + # `case_sensitive is None` is here to be compatible with the old behavior. + # Has to be removed in V3. + for field_name, f in fields.items(): + for _, env_name, _ in self._extract_field_info(f, field_name): + if case_sensitive is None or case_sensitive: + if field_name == key or env_name == key: + return f + elif field_name.lower() == key.lower() or env_name.lower() == key.lower(): + return f + return None + + def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[str, str | None]) -> dict[str, Any]: # noqa: C901 + """ + Process env_vars and extract the values of keys containing env_nested_delimiter into nested dictionaries. + + This is applied to a single field, hence filtering by env_var prefix. + + Args: + field_name: The field name. + field: The field. + env_vars: Environment variables. + + Returns: + A dictionary contains extracted values from nested env values. + """ + if not self.env_nested_delimiter: + return {} + + ann = field.annotation + is_dict = ann is dict or _lenient_issubclass(get_origin(ann), dict) + + prefixes = [ + f'{env_name}{self.env_nested_delimiter}' for _, env_name, _ in self._extract_field_info(field, field_name) + ] + result: dict[str, Any] = {} + for env_name, env_val in env_vars.items(): + try: + prefix = next(prefix for prefix in prefixes if env_name.startswith(prefix)) + except StopIteration: + continue + # we remove the prefix before splitting in case the prefix has characters in common with the delimiter + env_name_without_prefix = env_name[len(prefix) :] + *keys, last_key = env_name_without_prefix.split(self.env_nested_delimiter, self.maxsplit) + env_var = result + target_field: FieldInfo | None = field + for key in keys: + target_field = self.next_field(target_field, key, self.case_sensitive) + if isinstance(env_var, dict): + env_var = env_var.setdefault(key, {}) + + # get proper field with last_key + target_field = self.next_field(target_field, last_key, self.case_sensitive) + + # check if env_val maps to a complex field and if so, parse the env_val + if (target_field or is_dict) and env_val: + if isinstance(target_field, FieldInfo): + is_complex, allow_json_failure = self._field_is_complex(target_field) + if not is_complex and self._matches_alias_path_head(target_field, last_key): + # The env value sits under an AliasPath head (e.g. AliasPath('path', 0)); + # decode it so pydantic can navigate into the container (see #670). + is_complex, allow_json_failure = True, True + if self.env_parse_enums: + enum_val = _annotation_enum_name_to_val(target_field.annotation, env_val) + env_val = env_val if enum_val is None else enum_val + elif target_field: + # target_field is a raw type (e.g. from dict value type annotation) + is_complex = _annotation_is_complex(target_field, [], self._init_state) + allow_json_failure = True + else: + # nested field type is dict + is_complex, allow_json_failure = True, True + if is_complex: + try: + field_info = target_field if isinstance(target_field, FieldInfo) else None + env_val = self.decode_complex_value(last_key, field_info, env_val) # type: ignore + except ValueError: + if not allow_json_failure: + raise + if isinstance(env_var, dict) and ( + last_key not in env_var or not isinstance(env_val, EnvNoneType) or env_var[last_key] == {} + ): + env_var[last_key] = self._coerce_env_val_strict(target_field, env_val) + return result + + def _coerce_env_val_strict(self, field: FieldInfo | None, value: Any) -> Any: + """ + Coerce environment string values based on field annotation if model config is `strict=True` + or if the field annotation contains strict-annotated types (e.g. Optional[StrictBool]). + + Args: + field: The field. + value: The value to coerce. + + Returns: + The coerced value if successful, otherwise the original value. + """ + try: + should_coerce = self.config.get('strict') + if not should_coerce and isinstance(field, FieldInfo): + should_coerce = ( + is_union_origin(get_origin(field.annotation)) and _union_has_strict_types(field.annotation) + ) or _literal_has_numeric_enum(field.annotation) + if should_coerce and isinstance(value, str) and isinstance(field, FieldInfo): + if value == self.env_parse_none_str: + return value + if not _annotation_contains_types(field.annotation, (Json,), is_instance=True): + try: + return TypeAdapter(field.annotation).validate_python(value) + except ValidationError: + # Try JSON decoding as fallback (e.g. 'true' -> True for StrictBool) + decoded = json.loads(value) + if not isinstance(decoded, str): + return TypeAdapter(field.annotation).validate_python(decoded) + raise + except (ValidationError, json.JSONDecodeError): + # Allow validation error to be raised at time of instantiation + pass + return value + + def __repr__(self) -> str: + return ( + f'{self.__class__.__name__}(env_nested_delimiter={self.env_nested_delimiter!r}, ' + f'env_prefix_len={self.env_prefix_len!r})' + ) + + +__all__ = ['EnvSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py new file mode 100644 index 0000000..f3a9261 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py @@ -0,0 +1,349 @@ +from __future__ import annotations as _annotations + +import warnings +from collections.abc import Iterator, Mapping +from functools import cached_property +from typing import TYPE_CHECKING, Any + +from pydantic.fields import FieldInfo + +from ...exceptions import SettingsError +from ..types import SecretVersion +from ..utils import InitState +from .env import EnvSettingsSource + +if TYPE_CHECKING: + from google.auth import default as google_auth_default + from google.auth.credentials import Credentials + from google.cloud.secretmanager import SecretManagerServiceClient + + from pydantic_settings.main import BaseSettings +else: + Credentials = None + SecretManagerServiceClient = None + google_auth_default = None + + +def import_gcp_secret_manager() -> None: + global Credentials + global SecretManagerServiceClient + global google_auth_default + + try: + from google.auth import default as google_auth_default + from google.auth.credentials import Credentials + + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=FutureWarning) + from google.cloud.secretmanager import SecretManagerServiceClient + except ImportError as e: # pragma: no cover + raise ImportError( + 'GCP Secret Manager dependencies are not installed, run `pip install pydantic-settings[gcp-secret-manager]`' + ) from e + + +def _is_not_found_error(exc: Exception) -> bool: + try: + from google.api_core.exceptions import NotFound + + return isinstance(exc, NotFound) + except ImportError: + return False + + +class GoogleSecretManagerMapping(Mapping[str, str | None]): + _loaded_secrets: dict[str, str | None] + _secret_client: SecretManagerServiceClient + + def __init__(self, secret_client: SecretManagerServiceClient, project_id: str, case_sensitive: bool) -> None: + self._loaded_secrets = {} + self._secret_client = secret_client + self._project_id = project_id + self._case_sensitive = case_sensitive + + @property + def _gcp_project_path(self) -> str: + return self._secret_client.common_project_path(self._project_id) + + def _select_case_insensitive_secret(self, lower_name: str, candidates: list[str]) -> str: + if len(candidates) == 1: + return candidates[0] + + # Sort to ensure deterministic selection (prefer lowercase / ASCII last) + candidates.sort() + winner = candidates[-1] + warnings.warn( + f"Secret collision: Found multiple secrets {candidates} normalizing to '{lower_name}'. " + f"Using '{winner}' for case-insensitive lookup.", + UserWarning, + stacklevel=2, + ) + return winner + + @cached_property + def _secret_name_map(self) -> dict[str, str]: + mapping: dict[str, str] = {} + # Group secrets by normalized name to detect collisions + normalized_groups: dict[str, list[str]] = {} + + secrets = self._secret_client.list_secrets(parent=self._gcp_project_path) + for secret in secrets: + name = self._secret_client.parse_secret_path(secret.name).get('secret', '') + mapping[name] = name + + if not self._case_sensitive: + lower_name = name.lower() + if lower_name not in normalized_groups: + normalized_groups[lower_name] = [] + normalized_groups[lower_name].append(name) + + if not self._case_sensitive: + for lower_name, candidates in normalized_groups.items(): + mapping[lower_name] = self._select_case_insensitive_secret(lower_name, candidates) + + return mapping + + @property + def _secret_names(self) -> list[str]: + return list(self._secret_name_map.keys()) + + def _secret_version_path(self, key: str, version: str = 'latest') -> str: + return self._secret_client.secret_version_path(self._project_id, key, version) + + def _get_secret_value(self, gcp_secret_name: str, version: str = 'latest') -> str | None: + try: + return self._secret_client.access_secret_version( + name=self._secret_version_path(gcp_secret_name, version) + ).payload.data.decode('UTF-8') + except Exception: + return None + + def _get_secret_value_or_raise(self, gcp_secret_name: str) -> str | None: + try: + return self._secret_client.access_secret_version( + name=self._secret_version_path(gcp_secret_name) + ).payload.data.decode('UTF-8') + except Exception as e: + if _is_not_found_error(e): + raise KeyError(gcp_secret_name) from e + return None + + def __getitem__(self, key: str) -> str | None: + if key in self._loaded_secrets: + return self._loaded_secrets[key] + + if self._case_sensitive: + self._loaded_secrets[key] = self._get_secret_value_or_raise(key) + return self._loaded_secrets[key] + + gcp_secret_name = self._secret_name_map.get(key) + if gcp_secret_name is None: + gcp_secret_name = self._secret_name_map.get(key.lower()) + + if gcp_secret_name: + self._loaded_secrets[key] = self._get_secret_value(gcp_secret_name) + else: + raise KeyError(key) + + return self._loaded_secrets[key] + + def __len__(self) -> int: + return len(self._secret_names) + + def __iter__(self) -> Iterator[str]: + return iter(self._secret_names) + + +class GoogleSecretManagerSettingsSource(EnvSettingsSource): + _credentials: Credentials | None + _secret_client: SecretManagerServiceClient | None + _project_id: str | None + _explicit_project_id: str | None + + def __init__( + self, + settings_cls: type[BaseSettings], + credentials: Credentials | None = None, + project_id: str | None = None, + env_prefix: str | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + secret_client: SecretManagerServiceClient | None = None, + case_sensitive: bool | None = True, + project_id_field: str = 'project_id', + _init_state: InitState | None = None, + ) -> None: + """Settings source that reads secrets from Google Cloud Secret Manager. + + Args: + project_id: The GCP project to read secrets from. If not provided, it is + resolved lazily (see below). + project_id_field: The key, populated by a previous (higher priority) + settings source, to use as the ``project_id`` when one is not passed + explicitly. This must match the key used in ``current_state`` by the + previous source (typically the field name or its preferred alias). + For example, with ``some_field: str = Field(alias='GCP_PROJECT')``, + previous env sources will expose the value under ``'GCP_PROJECT'``. + Defaults to ``'project_id'``. + + The ``project_id`` is resolved lazily in :meth:`__call__` rather than at + construction time so that it can be sourced from settings resolved by previous + sources (only available via ``current_state`` once the source is called). + Resolution order is: + + 1. the explicit ``project_id`` argument + 2. the ``project_id_field`` value from previous settings sources + 3. ``google.auth.default()`` + """ + # Import Google Packages if they haven't already been imported + if SecretManagerServiceClient is None or Credentials is None or google_auth_default is None: + import_gcp_secret_manager() + + # Resolution is deferred to __call__ (see _resolve_gcp_project / _load_env_vars). + # _explicit_project_id is the immutable user input; _project_id is the resolved + # value and is written exclusively by _resolve_gcp_project. + self._explicit_project_id = project_id + self._project_id = None + self._credentials = credentials + self._secret_client = secret_client + self._project_id_field = project_id_field + self._env_vars_loaded = False + + super().__init__( + settings_cls, + case_sensitive=case_sensitive, + env_prefix=env_prefix, + env_ignore_empty=False, + env_parse_none_str=env_parse_none_str, + env_parse_enums=env_parse_enums, + _init_state=_init_state, + ) + # __init__ is past; subsequent _load_env_vars() calls may now resolve the project. + self._env_vars_loaded = True + + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + """Override get_field_value to get the secret value from GCP Secret Manager. + Look for a SecretVersion metadata field to specify a particular SecretVersion. + + Args: + field: The field to get the value for + field_name: The declared name of the field + + Returns: + A tuple of (value, key, value_is_complex), where `key` is the identifier used + to populate the model (either the field name or an alias, depending on + configuration). + """ + + secret_version = next((m.version for m in field.metadata if isinstance(m, SecretVersion)), None) + + # If a secret version is specified, try to get that specific version of the secret from + # GCP Secret Manager via the GoogleSecretManagerMapping. This allows different versions + # of the same secret name to be retrieved independently and cached in the GoogleSecretManagerMapping + if secret_version and isinstance(self.env_vars, GoogleSecretManagerMapping): + for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name): + if self.case_sensitive: + gcp_secret_name: str | None = env_name + else: + gcp_secret_name = self.env_vars._secret_name_map.get(env_name) + if gcp_secret_name is None: + gcp_secret_name = self.env_vars._secret_name_map.get(env_name.lower()) + + if gcp_secret_name: + env_val = self.env_vars._get_secret_value(gcp_secret_name, secret_version) + if env_val is not None: + # If populate_by_name is enabled, return field_name to allow multiple fields + # with the same alias but different versions to be distinguished + if self.settings_cls.model_config.get('populate_by_name'): + return env_val, field_name, value_is_complex + return env_val, field_key, value_is_complex + + # If a secret version is specified but not found, we should not fall back to "latest" (default behavior) + # as that would be incorrect. We return None to indicate the value was not found. + return None, field_name, False + + val, key, is_complex = super().get_field_value(field, field_name) + + # If populate_by_name is enabled, we need to return the field_name as the key + # without this being enabled, you cannot load two secrets with the same name but different versions + if self.settings_cls.model_config.get('populate_by_name') and val is not None: + return val, field_name, is_complex + return val, key, is_complex + + def _resolve_gcp_project(self) -> None: + """Resolve the credentials, project_id and Secret Manager client. + + ``project_id`` is resolved, in order of precedence, from: the explicit + ``project_id`` argument, the ``project_id_field`` value from previous settings + sources (``current_state``), and finally ``google.auth.default()``. + """ + if self._project_id is not None: + return + + project_id = self._explicit_project_id + credentials = self._credentials + + # Fall back to a value resolved by a previous (higher priority) settings source. + if project_id is None: + state_project_id = self.current_state.get(self._project_id_field) + if isinstance(state_project_id, str): + project_id = state_project_id + + # Fall back to google.auth.default for whatever is still missing. + # Credentials are only needed if we have to build a client ourselves — if the + # caller supplied a pre-built secret_client we can skip the auth call entirely + # when project_id is also known, avoiding an unnecessary RPC / file read. + need_credentials = self._secret_client is None and credentials is None + need_project = project_id is None + if need_credentials or need_project: + _creds, _project_id = google_auth_default() + if need_credentials: + credentials = _creds + if need_project and isinstance(_project_id, str): + project_id = _project_id + + if project_id is None: + raise AttributeError( + 'project_id is required to be specified either as an argument, via a previous ' + 'settings source, or from google.auth.default. See ' + 'https://google-auth.readthedocs.io/en/master/reference/google.auth.html#google.auth.default' + ) + + self._credentials = credentials + self._project_id = project_id + if self._secret_client is None: + self._secret_client = SecretManagerServiceClient(credentials=self._credentials) + + def _load_env_vars(self) -> Mapping[str, str | None]: + # During __init__ the previous sources have not run yet, so defer building the + # mapping until __call__, where current_state (and thus project_id) is available. + if not self._env_vars_loaded: + return {} + + self._resolve_gcp_project() + if self._project_id is None or self._secret_client is None: + raise SettingsError( + 'GoogleSecretManagerSettingsSource: could not determine GCP project_id or initialize the Secret Manager client. ' + 'Pass project_id explicitly or ensure it is available via application default credentials or a previous settings source.' + ) + return GoogleSecretManagerMapping( + self._secret_client, project_id=self._project_id, case_sensitive=self.case_sensitive + ) + + def __call__(self) -> dict[str, Any]: + # current_state is populated by previous sources right before __call__; (re)build + # the mapping now so project_id can be sourced from them. + self._env_vars_loaded = True + self.env_vars = self._load_env_vars() + return super().__call__() + + def __repr__(self) -> str: + # Prefer the resolved project_id, falling back to the explicit constructor + # arg so the repr is informative before the source has been called. + project_id = self._project_id if self._project_id is not None else self._explicit_project_id + return ( + f'{self.__class__.__name__}(project_id={project_id!r}, env_nested_delimiter={self.env_nested_delimiter!r})' + ) + + +__all__ = ['GoogleSecretManagerMapping', 'GoogleSecretManagerSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py new file mode 100644 index 0000000..872917c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py @@ -0,0 +1,53 @@ +"""JSON file settings source.""" + +from __future__ import annotations as _annotations + +import json +from typing import ( + TYPE_CHECKING, + Any, +) + +from ..base import ConfigFileSourceMixin, InitSettingsSource +from ..types import DEFAULT_PATH, ConfigFileSourceType +from ..utils import InitState + +if TYPE_CHECKING: + from pathlib import Path + + from pydantic_settings.main import BaseSettings + + from ..types import Traversable + + +class JsonConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin): + """ + A source class that loads variables from a JSON file + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + json_file: ConfigFileSourceType | None = DEFAULT_PATH, + json_file_encoding: str | None = None, + deep_merge: bool = False, + _init_state: InitState | None = None, + ): + self.json_file_path = json_file if json_file != DEFAULT_PATH else settings_cls.model_config.get('json_file') + self.json_file_encoding = ( + json_file_encoding + if json_file_encoding is not None + else settings_cls.model_config.get('json_file_encoding') + ) + self.json_data = self._read_files(self.json_file_path, deep_merge=deep_merge) + super().__init__(settings_cls, self.json_data, _init_state=_init_state) + + def _read_file(self, file_path: Path | Traversable) -> dict[str, Any]: + with file_path.open(encoding=self.json_file_encoding) as json_file: + return json.load(json_file) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(json_file={self.json_file_path})' + + +__all__ = ['JsonConfigSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py new file mode 100644 index 0000000..e25df06 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py @@ -0,0 +1,214 @@ +import os +import warnings +from collections.abc import Iterator +from functools import reduce +from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal, Optional + +from ...exceptions import SettingsError +from ...utils import path_type_label +from ..base import PydanticBaseSettingsSource +from ..utils import InitState, parse_env_vars +from .env import EnvSettingsSource +from .secrets import SecretsSettingsSource + +if TYPE_CHECKING: + from ...main import BaseSettings + from ...sources import PathType + + +SECRETS_DIR_MAX_SIZE = 16 * 2**20 # 16 MiB seems to be a reasonable default + + +class NestedSecretsSettingsSource(EnvSettingsSource): + def __init__( + self, + file_secret_settings: PydanticBaseSettingsSource | SecretsSettingsSource, + secrets_dir: Optional['PathType'] = None, + secrets_dir_missing: Literal['ok', 'warn', 'error'] | None = None, + secrets_dir_max_size: int | None = None, + secrets_case_sensitive: bool | None = None, + secrets_prefix: str | None = None, + secrets_nested_delimiter: str | None = None, + secrets_nested_subdir: bool | None = None, + # args for compatibility with SecretsSettingsSource, don't use directly + case_sensitive: bool | None = None, + env_prefix: str | None = None, + _init_state: InitState | None = None, + ) -> None: + # We allow the first argument to be settings_cls like original + # SecretsSettingsSource. However, it is recommended to pass + # SecretsSettingsSource instance instead (as it is shown in usage examples), + # otherwise `_secrets_dir` arg passed to Settings() constructor will be ignored. + settings_cls: type[BaseSettings] = getattr( + file_secret_settings, + 'settings_cls', + file_secret_settings, # type: ignore[arg-type] + ) + # config options + conf = settings_cls.model_config + self.secrets_dir: PathType | None = first_not_none( + getattr(file_secret_settings, 'secrets_dir', None), + secrets_dir, + conf.get('secrets_dir'), + ) + self.secrets_dir_missing: Literal['ok', 'warn', 'error'] = first_not_none( + secrets_dir_missing, + conf.get('secrets_dir_missing'), + 'warn', + ) + if self.secrets_dir_missing not in ('ok', 'warn', 'error'): + raise SettingsError(f'invalid secrets_dir_missing value: {self.secrets_dir_missing}') + self.secrets_dir_max_size: int = first_not_none( + secrets_dir_max_size, + conf.get('secrets_dir_max_size'), + SECRETS_DIR_MAX_SIZE, + ) + self.case_sensitive: bool = first_not_none( + secrets_case_sensitive, + conf.get('secrets_case_sensitive'), + case_sensitive, + conf.get('case_sensitive'), + False, + ) + self.secrets_prefix: str = first_not_none( + secrets_prefix, + conf.get('secrets_prefix'), + env_prefix, + conf.get('env_prefix'), + '', + ) + + # nested options + self.secrets_nested_delimiter: str | None = first_not_none( + secrets_nested_delimiter, + conf.get('secrets_nested_delimiter'), + conf.get('env_nested_delimiter'), + ) + self.secrets_nested_subdir: bool = first_not_none( + secrets_nested_subdir, + conf.get('secrets_nested_subdir'), + False, + ) + if self.secrets_nested_subdir: + if secrets_nested_delimiter or conf.get('secrets_nested_delimiter'): + raise SettingsError('Options secrets_nested_delimiter and secrets_nested_subdir are mutually exclusive') + else: + self.secrets_nested_delimiter = os.sep + + # ensure valid secrets_path + if self.secrets_dir is None: + paths = [] + elif isinstance(self.secrets_dir, (Path, str)): + paths = [self.secrets_dir] + else: + paths = list(self.secrets_dir) + self.secrets_paths: list[Path] = [Path(p).expanduser().resolve() for p in paths] + for path in self.secrets_paths: + self.validate_secrets_path(path) + + # construct parent, inheriting the init state of the wrapped source if not provided + super().__init__( + settings_cls, + case_sensitive=self.case_sensitive, + env_prefix=self.secrets_prefix, + env_nested_delimiter=self.secrets_nested_delimiter, + env_ignore_empty=False, # match SecretsSettingsSource behaviour + env_parse_enums=True, # we can pass everything here, it will still behave as "True" + env_parse_none_str=None, # match SecretsSettingsSource behaviour + _init_state=_init_state if _init_state is not None else getattr(file_secret_settings, '_init_state', None), + ) + self.env_parse_none_str = None # update manually because of None + + # update parent members + if not len(self.secrets_paths): + self.env_vars = {} + else: + secrets = reduce( + lambda d1, d2: dict((*d1.items(), *d2.items())), + (self.load_secrets(p) for p in self.secrets_paths), + ) + self.env_vars = parse_env_vars( + secrets, + self.case_sensitive, + self.env_ignore_empty, + self.env_parse_none_str, + ) + + def validate_secrets_path(self, path: Path) -> None: + if not path.exists(): + if self.secrets_dir_missing == 'ok': + pass + elif self.secrets_dir_missing == 'warn': + warnings.warn(f'directory "{path}" does not exist', stacklevel=2) + elif self.secrets_dir_missing == 'error': + raise SettingsError(f'directory "{path}" does not exist') + else: + raise ValueError # unreachable, checked before + else: + if not path.is_dir(): + raise SettingsError(f'secrets_dir must reference a directory, not a {path_type_label(path)}') + secrets_dir_size = sum(f.stat().st_size for f in self._iter_secret_files(path)) + if secrets_dir_size > self.secrets_dir_max_size: + raise SettingsError(f'secrets_dir size is above {self.secrets_dir_max_size} bytes') + + @staticmethod + def _iter_secret_files(path: Path) -> Iterator[Path]: + """Yield the secret files contained in ``path``. + + ``path`` is expected to already be resolved. The directory tree is walked + explicitly so that symbolic links are handled safely: + + * a file is only yielded if its real location stays within ``path``; entries + that resolve outside of it (e.g. through a symbolic link) are skipped, so + they neither contribute to the ``secrets_dir_max_size`` accounting nor get + loaded; + * each real directory is visited at most once, so cyclic or repeated + symlinks cannot make the walk loop and inflate the size accounting or the + number of loaded secrets. + + Because the size check and the loader share this iterator, they always see + the same set of files. + """ + seen_dirs: set[Path] = set() + + def walk(directory: Path) -> Iterator[Path]: + # Guard against symlink loops / a directory reachable through multiple + # links being traversed more than once. + resolved_dir = directory.resolve() + if resolved_dir in seen_dirs: + return + seen_dirs.add(resolved_dir) + try: + entries = sorted(directory.iterdir()) + except OSError: + return + for entry in entries: + resolved = entry.resolve() + if resolved.is_dir(): + # Only descend into directories that stay within secrets_dir. + # A symlinked directory pointing outside of ``path`` is not + # followed at all, so we never walk (potentially large) external + # trees and never read files from outside secrets_dir. + if resolved == path or path in resolved.parents: + yield from walk(entry) + elif resolved.is_file() and path in resolved.parents: + # Defense in depth: a file whose real location escapes + # secrets_dir (e.g. a symlink pointing outside of ``path``) is + # skipped from both the size accounting and the load. + yield entry + + yield from walk(path) + + @classmethod + def load_secrets(cls, path: Path) -> dict[str, str]: + # Explicit encoding, matching SecretsSettingsSource: the locale default would + # corrupt UTF-8 secrets on a non-UTF-8 Windows code page. + return {str(p.relative_to(path)): p.read_text(encoding='utf-8').strip() for p in cls._iter_secret_files(path)} + + def __repr__(self) -> str: + return f'NestedSecretsSettingsSource(secrets_dir={self.secrets_dir!r})' + + +def first_not_none(*objs: Any) -> Any: + return next(filter(lambda o: o is not None, objs), None) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py new file mode 100644 index 0000000..309b6b6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py @@ -0,0 +1,64 @@ +"""Pyproject TOML file settings source.""" + +from __future__ import annotations as _annotations + +from pathlib import Path +from typing import ( + TYPE_CHECKING, +) + +from ..utils import InitState +from .toml import TomlConfigSettingsSource + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + +class PyprojectTomlConfigSettingsSource(TomlConfigSettingsSource): + """ + A source class that loads variables from a `pyproject.toml` file. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + toml_file: Path | None = None, + _init_state: InitState | None = None, + ) -> None: + self.toml_file_path = self._pick_pyproject_toml_file( + toml_file, settings_cls.model_config.get('pyproject_toml_depth', 0) + ) + self.toml_table_header: tuple[str, ...] = settings_cls.model_config.get( + 'pyproject_toml_table_header', ('tool', 'pydantic-settings') + ) + self.toml_data = self._read_files(self.toml_file_path) + for key in self.toml_table_header: + self.toml_data = self.toml_data.get(key, {}) + super(TomlConfigSettingsSource, self).__init__(settings_cls, self.toml_data, _init_state=_init_state) + + @staticmethod + def _pick_pyproject_toml_file(provided: Path | None, depth: int) -> Path: + """Pick a `pyproject.toml` file path to use. + + Args: + provided: Explicit path provided when instantiating this class. + depth: Number of directories up the tree to check of a pyproject.toml. + + """ + if provided: + return provided.resolve() + rv = Path.cwd() / 'pyproject.toml' + count = 0 + if not rv.is_file(): + child = rv.parent.parent / 'pyproject.toml' + while count < depth: + if child.is_file(): + return child + if str(child.parent) == rv.root: + break # end discovery after checking system root once + child = child.parent.parent / 'pyproject.toml' + count += 1 + return rv + + +__all__ = ['PyprojectTomlConfigSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py new file mode 100644 index 0000000..df22acf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py @@ -0,0 +1,143 @@ +"""Secrets file settings source.""" + +from __future__ import annotations as _annotations + +import logging +import os +import warnings +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, +) + +from pydantic.fields import FieldInfo + +from pydantic_settings.utils import _settings_debug_enabled, logger, path_type_label + +from ...exceptions import SettingsError +from ..base import PydanticBaseEnvSettingsSource +from ..types import EnvPrefixTarget, PathType +from ..utils import InitState + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + +class SecretsSettingsSource(PydanticBaseEnvSettingsSource): + """ + Source class for loading settings values from secret files. + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + secrets_dir: PathType | None = None, + case_sensitive: bool | None = None, + env_prefix: str | None = None, + env_prefix_target: EnvPrefixTarget | None = None, + env_ignore_empty: bool | None = None, + env_parse_none_str: str | None = None, + env_parse_enums: bool | None = None, + _init_state: InitState | None = None, + ) -> None: + super().__init__( + settings_cls, + case_sensitive, + env_prefix, + env_prefix_target, + env_ignore_empty, + env_parse_none_str, + env_parse_enums, + _init_state, + ) + self.secrets_dir = secrets_dir if secrets_dir is not None else self.config.get('secrets_dir') + + def __call__(self) -> dict[str, Any]: + """ + Build fields from "secrets" files. + """ + secrets: dict[str, str | None] = {} + + if self.secrets_dir is None: + return secrets + + secrets_dirs = [self.secrets_dir] if isinstance(self.secrets_dir, (str, os.PathLike)) else self.secrets_dir + secrets_paths = [Path(p).expanduser() for p in secrets_dirs] + self.secrets_paths = [] + + for path in secrets_paths: + if not path.exists(): + warnings.warn(f'directory "{path}" does not exist') + else: + self.secrets_paths.append(path) + + if not len(self.secrets_paths): + return secrets + + for path in self.secrets_paths: + if not path.is_dir(): + raise SettingsError(f'secrets_dir must reference a directory, not a {path_type_label(path)}') + + return super().__call__() + + @classmethod + def find_case_path(cls, dir_path: Path, file_name: str, case_sensitive: bool) -> Path | None: + """ + Find a file within path's directory matching filename, optionally ignoring case. + + Args: + dir_path: Directory path. + file_name: File name. + case_sensitive: Whether to search for file name case sensitively. + + Returns: + Whether file path or `None` if file does not exist in directory. + """ + for f in dir_path.iterdir(): + if f.name == file_name or (not case_sensitive and f.name.lower() == file_name.lower()): + return f + return None + + def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: + """ + Gets the value for field from secret file and a flag to determine whether value is complex. + + Args: + field: The field. + field_name: The field name. + + Returns: + A tuple that contains the value (`None` if the file does not exist), key, and + a flag to determine whether value is complex. + """ + + debug = _settings_debug_enabled() and logger.isEnabledFor(logging.DEBUG) + + for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name): + # paths reversed to match the last-wins behaviour of `env_file` + for secrets_path in reversed(self.secrets_paths): + path = self.find_case_path(secrets_path, env_name, self.case_sensitive) + if not path: + # path does not exist, we currently don't return a warning for this + if debug: + logger.debug('Secret file not found, skipping: %s', (secrets_path / env_name).resolve()) + continue + + if path.is_file(): + if debug: + logger.debug('Loading secret file: %s', path.resolve()) + # Explicit encoding: `read_text()` would otherwise decode with the + # locale encoding, so a UTF-8 secret is corrupted on a non-UTF-8 + # Windows code page (silently, when every byte happens to map). + return path.read_text(encoding='utf-8').strip(), field_key, value_is_complex + else: + warnings.warn( + f'attempted to load secret file "{path}" but found a {path_type_label(path)} instead.', + stacklevel=4, + ) + + return None, field_key, value_is_complex + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(secrets_dir={self.secrets_dir!r})' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py new file mode 100644 index 0000000..9147adc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py @@ -0,0 +1,100 @@ +"""TOML file settings source.""" + +from __future__ import annotations as _annotations + +import sys +from collections.abc import Sequence +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, +) + +from ..base import ConfigFileSourceMixin, InitSettingsSource +from ..types import DEFAULT_PATH, ConfigFileSourceType +from ..utils import InitState + +if TYPE_CHECKING: + from pydantic_settings.main import BaseSettings + + from ..types import Traversable + + if sys.version_info >= (3, 11): + import tomllib + else: + tomllib = None + import tomli +else: + tomllib = None + tomli = None + + +def import_toml() -> None: + global tomli + global tomllib + if sys.version_info < (3, 11): + if tomli is not None: + return + try: + import tomli + except ImportError as e: # pragma: no cover + raise ImportError('tomli is not installed, run `pip install pydantic-settings[toml]`') from e + else: + if tomllib is not None: + return + import tomllib + + +class TomlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin): + """ + A source class that loads variables from a TOML file + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + toml_file: ConfigFileSourceType | None = DEFAULT_PATH, + toml_table_header: tuple[str, ...] = (), + deep_merge: bool = False, + _init_state: InitState | None = None, + ): + self.toml_file_path = toml_file if toml_file != DEFAULT_PATH else settings_cls.model_config.get('toml_file') + self.toml_table_header = ( + toml_table_header if toml_table_header else settings_cls.model_config.get('toml_table_header', ()) + ) + self.toml_data = self._read_files(self.toml_file_path, deep_merge=deep_merge) + + if self._any_file_exists(self.toml_file_path): + for key in self.toml_table_header: + if key not in self.toml_data: + raise KeyError(f'toml_table_header key "{key}" not found in {self.toml_file_path}') + self.toml_data = self.toml_data[key] + + super().__init__(settings_cls, self.toml_data, _init_state=_init_state) + + def _read_file(self, file_path: Path | Traversable) -> dict[str, Any]: + import_toml() + with file_path.open(mode='rb') as toml_file: + if sys.version_info < (3, 11): + return tomli.load(toml_file) + return tomllib.load(toml_file) + + @staticmethod + def _any_file_exists(paths: ConfigFileSourceType | None) -> bool: + """Check if any of the given file paths exist.""" + if paths is None: + return False + if isinstance(paths, str) or not isinstance(paths, Sequence): + paths = [paths] + + def _exists(path: Path | str | Traversable) -> bool: + if isinstance(path, (str, Path)): + return Path(path).exists() + # Non-`Path` `Traversable` (e.g. a resource inside a zip/wheel) is not + # `os.PathLike`, so it can't be wrapped in `Path`; query it directly. + return path.is_file() + + return any(_exists(path) for path in paths) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(toml_file={self.toml_file_path}, toml_table_header={self.toml_table_header})' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py new file mode 100644 index 0000000..b3aa64e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py @@ -0,0 +1,147 @@ +"""YAML file settings source.""" + +from __future__ import annotations as _annotations + +from typing import ( + TYPE_CHECKING, + Any, +) + +from ..base import ConfigFileSourceMixin, InitSettingsSource +from ..types import DEFAULT_PATH, ConfigFileSourceType +from ..utils import InitState + +if TYPE_CHECKING: + from pathlib import Path + + import yaml + + from pydantic_settings.main import BaseSettings + + from ..types import Traversable +else: + yaml = None + + +def import_yaml() -> None: + global yaml + if yaml is not None: + return + try: + import yaml + except ImportError as e: + raise ImportError('PyYAML is not installed, run `pip install pydantic-settings[yaml]`') from e + + +class YamlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin): + """ + A source class that loads variables from a yaml file + """ + + def __init__( + self, + settings_cls: type[BaseSettings], + yaml_file: ConfigFileSourceType | None = DEFAULT_PATH, + yaml_file_encoding: str | None = None, + yaml_config_section: str | None = None, + deep_merge: bool = False, + _init_state: InitState | None = None, + ): + self.yaml_file_path = yaml_file if yaml_file != DEFAULT_PATH else settings_cls.model_config.get('yaml_file') + self.yaml_file_encoding = ( + yaml_file_encoding + if yaml_file_encoding is not None + else settings_cls.model_config.get('yaml_file_encoding') + ) + self.yaml_config_section = ( + yaml_config_section + if yaml_config_section is not None + else settings_cls.model_config.get('yaml_config_section') + ) + self.yaml_data = self._read_files(self.yaml_file_path, deep_merge=deep_merge) + + if self.yaml_config_section is not None: + section_data = self._traverse_nested_section( + self.yaml_data, self.yaml_config_section, self.yaml_config_section + ) + # An empty section (`section:` with no children) parses to `None`; treat it as an + # empty mapping so it falls back to defaults, mirroring `_read_file`'s `or {}` for + # an empty whole file. A non-mapping section (e.g. a scalar) is a user error, so + # raise a clear message instead of letting `InitSettingsSource` choke on `.keys()`. + if section_data is None: + section_data = {} + elif not isinstance(section_data, dict): + raise TypeError( + f'yaml_config_section "{self.yaml_config_section}" in {self.yaml_file_path} ' + f'must be a mapping, got {type(section_data).__name__}.' + ) + self.yaml_data = section_data + super().__init__(settings_cls, self.yaml_data, _init_state=_init_state) + + def _read_file(self, file_path: Path | Traversable) -> dict[str, Any]: + import_yaml() + with file_path.open(encoding=self.yaml_file_encoding) as yaml_file: + return yaml.safe_load(yaml_file) or {} + + def _traverse_nested_section( + self, data: dict[str, Any], section_path: str, original_path: str | None = None + ) -> dict[str, Any]: + """ + Traverse nested YAML sections using dot-notation path. + + This method tries to match the longest possible key first before splitting on dots, + allowing access to YAML keys that contain literal dot characters. + + For example, with section_path="a.b.c", it will try: + 1. "a.b.c" as a literal key + 2. "a.b" as a key, then traverse to "c" + 3. "a" as a key, then traverse to "b.c" + 4. "a" as a key, then "b" as a key, then "c" as a key + """ + # Track the original path for error messages + if original_path is None: + original_path = section_path + + # Only reject truly empty paths + if not section_path: + raise ValueError('yaml_config_section cannot be empty') + + # Try the full path as a literal key first (even with leading/trailing/consecutive dots) + try: + return data[section_path] + except KeyError: + pass # Not a literal key, try splitting + except TypeError: + raise TypeError( + f'yaml_config_section path "{original_path}" cannot be traversed in {self.yaml_file_path}. ' + f'An intermediate value is not a dictionary.' + ) + + # If path contains no dots, we already tried it as a literal key above + if '.' not in section_path: + raise KeyError(f'yaml_config_section key "{original_path}" not found in {self.yaml_file_path}') + + # Try progressively shorter prefixes (greedy left-to-right approach) + parts = section_path.split('.') + for i in range(len(parts) - 1, 0, -1): + prefix = '.'.join(parts[:i]) + suffix = '.'.join(parts[i:]) + + if prefix in data: + # Found the prefix as a literal key, now recursively traverse the suffix + try: + return self._traverse_nested_section(data[prefix], suffix, original_path) + except TypeError: + raise TypeError( + f'yaml_config_section path "{original_path}" cannot be traversed in {self.yaml_file_path}. ' + f'An intermediate value is not a dictionary.' + ) + + # If we get here, no match was found + raise KeyError(f'yaml_config_section key "{original_path}" not found in {self.yaml_file_path}') + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(yaml_file={self.yaml_file_path})' + + +__all__ = ['YamlConfigSettingsSource'] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py new file mode 100644 index 0000000..29f449f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py @@ -0,0 +1,108 @@ +"""Type definitions for pydantic-settings sources.""" + +from __future__ import annotations as _annotations + +import sys +from collections.abc import Sequence +from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal + +if sys.version_info >= (3, 11): + from importlib.resources.abc import Traversable as Traversable # noqa: PLC0414 (explicit re-export) +else: + from importlib.abc import Traversable as Traversable # noqa: PLC0414 (explicit re-export) + +if TYPE_CHECKING: + from pydantic._internal._dataclasses import PydanticDataclass + from pydantic.main import BaseModel + + PydanticModel = PydanticDataclass | BaseModel +else: + PydanticModel = Any + + +class EnvNoneType(str): + pass + + +class NoDecode: + """Annotation to prevent decoding of a field value.""" + + +class ForceDecode: + """Annotation to force decoding of a field value.""" + + +EnvPrefixTarget = Literal['variable', 'alias', 'all'] +DotenvType = Path | str | Sequence[Path | str] +PathType = Path | str | Sequence[Path | str] +# Config file sources (json/toml/yaml) additionally accept packaged resources exposed as a +# `Traversable` (e.g. `importlib.resources.files(...).joinpath(...)`), including non-filesystem +# ones such as a file inside a zip/wheel. +# See https://github.com/pydantic/pydantic-settings/issues/299 +ConfigFileSourceType = Path | str | Traversable | Sequence[Path | str | Traversable] +DotenvFiltering = Literal['match_prefix', 'only_existing'] +DEFAULT_PATH: PathType = Path('') + +# This is used as default value for `_env_file` in the `BaseSettings` class and +# `env_file` in `DotEnvSettingsSource` so the default can be distinguished from `None`. +# See the docstring of `BaseSettings` for more details. +ENV_FILE_SENTINEL: DotenvType = Path('') + + +class _CliSubCommand: + pass + + +class _CliPositionalArg: + pass + + +class _CliImplicitFlag: + pass + + +class _CliToggleFlag(_CliImplicitFlag): + pass + + +class _CliDualFlag(_CliImplicitFlag): + pass + + +class _CliExplicitFlag: + pass + + +class _CliUnknownArgs: + pass + + +class SecretVersion: + def __init__(self, version: str) -> None: + self.version = version + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.version!r})' + + +__all__ = [ + 'DEFAULT_PATH', + 'ENV_FILE_SENTINEL', + 'ConfigFileSourceType', + 'DotenvType', + 'EnvNoneType', + 'EnvPrefixTarget', + 'ForceDecode', + 'NoDecode', + 'PathType', + 'PydanticModel', + 'SecretVersion', + '_CliDualFlag', + '_CliExplicitFlag', + '_CliImplicitFlag', + '_CliPositionalArg', + '_CliSubCommand', + '_CliToggleFlag', + '_CliUnknownArgs', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py new file mode 100644 index 0000000..d4bec22 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py @@ -0,0 +1,376 @@ +"""Utility functions for pydantic-settings sources.""" + +from __future__ import annotations as _annotations + +import warnings +from collections import deque +from collections.abc import Mapping, Sequence +from dataclasses import is_dataclass +from enum import Enum +from typing import Any, TypedDict, TypeVar, cast, get_args, get_origin + +from pydantic import BaseModel, Json, RootModel, Secret +from pydantic._internal._utils import is_model_class +from pydantic.dataclasses import is_pydantic_dataclass +from pydantic.fields import FieldInfo +from pydantic.types import Strict +from typing_inspection import typing_objects +from typing_inspection.introspection import is_union_origin + +from ..exceptions import IncompleteFieldDefinitionWarning, SettingsError +from ..utils import _lenient_issubclass +from .types import EnvNoneType + + +class InitState(TypedDict, total=False): + """State shared between settings sources during a single settings resolution.""" + + field_info_ids: set[int] + """The `id()`s of the incomplete `FieldInfo` instances that were already warned about.""" + + +def _warn_if_field_info_incomplete(field_info: FieldInfo, field_name: str, init_state: InitState) -> None: + """Warn if the field is incomplete, i.e. its annotation contains unresolved forward references. + + An incomplete `FieldInfo` instance is unsafe to inspect — any of its attributes (annotation, + aliases, metadata, default) may rely on the unresolved annotation, so settings sources may + silently fail to resolve the field's value. Each instance is only warned about once per + `init_state`, so that a field accessed by multiple sources during a single settings + resolution doesn't emit duplicate warnings. + """ + if getattr(field_info, '_complete', True): + return + warned_ids = init_state.setdefault('field_info_ids', set()) + if id(field_info) in warned_ids: + return + warned_ids.add(id(field_info)) + warnings.warn( + f'Field {field_name!r} has an incomplete definition: its annotation contains an unresolved ' + 'forward reference, so settings sources may fail to correctly resolve its value. ' + 'Call `model_rebuild()` on the model where the field is defined, once all the referenced ' + 'types are defined.', + IncompleteFieldDefinitionWarning, + ) + + +def _get_env_var_key(key: str, case_sensitive: bool = False) -> str: + return key if case_sensitive else key.lower() + + +def _parse_env_none_str(value: str | None, parse_none_str: str | None = None) -> str | None | EnvNoneType: + return value if not (value == parse_none_str and parse_none_str is not None) else EnvNoneType(value) + + +def parse_env_vars( + env_vars: Mapping[str, str | None], + case_sensitive: bool = False, + ignore_empty: bool = False, + parse_none_str: str | None = None, +) -> Mapping[str, str | None]: + return { + _get_env_var_key(k, case_sensitive): _parse_env_none_str(v, parse_none_str) + for k, v in env_vars.items() + if not (ignore_empty and v == '') + } + + +def _substitute_typevars(tp: Any, param_map: dict[Any, Any]) -> Any: + """Substitute TypeVars in a type annotation with concrete types from param_map.""" + if isinstance(tp, TypeVar) and tp in param_map: + return param_map[tp] + args = get_args(tp) + if not args: + return tp + new_args = tuple(_substitute_typevars(arg, param_map) for arg in args) + if new_args == args: + return tp + origin = get_origin(tp) + if origin is not None: + try: + return origin[new_args] + except TypeError: + # types.UnionType and similar are not directly subscriptable, + # reconstruct using | operator + import functools + import operator + + return functools.reduce(operator.or_, new_args) + return tp + + +def _resolve_type_alias(annotation: Any) -> Any: + """Resolve a TypeAliasType to its underlying value, substituting type params if parameterized.""" + if typing_objects.is_typealiastype(annotation): + return annotation.__value__ + origin = get_origin(annotation) + if typing_objects.is_typealiastype(origin): + type_params = getattr(origin, '__type_params__', ()) + type_args = get_args(annotation) + value = origin.__value__ + if type_params and type_args: + return _substitute_typevars(value, dict(zip(type_params, type_args))) + return value + return annotation + + +def _annotation_is_complex(annotation: Any, metadata: list[Any], init_state: InitState | None = None) -> bool: + # If the model is a root model, the root annotation should be used to + # evaluate the complexity. + annotation = _resolve_type_alias(annotation) + if annotation is not None and _lenient_issubclass(annotation, RootModel) and annotation is not RootModel: + annotation = cast('type[RootModel[Any]]', annotation) + root_field = annotation.model_fields['root'] + if init_state is not None: + _warn_if_field_info_incomplete(root_field, f'{annotation.__name__}.root', init_state) + root_annotation = root_field.annotation + if root_annotation is not None: # pragma: no branch + annotation = root_annotation + + if any(isinstance(md, Json) for md in metadata): # type: ignore[misc] + return False + + origin = get_origin(annotation) + + # Check if annotation is of the form Annotated[type, metadata]. + if typing_objects.is_annotated(origin): + # Return result of recursive call on inner type. + inner, *meta = get_args(annotation) + return _annotation_is_complex(inner, meta, init_state) + + if _lenient_issubclass(origin, Secret): + return False + + return ( + _annotation_is_complex_inner(annotation) + or _annotation_is_complex_inner(origin) + or hasattr(origin, '__pydantic_core_schema__') + or hasattr(origin, '__get_pydantic_core_schema__') + ) + + +def _get_field_metadata(field: FieldInfo) -> list[Any]: + annotation = _resolve_type_alias(field.annotation) + metadata = field.metadata + origin = get_origin(annotation) + if typing_objects.is_annotated(origin): + _, *meta = get_args(annotation) + metadata += meta + return metadata + + +def _annotation_is_complex_inner(annotation: type[Any] | None) -> bool: + if _lenient_issubclass(annotation, (str, bytes)): + return False + + return _lenient_issubclass( + annotation, (BaseModel, Mapping, Sequence, tuple, set, frozenset, deque) + ) or is_dataclass(annotation) + + +def _union_is_complex(annotation: type[Any] | None, metadata: list[Any], init_state: InitState | None = None) -> bool: + """Check if a union type contains any complex types.""" + for arg in get_args(annotation): + if _annotation_is_complex(arg, metadata, init_state): + return True + # _annotation_is_complex doesn't handle bare Union types, so when an arg + # is Annotated[Union[X, Y], ...], stripping Annotated yields a bare Union + # that _annotation_is_complex can't evaluate. Recurse into it, but only + # if the Annotated metadata doesn't suppress complexity (e.g. Json). + inner = _strip_annotated(arg) + if inner is not arg: + _, *inner_meta = get_args(arg) + if any(isinstance(md, Json) for md in inner_meta): # type: ignore[misc] + continue + if is_union_origin(get_origin(inner)) and _union_is_complex(inner, metadata, init_state): + return True + return False + + +def _union_has_strict_types(annotation: type[Any] | None) -> bool: + """Check if a union type contains any strict-annotated types.""" + for arg in get_args(annotation): + if typing_objects.is_annotated(get_origin(arg)): + _, *meta = get_args(arg) + if any(isinstance(m, Strict) for m in meta): + return True + return False + + +def _annotation_contains_types( + annotation: type[Any] | None, + types: tuple[Any, ...], + is_include_origin: bool = True, + is_strip_annotated: bool = False, + is_instance: bool = False, + collect: set[Any] | None = None, +) -> bool: + """Check if a type annotation contains any of the specified types.""" + if is_strip_annotated: + annotation = _strip_annotated(annotation) + if is_include_origin is True: + origin = get_origin(annotation) + if origin in types: + if collect is None: + return True + collect.add(annotation) + if is_instance and any(isinstance(origin, type_) for type_ in types): + if collect is None: + return True + collect.add(annotation) + for type_ in get_args(annotation): + if ( + _annotation_contains_types( + type_, + types, + is_include_origin=True, + is_strip_annotated=is_strip_annotated, + is_instance=is_instance, + collect=collect, + ) + and collect is None + ): + return True + if is_instance and any(isinstance(annotation, type_) for type_ in types): + if collect is None: + return True + collect.add(annotation) + if annotation in types: + if collect is not None: + collect.add(annotation) + return True + return False + + +def _strip_annotated(annotation: Any) -> Any: + if typing_objects.is_annotated(get_origin(annotation)): + return annotation.__origin__ + else: + return annotation + + +def _annotation_enum_val_to_name(annotation: type[Any] | None, value: Any) -> str | None: + for type_ in (annotation, get_origin(annotation)): + if _lenient_issubclass(type_, Enum): + enum_type = cast('type[Enum]', type_) + if value in enum_type.__members__.values(): + return enum_type(value).name + for arg in get_args(annotation): + enum_name = _annotation_enum_val_to_name(arg, value) + if enum_name is not None: + return enum_name + return None + + +def _annotation_enum_name_to_val(annotation: type[Any] | None, name: Any) -> Any: + for type_ in (annotation, get_origin(annotation)): + if _lenient_issubclass(type_, Enum): + enum_type = cast('type[Enum]', type_) + if name in enum_type.__members__: + return enum_type[name] + for arg in get_args(annotation): + enum_val = _annotation_enum_name_to_val(arg, name) + if enum_val is not None: + return enum_val + return None + + +def _literal_has_numeric_enum(annotation: type[Any] | None) -> bool: + """Check if annotation is a Literal type containing numeric Enum members (IntEnum, (int, Enum), (float, Enum)).""" + if typing_objects.is_literal(get_origin(annotation)): + return any(isinstance(arg, (int, float)) and isinstance(arg, Enum) for arg in get_args(annotation)) + # Handle Annotated wrapping, e.g. Annotated[Literal[IntEnum.member], Field(...)] + if typing_objects.is_annotated(get_origin(annotation)): + inner = get_args(annotation)[0] + return _literal_has_numeric_enum(inner) + # Handle Union/Optional wrapping, e.g. Optional[Literal[IntEnum.member]] + if is_union_origin(get_origin(annotation)): + return any(_literal_has_numeric_enum(arg) for arg in get_args(annotation)) + return False + + +def _get_model_fields(model_cls: type[Any]) -> dict[str, FieldInfo]: + """Get fields from a pydantic model or dataclass.""" + + if is_pydantic_dataclass(model_cls) and hasattr(model_cls, '__pydantic_fields__'): + return model_cls.__pydantic_fields__ + if is_model_class(model_cls): + return model_cls.model_fields + raise SettingsError(f'Error: {model_cls.__name__} is not subclass of BaseModel or pydantic.dataclasses.dataclass') + + +def _get_alias_names( + field_name: str, + field_info: FieldInfo, + alias_path_args: dict[str, int | None] | None = None, + case_sensitive: bool = True, + populate_by_name: bool = False, +) -> tuple[tuple[str, ...], bool]: + """Get alias names for a field, handling alias paths and case sensitivity.""" + from pydantic import AliasChoices, AliasPath + + alias_names: list[str] = [] + is_alias_path_only: bool = True + if not any((field_info.alias, field_info.validation_alias)): + alias_names += [field_name] + is_alias_path_only = False + else: + new_alias_paths: list[AliasPath] = [] + for alias in (field_info.alias, field_info.validation_alias): + if alias is None: + continue + elif isinstance(alias, str): + alias_names.append(alias) + is_alias_path_only = False + elif isinstance(alias, AliasChoices): + for name in alias.choices: + if isinstance(name, str): + alias_names.append(name) + is_alias_path_only = False + else: + new_alias_paths.append(name) + else: + new_alias_paths.append(alias) + for alias_path in new_alias_paths: + name = cast(str, alias_path.path[0]) + name = name.lower() if not case_sensitive else name + if alias_path_args is not None: + alias_path_args[name] = ( + alias_path.path[1] if len(alias_path.path) > 1 and isinstance(alias_path.path[1], int) else None + ) + if not alias_names and is_alias_path_only: + alias_names.append(name) + if populate_by_name and field_name not in alias_names: + alias_names.append(field_name) + is_alias_path_only = False + if not case_sensitive: + alias_names = [alias_name.lower() for alias_name in alias_names] + return tuple(dict.fromkeys(alias_names)), is_alias_path_only + + +def _is_function(obj: Any) -> bool: + """Check if an object is a function.""" + from types import BuiltinFunctionType, FunctionType + + return isinstance(obj, (FunctionType, BuiltinFunctionType)) + + +__all__ = [ + 'InitState', + '_annotation_contains_types', + '_annotation_enum_name_to_val', + '_annotation_enum_val_to_name', + '_annotation_is_complex', + '_annotation_is_complex_inner', + '_get_alias_names', + '_get_env_var_key', + '_get_model_fields', + '_is_function', + '_literal_has_numeric_enum', + '_parse_env_none_str', + '_resolve_type_alias', + '_strip_annotated', + '_union_has_strict_types', + '_union_is_complex', + '_warn_if_field_info_incomplete', + 'parse_env_vars', +] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py new file mode 100644 index 0000000..7bd6fe8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py @@ -0,0 +1,55 @@ +import logging +import os +import types +from pathlib import Path +from typing import Any, _Final, _GenericAlias, get_origin # type: ignore [attr-defined] + +logger = logging.getLogger('pydantic_settings') + +_DEBUG_ENV_VAR = 'PYDANTIC_SETTINGS_DEBUG' + + +def _settings_debug_enabled() -> bool: + """Whether settings source debugging is enabled via the `PYDANTIC_SETTINGS_DEBUG` env var.""" + return os.environ.get(_DEBUG_ENV_VAR, '').strip().lower() in ('1', 'true', 'yes', 'on') + + +_PATH_TYPE_LABELS = { + Path.is_dir: 'directory', + Path.is_file: 'file', + Path.is_mount: 'mount point', + Path.is_symlink: 'symlink', + Path.is_block_device: 'block device', + Path.is_char_device: 'char device', + Path.is_fifo: 'FIFO', + Path.is_socket: 'socket', +} + + +def path_type_label(p: Path) -> str: + """ + Find out what sort of thing a path is. + """ + assert p.exists(), 'path does not exist' + for method, name in _PATH_TYPE_LABELS.items(): + if method(p): + return name + + return 'unknown' # pragma: no cover + + +# TODO remove and replace usage by `isinstance(cls, type) and issubclass(cls, class_or_tuple)` +# once we drop support for Python 3.10. +def _lenient_issubclass(cls: Any, class_or_tuple: Any) -> bool: # pragma: no cover + try: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) + except TypeError: + if get_origin(cls) is not None: + # Up until Python 3.10, isinstance(, type) is True + # (e.g. list[int]) + return False + raise + + +_WithArgsTypes = (_GenericAlias, types.GenericAlias, types.UnionType) +_typing_base: Any = _Final # pyright: ignore[reportAttributeAccessIssue] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py new file mode 100644 index 0000000..b4e42de --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py @@ -0,0 +1 @@ +VERSION = '2.15.0' diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA new file mode 100644 index 0000000..0db0043 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA @@ -0,0 +1,93 @@ +Metadata-Version: 2.4 +Name: PyJWT +Version: 2.14.0 +Summary: JSON Web Token implementation in Python +Author-email: Jose Padilla +License-Expression: MIT +Project-URL: Homepage, https://github.com/jpadilla/pyjwt +Keywords: json,jwt,security,signing,token,web +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Utilities +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: AUTHORS.rst +Requires-Dist: typing_extensions>=4.0; python_version < "3.11" +Provides-Extra: crypto +Requires-Dist: cryptography>=3.4.0; extra == "crypto" +Dynamic: license-file + +PyJWT +===== + +.. image:: https://github.com/jpadilla/pyjwt/workflows/CI/badge.svg + :target: https://github.com/jpadilla/pyjwt/actions?query=workflow%3ACI + +.. image:: https://img.shields.io/pypi/v/pyjwt.svg + :target: https://pypi.python.org/pypi/pyjwt + +.. image:: https://codecov.io/gh/jpadilla/pyjwt/branch/master/graph/badge.svg + :target: https://codecov.io/gh/jpadilla/pyjwt + +.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=stable + :target: https://pyjwt.readthedocs.io/en/stable/ + +A Python implementation of `RFC 7519 `_. Original implementation was written by `@progrium `_. + +Sponsor +------- + +.. |auth0-logo| image:: https://github.com/user-attachments/assets/ee98379e-ee76-4bcb-943a-e25c4ea6d174 + :width: 160px + ++--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/signup `_. | ++--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +Installing +---------- + +Install with **pip**: + +.. code-block:: console + + $ pip install PyJWT + + +Usage +----- + +.. code-block:: pycon + + >>> import jwt + >>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") + >>> print(encoded) + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg + >>> jwt.decode(encoded, "secret", algorithms=["HS256"]) + {'some': 'payload'} + +Documentation +------------- + +View the full docs online at https://pyjwt.readthedocs.io/en/stable/ + + +Tests +----- + +You can run tests from the project root after cloning with: + +.. code-block:: console + + $ tox diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD new file mode 100644 index 0000000..478b91f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD @@ -0,0 +1,19 @@ +jwt/__init__.py,sha256=BWR5W0NaQShI2w1ngQD9E4qjCWB16MzjZHUcfkJ9YP0,1805 +jwt/algorithms.py,sha256=vsUpnHXbHUvXpfnxw4LIX8ZIAlcVt-UisyDhYdOutVo,40244 +jwt/api_jwk.py,sha256=XfmRO9SEb-fSV7nADDn4vGEcdfgydMmK5QM4p8sgxj8,6328 +jwt/api_jws.py,sha256=oMV41OCcgjr-AZ7T4LBvsYTGZf80z-QExoIprjJyJp0,17421 +jwt/api_jwt.py,sha256=iP4AZ7WXxyarU6SZYyB4IqGo79vBOGAxRr1DJGAM2hg,22244 +jwt/exceptions.py,sha256=NUD0h2A0MiHV6U-TPt_Ar0prnAHxyJJ0i6LHIWzd8VQ,2375 +jwt/help.py,sha256=HB3ZsrJVclR0XY-sDH-Lvl5K7ae5QidGiU6WyTAnYhs,1784 +jwt/jwk_set_cache.py,sha256=A395RCkJUDxKLj_M6RZUkS405AeDepEwztU4k4xDSoU,961 +jwt/jwks_client.py,sha256=ALpi3iPIyA2Q-x9zU5qPvzLEepWdjQ-69R5GgAxBmrc,11764 +jwt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jwt/types.py,sha256=R46Bkno1V08fquxnVr71AvW6uEPilHJlqkgX6ObZFnw,3174 +jwt/utils.py,sha256=5OPU-vCxXEegSZQAiOJWQ0tlZpIOxxY8FyzkdoTCmsA,4067 +jwt/warnings.py,sha256=iQTvP-NPDQA-tuhPhjifEkYRqMUgte99iOPMJc_DLJk,330 +pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst,sha256=klzkNGECnu2_VY7At89_xLBF3vUSDruXk3xwgUBxzwc,322 +pyjwt-2.14.0.dist-info/licenses/LICENSE,sha256=eXp6ICMdTEM-nxkR2xcx0GtYKLmPSZgZoDT3wPVvXOU,1085 +pyjwt-2.14.0.dist-info/METADATA,sha256=uFTrRhqu74BGlhIhB4asH79tEGTx1Rr17iYZt7vdsbo,3449 +pyjwt-2.14.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91 +pyjwt-2.14.0.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4 +pyjwt-2.14.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL new file mode 100644 index 0000000..13013d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst new file mode 100644 index 0000000..88e2b6a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst @@ -0,0 +1,7 @@ +Authors +======= + +``pyjwt`` is currently written and maintained by `Jose Padilla `_. +Originally written and maintained by `Jeff Lindsay `_. + +A full list of contributors can be found on GitHub’s `overview `_. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..fd0ecbc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 José Padilla + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt new file mode 100644 index 0000000..27ccc9b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt @@ -0,0 +1 @@ +jwt diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA new file mode 100644 index 0000000..9bc77db --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA @@ -0,0 +1,820 @@ +Metadata-Version: 2.4 +Name: python-dotenv +Version: 1.2.3 +Summary: Read key-value pairs from a .env file and set them as environment variables +Author-email: Saurabh Kumar +License: BSD-3-Clause +Project-URL: Source, https://github.com/theskumar/python-dotenv +Keywords: environment variables,deployments,settings,env,dotenv,configurations,python +Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Classifier: Environment :: Web Environment +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: cli +Requires-Dist: click>=5.0; extra == "cli" +Dynamic: license-file + +# python-dotenv + +[![Build Status][build_status_badge]][build_status_link] +[![PyPI version][pypi_badge]][pypi_link] + +python-dotenv reads key-value pairs from a `.env` file and can set them as +environment variables. It helps in the development of applications following the +[12-factor](https://12factor.net/) principles. + +- [Getting Started](#getting-started) +- [Other Use Cases](#other-use-cases) + - [Load configuration without altering the environment](#load-configuration-without-altering-the-environment) + - [Parse configuration as a stream](#parse-configuration-as-a-stream) + - [Load .env files in IPython](#load-env-files-in-ipython) +- [Command-line Interface](#command-line-interface) +- [File format](#file-format) + - [Multiline values](#multiline-values) + - [Variable expansion](#variable-expansion) +- [Related Projects](#related-projects) +- [Acknowledgements](#acknowledgements) + +## Getting Started + +```shell +pip install python-dotenv +``` + +If your application takes its configuration from environment variables, like a +12-factor application, launching it in development is not very practical because +you have to set those environment variables yourself. + +To help you with that, you can add python-dotenv to your application to make it +load the configuration from a `.env` file when it is present (e.g. in +development) while remaining configurable via the environment: + +```python +from dotenv import load_dotenv + +load_dotenv() # reads variables from a .env file and sets them in os.environ + +# Code of your application, which uses environment variables (e.g. from `os.environ` or +# `os.getenv`) as if they came from the actual environment. +``` + +By default, `load_dotenv()` will: + +- Look for a `.env` file in the same directory as the Python script (or higher up the directory tree). +- Read each key-value pair and add it to `os.environ`. +- **Not override** existing environment variables (`override=False`). Pass `override=True` to override existing variables. + +To configure the development environment, add a `.env` in the root directory of +your project: + +``` +. +├── .env +└── foo.py +``` + +The syntax of `.env` files supported by python-dotenv is similar to that of +Bash: + +```bash +# Development settings +DOMAIN=example.org +ADMIN_EMAIL=admin@${DOMAIN} +ROOT_URL=${DOMAIN}/app +``` + +If you use variables in values, ensure they are surrounded with `{` and `}`, +like `${DOMAIN}`, as bare variables such as `$DOMAIN` are not expanded. + +You will probably want to add `.env` to your `.gitignore`, especially if it +contains secrets like a password. + +See the section "[File format](#file-format)" below for more information about what you can write in a `.env` file. + +## Other Use Cases + +### Load configuration without altering the environment + +The function `dotenv_values` works more or less the same way as `load_dotenv`, +except it doesn't touch the environment, it just returns a `dict` with the +values parsed from the `.env` file. + +```python +from dotenv import dotenv_values + +config = dotenv_values(".env") # config = {"USER": "foo", "EMAIL": "foo@example.org"} +``` + +This notably enables advanced configuration management: + +```python +import os +from dotenv import dotenv_values + +config = { + **dotenv_values(".env.shared"), # load shared development variables + **dotenv_values(".env.secret"), # load sensitive variables + **os.environ, # override loaded values with environment variables +} +``` + +### Parse configuration as a stream + +`load_dotenv` and `dotenv_values` accept [streams][python_streams] via their +`stream` argument. It is thus possible to load the variables from sources other +than the filesystem (e.g. the network). + +```python +from io import StringIO + +from dotenv import load_dotenv + +config = StringIO("USER=foo\nEMAIL=foo@example.org") +load_dotenv(stream=config) +``` + +### Load .env files in IPython + +You can use dotenv in IPython. By default, it will use `find_dotenv` to search for a +`.env` file: + +```python +%load_ext dotenv +%dotenv +``` + +You can also specify a path: + +```python +%dotenv relative/or/absolute/path/to/.env +``` + +Optional flags: + +- `-o` to override existing variables. +- `-v` for increased verbosity. + +### Disable load_dotenv + +Set `PYTHON_DOTENV_DISABLED=1` to disable `load_dotenv()` from loading .env +files or streams. Useful when you can't modify third-party package calls or in +production. + +## Command-line Interface + +A CLI interface `dotenv` is also included, which helps you manipulate the `.env` +file without manually opening it. + +```shell +$ pip install "python-dotenv[cli]" +$ dotenv set USER foo +$ dotenv set EMAIL foo@example.org +$ dotenv list +USER=foo +EMAIL=foo@example.org +$ dotenv list --format=json +{ + "USER": "foo", + "EMAIL": "foo@example.org" +} +$ dotenv run -- python foo.py +``` + +Run `dotenv --help` for more information about the options and subcommands. + +## File format + +The format is not formally specified and still improves over time. That being +said, `.env` files should mostly look like Bash files. Reading from FIFOs (named +pipes) on Unix systems is also supported. + +Keys can be unquoted or single-quoted. Values can be unquoted, single- or +double-quoted. Spaces before and after keys, equal signs, and values are +ignored. Values can be followed by a comment. Lines can start with the `export` +directive, which does not affect their interpretation. + +Allowed escape sequences: + +- in single-quoted values: `\\`, `\'` +- in double-quoted values: `\\`, `\'`, `\"`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v` + +### Multiline values + +It is possible for single- or double-quoted values to span multiple lines. The +following examples are equivalent: + +```bash +FOO="first line +second line" +``` + +```bash +FOO="first line\nsecond line" +``` + +### Variable without a value + +A variable can have no value: + +```bash +FOO +``` + +It results in `dotenv_values` associating that variable name with the value +`None` (e.g. `{"FOO": None}`. `load_dotenv`, on the other hand, simply ignores +such variables. + +This shouldn't be confused with `FOO=`, in which case the variable is associated +with the empty string. + +### Variable expansion + +python-dotenv can interpolate variables using POSIX variable expansion. + +With `load_dotenv(override=True)` or `dotenv_values()`, the value of a variable +is the first of the values defined in the following list: + +- Value of that variable in the `.env` file. +- Value of that variable in the environment. +- Default value, if provided. +- Empty string. + +With `load_dotenv(override=False)`, the value of a variable is the first of the +values defined in the following list: + +- Value of that variable in the environment. +- Value of that variable in the `.env` file. +- Default value, if provided. +- Empty string. + +## Related Projects + +- [environs](https://github.com/sloria/environs) +- [Honcho](https://github.com/nickstenning/honcho) +- [dump-env](https://github.com/sobolevn/dump-env) +- [dynaconf](https://github.com/dynaconf/dynaconf) +- [parse_it](https://github.com/naorlivne/parse_it) +- [django-dotenv](https://github.com/jpadilla/django-dotenv) +- [django-environ](https://github.com/joke2k/django-environ) +- [python-decouple](https://github.com/HBNetwork/python-decouple) +- [django-configuration](https://github.com/jezdez/django-configurations) + +## Acknowledgements + +This project is currently maintained by [Saurabh Kumar][saurabh-homepage] and +[Bertrand Bonnefoy-Claudet][gh-bbc2] and would not have been possible without +the support of these [awesome people][contributors]. + +[gh-bbc2]: https://github.com/bbc2 +[saurabh-homepage]: https://saurabh-kumar.com +[pypi_link]: https://badge.fury.io/py/python-dotenv +[pypi_badge]: https://badge.fury.io/py/python-dotenv.svg +[python_streams]: https://docs.python.org/3/library/io.html +[contributors]: https://github.com/theskumar/python-dotenv/graphs/contributors +[build_status_link]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml +[build_status_badge]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml/badge.svg + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this +project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.2.3] - 2026-08-16 + +### Fixed + +- Strip a leading UTF-8 BOM from `.env` file contents so the first variable is no longer silently lost when the file is saved with BOM (e.g. by some JetBrains IDEs on Windows) by [@h1whelan] in [#640] +- `set_key` now escapes backslashes, so values containing them (Windows paths, regular expressions) survive a write/read round-trip. Quoted values ending in an escaped backslash are no longer mis-parsed as an escaped quote, which used to swallow the following lines by [@dchaudhari7177] in [#680] +- `dotenv run` now prints a friendly error instead of a traceback when no command is given by [@bbc2] in [#606] +- Cache the parsed result for empty `.env` files so repeated `dotenv_values`/`load_dotenv` calls no longer re-read the file by [@ReinerBRO] in [#638] + +## [1.2.2] - 2026-03-01 + +### Added + +- Support for Python 3.14, including the free-threaded (3.14t) build. (#588) + +### Changed + +- The `dotenv run` command now forwards flags directly to the specified command by [@bbc2] in [#607] +- Improved documentation clarity regarding override behavior and the reference page. +- Updated PyPy support to version 3.11. +- Documentation for FIFO file support. +- Dropped Support for Python 3.9. + +### Fixed + +- Improved `set_key` and `unset_key` behavior when interacting with symlinks by [@bbc2] in [790c5c0] +- Corrected the license specifier and added missing Python 3.14 classifiers in package metadata by [@JYOuyang] in [#590] + +### Breaking Changes + +- `dotenv.set_key` and `dotenv.unset_key` used to follow symlinks in some + situations. This is no longer the case. For that behavior to be restored in + all cases, `follow_symlinks=True` should be used. + +- In the CLI, `set` and `unset` used to follow symlinks in some situations. This + is no longer the case. + +- `dotenv.set_key`, `dotenv.unset_key` and the CLI commands `set` and `unset` + used to reset the file mode of the modified .env file to `0o600` in some + situations. This is no longer the case: The original mode of the file is now + preserved. Is the file needed to be created or wasn't a regular file, mode + `0o600` is used. + +## [1.2.1] - 2025-10-26 + +- Move more config to `pyproject.toml`, removed `setup.cfg` +- Add support for reading `.env` from FIFOs (Unix) by [@sidharth-sudhir] in [#586] + +## [1.2.0] - 2025-10-26 + +- Upgrade build system to use PEP 517 & PEP 518 to use `build` and `pyproject.toml` by [@EpicWink] in [#583] +- Add support for Python 3.14 by [@23f3001135] in [#579] +- Add support for disabling of `load_dotenv()` using `PYTHON_DOTENV_DISABLED` env var. by [@matthewfranglen] in [#569] + +## [1.1.1] - 2025-06-24 + +### Fixed + +- CLI: Ensure `find_dotenv` work reliably on python 3.13 by [@theskumar] in [#563] +- CLI: revert the use of execvpe on Windows by [@wrongontheinternet] in [#566] + +## [1.1.0] - 2025-03-25 + +### Added + +- Add support for python 3.13 +- Enhance `dotenv run`, switch to `execvpe` for better resource management and signal handling ([#523]) by [@eekstunt] + +### Fixed + +- `find_dotenv` and `load_dotenv` now correctly looks up at the current directory when running in debugger or pdb ([#553] by [@randomseed42]) + +### Misc + +- Drop support for Python 3.8 + +## [1.0.1] - 2024-01-23 + +### Fixed + +- Gracefully handle code which has been imported from a zipfile ([#456] by [@samwyma]) +- Allow modules using `load_dotenv` to be reloaded when launched in a separate thread ([#497] by [@freddyaboulton]) +- Fix file not closed after deletion, handle error in the rewrite function ([#469] by [@Qwerty-133]) + +### Misc + +- Use pathlib.Path in tests ([#466] by [@eumiro]) +- Fix year in release date in changelog.md ([#454] by [@jankislinger]) +- Use https in README links ([#474] by [@Nicals]) + +## [1.0.0] - 2023-02-24 + +### Fixed + +- Drop support for python 3.7, add python 3.12-dev (#449 by [@theskumar]) +- Handle situations where the cwd does not exist. (#446 by [@jctanner]) + +## [0.21.1] - 2023-01-21 + +### Added + +- Use Python 3.11 non-beta in CI (#438 by [@bbc2]) +- Modernize variables code (#434 by [@Nougat-Waffle]) +- Modernize main.py and parser.py code (#435 by [@Nougat-Waffle]) +- Improve conciseness of cli.py and **init**.py (#439 by [@Nougat-Waffle]) +- Improve error message for `get` and `list` commands when env file can't be opened (#441 by [@bbc2]) +- Updated License to align with BSD OSI template (#433 by [@lsmith77]) + +### Fixed + +- Fix Out-of-scope error when "dest" variable is undefined (#413 by [@theGOTOguy]) +- Fix IPython test warning about deprecated `magic` (#440 by [@bbc2]) +- Fix type hint for dotenv_path var, add StrPath alias (#432 by [@eaf]) + +## [0.21.0] - 2022-09-03 + +### Added + +- CLI: add support for invocations via 'python -m'. (#395 by [@theskumar]) +- `load_dotenv` function now returns `False`. (#388 by [@larsks]) +- CLI: add --format= option to list command. (#407 by [@sammck]) + +### Fixed + +- Drop Python 3.5 and 3.6 and upgrade GA (#393 by [@eggplants]) +- Use `open` instead of `io.open`. (#389 by [@rabinadk1]) +- Improve documentation for variables without a value (#390 by [@bbc2]) +- Add `parse_it` to Related Projects (#410 by [@naorlivne]) +- Update README.md (#415 by [@harveer07]) +- Improve documentation with direct use of MkDocs (#398 by [@bbc2]) + +## [0.20.0] - 2022-03-24 + +### Added + +- Add `encoding` (`Optional[str]`) parameter to `get_key`, `set_key` and `unset_key`. + (#379 by [@bbc2]) + +### Fixed + +- Use dict to specify the `entry_points` parameter of `setuptools.setup` (#376 by + [@mgorny]). +- Don't build universal wheels (#387 by [@bbc2]). + +## [0.19.2] - 2021-11-11 + +### Fixed + +- In `set_key`, add missing newline character before new entry if necessary. (#361 by + [@bbc2]) + +## [0.19.1] - 2021-08-09 + +### Added + +- Add support for Python 3.10. (#359 by [@theskumar]) + +## [0.19.0] - 2021-07-24 + +### Changed + +- Require Python 3.5 or a later version. Python 2 and 3.4 are no longer supported. (#341 + by [@bbc2]). + +### Added + +- The `dotenv_path` argument of `set_key` and `unset_key` now has a type of `Union[str, +os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]). +- The `stream` argument of `load_dotenv` and `dotenv_values` can now be a text stream + (`IO[str]`), which includes values like `io.StringIO("foo")` and `open("file.env", +"r")` (#348 by [@bbc2]). + +## [0.18.0] - 2021-06-20 + +### Changed + +- Raise `ValueError` if `quote_mode` isn't one of `always`, `auto` or `never` in + `set_key` (#330 by [@bbc2]). +- When writing a value to a .env file with `set_key` or `dotenv set ` (#330 + by [@bbc2]): + - Use single quotes instead of double quotes. + - Don't strip surrounding quotes. + - In `auto` mode, don't add quotes if the value is only made of alphanumeric characters + (as determined by `string.isalnum`). + +## [0.17.1] - 2021-04-29 + +### Fixed + +- Fixed tests for build environments relying on `PYTHONPATH` (#318 by [@befeleme]). + +## [0.17.0] - 2021-04-02 + +### Changed + +- Make `dotenv get ` only show the value, not `key=value` (#313 by [@bbc2]). + +### Added + +- Add `--override`/`--no-override` option to `dotenv run` (#312 by [@zueve] and [@bbc2]). + +## [0.16.0] - 2021-03-27 + +### Changed + +- The default value of the `encoding` parameter for `load_dotenv` and `dotenv_values` is + now `"utf-8"` instead of `None` (#306 by [@bbc2]). +- Fix resolution order in variable expansion with `override=False` (#287 by [@bbc2]). + +## [0.15.0] - 2020-10-28 + +### Added + +- Add `--export` option to `set` to make it prepend the binding with `export` (#270 by + [@jadutter]). + +### Changed + +- Make `set` command create the `.env` file in the current directory if no `.env` file was + found (#270 by [@jadutter]). + +### Fixed + +- Fix potentially empty expanded value for duplicate key (#260 by [@bbc2]). +- Fix import error on Python 3.5.0 and 3.5.1 (#267 by [@gongqingkui]). +- Fix parsing of unquoted values containing several adjacent space or tab characters + (#277 by [@bbc2], review by [@x-yuri]). + +## [0.14.0] - 2020-07-03 + +### Changed + +- Privilege definition in file over the environment in variable expansion (#256 by + [@elbehery95]). + +### Fixed + +- Improve error message for when file isn't found (#245 by [@snobu]). +- Use HTTPS URL in package meta data (#251 by [@ekohl]). + +## [0.13.0] - 2020-04-16 + +### Added + +- Add support for a Bash-like default value in variable expansion (#248 by [@bbc2]). + +## [0.12.0] - 2020-02-28 + +### Changed + +- Use current working directory to find `.env` when bundled by PyInstaller (#213 by + [@gergelyk]). + +### Fixed + +- Fix escaping of quoted values written by `set_key` (#236 by [@bbc2]). +- Fix `dotenv run` crashing on environment variables without values (#237 by [@yannham]). +- Remove warning when last line is empty (#238 by [@bbc2]). + +## [0.11.0] - 2020-02-07 + +### Added + +- Add `interpolate` argument to `load_dotenv` and `dotenv_values` to disable interpolation + (#232 by [@ulyssessouza]). + +### Changed + +- Use logging instead of warnings (#231 by [@bbc2]). + +### Fixed + +- Fix installation in non-UTF-8 environments (#225 by [@altendky]). +- Fix PyPI classifiers (#228 by [@bbc2]). + +## [0.10.5] - 2020-01-19 + +### Fixed + +- Fix handling of malformed lines and lines without a value (#222 by [@bbc2]): + - Don't print warning when key has no value. + - Reject more malformed lines (e.g. "A: B", "a='b',c"). +- Fix handling of lines with just a comment (#224 by [@bbc2]). + +## [0.10.4] - 2020-01-17 + +### Added + +- Make typing optional (#179 by [@techalchemy]). +- Print a warning on malformed line (#211 by [@bbc2]). +- Support keys without a value (#220 by [@ulyssessouza]). + +## 0.10.3 + +- Improve interactive mode detection ([@andrewsmith])([#183]). +- Refactor parser to fix parsing inconsistencies ([@bbc2])([#170]). + - Interpret escapes as control characters only in double-quoted strings. + - Interpret `#` as start of comment only if preceded by whitespace. + +## 0.10.2 + +- Add type hints and expose them to users ([@qnighy])([#172]) +- `load_dotenv` and `dotenv_values` now accept an `encoding` parameter, defaults to `None` + ([@theskumar])([@earlbread])([#161]) +- Fix `str`/`unicode` inconsistency in Python 2: values are always `str` now. ([@bbc2])([#121]) +- Fix Unicode error in Python 2, introduced in 0.10.0. ([@bbc2])([#176]) + +## 0.10.1 + +- Fix parsing of variable without a value ([@asyncee])([@bbc2])([#158]) + +## 0.10.0 + +- Add support for UTF-8 in unquoted values ([@bbc2])([#148]) +- Add support for trailing comments ([@bbc2])([#148]) +- Add backslashes support in values ([@bbc2])([#148]) +- Add support for newlines in values ([@bbc2])([#148]) +- Force environment variables to str with Python2 on Windows ([@greyli]) +- Drop Python 3.3 support ([@greyli]) +- Fix stderr/-out/-in redirection ([@venthur]) + +## 0.9.0 + +- Add `--version` parameter to cli ([@venthur]) +- Enable loading from current directory ([@cjauvin]) +- Add 'dotenv run' command for calling arbitrary shell script with .env ([@venthur]) + +## 0.8.1 + +- Add tests for docs ([@Flimm]) +- Make 'cli' support optional. Use `pip install python-dotenv[cli]`. ([@theskumar]) + +## 0.8.0 + +- `set_key` and `unset_key` only modified the affected file instead of + parsing and re-writing file, this causes comments and other file + entact as it is. +- Add support for `export` prefix in the line. +- Internal refractoring ([@theskumar]) +- Allow `load_dotenv` and `dotenv_values` to work with `StringIO())` ([@alanjds])([@theskumar])([#78]) + +## 0.7.1 + +- Remove hard dependency on iPython ([@theskumar]) + +## 0.7.0 + +- Add support to override system environment variable via .env. + ([@milonimrod](https://github.com/milonimrod)) + ([\#63](https://github.com/theskumar/python-dotenv/issues/63)) +- Disable ".env not found" warning by default + ([@maxkoryukov](https://github.com/maxkoryukov)) + ([\#57](https://github.com/theskumar/python-dotenv/issues/57)) + +## 0.6.5 + +- Add support for special characters `\`. + ([@pjona](https://github.com/pjona)) + ([\#60](https://github.com/theskumar/python-dotenv/issues/60)) + +## 0.6.4 + +- Fix issue with single quotes ([@Flimm]) + ([\#52](https://github.com/theskumar/python-dotenv/issues/52)) + +## 0.6.3 + +- Handle unicode exception in setup.py + ([\#46](https://github.com/theskumar/python-dotenv/issues/46)) + +## 0.6.2 + +- Fix dotenv list command ([@ticosax](https://github.com/ticosax)) +- Add iPython Support + ([@tillahoffmann](https://github.com/tillahoffmann)) + +## 0.6.0 + +- Drop support for Python 2.6 +- Handle escaped characters and newlines in quoted values. (Thanks + [@iameugenejo](https://github.com/iameugenejo)) +- Remove any spaces around unquoted key/value. (Thanks + [@paulochf](https://github.com/paulochf)) +- Added POSIX variable expansion. (Thanks + [@hugochinchilla](https://github.com/hugochinchilla)) + +## 0.5.1 + +- Fix `find_dotenv` - it now start search from the file where this + function is called from. + +## 0.5.0 + +- Add `find_dotenv` method that will try to find a `.env` file. + (Thanks [@isms](https://github.com/isms)) + +## 0.4.0 + +- cli: Added `-q/--quote` option to control the behaviour of quotes + around values in `.env`. (Thanks + [@hugochinchilla](https://github.com/hugochinchilla)). +- Improved test coverage. + + + +[#78]: https://github.com/theskumar/python-dotenv/issues/78 +[#121]: https://github.com/theskumar/python-dotenv/issues/121 +[#148]: https://github.com/theskumar/python-dotenv/issues/148 +[#158]: https://github.com/theskumar/python-dotenv/issues/158 +[#170]: https://github.com/theskumar/python-dotenv/issues/170 +[#172]: https://github.com/theskumar/python-dotenv/issues/172 +[#176]: https://github.com/theskumar/python-dotenv/issues/176 +[#183]: https://github.com/theskumar/python-dotenv/issues/183 +[#359]: https://github.com/theskumar/python-dotenv/issues/359 +[#469]: https://github.com/theskumar/python-dotenv/issues/469 +[#456]: https://github.com/theskumar/python-dotenv/issues/456 +[#466]: https://github.com/theskumar/python-dotenv/issues/466 +[#454]: https://github.com/theskumar/python-dotenv/issues/454 +[#474]: https://github.com/theskumar/python-dotenv/issues/474 +[#523]: https://github.com/theskumar/python-dotenv/issues/523 +[#553]: https://github.com/theskumar/python-dotenv/issues/553 +[#569]: https://github.com/theskumar/python-dotenv/issues/569 +[#583]: https://github.com/theskumar/python-dotenv/issues/583 +[#586]: https://github.com/theskumar/python-dotenv/issues/586 +[#590]: https://github.com/theskumar/python-dotenv/issues/590 +[#607]: https://github.com/theskumar/python-dotenv/issues/607 +[#588]: https://github.com/theskumar/python-dotenv/issues/588 +[#579]: https://github.com/theskumar/python-dotenv/pull/579 +[#566]: https://github.com/theskumar/python-dotenv/pull/566 +[#563]: https://github.com/theskumar/python-dotenv/pull/563 +[#497]: https://github.com/theskumar/python-dotenv/pull/497 +[#161]: https://github.com/theskumar/python-dotenv/issues/161 +[#606]: https://github.com/theskumar/python-dotenv/pull/606 +[#638]: https://github.com/theskumar/python-dotenv/pull/638 +[#640]: https://github.com/theskumar/python-dotenv/pull/640 +[#680]: https://github.com/theskumar/python-dotenv/pull/680 +[790c5c0]: https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311 + + + +[@23f3001135]: https://github.com/23f3001135 +[@EpicWink]: https://github.com/EpicWink +[@Flimm]: https://github.com/Flimm +[@Nicals]: https://github.com/Nicals +[@Nougat-Waffle]: https://github.com/Nougat-Waffle +[@Qwerty-133]: https://github.com/Qwerty-133 +[@ReinerBRO]: https://github.com/ReinerBRO +[@alanjds]: https://github.com/alanjds +[@altendky]: https://github.com/altendky +[@andrewsmith]: https://github.com/andrewsmith +[@asyncee]: https://github.com/asyncee +[@bbc2]: https://github.com/bbc2 +[@befeleme]: https://github.com/befeleme +[@cjauvin]: https://github.com/cjauvin +[@dchaudhari7177]: https://github.com/dchaudhari7177 +[@eaf]: https://github.com/eaf +[@earlbread]: https://github.com/earlbread +[@eekstunt]: https://github.com/eekstunt +[@eggplants]: https://github.com/eggplants +[@ekohl]: https://github.com/ekohl +[@elbehery95]: https://github.com/elbehery95 +[@eumiro]: https://github.com/eumiro +[@freddyaboulton]: https://github.com/freddyaboulton +[@gergelyk]: https://github.com/gergelyk +[@gongqingkui]: https://github.com/gongqingkui +[@greyli]: https://github.com/greyli +[@h1whelan]: https://github.com/h1whelan +[@harveer07]: https://github.com/harveer07 +[@jadutter]: https://github.com/jadutter +[@jankislinger]: https://github.com/jankislinger +[@jctanner]: https://github.com/jctanner +[@larsks]: https://github.com/larsks +[@lsmith77]: https://github.com/lsmith77 +[@matthewfranglen]: https://github.com/matthewfranglen +[@mgorny]: https://github.com/mgorny +[@naorlivne]: https://github.com/naorlivne +[@qnighy]: https://github.com/qnighy +[@rabinadk1]: https://github.com/rabinadk1 +[@randomseed42]: https://github.com/randomseed42 +[@sammck]: https://github.com/sammck +[@samwyma]: https://github.com/samwyma +[@sidharth-sudhir]: https://github.com/sidharth-sudhir +[@snobu]: https://github.com/snobu +[@techalchemy]: https://github.com/techalchemy +[@theGOTOguy]: https://github.com/theGOTOguy +[@theskumar]: https://github.com/theskumar +[@ulyssessouza]: https://github.com/ulyssessouza +[@venthur]: https://github.com/venthur +[@wrongontheinternet]: https://github.com/wrongontheinternet +[@x-yuri]: https://github.com/x-yuri +[@yannham]: https://github.com/yannham +[@zueve]: https://github.com/zueve +[@JYOuyang]: https://github.com/JYOuyang +[@burnout-projects]: https://github.com/burnout-projects +[@cpackham-atlnz]: https://github.com/cpackham-atlnz +[Unreleased]: https://github.com/theskumar/python-dotenv/compare/v1.2.3...HEAD +[1.2.3]: https://github.com/theskumar/python-dotenv/compare/v1.2.2...v1.2.3 +[1.2.2]: https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2 +[1.2.1]: https://github.com/theskumar/python-dotenv/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/theskumar/python-dotenv/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/theskumar/python-dotenv/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/theskumar/python-dotenv/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/theskumar/python-dotenv/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/theskumar/python-dotenv/compare/v0.21.0...v1.0.0 +[0.21.1]: https://github.com/theskumar/python-dotenv/compare/v0.21.0...v0.21.1 +[0.21.0]: https://github.com/theskumar/python-dotenv/compare/v0.20.0...v0.21.0 +[0.20.0]: https://github.com/theskumar/python-dotenv/compare/v0.19.2...v0.20.0 +[0.19.2]: https://github.com/theskumar/python-dotenv/compare/v0.19.1...v0.19.2 +[0.19.1]: https://github.com/theskumar/python-dotenv/compare/v0.19.0...v0.19.1 +[0.19.0]: https://github.com/theskumar/python-dotenv/compare/v0.18.0...v0.19.0 +[0.18.0]: https://github.com/theskumar/python-dotenv/compare/v0.17.1...v0.18.0 +[0.17.1]: https://github.com/theskumar/python-dotenv/compare/v0.17.0...v0.17.1 +[0.17.0]: https://github.com/theskumar/python-dotenv/compare/v0.16.0...v0.17.0 +[0.16.0]: https://github.com/theskumar/python-dotenv/compare/v0.15.0...v0.16.0 +[0.15.0]: https://github.com/theskumar/python-dotenv/compare/v0.14.0...v0.15.0 +[0.14.0]: https://github.com/theskumar/python-dotenv/compare/v0.13.0...v0.14.0 +[0.13.0]: https://github.com/theskumar/python-dotenv/compare/v0.12.0...v0.13.0 +[0.12.0]: https://github.com/theskumar/python-dotenv/compare/v0.11.0...v0.12.0 +[0.11.0]: https://github.com/theskumar/python-dotenv/compare/v0.10.5...v0.11.0 +[0.10.5]: https://github.com/theskumar/python-dotenv/compare/v0.10.4...v0.10.5 +[0.10.4]: https://github.com/theskumar/python-dotenv/compare/v0.10.3...v0.10.4 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD new file mode 100644 index 0000000..da675af --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD @@ -0,0 +1,15 @@ +dotenv/__init__.py,sha256=bhY-iK6wwHZamWII6eSCuCAWXUNdn2TQFmEOuXSU7SM,1230 +dotenv/__main__.py,sha256=N0RhLG7nHIqtlJHwwepIo-zbJPNx9sewCCRGY528h_4,129 +dotenv/cli.py,sha256=jFR-2VtVH0kthcycgcVeR2quvtADksFKT_3aO4AHy7s,6865 +dotenv/ipython.py,sha256=dHQBd9PcdCUphGb67Xwy3GkUqMSdf9XUUiNYxTZ3tYU,1326 +dotenv/main.py,sha256=aX6zPu5114tz22Hq-Wv5plR1SMGBHJNQG2fgVuVHDeo,15179 +dotenv/parser.py,sha256=YFJTOmzU4K1QUv6I5ZNoUwWcKO0_7A8JjNKafLN23KQ,5394 +dotenv/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +dotenv/variables.py,sha256=CD0qXOvvpB3q5RpBQMD9qX6vHX7SyW-SuiwGMFSlt08,2348 +dotenv/version.py,sha256=C-D_WWrVkBDmQmApLcm0sWNh2CgIrwWfc8_sB5vvU-Q,22 +python_dotenv-1.2.3.dist-info/licenses/LICENSE,sha256=gGGbcEnwjIFoOtDgHwjyV6hAZS3XHugxRtNmWMfSwrk,1556 +python_dotenv-1.2.3.dist-info/METADATA,sha256=I4DqlwB5a3OY_YFW4i-QKK_l7r-RjrhKa9XrsKj907U,29008 +python_dotenv-1.2.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91 +python_dotenv-1.2.3.dist-info/entry_points.txt,sha256=yRl1rCbswb1nQTQ_gZRlCw5QfabztUGnfGWLhlXFNdI,47 +python_dotenv-1.2.3.dist-info/top_level.txt,sha256=eyqUH4SHJNr6ahOYlxIunTr4XinE8Z5ajWLdrK3r0D8,7 +python_dotenv-1.2.3.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL new file mode 100644 index 0000000..13013d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt new file mode 100644 index 0000000..0a86823 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +dotenv = dotenv.__main__:cli diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE new file mode 100644 index 0000000..3a97119 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, Saurabh Kumar (python-dotenv), 2013, Ted Tieken (django-dotenv-rw), 2013, Jacob Kaplan-Moss (django-dotenv) + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of django-dotenv nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt new file mode 100644 index 0000000..fe7c01a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt @@ -0,0 +1 @@ +dotenv diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA new file mode 100644 index 0000000..d00853e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA @@ -0,0 +1,43 @@ +Metadata-Version: 2.4 +Name: python-multipart +Version: 0.0.32 +Summary: A streaming multipart parser for Python +Project-URL: Homepage, https://github.com/Kludex/python-multipart +Project-URL: Documentation, https://kludex.github.io/python-multipart/ +Project-URL: Changelog, https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md +Project-URL: Source, https://github.com/Kludex/python-multipart +Author-email: Andrew Dunham +Maintainer-email: Marcelo Trylesinski +License-Expression: Apache-2.0 +License-File: LICENSE.txt +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.10 +Description-Content-Type: text/markdown + +# [Python-Multipart](https://kludex.github.io/python-multipart/) + +[![Build Status](https://github.com/Kludex/python-multipart/workflows/CI/badge.svg)](https://github.com/Kludex/python-multipart/actions) +[![Package version](https://badge.fury.io/py/python-multipart.svg)](https://pypi.python.org/pypi/python-multipart) +[![Supported Python Version](https://img.shields.io/pypi/pyversions/python-multipart.svg?color=%2334D058)](https://pypi.org/project/python-multipart) +[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs) + +--- + +`python-multipart` is an Apache2-licensed streaming multipart parser for Python. +Test coverage is currently 100%. + +## Why? + +Because streaming uploads are awesome for large files. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD new file mode 100644 index 0000000..2e2bab8 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD @@ -0,0 +1,13 @@ +multipart/__init__.py,sha256=gbY7W9NUdsFZavp-iKQroN4FxonfZqzokkLW5B3jnZ0,1474 +multipart/decoders.py,sha256=XvkAwTU9UFPiXkc0hkvovHf0W6H3vK-2ieWlhav02hQ,40 +multipart/exceptions.py,sha256=6D_X-seiOmMAlIeiGlPGUs8-vpcvIGJeQycFMDb1f7A,42 +multipart/multipart.py,sha256=8fDH14j_VMbrch_58wlzi63XNARGv80kOZAyN72aG7A,41 +python_multipart/__init__.py,sha256=qr5CNo-H4S3lUCU61_h2_FczSuUdjzK_1E4aa06kFPo,937 +python_multipart/decoders.py,sha256=dJmwSRxowd5Xp3s86aIpU8f_Y55Kr0H4f5wgvScidEM,6676 +python_multipart/exceptions.py,sha256=U0lNBjhR4dQI97zV02aQpULQbDjjLqyWk90h5NrCxWA,954 +python_multipart/multipart.py,sha256=Ozy3ZJqDzJa6f4CgsiU4xO9DOb5PPVyUSrT72Rhfn6I,79407 +python_multipart/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +python_multipart-0.0.32.dist-info/METADATA,sha256=6dOZ196l7d8OTSS1wkaHqC5-UL8sKzD4-KBqZNiH3YY,2131 +python_multipart-0.0.32.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87 +python_multipart-0.0.32.dist-info/licenses/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +python_multipart-0.0.32.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL new file mode 100644 index 0000000..bd84226 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.30.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py new file mode 100644 index 0000000..7b60cac --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py @@ -0,0 +1,35 @@ +# Copyright 2012, Andrew Dunham +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "0.0.32" + +from .multipart import ( + BaseParser, + FormParser, + MultipartParser, + OctetStreamParser, + QuerystringParser, + create_form_parser, + parse_form, +) + +__all__ = ( + "BaseParser", + "FormParser", + "MultipartParser", + "OctetStreamParser", + "QuerystringParser", + "create_form_parser", + "parse_form", +) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py new file mode 100644 index 0000000..c4cdfb6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py @@ -0,0 +1,185 @@ +import base64 +import binascii +from typing import TYPE_CHECKING + +from .exceptions import DecodeError + +if TYPE_CHECKING: # pragma: no cover + from typing import Protocol, TypeVar + + _T_contra = TypeVar("_T_contra", contravariant=True) + + class SupportsWrite(Protocol[_T_contra]): + def write(self, __b: _T_contra) -> object: ... + + # No way to specify optional methods. See + # https://github.com/python/typing/issues/601 + # close() [Optional] + # finalize() [Optional] + + +class Base64Decoder: + """This object provides an interface to decode a stream of Base64 data. It + is instantiated with an "underlying object", and whenever a write() + operation is performed, it will decode the incoming data as Base64, and + call write() on the underlying object. This is primarily used for decoding + form data encoded as Base64, but can be used for other purposes:: + + from python_multipart.decoders import Base64Decoder + fd = open("notb64.txt", "wb") + decoder = Base64Decoder(fd) + try: + decoder.write("Zm9vYmFy") # "foobar" in Base64 + decoder.finalize() + finally: + decoder.close() + + # The contents of "notb64.txt" should be "foobar". + + This object will also pass all finalize() and close() calls to the + underlying object, if the underlying object supports them. + + Note that this class maintains a cache of base64 chunks, so that a write of + arbitrary size can be performed. You must call :meth:`finalize` on this + object after all writes are completed to ensure that all data is flushed + to the underlying object. + + :param underlying: the underlying object to pass writes to + """ + + def __init__(self, underlying: "SupportsWrite[bytes]") -> None: + self.cache = bytearray() + self.underlying = underlying + + def write(self, data: bytes) -> int: + """Takes any input data provided, decodes it as base64, and passes it + on to the underlying object. If the data provided is invalid base64 + data, then this method will raise + a :class:`python_multipart.exceptions.DecodeError` + + :param data: base64 data to decode + """ + + # Prepend any cache info to our data. + if len(self.cache) > 0: + data = bytes(self.cache) + data + + # Slice off a string that's a multiple of 4. + decode_len = (len(data) // 4) * 4 + val = data[:decode_len] + + # Decode and write, if we have any. + if len(val) > 0: + try: + decoded = base64.b64decode(val) + except binascii.Error: + raise DecodeError("There was an error raised while decoding base64-encoded data.") + + self.underlying.write(decoded) + + # Get the remaining bytes and save in our cache. + remaining_len = len(data) % 4 + if remaining_len > 0: + self.cache[:] = data[-remaining_len:] + else: + self.cache[:] = b"" + + # Return the length of the data to indicate no error. + return len(data) + + def close(self) -> None: + """Close this decoder. If the underlying object has a `close()` + method, this function will call it. + """ + if hasattr(self.underlying, "close"): + self.underlying.close() + + def finalize(self) -> None: + """Finalize this object. This should be called when no more data + should be written to the stream. This function can raise a + :class:`python_multipart.exceptions.DecodeError` if there is some remaining + data in the cache. + + If the underlying object has a `finalize()` method, this function will + call it. + """ + if len(self.cache) > 0: + raise DecodeError( + "There are %d bytes remaining in the Base64Decoder cache when finalize() is called" % len(self.cache) + ) + + if hasattr(self.underlying, "finalize"): + self.underlying.finalize() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(underlying={self.underlying!r})" + + +class QuotedPrintableDecoder: + """This object provides an interface to decode a stream of quoted-printable + data. It is instantiated with an "underlying object", in the same manner + as the :class:`python_multipart.decoders.Base64Decoder` class. This class behaves + in exactly the same way, including maintaining a cache of quoted-printable + chunks. + + :param underlying: the underlying object to pass writes to + """ + + def __init__(self, underlying: "SupportsWrite[bytes]") -> None: + self.cache = b"" + self.underlying = underlying + + def write(self, data: bytes) -> int: + """Takes any input data provided, decodes it as quoted-printable, and + passes it on to the underlying object. + + :param data: quoted-printable data to decode + """ + # Prepend any cache info to our data. + if len(self.cache) > 0: + data = self.cache + data + + # If the last 2 characters have an '=' sign in it, then we won't be + # able to decode the encoded value and we'll need to save it for the + # next decoding step. + if data[-2:].find(b"=") != -1: + enc, rest = data[:-2], data[-2:] + else: + enc = data + rest = b"" + + # Encode and write, if we have data. + if len(enc) > 0: + self.underlying.write(binascii.a2b_qp(enc)) + + # Save remaining in cache. + self.cache = rest + return len(data) + + def close(self) -> None: + """Close this decoder. If the underlying object has a `close()` + method, this function will call it. + """ + if hasattr(self.underlying, "close"): + self.underlying.close() + + def finalize(self) -> None: + """Finalize this object. This should be called when no more data + should be written to the stream. This function will not raise any + exceptions, but it may write more data to the underlying object if + there is data remaining in the cache. + + If the underlying object has a `finalize()` method, this function will + call it. + """ + # If we have a cache, write and then remove it. + if len(self.cache) > 0: # pragma: no cover + self.underlying.write(binascii.a2b_qp(self.cache)) + self.cache = b"" + + # Finalize our underlying stream. + if hasattr(self.underlying, "finalize"): + self.underlying.finalize() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(underlying={self.underlying!r})" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py new file mode 100644 index 0000000..2e7df21 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py @@ -0,0 +1,34 @@ +class FormParserError(ValueError): + """Base error class for our form parser.""" + + +class ParseError(FormParserError): + """This exception (or a subclass) is raised when there is an error while + parsing something. + """ + + def __init__(self, message: str, *, offset: int = -1) -> None: + super().__init__(message) + self.offset = offset + + +class MultipartParseError(ParseError): + """This is a specific error that is raised when the MultipartParser detects + an error while parsing. + """ + + +class QuerystringParseError(ParseError): + """This is a specific error that is raised when the QuerystringParser + detects an error while parsing. + """ + + +class DecodeError(ParseError): + """This exception is raised when there is a decoding error - for example + with the Base64Decoder or QuotedPrintableDecoder. + """ + + +class FileError(FormParserError, OSError): + """Exception class for problems with the File class.""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py new file mode 100644 index 0000000..887cf60 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py @@ -0,0 +1,1925 @@ +from __future__ import annotations + +import logging +import os +import shutil +import sys +import tempfile +from enum import IntEnum +from io import BufferedRandom, BytesIO +from numbers import Number +from typing import TYPE_CHECKING, cast + +from .decoders import Base64Decoder, QuotedPrintableDecoder +from .exceptions import FileError, FormParserError, MultipartParseError, QuerystringParseError + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, Literal, Protocol, TypeAlias, TypedDict + + class SupportsRead(Protocol): + def read(self, __n: int) -> bytes: ... + + class QuerystringCallbacks(TypedDict, total=False): + on_field_start: Callable[[], None] + on_field_name: Callable[[bytes, int, int], None] + on_field_data: Callable[[bytes, int, int], None] + on_field_end: Callable[[], None] + on_end: Callable[[], None] + + class OctetStreamCallbacks(TypedDict, total=False): + on_start: Callable[[], None] + on_data: Callable[[bytes, int, int], None] + on_end: Callable[[], None] + + class MultipartCallbacks(TypedDict, total=False): + on_part_begin: Callable[[], None] + on_part_data: Callable[[bytes, int, int], None] + on_part_end: Callable[[], None] + on_header_begin: Callable[[], None] + on_header_field: Callable[[bytes, int, int], None] + on_header_value: Callable[[bytes, int, int], None] + on_header_end: Callable[[], None] + on_headers_finished: Callable[[], None] + on_end: Callable[[], None] + + class FileConfig(TypedDict, total=False): + UPLOAD_DIR: str | bytes | None + UPLOAD_DELETE_TMP: bool + UPLOAD_KEEP_FILENAME: bool + UPLOAD_KEEP_EXTENSIONS: bool + MAX_MEMORY_FILE_SIZE: int + + class FormParserConfig(FileConfig): + UPLOAD_ERROR_ON_BAD_CTE: bool + MAX_BODY_SIZE: float + MAX_HEADER_COUNT: int + MAX_HEADER_SIZE: int + + CallbackName: TypeAlias = Literal[ + "start", + "data", + "end", + "field_start", + "field_name", + "field_data", + "field_end", + "part_begin", + "part_data", + "part_end", + "header_begin", + "header_field", + "header_value", + "header_end", + "headers_finished", + ] + +# Unique missing object. +_missing = object() + + +class QuerystringState(IntEnum): + """Querystring parser states. + + These are used to keep track of the state of the parser, and are used to determine + what to do when new data is encountered. + """ + + BEFORE_FIELD = 0 + FIELD_NAME = 1 + FIELD_DATA = 2 + + +class MultipartState(IntEnum): + """Multipart parser states. + + These are used to keep track of the state of the parser, and are used to determine + what to do when new data is encountered. + """ + + START = 0 + START_BOUNDARY = 1 + HEADER_FIELD_START = 2 + HEADER_FIELD = 3 + HEADER_VALUE_START = 4 + HEADER_VALUE = 5 + HEADER_VALUE_ALMOST_DONE = 6 + HEADERS_ALMOST_DONE = 7 + PART_DATA_START = 8 + PART_DATA = 9 + PART_DATA_END = 10 + END_BOUNDARY = 11 + END = 12 + + +# Flags for the multipart parser. +FLAG_PART_BOUNDARY = 1 +FLAG_LAST_BOUNDARY = 2 + +# Get constants. Since iterating over a str on Python 2 gives you a 1-length +# string, but iterating over a bytes object on Python 3 gives you an integer, +# we need to save these constants. +CR = b"\r"[0] +LF = b"\n"[0] +COLON = b":"[0] +SPACE = b" "[0] +HYPHEN = b"-"[0] +AMPERSAND = b"&"[0] +LOWER_A = b"a"[0] +LOWER_Z = b"z"[0] +NULL = b"\x00"[0] + +# fmt: off +# Mask for ASCII characters that can be http tokens. +# Per RFC7230 - 3.2.6, this is all alpha-numeric characters +# and these: !#$%&'*+-.^_`|~ +TOKEN_CHARS = ( + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + b"abcdefghijklmnopqrstuvwxyz" + b"0123456789" + b"!#$%&'*+-.^_`|~") +TOKEN_CHARS_SET = frozenset(TOKEN_CHARS) +# fmt: on + +DEFAULT_MAX_HEADER_COUNT = 8 +"""Default maximum number of headers allowed per multipart part.""" + +DEFAULT_MAX_HEADER_SIZE = 4096 + 128 +"""Default maximum size of a single multipart header line, including syntax overhead.""" + +MAX_BOUNDARY_LENGTH = 256 +"""Maximum allowed length of a multipart boundary. + +[RFC 2046 §5.1.1](https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.1) +recommends boundaries be at most 70 bytes. 256 bytes is generous headroom over +every HTTP client. +""" + + +def _parseparam(s: str) -> list[str]: + # Vendored from the standard library's + # [`email.message._parseparam`](https://github.com/python/cpython/blob/v3.14.2/Lib/email/message.py#L73-L96) + # to split a header into its `;`-separated parts without treating a `;` inside a double-quoted string as a + # separator - and without the RFC 2231 decoding that `email.message.Message.get_params` would apply on top. + s = ";" + s + plist: list[str] = [] + start = 0 + while s.find(";", start) == start: + start += 1 + end = s.find(";", start) + ind, diff = start, 0 + while end > 0: + diff += s.count('"', ind, end) - s.count('\\"', ind, end) + if diff % 2 == 0: + break + end, ind = ind, s.find(";", end + 1) + if end < 0: + end = len(s) + i = s.find("=", start, end) + if i == -1: + f = s[start:end] + else: + f = s[start:i].rstrip().lower() + "=" + s[i + 1 : end].lstrip() + plist.append(f.strip()) + start = end + return plist + + +def parse_options_header(value: str | bytes | None) -> tuple[bytes, dict[bytes, bytes]]: + """Parses a Content-Type header into a value in the following format: (content_type, {parameters}).""" + if not value: + return (b"", {}) + + # If we are passed bytes, we assume that it conforms to WSGI, encoding in latin-1. + if isinstance(value, bytes): # pragma: no cover + value = value.decode("latin-1") + + # For types + assert isinstance(value, str), "Value should be a string by now" + + # If we have no options, return the string as-is. + if ";" not in value: + return (value.lower().strip().encode("latin-1"), {}) + + ctype, *segments = _parseparam(value) + options: dict[bytes, bytes] = {} + for segment in segments: + key, _, val = segment.partition("=") + # [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2) + # forbids the RFC 5987/2231 extended syntax (`key*=`, `key*0`, ...) in + # multipart/form-data, so we ignore those parameters and keep the plain + # `key` authoritative. + if "*" in key: + continue + if len(val) >= 2 and val[0] == '"' and val[-1] == '"': + val = val[1:-1].replace("\\\\", "\\").replace('\\"', '"') + # Work around an IE6 bug where the full file path is sent instead of + # just the filename. + if key == "filename" and (val[1:3] == ":\\" or val[:2] == "\\\\"): + val = val.split("\\")[-1] + options[key.encode("latin-1")] = val.encode("latin-1") + return ctype.encode("latin-1"), options + + +class Field: + """A Field object represents a (parsed) form field. It represents a single + field with a corresponding name and value. + + The name that a :class:`Field` will be instantiated with is the same name + that would be found in the following HTML:: + + + + This class defines two methods, :meth:`on_data` and :meth:`on_end`, that + will be called when data is written to the Field, and when the Field is + finalized, respectively. + + Args: + name: The name of the form field. + content_type: The value of the Content-Type header for this field. + """ + + def __init__(self, name: bytes | None, *, content_type: str | None = None) -> None: + self._name = name + self._value: list[bytes] = [] + self._content_type = content_type + + # We cache the joined version of _value for speed. + self._cache = _missing + + @classmethod + def from_value(cls, name: bytes, value: bytes | None) -> Field: + """Create an instance of a :class:`Field`, and set the corresponding + value - either None or an actual value. This method will also + finalize the Field itself. + + Args: + name: the name of the form field. + value: the value of the form field - either a bytestring or None. + + Returns: + A new instance of a [`Field`][python_multipart.Field]. + """ + + f = cls(name) + if value is None: + f.set_none() + else: + f.write(value) + f.finalize() + return f + + def write(self, data: bytes) -> int: + """Write some data into the form field. + + Args: + data: The data to write to the field. + + Returns: + The number of bytes written. + """ + return self.on_data(data) + + def on_data(self, data: bytes) -> int: + """This method is a callback that will be called whenever data is + written to the Field. + + Args: + data: The data to write to the field. + + Returns: + The number of bytes written. + """ + self._value.append(data) + self._cache = _missing + return len(data) + + def on_end(self) -> None: + """This method is called whenever the Field is finalized.""" + if self._cache is _missing: + self._cache = b"".join(self._value) + + def finalize(self) -> None: + """Finalize the form field.""" + self.on_end() + + def close(self) -> None: + """Close the Field object. This will free any underlying cache.""" + # Free our value array. + if self._cache is _missing: + self._cache = b"".join(self._value) + + del self._value + + def set_none(self) -> None: + """Some fields in a querystring can possibly have a value of None - for + example, the string "foo&bar=&baz=asdf" will have a field with the + name "foo" and value None, one with name "bar" and value "", and one + with name "baz" and value "asdf". Since the write() interface doesn't + support writing None, this function will set the field value to None. + """ + self._cache = None + + @property + def field_name(self) -> bytes | None: + """This property returns the name of the field.""" + return self._name + + @property + def value(self) -> bytes | None: + """This property returns the value of the form field.""" + if self._cache is _missing: + self._cache = b"".join(self._value) + + assert isinstance(self._cache, bytes) or self._cache is None + return self._cache + + @property + def content_type(self) -> str | None: + """This property returns the content_type value of the field.""" + return self._content_type + + def __eq__(self, other: object) -> bool: + if isinstance(other, Field): + return self.field_name == other.field_name and self.value == other.value + else: + return NotImplemented + + def __repr__(self) -> str: + if self.value is not None and len(self.value) > 97: + # We get the repr, and then insert three dots before the final + # quote. + v = repr(self.value[:97])[:-1] + "...'" + else: + v = repr(self.value) + + return f"{self.__class__.__name__}(field_name={self.field_name!r}, value={v})" + + +class File: + """This class represents an uploaded file. It handles writing file data to + either an in-memory file or a temporary file on-disk, if the optional + threshold is passed. + + There are some options that can be passed to the File to change behavior + of the class. Valid options are as follows: + + | Name | Type | Default | Description | + |-----------------------|-------|---------|-------------| + | UPLOAD_DIR | `str` | None | The directory to store uploaded files in. If this is None, a temporary file will be created in the system's standard location. | + | UPLOAD_DELETE_TMP | `bool`| True | Delete automatically created TMP file | + | UPLOAD_KEEP_FILENAME | `bool`| False | Whether or not to keep the filename of the uploaded file. If True, then the filename will be converted to a safe representation (e.g. by removing any invalid path segments), and then saved with the same name). Otherwise, a temporary name will be used. | + | UPLOAD_KEEP_EXTENSIONS| `bool`| False | Whether or not to keep the uploaded file's extension. If False, the file will be saved with the default temporary extension (usually ".tmp"). Otherwise, the file's extension will be maintained. Note that this will properly combine with the UPLOAD_KEEP_FILENAME setting. | + | MAX_MEMORY_FILE_SIZE | `int` | 1 MiB | The maximum number of bytes of a File to keep in memory. By default, the contents of a File are kept into memory until a certain limit is reached, after which the contents of the File are written to a temporary file. This behavior can be disabled by setting this value to an appropriately large value (or, for example, infinity, such as `float('inf')`. | + + Args: + file_name: The name of the file that this [`File`][python_multipart.File] represents. + field_name: The name of the form field that this file was uploaded with. This can be None, if, for example, + the file was uploaded with Content-Type application/octet-stream. + config: The configuration for this File. See above for valid configuration keys and their corresponding values. + content_type: The value of the Content-Type header. + """ # noqa: E501 + + def __init__( + self, + file_name: bytes | None, + field_name: bytes | None = None, + config: FileConfig = {}, + *, + content_type: str | None = None, + ) -> None: + # Save configuration, set other variables default. + self.logger = logging.getLogger(__name__) + self._config = config + self._in_memory = True + self._bytes_written = 0 + self._fileobj: BytesIO | BufferedRandom = BytesIO() + + # Save the provided field/file name and content type. + self._field_name = field_name + self._file_name = file_name + self._content_type = content_type + + # Our actual file name is None by default, since, depending on our + # config, we may not actually use the provided name. + self._actual_file_name: bytes | None = None + + # Split the extension from the filename. + if file_name is not None: + # Extract just the basename to avoid directory traversal + basename = os.path.basename(file_name) + base, ext = os.path.splitext(basename) + self._file_base = base + self._ext = ext + + @property + def field_name(self) -> bytes | None: + """The form field associated with this file. May be None if there isn't + one, for example when we have an application/octet-stream upload. + """ + return self._field_name + + @property + def file_name(self) -> bytes | None: + """The file name given in the upload request.""" + return self._file_name + + @property + def actual_file_name(self) -> bytes | None: + """The file name that this file is saved as. Will be None if it's not + currently saved on disk. + """ + return self._actual_file_name + + @property + def file_object(self) -> BytesIO | BufferedRandom: + """The file object that we're currently writing to. Note that this + will either be an instance of a :class:`io.BytesIO`, or a regular file + object. + """ + return self._fileobj + + @property + def size(self) -> int: + """The total size of this file, counted as the number of bytes that + currently have been written to the file. + """ + return self._bytes_written + + @property + def in_memory(self) -> bool: + """A boolean representing whether or not this file object is currently + stored in-memory or on-disk. + """ + return self._in_memory + + @property + def content_type(self) -> str | None: + """The Content-Type value for this part, if it was set.""" + return self._content_type + + def flush_to_disk(self) -> None: + """If the file is already on-disk, do nothing. Otherwise, copy from + the in-memory buffer to a disk file, and then reassign our internal + file object to this new disk file. + + Note that if you attempt to flush a file that is already on-disk, a + warning will be logged to this module's logger. + """ + if not self._in_memory: + self.logger.warning("Trying to flush to disk when we're not in memory") + return + + # Go back to the start of our file. + self._fileobj.seek(0) + + # Open a new file. + new_file = self._get_disk_file() + + # Copy the file objects. + shutil.copyfileobj(self._fileobj, new_file) + + # Seek to the new position in our new file. + new_file.seek(self._bytes_written) + + # Reassign the fileobject. + old_fileobj = self._fileobj + self._fileobj = new_file + + # We're no longer in memory. + self._in_memory = False + + # Close the old file object. + old_fileobj.close() + + def _get_disk_file(self) -> BufferedRandom: + """This function is responsible for getting a file object on-disk for us.""" + self.logger.info("Opening a file on disk") + + file_dir = self._config.get("UPLOAD_DIR") + keep_filename = self._config.get("UPLOAD_KEEP_FILENAME", False) + keep_extensions = self._config.get("UPLOAD_KEEP_EXTENSIONS", False) + delete_tmp = self._config.get("UPLOAD_DELETE_TMP", True) + tmp_file: None | BufferedRandom = None + + # If we have a directory and are to keep the filename... + if file_dir is not None and keep_filename: + self.logger.info("Saving with filename in: %r", file_dir) + + # Build our filename. + # TODO: what happens if we don't have a filename? + fname = self._file_base + self._ext if keep_extensions else self._file_base + + path = os.path.join(file_dir, fname) # type: ignore[arg-type] + try: + self.logger.info("Opening file: %r", path) + tmp_file = open(path, "w+b") + except OSError: + tmp_file = None + + self.logger.exception("Error opening temporary file") + raise FileError("Error opening temporary file: %r" % path) + else: + # Build options array. + # Note that on Python 3, tempfile doesn't support byte names. We + # encode our paths using the default filesystem encoding. + suffix = self._ext.decode(sys.getfilesystemencoding()) if keep_extensions else None + + if file_dir is None: + dir = None + elif isinstance(file_dir, bytes): + dir = file_dir.decode(sys.getfilesystemencoding()) + else: + dir = file_dir # pragma: no cover + + # Create a temporary (named) file with the appropriate settings. + self.logger.info( + "Creating a temporary file with options: %r", {"suffix": suffix, "delete": delete_tmp, "dir": dir} + ) + try: + tmp_file = cast(BufferedRandom, tempfile.NamedTemporaryFile(suffix=suffix, delete=delete_tmp, dir=dir)) + except OSError: + self.logger.exception("Error creating named temporary file") + raise FileError("Error creating named temporary file") + + assert tmp_file is not None + # Encode filename as bytes. + if isinstance(tmp_file.name, str): + fname = tmp_file.name.encode(sys.getfilesystemencoding()) + else: + fname = cast(bytes, tmp_file.name) # pragma: no cover + + self._actual_file_name = fname + return tmp_file + + def write(self, data: bytes) -> int: + """Write some data to the File. + + :param data: a bytestring + """ + return self.on_data(data) + + def on_data(self, data: bytes) -> int: + """This method is a callback that will be called whenever data is + written to the File. + + Args: + data: The data to write to the file. + + Returns: + The number of bytes written. + """ + bwritten = self._fileobj.write(data) + + # If the bytes written isn't the same as the length, just return. + if bwritten != len(data): + self.logger.warning("bwritten != len(data) (%d != %d)", bwritten, len(data)) + return bwritten + + # Keep track of how many bytes we've written. + self._bytes_written += bwritten + + # If we're in-memory and are over our limit, we create a file. + max_memory_file_size = self._config.get("MAX_MEMORY_FILE_SIZE") + if self._in_memory and max_memory_file_size is not None and (self._bytes_written > max_memory_file_size): + self.logger.info("Flushing to disk") + self.flush_to_disk() + + # Return the number of bytes written. + return bwritten + + def on_end(self) -> None: + """This method is called whenever the Field is finalized.""" + # Flush the underlying file object + self._fileobj.flush() + + def finalize(self) -> None: + """Finalize the form file. This will not close the underlying file, + but simply signal that we are finished writing to the File. + """ + self.on_end() + + def close(self) -> None: + """Close the File object. This will actually close the underlying + file object (whether it's a :class:`io.BytesIO` or an actual file + object). + """ + self._fileobj.close() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(file_name={self.file_name!r}, field_name={self.field_name!r})" + + +class BaseParser: + """This class is the base class for all parsers. It contains the logic for + calling and adding callbacks. + + A callback can be one of two different forms. "Notification callbacks" are + callbacks that are called when something happens - for example, when a new + part of a multipart message is encountered by the parser. "Data callbacks" + are called when we get some sort of data - for example, part of the body of + a multipart chunk. Notification callbacks are called with no parameters, + whereas data callbacks are called with three, as follows:: + + data_callback(data, start, end) + + The "data" parameter is a bytestring (i.e. "foo" on Python 2, or b"foo" on + Python 3). "start" and "end" are integer indexes into the "data" string + that represent the data of interest. Thus, in a data callback, the slice + `data[start:end]` represents the data that the callback is "interested in". + The callback is not passed a copy of the data, since copying severely hurts + performance. + """ + + def __init__(self) -> None: + self.logger = logging.getLogger(__name__) + self.callbacks: QuerystringCallbacks | OctetStreamCallbacks | MultipartCallbacks = {} + + def callback( + self, name: CallbackName, data: bytes | None = None, start: int | None = None, end: int | None = None + ) -> None: + """This function calls a provided callback with some data. If the + callback is not set, will do nothing. + + Args: + name: The name of the callback to call (as a string). + data: Data to pass to the callback. If None, then it is assumed that the callback is a notification + callback, and no parameters are given. + end: An integer that is passed to the data callback. + start: An integer that is passed to the data callback. + """ + func = self.callbacks.get("on_" + name) + if func is None: + return + func = cast("Callable[..., Any]", func) + # Depending on whether we're given a buffer... + if data is not None: + # Don't do anything if we have start == end. + if start is not None and start == end: + return + func(data, start, end) + else: + func() + + def set_callback(self, name: CallbackName, new_func: Callable[..., Any] | None) -> None: + """Update the function for a callback. Removes from the callbacks dict + if new_func is None. + + :param name: The name of the callback to call (as a string). + + :param new_func: The new function for the callback. If None, then the + callback will be removed (with no error if it does not + exist). + """ + if new_func is None: + self.callbacks.pop("on_" + name, None) # type: ignore[misc] + else: + self.callbacks["on_" + name] = new_func # type: ignore[literal-required] + + def close(self) -> None: + pass # pragma: no cover + + def finalize(self) -> None: + pass # pragma: no cover + + def __repr__(self) -> str: + return "%s()" % self.__class__.__name__ + + +class OctetStreamParser(BaseParser): + """This parser parses an octet-stream request body and calls callbacks when + incoming data is received. Callbacks are as follows: + + | Callback Name | Parameters | Description | + |----------------|-----------------|-----------------------------------------------------| + | on_start | None | Called when the first data is parsed. | + | on_data | data, start, end| Called for each data chunk that is parsed. | + | on_end | None | Called when the parser is finished parsing all data.| + + Args: + callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser]. + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. + """ + + def __init__(self, callbacks: OctetStreamCallbacks = {}, max_size: float = float("inf")): + super().__init__() + self.callbacks = callbacks + self._started = False + + if not isinstance(max_size, Number) or max_size < 1: + raise ValueError("max_size must be a positive number, not %r" % max_size) + self.max_size: int | float = max_size + self._current_size = 0 + + def write(self, data: bytes) -> int: + """Write some data to the parser, which will perform size verification, + and then pass the data to the underlying callback. + + Args: + data: The data to write to the parser. + + Returns: + The number of bytes written. + """ + if not self._started: + self.callback("start") + self._started = True + + # Truncate data length. + data_len = len(data) + if (self._current_size + data_len) > self.max_size: + # We truncate the length of data that we are to process. + new_size = int(self.max_size - self._current_size) + self.logger.warning( + "Current size is %d (max %d), so truncating data length from %d to %d", + self._current_size, + self.max_size, + data_len, + new_size, + ) + data_len = new_size + + # Increment size, then callback, in case there's an exception. + self._current_size += data_len + self.callback("data", data, 0, data_len) + return data_len + + def finalize(self) -> None: + """Finalize this parser, which signals to that we are finished parsing, + and sends the on_end callback. + """ + self.callback("end") + + def __repr__(self) -> str: + return "%s()" % self.__class__.__name__ + + +class QuerystringParser(BaseParser): + """This is a streaming querystring parser. It will consume data, and call + the callbacks given when it has data. + + | Callback Name | Parameters | Description | + |----------------|-----------------|-----------------------------------------------------| + | on_field_start | None | Called when a new field is encountered. | + | on_field_name | data, start, end| Called when a portion of a field's name is encountered. | + | on_field_data | data, start, end| Called when a portion of a field's data is encountered. | + | on_field_end | None | Called when the end of a field is encountered. | + | on_end | None | Called when the parser is finished parsing all data.| + + Args: + callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser]. + strict_parsing: Whether or not to parse the body strictly. Defaults to False. If this is set to True, then the + behavior of the parser changes as the following: if a field has a value with an equal sign + (e.g. "foo=bar", or "foo="), it is always included. If a field has no equals sign (e.g. "...&name&..."), + it will be treated as an error if 'strict_parsing' is True, otherwise included. If an error is encountered, + then a [`QuerystringParseError`][python_multipart.exceptions.QuerystringParseError] will be raised. + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. + """ # noqa: E501 + + state: QuerystringState + + def __init__( + self, callbacks: QuerystringCallbacks = {}, strict_parsing: bool = False, max_size: float = float("inf") + ) -> None: + super().__init__() + self.state = QuerystringState.BEFORE_FIELD + self._found_sep = False + + self.callbacks = callbacks + + # Max-size stuff + if not isinstance(max_size, Number) or max_size < 1: + raise ValueError("max_size must be a positive number, not %r" % max_size) + self.max_size: int | float = max_size + self._current_size = 0 + + # Should parsing be strict? + self.strict_parsing = strict_parsing + + def write(self, data: bytes) -> int: + """Write some data to the parser, which will perform size verification, + parse into either a field name or value, and then pass the + corresponding data to the underlying callback. If an error is + encountered while parsing, a QuerystringParseError will be raised. The + "offset" attribute of the raised exception will be set to the offset in + the input data chunk (NOT the overall stream) that caused the error. + + Args: + data: The data to write to the parser. + + Returns: + The number of bytes written. + """ + # Handle sizing. + data_len = len(data) + if (self._current_size + data_len) > self.max_size: + # We truncate the length of data that we are to process. + new_size = int(self.max_size - self._current_size) + self.logger.warning( + "Current size is %d (max %d), so truncating data length from %d to %d", + self._current_size, + self.max_size, + data_len, + new_size, + ) + data_len = new_size + + l = 0 + try: + l = self._internal_write(data, data_len) + finally: + self._current_size += l + + return l + + def _internal_write(self, data: bytes, length: int) -> int: + state = self.state + strict_parsing = self.strict_parsing + found_sep = self._found_sep + + i = 0 + while i < length: + ch = data[i] + + # Depending on our state... + if state == QuerystringState.BEFORE_FIELD: + # If the 'found_sep' flag is set, we've already encountered + # and skipped a single separator. If so, we check our strict + # parsing flag and decide what to do. Otherwise, we haven't + # yet reached a separator, and thus, if we do, we need to skip + # it as it will be the boundary between fields that's supposed + # to be there. + if ch == AMPERSAND: + if found_sep: + # If we're parsing strictly, we disallow blank chunks. + if strict_parsing: + raise QuerystringParseError("Skipping duplicate ampersand at %d" % i, offset=i) + else: + self.logger.debug("Skipping duplicate ampersand at %d", i) + else: + # This case is when we're skipping the (first) + # separator between fields, so we just set our flag + # and continue on. + found_sep = True + else: + # Emit a field-start event, and go to that state. Also, + # reset the "found_sep" flag, for the next time we get to + # this state. + self.callback("field_start") + i -= 1 + state = QuerystringState.FIELD_NAME + found_sep = False + + elif state == QuerystringState.FIELD_NAME: + # Try and find a separator - we ensure that, if we do, we only + # look for the equal sign before it. + sep_pos = data.find(b"&", i, length) + + # See if we can find an equals sign in the remaining data. If + # so, we can immediately emit the field name and jump to the + # data state. + if sep_pos != -1: + equals_pos = data.find(b"=", i, sep_pos) + else: + equals_pos = data.find(b"=", i, length) + + if equals_pos != -1: + # Emit this name. + self.callback("field_name", data, i, equals_pos) + + # Jump i to this position. Note that it will then have 1 + # added to it below, which means the next iteration of this + # loop will inspect the character after the equals sign. + i = equals_pos + state = QuerystringState.FIELD_DATA + else: + # No equals sign found. + if not strict_parsing: + # See also comments in the QuerystringState.FIELD_DATA case below. + # If we found the separator, we emit the name and just + # end - there's no data callback at all (not even with + # a blank value). + if sep_pos != -1: + self.callback("field_name", data, i, sep_pos) + self.callback("field_end") + + i = sep_pos - 1 + state = QuerystringState.BEFORE_FIELD + else: + # Otherwise, no separator in this block, so the + # rest of this chunk must be a name. + self.callback("field_name", data, i, length) + i = length + + else: + # We're parsing strictly. If we find a separator, + # this is an error - we require an equals sign. + if sep_pos != -1: + raise QuerystringParseError( + "When strict_parsing is True, we require an " + "equals sign in all field chunks. Did not " + "find one in the chunk that starts at %d" % (i,), + offset=i, + ) + + # No separator in the rest of this chunk, so it's just + # a field name. + self.callback("field_name", data, i, length) + i = length + + elif state == QuerystringState.FIELD_DATA: + # Try finding an ampersand after this position. + sep_pos = data.find(b"&", i, length) + + # If we found it, callback this bit as data and then go back + # to expecting to find a field. + if sep_pos != -1: + self.callback("field_data", data, i, sep_pos) + self.callback("field_end") + + # Note that we go to the separator, which brings us to the + # "before field" state. This allows us to properly emit + # "field_start" events only when we actually have data for + # a field of some sort. + i = sep_pos - 1 + state = QuerystringState.BEFORE_FIELD + + # Otherwise, emit the rest as data and finish. + else: + self.callback("field_data", data, i, length) + i = length + + else: # pragma: no cover (error case) + msg = "Reached an unknown state %d at %d" % (state, i) + self.logger.warning(msg) + raise QuerystringParseError(msg, offset=i) + + i += 1 + + self.state = state + self._found_sep = found_sep + return length + + def finalize(self) -> None: + """Finalize this parser, which signals to that we are finished parsing, + if we're still in the middle of a field, an on_field_end callback, and + then the on_end callback. + """ + # If we're currently in the middle of a field, we finish it. + if self.state in (QuerystringState.FIELD_DATA, QuerystringState.FIELD_NAME): + self.callback("field_end") + self.callback("end") + + def __repr__(self) -> str: + return "{}(strict_parsing={!r}, max_size={!r})".format( + self.__class__.__name__, self.strict_parsing, self.max_size + ) + + +class MultipartParser(BaseParser): + """This class is a streaming multipart/form-data parser. + + | Callback Name | Parameters | Description | + |--------------------|-----------------|-------------| + | on_part_begin | None | Called when a new part of the multipart message is encountered. | + | on_part_data | data, start, end| Called when a portion of a part's data is encountered. | + | on_part_end | None | Called when the end of a part is reached. | + | on_header_begin | None | Called when we've found a new header in a part of a multipart message | + | on_header_field | data, start, end| Called each time an additional portion of a header is read (i.e. the part of the header that is before the colon; the "Foo" in "Foo: Bar"). | + | on_header_value | data, start, end| Called when we get data for a header. | + | on_header_end | None | Called when the current header is finished - i.e. we've reached the newline at the end of the header. | + | on_headers_finished| None | Called when all headers are finished, and before the part data starts. | + | on_end | None | Called when the parser is finished parsing all data. | + + Args: + boundary: The multipart boundary. This is required, and must match what is given in the HTTP request - usually in the Content-Type header. + callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser]. + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. + max_header_count: The maximum number of headers allowed per part. + max_header_size: The maximum size of a single header line (excluding the trailing CRLF). + """ # noqa: E501 + + def __init__( + self, + boundary: bytes | str, + callbacks: MultipartCallbacks = {}, + max_size: float = float("inf"), + *, + max_header_count: int = DEFAULT_MAX_HEADER_COUNT, + max_header_size: int = DEFAULT_MAX_HEADER_SIZE, + ) -> None: + # Initialize parser state. + super().__init__() + self.state = MultipartState.START + self.index = self.flags = 0 + + self.callbacks = callbacks + + if not isinstance(max_size, Number) or max_size < 1: + raise ValueError("max_size must be a positive number, not %r" % max_size) + self.max_size = max_size + self._current_size = 0 + + self.max_header_count = max_header_count + self._current_header_count = 0 + + self.max_header_size = max_header_size + self._current_header_size = 0 + + # Setup marks. These are used to track the state of data received. + self.marks: dict[str, int] = {} + + # Save our boundary. + if isinstance(boundary, str): # pragma: no cover + boundary = boundary.encode("latin-1") + if len(boundary) > MAX_BOUNDARY_LENGTH: + raise FormParserError(f"Boundary length {len(boundary)} exceeds maximum of {MAX_BOUNDARY_LENGTH}") + self.boundary = b"\r\n--" + boundary + + def write(self, data: bytes) -> int: + """Write some data to the parser, which will perform size verification, + and then parse the data into the appropriate location (e.g. header, + data, etc.), and pass this on to the underlying callback. If an error + is encountered, a MultipartParseError will be raised. The "offset" + attribute on the raised exception will be set to the offset of the byte + in the input chunk that caused the error. + + Args: + data: The data to write to the parser. + + Returns: + The number of bytes written. + """ + # Handle sizing. + data_len = len(data) + if (self._current_size + data_len) > self.max_size: + # We truncate the length of data that we are to process. + new_size = int(self.max_size - self._current_size) + self.logger.warning( + "Current size is %d (max %d), so truncating data length from %d to %d", + self._current_size, + self.max_size, + data_len, + new_size, + ) + data_len = new_size + + l = 0 + try: + l = self._internal_write(data, data_len) + finally: + self._current_size += l + + return l + + def _internal_write(self, data: bytes, length: int) -> int: + # Get values from locals. + boundary = self.boundary + boundary_length = len(boundary) + + # Get our state, flags and index. These are persisted between calls to + # this function. + state = self.state + index = self.index + flags = self.flags + current_header_count = self._current_header_count + current_header_size = self._current_header_size + + # Our index defaults to 0. + i = 0 + + def advance_header_size(amount: int = 1) -> None: + nonlocal current_header_size + current_header_size += amount + if current_header_size > self.max_header_size: + raise MultipartParseError("Maximum header size exceeded", offset=i) + + # Set a mark. + def set_mark(name: str) -> None: + self.marks[name] = i + + # Remove a mark. + def delete_mark(name: str, reset: bool = False) -> None: + self.marks.pop(name, None) + + # Helper function that makes calling a callback with data easier. The + # 'remaining' parameter will callback from the marked value until the + # end of the buffer, and reset the mark, instead of deleting it. This + # is used at the end of the function to call our callbacks with any + # remaining data in this chunk. + def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> None: + marked_index = self.marks.get(name) + if marked_index is None: + return + + # Otherwise, we call it from the mark to the current byte we're + # processing. + if end_i <= marked_index: + # There is no additional data to send. + pass + elif marked_index >= 0: + # We are emitting data from the local buffer. + self.callback(name, data, marked_index, end_i) + else: + # Some of the data comes from a partial boundary match. + # and requires look-behind. + # We need to use self.flags (and not flags) because we care about + # the state when we entered the loop. + lookbehind_len = -marked_index + if lookbehind_len <= boundary_length: + self.callback(name, boundary, 0, lookbehind_len) + elif self.flags & FLAG_PART_BOUNDARY: + lookback = boundary + b"\r\n" + self.callback(name, lookback, 0, lookbehind_len) + elif self.flags & FLAG_LAST_BOUNDARY: + lookback = boundary + b"--\r\n" + self.callback(name, lookback, 0, lookbehind_len) + else: # pragma: no cover (error case) + self.logger.warning("Look-back buffer error") + + if end_i > 0: + self.callback(name, data, 0, end_i) + # If we're getting remaining data, we have got all the data we + # can be certain is not a boundary, leaving only a partial boundary match. + if remaining: + self.marks[name] = end_i - length + else: + self.marks.pop(name, None) + + # For each byte... + while i < length: + c = data[i] + + if state == MultipartState.START: + # Skip leading newlines + if c == CR or c == LF: + i = data.find(b"-", i) + if i == -1: + # No boundary candidate in this chunk, so ignore the content after the leading CR/LF. + i = length + break + continue + + # index is used as in index into our boundary. Set to 0. + index = 0 + + # Move to the next state, but decrement i so that we re-process + # this character. + state = MultipartState.START_BOUNDARY + i -= 1 + + elif state == MultipartState.START_BOUNDARY: + # Check to ensure that the last 2 characters in our boundary + # are CRLF. + if index == boundary_length - 2: + if c == HYPHEN: + # Potential empty message. + state = MultipartState.END_BOUNDARY + elif c != CR: + # Error! + msg = "Did not find CR at end of boundary (%d)" % (i,) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + index += 1 + + elif index == boundary_length - 1: + if c != LF: + msg = "Did not find LF at end of boundary (%d)" % (i,) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + # The index is now used for indexing into our boundary. + index = 0 + + # Callback for the start of a part. + self.callback("part_begin") + current_header_count = 0 + current_header_size = 0 + + # Move to the next character and state. + state = MultipartState.HEADER_FIELD_START + + else: + # Check to ensure our boundary matches + if c != boundary[index + 2]: + msg = "Expected boundary character %r, got %r at index %d" % (boundary[index + 2], c, index + 2) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + # Increment index into boundary and continue. + index += 1 + + elif state == MultipartState.HEADER_FIELD_START: + # Mark the start of a header field here, reset the index, and + # continue parsing our header field. + index = 0 + + if c != CR: + current_header_count += 1 + if current_header_count > self.max_header_count: + raise MultipartParseError("Maximum header count exceeded", offset=i) + current_header_size = 0 + + # Set a mark of our header field. + set_mark("header_field") + + # Notify that we're starting a header if the next character is + # not a CR; a CR at the beginning of the header will cause us + # to stop parsing headers in the MultipartState.HEADER_FIELD state, + # below. + if c != CR: + self.callback("header_begin") + + # Move to parsing header fields. + state = MultipartState.HEADER_FIELD + i -= 1 + + elif state == MultipartState.HEADER_FIELD: + # If we've reached a CR at the beginning of a header, it means + # that we've reached the second of 2 newlines, and so there are + # no more headers to parse. + if c == CR and index == 0: + delete_mark("header_field") + state = MultipartState.HEADERS_ALMOST_DONE + i += 1 + continue + + # The field name runs until the colon; jump straight to it and + # validate the whole span at once instead of byte by byte. + colon = data.find(b":", i, length) + end = colon if colon != -1 else length + + # Enforce the size limit before slicing and validating, so an oversized header + # name fails fast instead of copying and scanning a potentially huge span. + advance_header_size(end - i if colon == -1 else end - i + 1) + + field = data[i:end] + if field.translate(None, TOKEN_CHARS): + bad = next(b for b in field if b not in TOKEN_CHARS_SET) + bad_i = i + field.index(bad) + msg = "Found invalid character %r in header at %d" % (bad, bad_i) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=bad_i) + + index += end - i + if colon == -1: + # Field name continues into the next chunk. + i = length + else: + # A 0-length header is an error. + if index == 0: + msg = "Found 0-length header at %d" % (i,) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + # Call our callback with the header field. + i = colon + data_callback("header_field", i) + + # Move to parsing the header value. + state = MultipartState.HEADER_VALUE_START + + elif state == MultipartState.HEADER_VALUE_START: + # Skip leading spaces. + if c == SPACE: + advance_header_size() + i += 1 + continue + + # Mark the start of the header value. + set_mark("header_value") + + # Move to the header-value state, reprocessing this character. + state = MultipartState.HEADER_VALUE + i -= 1 + + elif state == MultipartState.HEADER_VALUE: + # The value runs until the terminating CR; jump straight to it + # instead of inspecting every byte. + cr = data.find(b"\r", i, length) + end = cr if cr != -1 else length + advance_header_size(end - i) + if cr != -1: + i = cr + data_callback("header_value", i) + self.callback("header_end") + current_header_size = 0 + state = MultipartState.HEADER_VALUE_ALMOST_DONE + else: + i = length + + elif state == MultipartState.HEADER_VALUE_ALMOST_DONE: + # The last character should be a LF. If not, it's an error. + if c != LF: + msg = f"Did not find LF character at end of header (found {c!r})" + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + # Move back to the start of another header. Note that if that + # state detects ANOTHER newline, it'll trigger the end of our + # headers. + state = MultipartState.HEADER_FIELD_START + + elif state == MultipartState.HEADERS_ALMOST_DONE: + # We're almost done our headers. This is reached when we parse + # a CR at the beginning of a header, so our next character + # should be a LF, or it's an error. + if c != LF: + msg = f"Did not find LF at end of headers (found {c!r})" + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + self.callback("headers_finished") + state = MultipartState.PART_DATA_START + + elif state == MultipartState.PART_DATA_START: + # Mark the start of our part data. + set_mark("part_data") + + # Start processing part data, including this character. + state = MultipartState.PART_DATA + i -= 1 + + elif state == MultipartState.PART_DATA: + # We're processing our part data right now. During this, we + # need to efficiently search for our boundary, since any data + # on any number of lines can be a part of the current data. + + # Save the current value of our index. We use this in case we + # find part of a boundary, but it doesn't match fully. + prev_index = index + + # If our index is 0, we're starting a new part, so start our + # search. + if index == 0: + # The most common case is likely to be that the whole + # boundary is present in the buffer. + # Calling `find` is much faster than iterating here. + i0 = data.find(boundary, i, length) + if i0 >= 0: + # We matched the whole boundary string. + index = boundary_length - 1 + i = i0 + boundary_length - 1 + c = data[i] + else: + # No whole boundary, but the tail may hold a partial one + # that completes in the next chunk. Boundary starts with + # CR, which an RFC boundary contains nowhere else, so the + # last CR in the tail is the only candidate prefix start. + k = data.rfind(boundary[:1], max(i, length - boundary_length + 1), length) + if k != -1 and boundary.startswith(data[k:length]): + index = length - k + # Carry the partial via index; the end-of-chunk flush + # emits the data before it and re-marks the lookbehind. + i = length + continue + + # Now, we have a couple of cases here. If our index is before + # the end of the boundary... + if index < boundary_length: + # If the character matches... + if boundary[index] == c: + # The current character matches, so continue! + index += 1 + else: + index = 0 + + # Our index is equal to the length of our boundary! + elif index == boundary_length: + # First we increment it. + index += 1 + + # Now, if we've reached a newline, we need to set this as + # the potential end of our boundary. + if c == CR: + flags |= FLAG_PART_BOUNDARY + + # Otherwise, if this is a hyphen, we might be at the last + # of all boundaries. + elif c == HYPHEN: + flags |= FLAG_LAST_BOUNDARY + + # Otherwise, we reset our index, since this isn't either a + # newline or a hyphen. + else: + index = 0 + + # Our index is right after the part boundary, which should be + # a LF. + elif index == boundary_length + 1: + # If we're at a part boundary (i.e. we've seen a CR + # character already)... + if flags & FLAG_PART_BOUNDARY: + # We need a LF character next. + if c == LF: + # Unset the part boundary flag. + flags &= ~FLAG_PART_BOUNDARY + + # We have identified a boundary, callback for any data before it. + data_callback("part_data", i - index) + # Callback indicating that we've reached the end of + # a part, and are starting a new one. + self.callback("part_end") + self.callback("part_begin") + current_header_count = 0 + current_header_size = 0 + + # Move to parsing new headers. + index = 0 + state = MultipartState.HEADER_FIELD_START + i += 1 + continue + + # We didn't find an LF character, so no match. Reset + # our index and clear our flag. + index = 0 + flags &= ~FLAG_PART_BOUNDARY + + # Otherwise, if we're at the last boundary (i.e. we've + # seen a hyphen already)... + elif flags & FLAG_LAST_BOUNDARY: + # We need a second hyphen here. + if c == HYPHEN: + # We have identified a boundary, callback for any data before it. + data_callback("part_data", i - index) + # Callback to end the current part, and then the + # message. + self.callback("part_end") + self.callback("end") + state = MultipartState.END + else: + # No match, so reset index. + index = 0 + + # Otherwise, our index is 0. If the previous index is not, it + # means we reset something, and we need to take the data we + # thought was part of our boundary and send it along as actual + # data. + if index == 0 and prev_index > 0: + # Overwrite our previous index. + prev_index = 0 + + # Re-consider the current character, since this could be + # the start of the boundary itself. + i -= 1 + + elif state == MultipartState.END_BOUNDARY: + if index == boundary_length - 1: + if c != HYPHEN: + msg = "Did not find - at end of boundary (%d)" % (i,) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + index += 1 + self.callback("end") + state = MultipartState.END + + elif state == MultipartState.END: + # Silently discard any epilogue data (RFC 2046 section 5.1.1 allows a CRLF and optional + # epilogue after the closing boundary). Django and Werkzeug do the same. + i = length + break + + else: # pragma: no cover (error case) + # We got into a strange state somehow! Just stop processing. + msg = "Reached an unknown state %d at %d" % (state, i) + self.logger.warning(msg) + raise MultipartParseError(msg, offset=i) + + # Move to the next byte. + i += 1 + + # We call our callbacks with any remaining data. Note that we pass + # the 'remaining' flag, which sets the mark back to 0 instead of + # deleting it, if it's found. This is because, if the mark is found + # at this point, we assume that there's data for one of these things + # that has been parsed, but not yet emitted. And, as such, it implies + # that we haven't yet reached the end of this 'thing'. So, by setting + # the mark to 0, we cause any data callbacks that take place in future + # calls to this function to start from the beginning of that buffer. + data_callback("header_field", length, True) + data_callback("header_value", length, True) + data_callback("part_data", length - index, True) + + # Save values to locals. + self.state = state + self.index = index + self.flags = flags + self._current_header_count = current_header_count + self._current_header_size = current_header_size + + # Return our data length to indicate no errors, and that we processed + # all of it. + return length + + def finalize(self) -> None: + """Finalize this parser, which signals to that we are finished parsing. + + Note: It does not currently, but in the future, it will verify that we + are in the final state of the parser (i.e. the end of the multipart + message is well-formed), and, if not, throw an error. + """ + # TODO: verify that we're in the state MultipartState.END, otherwise throw an + # error or otherwise state that we're not finished parsing. + pass + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(boundary={self.boundary!r})" + + +class FormParser: + """This class is the all-in-one form parser. Given all the information + necessary to parse a form, it will instantiate the correct parser, create + the proper :class:`Field` and :class:`File` classes to store the data that + is parsed, and call the two given callbacks with each field and file as + they become available. + + Args: + content_type: The Content-Type of the incoming request. This is used to select the appropriate parser. + on_field: The callback to call when a field has been parsed and is ready for usage. See above for parameters. + on_file: The callback to call when a file has been parsed and is ready for usage. See above for parameters. + on_end: An optional callback to call when all fields and files in a request has been parsed. Can be None. + boundary: If the request is a multipart/form-data request, this should be the boundary of the request, as given + in the Content-Type header, as a bytestring. + file_name: If the request is of type application/octet-stream, then the body of the request will not contain any + information about the uploaded file. In such cases, you can provide the file name of the uploaded file + manually. + config: Configuration to use for this FormParser. The default values are taken from the DEFAULT_CONFIG value, + and then any keys present in this dictionary will overwrite the default values. + """ + + #: This is the default configuration for our form parser. + #: Note: all file sizes should be in bytes. + DEFAULT_CONFIG: FormParserConfig = { + "MAX_BODY_SIZE": float("inf"), + "MAX_HEADER_COUNT": DEFAULT_MAX_HEADER_COUNT, + "MAX_HEADER_SIZE": DEFAULT_MAX_HEADER_SIZE, + "MAX_MEMORY_FILE_SIZE": 1 * 1024 * 1024, + "UPLOAD_DIR": None, + "UPLOAD_DELETE_TMP": True, + "UPLOAD_KEEP_FILENAME": False, + "UPLOAD_KEEP_EXTENSIONS": False, + # Error on invalid Content-Transfer-Encoding? + "UPLOAD_ERROR_ON_BAD_CTE": False, + } + + def __init__( + self, + content_type: str, + on_field: Callable[[Field], None] | None, + on_file: Callable[[File], None] | None, + on_end: Callable[[], None] | None = None, + boundary: bytes | str | None = None, + file_name: bytes | None = None, + config: dict[Any, Any] = {}, + ) -> None: + self.logger = logging.getLogger(__name__) + + # Save variables. + self.content_type = content_type + self.boundary = boundary + self.bytes_received = 0 + self.parser = None + + # Save callbacks. + self.on_field = on_field + self.on_file = on_file + self.on_end = on_end + + # Set configuration options. + self.config: FormParserConfig = self.DEFAULT_CONFIG.copy() + self.config.update(config) # type: ignore[typeddict-item] + + parser: OctetStreamParser | MultipartParser | QuerystringParser | None = None + + # Depending on the Content-Type, we instantiate the correct parser. + if content_type == "application/octet-stream": + file: File | None = None + + def on_start() -> None: + nonlocal file + file = File(file_name, None, config=self.config) + + def on_data(data: bytes, start: int, end: int) -> None: + nonlocal file + assert file is not None + file.write(data[start:end]) + + def _on_end() -> None: + nonlocal file + assert file is not None + # Finalize the file itself. + file.finalize() + + # Call our callback. + if on_file: + on_file(file) + + # Call the on-end callback. + if self.on_end is not None: + self.on_end() + + # Instantiate an octet-stream parser + parser = OctetStreamParser( + callbacks={"on_start": on_start, "on_data": on_data, "on_end": _on_end}, + max_size=self.config["MAX_BODY_SIZE"], + ) + + elif content_type == "application/x-www-form-urlencoded" or content_type == "application/x-url-encoded": + name_buffer: list[bytes] = [] + + f: Field | None = None + + def on_field_start() -> None: + pass + + def on_field_name(data: bytes, start: int, end: int) -> None: + name_buffer.append(data[start:end]) + + def on_field_data(data: bytes, start: int, end: int) -> None: + nonlocal f + if f is None: + f = Field(b"".join(name_buffer)) + del name_buffer[:] + f.write(data[start:end]) + + def on_field_end() -> None: + nonlocal f + # Finalize and call callback. + if f is None: + # If we get here, it's because there was no field data. + # We create a field, set it to None, and then continue. + f = Field(b"".join(name_buffer)) + del name_buffer[:] + f.set_none() + + f.finalize() + if on_field: + on_field(f) + f = None + + def _on_end() -> None: + if self.on_end is not None: + self.on_end() + + # Instantiate parser. + parser = QuerystringParser( + callbacks={ + "on_field_start": on_field_start, + "on_field_name": on_field_name, + "on_field_data": on_field_data, + "on_field_end": on_field_end, + "on_end": _on_end, + }, + max_size=self.config["MAX_BODY_SIZE"], + ) + + elif content_type == "multipart/form-data": + if boundary is None: + self.logger.error("No boundary given") + raise FormParserError("No boundary given") + + header_name: list[bytes] = [] + header_value: list[bytes] = [] + headers: dict[bytes, bytes] = {} + + f_multi: File | Field | None = None + writer: File | Field | Base64Decoder | QuotedPrintableDecoder | None = None + is_file = False + + def on_part_begin() -> None: + # Reset headers in case this isn't the first part. + nonlocal headers + headers = {} + + def on_part_data(data: bytes, start: int, end: int) -> None: + nonlocal writer + assert writer is not None + writer.write(data[start:end]) + # TODO: check for error here. + + def on_part_end() -> None: + nonlocal f_multi, is_file + assert f_multi is not None + f_multi.finalize() + if is_file: + if on_file: + assert isinstance(f_multi, File) + on_file(f_multi) + else: + if on_field: + assert isinstance(f_multi, Field) + on_field(f_multi) + + def on_header_field(data: bytes, start: int, end: int) -> None: + header_name.append(data[start:end]) + + def on_header_value(data: bytes, start: int, end: int) -> None: + header_value.append(data[start:end]) + + def on_header_end() -> None: + headers[b"".join(header_name).lower()] = b"".join(header_value) + del header_name[:] + del header_value[:] + + def on_headers_finished() -> None: + nonlocal is_file, f_multi, writer + # Reset the 'is file' flag. + is_file = False + + # Parse the content-disposition header. + content_disp = headers.get(b"content-disposition") + disp, options = parse_options_header(content_disp) + + # Get the field and filename. + field_name = options.get(b"name") + file_name = options.get(b"filename") + # RFC 7578 §4.2: each part MUST have a Content-Disposition header with a "name" parameter. + if field_name is None: + raise FormParserError(f'Field name not found in Content-Disposition: "{content_disp!r}"') + + # Create the proper class. + content_type_b = headers.get(b"content-type") + content_type = content_type_b.decode("latin-1") if content_type_b is not None else None + if file_name is None: + f_multi = Field(field_name, content_type=content_type) + else: + f_multi = File(file_name, field_name, config=self.config, content_type=content_type) + is_file = True + + # Parse the given Content-Transfer-Encoding to determine what + # we need to do with the incoming data. + # TODO: check that we properly handle 8bit / 7bit encoding. + # RFC 2045 section 6.1: Content-Transfer-Encoding values are case-insensitive. + # https://www.rfc-editor.org/rfc/rfc2045#section-6.1 + transfer_encoding = headers.get(b"content-transfer-encoding", b"7bit").lower() + + if transfer_encoding in (b"binary", b"8bit", b"7bit"): + writer = f_multi + + elif transfer_encoding == b"base64": + writer = Base64Decoder(f_multi) + + elif transfer_encoding == b"quoted-printable": + writer = QuotedPrintableDecoder(f_multi) + + else: + self.logger.warning("Unknown Content-Transfer-Encoding: %r", transfer_encoding) + if self.config["UPLOAD_ERROR_ON_BAD_CTE"]: + raise FormParserError(f'Unknown Content-Transfer-Encoding "{transfer_encoding!r}"') + else: + # If we aren't erroring, then we just treat this as an + # unencoded Content-Transfer-Encoding. + writer = f_multi + + def _on_end() -> None: + nonlocal writer + if writer is not None: + writer.finalize() + if self.on_end is not None: + self.on_end() + + # Instantiate a multipart parser. + parser = MultipartParser( + boundary, + callbacks={ + "on_part_begin": on_part_begin, + "on_part_data": on_part_data, + "on_part_end": on_part_end, + "on_header_field": on_header_field, + "on_header_value": on_header_value, + "on_header_end": on_header_end, + "on_headers_finished": on_headers_finished, + "on_end": _on_end, + }, + max_size=self.config["MAX_BODY_SIZE"], + max_header_count=self.config["MAX_HEADER_COUNT"], + max_header_size=self.config["MAX_HEADER_SIZE"], + ) + + else: + self.logger.warning("Unknown Content-Type: %r", content_type) + raise FormParserError(f"Unknown Content-Type: {content_type}") + + self.parser = parser + + def write(self, data: bytes) -> int: + """Write some data. The parser will forward this to the appropriate + underlying parser. + + Args: + data: The data to write. + + Returns: + The number of bytes processed. + """ + self.bytes_received += len(data) + # TODO: check the parser's return value for errors? + assert self.parser is not None + return self.parser.write(data) + + def finalize(self) -> None: + """Finalize the parser.""" + if self.parser is not None and hasattr(self.parser, "finalize"): + self.parser.finalize() + + def close(self) -> None: + """Close the parser.""" + if self.parser is not None and hasattr(self.parser, "close"): + self.parser.close() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(content_type={self.content_type!r}, parser={self.parser!r})" + + +def create_form_parser( + headers: dict[str, bytes], + on_field: Callable[[Field], None] | None, + on_file: Callable[[File], None] | None, + config: dict[Any, Any] = {}, +) -> FormParser: + """This function is a helper function to aid in creating a FormParser + instances. Given a dictionary-like headers object, it will determine + the correct information needed, instantiate a FormParser with the + appropriate values and given callbacks, and then return the corresponding + parser. + + Args: + headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. + on_field: Callback to call with each parsed field. + on_file: Callback to call with each parsed file. + config: Configuration variables to pass to the FormParser. + """ + content_type: str | bytes | None = headers.get("Content-Type") + if content_type is None: + logging.getLogger(__name__).warning("No Content-Type header given") + raise ValueError("No Content-Type header given!") + + # Boundaries are optional (the FormParser will raise if one is needed + # but not given). + content_type, params = parse_options_header(content_type) + boundary = params.get(b"boundary") + + # We need content_type to be a string, not a bytes object. + content_type = content_type.decode("latin-1") + + # Instantiate a form parser. + form_parser = FormParser(content_type, on_field, on_file, boundary=boundary, config=config) + + # Return our parser. + return form_parser + + +def parse_form( + headers: dict[str, bytes], + input_stream: SupportsRead, + on_field: Callable[[Field], None] | None, + on_file: Callable[[File], None] | None, + chunk_size: int = 1048576, +) -> None: + """This function is useful if you just want to parse a request body, + without too much work. Pass it a dictionary-like object of the request's + headers, and a file-like object for the input stream, along with two + callbacks that will get called whenever a field or file is parsed. + + Args: + headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. + input_stream: A file-like object that represents the request body. The read() method must return bytestrings. + on_field: Callback to call with each parsed field. + on_file: Callback to call with each parsed file. + chunk_size: The maximum size to read from the input stream and write to the parser at one time. + Defaults to 1 MiB. + """ + if chunk_size < 1: + raise ValueError(f"chunk_size must be a positive number, not {chunk_size!r}") + + # Create our form parser. + parser = create_form_parser(headers, on_field, on_file) + + # Read chunks of 1MiB and write to the parser, but never read more than + # the given Content-Length, if any. + content_length: int | float | bytes | None = headers.get("Content-Length") + if content_length is not None: + content_length = int(content_length) + if content_length < 0: + raise ValueError("Content-Length must be non-negative") + else: + content_length = float("inf") + bytes_read = 0 + + while True: + # Read only up to the Content-Length given. + max_readable = int(min(content_length - bytes_read, chunk_size)) + buff = input_stream.read(max_readable) + + # Write to the parser and update our length. + parser.write(buff) + bytes_read += len(buff) + + # If we get a buffer that's smaller than the size requested, or if we + # have read up to our content length, we're done. + if len(buff) != max_readable or bytes_read == content_length: + break + + # Tell our parser that we're done writing data. + parser.finalize() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA new file mode 100644 index 0000000..9a6abbc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA @@ -0,0 +1,64 @@ +Metadata-Version: 2.4 +Name: referencing +Version: 0.37.0 +Summary: JSON Referencing + Python +Project-URL: Documentation, https://referencing.readthedocs.io/ +Project-URL: Homepage, https://github.com/python-jsonschema/referencing +Project-URL: Issues, https://github.com/python-jsonschema/referencing/issues/ +Project-URL: Funding, https://github.com/sponsors/Julian +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-referencing?utm_source=pypi-referencing&utm_medium=referral&utm_campaign=pypi-link +Project-URL: Changelog, https://referencing.readthedocs.io/en/stable/changes/ +Project-URL: Source, https://github.com/python-jsonschema/referencing +Author-email: Julian Berman +License-Expression: MIT +License-File: COPYING +Keywords: asyncapi,json,jsonschema,openapi,referencing +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: File Formats :: JSON +Classifier: Topic :: File Formats :: JSON :: JSON Schema +Requires-Python: >=3.10 +Requires-Dist: attrs>=22.2.0 +Requires-Dist: rpds-py>=0.7.0 +Requires-Dist: typing-extensions>=4.4.0; python_version < '3.13' +Description-Content-Type: text/x-rst + +=============== +``referencing`` +=============== + +|PyPI| |Pythons| |CI| |ReadTheDocs| |pre-commit| + +.. |PyPI| image:: https://img.shields.io/pypi/v/referencing.svg + :alt: PyPI version + :target: https://pypi.org/project/referencing/ + +.. |Pythons| image:: https://img.shields.io/pypi/pyversions/referencing.svg + :alt: Supported Python versions + :target: https://pypi.org/project/referencing/ + +.. |CI| image:: https://github.com/python-jsonschema/referencing/workflows/CI/badge.svg + :alt: Build status + :target: https://github.com/python-jsonschema/referencing/actions?query=workflow%3ACI + +.. |ReadTheDocs| image:: https://readthedocs.org/projects/referencing/badge/?version=stable&style=flat + :alt: ReadTheDocs status + :target: https://referencing.readthedocs.io/en/stable/ + +.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/python-jsonschema/referencing/main.svg + :alt: pre-commit.ci status + :target: https://results.pre-commit.ci/latest/github/python-jsonschema/referencing/main + + +An implementation-agnostic implementation of JSON reference resolution. + +See `the documentation `_ for more details. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD new file mode 100644 index 0000000..bb50514 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD @@ -0,0 +1,19 @@ +referencing/__init__.py,sha256=5IZKXaAH_FWyCJRkaTn1XcptLfg9cveLb9u5nYUxJKs,207 +referencing/_attrs.py,sha256=bgT-KMhDVLeGtWxM_SGKYeLaZBFzT2kUVFdAkOcXi8g,791 +referencing/_attrs.pyi,sha256=g2wX-aLEqQAvWU-s0qIN2OMGAqjOA0R72R_uSffO8_M,573 +referencing/_core.py,sha256=2SPGfaTvzA3sRqbj_4R2wC--fiXolCUVpLFLJJ6zdZ8,24732 +referencing/exceptions.py,sha256=zFgaEg6WiKeT58MQuKNsgGDnHszp26c4oReC6sF9gHM,4176 +referencing/jsonschema.py,sha256=jXZ6t6x9oTvTHMIC0tRyaFjW5r2qeHObYuu7UhSfK3Q,18615 +referencing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +referencing/retrieval.py,sha256=cn9EeAdaaTbnMDdC4JRIhiEpXDjflRdHCOLFrw1ZhLA,2729 +referencing/typing.py,sha256=WjUbnZ6jPAd31cnCFAaeWIVENzyHtHdJyOlelv1GY70,1445 +referencing/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +referencing/tests/test_core.py,sha256=eap0CAaI23vjMIbVyEj92qLddp3iHH3AxC55CKUN4LU,37854 +referencing/tests/test_exceptions.py,sha256=7eOdHyobXMt7-h5AnnH7u8iw2uHPaH7U4Bs9JhLgjWo,934 +referencing/tests/test_jsonschema.py,sha256=4QnjUWOAMAn5yeA8ZtldJkhI54vwKWJWB0LDzNdx5xc,11687 +referencing/tests/test_referencing_suite.py,sha256=wD6veMfLsUu0s4MLjm7pS8cg4cIfL7FMBENngk73zCI,2335 +referencing/tests/test_retrieval.py,sha256=vcbnfA4TqVeqUzW073wO-nLeqVIv0rQZWNWv0z9km48,3719 +referencing-0.37.0.dist-info/METADATA,sha256=yTsA5qsVJP1ghIN2YEwYmhFDdHdkdcJaMdV75Hsp3mo,2845 +referencing-0.37.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +referencing-0.37.0.dist-info/licenses/COPYING,sha256=QtzWNJX4e063x3V6-jebtVpT-Ur9el9lfZrfVyNuUVw,1057 +referencing-0.37.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING new file mode 100644 index 0000000..a9f853e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2022 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py new file mode 100644 index 0000000..e09207d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/__init__.py @@ -0,0 +1,7 @@ +""" +Cross-specification, implementation-agnostic JSON referencing. +""" + +from referencing._core import Anchor, Registry, Resource, Specification + +__all__ = ["Anchor", "Registry", "Resource", "Specification"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py new file mode 100644 index 0000000..ae85b86 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import NoReturn, TypeVar + +from attrs import define as _define, frozen as _frozen + +_T = TypeVar("_T") + + +def define(cls: type[_T]) -> type[_T]: # pragma: no cover + cls.__init_subclass__ = _do_not_subclass + return _define(cls) + + +def frozen(cls: type[_T]) -> type[_T]: + cls.__init_subclass__ = _do_not_subclass + return _frozen(cls) + + +class UnsupportedSubclassing(Exception): + def __str__(self): + return ( + "Subclassing is not part of referencing's public API. " + "If no other suitable API exists for what you're trying to do, " + "feel free to file an issue asking for one." + ) + + +@staticmethod +def _do_not_subclass() -> NoReturn: # pragma: no cover + raise UnsupportedSubclassing() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi new file mode 100644 index 0000000..eca6db2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi @@ -0,0 +1,21 @@ +from collections.abc import Callable +from typing import Any, TypeVar + +from attr import attrib, field + +class UnsupportedSubclassing(Exception): ... + +_T = TypeVar("_T") + +def __dataclass_transform__( + *, + frozen_default: bool = False, + field_descriptors: tuple[type | Callable[..., Any], ...] = ..., +) -> Callable[[_T], _T]: ... +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define(cls: type[_T]) -> type[_T]: ... +@__dataclass_transform__( + frozen_default=True, + field_descriptors=(attrib, field), +) +def frozen(cls: type[_T]) -> type[_T]: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py new file mode 100644 index 0000000..4206845 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/_core.py @@ -0,0 +1,739 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterable, Iterator, Sequence +from enum import Enum +from typing import Any, ClassVar, Generic, Protocol +from urllib.parse import unquote, urldefrag, urljoin + +from attrs import evolve, field +from rpds import HashTrieMap, HashTrieSet, List + +try: + from typing_extensions import TypeVar +except ImportError: # pragma: no cover + from typing import TypeVar + +from referencing import exceptions +from referencing._attrs import frozen +from referencing.typing import URI, Anchor as AnchorType, D, Mapping, Retrieve + +EMPTY_UNCRAWLED: HashTrieSet[URI] = HashTrieSet() +EMPTY_PREVIOUS_RESOLVERS: List[URI] = List() + + +class _Unset(Enum): + """ + What sillyness... + """ + + SENTINEL = 1 + + +_UNSET = _Unset.SENTINEL + + +class _MaybeInSubresource(Protocol[D]): + def __call__( + self, + segments: Sequence[int | str], + resolver: Resolver[D], + subresource: Resource[D], + ) -> Resolver[D]: ... + + +def _detect_or_error(contents: D) -> Specification[D]: + if not isinstance(contents, Mapping): + raise exceptions.CannotDetermineSpecification(contents) + + jsonschema_dialect_id = contents.get("$schema") # type: ignore[reportUnknownMemberType] + if not isinstance(jsonschema_dialect_id, str): + raise exceptions.CannotDetermineSpecification(contents) + + from referencing.jsonschema import specification_with + + return specification_with(jsonschema_dialect_id) + + +def _detect_or_default( + default: Specification[D], +) -> Callable[[D], Specification[D]]: + def _detect(contents: D) -> Specification[D]: + if not isinstance(contents, Mapping): + return default + + jsonschema_dialect_id = contents.get("$schema") # type: ignore[reportUnknownMemberType] + if jsonschema_dialect_id is None: + return default + + from referencing.jsonschema import specification_with + + return specification_with( + jsonschema_dialect_id, # type: ignore[reportUnknownArgumentType] + default=default, + ) + + return _detect + + +class _SpecificationDetector: + def __get__( + self, + instance: Specification[D] | None, + cls: type[Specification[D]], + ) -> Callable[[D], Specification[D]]: + if instance is None: + return _detect_or_error + else: + return _detect_or_default(instance) + + +@frozen +class Specification(Generic[D]): + """ + A specification which defines referencing behavior. + + The various methods of a `Specification` allow for varying referencing + behavior across JSON Schema specification versions, etc. + """ + + #: A short human-readable name for the specification, used for debugging. + name: str + + #: Find the ID of a given document. + id_of: Callable[[D], URI | None] + + #: Retrieve the subresources of the given document (without traversing into + #: the subresources themselves). + subresources_of: Callable[[D], Iterable[D]] + + #: While resolving a JSON pointer, conditionally enter a subresource + #: (if e.g. we have just entered a keyword whose value is a subresource) + maybe_in_subresource: _MaybeInSubresource[D] + + #: Retrieve the anchors contained in the given document. + _anchors_in: Callable[ + [Specification[D], D], + Iterable[AnchorType[D]], + ] = field(alias="anchors_in") + + #: An opaque specification where resources have no subresources + #: nor internal identifiers. + OPAQUE: ClassVar[Specification[Any]] + + #: Attempt to discern which specification applies to the given contents. + #: + #: May be called either as an instance method or as a class method, with + #: slightly different behavior in the following case: + #: + #: Recall that not all contents contains enough internal information about + #: which specification it is written for -- the JSON Schema ``{}``, + #: for instance, is valid under many different dialects and may be + #: interpreted as any one of them. + #: + #: When this method is used as an instance method (i.e. called on a + #: specific specification), that specification is used as the default + #: if the given contents are unidentifiable. + #: + #: On the other hand when called as a class method, an error is raised. + #: + #: To reiterate, ``DRAFT202012.detect({})`` will return ``DRAFT202012`` + #: whereas the class method ``Specification.detect({})`` will raise an + #: error. + #: + #: (Note that of course ``DRAFT202012.detect(...)`` may return some other + #: specification when given a schema which *does* identify as being for + #: another version). + #: + #: Raises: + #: + #: `CannotDetermineSpecification` + #: + #: if the given contents don't have any discernible + #: information which could be used to guess which + #: specification they identify as + detect = _SpecificationDetector() + + def __repr__(self) -> str: + return f"" + + def anchors_in(self, contents: D): + """ + Retrieve the anchors contained in the given document. + """ + return self._anchors_in(self, contents) + + def create_resource(self, contents: D) -> Resource[D]: + """ + Create a resource which is interpreted using this specification. + """ + return Resource(contents=contents, specification=self) + + +Specification.OPAQUE = Specification( + name="opaque", + id_of=lambda contents: None, + subresources_of=lambda contents: [], + anchors_in=lambda specification, contents: [], + maybe_in_subresource=lambda segments, resolver, subresource: resolver, +) + + +@frozen +class Resource(Generic[D]): + r""" + A document (deserialized JSON) with a concrete interpretation under a spec. + + In other words, a Python object, along with an instance of `Specification` + which describes how the document interacts with referencing -- both + internally (how it refers to other `Resource`\ s) and externally (how it + should be identified such that it is referenceable by other documents). + """ + + contents: D + _specification: Specification[D] = field(alias="specification") + + @classmethod + def from_contents( + cls, + contents: D, + default_specification: ( + type[Specification[D]] | Specification[D] + ) = Specification, + ) -> Resource[D]: + """ + Create a resource guessing which specification applies to the contents. + + Raises: + + `CannotDetermineSpecification` + + if the given contents don't have any discernible + information which could be used to guess which + specification they identify as + + """ + specification = default_specification.detect(contents) + return specification.create_resource(contents=contents) + + @classmethod + def opaque(cls, contents: D) -> Resource[D]: + """ + Create an opaque `Resource` -- i.e. one with opaque specification. + + See `Specification.OPAQUE` for details. + """ + return Specification.OPAQUE.create_resource(contents=contents) + + def id(self) -> URI | None: + """ + Retrieve this resource's (specification-specific) identifier. + """ + id = self._specification.id_of(self.contents) + if id is None: + return + return id.rstrip("#") + + def subresources(self) -> Iterable[Resource[D]]: + """ + Retrieve this resource's subresources. + """ + return ( + Resource.from_contents( + each, + default_specification=self._specification, + ) + for each in self._specification.subresources_of(self.contents) + ) + + def anchors(self) -> Iterable[AnchorType[D]]: + """ + Retrieve this resource's (specification-specific) identifier. + """ + return self._specification.anchors_in(self.contents) + + def pointer(self, pointer: str, resolver: Resolver[D]) -> Resolved[D]: + """ + Resolve the given JSON pointer. + + Raises: + + `exceptions.PointerToNowhere` + + if the pointer points to a location not present in the document + + """ + if not pointer: + return Resolved(contents=self.contents, resolver=resolver) + + contents = self.contents + segments: list[int | str] = [] + for segment in unquote(pointer[1:]).split("/"): + if isinstance(contents, Sequence): + segment = int(segment) + else: + segment = segment.replace("~1", "/").replace("~0", "~") + try: + contents = contents[segment] # type: ignore[reportUnknownArgumentType] + except LookupError as lookup_error: + error = exceptions.PointerToNowhere(ref=pointer, resource=self) + raise error from lookup_error + + segments.append(segment) + last = resolver + resolver = self._specification.maybe_in_subresource( + segments=segments, + resolver=resolver, + subresource=self._specification.create_resource(contents), + ) + if resolver is not last: + segments = [] + return Resolved(contents=contents, resolver=resolver) # type: ignore[reportUnknownArgumentType] + + +def _fail_to_retrieve(uri: URI): + raise exceptions.NoSuchResource(ref=uri) + + +@frozen +class Registry(Mapping[URI, Resource[D]]): + r""" + A registry of `Resource`\ s, each identified by their canonical URIs. + + Registries store a collection of in-memory resources, and optionally + enable additional resources which may be stored elsewhere (e.g. in a + database, a separate set of files, over the network, etc.). + + They also lazily walk their known resources, looking for subresources + within them. In other words, subresources contained within any added + resources will be retrievable via their own IDs (though this discovery of + subresources will be delayed until necessary). + + Registries are immutable, and their methods return new instances of the + registry with the additional resources added to them. + + The ``retrieve`` argument can be used to configure retrieval of resources + dynamically, either over the network, from a database, or the like. + Pass it a callable which will be called if any URI not present in the + registry is accessed. It must either return a `Resource` or else raise a + `NoSuchResource` exception indicating that the resource does not exist + even according to the retrieval logic. + """ + + _resources: HashTrieMap[URI, Resource[D]] = field( + default=HashTrieMap(), + converter=HashTrieMap.convert, # type: ignore[reportGeneralTypeIssues] + alias="resources", + ) + _anchors: HashTrieMap[tuple[URI, str], AnchorType[D]] = HashTrieMap() + _uncrawled: HashTrieSet[URI] = EMPTY_UNCRAWLED + _retrieve: Retrieve[D] = field(default=_fail_to_retrieve, alias="retrieve") + + def __getitem__(self, uri: URI) -> Resource[D]: + """ + Return the (already crawled) `Resource` identified by the given URI. + """ + try: + return self._resources[uri.rstrip("#")] + except KeyError: + raise exceptions.NoSuchResource(ref=uri) from None + + def __iter__(self) -> Iterator[URI]: + """ + Iterate over all crawled URIs in the registry. + """ + return iter(self._resources) + + def __len__(self) -> int: + """ + Count the total number of fully crawled resources in this registry. + """ + return len(self._resources) + + def __rmatmul__( + self, + new: Resource[D] | Iterable[Resource[D]], + ) -> Registry[D]: + """ + Create a new registry with resource(s) added using their internal IDs. + + Resources must have a internal IDs (e.g. the :kw:`$id` keyword in + modern JSON Schema versions), otherwise an error will be raised. + + Both a single resource as well as an iterable of resources works, i.e.: + + * ``resource @ registry`` or + + * ``[iterable, of, multiple, resources] @ registry`` + + which -- again, assuming the resources have internal IDs -- is + equivalent to calling `Registry.with_resources` as such: + + .. code:: python + + registry.with_resources( + (resource.id(), resource) for resource in new_resources + ) + + Raises: + + `NoInternalID` + + if the resource(s) in fact do not have IDs + + """ + if isinstance(new, Resource): + new = (new,) + + resources = self._resources + uncrawled = self._uncrawled + for resource in new: + id = resource.id() + if id is None: + raise exceptions.NoInternalID(resource=resource) + uncrawled = uncrawled.insert(id) + resources = resources.insert(id, resource) + return evolve(self, resources=resources, uncrawled=uncrawled) + + def __repr__(self) -> str: + size = len(self) + pluralized = "resource" if size == 1 else "resources" + if self._uncrawled: + uncrawled = len(self._uncrawled) + if uncrawled == size: + summary = f"uncrawled {pluralized}" + else: + summary = f"{pluralized}, {uncrawled} uncrawled" + else: + summary = f"{pluralized}" + return f"" + + def get_or_retrieve(self, uri: URI) -> Retrieved[D, Resource[D]]: + """ + Get a resource from the registry, crawling or retrieving if necessary. + + May involve crawling to find the given URI if it is not already known, + so the returned object is a `Retrieved` object which contains both the + resource value as well as the registry which ultimately contained it. + """ + resource = self._resources.get(uri) + if resource is not None: + return Retrieved(registry=self, value=resource) + + registry = self.crawl() + resource = registry._resources.get(uri) + if resource is not None: + return Retrieved(registry=registry, value=resource) + + try: + resource = registry._retrieve(uri) + except ( + exceptions.CannotDetermineSpecification, + exceptions.NoSuchResource, + ): + raise + except Exception as error: + raise exceptions.Unretrievable(ref=uri) from error + else: + registry = registry.with_resource(uri, resource) + return Retrieved(registry=registry, value=resource) + + def remove(self, uri: URI): + """ + Return a registry with the resource identified by a given URI removed. + """ + if uri not in self._resources: + raise exceptions.NoSuchResource(ref=uri) + + return evolve( + self, + resources=self._resources.remove(uri), + uncrawled=self._uncrawled.discard(uri), + anchors=HashTrieMap( + (k, v) for k, v in self._anchors.items() if k[0] != uri + ), + ) + + def anchor(self, uri: URI, name: str): + """ + Retrieve a given anchor from a resource which must already be crawled. + """ + value = self._anchors.get((uri, name)) + if value is not None: + return Retrieved(value=value, registry=self) + + registry = self.crawl() + value = registry._anchors.get((uri, name)) + if value is not None: + return Retrieved(value=value, registry=registry) + + resource = self[uri] + canonical_uri = resource.id() + if canonical_uri is not None: + value = registry._anchors.get((canonical_uri, name)) + if value is not None: + return Retrieved(value=value, registry=registry) + + if "/" in name: + raise exceptions.InvalidAnchor( + ref=uri, + resource=resource, + anchor=name, + ) + raise exceptions.NoSuchAnchor(ref=uri, resource=resource, anchor=name) + + def contents(self, uri: URI) -> D: + """ + Retrieve the (already crawled) contents identified by the given URI. + """ + return self[uri].contents + + def crawl(self) -> Registry[D]: + """ + Crawl all added resources, discovering subresources. + """ + resources = self._resources + anchors = self._anchors + uncrawled = [(uri, resources[uri]) for uri in self._uncrawled] + while uncrawled: + uri, resource = uncrawled.pop() + + id = resource.id() + if id is not None: + uri = urljoin(uri, id) + resources = resources.insert(uri, resource) + for each in resource.anchors(): + anchors = anchors.insert((uri, each.name), each) + uncrawled.extend((uri, each) for each in resource.subresources()) + return evolve( + self, + resources=resources, + anchors=anchors, + uncrawled=EMPTY_UNCRAWLED, + ) + + def with_resource(self, uri: URI, resource: Resource[D]): + """ + Add the given `Resource` to the registry, without crawling it. + """ + return self.with_resources([(uri, resource)]) + + def with_resources( + self, + pairs: Iterable[tuple[URI, Resource[D]]], + ) -> Registry[D]: + r""" + Add the given `Resource`\ s to the registry, without crawling them. + """ + resources = self._resources + uncrawled = self._uncrawled + for uri, resource in pairs: + # Empty fragment URIs are equivalent to URIs without the fragment. + # TODO: Is this true for non JSON Schema resources? Probably not. + uri = uri.rstrip("#") + uncrawled = uncrawled.insert(uri) + resources = resources.insert(uri, resource) + return evolve(self, resources=resources, uncrawled=uncrawled) + + def with_contents( + self, + pairs: Iterable[tuple[URI, D]], + **kwargs: Any, + ) -> Registry[D]: + r""" + Add the given contents to the registry, autodetecting when necessary. + """ + return self.with_resources( + (uri, Resource.from_contents(each, **kwargs)) + for uri, each in pairs + ) + + def combine(self, *registries: Registry[D]) -> Registry[D]: + """ + Combine together one or more other registries, producing a unified one. + """ + if registries == (self,): + return self + resources = self._resources + anchors = self._anchors + uncrawled = self._uncrawled + retrieve = self._retrieve + for registry in registries: + resources = resources.update(registry._resources) + anchors = anchors.update(registry._anchors) + uncrawled = uncrawled.update(registry._uncrawled) + + if registry._retrieve is not _fail_to_retrieve: + if registry._retrieve is not retrieve is not _fail_to_retrieve: + raise ValueError( # noqa: TRY003 + "Cannot combine registries with conflicting retrieval " + "functions.", + ) + retrieve = registry._retrieve + return evolve( + self, + anchors=anchors, + resources=resources, + uncrawled=uncrawled, + retrieve=retrieve, + ) + + def resolver(self, base_uri: URI = "") -> Resolver[D]: + """ + Return a `Resolver` which resolves references against this registry. + """ + return Resolver(base_uri=base_uri, registry=self) + + def resolver_with_root(self, resource: Resource[D]) -> Resolver[D]: + """ + Return a `Resolver` with a specific root resource. + """ + uri = resource.id() or "" + return Resolver( + base_uri=uri, + registry=self.with_resource(uri, resource), + ) + + +#: An anchor or resource. +AnchorOrResource = TypeVar( + "AnchorOrResource", + AnchorType[Any], + Resource[Any], + default=Resource[Any], +) + + +@frozen +class Retrieved(Generic[D, AnchorOrResource]): + """ + A value retrieved from a `Registry`. + """ + + value: AnchorOrResource + registry: Registry[D] + + +@frozen +class Resolved(Generic[D]): + """ + A reference resolved to its contents by a `Resolver`. + """ + + contents: D + resolver: Resolver[D] + + +@frozen +class Resolver(Generic[D]): + """ + A reference resolver. + + Resolvers help resolve references (including relative ones) by + pairing a fixed base URI with a `Registry`. + + This object, under normal circumstances, is expected to be used by + *implementers of libraries* built on top of `referencing` (e.g. JSON Schema + implementations or other libraries resolving JSON references), + not directly by end-users populating registries or while writing + schemas or other resources. + + References are resolved against the base URI, and the combined URI + is then looked up within the registry. + + The process of resolving a reference may itself involve calculating + a *new* base URI for future reference resolution (e.g. if an + intermediate resource sets a new base URI), or may involve encountering + additional subresources and adding them to a new registry. + """ + + _base_uri: URI = field(alias="base_uri") + _registry: Registry[D] = field(alias="registry") + _previous: List[URI] = field(default=List(), repr=False, alias="previous") + + def lookup(self, ref: URI) -> Resolved[D]: + """ + Resolve the given reference to the resource it points to. + + Raises: + + `exceptions.Unresolvable` + + or a subclass thereof (see below) if the reference isn't + resolvable + + `exceptions.NoSuchAnchor` + + if the reference is to a URI where a resource exists but + contains a plain name fragment which does not exist within + the resource + + `exceptions.PointerToNowhere` + + if the reference is to a URI where a resource exists but + contains a JSON pointer to a location within the resource + that does not exist + + """ + if ref.startswith("#"): + uri, fragment = self._base_uri, ref[1:] + else: + uri, fragment = urldefrag(urljoin(self._base_uri, ref)) + try: + retrieved = self._registry.get_or_retrieve(uri) + except exceptions.NoSuchResource: + raise exceptions.Unresolvable(ref=ref) from None + except exceptions.Unretrievable as error: + raise exceptions.Unresolvable(ref=ref) from error + + if fragment.startswith("/"): + resolver = self._evolve(registry=retrieved.registry, base_uri=uri) + return retrieved.value.pointer(pointer=fragment, resolver=resolver) + + if fragment: + retrieved = retrieved.registry.anchor(uri, fragment) + resolver = self._evolve(registry=retrieved.registry, base_uri=uri) + return retrieved.value.resolve(resolver=resolver) + + resolver = self._evolve(registry=retrieved.registry, base_uri=uri) + return Resolved(contents=retrieved.value.contents, resolver=resolver) + + def in_subresource(self, subresource: Resource[D]) -> Resolver[D]: + """ + Create a resolver for a subresource (which may have a new base URI). + """ + id = subresource.id() + if id is None: + return self + return evolve(self, base_uri=urljoin(self._base_uri, id)) + + def dynamic_scope(self) -> Iterable[tuple[URI, Registry[D]]]: + """ + In specs with such a notion, return the URIs in the dynamic scope. + """ + for uri in self._previous: + yield uri, self._registry + + def _evolve(self, base_uri: URI, **kwargs: Any): + """ + Evolve, appending to the dynamic scope. + """ + previous = self._previous + if self._base_uri and (not previous or base_uri != self._base_uri): + previous = previous.push_front(self._base_uri) + return evolve(self, base_uri=base_uri, previous=previous, **kwargs) + + +@frozen +class Anchor(Generic[D]): + """ + A simple anchor in a `Resource`. + """ + + name: str + resource: Resource[D] + + def resolve(self, resolver: Resolver[D]): + """ + Return the resource for this anchor. + """ + return Resolved(contents=self.resource.contents, resolver=resolver) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py new file mode 100644 index 0000000..3267fc7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py @@ -0,0 +1,165 @@ +""" +Errors, oh no! +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import attrs + +from referencing._attrs import frozen + +if TYPE_CHECKING: + from referencing import Resource + from referencing.typing import URI + + +@frozen +class NoSuchResource(KeyError): + """ + The given URI is not present in a registry. + + Unlike most exceptions, this class *is* intended to be publicly + instantiable and *is* part of the public API of the package. + """ + + ref: URI + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return NotImplemented + return attrs.astuple(self) == attrs.astuple(other) + + def __hash__(self) -> int: + return hash(attrs.astuple(self)) + + +@frozen +class NoInternalID(Exception): + """ + A resource has no internal ID, but one is needed. + + E.g. in modern JSON Schema drafts, this is the :kw:`$id` keyword. + + One might be needed if a resource was to-be added to a registry but no + other URI is available, and the resource doesn't declare its canonical URI. + """ + + resource: Resource[Any] + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return NotImplemented + return attrs.astuple(self) == attrs.astuple(other) + + def __hash__(self) -> int: + return hash(attrs.astuple(self)) + + +@frozen +class Unretrievable(KeyError): + """ + The given URI is not present in a registry, and retrieving it failed. + """ + + ref: URI + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return NotImplemented + return attrs.astuple(self) == attrs.astuple(other) + + def __hash__(self) -> int: + return hash(attrs.astuple(self)) + + +@frozen +class CannotDetermineSpecification(Exception): + """ + Attempting to detect the appropriate `Specification` failed. + + This happens if no discernible information is found in the contents of the + new resource which would help identify it. + """ + + contents: Any + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return NotImplemented + return attrs.astuple(self) == attrs.astuple(other) + + def __hash__(self) -> int: + return hash(attrs.astuple(self)) + + +@attrs.frozen # Because here we allow subclassing below. +class Unresolvable(Exception): + """ + A reference was unresolvable. + """ + + ref: URI + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return NotImplemented + return attrs.astuple(self) == attrs.astuple(other) + + def __hash__(self) -> int: + return hash(attrs.astuple(self)) + + +@frozen +class PointerToNowhere(Unresolvable): + """ + A JSON Pointer leads to a part of a document that does not exist. + """ + + resource: Resource[Any] + + def __str__(self) -> str: + msg = f"{self.ref!r} does not exist within {self.resource.contents!r}" + if self.ref == "/": + msg += ( + ". The pointer '/' is a valid JSON Pointer but it points to " + "an empty string property ''. If you intended to point " + "to the entire resource, you should use '#'." + ) + return msg + + +@frozen +class NoSuchAnchor(Unresolvable): + """ + An anchor does not exist within a particular resource. + """ + + resource: Resource[Any] + anchor: str + + def __str__(self) -> str: + return ( + f"{self.anchor!r} does not exist within {self.resource.contents!r}" + ) + + +@frozen +class InvalidAnchor(Unresolvable): + """ + An anchor which could never exist in a resource was dereferenced. + + It is somehow syntactically invalid. + """ + + resource: Resource[Any] + anchor: str + + def __str__(self) -> str: + return ( + f"'#{self.anchor}' is not a valid anchor, neither as a " + "plain name anchor nor as a JSON Pointer. You may have intended " + f"to use '#/{self.anchor}', as the slash is required *before each " + "segment* of a JSON pointer." + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py new file mode 100644 index 0000000..93e77a7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py @@ -0,0 +1,642 @@ +""" +Referencing implementations for JSON Schema specs (historic & current). +""" + +from __future__ import annotations + +from collections.abc import Iterable, Sequence, Set +from typing import Any + +from referencing import Anchor, Registry, Resource, Specification, exceptions +from referencing._attrs import frozen +from referencing._core import ( + _UNSET, # type: ignore[reportPrivateUsage] + Resolved as _Resolved, + Resolver as _Resolver, + _Unset, # type: ignore[reportPrivateUsage] +) +from referencing.typing import URI, Anchor as AnchorType, Mapping + +#: A JSON Schema which is a JSON object +ObjectSchema = Mapping[str, Any] + +#: A JSON Schema of any kind +Schema = bool | ObjectSchema + +#: A Resource whose contents are JSON Schemas +SchemaResource = Resource[Schema] + +#: A JSON Schema Registry +SchemaRegistry = Registry[Schema] + +#: The empty JSON Schema Registry +EMPTY_REGISTRY: SchemaRegistry = Registry() + + +@frozen +class UnknownDialect(Exception): + """ + A dialect identifier was found for a dialect unknown by this library. + + If it's a custom ("unofficial") dialect, be sure you've registered it. + """ + + uri: URI + + +def _dollar_id(contents: Schema) -> URI | None: + if isinstance(contents, bool): + return + return contents.get("$id") + + +def _legacy_dollar_id(contents: Schema) -> URI | None: + if isinstance(contents, bool) or "$ref" in contents: + return + id = contents.get("$id") + if id is not None and not id.startswith("#"): + return id + + +def _legacy_id(contents: ObjectSchema) -> URI | None: + if "$ref" in contents: + return + id = contents.get("id") + if id is not None and not id.startswith("#"): + return id + + +def _anchor( + specification: Specification[Schema], + contents: Schema, +) -> Iterable[AnchorType[Schema]]: + if isinstance(contents, bool): + return + anchor = contents.get("$anchor") + if anchor is not None: + yield Anchor( + name=anchor, + resource=specification.create_resource(contents), + ) + + dynamic_anchor = contents.get("$dynamicAnchor") + if dynamic_anchor is not None: + yield DynamicAnchor( + name=dynamic_anchor, + resource=specification.create_resource(contents), + ) + + +def _anchor_2019( + specification: Specification[Schema], + contents: Schema, +) -> Iterable[Anchor[Schema]]: + if isinstance(contents, bool): + return [] + anchor = contents.get("$anchor") + if anchor is None: + return [] + return [ + Anchor( + name=anchor, + resource=specification.create_resource(contents), + ), + ] + + +def _legacy_anchor_in_dollar_id( + specification: Specification[Schema], + contents: Schema, +) -> Iterable[Anchor[Schema]]: + if isinstance(contents, bool): + return [] + id = contents.get("$id", "") + if not id.startswith("#"): + return [] + return [ + Anchor( + name=id[1:], + resource=specification.create_resource(contents), + ), + ] + + +def _legacy_anchor_in_id( + specification: Specification[ObjectSchema], + contents: ObjectSchema, +) -> Iterable[Anchor[ObjectSchema]]: + id = contents.get("id", "") + if not id.startswith("#"): + return [] + return [ + Anchor( + name=id[1:], + resource=specification.create_resource(contents), + ), + ] + + +def _subresources_of( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + """ + Create a callable returning JSON Schema specification-style subschemas. + + Relies on specifying the set of keywords containing subschemas in their + values, in a subobject's values, or in a subarray. + """ + + def subresources_of(contents: Schema) -> Iterable[ObjectSchema]: + if isinstance(contents, bool): + return + for each in in_value: + if each in contents: + yield contents[each] + for each in in_subarray: + if each in contents: + yield from contents[each] + for each in in_subvalues: + if each in contents: + yield from contents[each].values() + + return subresources_of + + +def _subresources_of_with_crazy_items( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + """ + Specifically handle older drafts where there are some funky keywords. + """ + + def subresources_of(contents: Schema) -> Iterable[ObjectSchema]: + if isinstance(contents, bool): + return + for each in in_value: + if each in contents: + yield contents[each] + for each in in_subarray: + if each in contents: + yield from contents[each] + for each in in_subvalues: + if each in contents: + yield from contents[each].values() + + items = contents.get("items") + if items is not None: + if isinstance(items, Sequence): + yield from items + else: + yield items + + return subresources_of + + +def _subresources_of_with_crazy_items_dependencies( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + """ + Specifically handle older drafts where there are some funky keywords. + """ + + def subresources_of(contents: Schema) -> Iterable[ObjectSchema]: + if isinstance(contents, bool): + return + for each in in_value: + if each in contents: + yield contents[each] + for each in in_subarray: + if each in contents: + yield from contents[each] + for each in in_subvalues: + if each in contents: + yield from contents[each].values() + + items = contents.get("items") + if items is not None: + if isinstance(items, Sequence): + yield from items + else: + yield items + dependencies = contents.get("dependencies") + if dependencies is not None: + values = iter(dependencies.values()) + value = next(values, None) + if isinstance(value, Mapping): + yield value + yield from values + + return subresources_of + + +def _subresources_of_with_crazy_aP_items_dependencies( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + """ + Specifically handle even older drafts where there are some funky keywords. + """ + + def subresources_of(contents: ObjectSchema) -> Iterable[ObjectSchema]: + for each in in_value: + if each in contents: + yield contents[each] + for each in in_subarray: + if each in contents: + yield from contents[each] + for each in in_subvalues: + if each in contents: + yield from contents[each].values() + + items = contents.get("items") + if items is not None: + if isinstance(items, Sequence): + yield from items + else: + yield items + dependencies = contents.get("dependencies") + if dependencies is not None: + values = iter(dependencies.values()) + value = next(values, None) + if isinstance(value, Mapping): + yield value + yield from values + + for each in "additionalItems", "additionalProperties": + value = contents.get(each) + if isinstance(value, Mapping): + yield value + + return subresources_of + + +def _maybe_in_subresource( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + in_child = in_subvalues | in_subarray + + def maybe_in_subresource( + segments: Sequence[int | str], + resolver: _Resolver[Any], + subresource: Resource[Any], + ) -> _Resolver[Any]: + _segments = iter(segments) + for segment in _segments: + if segment not in in_value and ( + segment not in in_child or next(_segments, None) is None + ): + return resolver + return resolver.in_subresource(subresource) + + return maybe_in_subresource + + +def _maybe_in_subresource_crazy_items( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + in_child = in_subvalues | in_subarray + + def maybe_in_subresource( + segments: Sequence[int | str], + resolver: _Resolver[Any], + subresource: Resource[Any], + ) -> _Resolver[Any]: + _segments = iter(segments) + for segment in _segments: + if segment == "items" and isinstance( + subresource.contents, + Mapping, + ): + return resolver.in_subresource(subresource) + if segment not in in_value and ( + segment not in in_child or next(_segments, None) is None + ): + return resolver + return resolver.in_subresource(subresource) + + return maybe_in_subresource + + +def _maybe_in_subresource_crazy_items_dependencies( + in_value: Set[str] = frozenset(), + in_subvalues: Set[str] = frozenset(), + in_subarray: Set[str] = frozenset(), +): + in_child = in_subvalues | in_subarray + + def maybe_in_subresource( + segments: Sequence[int | str], + resolver: _Resolver[Any], + subresource: Resource[Any], + ) -> _Resolver[Any]: + _segments = iter(segments) + for segment in _segments: + if segment in {"items", "dependencies"} and isinstance( + subresource.contents, + Mapping, + ): + return resolver.in_subresource(subresource) + if segment not in in_value and ( + segment not in in_child or next(_segments, None) is None + ): + return resolver + return resolver.in_subresource(subresource) + + return maybe_in_subresource + + +#: JSON Schema draft 2020-12 +DRAFT202012 = Specification( + name="draft2020-12", + id_of=_dollar_id, + subresources_of=_subresources_of( + in_value={ + "additionalProperties", + "contains", + "contentSchema", + "else", + "if", + "items", + "not", + "propertyNames", + "then", + "unevaluatedItems", + "unevaluatedProperties", + }, + in_subarray={"allOf", "anyOf", "oneOf", "prefixItems"}, + in_subvalues={ + "$defs", + "definitions", + "dependentSchemas", + "patternProperties", + "properties", + }, + ), + anchors_in=_anchor, + maybe_in_subresource=_maybe_in_subresource( + in_value={ + "additionalProperties", + "contains", + "contentSchema", + "else", + "if", + "items", + "not", + "propertyNames", + "then", + "unevaluatedItems", + "unevaluatedProperties", + }, + in_subarray={"allOf", "anyOf", "oneOf", "prefixItems"}, + in_subvalues={ + "$defs", + "definitions", + "dependentSchemas", + "patternProperties", + "properties", + }, + ), +) +#: JSON Schema draft 2019-09 +DRAFT201909 = Specification( + name="draft2019-09", + id_of=_dollar_id, + subresources_of=_subresources_of_with_crazy_items( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "contentSchema", + "else", + "if", + "not", + "propertyNames", + "then", + "unevaluatedItems", + "unevaluatedProperties", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={ + "$defs", + "definitions", + "dependentSchemas", + "patternProperties", + "properties", + }, + ), + anchors_in=_anchor_2019, + maybe_in_subresource=_maybe_in_subresource_crazy_items( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "contentSchema", + "else", + "if", + "not", + "propertyNames", + "then", + "unevaluatedItems", + "unevaluatedProperties", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={ + "$defs", + "definitions", + "dependentSchemas", + "patternProperties", + "properties", + }, + ), +) +#: JSON Schema draft 7 +DRAFT7 = Specification( + name="draft-07", + id_of=_legacy_dollar_id, + subresources_of=_subresources_of_with_crazy_items_dependencies( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "else", + "if", + "not", + "propertyNames", + "then", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), + anchors_in=_legacy_anchor_in_dollar_id, + maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "else", + "if", + "not", + "propertyNames", + "then", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), +) +#: JSON Schema draft 6 +DRAFT6 = Specification( + name="draft-06", + id_of=_legacy_dollar_id, + subresources_of=_subresources_of_with_crazy_items_dependencies( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "not", + "propertyNames", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), + anchors_in=_legacy_anchor_in_dollar_id, + maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies( + in_value={ + "additionalItems", + "additionalProperties", + "contains", + "not", + "propertyNames", + }, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), +) +#: JSON Schema draft 4 +DRAFT4 = Specification( + name="draft-04", + id_of=_legacy_id, + subresources_of=_subresources_of_with_crazy_aP_items_dependencies( + in_value={"not"}, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), + anchors_in=_legacy_anchor_in_id, + maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies( + in_value={"additionalItems", "additionalProperties", "not"}, + in_subarray={"allOf", "anyOf", "oneOf"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), +) +#: JSON Schema draft 3 +DRAFT3 = Specification( + name="draft-03", + id_of=_legacy_id, + subresources_of=_subresources_of_with_crazy_aP_items_dependencies( + in_subarray={"extends"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), + anchors_in=_legacy_anchor_in_id, + maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies( + in_value={"additionalItems", "additionalProperties"}, + in_subarray={"extends"}, + in_subvalues={"definitions", "patternProperties", "properties"}, + ), +) + + +_SPECIFICATIONS: Registry[Specification[Schema]] = Registry( + { + dialect_id: Resource.opaque(specification) + for dialect_id, specification in [ + ("https://json-schema.org/draft/2020-12/schema", DRAFT202012), + ("https://json-schema.org/draft/2019-09/schema", DRAFT201909), + ("http://json-schema.org/draft-07/schema", DRAFT7), + ("http://json-schema.org/draft-06/schema", DRAFT6), + ("http://json-schema.org/draft-04/schema", DRAFT4), + ("http://json-schema.org/draft-03/schema", DRAFT3), + ] + }, +) + + +def specification_with( + dialect_id: URI, + default: Specification[Any] | _Unset = _UNSET, +) -> Specification[Any]: + """ + Retrieve the `Specification` with the given dialect identifier. + + Raises: + + `UnknownDialect` + + if the given ``dialect_id`` isn't known + + """ + resource = _SPECIFICATIONS.get(dialect_id.rstrip("#")) + if resource is not None: + return resource.contents + if default is _UNSET: + raise UnknownDialect(dialect_id) + return default + + +@frozen +class DynamicAnchor: + """ + Dynamic anchors, introduced in draft 2020. + """ + + name: str + resource: SchemaResource + + def resolve(self, resolver: _Resolver[Schema]) -> _Resolved[Schema]: + """ + Resolve this anchor dynamically. + """ + last = self.resource + for uri, registry in resolver.dynamic_scope(): + try: + anchor = registry.anchor(uri, self.name).value + except exceptions.NoSuchAnchor: + continue + if isinstance(anchor, DynamicAnchor): + last = anchor.resource + return _Resolved( + contents=last.contents, + resolver=resolver.in_subresource(last), + ) + + +def lookup_recursive_ref(resolver: _Resolver[Schema]) -> _Resolved[Schema]: + """ + Recursive references (via recursive anchors), present only in draft 2019. + + As per the 2019 specification (§ 8.2.4.2.1), only the ``#`` recursive + reference is supported (and is therefore assumed to be the relevant + reference). + """ + resolved = resolver.lookup("#") + if isinstance(resolved.contents, Mapping) and resolved.contents.get( + "$recursiveAnchor", + ): + for uri, _ in resolver.dynamic_scope(): + next_resolved = resolver.lookup(uri) + if not isinstance( + next_resolved.contents, + Mapping, + ) or not next_resolved.contents.get("$recursiveAnchor"): + break + resolved = next_resolved + return resolved diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py new file mode 100644 index 0000000..ac5a65b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py @@ -0,0 +1,94 @@ +""" +Helpers related to (dynamic) resource retrieval. +""" + +from __future__ import annotations + +from functools import lru_cache +from typing import TYPE_CHECKING +import json + +try: + from typing_extensions import TypeVar +except ImportError: # pragma: no cover + from typing import TypeVar + +from referencing import Resource + +if TYPE_CHECKING: + from collections.abc import Callable + + from referencing.typing import URI, D, Retrieve + +#: A serialized document (e.g. a JSON string) +_T = TypeVar("_T", default=str) + + +def to_cached_resource( + cache: Callable[[Retrieve[D]], Retrieve[D]] | None = None, + loads: Callable[[_T], D] = json.loads, + from_contents: Callable[[D], Resource[D]] = Resource.from_contents, +) -> Callable[[Callable[[URI], _T]], Retrieve[D]]: + """ + Create a retriever which caches its return values from a simpler callable. + + Takes a function which returns things like serialized JSON (strings) and + returns something suitable for passing to `Registry` as a retrieve + function. + + This decorator both reduces a small bit of boilerplate for a common case + (deserializing JSON from strings and creating `Resource` objects from the + result) as well as makes the probable need for caching a bit easier. + Retrievers which otherwise do expensive operations (like hitting the + network) might otherwise be called repeatedly. + + Examples + -------- + + .. testcode:: + + from referencing import Registry + from referencing.typing import URI + import referencing.retrieval + + + @referencing.retrieval.to_cached_resource() + def retrieve(uri: URI): + print(f"Retrieved {uri}") + + # Normally, go get some expensive JSON from the network, a file ... + return ''' + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "foo": "bar" + } + ''' + + one = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo") + print(one.value.contents["foo"]) + + # Retrieving the same URI again reuses the same value (and thus doesn't + # print another retrieval message here) + two = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo") + print(two.value.contents["foo"]) + + .. testoutput:: + + Retrieved urn:example:foo + bar + bar + + """ + if cache is None: + cache = lru_cache(maxsize=None) + + def decorator(retrieve: Callable[[URI], _T]): + @cache + def cached_retrieve(uri: URI): + response = retrieve(uri) + contents = loads(response) + return from_contents(contents) + + return cached_retrieve + + return decorator diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py new file mode 100644 index 0000000..3edddbc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py @@ -0,0 +1,1057 @@ +from rpds import HashTrieMap +import pytest + +from referencing import Anchor, Registry, Resource, Specification, exceptions +from referencing.jsonschema import DRAFT202012 + +ID_AND_CHILDREN = Specification( + name="id-and-children", + id_of=lambda contents: contents.get("ID"), + subresources_of=lambda contents: contents.get("children", []), + anchors_in=lambda specification, contents: [ + Anchor( + name=name, + resource=specification.create_resource(contents=each), + ) + for name, each in contents.get("anchors", {}).items() + ], + maybe_in_subresource=lambda segments, resolver, subresource: ( + resolver.in_subresource(subresource) + if not len(segments) % 2 + and all(each == "children" for each in segments[::2]) + else resolver + ), +) + + +def blow_up(uri): # pragma: no cover + """ + A retriever suitable for use in tests which expect it never to be used. + """ + raise RuntimeError("This retrieve function expects to never be called!") + + +class TestRegistry: + def test_with_resource(self): + """ + Adding a resource to the registry then allows re-retrieving it. + """ + + resource = Resource.opaque(contents={"foo": "bar"}) + uri = "urn:example" + registry = Registry().with_resource(uri=uri, resource=resource) + assert registry[uri] is resource + + def test_with_resources(self): + """ + Adding multiple resources to the registry is like adding each one. + """ + + one = Resource.opaque(contents={}) + two = Resource(contents={"foo": "bar"}, specification=ID_AND_CHILDREN) + registry = Registry().with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/foo/bar", two), + ], + ) + assert registry == Registry().with_resource( + uri="http://example.com/1", + resource=one, + ).with_resource( + uri="http://example.com/foo/bar", + resource=two, + ) + + def test_matmul_resource(self): + uri = "urn:example:resource" + resource = ID_AND_CHILDREN.create_resource({"ID": uri, "foo": 12}) + registry = resource @ Registry() + assert registry == Registry().with_resource(uri, resource) + + def test_matmul_many_resources(self): + one_uri = "urn:example:one" + one = ID_AND_CHILDREN.create_resource({"ID": one_uri, "foo": 12}) + + two_uri = "urn:example:two" + two = ID_AND_CHILDREN.create_resource({"ID": two_uri, "foo": 12}) + + registry = [one, two] @ Registry() + assert registry == Registry().with_resources( + [(one_uri, one), (two_uri, two)], + ) + + def test_matmul_resource_without_id(self): + resource = Resource.opaque(contents={"foo": "bar"}) + with pytest.raises(exceptions.NoInternalID) as e: + resource @ Registry() + assert e.value == exceptions.NoInternalID(resource=resource) + + def test_with_contents_from_json_schema(self): + uri = "urn:example" + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + registry = Registry().with_contents([(uri, schema)]) + + expected = Resource(contents=schema, specification=DRAFT202012) + assert registry[uri] == expected + + def test_with_contents_and_default_specification(self): + uri = "urn:example" + registry = Registry().with_contents( + [(uri, {"foo": "bar"})], + default_specification=Specification.OPAQUE, + ) + assert registry[uri] == Resource.opaque({"foo": "bar"}) + + def test_len(self): + total = 5 + registry = Registry().with_contents( + [(str(i), {"foo": "bar"}) for i in range(total)], + default_specification=Specification.OPAQUE, + ) + assert len(registry) == total + + def test_bool_empty(self): + assert not Registry() + + def test_bool_not_empty(self): + registry = Registry().with_contents( + [(str(i), {"foo": "bar"}) for i in range(3)], + default_specification=Specification.OPAQUE, + ) + assert registry + + def test_iter(self): + registry = Registry().with_contents( + [(str(i), {"foo": "bar"}) for i in range(8)], + default_specification=Specification.OPAQUE, + ) + assert set(registry) == {str(i) for i in range(8)} + + def test_crawl_still_has_top_level_resource(self): + resource = Resource.opaque({"foo": "bar"}) + uri = "urn:example" + registry = Registry({uri: resource}).crawl() + assert registry[uri] is resource + + def test_crawl_finds_a_subresource(self): + child_id = "urn:child" + root = ID_AND_CHILDREN.create_resource( + {"ID": "urn:root", "children": [{"ID": child_id, "foo": 12}]}, + ) + registry = root @ Registry() + with pytest.raises(LookupError): + registry[child_id] + + expected = ID_AND_CHILDREN.create_resource({"ID": child_id, "foo": 12}) + assert registry.crawl()[child_id] == expected + + def test_crawl_finds_anchors_with_id(self): + resource = ID_AND_CHILDREN.create_resource( + {"ID": "urn:bar", "anchors": {"foo": 12}}, + ) + registry = resource @ Registry() + + assert registry.crawl().anchor(resource.id(), "foo").value == Anchor( + name="foo", + resource=ID_AND_CHILDREN.create_resource(12), + ) + + def test_crawl_finds_anchors_no_id(self): + resource = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}}) + registry = Registry().with_resource("urn:root", resource) + + assert registry.crawl().anchor("urn:root", "foo").value == Anchor( + name="foo", + resource=ID_AND_CHILDREN.create_resource(12), + ) + + def test_contents(self): + resource = Resource.opaque({"foo": "bar"}) + uri = "urn:example" + registry = Registry().with_resource(uri, resource) + assert registry.contents(uri) == {"foo": "bar"} + + def test_getitem_strips_empty_fragments(self): + uri = "http://example.com/" + resource = ID_AND_CHILDREN.create_resource({"ID": uri + "#"}) + registry = resource @ Registry() + assert registry[uri] == registry[uri + "#"] == resource + + def test_contents_strips_empty_fragments(self): + uri = "http://example.com/" + resource = ID_AND_CHILDREN.create_resource({"ID": uri + "#"}) + registry = resource @ Registry() + assert ( + registry.contents(uri) + == registry.contents(uri + "#") + == {"ID": uri + "#"} + ) + + def test_contents_nonexistent_resource(self): + registry = Registry() + with pytest.raises(exceptions.NoSuchResource) as e: + registry.contents("urn:example") + assert e.value == exceptions.NoSuchResource(ref="urn:example") + + def test_crawled_anchor(self): + resource = ID_AND_CHILDREN.create_resource({"anchors": {"foo": "bar"}}) + registry = Registry().with_resource("urn:example", resource) + retrieved = registry.anchor("urn:example", "foo") + assert retrieved.value == Anchor( + name="foo", + resource=ID_AND_CHILDREN.create_resource("bar"), + ) + assert retrieved.registry == registry.crawl() + + def test_anchor_in_nonexistent_resource(self): + registry = Registry() + with pytest.raises(exceptions.NoSuchResource) as e: + registry.anchor("urn:example", "foo") + assert e.value == exceptions.NoSuchResource(ref="urn:example") + + def test_init(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = Registry( + { + "http://example.com/1": one, + "http://example.com/foo/bar": two, + }, + ) + assert ( + registry + == Registry() + .with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/foo/bar", two), + ], + ) + .crawl() + ) + + def test_dict_conversion(self): + """ + Passing a `dict` to `Registry` gets converted to a `HashTrieMap`. + + So continuing to use the registry works. + """ + + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = Registry( + {"http://example.com/1": one}, + ).with_resource("http://example.com/foo/bar", two) + assert ( + registry.crawl() + == Registry() + .with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/foo/bar", two), + ], + ) + .crawl() + ) + + def test_no_such_resource(self): + registry = Registry() + with pytest.raises(exceptions.NoSuchResource) as e: + registry["urn:bigboom"] + assert e.value == exceptions.NoSuchResource(ref="urn:bigboom") + + def test_combine(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + three = ID_AND_CHILDREN.create_resource({"baz": "quux"}) + four = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}}) + + first = Registry({"http://example.com/1": one}) + second = Registry().with_resource("http://example.com/foo/bar", two) + third = Registry( + { + "http://example.com/1": one, + "http://example.com/baz": three, + }, + ) + fourth = ( + Registry() + .with_resource( + "http://example.com/foo/quux", + four, + ) + .crawl() + ) + assert first.combine(second, third, fourth) == Registry( + [ + ("http://example.com/1", one), + ("http://example.com/baz", three), + ("http://example.com/foo/quux", four), + ], + anchors=HashTrieMap( + { + ("http://example.com/foo/quux", "foo"): Anchor( + name="foo", + resource=ID_AND_CHILDREN.create_resource(12), + ), + }, + ), + ).with_resource("http://example.com/foo/bar", two) + + def test_combine_self(self): + """ + Combining a registry with itself short-circuits. + + This is a performance optimization -- otherwise we do lots more work + (in jsonschema this seems to correspond to making the test suite take + *3x* longer). + """ + + registry = Registry({"urn:foo": "bar"}) + assert registry.combine(registry) is registry + + def test_combine_with_uncrawled_resources(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + three = ID_AND_CHILDREN.create_resource({"baz": "quux"}) + + first = Registry().with_resource("http://example.com/1", one) + second = Registry().with_resource("http://example.com/foo/bar", two) + third = Registry( + { + "http://example.com/1": one, + "http://example.com/baz": three, + }, + ) + expected = Registry( + [ + ("http://example.com/1", one), + ("http://example.com/foo/bar", two), + ("http://example.com/baz", three), + ], + ) + combined = first.combine(second, third) + assert combined != expected + assert combined.crawl() == expected + + def test_combine_with_single_retrieve(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + three = ID_AND_CHILDREN.create_resource({"baz": "quux"}) + + def retrieve(uri): # pragma: no cover + pass + + first = Registry().with_resource("http://example.com/1", one) + second = Registry( + retrieve=retrieve, + ).with_resource("http://example.com/2", two) + third = Registry().with_resource("http://example.com/3", three) + + assert first.combine(second, third) == Registry( + retrieve=retrieve, + ).with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/2", two), + ("http://example.com/3", three), + ], + ) + assert second.combine(first, third) == Registry( + retrieve=retrieve, + ).with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/2", two), + ("http://example.com/3", three), + ], + ) + + def test_combine_with_common_retrieve(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + three = ID_AND_CHILDREN.create_resource({"baz": "quux"}) + + def retrieve(uri): # pragma: no cover + pass + + first = Registry(retrieve=retrieve).with_resource( + "http://example.com/1", + one, + ) + second = Registry( + retrieve=retrieve, + ).with_resource("http://example.com/2", two) + third = Registry(retrieve=retrieve).with_resource( + "http://example.com/3", + three, + ) + + assert first.combine(second, third) == Registry( + retrieve=retrieve, + ).with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/2", two), + ("http://example.com/3", three), + ], + ) + assert second.combine(first, third) == Registry( + retrieve=retrieve, + ).with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/2", two), + ("http://example.com/3", three), + ], + ) + + def test_combine_conflicting_retrieve(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + three = ID_AND_CHILDREN.create_resource({"baz": "quux"}) + + def foo_retrieve(uri): # pragma: no cover + pass + + def bar_retrieve(uri): # pragma: no cover + pass + + first = Registry(retrieve=foo_retrieve).with_resource( + "http://example.com/1", + one, + ) + second = Registry().with_resource("http://example.com/2", two) + third = Registry(retrieve=bar_retrieve).with_resource( + "http://example.com/3", + three, + ) + + with pytest.raises(Exception, match="conflict.*retriev"): + first.combine(second, third) + + def test_remove(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = Registry({"urn:foo": one, "urn:bar": two}) + assert registry.remove("urn:foo") == Registry({"urn:bar": two}) + + def test_remove_uncrawled(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = Registry().with_resources( + [("urn:foo", one), ("urn:bar", two)], + ) + assert registry.remove("urn:foo") == Registry().with_resource( + "urn:bar", + two, + ) + + def test_remove_with_anchors(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"anchors": {"foo": "bar"}}) + registry = ( + Registry() + .with_resources( + [("urn:foo", one), ("urn:bar", two)], + ) + .crawl() + ) + assert ( + registry.remove("urn:bar") + == Registry() + .with_resource( + "urn:foo", + one, + ) + .crawl() + ) + + def test_remove_nonexistent_uri(self): + with pytest.raises(exceptions.NoSuchResource) as e: + Registry().remove("urn:doesNotExist") + assert e.value == exceptions.NoSuchResource(ref="urn:doesNotExist") + + def test_retrieve(self): + foo = Resource.opaque({"foo": "bar"}) + registry = Registry(retrieve=lambda uri: foo) + assert registry.get_or_retrieve("urn:example").value == foo + + def test_retrieve_arbitrary_exception(self): + foo = Resource.opaque({"foo": "bar"}) + + def retrieve(uri): + if uri == "urn:succeed": + return foo + raise Exception("Oh no!") + + registry = Registry(retrieve=retrieve) + assert registry.get_or_retrieve("urn:succeed").value == foo + with pytest.raises(exceptions.Unretrievable): + registry.get_or_retrieve("urn:uhoh") + + def test_retrieve_no_such_resource(self): + foo = Resource.opaque({"foo": "bar"}) + + def retrieve(uri): + if uri == "urn:succeed": + return foo + raise exceptions.NoSuchResource(ref=uri) + + registry = Registry(retrieve=retrieve) + assert registry.get_or_retrieve("urn:succeed").value == foo + with pytest.raises(exceptions.NoSuchResource): + registry.get_or_retrieve("urn:uhoh") + + def test_retrieve_cannot_determine_specification(self): + def retrieve(uri): + return Resource.from_contents({}) + + registry = Registry(retrieve=retrieve) + with pytest.raises(exceptions.CannotDetermineSpecification): + registry.get_or_retrieve("urn:uhoh") + + def test_retrieve_already_available_resource(self): + foo = Resource.opaque({"foo": "bar"}) + registry = Registry({"urn:example": foo}, retrieve=blow_up) + assert registry["urn:example"] == foo + assert registry.get_or_retrieve("urn:example").value == foo + + def test_retrieve_first_checks_crawlable_resource(self): + child = ID_AND_CHILDREN.create_resource({"ID": "urn:child", "foo": 12}) + root = ID_AND_CHILDREN.create_resource({"children": [child.contents]}) + registry = Registry(retrieve=blow_up).with_resource("urn:root", root) + assert registry.crawl()["urn:child"] == child + + def test_resolver(self): + one = Resource.opaque(contents={}) + registry = Registry({"http://example.com": one}) + resolver = registry.resolver(base_uri="http://example.com") + assert resolver.lookup("#").contents == {} + + def test_resolver_with_root_identified(self): + root = ID_AND_CHILDREN.create_resource({"ID": "http://example.com"}) + resolver = Registry().resolver_with_root(root) + assert resolver.lookup("http://example.com").contents == root.contents + assert resolver.lookup("#").contents == root.contents + + def test_resolver_with_root_unidentified(self): + root = Resource.opaque(contents={}) + resolver = Registry().resolver_with_root(root) + assert resolver.lookup("#").contents == root.contents + + def test_repr(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = Registry().with_resources( + [ + ("http://example.com/1", one), + ("http://example.com/foo/bar", two), + ], + ) + assert repr(registry) == "" + assert repr(registry.crawl()) == "" + + def test_repr_mixed_crawled(self): + one = Resource.opaque(contents={}) + two = ID_AND_CHILDREN.create_resource({"foo": "bar"}) + registry = ( + Registry( + {"http://example.com/1": one}, + ) + .crawl() + .with_resource(uri="http://example.com/foo/bar", resource=two) + ) + assert repr(registry) == "" + + def test_repr_one_resource(self): + registry = Registry().with_resource( + uri="http://example.com/1", + resource=Resource.opaque(contents={}), + ) + assert repr(registry) == "" + + def test_repr_empty(self): + assert repr(Registry()) == "" + + +class TestResource: + def test_from_contents_from_json_schema(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + resource = Resource.from_contents(schema) + assert resource == Resource(contents=schema, specification=DRAFT202012) + + def test_from_contents_with_no_discernible_information(self): + """ + Creating a resource with no discernible way to see what + specification it belongs to (e.g. no ``$schema`` keyword for JSON + Schema) raises an error. + """ + + with pytest.raises(exceptions.CannotDetermineSpecification): + Resource.from_contents({"foo": "bar"}) + + def test_from_contents_with_no_discernible_information_and_default(self): + resource = Resource.from_contents( + {"foo": "bar"}, + default_specification=Specification.OPAQUE, + ) + assert resource == Resource.opaque(contents={"foo": "bar"}) + + def test_from_contents_unneeded_default(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + resource = Resource.from_contents( + schema, + default_specification=Specification.OPAQUE, + ) + assert resource == Resource( + contents=schema, + specification=DRAFT202012, + ) + + def test_non_mapping_from_contents(self): + resource = Resource.from_contents( + True, + default_specification=ID_AND_CHILDREN, + ) + assert resource == Resource( + contents=True, + specification=ID_AND_CHILDREN, + ) + + def test_from_contents_with_fallback(self): + resource = Resource.from_contents( + {"foo": "bar"}, + default_specification=Specification.OPAQUE, + ) + assert resource == Resource.opaque(contents={"foo": "bar"}) + + def test_id_delegates_to_specification(self): + specification = Specification( + name="", + id_of=lambda contents: "urn:fixedID", + subresources_of=lambda contents: [], + anchors_in=lambda specification, contents: [], + maybe_in_subresource=( + lambda segments, resolver, subresource: resolver + ), + ) + resource = Resource( + contents={"foo": "baz"}, + specification=specification, + ) + assert resource.id() == "urn:fixedID" + + def test_id_strips_empty_fragment(self): + uri = "http://example.com/" + root = ID_AND_CHILDREN.create_resource({"ID": uri + "#"}) + assert root.id() == uri + + def test_subresources_delegates_to_specification(self): + resource = ID_AND_CHILDREN.create_resource({"children": [{}, 12]}) + assert list(resource.subresources()) == [ + ID_AND_CHILDREN.create_resource(each) for each in [{}, 12] + ] + + def test_subresource_with_different_specification(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + resource = ID_AND_CHILDREN.create_resource({"children": [schema]}) + assert list(resource.subresources()) == [ + DRAFT202012.create_resource(schema), + ] + + def test_anchors_delegates_to_specification(self): + resource = ID_AND_CHILDREN.create_resource( + {"anchors": {"foo": {}, "bar": 1, "baz": ""}}, + ) + assert list(resource.anchors()) == [ + Anchor(name="foo", resource=ID_AND_CHILDREN.create_resource({})), + Anchor(name="bar", resource=ID_AND_CHILDREN.create_resource(1)), + Anchor(name="baz", resource=ID_AND_CHILDREN.create_resource("")), + ] + + def test_pointer_to_mapping(self): + resource = Resource.opaque(contents={"foo": "baz"}) + resolver = Registry().resolver() + assert resource.pointer("/foo", resolver=resolver).contents == "baz" + + def test_pointer_to_array(self): + resource = Resource.opaque(contents={"foo": {"bar": [3]}}) + resolver = Registry().resolver() + assert resource.pointer("/foo/bar/0", resolver=resolver).contents == 3 + + def test_root_pointer(self): + contents = {"foo": "baz"} + resource = Resource.opaque(contents=contents) + resolver = Registry().resolver() + assert resource.pointer("", resolver=resolver).contents == contents + + def test_opaque(self): + contents = {"foo": "bar"} + assert Resource.opaque(contents) == Resource( + contents=contents, + specification=Specification.OPAQUE, + ) + + +class TestResolver: + def test_lookup_exact_uri(self): + resource = Resource.opaque(contents={"foo": "baz"}) + resolver = Registry({"http://example.com/1": resource}).resolver() + resolved = resolver.lookup("http://example.com/1") + assert resolved.contents == resource.contents + + def test_lookup_subresource(self): + root = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "children": [ + {"ID": "http://example.com/a", "foo": 12}, + ], + }, + ) + registry = root @ Registry() + resolved = registry.resolver().lookup("http://example.com/a") + assert resolved.contents == {"ID": "http://example.com/a", "foo": 12} + + def test_lookup_anchor_with_id(self): + root = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "anchors": {"foo": 12}, + }, + ) + registry = root @ Registry() + resolved = registry.resolver().lookup("http://example.com/#foo") + assert resolved.contents == 12 + + def test_lookup_anchor_without_id(self): + root = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}}) + resolver = Registry().with_resource("urn:example", root).resolver() + resolved = resolver.lookup("urn:example#foo") + assert resolved.contents == 12 + + def test_lookup_unknown_reference(self): + resolver = Registry().resolver() + ref = "http://example.com/does/not/exist" + with pytest.raises(exceptions.Unresolvable) as e: + resolver.lookup(ref) + assert e.value == exceptions.Unresolvable(ref=ref) + + def test_lookup_non_existent_pointer(self): + resource = Resource.opaque({"foo": {}}) + resolver = Registry({"http://example.com/1": resource}).resolver() + ref = "http://example.com/1#/foo/bar" + with pytest.raises(exceptions.Unresolvable) as e: + resolver.lookup(ref) + assert e.value == exceptions.PointerToNowhere( + ref="/foo/bar", + resource=resource, + ) + assert str(e.value) == "'/foo/bar' does not exist within {'foo': {}}" + + def test_lookup_non_existent_pointer_to_array_index(self): + resource = Resource.opaque([1, 2, 4, 8]) + resolver = Registry({"http://example.com/1": resource}).resolver() + ref = "http://example.com/1#/10" + with pytest.raises(exceptions.Unresolvable) as e: + resolver.lookup(ref) + assert e.value == exceptions.PointerToNowhere( + ref="/10", + resource=resource, + ) + + def test_lookup_pointer_to_empty_string(self): + resolver = Registry().resolver_with_root(Resource.opaque({"": {}})) + assert resolver.lookup("#/").contents == {} + + def test_lookup_non_existent_pointer_to_empty_string(self): + resource = Resource.opaque({"foo": {}}) + resolver = Registry().resolver_with_root(resource) + with pytest.raises( + exceptions.Unresolvable, + match="^'/' does not exist within {'foo': {}}.*'#'", + ) as e: + resolver.lookup("#/") + assert e.value == exceptions.PointerToNowhere( + ref="/", + resource=resource, + ) + + def test_lookup_non_existent_anchor(self): + root = ID_AND_CHILDREN.create_resource({"anchors": {}}) + resolver = Registry().with_resource("urn:example", root).resolver() + resolved = resolver.lookup("urn:example") + assert resolved.contents == root.contents + + ref = "urn:example#noSuchAnchor" + with pytest.raises(exceptions.Unresolvable) as e: + resolver.lookup(ref) + assert "'noSuchAnchor' does not exist" in str(e.value) + assert e.value == exceptions.NoSuchAnchor( + ref="urn:example", + resource=root, + anchor="noSuchAnchor", + ) + + def test_lookup_invalid_JSON_pointerish_anchor(self): + resolver = Registry().resolver_with_root( + ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "foo": {"bar": 12}, + }, + ), + ) + + valid = resolver.lookup("#/foo/bar") + assert valid.contents == 12 + + with pytest.raises(exceptions.InvalidAnchor) as e: + resolver.lookup("#foo/bar") + assert " '#/foo/bar'" in str(e.value) + + def test_lookup_retrieved_resource(self): + resource = Resource.opaque(contents={"foo": "baz"}) + resolver = Registry(retrieve=lambda uri: resource).resolver() + resolved = resolver.lookup("http://example.com/") + assert resolved.contents == resource.contents + + def test_lookup_failed_retrieved_resource(self): + """ + Unretrievable exceptions are also wrapped in Unresolvable. + """ + + uri = "http://example.com/" + + registry = Registry(retrieve=blow_up) + with pytest.raises(exceptions.Unretrievable): + registry.get_or_retrieve(uri) + + resolver = registry.resolver() + with pytest.raises(exceptions.Unresolvable): + resolver.lookup(uri) + + def test_repeated_lookup_from_retrieved_resource(self): + """ + A (custom-)retrieved resource is added to the registry returned by + looking it up. + """ + resource = Resource.opaque(contents={"foo": "baz"}) + once = [resource] + + def retrieve(uri): + return once.pop() + + resolver = Registry(retrieve=retrieve).resolver() + resolved = resolver.lookup("http://example.com/") + assert resolved.contents == resource.contents + + resolved = resolved.resolver.lookup("http://example.com/") + assert resolved.contents == resource.contents + + def test_repeated_anchor_lookup_from_retrieved_resource(self): + resource = Resource.opaque(contents={"foo": "baz"}) + once = [resource] + + def retrieve(uri): + return once.pop() + + resolver = Registry(retrieve=retrieve).resolver() + resolved = resolver.lookup("http://example.com/") + assert resolved.contents == resource.contents + + resolved = resolved.resolver.lookup("#") + assert resolved.contents == resource.contents + + # FIXME: The tests below aren't really representable in the current + # suite, though we should probably think of ways to do so. + + def test_in_subresource(self): + root = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "children": [ + { + "ID": "child/", + "children": [{"ID": "grandchild"}], + }, + ], + }, + ) + registry = root @ Registry() + + resolver = registry.resolver() + first = resolver.lookup("http://example.com/") + assert first.contents == root.contents + + with pytest.raises(exceptions.Unresolvable): + first.resolver.lookup("grandchild") + + sub = first.resolver.in_subresource( + ID_AND_CHILDREN.create_resource(first.contents["children"][0]), + ) + second = sub.lookup("grandchild") + assert second.contents == {"ID": "grandchild"} + + def test_in_pointer_subresource(self): + root = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "children": [ + { + "ID": "child/", + "children": [{"ID": "grandchild"}], + }, + ], + }, + ) + registry = root @ Registry() + + resolver = registry.resolver() + first = resolver.lookup("http://example.com/") + assert first.contents == root.contents + + with pytest.raises(exceptions.Unresolvable): + first.resolver.lookup("grandchild") + + second = first.resolver.lookup("#/children/0") + third = second.resolver.lookup("grandchild") + assert third.contents == {"ID": "grandchild"} + + def test_dynamic_scope(self): + one = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/", + "children": [ + { + "ID": "child/", + "children": [{"ID": "grandchild"}], + }, + ], + }, + ) + two = ID_AND_CHILDREN.create_resource( + { + "ID": "http://example.com/two", + "children": [{"ID": "two-child/"}], + }, + ) + registry = [one, two] @ Registry() + + resolver = registry.resolver() + first = resolver.lookup("http://example.com/") + second = first.resolver.lookup("#/children/0") + third = second.resolver.lookup("grandchild") + fourth = third.resolver.lookup("http://example.com/two") + assert list(fourth.resolver.dynamic_scope()) == [ + ("http://example.com/child/grandchild", fourth.resolver._registry), + ("http://example.com/child/", fourth.resolver._registry), + ("http://example.com/", fourth.resolver._registry), + ] + assert list(third.resolver.dynamic_scope()) == [ + ("http://example.com/child/", third.resolver._registry), + ("http://example.com/", third.resolver._registry), + ] + assert list(second.resolver.dynamic_scope()) == [ + ("http://example.com/", second.resolver._registry), + ] + assert list(first.resolver.dynamic_scope()) == [] + + +class TestSpecification: + def test_create_resource(self): + specification = Specification( + name="", + id_of=lambda contents: "urn:fixedID", + subresources_of=lambda contents: [], + anchors_in=lambda specification, contents: [], + maybe_in_subresource=( + lambda segments, resolver, subresource: resolver + ), + ) + resource = specification.create_resource(contents={"foo": "baz"}) + assert resource == Resource( + contents={"foo": "baz"}, + specification=specification, + ) + assert resource.id() == "urn:fixedID" + + def test_detect_from_json_schema(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + specification = Specification.detect(schema) + assert specification == DRAFT202012 + + def test_detect_with_no_discernible_information(self): + with pytest.raises(exceptions.CannotDetermineSpecification): + Specification.detect({"foo": "bar"}) + + def test_detect_with_non_URI_schema(self): + with pytest.raises(exceptions.CannotDetermineSpecification): + Specification.detect({"$schema": 37}) + + def test_detect_with_no_discernible_information_and_default(self): + specification = Specification.OPAQUE.detect({"foo": "bar"}) + assert specification is Specification.OPAQUE + + def test_detect_unneeded_default(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + specification = Specification.OPAQUE.detect(schema) + assert specification == DRAFT202012 + + def test_non_mapping_detect(self): + with pytest.raises(exceptions.CannotDetermineSpecification): + Specification.detect(True) + + def test_non_mapping_detect_with_default(self): + specification = ID_AND_CHILDREN.detect(True) + assert specification is ID_AND_CHILDREN + + def test_detect_with_fallback(self): + specification = Specification.OPAQUE.detect({"foo": "bar"}) + assert specification is Specification.OPAQUE + + def test_repr(self): + assert ( + repr(ID_AND_CHILDREN) == "" + ) + + +class TestOpaqueSpecification: + THINGS = [{"foo": "bar"}, True, 37, "foo", object()] + + @pytest.mark.parametrize("thing", THINGS) + def test_no_id(self, thing): + """ + An arbitrary thing has no ID. + """ + + assert Specification.OPAQUE.id_of(thing) is None + + @pytest.mark.parametrize("thing", THINGS) + def test_no_subresources(self, thing): + """ + An arbitrary thing has no subresources. + """ + + assert list(Specification.OPAQUE.subresources_of(thing)) == [] + + @pytest.mark.parametrize("thing", THINGS) + def test_no_anchors(self, thing): + """ + An arbitrary thing has no anchors. + """ + + assert list(Specification.OPAQUE.anchors_in(thing)) == [] + + +@pytest.mark.parametrize( + "cls", + [Anchor, Registry, Resource, Specification, exceptions.PointerToNowhere], +) +def test_nonsubclassable(cls): + with pytest.raises(Exception, match="(?i)subclassing"): + + class Boom(cls): # pragma: no cover + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py new file mode 100644 index 0000000..85cf99e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py @@ -0,0 +1,34 @@ +import itertools + +import pytest + +from referencing import Resource, exceptions + + +def pairs(choices): + return itertools.combinations(choices, 2) + + +TRUE = Resource.opaque(True) + + +thunks = ( + lambda: exceptions.CannotDetermineSpecification(TRUE), + lambda: exceptions.NoSuchResource("urn:example:foo"), + lambda: exceptions.NoInternalID(TRUE), + lambda: exceptions.InvalidAnchor(resource=TRUE, anchor="foo", ref="a#b"), + lambda: exceptions.NoSuchAnchor(resource=TRUE, anchor="foo", ref="a#b"), + lambda: exceptions.PointerToNowhere(resource=TRUE, ref="urn:example:foo"), + lambda: exceptions.Unresolvable("urn:example:foo"), + lambda: exceptions.Unretrievable("urn:example:foo"), +) + + +@pytest.mark.parametrize("one, two", pairs(each() for each in thunks)) +def test_eq_incompatible_types(one, two): + assert one != two + + +@pytest.mark.parametrize("thunk", thunks) +def test_hash(thunk): + assert thunk() in {thunk()} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py new file mode 100644 index 0000000..c80714d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py @@ -0,0 +1,382 @@ +import pytest + +from referencing import Registry, Resource, Specification +import referencing.jsonschema + + +@pytest.mark.parametrize( + "uri, expected", + [ + ( + "https://json-schema.org/draft/2020-12/schema", + referencing.jsonschema.DRAFT202012, + ), + ( + "https://json-schema.org/draft/2019-09/schema", + referencing.jsonschema.DRAFT201909, + ), + ( + "http://json-schema.org/draft-07/schema#", + referencing.jsonschema.DRAFT7, + ), + ( + "http://json-schema.org/draft-06/schema#", + referencing.jsonschema.DRAFT6, + ), + ( + "http://json-schema.org/draft-04/schema#", + referencing.jsonschema.DRAFT4, + ), + ( + "http://json-schema.org/draft-03/schema#", + referencing.jsonschema.DRAFT3, + ), + ], +) +def test_schemas_with_explicit_schema_keywords_are_detected(uri, expected): + """ + The $schema keyword in JSON Schema is a dialect identifier. + """ + contents = {"$schema": uri} + resource = Resource.from_contents(contents) + assert resource == Resource(contents=contents, specification=expected) + + +def test_unknown_dialect(): + dialect_id = "http://example.com/unknown-json-schema-dialect-id" + with pytest.raises(referencing.jsonschema.UnknownDialect) as excinfo: + Resource.from_contents({"$schema": dialect_id}) + assert excinfo.value.uri == dialect_id + + +@pytest.mark.parametrize( + "id, specification", + [ + ("$id", referencing.jsonschema.DRAFT202012), + ("$id", referencing.jsonschema.DRAFT201909), + ("$id", referencing.jsonschema.DRAFT7), + ("$id", referencing.jsonschema.DRAFT6), + ("id", referencing.jsonschema.DRAFT4), + ("id", referencing.jsonschema.DRAFT3), + ], +) +def test_id_of_mapping(id, specification): + uri = "http://example.com/some-schema" + assert specification.id_of({id: uri}) == uri + + +@pytest.mark.parametrize( + "specification", + [ + referencing.jsonschema.DRAFT202012, + referencing.jsonschema.DRAFT201909, + referencing.jsonschema.DRAFT7, + referencing.jsonschema.DRAFT6, + ], +) +@pytest.mark.parametrize("value", [True, False]) +def test_id_of_bool(specification, value): + assert specification.id_of(value) is None + + +@pytest.mark.parametrize( + "specification", + [ + referencing.jsonschema.DRAFT202012, + referencing.jsonschema.DRAFT201909, + referencing.jsonschema.DRAFT7, + referencing.jsonschema.DRAFT6, + ], +) +@pytest.mark.parametrize("value", [True, False]) +def test_anchors_in_bool(specification, value): + assert list(specification.anchors_in(value)) == [] + + +@pytest.mark.parametrize( + "specification", + [ + referencing.jsonschema.DRAFT202012, + referencing.jsonschema.DRAFT201909, + referencing.jsonschema.DRAFT7, + referencing.jsonschema.DRAFT6, + ], +) +@pytest.mark.parametrize("value", [True, False]) +def test_subresources_of_bool(specification, value): + assert list(specification.subresources_of(value)) == [] + + +@pytest.mark.parametrize( + "uri, expected", + [ + ( + "https://json-schema.org/draft/2020-12/schema", + referencing.jsonschema.DRAFT202012, + ), + ( + "https://json-schema.org/draft/2019-09/schema", + referencing.jsonschema.DRAFT201909, + ), + ( + "http://json-schema.org/draft-07/schema#", + referencing.jsonschema.DRAFT7, + ), + ( + "http://json-schema.org/draft-06/schema#", + referencing.jsonschema.DRAFT6, + ), + ( + "http://json-schema.org/draft-04/schema#", + referencing.jsonschema.DRAFT4, + ), + ( + "http://json-schema.org/draft-03/schema#", + referencing.jsonschema.DRAFT3, + ), + ], +) +def test_specification_with(uri, expected): + assert referencing.jsonschema.specification_with(uri) == expected + + +@pytest.mark.parametrize( + "uri, expected", + [ + ( + "http://json-schema.org/draft-07/schema", + referencing.jsonschema.DRAFT7, + ), + ( + "http://json-schema.org/draft-06/schema", + referencing.jsonschema.DRAFT6, + ), + ( + "http://json-schema.org/draft-04/schema", + referencing.jsonschema.DRAFT4, + ), + ( + "http://json-schema.org/draft-03/schema", + referencing.jsonschema.DRAFT3, + ), + ], +) +def test_specification_with_no_empty_fragment(uri, expected): + assert referencing.jsonschema.specification_with(uri) == expected + + +def test_specification_with_unknown_dialect(): + dialect_id = "http://example.com/unknown-json-schema-dialect-id" + with pytest.raises(referencing.jsonschema.UnknownDialect) as excinfo: + referencing.jsonschema.specification_with(dialect_id) + assert excinfo.value.uri == dialect_id + + +def test_specification_with_default(): + dialect_id = "http://example.com/unknown-json-schema-dialect-id" + specification = referencing.jsonschema.specification_with( + dialect_id, + default=Specification.OPAQUE, + ) + assert specification is Specification.OPAQUE + + +# FIXME: The tests below should move to the referencing suite but I haven't yet +# figured out how to represent dynamic (& recursive) ref lookups in it. +def test_lookup_trivial_dynamic_ref(): + one = referencing.jsonschema.DRAFT202012.create_resource( + {"$dynamicAnchor": "foo"}, + ) + resolver = Registry().with_resource("http://example.com", one).resolver() + resolved = resolver.lookup("http://example.com#foo") + assert resolved.contents == one.contents + + +def test_multiple_lookup_trivial_dynamic_ref(): + TRUE = referencing.jsonschema.DRAFT202012.create_resource(True) + root = referencing.jsonschema.DRAFT202012.create_resource( + { + "$id": "http://example.com", + "$dynamicAnchor": "fooAnchor", + "$defs": { + "foo": { + "$id": "foo", + "$dynamicAnchor": "fooAnchor", + "$defs": { + "bar": True, + "baz": { + "$dynamicAnchor": "fooAnchor", + }, + }, + }, + }, + }, + ) + resolver = ( + Registry() + .with_resources( + [ + ("http://example.com", root), + ("http://example.com/foo/", TRUE), + ("http://example.com/foo/bar", root), + ], + ) + .resolver() + ) + + first = resolver.lookup("http://example.com") + second = first.resolver.lookup("foo/") + resolver = second.resolver.lookup("bar").resolver + fourth = resolver.lookup("#fooAnchor") + assert fourth.contents == root.contents + + +def test_multiple_lookup_dynamic_ref_to_nondynamic_ref(): + one = referencing.jsonschema.DRAFT202012.create_resource( + {"$anchor": "fooAnchor"}, + ) + two = referencing.jsonschema.DRAFT202012.create_resource( + { + "$id": "http://example.com", + "$dynamicAnchor": "fooAnchor", + "$defs": { + "foo": { + "$id": "foo", + "$dynamicAnchor": "fooAnchor", + "$defs": { + "bar": True, + "baz": { + "$dynamicAnchor": "fooAnchor", + }, + }, + }, + }, + }, + ) + resolver = ( + Registry() + .with_resources( + [ + ("http://example.com", two), + ("http://example.com/foo/", one), + ("http://example.com/foo/bar", two), + ], + ) + .resolver() + ) + + first = resolver.lookup("http://example.com") + second = first.resolver.lookup("foo/") + resolver = second.resolver.lookup("bar").resolver + fourth = resolver.lookup("#fooAnchor") + assert fourth.contents == two.contents + + +def test_lookup_trivial_recursive_ref(): + one = referencing.jsonschema.DRAFT201909.create_resource( + {"$recursiveAnchor": True}, + ) + resolver = Registry().with_resource("http://example.com", one).resolver() + first = resolver.lookup("http://example.com") + resolved = referencing.jsonschema.lookup_recursive_ref( + resolver=first.resolver, + ) + assert resolved.contents == one.contents + + +def test_lookup_recursive_ref_to_bool(): + TRUE = referencing.jsonschema.DRAFT201909.create_resource(True) + registry = Registry({"http://example.com": TRUE}) + resolved = referencing.jsonschema.lookup_recursive_ref( + resolver=registry.resolver(base_uri="http://example.com"), + ) + assert resolved.contents == TRUE.contents + + +def test_multiple_lookup_recursive_ref_to_bool(): + TRUE = referencing.jsonschema.DRAFT201909.create_resource(True) + root = referencing.jsonschema.DRAFT201909.create_resource( + { + "$id": "http://example.com", + "$recursiveAnchor": True, + "$defs": { + "foo": { + "$id": "foo", + "$recursiveAnchor": True, + "$defs": { + "bar": True, + "baz": { + "$recursiveAnchor": True, + "$anchor": "fooAnchor", + }, + }, + }, + }, + }, + ) + resolver = ( + Registry() + .with_resources( + [ + ("http://example.com", root), + ("http://example.com/foo/", TRUE), + ("http://example.com/foo/bar", root), + ], + ) + .resolver() + ) + + first = resolver.lookup("http://example.com") + second = first.resolver.lookup("foo/") + resolver = second.resolver.lookup("bar").resolver + fourth = referencing.jsonschema.lookup_recursive_ref(resolver=resolver) + assert fourth.contents == root.contents + + +def test_multiple_lookup_recursive_ref_with_nonrecursive_ref(): + one = referencing.jsonschema.DRAFT201909.create_resource( + {"$recursiveAnchor": True}, + ) + two = referencing.jsonschema.DRAFT201909.create_resource( + { + "$id": "http://example.com", + "$recursiveAnchor": True, + "$defs": { + "foo": { + "$id": "foo", + "$recursiveAnchor": True, + "$defs": { + "bar": True, + "baz": { + "$recursiveAnchor": True, + "$anchor": "fooAnchor", + }, + }, + }, + }, + }, + ) + three = referencing.jsonschema.DRAFT201909.create_resource( + {"$recursiveAnchor": False}, + ) + resolver = ( + Registry() + .with_resources( + [ + ("http://example.com", three), + ("http://example.com/foo/", two), + ("http://example.com/foo/bar", one), + ], + ) + .resolver() + ) + + first = resolver.lookup("http://example.com") + second = first.resolver.lookup("foo/") + resolver = second.resolver.lookup("bar").resolver + fourth = referencing.jsonschema.lookup_recursive_ref(resolver=resolver) + assert fourth.contents == two.contents + + +def test_empty_registry(): + assert referencing.jsonschema.EMPTY_REGISTRY == Registry() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py new file mode 100644 index 0000000..4b8ae91 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py @@ -0,0 +1,66 @@ +from pathlib import Path +import json +import os + +import pytest + +from referencing import Registry +from referencing.exceptions import Unresolvable +import referencing.jsonschema + + +class SuiteNotFound(Exception): + def __str__(self): # pragma: no cover + return ( + "Cannot find the referencing suite. " + "Set the REFERENCING_SUITE environment variable to the path to " + "the suite, or run the test suite from alongside a full checkout " + "of the git repository." + ) + + +if "REFERENCING_SUITE" in os.environ: # pragma: no cover + SUITE = Path(os.environ["REFERENCING_SUITE"]) / "tests" +else: + SUITE = Path(__file__).parent.parent.parent / "suite/tests" +if not SUITE.is_dir(): # pragma: no cover + raise SuiteNotFound() +DIALECT_IDS = json.loads(SUITE.joinpath("specifications.json").read_text()) + + +@pytest.mark.parametrize( + "test_path", + [ + pytest.param(each, id=f"{each.parent.name}-{each.stem}") + for each in SUITE.glob("*/**/*.json") + ], +) +def test_referencing_suite(test_path, subtests): + dialect_id = DIALECT_IDS[test_path.relative_to(SUITE).parts[0]] + specification = referencing.jsonschema.specification_with(dialect_id) + loaded = json.loads(test_path.read_text()) + registry = loaded["registry"] + registry = Registry().with_resources( + (uri, specification.create_resource(contents)) + for uri, contents in loaded["registry"].items() + ) + for test in loaded["tests"]: + with subtests.test(test=test): + if "normalization" in test_path.stem: + pytest.xfail("APIs need to change for proper URL support.") + + resolver = registry.resolver(base_uri=test.get("base_uri", "")) + + if test.get("error"): + with pytest.raises(Unresolvable): + resolver.lookup(test["ref"]) + else: + resolved = resolver.lookup(test["ref"]) + assert resolved.contents == test["target"] + + then = test.get("then") + while then: # pragma: no cover + with subtests.test(test=test, then=then): + resolved = resolved.resolver.lookup(then["ref"]) + assert resolved.contents == then["target"] + then = then.get("then") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py new file mode 100644 index 0000000..d0a8f8a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py @@ -0,0 +1,106 @@ +from functools import lru_cache +import json + +import pytest + +from referencing import Registry, Resource, exceptions +from referencing.jsonschema import DRAFT202012 +from referencing.retrieval import to_cached_resource + + +class TestToCachedResource: + def test_it_caches_retrieved_resources(self): + contents = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + stack = [json.dumps(contents)] + + @to_cached_resource() + def retrieve(uri): + return stack.pop() + + registry = Registry(retrieve=retrieve) + + expected = Resource.from_contents(contents) + + got = registry.get_or_retrieve("urn:example:schema") + assert got.value == expected + + # And a second time we get the same value. + again = registry.get_or_retrieve("urn:example:schema") + assert again.value is got.value + + def test_custom_loader(self): + contents = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + stack = [json.dumps(contents)[::-1]] + + @to_cached_resource(loads=lambda s: json.loads(s[::-1])) + def retrieve(uri): + return stack.pop() + + registry = Registry(retrieve=retrieve) + + expected = Resource.from_contents(contents) + + got = registry.get_or_retrieve("urn:example:schema") + assert got.value == expected + + # And a second time we get the same value. + again = registry.get_or_retrieve("urn:example:schema") + assert again.value is got.value + + def test_custom_from_contents(self): + contents = {} + stack = [json.dumps(contents)] + + @to_cached_resource(from_contents=DRAFT202012.create_resource) + def retrieve(uri): + return stack.pop() + + registry = Registry(retrieve=retrieve) + + expected = DRAFT202012.create_resource(contents) + + got = registry.get_or_retrieve("urn:example:schema") + assert got.value == expected + + # And a second time we get the same value. + again = registry.get_or_retrieve("urn:example:schema") + assert again.value is got.value + + def test_custom_cache(self): + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"} + mapping = { + "urn:example:1": dict(schema, foo=1), + "urn:example:2": dict(schema, foo=2), + "urn:example:3": dict(schema, foo=3), + } + + resources = { + uri: Resource.from_contents(contents) + for uri, contents in mapping.items() + } + + @to_cached_resource(cache=lru_cache(maxsize=2)) + def retrieve(uri): + return json.dumps(mapping.pop(uri)) + + registry = Registry(retrieve=retrieve) + + got = registry.get_or_retrieve("urn:example:1") + assert got.value == resources["urn:example:1"] + assert registry.get_or_retrieve("urn:example:1").value is got.value + assert registry.get_or_retrieve("urn:example:1").value is got.value + + got = registry.get_or_retrieve("urn:example:2") + assert got.value == resources["urn:example:2"] + assert registry.get_or_retrieve("urn:example:2").value is got.value + assert registry.get_or_retrieve("urn:example:2").value is got.value + + # This still succeeds, but evicts the first URI + got = registry.get_or_retrieve("urn:example:3") + assert got.value == resources["urn:example:3"] + assert registry.get_or_retrieve("urn:example:3").value is got.value + assert registry.get_or_retrieve("urn:example:3").value is got.value + + # And now this fails (as we popped the value out of `mapping`) + with pytest.raises(exceptions.Unretrievable): + registry.get_or_retrieve("urn:example:1") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py new file mode 100644 index 0000000..a614464 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/referencing/typing.py @@ -0,0 +1,61 @@ +""" +Type-annotation related support for the referencing library. +""" + +from __future__ import annotations + +from collections.abc import Mapping as Mapping +from typing import TYPE_CHECKING, Any, Protocol + +try: + from typing_extensions import TypeVar +except ImportError: # pragma: no cover + from typing import TypeVar + +if TYPE_CHECKING: + from referencing._core import Resolved, Resolver, Resource + +#: A URI which identifies a `Resource`. +URI = str + +#: The type of documents within a registry. +D = TypeVar("D", default=Any) + + +class Retrieve(Protocol[D]): + """ + A retrieval callable, usable within a `Registry` for resource retrieval. + + Does not make assumptions about where the resource might be coming from. + """ + + def __call__(self, uri: URI) -> Resource[D]: + """ + Retrieve the resource with the given URI. + + Raise `referencing.exceptions.NoSuchResource` if you wish to indicate + the retriever cannot lookup the given URI. + """ + ... + + +class Anchor(Protocol[D]): + """ + An anchor within a `Resource`. + + Beyond "simple" anchors, some specifications like JSON Schema's 2020 + version have dynamic anchors. + """ + + @property + def name(self) -> str: + """ + Return the name of this anchor. + """ + ... + + def resolve(self, resolver: Resolver[D]) -> Resolved[D]: + """ + Return the resource for this anchor. + """ + ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py new file mode 100644 index 0000000..257da6a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.py @@ -0,0 +1,5 @@ +from .rpds import * + +__doc__ = rpds.__doc__ +if hasattr(rpds, "__all__"): + __all__ = rpds.__all__ \ No newline at end of file diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi new file mode 100644 index 0000000..51f03b0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi @@ -0,0 +1,79 @@ +from collections.abc import ( + ItemsView, + Iterable, + Iterator, + KeysView, + Mapping, + ValuesView, +) +from typing import ( + TypeVar, +) + +_T = TypeVar("_T") +_KT_co = TypeVar("_KT_co", covariant=True) +_VT_co = TypeVar("_VT_co", covariant=True) +_KU_co = TypeVar("_KU_co", covariant=True) +_VU_co = TypeVar("_VU_co", covariant=True) + +class HashTrieMap(Mapping[_KT_co, _VT_co]): + def __init__( + self, + value: Mapping[_KT_co, _VT_co] | Iterable[tuple[_KT_co, _VT_co]] = {}, + **kwds: Mapping[_KT_co, _VT_co], + ): ... + def __getitem__(self, key: _KT_co) -> _VT_co: ... + def __iter__(self) -> Iterator[_KT_co]: ... + def __len__(self) -> int: ... + def discard(self, key: _KT_co) -> HashTrieMap[_KT_co, _VT_co]: ... + def items(self) -> ItemsView[_KT_co, _VT_co]: ... + def keys(self) -> KeysView[_KT_co]: ... + def values(self) -> ValuesView[_VT_co]: ... + def remove(self, key: _KT_co) -> HashTrieMap[_KT_co, _VT_co]: ... + def insert( + self, + key: _KT_co, + val: _VT_co, + ) -> HashTrieMap[_KT_co, _VT_co]: ... + def update( + self, + *args: Mapping[_KU_co, _VU_co] | Iterable[tuple[_KU_co, _VU_co]], + ) -> HashTrieMap[_KT_co | _KU_co, _VT_co | _VU_co]: ... + @classmethod + def convert( + cls, + value: Mapping[_KT_co, _VT_co] | Iterable[tuple[_KT_co, _VT_co]], + ) -> HashTrieMap[_KT_co, _VT_co]: ... + @classmethod + def fromkeys( + cls, + keys: Iterable[_KT_co], + value: _VT_co = None, + ) -> HashTrieMap[_KT_co, _VT_co]: ... + +class HashTrieSet(frozenset[_T]): + def __init__(self, value: Iterable[_T] = ()): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def discard(self, value: _T) -> HashTrieSet[_T]: ... + def remove(self, value: _T) -> HashTrieSet[_T]: ... + def insert(self, value: _T) -> HashTrieSet[_T]: ... + def update(self, *args: Iterable[_T]) -> HashTrieSet[_T]: ... + +class List(Iterable[_T]): + def __init__(self, value: Iterable[_T] = (), *more: _T): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def push_front(self, value: _T) -> List[_T]: ... + def drop_first(self) -> List[_T]: ... + +class Queue(Iterable[_T]): + def __init__(self, value: Iterable[_T] = (), *more: _T): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def enqueue(self, value: _T) -> Queue[_T]: ... + def dequeue(self, value: _T) -> Queue[_T]: ... + @property + def is_empty(self) -> _T: ... + @property + def peek(self) -> _T: ... diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so new file mode 100644 index 0000000..d0a6c50 Binary files /dev/null and b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so differ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA new file mode 100644 index 0000000..bcf64a4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA @@ -0,0 +1,99 @@ +Metadata-Version: 2.4 +Name: rpds-py +Version: 2026.6.3 +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Rust +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +License-File: LICENSE +Summary: Python bindings to Rust's persistent data structures (rpds) +Keywords: data structures,rust,persistent +Author-email: Julian Berman +License-Expression: MIT +Requires-Python: >=3.11 +Description-Content-Type: text/x-rst; charset=UTF-8 +Project-URL: Documentation, https://rpds.readthedocs.io/ +Project-URL: Funding, https://github.com/sponsors/Julian +Project-URL: Homepage, https://github.com/crate-py/rpds +Project-URL: Issues, https://github.com/crate-py/rpds/issues/ +Project-URL: Source, https://github.com/crate-py/rpds +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-rpds-py?utm_source=pypi-rpds-py&utm_medium=referral&utm_campaign=pypi-link +Project-URL: Upstream, https://github.com/orium/rpds + +=========== +``rpds.py`` +=========== + +|PyPI| |Pythons| |CI| + +.. |PyPI| image:: https://img.shields.io/pypi/v/rpds-py.svg + :alt: PyPI version + :target: https://pypi.org/project/rpds-py/ + +.. |Pythons| image:: https://img.shields.io/pypi/pyversions/rpds-py.svg + :alt: Supported Python versions + :target: https://pypi.org/project/rpds-py/ + +.. |CI| image:: https://github.com/crate-py/rpds/workflows/CI/badge.svg + :alt: Build status + :target: https://github.com/crate-py/rpds/actions?query=workflow%3ACI + +.. |ReadTheDocs| image:: https://readthedocs.org/projects/referencing/badge/?version=stable&style=flat + :alt: ReadTheDocs status + :target: https://referencing.readthedocs.io/en/stable/ + + +Python bindings to the `Rust rpds crate `_ for persistent data structures. + +What's here is quite minimal (in transparency, it was written initially to support replacing ``pyrsistent`` in the `referencing library `_). +If you see something missing (which is very likely), a PR is definitely welcome to add it. + +Installation +------------ + +The distribution on PyPI is named ``rpds.py`` (equivalently ``rpds-py``), and thus can be installed via e.g.: + +.. code:: sh + + $ pip install rpds-py + +Note that if you install ``rpds-py`` from source, you will need a Rust toolchain installed, as it is a build-time dependency. +An example of how to do so in a ``Dockerfile`` can be found `here `_. + +If you believe you are on a common platform which should have wheels built (i.e. and not need to compile from source), feel free to file an issue or pull request modifying the GitHub action used here to build wheels via ``maturin``. + +Usage +----- + +Methods in general are named similarly to their ``rpds`` counterparts (rather than ``pyrsistent``\ 's conventions, though probably a full drop-in ``pyrsistent``\ -compatible wrapper module is a good addition at some point). + +.. code:: python + + >>> from rpds import HashTrieMap, HashTrieSet, List + + >>> m = HashTrieMap({"foo": "bar", "baz": "quux"}) + >>> m.insert("spam", 37) == HashTrieMap({"foo": "bar", "baz": "quux", "spam": 37}) + True + >>> m.remove("foo") == HashTrieMap({"baz": "quux"}) + True + + >>> s = HashTrieSet({"foo", "bar", "baz", "quux"}) + >>> s.insert("spam") == HashTrieSet({"foo", "bar", "baz", "quux", "spam"}) + True + >>> s.remove("foo") == HashTrieSet({"bar", "baz", "quux"}) + True + + >>> L = List([1, 3, 5]) + >>> L.push_front(-1) == List([-1, 1, 3, 5]) + True + >>> L.rest == List([3, 5]) + True + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD new file mode 100644 index 0000000..fdc6559 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD @@ -0,0 +1,9 @@ +rpds/__init__.py,sha256=w3MgXW7lpTCICw0KXbw20QX573_kbsEnWIeMsCAugvM,99 +rpds/__init__.pyi,sha256=am7x6oMa_pu_kv1NlolqJbPr6_UvCvoyxGKrDGSMKEk,2602 +rpds/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +rpds/rpds.cpython-313-x86_64-linux-gnu.so,sha256=vds1186u90ikZhpzd04f-NWudpabMeHMf3V_tCS9jtU,965608 +rpds_py-2026.6.3.dist-info/METADATA,sha256=-L57F9o5gLLEhZaUFBGkPHCsQbHi1TCqUx8sFeqy6xw,4147 +rpds_py-2026.6.3.dist-info/WHEEL,sha256=7BZfoG3Y9QaTfCMq_fsT074r2quc7iaYrEUm_vuSIJQ,147 +rpds_py-2026.6.3.dist-info/licenses/LICENSE,sha256=MU5Okb47qpPA-0vMyeTpfNZD64ObBlr5IXgsIXX-mQk,1057 +rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json,sha256=gKO_uuFgflGyVpVmpGm4ptmphNSSPAgkCGQYpsij52M,20594 +rpds_py-2026.6.3.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL new file mode 100644 index 0000000..a4ceeb5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: maturin (1.14.1) +Root-Is-Purelib: false +Tag: cp313-cp313-manylinux_2_17_x86_64 +Tag: cp313-cp313-manylinux2014_x86_64 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE new file mode 100644 index 0000000..119a1f2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json new file mode 100644 index 0000000..87f246b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json @@ -0,0 +1,680 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:db798bf9-1726-4cb1-82cb-2a13771d0822", + "metadata": { + "timestamp": "2026-06-30T07:08:42.115208144Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.9" + } + ], + "component": { + "type": "library", + "bom-ref": "path+file:///home/runner/work/rpds/rpds#rpds-py@2026.6.3", + "name": "rpds-py", + "version": "2026.6.3", + "scope": "required", + "purl": "pkg:cargo/rpds-py@2026.6.3?download_url=file://.", + "components": [ + { + "type": "library", + "bom-ref": "path+file:///home/runner/work/rpds/rpds#rpds-py@2026.6.3 bin-target-0", + "name": "rpds", + "version": "2026.6.3", + "purl": "pkg:cargo/rpds-py@2026.6.3?download_url=file://.#src/lib.rs" + } + ] + }, + "properties": [ + { + "name": "cdx:rustc:sbom:target:all_targets", + "value": "true" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#archery@1.2.2", + "author": "Diogo Sousa ", + "name": "archery", + "version": "1.2.2", + "description": "Abstract over the atomicity of reference-counting pointers", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/archery@1.2.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/archery" + }, + { + "type": "website", + "url": "https://github.com/orium/archery" + }, + { + "type": "vcs", + "url": "https://github.com/orium/archery" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.177", + "author": "The Rust Project Developers", + "name": "libc", + "version": "0.2.177", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.177", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "author": "Aleksey Kladov ", + "name": "once_cell", + "version": "1.21.3", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103", + "author": "David Tolnay , Alex Crichton ", + "name": "proc-macro2", + "version": "1.0.103", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.103", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-build-config", + "version": "0.29.0", + "description": "Build configuration for the PyO3 ecosystem", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-build-config@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-ffi", + "version": "0.29.0", + "description": "Python-API bindings for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-ffi@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros-backend", + "version": "0.29.0", + "description": "Code generation for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros-backend@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros", + "version": "0.29.0", + "description": "Proc macros for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros@0.29.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "author": "PyO3 Project and Contributors ", + "name": "pyo3", + "version": "0.29.0", + "description": "Bindings to Python interpreter", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3@0.29.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crate/pyo3/" + }, + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "pyo3-python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42", + "author": "David Tolnay ", + "name": "quote", + "version": "1.0.42", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.42", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rpds@1.2.1", + "author": "Diogo Sousa ", + "name": "rpds", + "version": "1.2.1", + "description": "Persistent data structures with structural sharing", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e025feb26210bc196b908e72deb063b1b4000754304341cbc168a1e72c857ebc" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/rpds@1.2.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rpds" + }, + { + "type": "website", + "url": "https://github.com/orium/rpds" + }, + { + "type": "vcs", + "url": "https://github.com/orium/rpds" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "author": "The Servo Project Developers", + "name": "smallvec", + "version": "1.15.1", + "description": "'Small vector' optimization: store up to a small number of items on the stack", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/smallvec@1.15.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/smallvec/" + }, + { + "type": "vcs", + "url": "https://github.com/servo/rust-smallvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.111", + "author": "David Tolnay ", + "name": "syn", + "version": "2.0.111", + "description": "Parser for Rust source code", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.111", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.3", + "author": "Dan Gohman ", + "name": "target-lexicon", + "version": "0.13.3", + "description": "LLVM target triple types", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/target-lexicon@0.13.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/target-lexicon/" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/target-lexicon" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#triomphe@0.1.15", + "author": "Manish Goregaokar , The Servo Project Developers", + "name": "triomphe", + "version": "0.1.15", + "description": "A fork of std::sync::Arc with some extra functionality and without weak references (originally servo_arc)", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/triomphe@0.1.15", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Manishearth/triomphe" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.22", + "author": "David Tolnay ", + "name": "unicode-ident", + "version": "1.0.22", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + } + ], + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.22", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///home/runner/work/rpds/rpds#rpds-py@2026.6.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#archery@1.2.2", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#rpds@1.2.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#archery@1.2.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#triomphe@0.1.15" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.177" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.177", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.111" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.111" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.177", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.29.0", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rpds@1.2.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#archery@1.2.2", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.111", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.3" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#triomphe@0.1.15" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.22" + } + ] +} \ No newline at end of file diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA new file mode 100644 index 0000000..f461f39 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA @@ -0,0 +1,493 @@ +Metadata-Version: 2.4 +Name: sse-starlette +Version: 3.4.11 +Summary: SSE plugin for Starlette +Author-email: sysid +License-Expression: BSD-3-Clause +Project-URL: Homepage, https://github.com/sysid/sse-starlette +Project-URL: Repository, https://github.com/sysid/sse-starlette +Project-URL: Issues, https://github.com/sysid/sse-starlette/issues +Project-URL: Source, https://github.com/sysid/sse-starlette +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: AUTHORS +Requires-Dist: starlette>=0.49.1 +Requires-Dist: anyio>=4.7.0 +Provides-Extra: examples +Requires-Dist: uvicorn>=0.34.0; extra == "examples" +Requires-Dist: fastapi>=0.115.12; extra == "examples" +Requires-Dist: pydantic>=2; extra == "examples" +Provides-Extra: examples-db +Requires-Dist: sqlalchemy[asyncio]>=2.0.41; extra == "examples-db" +Requires-Dist: aiosqlite>=0.21.0; extra == "examples-db" +Provides-Extra: uvicorn +Requires-Dist: uvicorn>=0.34.0; extra == "uvicorn" +Provides-Extra: granian +Requires-Dist: granian>=2.3.1; extra == "granian" +Provides-Extra: daphne +Requires-Dist: daphne>=4.2.0; extra == "daphne" +Dynamic: license-file + +# Server-Sent Events for [Starlette](https://github.com/encode/starlette) and [FastAPI](https://fastapi.tiangolo.com/) + +[![Downloads](https://static.pepy.tech/badge/sse-starlette/week)](https://pepy.tech/project/sse-starlette) +[![PyPI Version][pypi-image]][pypi-url] +[![Build Status][build-image]][build-url] + +> Background: https://sysid.github.io/server-sent-events/ + +Production ready Server-Sent Events implementation for Starlette and FastAPI following the [W3C SSE specification](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). + +## Installation + +```bash +pip install sse-starlette +uv add sse-starlette + +# To run the examples (fastapi, uvicorn, pydantic) +uv add sse-starlette[examples] + +# Example 03 also needs the DB extras (sqlalchemy, aiosqlite) +uv add sse-starlette[examples,examples-db] + +# Recommended ASGI server +uv add sse-starlette[uvicorn,granian,daphne] +``` + +## Quick Start + +```python +import asyncio +from starlette.applications import Starlette +from starlette.routing import Route +from sse_starlette import EventSourceResponse + +async def generate_events(): + for i in range(10): + yield {"data": f"Event {i}"} + await asyncio.sleep(1) + +async def sse_endpoint(request): + return EventSourceResponse(generate_events()) + +app = Starlette(routes=[Route("/events", sse_endpoint)]) +``` + +## Core Features + +- **Standards Compliant**: Full SSE specification implementation +- **Framework Integration**: Native Starlette and FastAPI support +- **Async/Await**: Built on modern Python async patterns +- **Connection Management**: Automatic client disconnect detection +- **Graceful Shutdown**: Proper cleanup on server termination with cooperative shutdown support +- **Thread Safety**: Context-local event management for multi-threaded applications +- **Multi-Loop Support**: Works correctly with multiple asyncio event loops + +For a detailed look at the internal task coordination, shutdown detection, and cancellation +flows, see [ARCHITECTURE.md](ARCHITECTURE.md). + +## Key Components + +### EventSourceResponse + +The main response class that handles SSE streaming: + +```python +from sse_starlette import EventSourceResponse + +# Basic usage +async def stream_data(): + for item in data: + yield {"data": item, "event": "update", "id": str(item.id)} + +return EventSourceResponse(stream_data()) +``` + +### ServerSentEvent + +For structured event creation: + +```python +from sse_starlette import ServerSentEvent + +event = ServerSentEvent( + data="Custom message", + event="notification", + id="msg-123", + retry=5000 +) +``` + +### JSONServerSentEvent + +For an easy way to send json data as SSE events: + +```python +from sse_starlette import JSONServerSentEvent + +event = JSONServerSentEvent( + data={"field":"value"}, # Anything serializable with json.dumps +) +``` + +## Advanced Usage + +### Custom Ping Configuration + +```python +from sse_starlette import ServerSentEvent + +def custom_ping(): + return ServerSentEvent(comment="Custom ping message") + +return EventSourceResponse( + generate_events(), + ping=10, # Ping every 10 seconds + ping_message_factory=custom_ping +) +``` + +### Multi-Threaded Usage + +sse-starlette now supports usage in multi-threaded applications and with multiple asyncio event loops: + +```python +import threading +import asyncio +from sse_starlette import EventSourceResponse + +def run_sse_in_thread(): + """SSE streaming works correctly in separate threads""" + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def thread_events(): + for i in range(5): + yield {"data": f"Thread event {i}"} + await asyncio.sleep(1) + + # This works without "Event bound to different loop" errors + response = EventSourceResponse(thread_events()) + loop.close() + +# Start SSE in multiple threads +for i in range(3): + thread = threading.Thread(target=run_sse_in_thread) + thread.start() +``` + +### Database Streaming (Thread-Safe) + +```python +async def stream_database_results(request): + # CORRECT: Create session within generator context + async with AsyncSession() as session: + results = await session.execute(select(User)) + for row in results: + if await request.is_disconnected(): + break + yield {"data": row.name, "id": str(row.id)} + +return EventSourceResponse(stream_database_results(request)) +``` + +### Error Handling and Timeouts + +```python +async def robust_stream(request): + try: + for i in range(100): + if await request.is_disconnected(): + break + yield {"data": f"Item {i}"} + await asyncio.sleep(0.5) + except asyncio.CancelledError: + # Client disconnected - perform cleanup + raise + +return EventSourceResponse( + robust_stream(request), + send_timeout=30, # Timeout hanging sends + headers={"Cache-Control": "no-cache"} +) +``` + +### Memory Channels Alternative + +For complex data flows, use memory channels instead of generators: + +```python +import anyio +from functools import partial + +async def data_producer(send_channel): + async with send_channel: + for i in range(10): + await send_channel.send({"data": f"Item {i}"}) + await anyio.sleep(1) + +async def channel_endpoint(request): + send_channel, receive_channel = anyio.create_memory_object_stream(10) + + return EventSourceResponse( + receive_channel, + data_sender_callable=partial(data_producer, send_channel) + ) +``` + +### Cooperative Shutdown + +By default, generators receive `CancelledError` immediately when the server shuts down. With +cooperative shutdown, generators can detect the shutdown signal, send farewell events to clients, +and exit gracefully within a configurable grace period. + +```python +import anyio +from sse_starlette import EventSourceResponse + +async def graceful_stream(request): + shutdown_event = anyio.Event() + + async def generate(): + try: + while not shutdown_event.is_set(): + yield {"data": "tick"} + # Check for shutdown between iterations + with anyio.move_on_after(1.0): + await shutdown_event.wait() + # Shutdown detected — send farewell event + yield {"event": "shutdown", "data": "Server is shutting down"} + except anyio.get_cancelled_exc_class(): + # Grace period expired — generator force-cancelled + raise + + return EventSourceResponse( + generate(), + shutdown_event=shutdown_event, # Library sets this on shutdown + shutdown_grace_period=5.0, # Seconds to wait before force-cancel + ) +``` + +**How it works:** + +1. On server shutdown, the library sets your `shutdown_event` +2. Your generator sees the event and can yield final events +3. If the generator exits within `shutdown_grace_period`, shutdown is clean (no `CancelledError`) +4. If the generator doesn't exit in time, it is force-cancelled as before + +**Important:** `shutdown_grace_period` should be less than your ASGI server's graceful shutdown +timeout (e.g. uvicorn's `--timeout-graceful-shutdown`), otherwise the process is killed before the +grace period expires. + +Without `shutdown_event` (the default), behavior is identical to previous versions: immediate +cancellation on server shutdown. + +## Configuration Options + +### EventSourceResponse Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `content` | `ContentStream` | Required | Async generator or iterable | +| `ping` | `int` | 15 | Ping interval in seconds (`0` disables keep-alive pings) | +| `sep` | `str` | `"\r\n"` | Line separator (`\r\n`, `\r`, `\n`) | +| `send_timeout` | `float` | `None` | Send operation timeout in seconds | +| `headers` | `dict` | `None` | Additional HTTP headers | +| `ping_message_factory` | `Callable` | `None` | Custom ping message creator | +| `shutdown_event` | `anyio.Event` | `None` | Event set by library on server shutdown | +| `shutdown_grace_period` | `float` | `0` | Seconds to wait after setting `shutdown_event` before force-cancel | + +### Client Disconnection + +```python +async def monitored_stream(request): + events_sent = 0 + try: + while events_sent < 100: + if await request.is_disconnected(): + print(f"Client disconnected after {events_sent} events") + break + + yield {"data": f"Event {events_sent}"} + events_sent += 1 + await asyncio.sleep(1) + + except asyncio.CancelledError: + print("Stream cancelled") + raise +``` + +## Testing + +sse-starlette includes now comprehensive test isolation without manual setup. The library automatically handles event loop contexts, eliminating the need for manual state resets: + +```python +# this is deprecated and not needed since version 3.0.0 +import pytest +from sse_starlette import EventSourceResponse + +@pytest.fixture +def reset_sse_app_status(): + AppStatus.should_exit_event = None + yield + AppStatus.should_exit_event = None +``` + +## Production Considerations + +### Performance Limits + +- **Memory**: Each connection maintains a buffer. Monitor memory usage. +- **Connections**: Limited by system file descriptors and application design. +- **Network**: High-frequency events can saturate bandwidth. + +### Error Recovery + +Implement client-side reconnection logic: + +```javascript +function createEventSource(url) { + const eventSource = new EventSource(url); + + eventSource.onerror = function() { + setTimeout(() => { + createEventSource(url); // Reconnect after delay + }, 5000); + }; + + return eventSource; +} +``` + +## Learning Resources + +### Examples Directory + +The `examples/` directory contains production-ready patterns: + +- **`01_basic_sse.py`**: Fundamental SSE concepts +- **`02_message_broadcasting.py`**: Multi-client message distribution +- **`03_database_streaming.py`**: Thread-safe database integration +- **`04_advanced_features.py`**: Custom protocols and error handling + +### Demonstrations Directory + +The `examples/demonstrations/` directory provides educational scenarios: + +**Basic Patterns** (`basic_patterns/`): +- Client disconnect detection and cleanup +- Graceful server shutdown behavior + +**Production Scenarios** (`production_scenarios/`): +- Load testing with concurrent clients +- Network interruption handling + +**Advanced Patterns** (`advanced_patterns/`): +- Memory channels vs generators +- Error recovery and circuit breakers +- Custom protocol development + +Run any demonstration: +```bash +python examples/demonstrations/basic_patterns/client_disconnect.py +python examples/demonstrations/production_scenarios/load_simulations.py +python examples/demonstrations/advanced_patterns/error_recovery.py +``` + +## Troubleshooting + +### Common Issues + +**Database session errors with async generators** +- Create database sessions inside generators, not as dependencies + +**Hanging connections after client disconnect** +- Always check `await request.is_disconnected()` in loops +- Use `send_timeout` parameter to detect dead connections + +If you are using Postman, please see: https://github.com/sysid/sse-starlette/issues/47#issuecomment-1445953826 + +### Performance Optimization + +```python +# Connection limits +class ConnectionLimiter: + def __init__(self, max_connections=100): + self.semaphore = asyncio.Semaphore(max_connections) + + async def limited_endpoint(self, request): + async with self.semaphore: + return EventSourceResponse(generate_events()) +``` + +## Network-Level Gotchas + +Network infrastructure components can buffer SSE streams, breaking real-time delivery. Here are the most common issues and solutions: + +### Reverse Proxy Buffering (Nginx/Apache) + +**Problem**: Nginx buffers responses by default, delaying SSE events until ~16KB accumulates. + +**Solution**: Add the `X-Accel-Buffering: no` header. + +**Nginx Configuration** (if you can't modify app headers): +```nginx +location /events { + proxy_pass http://localhost:8000; + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; # Disable for this location + chunked_transfer_encoding off; +} +``` + +### CDN Issues + +**Cloudflare**: Buffers ~100KB before flushing to clients, breaking real-time delivery. +**Akamai**: Edge servers buffer by default. + +### Load Balancer Problems + +**HAProxy**: Timeout settings must exceed heartbeat frequency. +```haproxy +# Ensure timeouts > ping interval +timeout client 60s # If ping every 45s +timeout server 60s +``` + +**F5 Load Balancers**: Buffer responses by default. + +## Contributing + +See examples and demonstrations for implementation patterns. Run tests with: + +```bash +make test-unit # Unit tests only (default dev install) +make test # Unit + docker integration tests +make test-experimentation # Optional experimentation tests (multi-consumer load tests) + # Requires the `experimentation` dependency group: + # uv sync --group experimentation +``` + +The `experimentation` group is opt-in (kept out of the default `dev` install) to keep the +contributor footprint small. See `tests/experimentation/` for the tests it enables. + + +[pypi-image]: https://badge.fury.io/py/sse-starlette.svg +[pypi-url]: https://pypi.org/project/sse-starlette/ +[build-image]: https://github.com/sysid/sse-starlette/actions/workflows/build.yml/badge.svg +[build-url]: https://github.com/sysid/sse-starlette/actions/workflows/build.yml +[coverage-image]: https://codecov.io/gh/sysid/sse-starlette/branch/master/graph/badge.svg + + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD new file mode 100644 index 0000000..7892a92 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD @@ -0,0 +1,11 @@ +sse_starlette/__init__.py,sha256=9ixg0ov-gHr8LSSotykb6kDhX4WqNrT_0-TD1rLkgF0,219 +sse_starlette/_utils.py,sha256=c1971mx2Wlr_MN7ekij6JYN-e3i9IQbY3l4MqDQ_VEA,1439 +sse_starlette/event.py,sha256=wePjktO-n0jsguXGmlu3hQooQWfJYNrUa8iD_RDe2K0,2801 +sse_starlette/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sse_starlette/sse.py,sha256=M7qzZzruHeJQLoLoCTQ4f7oJXu5_XaA4RTawayI4bV0,21365 +sse_starlette-3.4.11.dist-info/licenses/AUTHORS,sha256=wv9j0uzxKLvairGyAWgwpdoSQgL7uXFoaFu5lGrtPxo,395 +sse_starlette-3.4.11.dist-info/licenses/LICENSE,sha256=gK9r_Mvr0sFNSquWaVyrmVDhdxkMyA9e-BnpmZaIPo4,1511 +sse_starlette-3.4.11.dist-info/METADATA,sha256=DSt-l025V8pKogBTkYUWpvKKhDPv9AFyhIPLsjXkg6Q,15343 +sse_starlette-3.4.11.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91 +sse_starlette-3.4.11.dist-info/top_level.txt,sha256=-WvV5NMWkq_J766sZPusKqv8pESM-Dxugi96Lp9oer4,14 +sse_starlette-3.4.11.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL new file mode 100644 index 0000000..13013d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS new file mode 100644 index 0000000..26fddfc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS @@ -0,0 +1,10 @@ +sysid +synodriver +Skyler Lewis +Margret `Pax` Williams +nekonoshiri +justindujardin +Håvard Thom <34199185+havardthom@users.noreply.github.com> +Vaibhav Mule +issmirnov +Cyprien diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE new file mode 100644 index 0000000..3d12971 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE @@ -0,0 +1,27 @@ +Copyright © 2020, [sysid](https://sysid.github.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt new file mode 100644 index 0000000..a0dde6f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt @@ -0,0 +1 @@ +sse_starlette diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py new file mode 100644 index 0000000..207a327 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py @@ -0,0 +1,5 @@ +from sse_starlette.event import ServerSentEvent, JSONServerSentEvent +from sse_starlette.sse import EventSourceResponse + +__all__ = ["EventSourceResponse", "ServerSentEvent", "JSONServerSentEvent"] +__version__ = "3.4.11" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py new file mode 100644 index 0000000..c7e03d4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py @@ -0,0 +1,37 @@ +import sys +from contextlib import contextmanager +from typing import Generator + +# Divergence #3 (see sse_starlette/sse.py module docstring): vendored copy of +# starlette._utils.collapse_excgroups. We do not import the upstream version +# because it lives in a private (underscore-prefixed) Starlette module, and +# the pinned floor (starlette>=0.41.3) makes that import surface unstable. +# +# Behaviour: AnyIO v4 wraps task-group failures in an ExceptionGroup; this +# helper unwraps single-exception groups so user middleware sees the bare +# exception (matching pre-v4 anyio and Starlette's StreamingResponse). +# Solution per https://anyio.readthedocs.io/en/stable/migration.html + +has_exceptiongroups = True +if sys.version_info < (3, 11): + try: + from exceptiongroup import BaseExceptionGroup # noqa: F401 + except ImportError: + has_exceptiongroups = False + + +@contextmanager +def collapse_excgroups() -> Generator[None, None, None]: + try: + yield + except BaseException as exc: + if has_exceptiongroups: + # `ty` does not narrow BaseExceptionGroup.exceptions; the runtime + # contract is identical to starlette._utils.collapse_excgroups. + while isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1: # ty: ignore[unresolved-attribute] + exc = exc.exceptions[0] # ty: ignore[unresolved-attribute] + + raise exc + + +__all__ = ["collapse_excgroups"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py new file mode 100644 index 0000000..d72cfd5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py @@ -0,0 +1,96 @@ +import io +import re +import json +from typing import Optional, Any, Union + + +class ServerSentEvent: + """ + Helper class to format data for Server-Sent Events (SSE). + """ + + _LINE_SEP_EXPR = re.compile(r"\r\n|\r|\n") + DEFAULT_SEPARATOR = "\r\n" + + def __init__( + self, + data: Optional[Any] = None, + *, + event: Optional[str] = None, + id: Optional[str] = None, + retry: Optional[int] = None, + comment: Optional[str] = None, + sep: Optional[str] = None, + ) -> None: + self.data = data + self.event = event + self.id = id + self.retry = retry + self.comment = comment + self._sep = sep if sep is not None else self.DEFAULT_SEPARATOR + + def encode(self) -> bytes: + buffer = io.StringIO() + if self.comment is not None: + for chunk in self._LINE_SEP_EXPR.split(str(self.comment)): + buffer.write(f": {chunk}{self._sep}") + + if self.id is not None: + # Clean newlines in the event id + buffer.write("id: " + self._LINE_SEP_EXPR.sub("", self.id) + self._sep) + + if self.event is not None: + # Clean newlines in the event name + buffer.write( + "event: " + self._LINE_SEP_EXPR.sub("", self.event) + self._sep + ) + + if self.data is not None: + # Break multi-line data into multiple data: lines + for chunk in self._LINE_SEP_EXPR.split(str(self.data)): + buffer.write(f"data: {chunk}{self._sep}") + + if self.retry is not None: + if not isinstance(self.retry, int): + raise TypeError("retry argument must be int") + buffer.write(f"retry: {self.retry}{self._sep}") + + buffer.write(self._sep) + return buffer.getvalue().encode("utf-8") + + +class JSONServerSentEvent(ServerSentEvent): + """ + Helper class to format JSON data for Server-Sent Events (SSE). + """ + + def __init__( + self, + data: Optional[Any] = None, + *args, + **kwargs, + ) -> None: + super().__init__( + json.dumps( + data, + ensure_ascii=False, + allow_nan=False, + indent=None, + separators=(",", ":"), + ) + if data is not None + else None, + *args, + **kwargs, + ) + + +def ensure_bytes(data: Union[bytes, dict, ServerSentEvent, Any], sep: str) -> bytes: + if isinstance(data, (bytes, memoryview)): + return bytes(data) + if isinstance(data, ServerSentEvent): + return data.encode() + if isinstance(data, dict): + data["sep"] = sep + return ServerSentEvent(**data).encode() + return ServerSentEvent(str(data), sep=sep).encode() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py new file mode 100644 index 0000000..c096b66 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py @@ -0,0 +1,531 @@ +"""Server-Sent Events response for Starlette / FastAPI. + +Intentional divergence from ``starlette.responses.StreamingResponse`` +-------------------------------------------------------------------- + +``EventSourceResponse`` is modelled on Starlette's ``StreamingResponse`` and +re-syncs most of its behaviour (WebSocket denial, ``collapse_excgroups()`` +around the task group, ``memoryview`` chunk handling). The following points +are deliberate divergences — DO NOT "fix" them without reading the rationale: + +1. ASGI ``spec_version >= 2.4`` fast path is NOT adopted. + Upstream short-circuits to ``await stream_response(send)`` and converts + ``OSError`` into ``ClientDisconnect``, skipping ``listen_for_disconnect``. + We keep ``_listen_for_disconnect`` running because it + (a) invokes ``client_close_handler_callable`` on disconnect, + (b) flips ``self.active = False`` so ``_ping`` and the cooperative + shutdown grace loop exit promptly. + Adopting the upstream fast path would regress both features. + +2. ``_wrap_websocket_denial_send`` is inlined in this module rather than + inherited from ``starlette.responses.Response``. The helper landed on + Starlette ``main`` after our minimum pin (``starlette>=0.41.3``); inline + until the floor moves past the release that contains it. + +3. ``collapse_excgroups()`` is vendored in ``sse_starlette._utils`` rather + than imported from ``starlette._utils`` (private module). +""" + +import asyncio +import logging +import signal +import threading +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Coroutine, + Iterator, + Mapping, + Optional, + Set, + Union, +) + +import anyio +from starlette.background import BackgroundTask +from starlette.concurrency import iterate_in_threadpool +from starlette.datastructures import MutableHeaders +from starlette.responses import Response +from starlette.types import Receive, Scope, Send, Message + +from sse_starlette._utils import collapse_excgroups +from sse_starlette.event import ServerSentEvent, ensure_bytes + + +logger = logging.getLogger(__name__) + + +@dataclass +class _ShutdownState: + """Per-thread state for shutdown coordination. + + Issue #152 fix: Uses threading.local() instead of ContextVar to ensure + one watcher per thread rather than one per async context. + """ + + events: Set[anyio.Event] = field(default_factory=set) + watcher_started: bool = False + + +# Each thread gets its own shutdown state (one event loop per thread typically) +_thread_state = threading.local() + + +def _get_shutdown_state() -> _ShutdownState: + """Get or create shutdown state for the current thread.""" + state = getattr(_thread_state, "shutdown_state", None) + if state is None: + state = _ShutdownState() + _thread_state.shutdown_state = state + return state + + +def _get_uvicorn_server(): + """ + Try to get uvicorn Server instance via signal handler introspection. + + When uvicorn registers signal handlers, they're bound methods on the Server instance. + We can retrieve the Server from the handler's __self__ attribute. + + Returns None if: + - Not running under uvicorn + - Signal handler isn't a bound method + - Any introspection fails + """ + try: + handler = signal.getsignal(signal.SIGTERM) + if hasattr(handler, "__self__"): + server = handler.__self__ + if hasattr(server, "should_exit"): + return server + except Exception: + pass + return None + + +async def _shutdown_watcher() -> None: + """ + Poll for shutdown and broadcast to all events in this context. + + One watcher runs per thread (event loop). Checks two shutdown sources: + 1. AppStatus.should_exit - set when our monkey-patch works + 2. uvicorn Server.should_exit - via signal handler introspection (Issue #132 fix) + + When either becomes True, signals all registered events. + """ + state = _get_shutdown_state() + uvicorn_server = _get_uvicorn_server() + + try: + while True: + # Check our flag (monkey-patch worked or manually set) + if AppStatus.should_exit: + break + # Check uvicorn's flag directly (monkey-patch failed - Issue #132) + if ( + AppStatus.enable_automatic_graceful_drain + and uvicorn_server is not None + and uvicorn_server.should_exit + ): + AppStatus.should_exit = True # Sync state for consistency + break + await anyio.sleep(0.5) + + # Shutdown detected - broadcast to all waiting events + for event in list(state.events): + event.set() + finally: + # Allow watcher to be restarted if loop is reused + state.watcher_started = False + + +def _ensure_watcher_started_on_this_loop() -> None: + """Ensure the shutdown watcher is running for this thread (event loop).""" + state = _get_shutdown_state() + if not state.watcher_started: + state.watcher_started = True + try: + loop = asyncio.get_running_loop() + loop.create_task(_shutdown_watcher()) + except RuntimeError: + # No running loop - shouldn't happen in normal use + state.watcher_started = False + + +def _wrap_websocket_denial_send(send: Send) -> Send: + """Mirror of ``starlette.responses.Response._wrap_websocket_denial_send``. + + Divergence #2 (see module docstring): inlined because the helper landed + on Starlette ``main`` (commit 9ee9519) after our minimum pin + ``starlette>=0.41.3``. Drop this once the floor moves past the release + that contains it. + """ + + async def wrapped(message: Message) -> None: + message_type = message["type"] + if message_type in {"http.response.start", "http.response.body"}: + message = {**message, "type": "websocket." + message_type} + await send(message) + + return wrapped + + +class SendTimeoutError(TimeoutError): + pass + + +class AppStatus: + """Helper to capture a shutdown signal from Uvicorn so we can gracefully terminate SSE streams.""" + + should_exit = False + enable_automatic_graceful_drain = True + original_handler: Optional[Callable] = None + + @staticmethod + def disable_automatic_graceful_drain(): + """ + Prevent automatic SSE stream termination on server shutdown. + + WARNING: When disabled, you MUST set AppStatus.should_exit = True + at some point during shutdown, or streams will never close and the + server will hang indefinitely (or until uvicorn's graceful shutdown + timeout expires). + """ + AppStatus.enable_automatic_graceful_drain = False + + @staticmethod + def enable_automatic_graceful_drain_mode(): + """ + Re-enable automatic SSE stream termination on server shutdown. + + This restores the default behavior where SIGTERM triggers immediate + stream draining. Call this to undo a previous call to + disable_automatic_graceful_drain(). + """ + AppStatus.enable_automatic_graceful_drain = True + + @staticmethod + def handle_exit(*args, **kwargs): + if AppStatus.enable_automatic_graceful_drain: + AppStatus.should_exit = True + if AppStatus.original_handler is not None: + AppStatus.original_handler(*args, **kwargs) + + +try: + from uvicorn.main import Server + + AppStatus.original_handler = Server.handle_exit + Server.handle_exit = AppStatus.handle_exit # type: ignore +except ImportError: + logger.debug( + "Uvicorn not installed. Graceful shutdown on server termination disabled." + ) + +Content = Union[str, bytes, dict, ServerSentEvent, Any] +SyncContentStream = Iterator[Content] +AsyncContentStream = AsyncIterable[Content] +ContentStream = Union[AsyncContentStream, SyncContentStream] + + +class EventSourceResponse(Response): + """Streaming response implementing the SSE (Server-Sent Events) specification. + + Args: + content: Async iterable or sync iterator yielding SSE event data. + status_code: HTTP status code. Default: 200. + headers: Additional HTTP headers. + media_type: Response media type. Default: "text/event-stream". + background: Background task to run after response completes. + ping: Ping interval in seconds. Default: 15. Set to 0 to disable + keep-alive pings entirely (no ping task is started). + sep: Line separator for SSE messages ("\\r\\n", "\\r", or "\\n"). + ping_message_factory: Callable returning custom ping ServerSentEvent. + data_sender_callable: Async callable for push-based data sending. + send_timeout: Timeout in seconds for individual data or heartbeat sends. + client_close_handler_callable: Async callback on client disconnect. + shutdown_event: Optional ``anyio.Event`` set by the library when server + shutdown is detected. Generators can watch this event to send farewell + messages and exit cooperatively instead of receiving CancelledError. + shutdown_grace_period: Seconds to wait after setting ``shutdown_event`` + before force-cancelling the generator. Must be >= 0. Should be less + than your ASGI server's graceful shutdown timeout. Default: 0 + (immediate cancel, identical to pre-v3.3.0 behavior). + """ + + DEFAULT_PING_INTERVAL = 15 + DEFAULT_SEPARATOR = "\r\n" + + def __init__( + self, + content: ContentStream, + status_code: int = 200, + headers: Optional[Mapping[str, str]] = None, + media_type: str = "text/event-stream", + background: Optional[BackgroundTask] = None, + ping: Optional[Union[int, float]] = None, + sep: Optional[str] = None, + ping_message_factory: Optional[Callable[[], ServerSentEvent]] = None, + data_sender_callable: Optional[ + Callable[[], Coroutine[None, None, None]] + ] = None, + send_timeout: Optional[float] = None, + client_close_handler_callable: Optional[ + Callable[[Message], Awaitable[None]] + ] = None, + shutdown_event: Optional[anyio.Event] = None, + shutdown_grace_period: float = 0, + ) -> None: + # Validate separator + if sep not in (None, "\r\n", "\r", "\n"): + raise ValueError(f"sep must be one of: \\r\\n, \\r, \\n, got: {sep}") + self.sep = sep or self.DEFAULT_SEPARATOR + + # If content is sync, wrap it for async iteration + if isinstance(content, AsyncIterable): + self.body_iterator = content + else: + self.body_iterator = iterate_in_threadpool(content) + + self.status_code = status_code + self.media_type = self.media_type if media_type is None else media_type + self.background = background + self.data_sender_callable = data_sender_callable + self.send_timeout = send_timeout + + # Build SSE-specific headers. + _headers = MutableHeaders() + if headers is not None: # pragma: no cover + _headers.update(headers) + + # "The no-store response directive indicates that any caches of any kind (private or shared) + # should not store this response." + # -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + # allow cache control header to be set by user to support fan out proxies + # https://www.fastly.com/blog/server-sent-events-fastly + + _headers.setdefault("Cache-Control", "no-store") + # mandatory for servers-sent events headers + _headers["Connection"] = "keep-alive" + _headers["X-Accel-Buffering"] = "no" + self.init_headers(_headers) + + self.ping_interval = self.DEFAULT_PING_INTERVAL if ping is None else ping + self.ping_message_factory = ping_message_factory + + self.client_close_handler_callable = client_close_handler_callable + + # Cooperative shutdown (Issue #167): Allow generators to send farewell + # events before force-cancellation. The grace period should be less than + # your ASGI server's graceful shutdown timeout (e.g. uvicorn's + # --timeout-graceful-shutdown), otherwise the process is killed before + # the grace period expires. + if shutdown_grace_period < 0: + raise ValueError("shutdown_grace_period must be >= 0") + self._shutdown_event = shutdown_event + self._shutdown_grace_period = shutdown_grace_period + + self.active = True + # https://github.com/sysid/sse-starlette/pull/55#issuecomment-1732374113 + self._send_lock = anyio.Lock() + + @property + def ping_interval(self) -> Union[int, float]: + return self._ping_interval + + @ping_interval.setter + def ping_interval(self, value: Union[int, float]) -> None: + if not isinstance(value, (int, float)): + raise TypeError("ping interval must be int or float") + if value < 0: + raise ValueError("ping interval must be >= 0 (0 disables ping)") + self._ping_interval = value + + def enable_compression(self, force: bool = False) -> None: + raise NotImplementedError("Compression is not supported for SSE streams.") + + async def _send_with_timeout(self, send: Send, message: Message) -> None: + """Send one ASGI message, applying the response's per-send timeout.""" + with anyio.move_on_after(self.send_timeout) as cancel_scope: + await send(message) + + if cancel_scope and cancel_scope.cancel_called: + raise SendTimeoutError() + + async def _stream_response(self, send: Send) -> None: + """Send out SSE data to the client as it becomes available in the iterator.""" + await send( + { + "type": "http.response.start", + "status": self.status_code, + "headers": self.raw_headers, + } + ) + + async for data in self.body_iterator: + chunk = ensure_bytes(data, self.sep) + logger.debug("chunk: %s", chunk) + try: + await self._send_with_timeout( + send, + {"type": "http.response.body", "body": chunk, "more_body": True}, + ) + except SendTimeoutError: + aclose = getattr(self.body_iterator, "aclose", None) + if aclose is not None: + await aclose() + raise + + async with self._send_lock: + self.active = False + await send({"type": "http.response.body", "body": b"", "more_body": False}) + + async def _listen_for_disconnect(self, receive: Receive) -> None: + """Watch for a disconnect message from the client. + + Divergence #1 (see module docstring): kept unconditionally instead of + adopting Starlette's ASGI 2.4 ``OSError → ClientDisconnect`` fast path, + because this loop drives ``client_close_handler_callable`` and flips + ``self.active = False`` for ``_ping`` and the shutdown grace loop. + """ + while self.active: + message = await receive() + if message["type"] == "http.disconnect": + self.active = False + logger.debug("Got event: http.disconnect. Stop streaming.") + if self.client_close_handler_callable: + await self.client_close_handler_callable(message) + break + + @staticmethod + async def _listen_for_exit_signal() -> None: + """Wait for shutdown signal via the shared watcher.""" + if AppStatus.should_exit: + return + + _ensure_watcher_started_on_this_loop() + + state = _get_shutdown_state() + event = anyio.Event() + state.events.add(event) + + try: + # Double-check after registration + if AppStatus.should_exit: + return + await event.wait() + finally: + state.events.discard(event) + + async def _listen_for_exit_signal_with_grace(self) -> None: + """Wait for shutdown signal, then optionally give generator a grace period. + + Issue #167: When a shutdown_event is provided, the library sets it before + returning, giving the generator a chance to send farewell events and exit + cooperatively. The shutdown_grace_period controls how long to wait before + force-cancelling via task group cancellation. + """ + await self._listen_for_exit_signal() + + # Signal the user's generator that shutdown is happening + if self._shutdown_event: + self._shutdown_event.set() + + # Grace period: let generator finish naturally before force-cancel + if self._shutdown_grace_period > 0: + with anyio.move_on_after(self._shutdown_grace_period): + while self.active: + await anyio.sleep(0.1) + + async def _ping(self, send: Send) -> None: + """Periodically send ping messages to keep the connection alive on proxies. + - frequency ca every 15 seconds. + - Alternatively one can send periodically a comment line (one starting with a ':' character) + + Invariant (Issue #206): must NOT be started when ``ping_interval == 0`` + (ping disabled), because ``anyio.sleep(0)`` is a bare checkpoint and the + loop would busy-spin, flooding the client with pings and contending for + ``_send_lock`` with the data stream. ``__call__`` skips the task instead + of returning early here, since ``_ping`` runs under ``cancel_on_finish`` + and any return would tear down the whole response. + """ + while self.active: + await anyio.sleep(self._ping_interval) + sse_ping = ( + self.ping_message_factory() + if self.ping_message_factory + else ServerSentEvent( + comment=f"ping - {datetime.now(timezone.utc)}", sep=self.sep + ) + ) + ping_bytes = ensure_bytes(sse_ping, self.sep) + logger.debug("ping: %s", ping_bytes) + + async with self._send_lock: + if self.active: + # A SendTimeoutError raised here deliberately does NOT + # aclose() the body iterator, unlike _stream_response. When a + # heartbeat times out, _stream_response is normally suspended + # inside body_iterator.__anext__(), where ag_running_async is + # set, so aclose() would raise "RuntimeError: aclose(): + # asynchronous generator is already running" (verified). + # Propagating instead lets the task group cancel + # _stream_response, which throws into the suspended + # __anext__ and runs the generator's finally block -- see + # test_ping_whenSendTimesOut_thenTearsDownResponseAndRunsCleanup. + await self._send_with_timeout( + send, + { + "type": "http.response.body", + "body": ping_bytes, + "more_body": True, + }, + ) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """Entrypoint for Starlette's ASGI contract. We spin up tasks: + - _stream_response to push events + - _ping to keep the connection alive + - _listen_for_exit_signal to respond to server shutdown + - _listen_for_disconnect to respond to client disconnect + """ + # WebSocket denial parity with Starlette's StreamingResponse: a + # streaming response on a websocket scope must wrap send so message + # types become ``websocket.http.response.*``. + if scope["type"] == "websocket": + send = _wrap_websocket_denial_send(send) + + # collapse_excgroups parity with Starlette's StreamingResponse: anyio + # v4 wraps task-group failures in ExceptionGroup; user middleware + # expects the bare exception. + with collapse_excgroups(): + async with anyio.create_task_group() as task_group: + # https://trio.readthedocs.io/en/latest/reference-core.html#custom-supervisors + async def cancel_on_finish(coro: Callable[[], Awaitable[None]]): + await coro() + task_group.cancel_scope.cancel() + + task_group.start_soon( + cancel_on_finish, lambda: self._stream_response(send) + ) + # ping_interval == 0 disables keep-alive pings entirely (#206) + if self._ping_interval > 0: + task_group.start_soon(cancel_on_finish, lambda: self._ping(send)) + task_group.start_soon( + cancel_on_finish, self._listen_for_exit_signal_with_grace + ) + + if self.data_sender_callable: + task_group.start_soon(self.data_sender_callable) + + # Wait for the client to disconnect last + task_group.start_soon( + cancel_on_finish, lambda: self._listen_for_disconnect(receive) + ) + + if self.background is not None: + await self.background() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA new file mode 100644 index 0000000..714e2cf --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA @@ -0,0 +1,182 @@ +Metadata-Version: 2.5 +Name: starlette +Version: 1.7.0 +Summary: The little ASGI library that shines. +Project-URL: Homepage, https://github.com/Kludex/starlette +Project-URL: Documentation, https://starlette.dev/ +Project-URL: Changelog, https://starlette.dev/release-notes/ +Project-URL: Funding, https://github.com/sponsors/Kludex +Project-URL: Source, https://github.com/Kludex/starlette +Author-email: Tom Christie +Maintainer-email: Marcelo Trylesinski +License-Expression: BSD-3-Clause +License-File: LICENSE.md +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Web Environment +Classifier: Framework :: AnyIO +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.10 +Requires-Dist: anyio<5,>=4.0.0 +Requires-Dist: typing-extensions>=4.10.0; python_version < '3.13' +Provides-Extra: full +Requires-Dist: httpx2>=2.0.0; extra == 'full' +Requires-Dist: httpx<0.29.0,>=0.27.0; extra == 'full' +Requires-Dist: itsdangerous; extra == 'full' +Requires-Dist: jinja2; extra == 'full' +Requires-Dist: opentelemetry-api; extra == 'full' +Requires-Dist: python-multipart>=0.0.18; extra == 'full' +Requires-Dist: pyyaml; extra == 'full' +Description-Content-Type: text/markdown + +

+ + + + starlette-logo + +

+ +

+ ✨ The little ASGI framework that shines. ✨ +

+ +--- + +[![Build Status](https://github.com/Kludex/starlette/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/starlette/actions) +[![Package version](https://badge.fury.io/py/starlette.svg)](https://pypi.python.org/pypi/starlette) +[![Supported Python Version](https://img.shields.io/pypi/pyversions/starlette.svg?color=%2334D058)](https://pypi.org/project/starlette) +[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs) + +--- + +**Documentation**:
https://starlette.dev + +**Source Code**: https://github.com/Kludex/starlette + +--- + +# Starlette + +Starlette is a lightweight [ASGI][asgi] framework/toolkit, +which is ideal for building async web services in Python. + +It is production-ready, and gives you the following: + +* A lightweight, low-complexity HTTP web framework. +* WebSocket support. +* In-process background tasks. +* Startup and shutdown events. +* Test client built on `httpx2`. +* CORS, GZip, Static Files, Streaming responses. +* Session and Cookie support. +* 100% test coverage. +* 100% type annotated codebase. +* Few hard dependencies. +* Compatible with `asyncio` and `trio` backends. +* Great overall performance [against independent benchmarks][techempower]. + +## Installation + +```shell +$ pip install starlette +``` + +You'll also want to install an ASGI server, such as [uvicorn](https://uvicorn.dev) or any of the [other ASGI server implementations](https://asgi.readthedocs.io/en/latest/implementations.html#servers). + +```shell +$ pip install uvicorn +``` + +## Example + +```python title="main.py" +from starlette.applications import Starlette +from starlette.responses import JSONResponse +from starlette.routing import Route + + +async def homepage(request): + return JSONResponse({'hello': 'world'}) + +routes = [ + Route("/", endpoint=homepage) +] + +app = Starlette(debug=True, routes=routes) +``` + +Then run the application using Uvicorn: + +```shell +$ uvicorn main:app +``` + +## Dependencies + +Starlette only requires `anyio`, and the following are optional: + +* [`httpx2`][httpx2] - Required if you want to use the `TestClient`. +* [`jinja2`][jinja2] - Required if you want to use `Jinja2Templates`. +* [`opentelemetry-api`][opentelemetry-api] - Required for `OpenTelemetryMiddleware`. +* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`. +* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support. +* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support. + +You can install all of these with `pip install starlette[full]`. + +## Framework or Toolkit + +Starlette is designed to be used either as a complete framework, or as +an ASGI toolkit. You can use any of its components independently. + +```python +from starlette.responses import PlainTextResponse + + +async def app(scope, receive, send): + assert scope['type'] == 'http' + response = PlainTextResponse('Hello, world!') + await response(scope, receive, send) +``` + +Run the `app` application in `example.py`: + +```shell +$ uvicorn example:app +INFO: Started server process [11509] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Run uvicorn with `--reload` to enable auto-reloading on code changes. + +## Modularity + +The modularity that Starlette is designed on promotes building reusable +components that can be shared between any ASGI framework. This should enable +an ecosystem of shared middleware and mountable applications. + +The clean API separation also means it's easier to understand each component +in isolation. + +--- + +

Starlette is BSD licensed code.
Designed & crafted with care.

— ⭐️ —

+ +[asgi]: https://asgi.readthedocs.io/en/latest/ +[httpx2]: https://pypi.org/project/httpx2/ +[jinja2]: https://jinja.palletsprojects.com/ +[opentelemetry-api]: https://opentelemetry.io/docs/languages/python/api/ +[python-multipart]: https://multipart.fastapiexpert.com/ +[itsdangerous]: https://itsdangerous.palletsprojects.com/ +[sqlalchemy]: https://www.sqlalchemy.org +[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation +[techempower]: https://github.com/TechEmpower/FrameworkBenchmarks diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD new file mode 100644 index 0000000..06c5375 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD @@ -0,0 +1,41 @@ +starlette/__init__.py,sha256=lEEx5lwinUby-YNl7w-VFGUcjcibsJ9EuzgJ2W7ocrU,22 +starlette/_exception_handler.py,sha256=8Vm915fWYe9xLo_za5iQ5WyrAvFBu2kEEJc7XreTyOk,2257 +starlette/_utils.py,sha256=NTNRqxu24V4GwVq5gnhQ0oFBlRnrjbywbKahzeiwrw0,4211 +starlette/applications.py,sha256=gYh-C6992lgRukt6Z-Scu6t66XsyG6cuR1x5f5iWkOk,5763 +starlette/authentication.py,sha256=rJx0wvk1xsVifLsyiHV6P_b3oAherBRXW6QkXKw3APM,5341 +starlette/background.py,sha256=D-XpUpFRgUBnZeA1tHgzMOgube6ICVZFlOA4ML0q1T8,1122 +starlette/concurrency.py,sha256=Pp4kXQYO85cWxY7XVNt5lcYvhYHUquyLguCec7hKKoM,1701 +starlette/config.py,sha256=GVwko0pGSusxuQHO35tL_Nt_ApL0fmVYL3IiHHYpLW4,4426 +starlette/convertors.py,sha256=F1rse3AacN9rsfJnTeuDnjbN51r_ouHc3WLyYkjkX_o,2304 +starlette/datastructures.py,sha256=NdxfT1u9a-NIoFGoz257evM14p1sq__46iMp9Xo_-60,23200 +starlette/endpoints.py,sha256=KI-tMTdlqi4rU1eR9hnOptdyGFxc_T1adwOKIgeOBm0,5304 +starlette/exceptions.py,sha256=-0sNmNNLa46g9V6xpsqOqqPwDRJMoe4vEqDAbCVQJTU,1492 +starlette/formparsers.py,sha256=rdPatCDCwT8yj1mDeFOAHknV7O4x3wU0cCZs4GbVaOI,12374 +starlette/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +starlette/requests.py,sha256=hpGwGuSez6CF_mQZNDZ49zt3yS_GOkERBqd9jpvwrEQ,12708 +starlette/responses.py,sha256=5Ypb6Ud-MLloC7Jw4PGw_G4lKuG6ZXZecNOUZzLTJ_c,22090 +starlette/routing.py,sha256=FSFciPQYfJjPSY51exiMDgb76A2V94t2MMTlH5JhGvQ,30472 +starlette/schemas.py,sha256=e9KnCHN1KBV9F8Ml54wC55ItHE5LES5WWXLVEYUgot0,5494 +starlette/staticfiles.py,sha256=UP3kCtjzAs1ZsdluuP7NLEq55ER_FI65LMfKVbrsMBg,9071 +starlette/status.py,sha256=XYcVn-aU522TYJ8BJxl0WUnf6J_3yBTQz7Tqgye8KO4,6430 +starlette/templating.py,sha256=y-ATlQrNDoLMZs5hrHzQBTapECBDr9wAl-bs8Blk5jw,5447 +starlette/testclient.py,sha256=noWneuuRPAkTsqnel3rmF1VOzQimReyJvKLggJ__dhs,21597 +starlette/types.py,sha256=87wTuwF1036QxRse3mL5qOuS9i4a9xaIZMqTjY-lvvo,1041 +starlette/websockets.py,sha256=mflI9LUwlf93_uujI8w-tY-noS-zdREbzaUmeOVDP9c,8518 +starlette/middleware/__init__.py,sha256=BORnOj0n0mL1OWUUs1PLTu1SASmvT8kEEDA6cbuhuPo,1349 +starlette/middleware/authentication.py,sha256=-RSwwJ0pIkrM0v-2Ip8HXVB24xtSp7ObzR3OfTC8wSA,1800 +starlette/middleware/base.py,sha256=5Od_lmyCvfI0jsEDWiQTiHy9DEbAb3tKAPedJ24tils,10500 +starlette/middleware/body_limit.py,sha256=L12EvFC-9ryO_A_2ngdDpt6WaVkwhAtqiuJPhyhqyA8,4609 +starlette/middleware/cors.py,sha256=_OnWebUt2WcPOCFtqs3KsVnGmVzbUlbuUWLcWNJaXE8,7688 +starlette/middleware/errors.py,sha256=E14AZhjybcfRH72ByiaQ7Mdt0AwnYZqiv6zGtC0F0xM,8126 +starlette/middleware/exceptions.py,sha256=7OgSUiBgwHS4VMmpaWlw21uDKNDmOMzLVZrWDLDUqWo,2784 +starlette/middleware/gzip.py,sha256=7ijfBszT4h6syblkcd89ple43WqYBgH-hCycsKuWtms,8805 +starlette/middleware/httpsredirect.py,sha256=fogRKypMx3gKzoHgJpKPIITGdKxFdUCEAqskRHndZBQ,1023 +starlette/middleware/opentelemetry.py,sha256=lAP3BYvOjunvnPBK9GcQaZR7MF6wVmYLbpc75-54hwI,5719 +starlette/middleware/sessions.py,sha256=8YpoNTO02-KWaUvtJdNe7lKExKxYuZD-hIRC3TdtBf4,5175 +starlette/middleware/trustedhost.py,sha256=WkeCKP_loVg5IBpjjMyoWXEnS0URQmon8Iee-dTz46U,2456 +starlette/middleware/wsgi.py,sha256=8_xjdUNjA-DcWY0owaqyiZlGcvml4UMIB18rGxtdNvg,5536 +starlette-1.7.0.dist-info/METADATA,sha256=94cPwZq3wuNkiHBXNAuftH6b2nTSSwbFjKOZSUq_4Bk,6610 +starlette-1.7.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87 +starlette-1.7.0.dist-info/licenses/LICENSE.md,sha256=3LlWd6AiQCQxh-lk-UGEfRmxeCHPmeWvrmhPqzKMGb8,1518 +starlette-1.7.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL new file mode 100644 index 0000000..51abe1f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.32.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..d16a60e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2018, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py new file mode 100644 index 0000000..14d9d2f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/__init__.py @@ -0,0 +1 @@ +__version__ = "1.7.0" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py new file mode 100644 index 0000000..9491f8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import Any + +from starlette._utils import is_async_callable +from starlette.concurrency import run_in_threadpool +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send +from starlette.websockets import WebSocket + +ExceptionHandlers = dict[Any, ExceptionHandler] +StatusHandlers = dict[int, ExceptionHandler] + + +def _lookup_exception_handler(exc_handlers: ExceptionHandlers, exc: Exception) -> ExceptionHandler | None: + for cls in type(exc).__mro__: + if cls in exc_handlers: + return exc_handlers[cls] + return None + + +def wrap_app_handling_exceptions(app: ASGIApp, conn: Request | WebSocket) -> ASGIApp: + exception_handlers: ExceptionHandlers + status_handlers: StatusHandlers + try: + exception_handlers, status_handlers = conn.scope["starlette.exception_handlers"] + except KeyError: + exception_handlers, status_handlers = {}, {} + + async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None: + response_started = False + + async def sender(message: Message) -> None: + nonlocal response_started + + if message["type"] == "http.response.start": + response_started = True + await send(message) + + try: + await app(scope, receive, sender) + except Exception as exc: + handler = None + + if isinstance(exc, HTTPException): + handler = status_handlers.get(exc.status_code) + + if handler is None: + handler = _lookup_exception_handler(exception_handlers, exc) + + if handler is None: + raise exc + + if response_started: + raise RuntimeError("Caught handled exception, but response already started.") from exc + + if is_async_callable(handler): + response = await handler(conn, exc) # type: ignore[arg-type] + else: + response = await run_in_threadpool(handler, conn, exc) # type: ignore[arg-type] + if response is not None: + await response(scope, receive, sender) + + return wrapped_app diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py new file mode 100644 index 0000000..21bc95a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/_utils.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +import functools +import re +import sys +from collections.abc import AsyncGenerator, Awaitable, Callable, Generator +from contextlib import AbstractAsyncContextManager, asynccontextmanager +from dataclasses import dataclass +from ipaddress import AddressValueError, IPv6Address +from typing import Any, Generic, Protocol, TypeVar, overload + +import anyio.abc + +from starlette.types import Scope + +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction + from typing import TypeIs +else: # pragma: no cover + from asyncio import iscoroutinefunction + + from typing_extensions import TypeIs + +if sys.version_info < (3, 11): # pragma: no cover + from exceptiongroup import BaseExceptionGroup + + +T = TypeVar("T") +AwaitableCallable = Callable[..., Awaitable[T]] + +# Reject characters that could make a Host header change the URL path or authority. +_HOST_RE = re.compile( + r"^(?P[a-z0-9._~%!$&'()*+,;=-]+|\[(?:(?P[a-f0-9]*:[a-f0-9.:]+)|" + r"(?-i:v)[a-f0-9]+\.[a-z0-9._~!$&'()*+,;=:-]+)\])(?::(?P[0-9]+))?$", + re.IGNORECASE, +) + + +@dataclass(frozen=True, slots=True) +class ParsedHost: + host: str + port: str | None + + @property + def authority(self) -> str: + return self.host if self.port is None else f"{self.host}:{self.port}" + + @property + def is_valid_port(self) -> bool: + if self.port is None: + return True + port = self.port.lstrip("0") + return len(port) <= 5 and int(port or "0") <= 65535 + + +@overload +def is_async_callable(obj: AwaitableCallable[T]) -> TypeIs[AwaitableCallable[T]]: ... + + +@overload +def is_async_callable(obj: Any) -> TypeIs[AwaitableCallable[Any]]: ... + + +def is_async_callable(obj: Any) -> Any: + while isinstance(obj, functools.partial): + obj = obj.func + + return iscoroutinefunction(obj) or (callable(obj) and iscoroutinefunction(obj.__call__)) + + +T_co = TypeVar("T_co", covariant=True) + + +class AwaitableOrContextManager( + Awaitable[T_co], AbstractAsyncContextManager[T_co], Protocol[T_co] +): ... # pragma: no branch + + +class SupportsAsyncClose(Protocol): + async def close(self) -> None: ... # pragma: no cover + + +SupportsAsyncCloseType = TypeVar("SupportsAsyncCloseType", bound=SupportsAsyncClose, covariant=False) + + +class AwaitableOrContextManagerWrapper(Generic[SupportsAsyncCloseType]): + __slots__ = ("aw", "entered") + + def __init__(self, aw: Awaitable[SupportsAsyncCloseType]) -> None: + self.aw = aw + + def __await__(self) -> Generator[Any, None, SupportsAsyncCloseType]: + return self.aw.__await__() + + async def __aenter__(self) -> SupportsAsyncCloseType: + self.entered = await self.aw + return self.entered + + async def __aexit__(self, *args: Any) -> None | bool: + await self.entered.close() + return None + + +@asynccontextmanager +async def create_collapsing_task_group() -> AsyncGenerator[anyio.abc.TaskGroup, None]: + try: + async with anyio.create_task_group() as tg: + yield tg + except BaseExceptionGroup as excs: + if len(excs.exceptions) != 1: + raise + + exc = excs.exceptions[0] + context = None if exc.__suppress_context__ else exc.__context__ + raise exc from exc.__cause__ or context + + +def parse_host_header(host_header: str | None) -> ParsedHost | None: + """Parse `host_header` into its host and port components. + + The host preserves brackets around IP literals. Invalid headers produce `None`. + """ + if host_header is None: + return None + + match = _HOST_RE.fullmatch(host_header) + if match is None: + return None + + ipv6 = match["ipv6"] + if ipv6 is not None: + try: + IPv6Address(ipv6) + except AddressValueError: + return None + + return ParsedHost(match["host"], match["port"]) + + +def get_route_path(scope: Scope) -> str: + path: str = scope["path"] + root_path = scope.get("root_path", "") + if not root_path: + return path + + if not path.startswith(root_path): + return path + + if path == root_path: + return "" + + if path[len(root_path)] == "/": + return path[len(root_path) :] + + return path diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py new file mode 100644 index 0000000..a28cde4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/applications.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Mapping, Sequence +from typing import Any, ParamSpec, TypeVar + +from starlette.datastructures import State, URLPath +from starlette.middleware import Middleware, _MiddlewareFactory +from starlette.middleware.body_limit import RequestBodyLimitMiddleware +from starlette.middleware.errors import ServerErrorMiddleware +from starlette.middleware.exceptions import ExceptionMiddleware +from starlette.requests import Request +from starlette.responses import Response +from starlette.routing import BaseRoute, Router +from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send + +AppType = TypeVar("AppType", bound="Starlette") +P = ParamSpec("P") + + +class Starlette: + """Creates a Starlette application.""" + + def __init__( + self: AppType, + debug: bool = False, + routes: Sequence[BaseRoute] | None = None, + middleware: Sequence[Middleware] | None = None, + exception_handlers: Mapping[Any, ExceptionHandler] | None = None, + lifespan: Lifespan[AppType] | None = None, + *, + max_body_size: int | None = None, + ) -> None: + """Initializes the application. + + Parameters: + debug: Boolean indicating if debug tracebacks should be returned on errors. + routes: A list of routes to serve incoming HTTP and WebSocket requests. + middleware: A list of middleware to run for every request. A starlette + application will always automatically include two middleware classes. + `ServerErrorMiddleware` is added as the very outermost middleware, to handle + any uncaught errors occurring anywhere in the entire stack. + `ExceptionMiddleware` is added as the very innermost middleware, to deal + with handled exception cases occurring in the routing or endpoints. + exception_handlers: A mapping of either integer status codes, + or exception class types onto callables which handle the exceptions. + Exception handler callables should be of the form + `handler(request, exc) -> response` and may be either standard functions, or + async functions. + lifespan: A lifespan context function, which can be used to perform + startup and shutdown tasks. This is a newer style that replaces the + `on_startup` and `on_shutdown` handlers. Use one or the other, not both. + max_body_size: Non-negative maximum total size in bytes of an HTTP request + body. The default, `None`, does not limit request body size. + """ + self.debug = debug + self.state = State() + self.router = Router(routes, lifespan=lifespan) + self.max_body_size = max_body_size + self.exception_handlers = {} if exception_handlers is None else dict(exception_handlers) + self.user_middleware = [] if middleware is None else list(middleware) + self.middleware_stack: ASGIApp | None = None + + def build_middleware_stack(self) -> ASGIApp: + debug = self.debug + error_handler = None + exception_handlers: dict[Any, ExceptionHandler] = {} + + for key, value in self.exception_handlers.items(): + if key in (500, Exception): + error_handler = value + else: + exception_handlers[key] = value + + middleware = [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] + if self.max_body_size is not None: + middleware.append(Middleware(RequestBodyLimitMiddleware, max_body_size=self.max_body_size)) + middleware += self.user_middleware + middleware.append(Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)) + + app = self.router + for cls, args, kwargs in reversed(middleware): + app = cls(app, *args, **kwargs) + return app + + @property + def routes(self) -> list[BaseRoute]: + return self.router.routes + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + return self.router.url_path_for(name, **path_params) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + scope["app"] = self + if self.middleware_stack is None: + self.middleware_stack = self.build_middleware_stack() + await self.middleware_stack(scope, receive, send) + + def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None: + self.router.mount(path, app=app, name=name) # pragma: no cover + + def host(self, host: str, app: ASGIApp, name: str | None = None) -> None: + self.router.host(host, app=app, name=name) # pragma: no cover + + def add_middleware(self, middleware_class: _MiddlewareFactory[P], *args: P.args, **kwargs: P.kwargs) -> None: + if self.middleware_stack is not None: # pragma: no cover + raise RuntimeError("Cannot add middleware after an application has started") + self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs)) + + def add_exception_handler( + self, + exc_class_or_status_code: int | type[Exception], + handler: ExceptionHandler, + ) -> None: # pragma: no cover + self.exception_handlers[exc_class_or_status_code] = handler + + def add_route( + self, + path: str, + route: Callable[[Request], Awaitable[Response] | Response], + methods: list[str] | None = None, + name: str | None = None, + include_in_schema: bool = True, + ) -> None: # pragma: no cover + self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py new file mode 100644 index 0000000..317ef01 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/authentication.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +import functools +import inspect +from collections.abc import Callable, Sequence +from typing import Any, ParamSpec +from urllib.parse import urlencode + +from starlette._utils import is_async_callable +from starlette.exceptions import HTTPException +from starlette.requests import HTTPConnection, Request +from starlette.responses import RedirectResponse +from starlette.websockets import WebSocket + +_P = ParamSpec("_P") + + +def has_required_scope(conn: HTTPConnection, scopes: Sequence[str]) -> bool: + for scope in scopes: + if scope not in conn.auth.scopes: + return False + return True + + +def requires( + scopes: str | Sequence[str], + status_code: int = 403, + redirect: str | None = None, +) -> Callable[[Callable[_P, Any]], Callable[_P, Any]]: + scopes_list = [scopes] if isinstance(scopes, str) else list(scopes) + + def decorator( + func: Callable[_P, Any], + ) -> Callable[_P, Any]: + sig = inspect.signature(func) + for idx, parameter in enumerate(sig.parameters.values()): + if parameter.name == "request" or parameter.name == "websocket": + type_ = parameter.name + break + else: + raise Exception(f'No "request" or "websocket" argument on function "{func}"') + + if type_ == "websocket": + # Handle websocket functions. (Always async) + @functools.wraps(func) + async def websocket_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None: + websocket = kwargs.get("websocket", args[idx] if idx < len(args) else None) + assert isinstance(websocket, WebSocket), ( + "Parameter with name 'websocket' is required to be of type 'WebSocket'" + f" not '{type(websocket).__name__}'" + ) + + if not has_required_scope(websocket, scopes_list): + await websocket.close() + else: + await func(*args, **kwargs) + + return websocket_wrapper + + elif is_async_callable(func): + # Handle async request/response functions. + @functools.wraps(func) + async def async_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Any: + request = kwargs.get("request", args[idx] if idx < len(args) else None) + assert isinstance(request, Request), ( + f"Parameter with name 'request' is required to be of type 'Request' not '{type(request).__name__}'" + ) + + if not has_required_scope(request, scopes_list): + if redirect is not None: + orig_request_qparam = urlencode({"next": str(request.url)}) + next_url = f"{request.url_for(redirect)}?{orig_request_qparam}" + return RedirectResponse(url=next_url, status_code=303) + raise HTTPException(status_code=status_code) + return await func(*args, **kwargs) + + return async_wrapper + + else: + # Handle sync request/response functions. + @functools.wraps(func) + def sync_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Any: + request = kwargs.get("request", args[idx] if idx < len(args) else None) + assert isinstance(request, Request), ( + f"Parameter with name 'request' is required to be of type 'Request' not '{type(request).__name__}'" + ) + + if not has_required_scope(request, scopes_list): + if redirect is not None: + orig_request_qparam = urlencode({"next": str(request.url)}) + next_url = f"{request.url_for(redirect)}?{orig_request_qparam}" + return RedirectResponse(url=next_url, status_code=303) + raise HTTPException(status_code=status_code) + return func(*args, **kwargs) + + return sync_wrapper + + return decorator + + +class AuthenticationError(Exception): + pass + + +class AuthenticationBackend: + async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, BaseUser] | None: + raise NotImplementedError() # pragma: no cover + + +class AuthCredentials: + def __init__(self, scopes: Sequence[str] | None = None): + self.scopes = [] if scopes is None else list(scopes) + + +class BaseUser: + @property + def is_authenticated(self) -> bool: + raise NotImplementedError() # pragma: no cover + + @property + def display_name(self) -> str: + raise NotImplementedError() # pragma: no cover + + @property + def identity(self) -> str: + raise NotImplementedError() # pragma: no cover + + +class SimpleUser(BaseUser): + def __init__(self, username: str) -> None: + self.username = username + + @property + def is_authenticated(self) -> bool: + return True + + @property + def display_name(self) -> str: + return self.username + + @property + def identity(self) -> str: + return self.username + + +class UnauthenticatedUser(BaseUser): + @property + def is_authenticated(self) -> bool: + return False + + @property + def display_name(self) -> str: + return "" + + @property + def identity(self) -> str: + return "" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py new file mode 100644 index 0000000..55135ee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/background.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from collections.abc import Callable, Sequence +from typing import Any, ParamSpec + +from starlette._utils import is_async_callable +from starlette.concurrency import run_in_threadpool + +P = ParamSpec("P") + + +class BackgroundTask: + def __init__(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None: + self.func = func + self.args = args + self.kwargs = kwargs + self.is_async = is_async_callable(func) + + async def __call__(self) -> None: + if self.is_async: + await self.func(*self.args, **self.kwargs) + else: + await run_in_threadpool(self.func, *self.args, **self.kwargs) + + +class BackgroundTasks(BackgroundTask): + def __init__(self, tasks: Sequence[BackgroundTask] | None = None): + self.tasks = list(tasks) if tasks else [] + + def add_task(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None: + task = BackgroundTask(func, *args, **kwargs) + self.tasks.append(task) + + async def __call__(self) -> None: + for task in self.tasks: + await task() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py new file mode 100644 index 0000000..a2c55be --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import functools +import warnings +from collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator +from typing import ParamSpec, TypeVar + +import anyio.to_thread + +from starlette.exceptions import StarletteDeprecationWarning + +P = ParamSpec("P") +T = TypeVar("T") + + +async def run_until_first_complete(*args: tuple[Callable, dict]) -> None: # type: ignore[type-arg] + warnings.warn( + "run_until_first_complete is deprecated and will be removed in a future version.", + StarletteDeprecationWarning, + ) + + async with anyio.create_task_group() as task_group: + + async def run(func: Callable[[], Coroutine]) -> None: # type: ignore[type-arg] + await func() + task_group.cancel_scope.cancel() + + for func, kwargs in args: + task_group.start_soon(run, functools.partial(func, **kwargs)) + + +async def run_in_threadpool(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: + func = functools.partial(func, *args, **kwargs) + return await anyio.to_thread.run_sync(func) + + +class _StopIteration(Exception): + pass + + +def _next(iterator: Iterator[T]) -> T: + # We can't raise `StopIteration` from within the threadpool iterator + # and catch it outside that context, so we coerce them into a different + # exception type. + try: + return next(iterator) + except StopIteration: + raise _StopIteration + + +async def iterate_in_threadpool( + iterator: Iterable[T], +) -> AsyncIterator[T]: + as_iterator = iter(iterator) + while True: + try: + yield await anyio.to_thread.run_sync(_next, as_iterator) + except _StopIteration: + break diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py new file mode 100644 index 0000000..56c25ca --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/config.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import os +import warnings +from collections.abc import Callable, Iterator, Mapping, MutableMapping +from pathlib import Path +from typing import Any, TypeVar, overload + + +class undefined: + pass + + +class EnvironError(Exception): + pass + + +class Environ(MutableMapping[str, str]): + def __init__(self, environ: MutableMapping[str, str] = os.environ): + self._environ = environ + self._has_been_read: set[str] = set() + + def __getitem__(self, key: str) -> str: + self._has_been_read.add(key) + return self._environ.__getitem__(key) + + def __setitem__(self, key: str, value: str) -> None: + if key in self._has_been_read: + raise EnvironError(f"Attempting to set environ['{key}'], but the value has already been read.") + self._environ.__setitem__(key, value) + + def __delitem__(self, key: str) -> None: + if key in self._has_been_read: + raise EnvironError(f"Attempting to delete environ['{key}'], but the value has already been read.") + self._environ.__delitem__(key) + + def __iter__(self) -> Iterator[str]: + return iter(self._environ) + + def __len__(self) -> int: + return len(self._environ) + + +environ = Environ() + +T = TypeVar("T") + + +class Config: + def __init__( + self, + env_file: str | Path | None = None, + environ: Mapping[str, str] = environ, + env_prefix: str = "", + encoding: str = "utf-8", + ) -> None: + self.environ = environ + self.env_prefix = env_prefix + self.file_values: dict[str, str] = {} + if env_file is not None: + if not os.path.isfile(env_file): + warnings.warn(f"Config file '{env_file}' not found.") + else: + self.file_values = self._read_file(env_file, encoding) + + @overload + def __call__(self, key: str, *, default: None) -> str | None: ... + + @overload + def __call__(self, key: str, cast: type[T], default: T = ...) -> T: ... + + @overload + def __call__(self, key: str, cast: type[str] = ..., default: str = ...) -> str: ... + + @overload + def __call__( + self, + key: str, + cast: Callable[[Any], T] = ..., + default: Any = ..., + ) -> T: ... + + @overload + def __call__(self, key: str, cast: type[str] = ..., default: T = ...) -> T | str: ... + + def __call__( + self, + key: str, + cast: Callable[[Any], Any] | None = None, + default: Any = undefined, + ) -> Any: + return self.get(key, cast, default) + + def get( + self, + key: str, + cast: Callable[[Any], Any] | None = None, + default: Any = undefined, + ) -> Any: + key = self.env_prefix + key + if key in self.environ: + value = self.environ[key] + return self._perform_cast(key, value, cast) + if key in self.file_values: + value = self.file_values[key] + return self._perform_cast(key, value, cast) + if default is not undefined: + return self._perform_cast(key, default, cast) + raise KeyError(f"Config '{key}' is missing, and has no default.") + + def _read_file(self, file_name: str | Path, encoding: str) -> dict[str, str]: + file_values: dict[str, str] = {} + with open(file_name, encoding=encoding) as input_file: + for line in input_file.readlines(): + line = line.strip() + if "=" in line and not line.startswith("#"): + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip("\"'") + file_values[key] = value + return file_values + + def _perform_cast( + self, + key: str, + value: Any, + cast: Callable[[Any], Any] | None = None, + ) -> Any: + if cast is None or value is None: + return value + elif cast is bool and isinstance(value, str): + mapping = {"true": True, "1": True, "false": False, "0": False} + value = value.lower() + if value not in mapping: + raise ValueError(f"Config '{key}' has value '{value}'. Not a valid bool.") + return mapping[value] + try: + return cast(value) + except (TypeError, ValueError): + raise ValueError(f"Config '{key}' has value '{value}'. Not a valid {cast.__name__}.") diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py new file mode 100644 index 0000000..72b1cf9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/convertors.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import math +import uuid +from typing import Any, ClassVar, Generic, TypeVar + +T = TypeVar("T") + + +class Convertor(Generic[T]): + regex: ClassVar[str] = "" + + def convert(self, value: str) -> T: + raise NotImplementedError() # pragma: no cover + + def to_string(self, value: T) -> str: + raise NotImplementedError() # pragma: no cover + + +class StringConvertor(Convertor[str]): + regex = "[^/]+" + + def convert(self, value: str) -> str: + return value + + def to_string(self, value: str) -> str: + value = str(value) + assert "/" not in value, "May not contain path separators" + assert value, "Must not be empty" + return value + + +class PathConvertor(Convertor[str]): + regex = ".*" + + def convert(self, value: str) -> str: + return str(value) + + def to_string(self, value: str) -> str: + return str(value) + + +class IntegerConvertor(Convertor[int]): + regex = "[0-9]+" + + def convert(self, value: str) -> int: + return int(value) + + def to_string(self, value: int) -> str: + value = int(value) + assert value >= 0, "Negative integers are not supported" + return str(value) + + +class FloatConvertor(Convertor[float]): + regex = r"[0-9]+(\.[0-9]+)?" + + def convert(self, value: str) -> float: + return float(value) + + def to_string(self, value: float) -> str: + value = float(value) + assert value >= 0.0, "Negative floats are not supported" + assert not math.isnan(value), "NaN values are not supported" + assert not math.isinf(value), "Infinite values are not supported" + return ("%0.20f" % value).rstrip("0").rstrip(".") + + +class UUIDConvertor(Convertor[uuid.UUID]): + regex = "[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}" + + def convert(self, value: str) -> uuid.UUID: + return uuid.UUID(value) + + def to_string(self, value: uuid.UUID) -> str: + return str(value) + + +CONVERTOR_TYPES: dict[str, Convertor[Any]] = { + "str": StringConvertor(), + "path": PathConvertor(), + "int": IntegerConvertor(), + "float": FloatConvertor(), + "uuid": UUIDConvertor(), +} + + +def register_url_convertor(key: str, convertor: Convertor[Any]) -> None: + CONVERTOR_TYPES[key] = convertor diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py new file mode 100644 index 0000000..d4ec95e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py @@ -0,0 +1,704 @@ +from __future__ import annotations + +from collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, MutableMapping, Sequence, ValuesView +from shlex import shlex +from typing import Any, BinaryIO, Literal, NamedTuple, TypeVar, cast +from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit + +from starlette._utils import parse_host_header +from starlette.concurrency import run_in_threadpool +from starlette.types import Scope + + +class Address(NamedTuple): + host: str + port: int + + +_KeyType = TypeVar("_KeyType") +# Mapping keys are invariant but their values are covariant since +# you can only read them +# that is, you can't do `Mapping[str, Animal]()["fido"] = Dog()` +_CovariantValueType = TypeVar("_CovariantValueType", covariant=True) + + +class URL: + def __init__( + self, + url: str = "", + scope: Scope | None = None, + **components: Any, + ) -> None: + if scope is not None: + assert not url, 'Cannot set both "url" and "scope".' + assert not components, 'Cannot set both "scope" and "**components".' + scheme = scope.get("scheme", "http") + server = scope.get("server", None) + path = scope["path"] + query_string = scope.get("query_string", b"") + + host_header = None + for key, value in scope["headers"]: + if key == b"host": + host_header = value.decode("latin-1") + break + + parsed_host = parse_host_header(host_header) + if parsed_host is not None and parsed_host.is_valid_port: + netloc = parsed_host.authority + elif server is not None: + host, port = server + if ":" in host and not host.startswith("["): + host = f"[{host}]" + default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme] + netloc = host if port == default_port else f"{host}:{port}" + else: + netloc = None + + query = query_string.decode() + if netloc is not None: + url = SplitResult(scheme=scheme, netloc=netloc, path=path, query=query, fragment="").geturl() + else: + url = f"{path}?{query}" if query else path + elif components: + assert not url, 'Cannot set both "url" and "**components".' + url = URL("").replace(**components).components.geturl() + + self._url = url + + @property + def components(self) -> SplitResult: + if not hasattr(self, "_components"): + self._components = urlsplit(self._url) + return self._components + + @property + def scheme(self) -> str: + return self.components.scheme + + @property + def netloc(self) -> str: + return self.components.netloc + + @property + def path(self) -> str: + return self.components.path + + @property + def query(self) -> str: + return self.components.query + + @property + def fragment(self) -> str: + return self.components.fragment + + @property + def username(self) -> None | str: + return self.components.username + + @property + def password(self) -> None | str: + return self.components.password + + @property + def hostname(self) -> None | str: + return self.components.hostname + + @property + def port(self) -> int | None: + return self.components.port + + @property + def is_secure(self) -> bool: + return self.scheme in ("https", "wss") + + def replace(self, **kwargs: Any) -> URL: + if "username" in kwargs or "password" in kwargs or "hostname" in kwargs or "port" in kwargs: + hostname = kwargs.pop("hostname", None) + port = kwargs.pop("port", self.port) + username = kwargs.pop("username", self.username) + password = kwargs.pop("password", self.password) + + if hostname is None: + netloc = self.netloc + _, _, hostname = netloc.rpartition("@") + + if hostname and hostname[-1] != "]": + hostname = hostname.rsplit(":", 1)[0] + + netloc = hostname + if port is not None: + netloc += f":{port}" + if username is not None: + userpass = username + if password is not None: + userpass += f":{password}" + netloc = f"{userpass}@{netloc}" + + kwargs["netloc"] = netloc + + components = self.components._replace(**kwargs) + return self.__class__(components.geturl()) + + def include_query_params(self, **kwargs: Any) -> URL: + params = MultiDict(parse_qsl(self.query, keep_blank_values=True)) + params.update({str(key): str(value) for key, value in kwargs.items()}) + query = urlencode(params.multi_items()) + return self.replace(query=query) + + def replace_query_params(self, **kwargs: Any) -> URL: + query = urlencode([(str(key), str(value)) for key, value in kwargs.items()]) + return self.replace(query=query) + + def remove_query_params(self, keys: str | Sequence[str]) -> URL: + if isinstance(keys, str): + keys = [keys] + params = MultiDict(parse_qsl(self.query, keep_blank_values=True)) + for key in keys: + params.pop(key, None) + query = urlencode(params.multi_items()) + return self.replace(query=query) + + def __eq__(self, other: Any) -> bool: + return str(self) == str(other) + + def __str__(self) -> str: + return self._url + + def __repr__(self) -> str: + url = str(self) + if self.password: + url = str(self.replace(password="********")) + return f"{self.__class__.__name__}({repr(url)})" + + +class URLPath(str): + """ + A URL path string that may also hold an associated protocol and/or host. + Used by the routing to return `url_path_for` matches. + """ + + def __new__(cls, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> URLPath: + assert protocol in ("http", "websocket", "") + return str.__new__(cls, path) + + def __init__(self, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> None: + self.protocol = protocol + self.host = host + + def make_absolute_url(self, base_url: str | URL) -> URL: + if isinstance(base_url, str): + base_url = URL(base_url) + if self.protocol: + scheme = { + "http": {True: "https", False: "http"}, + "websocket": {True: "wss", False: "ws"}, + }[self.protocol][base_url.is_secure] + else: + scheme = base_url.scheme + + netloc = self.host or base_url.netloc + path = base_url.path.rstrip("/") + str(self) + return URL(scheme=scheme, netloc=netloc, path=path) + + +class Secret: + """ + Holds a string value that should not be revealed in tracebacks etc. + You should cast the value to `str` at the point it is required. + """ + + def __init__(self, value: str): + self._value = value + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return f"{class_name}('**********')" + + def __str__(self) -> str: + return self._value + + def __bool__(self) -> bool: + return bool(self._value) + + +class CommaSeparatedStrings(Sequence[str]): + def __init__(self, value: str | Sequence[str]): + if isinstance(value, str): + splitter = shlex(value, posix=True) + splitter.whitespace = "," + splitter.whitespace_split = True + self._items = [item.strip() for item in splitter] + else: + self._items = list(value) + + def __len__(self) -> int: + return len(self._items) + + def __getitem__(self, index: int | slice) -> Any: + return self._items[index] + + def __iter__(self) -> Iterator[str]: + return iter(self._items) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + items = [item for item in self] + return f"{class_name}({items!r})" + + def __str__(self) -> str: + return ", ".join(repr(item) for item in self) + + +class ImmutableMultiDict(Mapping[_KeyType, _CovariantValueType]): + _dict: dict[_KeyType, _CovariantValueType] + + def __init__( + self, + *args: ImmutableMultiDict[_KeyType, _CovariantValueType] + | Mapping[_KeyType, _CovariantValueType] + | Iterable[tuple[_KeyType, _CovariantValueType]], + **kwargs: Any, + ) -> None: + assert len(args) < 2, "Too many arguments." + + value: Any = args[0] if args else [] + if kwargs: + value = ImmutableMultiDict(value).multi_items() + ImmutableMultiDict(kwargs).multi_items() + + if not value: + _items: list[tuple[Any, Any]] = [] + elif hasattr(value, "multi_items"): + value = cast(ImmutableMultiDict[_KeyType, _CovariantValueType], value) + _items = list(value.multi_items()) + elif hasattr(value, "items"): + value = cast(Mapping[_KeyType, _CovariantValueType], value) + _items = list(value.items()) + else: + value = cast("list[tuple[Any, Any]]", value) + _items = list(value) + + self._dict = {k: v for k, v in _items} + self._list = _items + + def getlist(self, key: Any) -> list[_CovariantValueType]: + return [item_value for item_key, item_value in self._list if item_key == key] + + def keys(self) -> KeysView[_KeyType]: + return self._dict.keys() + + def values(self) -> ValuesView[_CovariantValueType]: + return self._dict.values() + + def items(self) -> ItemsView[_KeyType, _CovariantValueType]: + return self._dict.items() + + def multi_items(self) -> list[tuple[_KeyType, _CovariantValueType]]: + return list(self._list) + + def __getitem__(self, key: _KeyType) -> _CovariantValueType: + return self._dict[key] + + def __contains__(self, key: Any) -> bool: + return key in self._dict + + def __iter__(self) -> Iterator[_KeyType]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._dict) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, self.__class__): + return False + return sorted(self._list) == sorted(other._list) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + items = self.multi_items() + return f"{class_name}({items!r})" + + +class MultiDict(ImmutableMultiDict[Any, Any]): + def __setitem__(self, key: Any, value: Any) -> None: + self.setlist(key, [value]) + + def __delitem__(self, key: Any) -> None: + self._list = [(k, v) for k, v in self._list if k != key] + del self._dict[key] + + def pop(self, key: Any, default: Any = None) -> Any: + self._list = [(k, v) for k, v in self._list if k != key] + return self._dict.pop(key, default) + + def popitem(self) -> tuple[Any, Any]: + key, value = self._dict.popitem() + self._list = [(k, v) for k, v in self._list if k != key] + return key, value + + def poplist(self, key: Any) -> list[Any]: + values = [v for k, v in self._list if k == key] + self.pop(key) + return values + + def clear(self) -> None: + self._dict.clear() + self._list.clear() + + def setdefault(self, key: Any, default: Any = None) -> Any: + if key not in self: + self._dict[key] = default + self._list.append((key, default)) + + return self[key] + + def setlist(self, key: Any, values: list[Any]) -> None: + if not values: + self.pop(key, None) + else: + existing_items = [(k, v) for (k, v) in self._list if k != key] + self._list = existing_items + [(key, value) for value in values] + self._dict[key] = values[-1] + + def append(self, key: Any, value: Any) -> None: + self._list.append((key, value)) + self._dict[key] = value + + def update( + self, + *args: MultiDict | Mapping[Any, Any] | list[tuple[Any, Any]], + **kwargs: Any, + ) -> None: + value = MultiDict(*args, **kwargs) + existing_items = [(k, v) for (k, v) in self._list if k not in value.keys()] + self._list = existing_items + value.multi_items() + self._dict.update(value) + + +class QueryParams(ImmutableMultiDict[str, str]): + """ + An immutable multidict. + """ + + def __init__( + self, + *args: ImmutableMultiDict[Any, Any] | Mapping[Any, Any] | list[tuple[Any, Any]] | str | bytes, + **kwargs: Any, + ) -> None: + assert len(args) < 2, "Too many arguments." + + value = args[0] if args else [] + + if isinstance(value, str): + super().__init__(parse_qsl(value, keep_blank_values=True), **kwargs) + elif isinstance(value, bytes): + super().__init__(parse_qsl(value.decode("latin-1"), keep_blank_values=True), **kwargs) + else: + super().__init__(*args, **kwargs) # type: ignore[arg-type] + self._list = [(str(k), str(v)) for k, v in self._list] + self._dict = {str(k): str(v) for k, v in self._dict.items()} + + def __str__(self) -> str: + return urlencode(self._list) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + query_string = str(self) + return f"{class_name}({query_string!r})" + + +class UploadFile: + """ + An uploaded file included as part of the request data. + """ + + def __init__( + self, + file: BinaryIO, + *, + size: int | None = None, + filename: str | None = None, + headers: Headers | None = None, + ) -> None: + self.filename = filename + self.file = file + self.size = size + self.headers = headers or Headers() + + # Capture max size from SpooledTemporaryFile if one is provided. This slightly speeds up future checks. + # Note 0 means unlimited mirroring SpooledTemporaryFile's __init__ + self._max_mem_size = getattr(self.file, "_max_size", 0) + + @property + def content_type(self) -> str | None: + return self.headers.get("content-type", None) + + @property + def _in_memory(self) -> bool: + # check for SpooledTemporaryFile._rolled + rolled_to_disk = getattr(self.file, "_rolled", True) + return not rolled_to_disk + + def _will_roll(self, size_to_add: int) -> bool: + # If we're not in_memory then we will always roll + if not self._in_memory: + return True + + # Check for SpooledTemporaryFile._max_size + future_size = self.file.tell() + size_to_add + return bool(future_size > self._max_mem_size) if self._max_mem_size else False + + async def write(self, data: bytes) -> None: + new_data_len = len(data) + if self.size is not None: + self.size += new_data_len + + if self._will_roll(new_data_len): + await run_in_threadpool(self.file.write, data) + else: + self.file.write(data) + + async def read(self, size: int = -1) -> bytes: + if self._in_memory: + return self.file.read(size) + return await run_in_threadpool(self.file.read, size) + + async def seek(self, offset: int) -> None: + if self._in_memory: + self.file.seek(offset) + else: + await run_in_threadpool(self.file.seek, offset) + + async def close(self) -> None: + if self._in_memory: + self.file.close() + else: + await run_in_threadpool(self.file.close) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(filename={self.filename!r}, size={self.size!r}, headers={self.headers!r})" + + +class FormData(ImmutableMultiDict[str, UploadFile | str]): + """ + An immutable multidict, containing both file uploads and text input. + """ + + def __init__( + self, + *args: FormData | Mapping[str, str | UploadFile] | list[tuple[str, str | UploadFile]], + **kwargs: str | UploadFile, + ) -> None: + super().__init__(*args, **kwargs) + + async def close(self) -> None: + for key, value in self.multi_items(): + if isinstance(value, UploadFile): + await value.close() + + +class Headers(Mapping[str, str]): + """ + An immutable, case-insensitive multidict. + """ + + def __init__( + self, + headers: Mapping[str, str] | None = None, + raw: list[tuple[bytes, bytes]] | None = None, + scope: MutableMapping[str, Any] | None = None, + ) -> None: + self._list: list[tuple[bytes, bytes]] = [] + if headers is not None: + assert raw is None, 'Cannot set both "headers" and "raw".' + assert scope is None, 'Cannot set both "headers" and "scope".' + self._list = [(key.lower().encode("latin-1"), value.encode("latin-1")) for key, value in headers.items()] + elif raw is not None: + assert scope is None, 'Cannot set both "raw" and "scope".' + self._list = raw + elif scope is not None: + # scope["headers"] isn't necessarily a list + # it might be a tuple or other iterable + self._list = scope["headers"] = list(scope["headers"]) + + @property + def raw(self) -> list[tuple[bytes, bytes]]: + return list(self._list) + + def keys(self) -> list[str]: # type: ignore[override] + return [key.decode("latin-1") for key, value in self._list] + + def values(self) -> list[str]: # type: ignore[override] + return [value.decode("latin-1") for key, value in self._list] + + def items(self) -> list[tuple[str, str]]: # type: ignore[override] + return [(key.decode("latin-1"), value.decode("latin-1")) for key, value in self._list] + + def getlist(self, key: str) -> list[str]: + get_header_key = key.lower().encode("latin-1") + return [item_value.decode("latin-1") for item_key, item_value in self._list if item_key == get_header_key] + + def mutablecopy(self) -> MutableHeaders: + return MutableHeaders(raw=self._list[:]) + + def __getitem__(self, key: str) -> str: + get_header_key = key.lower().encode("latin-1") + for header_key, header_value in self._list: + if header_key == get_header_key: + return header_value.decode("latin-1") + raise KeyError(key) + + def __contains__(self, key: Any) -> bool: + get_header_key = key.lower().encode("latin-1") + for header_key, header_value in self._list: + if header_key == get_header_key: + return True + return False + + def __iter__(self) -> Iterator[Any]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._list) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Headers): + return False + return sorted(self._list) == sorted(other._list) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + as_dict = dict(self.items()) + if len(as_dict) == len(self): + return f"{class_name}({as_dict!r})" + return f"{class_name}(raw={self.raw!r})" + + +class MutableHeaders(Headers): + def __setitem__(self, key: str, value: str) -> None: + """ + Set the header `key` to `value`, removing any duplicate entries. + Retains insertion order. + """ + set_key = key.lower().encode("latin-1") + set_value = value.encode("latin-1") + + found_indexes: list[int] = [] + for idx, (item_key, item_value) in enumerate(self._list): + if item_key == set_key: + found_indexes.append(idx) + + for idx in reversed(found_indexes[1:]): + del self._list[idx] + + if found_indexes: + idx = found_indexes[0] + self._list[idx] = (set_key, set_value) + else: + self._list.append((set_key, set_value)) + + def __delitem__(self, key: str) -> None: + """ + Remove the header `key`. + """ + del_key = key.lower().encode("latin-1") + + pop_indexes: list[int] = [] + for idx, (item_key, item_value) in enumerate(self._list): + if item_key == del_key: + pop_indexes.append(idx) + + for idx in reversed(pop_indexes): + del self._list[idx] + + def __ior__(self, other: Mapping[str, str]) -> MutableHeaders: + if not isinstance(other, Mapping): + raise TypeError(f"Expected a mapping but got {other.__class__.__name__}") + self.update(other) + return self + + def __or__(self, other: Mapping[str, str]) -> MutableHeaders: + if not isinstance(other, Mapping): + raise TypeError(f"Expected a mapping but got {other.__class__.__name__}") + new = self.mutablecopy() + new.update(other) + return new + + @property + def raw(self) -> list[tuple[bytes, bytes]]: + return self._list + + def setdefault(self, key: str, value: str) -> str: + """ + If the header `key` does not exist, then set it to `value`. + Returns the header value. + """ + set_key = key.lower().encode("latin-1") + set_value = value.encode("latin-1") + + for idx, (item_key, item_value) in enumerate(self._list): + if item_key == set_key: + return item_value.decode("latin-1") + self._list.append((set_key, set_value)) + return value + + def update(self, other: Mapping[str, str]) -> None: + for key, val in other.items(): + self[key] = val + + def append(self, key: str, value: str) -> None: + """ + Append a header, preserving any duplicate entries. + """ + append_key = key.lower().encode("latin-1") + append_value = value.encode("latin-1") + self._list.append((append_key, append_value)) + + def add_vary_header(self, vary: str) -> None: + existing = self.get("vary") + if existing is not None: + vary = ", ".join([existing, vary]) + self["vary"] = vary + + +class State: + """ + An object that can be used to store arbitrary state. + + Used for `request.state` and `app.state`. + """ + + _state: dict[str, Any] + + def __init__(self, state: dict[str, Any] | None = None): + if state is None: + state = {} + super().__setattr__("_state", state) + + def __setattr__(self, key: Any, value: Any) -> None: + self._state[key] = value + + def __getattr__(self, key: Any) -> Any: + try: + return self._state[key] + except KeyError: + message = "'{}' object has no attribute '{}'" + raise AttributeError(message.format(self.__class__.__name__, key)) + + def __delattr__(self, key: Any) -> None: + del self._state[key] + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + def __len__(self) -> int: + return len(self._state) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py new file mode 100644 index 0000000..9b2032c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import json +from collections.abc import Callable, Generator +from typing import Any, Literal + +from starlette import status +from starlette._utils import is_async_callable +from starlette.concurrency import run_in_threadpool +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.responses import PlainTextResponse, Response +from starlette.types import Message, Receive, Scope, Send +from starlette.websockets import WebSocket + + +class HTTPEndpoint: + def __init__(self, scope: Scope, receive: Receive, send: Send) -> None: + assert scope["type"] == "http" + self.scope = scope + self.receive = receive + self.send = send + self._allowed_methods = [ + method + for method in ("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "QUERY") + if getattr(self, method.lower(), None) is not None + ] + + def __await__(self) -> Generator[Any, None, None]: + return self.dispatch().__await__() + + async def dispatch(self) -> None: + request = Request(self.scope, receive=self.receive) + handler_name = "get" if request.method == "HEAD" and not hasattr(self, "head") else request.method.lower() + + handler: Callable[[Request], Any] + if request.method in self._allowed_methods or (request.method == "HEAD" and "GET" in self._allowed_methods): + handler = getattr(self, handler_name) + else: + handler = self.method_not_allowed + is_async = is_async_callable(handler) + if is_async: + response = await handler(request) + else: + response = await run_in_threadpool(handler, request) + await response(self.scope, self.receive, self.send) + + async def method_not_allowed(self, request: Request) -> Response: + # If we're running inside a starlette application then raise an + # exception, so that the configurable exception handler can deal with + # returning the response. For plain ASGI apps, just return the response. + headers = {"Allow": ", ".join(self._allowed_methods)} + if "app" in self.scope: + raise HTTPException(status_code=405, headers=headers) + return PlainTextResponse("Method Not Allowed", status_code=405, headers=headers) + + +class WebSocketEndpoint: + encoding: Literal["text", "bytes", "json"] | None = None + + def __init__(self, scope: Scope, receive: Receive, send: Send) -> None: + assert scope["type"] == "websocket" + self.scope = scope + self.receive = receive + self.send = send + + def __await__(self) -> Generator[Any, None, None]: + return self.dispatch().__await__() + + async def dispatch(self) -> None: + websocket = WebSocket(self.scope, receive=self.receive, send=self.send) + await self.on_connect(websocket) + + close_code = status.WS_1000_NORMAL_CLOSURE + + try: + while True: + message = await websocket.receive() + if message["type"] == "websocket.receive": + data = await self.decode(websocket, message) + await self.on_receive(websocket, data) + elif message["type"] == "websocket.disconnect": # pragma: no branch + close_code = int(message.get("code") or status.WS_1000_NORMAL_CLOSURE) + break + except Exception as exc: + close_code = status.WS_1011_INTERNAL_ERROR + raise exc + finally: + await self.on_disconnect(websocket, close_code) + + async def decode(self, websocket: WebSocket, message: Message) -> Any: + if self.encoding == "text": + if message.get("text") is None: + await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA) + raise RuntimeError("Expected text websocket messages, but got bytes") + return message["text"] + + elif self.encoding == "bytes": + if message.get("bytes") is None: + await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA) + raise RuntimeError("Expected bytes websocket messages, but got text") + return message["bytes"] + + elif self.encoding == "json": + if message.get("text") is not None: + text = message["text"] + else: + text = message["bytes"].decode("utf-8") + + try: + return json.loads(text) + except json.decoder.JSONDecodeError: + await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA) + raise RuntimeError("Malformed JSON data received.") + + assert self.encoding is None, f"Unsupported 'encoding' attribute {self.encoding}" + return message["text"] if message.get("text") is not None else message["bytes"] + + async def on_connect(self, websocket: WebSocket) -> None: + """Override to handle an incoming websocket connection""" + await websocket.accept() + + async def on_receive(self, websocket: WebSocket, data: Any) -> None: + """Override to handle an incoming websocket message""" + + async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None: + """Override to handle a disconnecting websocket""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py new file mode 100644 index 0000000..37bdae1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import http.client +from collections.abc import Mapping + + +class HTTPException(Exception): + def __init__(self, status_code: int, detail: str | None = None, headers: Mapping[str, str] | None = None) -> None: + if detail is None: + detail = http.client.responses.get(status_code, "") + self.status_code = status_code + self.detail = detail + self.headers = headers + + def __str__(self) -> str: + return f"{self.status_code}: {self.detail}" + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return f"{class_name}(status_code={self.status_code!r}, detail={self.detail!r})" + + +class WebSocketException(Exception): + def __init__(self, code: int, reason: str | None = None) -> None: + self.code = code + self.reason = reason or "" + + def __str__(self) -> str: + return f"{self.code}: {self.reason}" + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return f"{class_name}(code={self.code!r}, reason={self.reason!r})" + + +class StarletteDeprecationWarning(UserWarning): + """A custom deprecation warning for Starlette. + + Unlike the built-in DeprecationWarning, this inherits from UserWarning to ensure it is visible by default, helping + users discover deprecated features without needing to enable warnings explicitly. + + Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries + """ diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py new file mode 100644 index 0000000..e3cdc85 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py @@ -0,0 +1,301 @@ +from __future__ import annotations + +from collections.abc import AsyncGenerator +from dataclasses import dataclass, field +from enum import Enum +from tempfile import SpooledTemporaryFile +from typing import TYPE_CHECKING +from urllib.parse import unquote_plus + +from starlette.datastructures import FormData, Headers, UploadFile + +if TYPE_CHECKING: + import python_multipart as multipart + from python_multipart.exceptions import FormParserError + from python_multipart.multipart import MultipartCallbacks, QuerystringCallbacks, parse_options_header +else: + try: + try: + import python_multipart as multipart + from python_multipart.exceptions import FormParserError + from python_multipart.multipart import parse_options_header + except ModuleNotFoundError: # pragma: no cover + import multipart + from multipart.exceptions import FormParserError + from multipart.multipart import parse_options_header + except ModuleNotFoundError: # pragma: no cover + multipart = None + parse_options_header = None + + +class FormMessage(Enum): + FIELD_START = 1 + FIELD_NAME = 2 + FIELD_DATA = 3 + FIELD_END = 4 + END = 5 + + +@dataclass +class MultipartPart: + content_disposition: bytes | None = None + field_name: str = "" + data: bytearray = field(default_factory=bytearray) + file: UploadFile | None = None + item_headers: list[tuple[bytes, bytes]] = field(default_factory=list) + + +def _user_safe_decode(src: bytes | bytearray, codec: str) -> str: + try: + return src.decode(codec) + except (UnicodeDecodeError, LookupError): + return src.decode("latin-1") + + +class MultiPartException(Exception): + def __init__(self, message: str) -> None: + self.message = message + + +class FormParser: + def __init__( + self, + headers: Headers, + stream: AsyncGenerator[bytes, None], + *, + max_fields: int | float = 1000, + max_part_size: int = 1024 * 1024, # 1MB + ) -> None: + assert multipart is not None, "The `python-multipart` library must be installed to use form parsing." + self.headers = headers + self.stream = stream + self.max_fields = max_fields + self.max_part_size = max_part_size + self.messages: list[tuple[FormMessage, bytes]] = [] + self._current_field_size = 0 + self._current_fields = 0 + + def on_field_start(self) -> None: + self._current_field_size = 0 + message = (FormMessage.FIELD_START, b"") + self.messages.append(message) + + def on_field_name(self, data: bytes, start: int, end: int) -> None: + self._current_field_size += end - start + if self._current_field_size > self.max_part_size: + raise MultiPartException(f"Field exceeded maximum size of {int(self.max_part_size / 1024)}KB.") + message = (FormMessage.FIELD_NAME, data[start:end]) + self.messages.append(message) + + def on_field_data(self, data: bytes, start: int, end: int) -> None: + self._current_field_size += end - start + if self._current_field_size > self.max_part_size: + raise MultiPartException(f"Field exceeded maximum size of {int(self.max_part_size / 1024)}KB.") + message = (FormMessage.FIELD_DATA, data[start:end]) + self.messages.append(message) + + def on_field_end(self) -> None: + self._current_fields += 1 + if self._current_fields > self.max_fields: + raise MultiPartException(f"Too many fields. Maximum number of fields is {self.max_fields}.") + message = (FormMessage.FIELD_END, b"") + self.messages.append(message) + + def on_end(self) -> None: + message = (FormMessage.END, b"") + self.messages.append(message) + + async def parse(self) -> FormData: + # Callbacks dictionary. + callbacks: QuerystringCallbacks = { + "on_field_start": self.on_field_start, + "on_field_name": self.on_field_name, + "on_field_data": self.on_field_data, + "on_field_end": self.on_field_end, + "on_end": self.on_end, + } + + # Create the parser. + parser = multipart.QuerystringParser(callbacks) + field_name = bytearray() + field_value = bytearray() + + items: list[tuple[str, str | UploadFile]] = [] + + # Feed the parser with data from the request. + async for chunk in self.stream: + if chunk: + parser.write(chunk) + else: + parser.finalize() + messages = list(self.messages) + self.messages.clear() + for message_type, message_bytes in messages: + if message_type == FormMessage.FIELD_START: + field_name = bytearray() + field_value = bytearray() + elif message_type == FormMessage.FIELD_NAME: + field_name.extend(message_bytes) + elif message_type == FormMessage.FIELD_DATA: + field_value.extend(message_bytes) + elif message_type == FormMessage.FIELD_END: + name = unquote_plus(field_name.decode("latin-1")) + value = unquote_plus(field_value.decode("latin-1")) + items.append((name, value)) + + return FormData(items) + + +class MultiPartParser: + spool_max_size = 1024 * 1024 # 1MB + """The maximum size of the spooled temporary file used to store file data.""" + max_part_size = 1024 * 1024 # 1MB + """The maximum size of a part in the multipart request.""" + + def __init__( + self, + headers: Headers, + stream: AsyncGenerator[bytes, None], + *, + max_files: int | float = 1000, + max_fields: int | float = 1000, + max_part_size: int = 1024 * 1024, # 1MB + ) -> None: + assert multipart is not None, "The `python-multipart` library must be installed to use form parsing." + self.headers = headers + self.stream = stream + self.max_files = max_files + self.max_fields = max_fields + self.items: list[tuple[str, str | UploadFile]] = [] + self._current_files = 0 + self._current_fields = 0 + self._current_partial_header_name: bytes = b"" + self._current_partial_header_value: bytes = b"" + self._current_part = MultipartPart() + self._charset = "" + self._file_parts_to_write: list[tuple[MultipartPart, bytes]] = [] + self._file_parts_to_finish: list[MultipartPart] = [] + self._files_to_close_on_error: list[SpooledTemporaryFile[bytes]] = [] + self.max_part_size = max_part_size + + def on_part_begin(self) -> None: + self._current_part = MultipartPart() + + def on_part_data(self, data: bytes, start: int, end: int) -> None: + message_bytes = data[start:end] + if self._current_part.file is None: + if len(self._current_part.data) + len(message_bytes) > self.max_part_size: + raise MultiPartException(f"Part exceeded maximum size of {int(self.max_part_size / 1024)}KB.") + self._current_part.data.extend(message_bytes) + else: + self._file_parts_to_write.append((self._current_part, message_bytes)) + + def on_part_end(self) -> None: + if self._current_part.file is None: + self.items.append( + ( + self._current_part.field_name, + _user_safe_decode(self._current_part.data, self._charset), + ) + ) + else: + self._file_parts_to_finish.append(self._current_part) + # The file can be added to the items right now even though it's not + # finished yet, because it will be finished in the `parse()` method, before + # self.items is used in the return value. + self.items.append((self._current_part.field_name, self._current_part.file)) + + def on_header_field(self, data: bytes, start: int, end: int) -> None: + self._current_partial_header_name += data[start:end] + + def on_header_value(self, data: bytes, start: int, end: int) -> None: + self._current_partial_header_value += data[start:end] + + def on_header_end(self) -> None: + field = self._current_partial_header_name.lower() + if field == b"content-disposition": + self._current_part.content_disposition = self._current_partial_header_value + self._current_part.item_headers.append((field, self._current_partial_header_value)) + self._current_partial_header_name = b"" + self._current_partial_header_value = b"" + + def on_headers_finished(self) -> None: + disposition, options = parse_options_header(self._current_part.content_disposition) + try: + self._current_part.field_name = _user_safe_decode(options[b"name"], self._charset) + except KeyError: + raise MultiPartException('The Content-Disposition header field "name" must be provided.') + if b"filename" in options: + self._current_files += 1 + if self._current_files > self.max_files: + raise MultiPartException(f"Too many files. Maximum number of files is {self.max_files}.") + filename = _user_safe_decode(options[b"filename"], self._charset) + tempfile = SpooledTemporaryFile(max_size=self.spool_max_size) + self._files_to_close_on_error.append(tempfile) + self._current_part.file = UploadFile( + file=tempfile, # type: ignore[arg-type] + size=0, + filename=filename, + headers=Headers(raw=self._current_part.item_headers), + ) + else: + self._current_fields += 1 + if self._current_fields > self.max_fields: + raise MultiPartException(f"Too many fields. Maximum number of fields is {self.max_fields}.") + self._current_part.file = None + + def on_end(self) -> None: + pass + + async def parse(self) -> FormData: + # Parse the Content-Type header to get the multipart boundary. + _, params = parse_options_header(self.headers["Content-Type"]) + charset = params.get(b"charset", "utf-8") + if isinstance(charset, bytes): + charset = charset.decode("latin-1") + self._charset = charset + try: + boundary = params[b"boundary"] + except KeyError: + raise MultiPartException("Missing boundary in multipart.") + + # Callbacks dictionary. + callbacks: MultipartCallbacks = { + "on_part_begin": self.on_part_begin, + "on_part_data": self.on_part_data, + "on_part_end": self.on_part_end, + "on_header_field": self.on_header_field, + "on_header_value": self.on_header_value, + "on_header_end": self.on_header_end, + "on_headers_finished": self.on_headers_finished, + "on_end": self.on_end, + } + + try: + parser = multipart.MultipartParser(boundary, callbacks) + # Feed the parser with data from the request. + async for chunk in self.stream: + parser.write(chunk) + # Write file data, it needs to use await with the UploadFile methods + # that call the corresponding file methods *in a threadpool*, + # otherwise, if they were called directly in the callback methods above + # (regular, non-async functions), that would block the event loop in + # the main thread. + for part, data in self._file_parts_to_write: + assert part.file # for type checkers + await part.file.write(data) + for part in self._file_parts_to_finish: + assert part.file # for type checkers + await part.file.seek(0) + self._file_parts_to_write.clear() + self._file_parts_to_finish.clear() + parser.finalize() + except BaseException as exc: + # Close all the files if parsing or reading the request stream fails. + for file in self._files_to_close_on_error: + file.close() + if isinstance(exc, FormParserError): + raise MultiPartException("Invalid multipart data.") from exc + raise + + return FormData(self.items) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py new file mode 100644 index 0000000..f8d47d7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Iterator +from typing import Any, ParamSpec, Protocol + +P = ParamSpec("P") + + +_Scope = Any +_Receive = Callable[[], Awaitable[Any]] +_Send = Callable[[Any], Awaitable[None]] +# Since `starlette.types.ASGIApp` type differs from `ASGIApplication` from `asgiref` +# we need to define a more permissive version of ASGIApp that doesn't cause type errors. +_ASGIApp = Callable[[_Scope, _Receive, _Send], Awaitable[None]] + + +class _MiddlewareFactory(Protocol[P]): + def __call__(self, app: _ASGIApp, /, *args: P.args, **kwargs: P.kwargs) -> _ASGIApp: ... # pragma: no cover + + +class Middleware: + def __init__(self, cls: _MiddlewareFactory[P], *args: P.args, **kwargs: P.kwargs) -> None: + self.cls = cls + self.args = args + self.kwargs = kwargs + + def __iter__(self) -> Iterator[Any]: + as_tuple = (self.cls, self.args, self.kwargs) + return iter(as_tuple) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + args_strings = [f"{value!r}" for value in self.args] + option_strings = [f"{key}={value!r}" for key, value in self.kwargs.items()] + name = getattr(self.cls, "__name__", "") + args_repr = ", ".join([name] + args_strings + option_strings) + return f"{class_name}({args_repr})" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py new file mode 100644 index 0000000..935385f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from collections.abc import Callable + +from starlette.authentication import ( + AuthCredentials, + AuthenticationBackend, + AuthenticationError, + UnauthenticatedUser, +) +from starlette.requests import HTTPConnection +from starlette.responses import PlainTextResponse, Response +from starlette.types import ASGIApp, Receive, Scope, Send + + +class AuthenticationMiddleware: + def __init__( + self, + app: ASGIApp, + backend: AuthenticationBackend, + on_error: Callable[[HTTPConnection, AuthenticationError], Response] | None = None, + ) -> None: + self.app = app + self.backend = backend + self.on_error: Callable[[HTTPConnection, AuthenticationError], Response] = ( + on_error if on_error is not None else self.default_on_error + ) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] not in ["http", "websocket"]: + await self.app(scope, receive, send) + return + + conn = HTTPConnection(scope) + try: + auth_result = await self.backend.authenticate(conn) + except AuthenticationError as exc: + response = self.on_error(conn, exc) + if scope["type"] == "websocket": + await send({"type": "websocket.close", "code": 1000}) + else: + await response(scope, receive, send) + return + + if auth_result is None: + auth_result = AuthCredentials(), UnauthenticatedUser() + scope["auth"], scope["user"] = auth_result + await self.app(scope, receive, send) + + @staticmethod + def default_on_error(conn: HTTPConnection, exc: Exception) -> Response: + return PlainTextResponse(str(exc), status_code=400) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py new file mode 100644 index 0000000..a7ecb80 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Callable, Mapping, MutableMapping +from typing import Any, TypeVar + +import anyio + +from starlette._utils import create_collapsing_task_group +from starlette.requests import ClientDisconnect, Request +from starlette.responses import Response +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +RequestResponseEndpoint = Callable[[Request], Awaitable[Response]] +DispatchFunction = Callable[[Request, RequestResponseEndpoint], Awaitable[Response]] +BodyStreamGenerator = AsyncGenerator[bytes | MutableMapping[str, Any], None] +AsyncContentStream = AsyncIterable[str | bytes | memoryview | MutableMapping[str, Any]] +T = TypeVar("T") + + +class _CachedRequest(Request): + """ + If the user calls Request.body() from their dispatch function + we cache the entire request body in memory and pass that to downstream middlewares, + but if they call Request.stream() then all we do is send an + empty body so that downstream things don't hang forever. + """ + + def __init__(self, scope: Scope, receive: Receive): + super().__init__(scope, receive) + self._wrapped_rcv_disconnected = False + self._wrapped_rcv_consumed = False + self._wrapped_rc_stream = self.stream() + + async def wrapped_receive(self) -> Message: + # wrapped_rcv state 1: disconnected + if self._wrapped_rcv_disconnected: + # we've already sent a disconnect to the downstream app + # we don't need to wait to get another one + # (although most ASGI servers will just keep sending it) + return {"type": "http.disconnect"} + # wrapped_rcv state 1: consumed but not yet disconnected + if self._wrapped_rcv_consumed: + # since the downstream app has consumed us all that is left + # is to send it a disconnect + if self._is_disconnected: + # the middleware has already seen the disconnect + # since we know the client is disconnected no need to wait + # for the message + self._wrapped_rcv_disconnected = True + return {"type": "http.disconnect"} + # we don't know yet if the client is disconnected or not + # so we'll wait until we get that message + msg = await self.receive() + if msg["type"] != "http.disconnect": # pragma: no cover + # at this point a disconnect is all that we should be receiving + # if we get something else, things went wrong somewhere + raise RuntimeError(f"Unexpected message received: {msg['type']}") + self._wrapped_rcv_disconnected = True + return msg + + # wrapped_rcv state 3: not yet consumed + if getattr(self, "_body", None) is not None: + # body() was called, we return it even if the client disconnected + self._wrapped_rcv_consumed = True + return { + "type": "http.request", + "body": self._body, + "more_body": False, + } + elif self._stream_consumed: + # stream() was called to completion + # return an empty body so that downstream apps don't hang + # waiting for a disconnect + self._wrapped_rcv_consumed = True + return { + "type": "http.request", + "body": b"", + "more_body": False, + } + else: + # body() was never called and stream() wasn't consumed + try: + stream = self.stream() + chunk = await stream.__anext__() + self._wrapped_rcv_consumed = self._stream_consumed + return { + "type": "http.request", + "body": chunk, + "more_body": not self._stream_consumed, + } + except ClientDisconnect: + self._wrapped_rcv_disconnected = True + return {"type": "http.disconnect"} + + +class BaseHTTPMiddleware: + def __init__(self, app: ASGIApp, dispatch: DispatchFunction | None = None) -> None: + self.app = app + self.dispatch_func = self.dispatch if dispatch is None else dispatch + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + await self.app(scope, receive, send) + return + + request = _CachedRequest(scope, receive) + wrapped_receive = request.wrapped_receive + response_sent = anyio.Event() + app_exc: Exception | None = None + exception_already_raised = False + + async def call_next(request: Request) -> Response: + async def receive_or_disconnect() -> Message: + if response_sent.is_set(): + return {"type": "http.disconnect"} + + async with anyio.create_task_group() as task_group: + + async def wrap(func: Callable[[], Awaitable[T]]) -> T: + result = await func() + task_group.cancel_scope.cancel() + return result + + task_group.start_soon(wrap, response_sent.wait) + message = await wrap(wrapped_receive) + + if response_sent.is_set(): + return {"type": "http.disconnect"} + + return message + + async def send_no_error(message: Message) -> None: + try: + await send_stream.send(message) + except anyio.BrokenResourceError: + # recv_stream has been closed, i.e. response_sent has been set. + return + + if message["type"] == "http.response.pathsend" or ( + message["type"] == "http.response.body" and not message.get("more_body", False) + ): + await response_sent.wait() + + async def coro() -> None: + nonlocal app_exc + + with send_stream: + try: + await self.app(scope, receive_or_disconnect, send_no_error) + except Exception as exc: + app_exc = exc + + task_group.start_soon(coro) + + try: + message = await recv_stream.receive() + info = message.get("info", None) + if message["type"] == "http.response.debug" and info is not None: + message = await recv_stream.receive() + except anyio.EndOfStream: + if app_exc is not None: + nonlocal exception_already_raised + exception_already_raised = True + # Prevent `anyio.EndOfStream` from polluting app exception context. + # If both cause and context are None then the context is suppressed + # and `anyio.EndOfStream` is not present in the exception traceback. + # If exception cause is not None then it is propagated with + # reraising here. + # If exception has no cause but has context set then the context is + # propagated as a cause with the reraise. This is necessary in order + # to prevent `anyio.EndOfStream` from polluting the exception + # context. + raise app_exc from app_exc.__cause__ or app_exc.__context__ + raise RuntimeError("No response returned.") + + assert message["type"] == "http.response.start" + + async def body_stream() -> BodyStreamGenerator: + async for message in recv_stream: + if message["type"] == "http.response.pathsend": + yield message + break + assert message["type"] == "http.response.body", f"Unexpected message: {message}" + body = message.get("body", b"") + if body: + yield body + if not message.get("more_body", False): + break + + response = _StreamingResponse(status_code=message["status"], content=body_stream(), info=info) + response.raw_headers = message["headers"] + return response + + send_stream, recv_stream = anyio.create_memory_object_stream[Message]() + with recv_stream, send_stream: + async with create_collapsing_task_group() as task_group: + response = await self.dispatch_func(request, call_next) + await response(scope, wrapped_receive, send) + response_sent.set() + recv_stream.close() + if app_exc is not None and not exception_already_raised: + raise app_exc + + async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: + raise NotImplementedError() # pragma: no cover + + +class _StreamingResponse(Response): + def __init__( + self, + content: AsyncContentStream, + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + info: Mapping[str, Any] | None = None, + ) -> None: + self.info = info + self.body_iterator = content + self.status_code = status_code + self.media_type = media_type + self.init_headers(headers) + self.background = None + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if self.info is not None: + await send({"type": "http.response.debug", "info": self.info}) + await send( + { + "type": "http.response.start", + "status": self.status_code, + "headers": self.raw_headers, + } + ) + + should_close_body = True + async for chunk in self.body_iterator: + if isinstance(chunk, dict): + # We got an ASGI message which is not response body (eg: pathsend) + should_close_body = False + await send(chunk) + continue + await send({"type": "http.response.body", "body": chunk, "more_body": True}) + + if should_close_body: + await send({"type": "http.response.body", "body": b"", "more_body": False}) + + if self.background: + await self.background() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py new file mode 100644 index 0000000..159c70d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from typing import cast + +from starlette.datastructures import Headers +from starlette.exceptions import HTTPException +from starlette.responses import PlainTextResponse +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +MAX_BODY_SIZE_SCOPE_KEY = "starlette.max_body_size" +_BODY_LIMIT_RESPONDER_SCOPE_KEY = "starlette._body_limit_responder" + + +class _Missing: + __slots__ = () + + +_MISSING = _Missing() + + +class _RequestBodyTooLarge(HTTPException): + def __init__(self) -> None: + super().__init__(status_code=413, detail="Content Too Large") + + +class _RequestBodyLimitResponseSent(Exception): + pass + + +class RequestBodyLimitMiddleware: + """Limit the total size of an HTTP request body.""" + + def __init__(self, app: ASGIApp, max_body_size: int) -> None: + self.app = app + self.max_body_size = max_body_size + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + return await self.app(scope, receive, send) + + responder = RequestBodyLimitResponder(self.app, self.max_body_size) + await responder(scope, receive, send) + + +class RequestBodyLimitResponder: + def __init__(self, app: ASGIApp, max_body_size: int) -> None: + self.app = app + self.max_body_size = max_body_size + self._scope: Scope | None = None + self._receive: Receive | None = None + self._send: Send | None = None + self.content_length: int | None = None + self.total_size = 0 + self.response_started = False + + @property + def scope(self) -> Scope: + assert self._scope is not None + return self._scope + + @property + def receive(self) -> Receive: + assert self._receive is not None + return self._receive + + @property + def send(self) -> Send: + assert self._send is not None + return self._send + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + previous_scope_limit = cast(int | _Missing, scope.get(MAX_BODY_SIZE_SCOPE_KEY, _MISSING)) + scope[MAX_BODY_SIZE_SCOPE_KEY] = self.max_body_size + + active_responder = cast(RequestBodyLimitResponder | None, scope.get(_BODY_LIMIT_RESPONDER_SCOPE_KEY)) + if active_responder is not None: + active_responder.max_body_size = self.max_body_size + if active_responder.total_size > active_responder.max_body_size: + raise _RequestBodyTooLarge + return await self.app(scope, receive, send) + + self._scope = scope + self._receive = receive + self._send = send + self.content_length = _get_content_length(scope) + scope[_BODY_LIMIT_RESPONDER_SCOPE_KEY] = self + + try: + await self.app(scope, self.receive_with_limit, self.send_with_limit) + except _RequestBodyTooLarge: + if self.response_started: + raise + response = PlainTextResponse("Content Too Large", status_code=413) + await response(scope, receive, send) + except _RequestBodyLimitResponseSent: + pass + finally: + scope.pop(_BODY_LIMIT_RESPONDER_SCOPE_KEY, None) + if isinstance(previous_scope_limit, _Missing): + scope.pop(MAX_BODY_SIZE_SCOPE_KEY, None) + else: + scope[MAX_BODY_SIZE_SCOPE_KEY] = previous_scope_limit + + async def receive_with_limit(self) -> Message: + if self.content_length is not None and self.content_length > self.max_body_size: + raise _RequestBodyTooLarge + + message = await self.receive() + if message["type"] == "http.request": + self.total_size += len(message.get("body", b"")) + if self.total_size > self.max_body_size: + raise _RequestBodyTooLarge + return message + + async def send_with_limit(self, message: Message) -> None: + if message["type"] == "http.response.start": + self.response_started = True + if self.content_length is not None and self.content_length > self.max_body_size: + response = PlainTextResponse("Content Too Large", status_code=413) + await response(self.scope, self.receive, self.send) + raise _RequestBodyLimitResponseSent + await self.send(message) + + +def _get_content_length(scope: Scope) -> int | None: + content_length = Headers(scope=scope).get("content-length") + if content_length is None: + return None + try: + return int(content_length) + except ValueError: + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py new file mode 100644 index 0000000..fe4c234 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +import functools +import re +from collections.abc import Collection + +from starlette.datastructures import Headers, MutableHeaders +from starlette.responses import PlainTextResponse, Response +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +ALL_METHODS = ("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "QUERY") +SAFELISTED_HEADERS = {"Accept", "Accept-Language", "Content-Language", "Content-Type"} + + +class CORSMiddleware: + def __init__( + self, + app: ASGIApp, + allow_origins: Collection[str] = (), + allow_methods: Collection[str] = ("GET",), + allow_headers: Collection[str] = (), + allow_credentials: bool = False, + allow_origin_regex: str | None = None, + allow_private_network: bool = False, + expose_headers: Collection[str] = (), + max_age: int = 600, + ) -> None: + if "*" in allow_methods: + allow_methods = ALL_METHODS + + compiled_allow_origin_regex = None + if allow_origin_regex is not None: + compiled_allow_origin_regex = re.compile(allow_origin_regex) + + allow_all_origins = "*" in allow_origins + allow_all_headers = "*" in allow_headers + preflight_explicit_allow_origin = not allow_all_origins or allow_credentials + + simple_headers: dict[str, str] = {} + if allow_all_origins: + simple_headers["Access-Control-Allow-Origin"] = "*" + if allow_credentials: + simple_headers["Access-Control-Allow-Credentials"] = "true" + if expose_headers: + simple_headers["Access-Control-Expose-Headers"] = ", ".join(expose_headers) + + preflight_headers: dict[str, str] = { + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network" # noqa: E501 # fmt: skip + } + if not preflight_explicit_allow_origin: + preflight_headers["Access-Control-Allow-Origin"] = "*" + preflight_headers.update( + { + "Access-Control-Allow-Methods": ", ".join(allow_methods), + "Access-Control-Max-Age": str(max_age), + } + ) + allow_headers = sorted(SAFELISTED_HEADERS | set(allow_headers)) + if allow_headers and not allow_all_headers: + preflight_headers["Access-Control-Allow-Headers"] = ", ".join(allow_headers) + if allow_credentials: + preflight_headers["Access-Control-Allow-Credentials"] = "true" + + self.app = app + self.allow_origins = allow_origins + self.allow_methods = allow_methods + self.allow_headers = [h.lower() for h in allow_headers] + self.allow_all_origins = allow_all_origins + self.allow_all_headers = allow_all_headers + self.allow_credentials = allow_credentials + self.preflight_explicit_allow_origin = preflight_explicit_allow_origin + self.allow_origin_regex = compiled_allow_origin_regex + self.allow_private_network = allow_private_network + self.simple_headers = simple_headers + self.preflight_headers = preflight_headers + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": # pragma: no cover + await self.app(scope, receive, send) + return + + method = scope["method"] + headers = Headers(scope=scope) + origin = headers.get("origin") + + if origin is not None and method == "OPTIONS" and "access-control-request-method" in headers: + response = self.preflight_response(request_headers=headers) + await response(scope, receive, send) + return + + await self.simple_response(scope, receive, send, request_headers=headers) + + def is_allowed_origin(self, origin: str) -> bool: + if self.allow_all_origins: + return True + + if self.allow_origin_regex is not None and self.allow_origin_regex.fullmatch(origin): + return True + + return origin in self.allow_origins + + def preflight_response(self, request_headers: Headers) -> Response: + requested_origin = request_headers["origin"] + requested_method = request_headers["access-control-request-method"] + requested_headers = request_headers.get("access-control-request-headers") + requested_private_network = request_headers.get("access-control-request-private-network") + + headers = dict(self.preflight_headers) + failures: list[str] = [] + + if self.is_allowed_origin(origin=requested_origin): + if self.preflight_explicit_allow_origin: + # The "else" case is already accounted for in self.preflight_headers + # and the value would be "*". + headers["Access-Control-Allow-Origin"] = requested_origin + else: + failures.append("origin") + + if requested_method not in self.allow_methods: + failures.append("method") + + # If we allow all headers, then we have to mirror back any requested + # headers in the response. + if self.allow_all_headers and requested_headers is not None: + headers["Access-Control-Allow-Headers"] = requested_headers + elif requested_headers is not None: + for header in [h.lower() for h in requested_headers.split(",")]: + if header.strip() not in self.allow_headers: + failures.append("headers") + break + + if requested_private_network is not None: + if self.allow_private_network: + headers["Access-Control-Allow-Private-Network"] = "true" + else: + failures.append("private-network") + + # We don't strictly need to use 400 responses here, since its up to + # the browser to enforce the CORS policy, but its more informative + # if we do. + if failures: + failure_text = "Disallowed CORS " + ", ".join(failures) + return PlainTextResponse(failure_text, status_code=400, headers=headers) + + return PlainTextResponse("OK", status_code=200, headers=headers) + + async def simple_response(self, scope: Scope, receive: Receive, send: Send, request_headers: Headers) -> None: + send = functools.partial(self.send, send=send, request_headers=request_headers) + await self.app(scope, receive, send) + + async def send(self, message: Message, send: Send, request_headers: Headers) -> None: + if message["type"] != "http.response.start": + await send(message) + return + + message.setdefault("headers", []) + headers = MutableHeaders(scope=message) + origin = request_headers.get("Origin") + if origin is not None: + headers.update(self.simple_headers) + + # If credentials are allowed, then we must respond with the specific origin instead of '*'. + if origin is not None and self.allow_all_origins and self.allow_credentials: + self.allow_explicit_origin(headers, origin) + + # If we only allow specific origins, then we have to mirror back the Origin header in the response. + elif origin is not None and not self.allow_all_origins and self.is_allowed_origin(origin=origin): + self.allow_explicit_origin(headers, origin) + else: + headers["Vary"] = ", ".join([*headers.getlist("Vary"), "Origin"]) + + await send(message) + + @staticmethod + def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None: + headers["Access-Control-Allow-Origin"] = origin + headers["Vary"] = ", ".join([*headers.getlist("Vary"), "Origin"]) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py new file mode 100644 index 0000000..e0ebd65 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py @@ -0,0 +1,259 @@ +from __future__ import annotations + +import html +import inspect +import sys +import traceback + +from starlette._utils import is_async_callable +from starlette.concurrency import run_in_threadpool +from starlette.requests import Request +from starlette.responses import HTMLResponse, PlainTextResponse, Response +from starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send + +STYLES = """ +p { + color: #211c1c; +} +.traceback-container { + border: 1px solid #038BB8; +} +.traceback-title { + background-color: #038BB8; + color: lemonchiffon; + padding: 12px; + font-size: 20px; + margin-top: 0px; +} +.frame-line { + padding-left: 10px; + font-family: monospace; +} +.frame-filename { + font-family: monospace; +} +.center-line { + background-color: #038BB8; + color: #f9f6e1; + padding: 5px 0px 5px 5px; +} +.lineno { + margin-right: 5px; +} +.frame-title { + font-weight: unset; + padding: 10px 10px 10px 10px; + background-color: #E4F4FD; + margin-right: 10px; + color: #191f21; + font-size: 17px; + border: 1px solid #c7dce8; +} +.collapse-btn { + float: right; + padding: 0px 5px 1px 5px; + border: solid 1px #96aebb; + cursor: pointer; +} +.collapsed { + display: none; +} +.source-code { + font-family: courier; + font-size: small; + padding-bottom: 10px; +} +""" + +JS = """ + +""" + +TEMPLATE = """ + + + + Starlette Debugger + + +

500 Server Error

+

{error}

+
+

Traceback

+
{exc_html}
+
+ {js} + + +""" + +FRAME_TEMPLATE = """ +
+

File {frame_filename}, + line {frame_lineno}, + in {frame_name} + {collapse_button} +

+
{code_context}
+
+""" # noqa: E501 + +LINE = """ +

+{lineno}. {line}

+""" + +CENTER_LINE = """ +

+{lineno}. {line}

+""" + + +class ServerErrorMiddleware: + """ + Handles returning 500 responses when a server error occurs. + + If 'debug' is set, then traceback responses will be returned, + otherwise the designated 'handler' will be called. + + This middleware class should generally be used to wrap *everything* + else up, so that unhandled exceptions anywhere in the stack + always result in an appropriate 500 response. + """ + + def __init__( + self, + app: ASGIApp, + handler: ExceptionHandler | None = None, + debug: bool = False, + ) -> None: + self.app = app + self.handler = handler + self.debug = debug + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + await self.app(scope, receive, send) + return + + response_started = False + + async def _send(message: Message) -> None: + nonlocal response_started, send + + if message["type"] == "http.response.start": + response_started = True + await send(message) + + try: + await self.app(scope, receive, _send) + except Exception as exc: + request = Request(scope) + if self.debug: + # In debug mode, return traceback responses. + response = await run_in_threadpool(self.debug_response, request, exc) + elif self.handler is None: + # Use our default 500 error handler. + response = self.error_response(request, exc) + else: + # Use an installed 500 error handler. + if is_async_callable(self.handler): + response = await self.handler(request, exc) # type: ignore[assignment, arg-type] + else: + response = await run_in_threadpool(self.handler, request, exc) # type: ignore[arg-type] + + if not response_started: + await response(scope, receive, send) + + # We always continue to raise the exception. + # This allows servers to log the error, or allows test clients + # to optionally raise the error within the test case. + raise exc + + def format_line(self, index: int, line: str, frame_lineno: int, frame_index: int) -> str: + values = { + # HTML escape - line could contain < or > + "line": html.escape(line).replace(" ", " "), + "lineno": (frame_lineno - frame_index) + index, + } + + if index != frame_index: + return LINE.format(**values) + return CENTER_LINE.format(**values) + + def generate_frame_html(self, frame: inspect.FrameInfo, is_collapsed: bool) -> str: + code_context = "".join( + self.format_line( + index, + line, + frame.lineno, + frame.index, # type: ignore[arg-type] + ) + for index, line in enumerate(frame.code_context or []) + ) + + values = { + # HTML escape - filename could contain < or >, especially if it's a virtual + # file e.g. in the REPL + "frame_filename": html.escape(frame.filename), + "frame_lineno": frame.lineno, + # HTML escape - if you try very hard it's possible to name a function with < + # or > + "frame_name": html.escape(frame.function), + "code_context": code_context, + "collapsed": "collapsed" if is_collapsed else "", + "collapse_button": "+" if is_collapsed else "‒", + } + return FRAME_TEMPLATE.format(**values) + + def generate_html(self, exc: Exception, limit: int = 7) -> str: + traceback_obj = traceback.TracebackException.from_exception(exc, capture_locals=True) + + exc_html = "" + is_collapsed = False + exc_traceback = exc.__traceback__ + if exc_traceback is not None: + frames = inspect.getinnerframes(exc_traceback, limit) + for frame in reversed(frames): + exc_html += self.generate_frame_html(frame, is_collapsed) + is_collapsed = True + + if sys.version_info >= (3, 13): # pragma: no cover + exc_type_str = traceback_obj.exc_type_str + else: # pragma: no cover + exc_type_str = traceback_obj.exc_type.__name__ + + # escape error class and text + error = f"{html.escape(exc_type_str)}: {html.escape(str(traceback_obj))}" + + return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html) + + def generate_plain_text(self, exc: Exception) -> str: + return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)) + + def debug_response(self, request: Request, exc: Exception) -> Response: + accept = request.headers.get("accept", "") + + if "text/html" in accept: + content = self.generate_html(exc) + return HTMLResponse(content, status_code=500) + content = self.generate_plain_text(exc) + return PlainTextResponse(content, status_code=500) + + def error_response(self, request: Request, exc: Exception) -> Response: + return PlainTextResponse("Internal Server Error", status_code=500) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py new file mode 100644 index 0000000..6c98558 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from starlette._exception_handler import ( + ExceptionHandlers, + StatusHandlers, + wrap_app_handling_exceptions, +) +from starlette.exceptions import HTTPException, WebSocketException +from starlette.requests import Request +from starlette.responses import PlainTextResponse, Response +from starlette.types import ASGIApp, ExceptionHandler, Receive, Scope, Send +from starlette.websockets import WebSocket + + +class ExceptionMiddleware: + def __init__( + self, + app: ASGIApp, + handlers: Mapping[Any, ExceptionHandler] | None = None, + debug: bool = False, + ) -> None: + self.app = app + self.debug = debug # TODO: We ought to handle 404 cases if debug is set. + self._status_handlers: StatusHandlers = {} + self._exception_handlers: ExceptionHandlers = { + HTTPException: self.http_exception, + WebSocketException: self.websocket_exception, + } + if handlers is not None: # pragma: no branch + for key, value in handlers.items(): + self.add_exception_handler(key, value) + + def add_exception_handler( + self, + exc_class_or_status_code: int | type[Exception], + handler: ExceptionHandler, + ) -> None: + if isinstance(exc_class_or_status_code, int): + self._status_handlers[exc_class_or_status_code] = handler + else: + assert issubclass(exc_class_or_status_code, Exception) + self._exception_handlers[exc_class_or_status_code] = handler + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] not in ("http", "websocket"): + await self.app(scope, receive, send) + return + + scope["starlette.exception_handlers"] = ( + self._exception_handlers, + self._status_handlers, + ) + + conn: Request | WebSocket + if scope["type"] == "http": + conn = Request(scope, receive, send) + else: + conn = WebSocket(scope, receive, send) + + await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) + + async def http_exception(self, request: Request, exc: Exception) -> Response: + assert isinstance(exc, HTTPException) + if exc.status_code in {204, 304}: + return Response(status_code=exc.status_code, headers=exc.headers) + return PlainTextResponse(exc.detail, status_code=exc.status_code, headers=exc.headers) + + async def websocket_exception(self, websocket: WebSocket, exc: Exception) -> None: + assert isinstance(exc, WebSocketException) + await websocket.close(code=exc.code, reason=exc.reason) # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py new file mode 100644 index 0000000..4473f5a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import zlib +from typing import NoReturn + +import anyio.lowlevel +import anyio.to_thread + +from starlette.datastructures import Headers, MutableHeaders +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +# TODO(v2): We should rename `DEFAULT_EXCLUDED_CONTENT_TYPES` to `DEFAULT_EXCLUDE_CONTENT_TYPES`. +DEFAULT_EXCLUDED_CONTENT_TYPES = ( + "application/gzip", + "application/x-gzip", + "application/zip", + "audio/*", + "font/woff", + "font/woff2", + "image/avif", + "image/gif", + "image/jpeg", + "image/png", + "image/webp", + "text/event-stream", + "video/*", +) + +_gzip_capacity_limiter: anyio.lowlevel.RunVar[anyio.CapacityLimiter] = anyio.lowlevel.RunVar("_gzip_capacity_limiter") + + +def _get_gzip_capacity_limiter() -> anyio.CapacityLimiter: + """Return the capacity limiter used for worker-thread GZip compression.""" + try: + return _gzip_capacity_limiter.get() + except LookupError: + # Keep gzip compression isolated from AnyIO's default worker-thread + # capacity limiter while matching its default concurrency. + limiter = anyio.CapacityLimiter(40) + _gzip_capacity_limiter.set(limiter) + return limiter + + +class GZipMiddleware: + def __init__( + self, + app: ASGIApp, + minimum_size: int = 500, + compresslevel: int = 9, + thread_minimum_size: int = 128 * 1024, # 128 KiB + *, + exclude_content_types: tuple[str, ...] = DEFAULT_EXCLUDED_CONTENT_TYPES, + ) -> None: + self.app = app + self.minimum_size = minimum_size + self.compresslevel = compresslevel + self.thread_minimum_size = thread_minimum_size + self.exclude_content_types = _normalize_content_types(exclude_content_types) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": # pragma: no cover + await self.app(scope, receive, send) + return + + headers = Headers(scope=scope) + responder: ASGIApp + if "gzip" in headers.get("Accept-Encoding", ""): + responder = GZipResponder( + self.app, + self.minimum_size, + compresslevel=self.compresslevel, + thread_minimum_size=self.thread_minimum_size, + exclude_content_types=self.exclude_content_types, + ) + else: + responder = IdentityResponder(self.app, self.minimum_size, exclude_content_types=self.exclude_content_types) + + await responder(scope, receive, send) + + +class IdentityResponder: + content_encoding: str + + def __init__( + self, + app: ASGIApp, + minimum_size: int, + *, + exclude_content_types: tuple[str, ...] = DEFAULT_EXCLUDED_CONTENT_TYPES, + ) -> None: + self.app = app + self.minimum_size = minimum_size + self.exclude_content_types = _normalize_content_types(exclude_content_types) + self.send: Send = unattached_send + self.initial_message: Message = {} + self.started = False + self.content_encoding_set = False + self.content_type_is_excluded = False + self.partial_response = False + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + self.send = send + await self.app(scope, receive, self.send_with_compression) + + async def send_with_compression(self, message: Message) -> None: + message_type = message["type"] + if message_type == "http.response.start": + # Don't send the initial message until we've determined how to + # modify the outgoing headers correctly. + self.initial_message = message + headers = Headers(raw=self.initial_message["headers"]) + self.content_encoding_set = "content-encoding" in headers + self.partial_response = message["status"] == 206 + media_type = headers.get("content-type", "").partition(";")[0].strip().lower() + media_types = {media_type, media_type.partition("/")[0] + "/*"} + self.content_type_is_excluded = not media_types.isdisjoint(self.exclude_content_types) + elif message_type == "http.response.body" and ( + self.content_encoding_set or self.partial_response or self.content_type_is_excluded + ): + if not self.started: + self.started = True + await self.send(self.initial_message) + await self.send(message) + elif message_type == "http.response.body" and not self.started: + self.started = True + body = message.get("body", b"") + more_body = message.get("more_body", False) + if len(body) < self.minimum_size and not more_body: + # Don't apply compression to small outgoing responses. + await self.send(self.initial_message) + await self.send(message) + elif not more_body: + # Standard response. + body = await self.apply_compression(body, more_body=False) + + headers = MutableHeaders(raw=self.initial_message["headers"]) + headers.add_vary_header("Accept-Encoding") + if body != message["body"]: + headers["Content-Encoding"] = self.content_encoding + headers["Content-Length"] = str(len(body)) + message["body"] = body + + await self.send(self.initial_message) + await self.send(message) + else: + # Initial body in streaming response. + body = await self.apply_compression(body, more_body=True) + + headers = MutableHeaders(raw=self.initial_message["headers"]) + headers.add_vary_header("Accept-Encoding") + if body != message["body"]: + headers["Content-Encoding"] = self.content_encoding + del headers["Content-Length"] + message["body"] = body + + await self.send(self.initial_message) + await self.send(message) + elif message_type == "http.response.body": + # Remaining body in streaming response. + body = message.get("body", b"") + more_body = message.get("more_body", False) + + message["body"] = await self.apply_compression(body, more_body=more_body) + + await self.send(message) + elif message_type == "http.response.pathsend": # pragma: no branch + # Don't apply GZip to pathsend responses + await self.send(self.initial_message) + await self.send(message) + + async def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: + """Apply compression on the response body. + + If more_body is False, the compression stream is finalized. Compression + resources are only allocated once a body is actually compressed. + """ + return body + + +class GZipResponder(IdentityResponder): + content_encoding = "gzip" + + def __init__( + self, + app: ASGIApp, + minimum_size: int, + compresslevel: int = 9, + *, + thread_minimum_size: int = 128 * 1024, # 128 KiB + exclude_content_types: tuple[str, ...] = DEFAULT_EXCLUDED_CONTENT_TYPES, + ) -> None: + super().__init__(app, minimum_size, exclude_content_types=exclude_content_types) + + self.compresslevel = compresslevel + self.thread_minimum_size = thread_minimum_size + self._compressor: zlib._Compress | None = None + + @property + def compressor(self) -> zlib._Compress: + if self._compressor is None: + self._compressor = zlib.compressobj(self.compresslevel, zlib.DEFLATED, 16 + zlib.MAX_WBITS) + return self._compressor + + async def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: + if len(body) >= self.thread_minimum_size: + # Compressing large chunks inline would block the event loop. + limiter = _get_gzip_capacity_limiter() + return await anyio.to_thread.run_sync(self._compress_body, body, more_body, limiter=limiter) + return self._compress_body(body, more_body) + + def _compress_body(self, body: bytes, more_body: bool) -> bytes: + if more_body: + return self.compressor.compress(body) + self.compressor.flush(zlib.Z_SYNC_FLUSH) + return self.compressor.compress(body) + self.compressor.flush() + + +async def unattached_send(message: Message) -> NoReturn: + raise RuntimeError("send awaitable not set") # pragma: no cover + + +def _normalize_content_types(content_types: tuple[str, ...]) -> tuple[str, ...]: + return tuple(content_type.partition(";")[0].strip().lower() for content_type in content_types) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py new file mode 100644 index 0000000..da40919 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py @@ -0,0 +1,22 @@ +from starlette.datastructures import URL +from starlette.responses import PlainTextResponse, RedirectResponse +from starlette.types import ASGIApp, Receive, Scope, Send + + +class HTTPSRedirectMiddleware: + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] in ("http", "websocket") and scope["scheme"] in ("http", "ws"): + url = URL(scope=scope) + if not url.netloc: + await PlainTextResponse("Invalid host header", status_code=400)(scope, receive, send) + return + redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme] + netloc = url.hostname if url.port in (80, 443) else url.netloc + url = url.replace(scheme=redirect_scheme, netloc=netloc) + response = RedirectResponse(url, status_code=307) + await response(scope, receive, send) + else: + await self.app(scope, receive, send) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py new file mode 100644 index 0000000..666766c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import re +from collections.abc import Sequence + +try: + from opentelemetry import metrics, propagate, trace + from opentelemetry.trace import SpanKind, Status, StatusCode +except ImportError: # pragma: no cover + raise ImportError("The `opentelemetry-api` package is required to use `OpenTelemetryMiddleware`.") from None + +from starlette import __version__ +from starlette.datastructures import URL +from starlette.routing import Mount +from starlette.types import ASGIApp, Message, Receive, Scope, Send + + +class OpenTelemetryMiddleware: + """Create OpenTelemetry server spans for incoming HTTP requests. + + This middleware is experimental. Its API and emitted telemetry may change in minor + releases without a deprecation period. + + Args: + app: The ASGI application to wrap. + excluded_urls: Regular expressions matched against the full request URL. + Pass a comma-separated string or a sequence. By default, exclude no URLs. + tracer_provider: Optional tracer provider. If omitted, use the global tracer provider. + meter_provider: Optional meter provider. If omitted, use the global meter provider. + """ + + def __init__( + self, + app: ASGIApp, + *, + excluded_urls: str | Sequence[str] = (), + tracer_provider: trace.TracerProvider | None = None, + meter_provider: metrics.MeterProvider | None = None, + ) -> None: + self.app = app + self._meter_provider = meter_provider + if isinstance(excluded_urls, str): + excluded_urls = [pattern.strip() for pattern in excluded_urls.split(",")] if excluded_urls else () + self._excluded_urls = tuple(re.compile(pattern) for pattern in excluded_urls) + self._tracer_provider = tracer_provider or trace.get_tracer_provider() + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http" or scope.get("starlette.opentelemetry"): + return await self.app(scope, receive, send) + + scope["starlette.opentelemetry"] = True + try: + tracer_provider = self._tracer_provider + if isinstance(tracer_provider, trace.NoOpTracerProvider): + return await self.app(scope, receive, send) + + url = URL(scope=scope) + if any(pattern.search(str(url)) for pattern in self._excluded_urls): + return await self.app(scope, receive, send) + + original_method = scope.get("method", "") + method = original_method.upper() + + headers: dict[str, list[str]] = {} + for name, value in scope.get("headers", []): + headers.setdefault(name.decode("latin-1").lower(), []).append(value.decode("latin-1")) + + attributes: dict[str, str | int] = { + "http.request.method": method, + "url.path": scope.get("path", ""), + "url.scheme": scope.get("scheme", "http"), + } + if original_method != method: + attributes["http.request.method_original"] = original_method + if url.query: + attributes["url.query"] = url.query + if url.hostname is not None: + attributes["server.address"] = url.hostname + server = scope.get("server") + server_port = url.port if url.port is not None else server[1] if server is not None else None + if server_port is not None: + attributes["server.port"] = server_port + if scope.get("http_version"): + attributes["network.protocol.version"] = scope["http_version"] + if scope.get("client") is not None: + attributes["client.address"] = scope["client"][0] + if headers.get("user-agent"): + attributes["user_agent.original"] = headers["user-agent"][0] + + with tracer_provider.get_tracer("starlette", __version__).start_as_current_span( + method, + context=propagate.extract(headers), + kind=SpanKind.SERVER, + attributes=attributes, + ) as span: + + async def send_with_telemetry(message: Message) -> None: + if message["type"] == "http.response.start": + status_code = message["status"] + span.set_attribute("http.response.status_code", status_code) + if status_code >= 500: + span.set_attribute("error.type", str(status_code)) + span.set_status(Status(StatusCode.ERROR)) + await send(message) + + try: + await self.app(scope, receive, send_with_telemetry) + except Exception as exc: + span.set_attribute("error.type", type(exc).__qualname__) + raise + finally: + route = scope.get("route") + if isinstance(route, Mount): + route_path = scope.get("root_path") or "/" + else: + path_format = getattr(route, "path_format", None) + route_path = ( + scope.get("root_path", "").rstrip("/") + path_format or "/" + if isinstance(path_format, str) + else None + ) + if route_path is not None: + span.update_name(f"{method} {route_path}") + span.set_attribute("http.route", route_path) + finally: + del scope["starlette.opentelemetry"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py new file mode 100644 index 0000000..efe5e9e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import json +import typing +from base64 import b64decode, b64encode +from typing import Literal + +import itsdangerous +from itsdangerous.exc import BadSignature + +from starlette.datastructures import MutableHeaders, Secret +from starlette.requests import HTTPConnection +from starlette.types import ASGIApp, Message, Receive, Scope, Send + + +class SessionMiddleware: + def __init__( + self, + app: ASGIApp, + secret_key: str | Secret, + session_cookie: str = "session", + max_age: int | None = 14 * 24 * 60 * 60, # 14 days, in seconds + path: str = "/", + same_site: Literal["lax", "strict", "none"] = "lax", + https_only: bool = False, + domain: str | None = None, + partitioned: bool = False, + ) -> None: + self.app = app + self.signer = itsdangerous.TimestampSigner(str(secret_key)) + self.session_cookie = session_cookie + self.max_age = max_age + self.path = path + self.security_flags = "httponly; samesite=" + same_site + if https_only: # Secure flag can be used with HTTPS only + self.security_flags += "; secure" + if domain is not None: + self.security_flags += f"; domain={domain}" + if partitioned: + self.security_flags += "; partitioned" + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] not in ("http", "websocket"): # pragma: no cover + await self.app(scope, receive, send) + return + + connection = HTTPConnection(scope) + initial_session_was_empty = True + + if self.session_cookie in connection.cookies: + data = connection.cookies[self.session_cookie].encode("utf-8") + try: + data = self.signer.unsign(data, max_age=self.max_age) + scope["session"] = Session(json.loads(b64decode(data))) + initial_session_was_empty = False + except BadSignature: + scope["session"] = Session() + else: + scope["session"] = Session() + + async def send_wrapper(message: Message) -> None: + if message["type"] == "http.response.start": + session: Session = scope["session"] + headers = MutableHeaders(scope=message) + if session.accessed: + headers.add_vary_header("Cookie") + if session.modified and session: + # We have session data to persist. + data = b64encode(json.dumps(session).encode("utf-8")) + data = self.signer.sign(data) + header_value = "{session_cookie}={data}; path={path}; {max_age}{security_flags}".format( + session_cookie=self.session_cookie, + data=data.decode("utf-8"), + path=self.path, + max_age=f"Max-Age={self.max_age}; " if self.max_age else "", + security_flags=self.security_flags, + ) + headers.append("Set-Cookie", header_value) + elif session.modified and not initial_session_was_empty: + # The session has been cleared. + header_value = "{session_cookie}={data}; path={path}; {expires}{security_flags}".format( + session_cookie=self.session_cookie, + data="null", + path=self.path, + expires="expires=Thu, 01 Jan 1970 00:00:00 GMT; ", + security_flags=self.security_flags, + ) + headers.append("Set-Cookie", header_value) + await send(message) + + await self.app(scope, receive, send_wrapper) + + +class Session(dict[str, typing.Any]): + accessed: bool = False + modified: bool = False + + def mark_accessed(self) -> None: + self.accessed = True + + def mark_modified(self) -> None: + self.accessed = True + self.modified = True + + def __setitem__(self, key: str, value: typing.Any) -> None: + self.mark_modified() + super().__setitem__(key, value) + + def __delitem__(self, key: str) -> None: + self.mark_modified() + super().__delitem__(key) + + def clear(self) -> None: + self.mark_modified() + super().clear() + + def pop(self, key: str, *args: typing.Any) -> typing.Any: + self.modified = self.modified or key in self + return super().pop(key, *args) + + def popitem(self) -> tuple[str, typing.Any]: + item = super().popitem() + self.mark_modified() + return item + + def setdefault(self, key: str, default: typing.Any = None) -> typing.Any: + if key not in self: + self.mark_modified() + return super().setdefault(key, default) + + def update(self, *args: typing.Any, **kwargs: typing.Any) -> None: + self.mark_modified() + super().update(*args, **kwargs) + + def __ior__(self, other: typing.Any, /) -> Session: # type: ignore[override,misc] + self.mark_modified() + return super().__ior__(other) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py new file mode 100644 index 0000000..c57aa9a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from collections.abc import Sequence + +from starlette._utils import parse_host_header +from starlette.datastructures import URL, Headers +from starlette.responses import PlainTextResponse, RedirectResponse, Response +from starlette.types import ASGIApp, Receive, Scope, Send + +ENFORCE_DOMAIN_WILDCARD = "Domain wildcard patterns must be like '*.example.com'." + + +class TrustedHostMiddleware: + def __init__( + self, + app: ASGIApp, + allowed_hosts: Sequence[str] | None = None, + www_redirect: bool = True, + ) -> None: + if allowed_hosts is None: + allowed_hosts = ["*"] + + for pattern in allowed_hosts: + assert "*" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD + if pattern.startswith("*") and pattern != "*": + assert pattern.startswith("*."), ENFORCE_DOMAIN_WILDCARD + self.app = app + self.allowed_hosts = list(allowed_hosts) + self.allow_any = "*" in allowed_hosts + self.www_redirect = www_redirect + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if self.allow_any or scope["type"] not in ( + "http", + "websocket", + ): # pragma: no cover + await self.app(scope, receive, send) + return + + headers = Headers(scope=scope) + parsed_host = parse_host_header(headers.get("host")) + if parsed_host is None: + await PlainTextResponse("Invalid host header", status_code=400)(scope, receive, send) + return + host = parsed_host.host + + is_valid_host = False + found_www_redirect = False + for pattern in self.allowed_hosts: + if host == pattern or (pattern.startswith("*") and host.endswith(pattern[1:])): + is_valid_host = True + break + elif "www." + host == pattern: + found_www_redirect = True + + if is_valid_host: + await self.app(scope, receive, send) + else: + response: Response + if found_www_redirect and self.www_redirect: + url = URL(scope=scope) + redirect_url = url.replace(netloc="www." + url.netloc) + response = RedirectResponse(url=str(redirect_url)) + else: + response = PlainTextResponse("Invalid host header", status_code=400) + await response(scope, receive, send) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py new file mode 100644 index 0000000..0871926 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +import io +import math +import sys +import warnings +from collections.abc import Callable, MutableMapping +from typing import Any + +import anyio.from_thread +import anyio.to_thread +from anyio.abc import ObjectReceiveStream, ObjectSendStream + +from starlette._utils import create_collapsing_task_group +from starlette.exceptions import StarletteDeprecationWarning +from starlette.types import Receive, Scope, Send + +warnings.warn( + "starlette.middleware.wsgi is deprecated and will be removed in a future release. " + "Please refer to https://github.com/abersheeran/a2wsgi as a replacement.", + StarletteDeprecationWarning, + stacklevel=2, +) + + +def build_environ(scope: Scope, body: bytes) -> dict[str, Any]: + """ + Builds a scope and request body into a WSGI environ object. + """ + + script_name = scope.get("root_path", "").encode("utf8").decode("latin1") + path_info = scope["path"].encode("utf8").decode("latin1") + if path_info.startswith(script_name): + path_info = path_info[len(script_name) :] + + environ = { + "REQUEST_METHOD": scope["method"], + "SCRIPT_NAME": script_name, + "PATH_INFO": path_info, + "QUERY_STRING": scope["query_string"].decode("ascii"), + "SERVER_PROTOCOL": f"HTTP/{scope['http_version']}", + "wsgi.version": (1, 0), + "wsgi.url_scheme": scope.get("scheme", "http"), + "wsgi.input": io.BytesIO(body), + "wsgi.errors": sys.stdout, + "wsgi.multithread": True, + "wsgi.multiprocess": True, + "wsgi.run_once": False, + } + + # Get server name and port - required in WSGI, not in ASGI + server = scope.get("server") or ("localhost", 80) + environ["SERVER_NAME"] = server[0] + environ["SERVER_PORT"] = server[1] + + # Get client IP address + if scope.get("client"): + environ["REMOTE_ADDR"] = scope["client"][0] + + # Go through headers and make them into environ entries + for name, value in scope.get("headers", []): + name = name.decode("latin1") + if name == "content-length": + corrected_name = "CONTENT_LENGTH" + elif name == "content-type": + corrected_name = "CONTENT_TYPE" + else: + corrected_name = f"HTTP_{name}".upper().replace("-", "_") + # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in + # case + value = value.decode("latin1") + if corrected_name in environ: + value = environ[corrected_name] + "," + value + environ[corrected_name] = value + return environ + + +class WSGIMiddleware: + def __init__(self, app: Callable[..., Any]) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + assert scope["type"] == "http" + responder = WSGIResponder(self.app, scope) + await responder(receive, send) + + +class WSGIResponder: + stream_send: ObjectSendStream[MutableMapping[str, Any]] + stream_receive: ObjectReceiveStream[MutableMapping[str, Any]] + + def __init__(self, app: Callable[..., Any], scope: Scope) -> None: + self.app = app + self.scope = scope + self.status = None + self.response_headers = None + self.stream_send, self.stream_receive = anyio.create_memory_object_stream(math.inf) + self.response_started = False + self.exc_info: Any = None + + async def __call__(self, receive: Receive, send: Send) -> None: + body = b"" + more_body = True + while more_body: + message = await receive() + body += message.get("body", b"") + more_body = message.get("more_body", False) + environ = build_environ(self.scope, body) + + async with create_collapsing_task_group() as task_group: + task_group.start_soon(self.sender, send) + async with self.stream_send: + await anyio.to_thread.run_sync(self.wsgi, environ, self.start_response) + if self.exc_info is not None: + raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2]) + + async def sender(self, send: Send) -> None: + async with self.stream_receive: + async for message in self.stream_receive: + await send(message) + + def start_response( + self, + status: str, + response_headers: list[tuple[str, str]], + exc_info: Any = None, + ) -> None: + self.exc_info = exc_info + if not self.response_started: # pragma: no branch + self.response_started = True + status_code_string, _ = status.split(" ", 1) + status_code = int(status_code_string) + headers = [ + (name.strip().encode("ascii").lower(), value.strip().encode("ascii")) + for name, value in response_headers + ] + anyio.from_thread.run( + self.stream_send.send, + { + "type": "http.response.start", + "status": status_code, + "headers": headers, + }, + ) + + def wsgi( + self, + environ: dict[str, Any], + start_response: Callable[..., Any], + ) -> None: + for chunk in self.app(environ, start_response): + anyio.from_thread.run( + self.stream_send.send, + {"type": "http.response.body", "body": chunk, "more_body": True}, + ) + + anyio.from_thread.run(self.stream_send.send, {"type": "http.response.body", "body": b""}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py new file mode 100644 index 0000000..d4f3f4b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/requests.py @@ -0,0 +1,348 @@ +from __future__ import annotations + +import json +import sys +from collections.abc import AsyncGenerator, Iterator, Mapping +from http import cookies as http_cookies +from typing import TYPE_CHECKING, Any, Generic, NoReturn, cast + +import anyio + +from starlette._utils import AwaitableOrContextManager, AwaitableOrContextManagerWrapper +from starlette.datastructures import URL, Address, FormData, Headers, QueryParams, State +from starlette.exceptions import HTTPException +from starlette.formparsers import FormParser, MultiPartException, MultiPartParser +from starlette.types import Message, Receive, Scope, Send + +if TYPE_CHECKING: + from python_multipart.multipart import parse_options_header + + from starlette.applications import Starlette + from starlette.middleware.sessions import Session + from starlette.routing import Router +else: + try: + try: + from python_multipart.multipart import parse_options_header + except ModuleNotFoundError: # pragma: no cover + from multipart.multipart import parse_options_header + except ModuleNotFoundError: # pragma: no cover + parse_options_header = None + +if sys.version_info >= (3, 13): # pragma: no cover + from typing import TypeVar +else: # pragma: no cover + from typing_extensions import TypeVar + +SERVER_PUSH_HEADERS_TO_COPY = { + "accept", + "accept-encoding", + "accept-language", + "cache-control", + "user-agent", +} + + +def cookie_parser(cookie_string: str) -> dict[str, str]: + """ + This function parses a ``Cookie`` HTTP header into a dict of key/value pairs. + + It attempts to mimic browser cookie parsing behavior: browsers and web servers + frequently disregard the spec (RFC 6265) when setting and reading cookies, + so we attempt to suit the common scenarios here. + + This function has been adapted from Django 3.1.0. + Note: we are explicitly _NOT_ using `SimpleCookie.load` because it is based + on an outdated spec and will fail on lots of input we want to support + """ + cookie_dict: dict[str, str] = {} + for chunk in cookie_string.split(";"): + if "=" in chunk: + key, val = chunk.split("=", 1) + else: + # Assume an empty name per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + key, val = "", chunk + key, val = key.strip(), val.strip() + if key or val: + # unquote using Python's algorithm. + cookie_dict[key] = http_cookies._unquote(val) + return cookie_dict + + +class ClientDisconnect(Exception): + pass + + +StateT = TypeVar("StateT", bound=Mapping[str, Any] | State, default=State) + + +class HTTPConnection(Mapping[str, Any], Generic[StateT]): + """ + A base class for incoming HTTP connections, that is used to provide + any functionality that is common to both `Request` and `WebSocket`. + """ + + def __init__(self, scope: Scope, receive: Receive | None = None) -> None: + assert scope["type"] in ("http", "websocket") + self.scope = scope + + def __getitem__(self, key: str) -> Any: + return self.scope[key] + + def __iter__(self) -> Iterator[str]: + return iter(self.scope) + + def __len__(self) -> int: + return len(self.scope) + + # Don't use the `abc.Mapping.__eq__` implementation. + # Connection instances should never be considered equal + # unless `self is other`. + __eq__ = object.__eq__ + __hash__ = object.__hash__ + + @property + def app(self) -> Any: + return self.scope["app"] + + @property + def url(self) -> URL: + if not hasattr(self, "_url"): # pragma: no branch + self._url = URL(scope=self.scope) + return self._url + + @property + def base_url(self) -> URL: + if not hasattr(self, "_base_url"): + base_url_scope = dict(self.scope) + # This is used by request.url_for, it might be used inside a Mount which + # would have its own child scope with its own root_path, but the base URL + # for url_for should still be the top level app root path. + app_root_path = base_url_scope.get("app_root_path", base_url_scope.get("root_path", "")) + path = app_root_path + if not path.endswith("/"): + path += "/" + base_url_scope["path"] = path + base_url_scope["query_string"] = b"" + base_url_scope["root_path"] = app_root_path + self._base_url = URL(scope=base_url_scope) + return self._base_url + + @property + def headers(self) -> Headers: + if not hasattr(self, "_headers"): + self._headers = Headers(scope=self.scope) + return self._headers + + @property + def query_params(self) -> QueryParams: + if not hasattr(self, "_query_params"): # pragma: no branch + self._query_params = QueryParams(self.scope["query_string"]) + return self._query_params + + @property + def path_params(self) -> dict[str, Any]: + path_params: dict[str, Any] = self.scope.get("path_params", {}) + return path_params + + @property + def cookies(self) -> dict[str, str]: + if not hasattr(self, "_cookies"): + cookies: dict[str, str] = {} + cookie_headers = self.headers.getlist("cookie") + + for header in cookie_headers: + cookies.update(cookie_parser(header)) + + self._cookies = cookies + return self._cookies + + @property + def client(self) -> Address | None: + # client is a 2 item tuple of (host, port), None if missing + host_port = self.scope.get("client") + if host_port is not None: + return Address(*host_port) + return None + + @property + def session(self) -> dict[str, Any]: + assert "session" in self.scope, "SessionMiddleware must be installed to access request.session" + session: Session = self.scope["session"] + # We keep the hasattr in case people actually use their own `SessionMiddleware` implementation. + if hasattr(session, "mark_accessed"): # pragma: no branch + session.mark_accessed() + return session + + @property + def auth(self) -> Any: + assert "auth" in self.scope, "AuthenticationMiddleware must be installed to access request.auth" + return self.scope["auth"] + + @property + def user(self) -> Any: + assert "user" in self.scope, "AuthenticationMiddleware must be installed to access request.user" + return self.scope["user"] + + @property + def state(self) -> StateT: + if not hasattr(self, "_state"): + # Ensure 'state' has an empty dict if it's not already populated. + self.scope.setdefault("state", {}) + # Create a state instance with a reference to the dict in which it should + # store info + self._state = State(self.scope["state"]) + return cast(StateT, self._state) + + def url_for(self, name: str, /, **path_params: Any) -> URL: + url_path_provider: Router | Starlette | None = self.scope.get("router") or self.scope.get("app") + if url_path_provider is None: + raise RuntimeError("The `url_for` method can only be used inside a Starlette application or with a router.") + url_path = url_path_provider.url_path_for(name, **path_params) + return url_path.make_absolute_url(base_url=self.base_url) + + +async def empty_receive() -> NoReturn: + raise RuntimeError("Receive channel has not been made available") + + +async def empty_send(message: Message) -> NoReturn: + raise RuntimeError("Send channel has not been made available") + + +class Request(HTTPConnection[StateT]): + _form: FormData | None + + def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send): + super().__init__(scope) + assert scope["type"] == "http" + self._receive = receive + self._send = send + self._stream_consumed = False + self._is_disconnected = False + self._form = None + + @property + def method(self) -> str: + return cast(str, self.scope["method"]) + + @property + def receive(self) -> Receive: + return self._receive + + async def stream(self) -> AsyncGenerator[bytes, None]: + if hasattr(self, "_body"): + yield self._body + yield b"" + return + if self._stream_consumed: + raise RuntimeError("Stream consumed") + while not self._stream_consumed: + message = await self._receive() + if message["type"] == "http.request": + body = message.get("body", b"") + if not message.get("more_body", False): + self._stream_consumed = True + if body: + yield body + elif message["type"] == "http.disconnect": # pragma: no branch + self._is_disconnected = True + raise ClientDisconnect() + yield b"" + + async def body(self) -> bytes: + if not hasattr(self, "_body"): + chunks: list[bytes] = [] + async for chunk in self.stream(): + chunks.append(chunk) + self._body = b"".join(chunks) + return self._body + + async def json(self) -> Any: + if not hasattr(self, "_json"): # pragma: no branch + body = await self.body() + self._json = json.loads(body) + return self._json + + async def _get_form( + self, + *, + max_files: int | float = 1000, + max_fields: int | float = 1000, + max_part_size: int = 1024 * 1024, + ) -> FormData: + if self._form is None: # pragma: no branch + assert parse_options_header is not None, ( + "The `python-multipart` library must be installed to use form parsing." + ) + content_type_header = self.headers.get("Content-Type") + content_type: bytes + content_type, _ = parse_options_header(content_type_header) + if content_type == b"multipart/form-data": + try: + multipart_parser = MultiPartParser( + self.headers, + self.stream(), + max_files=max_files, + max_fields=max_fields, + max_part_size=max_part_size, + ) + self._form = await multipart_parser.parse() + except MultiPartException as exc: + if "app" in self.scope: + raise HTTPException(status_code=400, detail=exc.message) + raise exc + elif content_type == b"application/x-www-form-urlencoded": + try: + form_parser = FormParser( + self.headers, + self.stream(), + max_fields=max_fields, + max_part_size=max_part_size, + ) + self._form = await form_parser.parse() + except MultiPartException as exc: + if "app" in self.scope: + raise HTTPException(status_code=400, detail=exc.message) + raise exc + else: + self._form = FormData() + return self._form + + def form( + self, + *, + max_files: int | float = 1000, + max_fields: int | float = 1000, + max_part_size: int = 1024 * 1024, + ) -> AwaitableOrContextManager[FormData]: + return AwaitableOrContextManagerWrapper( + self._get_form(max_files=max_files, max_fields=max_fields, max_part_size=max_part_size) + ) + + async def close(self) -> None: + if self._form is not None: # pragma: no branch + await self._form.close() + + async def is_disconnected(self) -> bool: + if not self._is_disconnected: + message: Message = {} + + # If message isn't immediately available, move on + with anyio.CancelScope() as cs: + cs.cancel() + message = await self._receive() + + if message.get("type") == "http.disconnect": + self._is_disconnected = True + + return self._is_disconnected + + async def send_push_promise(self, path: str) -> None: + if "http.response.push" in self.scope.get("extensions", {}): + raw_headers: list[tuple[bytes, bytes]] = [] + for name in SERVER_PUSH_HEADERS_TO_COPY: + for value in self.headers.getlist(name): + raw_headers.append((name.encode("latin-1"), value.encode("latin-1"))) + await self._send({"type": "http.response.push", "path": path, "headers": raw_headers}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py new file mode 100644 index 0000000..bff2467 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/responses.py @@ -0,0 +1,579 @@ +from __future__ import annotations + +import hashlib +import http.cookies +import json +import os +import stat +import sys +from collections.abc import AsyncIterable, Awaitable, Callable, Iterable, Mapping, Sequence +from datetime import datetime +from email.utils import format_datetime, formatdate +from functools import partial +from mimetypes import guess_type +from secrets import token_hex +from typing import Any, Literal +from urllib.parse import quote + +import anyio +import anyio.to_thread + +from starlette._utils import create_collapsing_task_group +from starlette.background import BackgroundTask +from starlette.concurrency import iterate_in_threadpool +from starlette.datastructures import URL, Headers, MutableHeaders +from starlette.requests import ClientDisconnect +from starlette.types import Message, Receive, Scope, Send + + +class Response: + media_type = None + charset = "utf-8" + + def __init__( + self, + content: Any = None, + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + ) -> None: + self.status_code = status_code + if media_type is not None: + self.media_type = media_type + self.background = background + self.body = self.render(content) + self.init_headers(headers) + + def render(self, content: Any) -> bytes | memoryview: + if content is None: + return b"" + if isinstance(content, bytes | memoryview): + return content + return content.encode(self.charset) # type: ignore + + def init_headers(self, headers: Mapping[str, str] | None = None) -> None: + if headers is None: + raw_headers: list[tuple[bytes, bytes]] = [] + populate_content_length = True + populate_content_type = True + else: + raw_headers = [(k.lower().encode("latin-1"), v.encode("latin-1")) for k, v in headers.items()] + keys = [h[0] for h in raw_headers] + populate_content_length = b"content-length" not in keys + populate_content_type = b"content-type" not in keys + + body = getattr(self, "body", None) + if ( + body is not None + and populate_content_length + and not (self.status_code < 200 or self.status_code in (204, 304)) + ): + content_length = str(len(body)) + raw_headers.append((b"content-length", content_length.encode("latin-1"))) + + content_type = self.media_type + if content_type is not None and populate_content_type: + if content_type.startswith("text/") and "charset=" not in content_type.lower(): + content_type += "; charset=" + self.charset + raw_headers.append((b"content-type", content_type.encode("latin-1"))) + + self.raw_headers = raw_headers + + @property + def headers(self) -> MutableHeaders: + if not hasattr(self, "_headers"): + self._headers = MutableHeaders(raw=self.raw_headers) + return self._headers + + def set_cookie( + self, + key: str, + value: str = "", + max_age: int | None = None, + expires: datetime | str | int | None = None, + path: str | None = "/", + domain: str | None = None, + secure: bool = False, + httponly: bool = False, + samesite: Literal["lax", "strict", "none"] | None = "lax", + partitioned: bool = False, + ) -> None: + cookie: http.cookies.BaseCookie[str] = http.cookies.SimpleCookie() + cookie[key] = value + if max_age is not None: + cookie[key]["max-age"] = max_age + if expires is not None: + if isinstance(expires, datetime): + cookie[key]["expires"] = format_datetime(expires, usegmt=True) + else: + cookie[key]["expires"] = expires + if path is not None: + cookie[key]["path"] = path + if domain is not None: + cookie[key]["domain"] = domain + if secure: + cookie[key]["secure"] = True + if httponly: + cookie[key]["httponly"] = True + if samesite is not None: + assert samesite.lower() in [ + "strict", + "lax", + "none", + ], "samesite must be either 'strict', 'lax' or 'none'" + cookie[key]["samesite"] = samesite + if partitioned: + if sys.version_info < (3, 14): + raise ValueError("Partitioned cookies are only supported in Python 3.14 and above.") # pragma: no cover + cookie[key]["partitioned"] = True # pragma: no cover + + cookie_val = cookie.output(header="").strip() + self.raw_headers.append((b"set-cookie", cookie_val.encode("latin-1"))) + + def delete_cookie( + self, + key: str, + path: str = "/", + domain: str | None = None, + secure: bool = False, + httponly: bool = False, + samesite: Literal["lax", "strict", "none"] | None = "lax", + partitioned: bool = False, + ) -> None: + self.set_cookie( + key, + max_age=0, + expires=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + partitioned=partitioned, + ) + + def _wrap_websocket_denial_send(self, send: Send) -> Send: + async def wrapped(message: Message) -> None: + message_type = message["type"] + if message_type in {"http.response.start", "http.response.body"}: # pragma: no branch + message = {**message, "type": "websocket." + message_type} + await send(message) + + return wrapped + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] == "websocket": + send = self._wrap_websocket_denial_send(send) + await send({"type": "http.response.start", "status": self.status_code, "headers": self.raw_headers}) + await send({"type": "http.response.body", "body": self.body}) + + if self.background is not None: + await self.background() + + +class HTMLResponse(Response): + media_type = "text/html" + + +class PlainTextResponse(Response): + media_type = "text/plain" + + +class JSONResponse(Response): + media_type = "application/json" + + def __init__( + self, + content: Any, + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + ) -> None: + super().__init__(content, status_code, headers, media_type, background) + + def render(self, content: Any) -> bytes: + return json.dumps( + content, + ensure_ascii=False, + allow_nan=False, + indent=None, + separators=(",", ":"), + ).encode("utf-8") + + +class RedirectResponse(Response): + def __init__( + self, + url: str | URL, + status_code: int = 307, + headers: Mapping[str, str] | None = None, + background: BackgroundTask | None = None, + ) -> None: + super().__init__(content=b"", status_code=status_code, headers=headers, background=background) + self.headers["location"] = quote(str(url), safe=":/%#?=@[]!$&'()*+,;") + + +Content = str | bytes | memoryview +SyncContentStream = Iterable[Content] +AsyncContentStream = AsyncIterable[Content] +ContentStream = AsyncContentStream | SyncContentStream + + +class StreamingResponse(Response): + body_iterator: AsyncContentStream + + def __init__( + self, + content: ContentStream, + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + ) -> None: + if isinstance(content, AsyncIterable): + self.body_iterator = content + else: + self.body_iterator = iterate_in_threadpool(content) + self.status_code = status_code + self.media_type = self.media_type if media_type is None else media_type + self.background = background + self.init_headers(headers) + + async def listen_for_disconnect(self, receive: Receive) -> None: + while True: + message = await receive() + if message["type"] == "http.disconnect": + break + + async def stream_response(self, send: Send) -> None: + await send({"type": "http.response.start", "status": self.status_code, "headers": self.raw_headers}) + async for chunk in self.body_iterator: + if not isinstance(chunk, bytes | memoryview): + chunk = chunk.encode(self.charset) + await send({"type": "http.response.body", "body": chunk, "more_body": True}) + + await send({"type": "http.response.body", "body": b"", "more_body": False}) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] == "websocket": + send = self._wrap_websocket_denial_send(send) + await self.stream_response(send) + if self.background is not None: + await self.background() + return + + spec_version = tuple(map(int, scope.get("asgi", {}).get("spec_version", "2.0").split("."))) + + if spec_version >= (2, 4): + try: + await self.stream_response(send) + except OSError: + raise ClientDisconnect() + else: + async with create_collapsing_task_group() as task_group: + + async def wrap(func: Callable[[], Awaitable[None]]) -> None: + await func() + task_group.cancel_scope.cancel() + + task_group.start_soon(wrap, partial(self.stream_response, send)) + await wrap(partial(self.listen_for_disconnect, receive)) + + if self.background is not None: + await self.background() + + +class MalformedRangeHeader(Exception): + def __init__(self, content: str = "Malformed range header.") -> None: + self.content = content + + +class RangeNotSatisfiable(Exception): + def __init__(self, max_size: int) -> None: + self.max_size = max_size + + +class FileResponse(Response): + chunk_size = 64 * 1024 + max_ranges = 100 + + def __init__( + self, + path: str | os.PathLike[str], + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + filename: str | None = None, + stat_result: os.stat_result | None = None, + content_disposition_type: str = "attachment", + ) -> None: + self.path = path + self.status_code = status_code + self.filename = filename + if media_type is None: + media_type = guess_type(filename or path)[0] or "application/octet-stream" + self.media_type = media_type + self.background = background + self.init_headers(headers) + self.headers.setdefault("accept-ranges", "bytes") + if self.filename is not None: + content_disposition_filename = quote(self.filename) + if content_disposition_filename != self.filename: + content_disposition = f"{content_disposition_type}; filename*=utf-8''{content_disposition_filename}" + else: + content_disposition = f'{content_disposition_type}; filename="{self.filename}"' + self.headers.setdefault("content-disposition", content_disposition) + self.stat_result = stat_result + if stat_result is not None: + self.set_stat_headers(stat_result) + + def set_stat_headers(self, stat_result: os.stat_result) -> None: + content_length = str(stat_result.st_size) + last_modified = formatdate(stat_result.st_mtime, usegmt=True) + etag_base = str(stat_result.st_mtime) + "-" + str(stat_result.st_size) + etag = f'"{hashlib.md5(etag_base.encode(), usedforsecurity=False).hexdigest()}"' + + self.headers.setdefault("content-length", content_length) + self.headers.setdefault("last-modified", last_modified) + self.headers.setdefault("etag", etag) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + scope_type = scope["type"] + send_header_only = scope_type == "http" and scope["method"].upper() == "HEAD" + send_pathsend = scope_type == "http" and "http.response.pathsend" in scope.get("extensions", {}) + if scope_type == "websocket": + send = self._wrap_websocket_denial_send(send) + + if self.stat_result is None: + try: + stat_result = await anyio.to_thread.run_sync(os.stat, self.path) + self.set_stat_headers(stat_result) + except FileNotFoundError: + raise RuntimeError(f"File at path {self.path} does not exist.") + else: + mode = stat_result.st_mode + if not stat.S_ISREG(mode): + raise RuntimeError(f"File at path {self.path} is not a file.") + else: + stat_result = self.stat_result + + headers = Headers(scope=scope) + http_range = headers.get("range") + http_if_range = headers.get("if-range") + + if ( + self.status_code != 200 + or http_range is None + or (http_if_range is not None and not self._should_use_range(http_if_range)) + ): + await self._handle_simple(send, send_header_only, send_pathsend) + else: + try: + ranges = self._parse_range_header(http_range, stat_result.st_size) + except MalformedRangeHeader as exc: + return await PlainTextResponse(exc.content, status_code=400)(scope, receive, send) + except RangeNotSatisfiable as exc: + response = PlainTextResponse(status_code=416, headers={"Content-Range": f"bytes */{exc.max_size}"}) + return await response(scope, receive, send) + + if len(ranges) == 0: + await self._handle_simple(send, send_header_only, send_pathsend) + elif len(ranges) == 1: + start, end = ranges[0] + await self._handle_single_range(send, start, end, stat_result.st_size, send_header_only) + else: + await self._handle_multiple_ranges(send, ranges, stat_result.st_size, send_header_only) + + if self.background is not None: + await self.background() + + async def _handle_simple(self, send: Send, send_header_only: bool, send_pathsend: bool) -> None: + await send({"type": "http.response.start", "status": self.status_code, "headers": self.raw_headers}) + if send_header_only: + await send({"type": "http.response.body", "body": b"", "more_body": False}) + elif send_pathsend: + await send({"type": "http.response.pathsend", "path": str(self.path)}) + else: + async with await anyio.open_file(self.path, mode="rb") as file: + more_body = True + while more_body: + chunk = await file.read(self.chunk_size) + more_body = len(chunk) == self.chunk_size + await send({"type": "http.response.body", "body": chunk, "more_body": more_body}) + + async def _handle_single_range( + self, send: Send, start: int, end: int, file_size: int, send_header_only: bool + ) -> None: + headers = MutableHeaders(raw=list(self.raw_headers)) + headers["content-range"] = f"bytes {start}-{end - 1}/{file_size}" + headers["content-length"] = str(end - start) + await send({"type": "http.response.start", "status": 206, "headers": headers.raw}) + if send_header_only: + await send({"type": "http.response.body", "body": b"", "more_body": False}) + else: + async with await anyio.open_file(self.path, mode="rb") as file: + await file.seek(start) + more_body = True + while more_body: + chunk = await file.read(min(self.chunk_size, end - start)) + start += len(chunk) + more_body = len(chunk) == self.chunk_size and start < end + await send({"type": "http.response.body", "body": chunk, "more_body": more_body}) + + async def _handle_multiple_ranges( + self, + send: Send, + ranges: list[tuple[int, int]], + file_size: int, + send_header_only: bool, + ) -> None: + # In firefox and chrome, they use boundary with 95-96 bits entropy (that's roughly 13 bytes). + boundary = token_hex(13) + content_length, header_generator = self.generate_multipart( + ranges, boundary, file_size, self.headers["content-type"] + ) + headers = MutableHeaders(raw=list(self.raw_headers)) + headers["content-type"] = f"multipart/byteranges; boundary={boundary}" + headers["content-length"] = str(content_length) + await send({"type": "http.response.start", "status": 206, "headers": headers.raw}) + if send_header_only: + await send({"type": "http.response.body", "body": b"", "more_body": False}) + else: + async with await anyio.open_file(self.path, mode="rb") as file: + for start, end in ranges: + await send({"type": "http.response.body", "body": header_generator(start, end), "more_body": True}) + await file.seek(start) + while start < end: + chunk = await file.read(min(self.chunk_size, end - start)) + start += len(chunk) + await send({"type": "http.response.body", "body": chunk, "more_body": True}) + await send({"type": "http.response.body", "body": b"\r\n", "more_body": True}) + await send( + { + "type": "http.response.body", + "body": f"--{boundary}--".encode("latin-1"), + "more_body": False, + } + ) + + def _should_use_range(self, http_if_range: str) -> bool: + return http_if_range == self.headers["last-modified"] or http_if_range == self.headers["etag"] + + @classmethod + def _parse_range_header(cls, http_range: str, file_size: int) -> list[tuple[int, int]]: + ranges: list[tuple[int, int]] = [] + try: + units, range_ = http_range.split("=", 1) + except ValueError: + raise MalformedRangeHeader() + + units = units.strip().lower() + + if units != "bytes": + raise MalformedRangeHeader("Only support bytes range") + + if range_.count(",") + 1 > cls.max_ranges: + return [] + + ranges = cls._parse_ranges(range_, file_size) + + if len(ranges) == 0: + raise MalformedRangeHeader("Range header: range must be requested") + + if any(not (0 <= start < file_size) for start, _ in ranges): + raise RangeNotSatisfiable(file_size) + + if any(start >= end for start, end in ranges): + raise MalformedRangeHeader("Range header: start must be less than end") + + if len(ranges) == 1: + return ranges + + # Merge overlapping ranges + ranges.sort() + result: list[tuple[int, int]] = [ranges[0]] + for start, end in ranges[1:]: + last_start, last_end = result[-1] + if start <= last_end: + result[-1] = (last_start, max(last_end, end)) + else: + result.append((start, end)) + + return result + + @classmethod + def _parse_ranges(cls, range_: str, file_size: int) -> list[tuple[int, int]]: + ranges: list[tuple[int, int]] = [] + + for part in range_.split(","): + part = part.strip() + + # If the range is empty or a single dash, we ignore it. + if not part or part == "-": + continue + + # If the range is not in the format "start-end", we ignore it. + if "-" not in part: + continue + + start_str, end_str = part.split("-", 1) + start_str = start_str.strip() + end_str = end_str.strip() + + try: + start = int(start_str) if start_str else max(file_size - int(end_str), 0) + end = int(end_str) + 1 if start_str and end_str and int(end_str) < file_size else file_size + ranges.append((start, end)) + except ValueError: + # If the range is not numeric, we ignore it. + continue + + return ranges + + def generate_multipart( + self, + ranges: Sequence[tuple[int, int]], + boundary: str, + max_size: int, + content_type: str, + ) -> tuple[int, Callable[[int, int], bytes]]: + r""" + Multipart response headers generator. + + ``` + --{boundary}\r\n + Content-Type: {content_type}\r\n + Content-Range: bytes {start}-{end-1}/{max_size}\r\n + \r\n + ..........content...........\r\n + --{boundary}\r\n + Content-Type: {content_type}\r\n + Content-Range: bytes {start}-{end-1}/{max_size}\r\n + \r\n + ..........content...........\r\n + --{boundary}-- + ``` + """ + boundary_len = len(boundary) + static_header_part_len = 49 + boundary_len + len(content_type) + len(str(max_size)) + content_length = sum( + (len(str(start)) + len(str(end - 1)) + static_header_part_len) # Headers + + (end - start) # Content + for start, end in ranges + ) + ( + 4 + boundary_len # --boundary-- + ) + return ( + content_length, + lambda start, end: ( + f"""\ +--{boundary}\r +Content-Type: {content_type}\r +Content-Range: bytes {start}-{end - 1}/{max_size}\r +\r +""" + ).encode("latin-1"), + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py new file mode 100644 index 0000000..9969d38 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/routing.py @@ -0,0 +1,765 @@ +from __future__ import annotations + +import contextlib +import functools +import inspect +import re +import traceback +import types +import warnings +from collections.abc import Awaitable, Callable, Collection, Generator, Sequence +from contextlib import AbstractAsyncContextManager, AbstractContextManager, asynccontextmanager +from enum import Enum +from re import Pattern +from typing import Any, TypeVar + +from starlette._exception_handler import wrap_app_handling_exceptions +from starlette._utils import get_route_path, is_async_callable, parse_host_header +from starlette.concurrency import run_in_threadpool +from starlette.convertors import CONVERTOR_TYPES, Convertor +from starlette.datastructures import URL, Headers, URLPath +from starlette.exceptions import HTTPException, StarletteDeprecationWarning +from starlette.middleware import Middleware +from starlette.middleware.body_limit import RequestBodyLimitMiddleware +from starlette.requests import Request +from starlette.responses import PlainTextResponse, RedirectResponse, Response +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send +from starlette.websockets import WebSocket, WebSocketClose + + +class NoMatchFound(Exception): + """ + Raised by `.url_for(name, **path_params)` and `.url_path_for(name, **path_params)` + if no matching route exists. + """ + + def __init__(self, name: str, path_params: dict[str, Any]) -> None: + params = ", ".join(list(path_params.keys())) + super().__init__(f'No route exists for name "{name}" and params "{params}".') + + +class Match(Enum): + NONE = 0 + PARTIAL = 1 + FULL = 2 + + +def request_response( + func: Callable[[Request], Awaitable[Response] | Response], +) -> ASGIApp: + """ + Takes a function or coroutine `func(request) -> response`, + and returns an ASGI application. + """ + f: Callable[[Request], Awaitable[Response]] = ( + func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type: ignore[assignment, call-arg] + ) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + request = Request(scope, receive, send) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + response = await f(request) + await response(scope, receive, send) + + await wrap_app_handling_exceptions(app, request)(scope, receive, send) + + return app + + +def websocket_session( + func: Callable[[WebSocket], Awaitable[None]], +) -> ASGIApp: + """ + Takes a coroutine `func(session)`, and returns an ASGI application. + """ + # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async" + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + session = WebSocket(scope, receive=receive, send=send) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + await func(session) + + await wrap_app_handling_exceptions(app, session)(scope, receive, send) + + return app + + +def get_name(endpoint: Callable[..., Any]) -> str: + return getattr(endpoint, "__name__", endpoint.__class__.__name__) + + +def replace_params( + path: str, + param_convertors: dict[str, Convertor[Any]], + path_params: dict[str, str], +) -> tuple[str, dict[str, str]]: + for key, value in list(path_params.items()): + if "{" + key + "}" in path: + convertor = param_convertors[key] + value = convertor.to_string(value) + path = path.replace("{" + key + "}", value) + path_params.pop(key) + return path, path_params + + +# Match parameters in URL paths, eg. '{param}', and '{param:int}' +PARAM_REGEX = re.compile("{([a-zA-Z_][a-zA-Z0-9_]*)(:[a-zA-Z_][a-zA-Z0-9_]*)?}") + + +def compile_path( + path: str, +) -> tuple[Pattern[str], str, dict[str, Convertor[Any]]]: + """ + Given a path string, like: "/{username:str}", + or a host string, like: "{subdomain}.mydomain.org", return a three-tuple + of (regex, format, {param_name:convertor}). + + regex: "/(?P[^/]+)" + format: "/{username}" + convertors: {"username": StringConvertor()} + """ + is_host = not path.startswith("/") + + path_regex = "^" + path_format = "" + duplicated_params: set[str] = set() + + idx = 0 + param_convertors = {} + for match in PARAM_REGEX.finditer(path): + param_name, convertor_type = match.groups("str") + convertor_type = convertor_type.lstrip(":") + assert convertor_type in CONVERTOR_TYPES, f"Unknown path convertor '{convertor_type}'" + convertor = CONVERTOR_TYPES[convertor_type] + + path_regex += re.escape(path[idx : match.start()]) + path_regex += f"(?P<{param_name}>{convertor.regex})" + + path_format += path[idx : match.start()] + path_format += "{%s}" % param_name + + if param_name in param_convertors: + duplicated_params.add(param_name) + + param_convertors[param_name] = convertor + + idx = match.end() + + if duplicated_params: + names = ", ".join(sorted(duplicated_params)) + ending = "s" if len(duplicated_params) > 1 else "" + raise ValueError(f"Duplicated param name{ending} {names} at path {path}") + + if is_host: + # Align with `Host.matches()` behavior, which ignores port. + hostname = path[idx:] + if not hostname.endswith("]"): + hostname = hostname.rsplit(":", 1)[0] + path_regex += re.escape(hostname) + "$" + else: + path_regex += re.escape(path[idx:]) + "$" + + path_format += path[idx:] + + return re.compile(path_regex), path_format, param_convertors + + +class BaseRoute: + def matches(self, scope: Scope) -> tuple[Match, Scope]: + raise NotImplementedError() # pragma: no cover + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + raise NotImplementedError() # pragma: no cover + + async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: + raise NotImplementedError() # pragma: no cover + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """ + A route may be used in isolation as a stand-alone ASGI app. + This is a somewhat contrived case, as they'll almost always be used + within a Router, but could be useful for some tooling and minimal apps. + """ + match, child_scope = self.matches(scope) + if match == Match.NONE: + if scope["type"] == "http": + response = PlainTextResponse("Not Found", status_code=404) + await response(scope, receive, send) + elif scope["type"] == "websocket": # pragma: no branch + websocket_close = WebSocketClose() + await websocket_close(scope, receive, send) + return + + scope.update(child_scope) + await self.handle(scope, receive, send) + + +class Route(BaseRoute): + def __init__( + self, + path: str, + endpoint: Callable[..., Any], + *, + methods: Collection[str] | None = None, + name: str | None = None, + include_in_schema: bool = True, + middleware: Sequence[Middleware] | None = None, + max_body_size: int | None = None, + ) -> None: + assert path.startswith("/"), "Routed paths must start with '/'" + self.path = path + self.endpoint = endpoint + self.name = get_name(endpoint) if name is None else name + self.include_in_schema = include_in_schema + + endpoint_handler = endpoint + while isinstance(endpoint_handler, functools.partial): + endpoint_handler = endpoint_handler.func + if inspect.isfunction(endpoint_handler) or inspect.ismethod(endpoint_handler): + # Endpoint is function or method. Treat it as `func(request) -> response`. + self.app = request_response(endpoint) + if methods is None: + methods = ["GET"] + else: + # Endpoint is a class. Treat it as ASGI. + self.app = endpoint + + if middleware is not None: + for cls, args, kwargs in reversed(middleware): + self.app = cls(self.app, *args, **kwargs) + if max_body_size is not None: + self.app = RequestBodyLimitMiddleware(self.app, max_body_size=max_body_size) + + if methods is None: + self.methods = None + else: + self.methods = {method.upper() for method in methods} + if "GET" in self.methods: + self.methods.add("HEAD") + + self.path_regex, self.path_format, self.param_convertors = compile_path(path) + + def matches(self, scope: Scope) -> tuple[Match, Scope]: + path_params: dict[str, Any] + if scope["type"] == "http": + route_path = get_route_path(scope) + match = self.path_regex.match(route_path) + if match: + matched_params = match.groupdict() + for key, value in matched_params.items(): + matched_params[key] = self.param_convertors[key].convert(value) + path_params = dict(scope.get("path_params", {})) + path_params.update(matched_params) + child_scope = {"endpoint": self.endpoint, "path_params": path_params} + if self.methods and scope["method"] not in self.methods: + return Match.PARTIAL, child_scope + else: + return Match.FULL, child_scope + return Match.NONE, {} + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + seen_params = set(path_params.keys()) + expected_params = set(self.param_convertors.keys()) + + if name != self.name or seen_params != expected_params: + raise NoMatchFound(name, path_params) + + path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params) + assert not remaining_params + return URLPath(path=path, protocol="http") + + async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: + if self.methods and scope["method"] not in self.methods: + headers = {"Allow": ", ".join(self.methods)} + if "app" in scope: + raise HTTPException(status_code=405, headers=headers) + else: + response = PlainTextResponse("Method Not Allowed", status_code=405, headers=headers) + await response(scope, receive, send) + else: + await self.app(scope, receive, send) + + def __eq__(self, other: Any) -> bool: + return ( + isinstance(other, Route) + and self.path == other.path + and self.endpoint == other.endpoint + and self.methods == other.methods + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + methods = sorted(self.methods or []) + path, name = self.path, self.name + return f"{class_name}(path={path!r}, name={name!r}, methods={methods!r})" + + +class WebSocketRoute(BaseRoute): + def __init__( + self, + path: str, + endpoint: Callable[..., Any], + *, + name: str | None = None, + middleware: Sequence[Middleware] | None = None, + ) -> None: + assert path.startswith("/"), "Routed paths must start with '/'" + self.path = path + self.endpoint = endpoint + self.name = get_name(endpoint) if name is None else name + + endpoint_handler = endpoint + while isinstance(endpoint_handler, functools.partial): + endpoint_handler = endpoint_handler.func + if inspect.isfunction(endpoint_handler) or inspect.ismethod(endpoint_handler): + # Endpoint is function or method. Treat it as `func(websocket)`. + self.app = websocket_session(endpoint) + else: + # Endpoint is a class. Treat it as ASGI. + self.app = endpoint + + if middleware is not None: + for cls, args, kwargs in reversed(middleware): + self.app = cls(self.app, *args, **kwargs) + + self.path_regex, self.path_format, self.param_convertors = compile_path(path) + + def matches(self, scope: Scope) -> tuple[Match, Scope]: + path_params: dict[str, Any] + if scope["type"] == "websocket": + route_path = get_route_path(scope) + match = self.path_regex.match(route_path) + if match: + matched_params = match.groupdict() + for key, value in matched_params.items(): + matched_params[key] = self.param_convertors[key].convert(value) + path_params = dict(scope.get("path_params", {})) + path_params.update(matched_params) + child_scope = {"endpoint": self.endpoint, "path_params": path_params} + return Match.FULL, child_scope + return Match.NONE, {} + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + seen_params = set(path_params.keys()) + expected_params = set(self.param_convertors.keys()) + + if name != self.name or seen_params != expected_params: + raise NoMatchFound(name, path_params) + + path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params) + assert not remaining_params + return URLPath(path=path, protocol="websocket") + + async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: + await self.app(scope, receive, send) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, WebSocketRoute) and self.path == other.path and self.endpoint == other.endpoint + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(path={self.path!r}, name={self.name!r})" + + +class Mount(BaseRoute): + def __init__( + self, + path: str, + app: ASGIApp | None = None, + routes: Sequence[BaseRoute] | None = None, + name: str | None = None, + *, + middleware: Sequence[Middleware] | None = None, + max_body_size: int | None = None, + ) -> None: + assert path == "" or path.startswith("/"), "Routed paths must start with '/'" + assert app is not None or routes is not None, "Either 'app=...', or 'routes=' must be specified" + self.path = path.rstrip("/") + if app is not None: + self._base_app: ASGIApp = app + else: + self._base_app = Router(routes=routes) + self.app = self._base_app + if middleware is not None: + for cls, args, kwargs in reversed(middleware): + self.app = cls(self.app, *args, **kwargs) + if max_body_size is not None: + self.app = RequestBodyLimitMiddleware(self.app, max_body_size=max_body_size) + self.name = name + self.path_regex, self.path_format, self.param_convertors = compile_path(self.path + "/{path:path}") + + @property + def routes(self) -> list[BaseRoute]: + return getattr(self._base_app, "routes", []) + + def matches(self, scope: Scope) -> tuple[Match, Scope]: + path_params: dict[str, Any] + if scope["type"] in ("http", "websocket"): # pragma: no branch + root_path = scope.get("root_path", "") + route_path = get_route_path(scope) + match = self.path_regex.match(route_path) + if match: + matched_params = match.groupdict() + for key, value in matched_params.items(): + matched_params[key] = self.param_convertors[key].convert(value) + remaining_path = "/" + matched_params.pop("path") + matched_path = route_path[: -len(remaining_path)] + path_params = dict(scope.get("path_params", {})) + path_params.update(matched_params) + child_scope = { + "path_params": path_params, + # app_root_path will only be set at the top level scope, + # initialized with the (optional) value of a root_path + # set above/before Starlette. And even though any + # mount will have its own child scope with its own respective + # root_path, the app_root_path will always be available in all + # the child scopes with the same top level value because it's + # set only once here with a default, any other child scope will + # just inherit that app_root_path default value stored in the + # scope. All this is needed to support Request.url_for(), as it + # uses the app_root_path to build the URL path. + "app_root_path": scope.get("app_root_path", root_path), + "root_path": root_path + matched_path, + "endpoint": self.app, + } + return Match.FULL, child_scope + return Match.NONE, {} + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + if self.name is not None and name == self.name and "path" in path_params: + # 'name' matches "". + path_params["path"] = path_params["path"].lstrip("/") + path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params) + if not remaining_params: + return URLPath(path=path) + elif self.name is None or name.startswith(self.name + ":"): + if self.name is None: + # No mount name. + remaining_name = name + else: + # 'name' matches ":". + remaining_name = name[len(self.name) + 1 :] + path_kwarg = path_params.get("path") + path_params["path"] = "" + path_prefix, remaining_params = replace_params(self.path_format, self.param_convertors, path_params) + if path_kwarg is not None: + remaining_params["path"] = path_kwarg + for route in self.routes or []: + try: + url = route.url_path_for(remaining_name, **remaining_params) + return URLPath(path=path_prefix.rstrip("/") + str(url), protocol=url.protocol) + except NoMatchFound: + pass + raise NoMatchFound(name, path_params) + + async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: + await self.app(scope, receive, send) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Mount) and self.path == other.path and self.app == other.app + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + name = self.name or "" + return f"{class_name}(path={self.path!r}, name={name!r}, app={self.app!r})" + + +class Host(BaseRoute): + def __init__(self, host: str, app: ASGIApp, name: str | None = None) -> None: + assert not host.startswith("/"), "Host must not start with '/'" + self.host = host + self.app = app + self.name = name + self.host_regex, self.host_format, self.param_convertors = compile_path(host) + + @property + def routes(self) -> list[BaseRoute]: + return getattr(self.app, "routes", []) + + def matches(self, scope: Scope) -> tuple[Match, Scope]: + if scope["type"] in ("http", "websocket"): # pragma:no branch + headers = Headers(scope=scope) + parsed_host = parse_host_header(headers.get("host")) + if parsed_host is None: + return Match.NONE, {} + host = parsed_host.host + + match = self.host_regex.match(host) + if match: + matched_params = match.groupdict() + for key, value in matched_params.items(): + matched_params[key] = self.param_convertors[key].convert(value) + path_params = dict(scope.get("path_params", {})) + path_params.update(matched_params) + child_scope = {"path_params": path_params, "endpoint": self.app} + return Match.FULL, child_scope + return Match.NONE, {} + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + if self.name is not None and name == self.name and "path" in path_params: + # 'name' matches "". + path = path_params.pop("path") + host, remaining_params = replace_params(self.host_format, self.param_convertors, path_params) + if not remaining_params: + return URLPath(path=path, host=host) + elif self.name is None or name.startswith(self.name + ":"): + if self.name is None: + # No mount name. + remaining_name = name + else: + # 'name' matches ":". + remaining_name = name[len(self.name) + 1 :] + host, remaining_params = replace_params(self.host_format, self.param_convertors, path_params) + for route in self.routes or []: + try: + url = route.url_path_for(remaining_name, **remaining_params) + return URLPath(path=str(url), protocol=url.protocol, host=host) + except NoMatchFound: + pass + raise NoMatchFound(name, path_params) + + async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: + await self.app(scope, receive, send) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Host) and self.host == other.host and self.app == other.app + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + name = self.name or "" + return f"{class_name}(host={self.host!r}, name={name!r}, app={self.app!r})" + + +_T = TypeVar("_T") + + +class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]): + def __init__(self, cm: AbstractContextManager[_T]): + self._cm = cm + + async def __aenter__(self) -> _T: + return self._cm.__enter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> bool | None: + return self._cm.__exit__(exc_type, exc_value, traceback) + + +def _wrap_gen_lifespan_context( + lifespan_context: Callable[[Any], Generator[Any, Any, Any]], +) -> Callable[[Any], AbstractAsyncContextManager[Any]]: + cmgr = contextlib.contextmanager(lifespan_context) + + @functools.wraps(cmgr) + def wrapper(app: Any) -> _AsyncLiftContextManager[Any]: + return _AsyncLiftContextManager(cmgr(app)) + + return wrapper + + +class _DefaultLifespan: + def __init__(self, router: Router): + self._router = router + + async def __aenter__(self) -> None: + pass + + async def __aexit__(self, *exc_info: object) -> None: + pass + + def __call__(self: _T, app: object) -> _T: + return self + + +class Router: + def __init__( + self, + routes: Sequence[BaseRoute] | None = None, + redirect_slashes: bool = True, + default: ASGIApp | None = None, + # the generic to Lifespan[AppType] is the type of the top level application + # which the router cannot know statically, so we use Any + lifespan: Lifespan[Any] | None = None, + *, + middleware: Sequence[Middleware] | None = None, + max_body_size: int | None = None, + ) -> None: + self.routes = [] if routes is None else list(routes) + self.redirect_slashes = redirect_slashes + self.default = self.not_found if default is None else default + + if lifespan is None: + self.lifespan_context: Lifespan[Any] = _DefaultLifespan(self) + + elif inspect.isasyncgenfunction(lifespan): + warnings.warn( + "async generator function lifespans are deprecated, " + "use an @contextlib.asynccontextmanager function instead", + StarletteDeprecationWarning, + ) + self.lifespan_context = asynccontextmanager(lifespan) + elif inspect.isgeneratorfunction(lifespan): + warnings.warn( + "generator function lifespans are deprecated, use an @contextlib.asynccontextmanager function instead", + StarletteDeprecationWarning, + ) + self.lifespan_context = _wrap_gen_lifespan_context(lifespan) + else: + self.lifespan_context = lifespan + + self.middleware_stack = self.app + if middleware: + for cls, args, kwargs in reversed(middleware): + self.middleware_stack = cls(self.middleware_stack, *args, **kwargs) + if max_body_size is not None: + self.middleware_stack = RequestBodyLimitMiddleware(self.middleware_stack, max_body_size=max_body_size) + + async def not_found(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] == "websocket": + websocket_close = WebSocketClose() + await websocket_close(scope, receive, send) + return + + # If we're running inside a starlette application then raise an + # exception, so that the configurable exception handler can deal with + # returning the response. For plain ASGI apps, just return the response. + if "app" in scope: + raise HTTPException(status_code=404) + else: + response = PlainTextResponse("Not Found", status_code=404) + await response(scope, receive, send) + + def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: + for route in self.routes: + try: + return route.url_path_for(name, **path_params) + except NoMatchFound: + pass + raise NoMatchFound(name, path_params) + + async def lifespan(self, scope: Scope, receive: Receive, send: Send) -> None: + """ + Handle ASGI lifespan messages, which allows us to manage application + startup and shutdown events. + """ + started = False + app: Any = scope.get("app") + await receive() + try: + async with self.lifespan_context(app) as maybe_state: + if maybe_state is not None: + if "state" not in scope: + raise RuntimeError('The server does not support "state" in the lifespan scope.') + scope["state"].update(maybe_state) + await send({"type": "lifespan.startup.complete"}) + started = True + await receive() + except BaseException: + exc_text = traceback.format_exc() + if started: + await send({"type": "lifespan.shutdown.failed", "message": exc_text}) + else: + await send({"type": "lifespan.startup.failed", "message": exc_text}) + raise + else: + await send({"type": "lifespan.shutdown.complete"}) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """ + The main entry point to the Router class. + """ + await self.middleware_stack(scope, receive, send) + + async def app(self, scope: Scope, receive: Receive, send: Send) -> None: + assert scope["type"] in ("http", "websocket", "lifespan") + + if "router" not in scope: + scope["router"] = self + + if scope["type"] == "lifespan": + await self.lifespan(scope, receive, send) + return + + partial = None + + for route in self.routes: + # Determine if any route matches the incoming scope, + # and hand over to the matching route if found. + match, child_scope = route.matches(scope) + if match == Match.FULL: + scope["route"] = route + scope.update(child_scope) + await route.handle(scope, receive, send) + return + elif match == Match.PARTIAL and partial is None: + partial = route + partial_scope = child_scope + + if partial is not None: + #  Handle partial matches. These are cases where an endpoint is + # able to handle the request, but is not a preferred option. + # We use this in particular to deal with "405 Method Not Allowed". + scope["route"] = partial + scope.update(partial_scope) + await partial.handle(scope, receive, send) + return + + route_path = get_route_path(scope) + if scope["type"] == "http" and self.redirect_slashes and route_path != "/": + redirect_scope = dict(scope) + if route_path.endswith("/"): + redirect_scope["path"] = redirect_scope["path"].rstrip("/") + else: + redirect_scope["path"] = redirect_scope["path"] + "/" + + for route in self.routes: + match, child_scope = route.matches(redirect_scope) + if match != Match.NONE: + redirect_url = URL(scope=redirect_scope) + response = RedirectResponse(url=str(redirect_url)) + await response(scope, receive, send) + return + + await self.default(scope, receive, send) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Router) and self.routes == other.routes + + def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None: # pragma: no cover + route = Mount(path, app=app, name=name) + self.routes.append(route) + + def host(self, host: str, app: ASGIApp, name: str | None = None) -> None: # pragma: no cover + route = Host(host, app=app, name=name) + self.routes.append(route) + + def add_route( + self, + path: str, + endpoint: Callable[[Request], Awaitable[Response] | Response], + methods: Collection[str] | None = None, + name: str | None = None, + include_in_schema: bool = True, + ) -> None: # pragma: no cover + route = Route( + path, + endpoint=endpoint, + methods=methods, + name=name, + include_in_schema=include_in_schema, + ) + self.routes.append(route) + + def add_websocket_route( + self, + path: str, + endpoint: Callable[[WebSocket], Awaitable[None]], + name: str | None = None, + ) -> None: # pragma: no cover + route = WebSocketRoute(path, endpoint=endpoint, name=name) + self.routes.append(route) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py new file mode 100644 index 0000000..484664c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/schemas.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import inspect +import re +from collections.abc import Callable +from typing import Any, NamedTuple + +from starlette.requests import Request +from starlette.responses import Response +from starlette.routing import BaseRoute, Host, Mount, Route + +try: + import yaml +except ModuleNotFoundError: # pragma: no cover + yaml = None # type: ignore[assignment] + + +class OpenAPIResponse(Response): + media_type = "application/vnd.oai.openapi" + + def render(self, content: Any) -> bytes: + assert yaml is not None, "`pyyaml` must be installed to use OpenAPIResponse." + assert isinstance(content, dict), "The schema passed to OpenAPIResponse should be a dictionary." + return yaml.dump(content, default_flow_style=False).encode("utf-8") + + +class EndpointInfo(NamedTuple): + path: str + http_method: str + func: Callable[..., Any] + + +_remove_converter_pattern = re.compile(r":\w+}") + + +class BaseSchemaGenerator: + def get_schema(self, routes: list[BaseRoute]) -> dict[str, Any]: + raise NotImplementedError() # pragma: no cover + + def get_endpoints(self, routes: list[BaseRoute]) -> list[EndpointInfo]: + """ + Given the routes, yields the following information: + + - path + eg: /users/ + - http_method + one of 'get', 'post', 'put', 'patch', 'delete', 'options', 'query' + - func + method ready to extract the docstring + """ + endpoints_info: list[EndpointInfo] = [] + + for route in routes: + if isinstance(route, Mount | Host): + routes = route.routes or [] + if isinstance(route, Mount): + path = self._remove_converter(route.path) + else: + path = "" + sub_endpoints = [ + EndpointInfo( + path="".join((path, sub_endpoint.path)), + http_method=sub_endpoint.http_method, + func=sub_endpoint.func, + ) + for sub_endpoint in self.get_endpoints(routes) + ] + endpoints_info.extend(sub_endpoints) + + elif not isinstance(route, Route) or not route.include_in_schema: + continue + + elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint): + path = self._remove_converter(route.path) + for method in route.methods or ["GET"]: + if method == "HEAD": + continue + endpoints_info.append(EndpointInfo(path, method.lower(), route.endpoint)) + else: + path = self._remove_converter(route.path) + for method in ["get", "post", "put", "patch", "delete", "options", "query"]: + if not hasattr(route.endpoint, method): + continue + func = getattr(route.endpoint, method) + endpoints_info.append(EndpointInfo(path, method.lower(), func)) + + return endpoints_info + + def _remove_converter(self, path: str) -> str: + """ + Remove the converter from the path. + For example, a route like this: + Route("/users/{id:int}", endpoint=get_user, methods=["GET"]) + Should be represented as `/users/{id}` in the OpenAPI schema. + """ + return _remove_converter_pattern.sub("}", path) + + def parse_docstring(self, func_or_method: Callable[..., Any]) -> dict[str, Any]: + """ + Given a function, parse the docstring as YAML and return a dictionary of info. + """ + docstring = func_or_method.__doc__ + if not docstring: + return {} + + assert yaml is not None, "`pyyaml` must be installed to use parse_docstring." + + # We support having regular docstrings before the schema + # definition. Here we return just the schema part from + # the docstring. + docstring = docstring.split("---")[-1] + + parsed = yaml.safe_load(docstring) + + if not isinstance(parsed, dict): + # A regular docstring (not yaml formatted) can return + # a simple string here, which wouldn't follow the schema. + return {} + + return parsed + + def OpenAPIResponse(self, request: Request) -> Response: + routes = request.app.routes + schema = self.get_schema(routes=routes) + return OpenAPIResponse(schema) + + +class SchemaGenerator(BaseSchemaGenerator): + def __init__(self, base_schema: dict[str, Any]) -> None: + self.base_schema = base_schema + + def get_schema(self, routes: list[BaseRoute]) -> dict[str, Any]: + schema = dict(self.base_schema) + schema.setdefault("paths", {}) + openapi_version_match = re.match(r"^(\d+)\.(\d+)", str(schema.get("openapi", ""))) + supports_query = openapi_version_match is not None and tuple(map(int, openapi_version_match.groups())) >= (3, 2) + endpoints_info = self.get_endpoints(routes) + + for endpoint in endpoints_info: + if endpoint.http_method == "query" and not supports_query: + continue + parsed = self.parse_docstring(endpoint.func) + + if not parsed: + continue + + if endpoint.path not in schema["paths"]: + schema["paths"][endpoint.path] = {} + + schema["paths"][endpoint.path][endpoint.http_method] = parsed + + return schema diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py new file mode 100644 index 0000000..77b62e1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +import errno +import importlib.util +import os +import stat +from email.utils import parsedate +from typing import Union + +import anyio +import anyio.to_thread + +from starlette._utils import get_route_path +from starlette.datastructures import URL, Headers +from starlette.exceptions import HTTPException +from starlette.responses import FileResponse, RedirectResponse, Response +from starlette.types import Receive, Scope, Send +from starlette.websockets import WebSocketClose + +PathLike = Union[str, "os.PathLike[str]"] + + +class NotModifiedResponse(Response): + NOT_MODIFIED_HEADERS = ( + "cache-control", + "content-location", + "date", + "etag", + "expires", + "vary", + ) + + def __init__(self, headers: Headers): + super().__init__( + status_code=304, + headers={name: value for name, value in headers.items() if name in self.NOT_MODIFIED_HEADERS}, + ) + + +class StaticFiles: + def __init__( + self, + *, + directory: PathLike | None = None, + packages: list[str | tuple[str, str]] | None = None, + html: bool = False, + check_dir: bool = True, + follow_symlink: bool = False, + ) -> None: + self.directory = directory + self.packages = packages + self.all_directories = self.get_directories(directory, packages) + self.html = html + self.config_checked = False + self.follow_symlink = follow_symlink + if check_dir and directory is not None and not os.path.isdir(directory): + raise RuntimeError(f"Directory '{directory}' does not exist") + + def get_directories( + self, + directory: PathLike | None = None, + packages: list[str | tuple[str, str]] | None = None, + ) -> list[PathLike]: + """ + Given `directory` and `packages` arguments, return a list of all the + directories that should be used for serving static files from. + """ + directories = [] + if directory is not None: + directories.append(directory) + + for package in packages or []: + if isinstance(package, tuple): + package, statics_dir = package + else: + statics_dir = "statics" + spec = importlib.util.find_spec(package) + assert spec is not None, f"Package {package!r} could not be found." + assert spec.origin is not None, f"Package {package!r} could not be found." + package_directory = os.path.normpath(os.path.join(spec.origin, "..", statics_dir)) + assert os.path.isdir(package_directory), ( + f"Directory '{statics_dir!r}' in package {package!r} could not be found." + ) + directories.append(package_directory) + + return directories + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """ + The ASGI entry point. + """ + if scope["type"] == "websocket": + websocket_close = WebSocketClose() + await websocket_close(scope, receive, send) + return + + assert scope["type"] == "http" + + if not self.config_checked: + await self.check_config() + self.config_checked = True + + path = self.get_path(scope) + response = await self.get_response(path, scope) + await response(scope, receive, send) + + def get_path(self, scope: Scope) -> str: + """ + Given the ASGI scope, return the `path` string to serve up, + with OS specific path separators, and any '..', '.' components removed. + """ + route_path = get_route_path(scope) + return os.path.normpath(os.path.join(*route_path.split("/"))) + + async def get_response(self, path: str, scope: Scope) -> Response: + """ + Returns an HTTP response, given the incoming path, method and request headers. + """ + if scope["method"] not in ("GET", "HEAD"): + raise HTTPException(status_code=405) + + try: + full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, path) + except PermissionError: + raise HTTPException(status_code=401) + except OSError as exc: + # Filename is too long, so it can't be a valid static file. + if exc.errno == errno.ENAMETOOLONG: + raise HTTPException(status_code=404) + + raise exc + except ValueError: + # Null bytes or other invalid characters in the path. + raise HTTPException(status_code=404) + + if stat_result and stat.S_ISREG(stat_result.st_mode): + # We have a static file to serve. + return self.file_response(full_path, stat_result, scope) + + elif stat_result and stat.S_ISDIR(stat_result.st_mode) and self.html: + # We're in HTML mode, and have got a directory URL. + # Check if we have 'index.html' file to serve. + index_path = os.path.join(path, "index.html") + full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, index_path) + if stat_result is not None and stat.S_ISREG(stat_result.st_mode): + if not scope["path"].endswith("/"): + # Directory URLs should redirect to always end in "/". + url = URL(scope=scope) + url = url.replace(path=url.path + "/") + return RedirectResponse(url=url) + return self.file_response(full_path, stat_result, scope) + + if self.html: + # Check for '404.html' if we're in HTML mode. + full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, "404.html") + if stat_result and stat.S_ISREG(stat_result.st_mode): + return FileResponse(full_path, stat_result=stat_result, status_code=404) + raise HTTPException(status_code=404) + + def lookup_path(self, path: str) -> tuple[str, os.stat_result | None]: + # Reject absolute paths so they cannot escape the served directory. + if path.startswith(("/", "\\")): + return "", None + for directory in self.all_directories: + joined_path = os.path.join(directory, path) + if self.follow_symlink: + full_path = os.path.abspath(joined_path) + directory = os.path.abspath(directory) + else: + full_path = os.path.realpath(joined_path) + directory = os.path.realpath(directory) + if os.path.commonpath([full_path, directory]) != str(directory): + # Don't allow misbehaving clients to break out of the static files directory. + continue + try: + return full_path, os.stat(full_path) + except (FileNotFoundError, NotADirectoryError): + continue + return "", None + + def file_response( + self, + full_path: PathLike, + stat_result: os.stat_result, + scope: Scope, + status_code: int = 200, + ) -> Response: + request_headers = Headers(scope=scope) + + response = FileResponse(full_path, status_code=status_code, stat_result=stat_result) + if self.is_not_modified(response.headers, request_headers): + return NotModifiedResponse(response.headers) + return response + + async def check_config(self) -> None: + """ + Perform a one-off configuration check that StaticFiles is actually + pointed at a directory, so that we can raise loud errors rather than + just returning 404 responses. + """ + if self.directory is None: + return + + try: + stat_result = await anyio.to_thread.run_sync(os.stat, self.directory) + except FileNotFoundError: + raise RuntimeError(f"StaticFiles directory '{self.directory}' does not exist.") + if not (stat.S_ISDIR(stat_result.st_mode) or stat.S_ISLNK(stat_result.st_mode)): + raise RuntimeError(f"StaticFiles path '{self.directory}' is not a directory.") + + def is_not_modified(self, response_headers: Headers, request_headers: Headers) -> bool: + """ + Given the request and response headers, return `True` if an HTTP + "Not Modified" response could be returned instead. + """ + if if_none_match := request_headers.get("if-none-match"): + if if_none_match.strip() == "*": + return True + # The "etag" header is added by FileResponse, so it's always present. + etag = response_headers["etag"] + return etag in [tag.strip().removeprefix("W/") for tag in if_none_match.split(",")] + + try: + if_modified_since = parsedate(request_headers["if-modified-since"]) + last_modified = parsedate(response_headers["last-modified"]) + if if_modified_since is not None and last_modified is not None and if_modified_since >= last_modified: + return True + except KeyError: + pass + + return False diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py new file mode 100644 index 0000000..202b6b5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/status.py @@ -0,0 +1,211 @@ +""" +HTTP codes +See HTTP Status Code Registry: +https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + +And RFC 9110 - https://www.rfc-editor.org/rfc/rfc9110 +""" + +from __future__ import annotations + +import warnings + +from starlette.exceptions import StarletteDeprecationWarning + +__all__ = [ + "HTTP_100_CONTINUE", + "HTTP_101_SWITCHING_PROTOCOLS", + "HTTP_102_PROCESSING", + "HTTP_103_EARLY_HINTS", + "HTTP_200_OK", + "HTTP_201_CREATED", + "HTTP_202_ACCEPTED", + "HTTP_203_NON_AUTHORITATIVE_INFORMATION", + "HTTP_204_NO_CONTENT", + "HTTP_205_RESET_CONTENT", + "HTTP_206_PARTIAL_CONTENT", + "HTTP_207_MULTI_STATUS", + "HTTP_208_ALREADY_REPORTED", + "HTTP_226_IM_USED", + "HTTP_300_MULTIPLE_CHOICES", + "HTTP_301_MOVED_PERMANENTLY", + "HTTP_302_FOUND", + "HTTP_303_SEE_OTHER", + "HTTP_304_NOT_MODIFIED", + "HTTP_305_USE_PROXY", + "HTTP_306_RESERVED", + "HTTP_307_TEMPORARY_REDIRECT", + "HTTP_308_PERMANENT_REDIRECT", + "HTTP_400_BAD_REQUEST", + "HTTP_401_UNAUTHORIZED", + "HTTP_402_PAYMENT_REQUIRED", + "HTTP_403_FORBIDDEN", + "HTTP_404_NOT_FOUND", + "HTTP_405_METHOD_NOT_ALLOWED", + "HTTP_406_NOT_ACCEPTABLE", + "HTTP_407_PROXY_AUTHENTICATION_REQUIRED", + "HTTP_408_REQUEST_TIMEOUT", + "HTTP_409_CONFLICT", + "HTTP_410_GONE", + "HTTP_411_LENGTH_REQUIRED", + "HTTP_412_PRECONDITION_FAILED", + "HTTP_413_CONTENT_TOO_LARGE", + "HTTP_414_URI_TOO_LONG", + "HTTP_415_UNSUPPORTED_MEDIA_TYPE", + "HTTP_416_RANGE_NOT_SATISFIABLE", + "HTTP_417_EXPECTATION_FAILED", + "HTTP_418_IM_A_TEAPOT", + "HTTP_421_MISDIRECTED_REQUEST", + "HTTP_422_UNPROCESSABLE_CONTENT", + "HTTP_423_LOCKED", + "HTTP_424_FAILED_DEPENDENCY", + "HTTP_425_TOO_EARLY", + "HTTP_426_UPGRADE_REQUIRED", + "HTTP_428_PRECONDITION_REQUIRED", + "HTTP_429_TOO_MANY_REQUESTS", + "HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE", + "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS", + "HTTP_500_INTERNAL_SERVER_ERROR", + "HTTP_501_NOT_IMPLEMENTED", + "HTTP_502_BAD_GATEWAY", + "HTTP_503_SERVICE_UNAVAILABLE", + "HTTP_504_GATEWAY_TIMEOUT", + "HTTP_505_HTTP_VERSION_NOT_SUPPORTED", + "HTTP_506_VARIANT_ALSO_NEGOTIATES", + "HTTP_507_INSUFFICIENT_STORAGE", + "HTTP_508_LOOP_DETECTED", + "HTTP_510_NOT_EXTENDED", + "HTTP_511_NETWORK_AUTHENTICATION_REQUIRED", + "WS_1000_NORMAL_CLOSURE", + "WS_1001_GOING_AWAY", + "WS_1002_PROTOCOL_ERROR", + "WS_1003_UNSUPPORTED_DATA", + "WS_1005_NO_STATUS_RCVD", + "WS_1006_ABNORMAL_CLOSURE", + "WS_1007_INVALID_FRAME_PAYLOAD_DATA", + "WS_1008_POLICY_VIOLATION", + "WS_1009_MESSAGE_TOO_BIG", + "WS_1010_MANDATORY_EXT", + "WS_1011_INTERNAL_ERROR", + "WS_1012_SERVICE_RESTART", + "WS_1013_TRY_AGAIN_LATER", + "WS_1014_BAD_GATEWAY", + "WS_1015_TLS_HANDSHAKE", +] + +HTTP_100_CONTINUE = 100 +HTTP_101_SWITCHING_PROTOCOLS = 101 +HTTP_102_PROCESSING = 102 +HTTP_103_EARLY_HINTS = 103 +HTTP_200_OK = 200 +HTTP_201_CREATED = 201 +HTTP_202_ACCEPTED = 202 +HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 +HTTP_204_NO_CONTENT = 204 +HTTP_205_RESET_CONTENT = 205 +HTTP_206_PARTIAL_CONTENT = 206 +HTTP_207_MULTI_STATUS = 207 +HTTP_208_ALREADY_REPORTED = 208 +HTTP_226_IM_USED = 226 +HTTP_300_MULTIPLE_CHOICES = 300 +HTTP_301_MOVED_PERMANENTLY = 301 +HTTP_302_FOUND = 302 +HTTP_303_SEE_OTHER = 303 +HTTP_304_NOT_MODIFIED = 304 +HTTP_305_USE_PROXY = 305 +HTTP_306_RESERVED = 306 +HTTP_307_TEMPORARY_REDIRECT = 307 +HTTP_308_PERMANENT_REDIRECT = 308 +HTTP_400_BAD_REQUEST = 400 +HTTP_401_UNAUTHORIZED = 401 +HTTP_402_PAYMENT_REQUIRED = 402 +HTTP_403_FORBIDDEN = 403 +HTTP_404_NOT_FOUND = 404 +HTTP_405_METHOD_NOT_ALLOWED = 405 +HTTP_406_NOT_ACCEPTABLE = 406 +HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 +HTTP_408_REQUEST_TIMEOUT = 408 +HTTP_409_CONFLICT = 409 +HTTP_410_GONE = 410 +HTTP_411_LENGTH_REQUIRED = 411 +HTTP_412_PRECONDITION_FAILED = 412 +HTTP_413_CONTENT_TOO_LARGE = 413 +HTTP_414_URI_TOO_LONG = 414 +HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 +HTTP_416_RANGE_NOT_SATISFIABLE = 416 +HTTP_417_EXPECTATION_FAILED = 417 +HTTP_418_IM_A_TEAPOT = 418 +HTTP_421_MISDIRECTED_REQUEST = 421 +HTTP_422_UNPROCESSABLE_CONTENT = 422 +HTTP_423_LOCKED = 423 +HTTP_424_FAILED_DEPENDENCY = 424 +HTTP_425_TOO_EARLY = 425 +HTTP_426_UPGRADE_REQUIRED = 426 +HTTP_428_PRECONDITION_REQUIRED = 428 +HTTP_429_TOO_MANY_REQUESTS = 429 +HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 +HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451 +HTTP_500_INTERNAL_SERVER_ERROR = 500 +HTTP_501_NOT_IMPLEMENTED = 501 +HTTP_502_BAD_GATEWAY = 502 +HTTP_503_SERVICE_UNAVAILABLE = 503 +HTTP_504_GATEWAY_TIMEOUT = 504 +HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 +HTTP_506_VARIANT_ALSO_NEGOTIATES = 506 +HTTP_507_INSUFFICIENT_STORAGE = 507 +HTTP_508_LOOP_DETECTED = 508 +HTTP_510_NOT_EXTENDED = 510 +HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 + + +""" +WebSocket codes +https://www.iana.org/assignments/websocket/websocket.xml#close-code-number +https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent +""" +WS_1000_NORMAL_CLOSURE = 1000 +WS_1001_GOING_AWAY = 1001 +WS_1002_PROTOCOL_ERROR = 1002 +WS_1003_UNSUPPORTED_DATA = 1003 +WS_1005_NO_STATUS_RCVD = 1005 +WS_1006_ABNORMAL_CLOSURE = 1006 +WS_1007_INVALID_FRAME_PAYLOAD_DATA = 1007 +WS_1008_POLICY_VIOLATION = 1008 +WS_1009_MESSAGE_TOO_BIG = 1009 +WS_1010_MANDATORY_EXT = 1010 +WS_1011_INTERNAL_ERROR = 1011 +WS_1012_SERVICE_RESTART = 1012 +WS_1013_TRY_AGAIN_LATER = 1013 +WS_1014_BAD_GATEWAY = 1014 +WS_1015_TLS_HANDSHAKE = 1015 + +__deprecated__ = { + "HTTP_413_REQUEST_ENTITY_TOO_LARGE": 413, + "HTTP_414_REQUEST_URI_TOO_LONG": 414, + "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE": 416, + "HTTP_422_UNPROCESSABLE_ENTITY": 422, +} + + +def __getattr__(name: str) -> int: + deprecation_changes = { + "HTTP_413_REQUEST_ENTITY_TOO_LARGE": "HTTP_413_CONTENT_TOO_LARGE", + "HTTP_414_REQUEST_URI_TOO_LONG": "HTTP_414_URI_TOO_LONG", + "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE": "HTTP_416_RANGE_NOT_SATISFIABLE", + "HTTP_422_UNPROCESSABLE_ENTITY": "HTTP_422_UNPROCESSABLE_CONTENT", + } + + deprecated = __deprecated__.get(name) + if deprecated: + warnings.warn( + f"'{name}' is deprecated. Use '{deprecation_changes[name]}' instead.", + category=StarletteDeprecationWarning, + stacklevel=3, + ) + return deprecated + + raise AttributeError(f"module 'starlette.status' has no attribute '{name}'") + + +def __dir__() -> list[str]: + return sorted(list(__all__) + list(__deprecated__.keys())) # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py new file mode 100644 index 0000000..67872b3 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/templating.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from os import PathLike +from typing import TYPE_CHECKING, Any, overload + +from starlette.background import BackgroundTask +from starlette.datastructures import URL +from starlette.requests import Request +from starlette.responses import HTMLResponse +from starlette.types import Receive, Scope, Send + +try: + import jinja2 + + # @contextfunction was renamed to @pass_context in Jinja 3.0, and was removed in 3.1 + # hence we try to get pass_context (most installs will be >=3.1) + # and fall back to contextfunction, + # adding a type ignore for mypy to let us access an attribute that may not exist + if TYPE_CHECKING: + pass_context = jinja2.pass_context + else: + if hasattr(jinja2, "pass_context"): + pass_context = jinja2.pass_context + else: # pragma: no cover + pass_context = jinja2.contextfunction # type: ignore[attr-defined] +except ImportError as _import_error: # pragma: no cover + raise ImportError("jinja2 must be installed to use Jinja2Templates") from _import_error + + +class _TemplateResponse(HTMLResponse): + def __init__( + self, + template: Any, + context: dict[str, Any], + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + ): + self.template = template + self.context = context + content = template.render(context) + super().__init__(content, status_code, headers, media_type, background) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + request = self.context.get("request", {}) + extensions = request.get("extensions", {}) + if "http.response.debug" in extensions: # pragma: no branch + await send({"type": "http.response.debug", "info": {"template": self.template, "context": self.context}}) + await super().__call__(scope, receive, send) + + +class Jinja2Templates: + """Jinja2 template renderer. + + Example: + ```python + from starlette.templating import Jinja2Templates + + templates = Jinja2Templates(directory="templates") + + async def homepage(request: Request) -> Response: + return templates.TemplateResponse(request, "index.html") + ``` + """ + + @overload + def __init__( + self, + directory: str | PathLike[str] | Sequence[str | PathLike[str]], + *, + context_processors: list[Callable[[Request], dict[str, Any]]] | None = None, + ) -> None: ... + + @overload + def __init__( + self, + *, + env: jinja2.Environment, + context_processors: list[Callable[[Request], dict[str, Any]]] | None = None, + ) -> None: ... + + def __init__( + self, + directory: str | PathLike[str] | Sequence[str | PathLike[str]] | None = None, + *, + context_processors: list[Callable[[Request], dict[str, Any]]] | None = None, + env: jinja2.Environment | None = None, + ) -> None: + assert bool(directory) ^ bool(env), "either 'directory' or 'env' arguments must be passed" + self.context_processors = context_processors or [] + if directory is not None: + loader = jinja2.FileSystemLoader(directory) + self.env = jinja2.Environment(loader=loader, autoescape=jinja2.select_autoescape()) + elif env is not None: # pragma: no branch + self.env = env + + self._setup_env_defaults(self.env) + + def _setup_env_defaults(self, env: jinja2.Environment) -> None: + @pass_context + def url_for( + context: dict[str, Any], + name: str, + /, + **path_params: Any, + ) -> URL: + request: Request = context["request"] + return request.url_for(name, **path_params) + + env.globals.setdefault("url_for", url_for) + + def get_template(self, name: str) -> jinja2.Template: + return self.env.get_template(name) + + def TemplateResponse( + self, + request: Request, + name: str, + context: dict[str, Any] | None = None, + status_code: int = 200, + headers: Mapping[str, str] | None = None, + media_type: str | None = None, + background: BackgroundTask | None = None, + ) -> _TemplateResponse: + """ + Render a template and return an HTML response. + + Args: + request: The incoming request instance. + name: The template file name to render. + context: Variables to pass to the template. + status_code: HTTP status code for the response. + headers: Additional headers to include in the response. + media_type: Media type for the response. + background: Background task to run after response is sent. + + Returns: + An HTML response with the rendered template content. + """ + context = context or {} + + context.setdefault("request", request) + for context_processor in self.context_processors: + context.update(context_processor(request)) + + template = self.get_template(name) + return _TemplateResponse( + template, + context, + status_code=status_code, + headers=headers, + media_type=media_type, + background=background, + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py new file mode 100644 index 0000000..2f3f468 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/testclient.py @@ -0,0 +1,568 @@ +from __future__ import annotations + +import contextlib +import inspect +import io +import json +import math +import sys +import warnings +from collections.abc import Awaitable, Callable, Generator, Iterable, Mapping, Sequence +from concurrent.futures import Future +from contextlib import AbstractContextManager +from types import GeneratorType +from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeGuard, cast +from urllib.parse import unquote, urljoin + +import anyio +import anyio.abc +import anyio.from_thread +from anyio.streams.stapled import StapledObjectStream + +from starlette._utils import is_async_callable +from starlette.exceptions import StarletteDeprecationWarning +from starlette.types import ASGIApp, Message, Receive, Scope, Send +from starlette.websockets import WebSocketDisconnect + +if sys.version_info >= (3, 11): # pragma: no cover + from typing import Self +else: # pragma: no cover + from typing_extensions import Self + +if TYPE_CHECKING: + import httpx2 as httpx +else: + try: + import httpx2 as httpx + except ModuleNotFoundError: # pragma: no cover + try: + import httpx + except ModuleNotFoundError: + raise RuntimeError( + "The starlette.testclient module requires the httpx2 package to be installed.\n" + "You can install this with:\n" + " $ pip install httpx2\n" + ) from None + else: + warnings.warn( + "Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.", + StarletteDeprecationWarning, + stacklevel=2, + ) + +_PortalFactoryType = Callable[[], AbstractContextManager[anyio.from_thread.BlockingPortal]] + +ASGIInstance = Callable[[Receive, Send], Awaitable[None]] +ASGI2App = Callable[[Scope], ASGIInstance] +ASGI3App = Callable[[Scope, Receive, Send], Awaitable[None]] + + +_RequestData = Mapping[str, str | Iterable[str] | bytes] + + +def _is_asgi3(app: ASGI2App | ASGI3App) -> TypeGuard[ASGI3App]: + if inspect.isclass(app): + return hasattr(app, "__await__") + return is_async_callable(app) + + +class _WrapASGI2: + """ + Provide an ASGI3 interface onto an ASGI2 app. + """ + + def __init__(self, app: ASGI2App) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + instance = self.app(scope) + await instance(receive, send) + + +class _AsyncBackend(TypedDict): + backend: str + backend_options: dict[str, Any] + + +class _Upgrade(Exception): + def __init__(self, session: WebSocketTestSession) -> None: + self.session = session + + +class WebSocketDenialResponse( # type: ignore[misc] + httpx.Response, + WebSocketDisconnect, +): + """ + A special case of `WebSocketDisconnect`, raised in the `TestClient` if the + `WebSocket` is closed before being accepted with a `send_denial_response()`. + """ + + +class WebSocketTestSession: + def __init__( + self, + app: ASGI3App, + scope: Scope, + portal_factory: _PortalFactoryType, + ) -> None: + self.app = app + self.scope = scope + self.accepted_subprotocol = None + self.portal_factory = portal_factory + self.extra_headers = None + + def __enter__(self) -> Self: + with contextlib.ExitStack() as stack: + self.portal = portal = stack.enter_context(self.portal_factory()) + fut, cs = portal.start_task(self._run) + stack.callback(fut.result) + stack.callback(portal.call, cs.cancel) + self.send({"type": "websocket.connect"}) + message = self.receive() + self._raise_on_close(message) + self.accepted_subprotocol = message.get("subprotocol", None) + self.extra_headers = message.get("headers", None) + stack.callback(self.close, 1000) + self.exit_stack = stack.pop_all() + return self + + def __exit__(self, *args: Any) -> bool | None: + return self.exit_stack.__exit__(*args) + + async def _run(self, *, task_status: anyio.abc.TaskStatus[anyio.CancelScope]) -> None: + """ + The sub-thread in which the websocket session runs. + """ + send_tx, send_rx = anyio.create_memory_object_stream[Message](math.inf) + receive_tx, receive_rx = anyio.create_memory_object_stream[Message](math.inf) + with send_tx, send_rx, receive_tx, receive_rx, anyio.CancelScope() as cs: + self._receive_tx = receive_tx + self._send_rx = send_rx + task_status.started(cs) + await self.app(self.scope, receive_rx.receive, send_tx.send) + + # wait for cs.cancel to be called before closing streams + await anyio.sleep_forever() + + def _raise_on_close(self, message: Message) -> None: + if message["type"] == "websocket.close": + raise WebSocketDisconnect(code=message.get("code", 1000), reason=message.get("reason", "")) + elif message["type"] == "websocket.http.response.start": + status_code: int = message["status"] + headers: list[tuple[bytes, bytes]] = message["headers"] + body: list[bytes] = [] + while True: + message = self.receive() + assert message["type"] == "websocket.http.response.body" + body.append(message["body"]) + if not message.get("more_body", False): + break + raise WebSocketDenialResponse(status_code=status_code, headers=headers, content=b"".join(body)) + + def send(self, message: Message) -> None: + self.portal.call(self._receive_tx.send, message) + + def send_text(self, data: str) -> None: + self.send({"type": "websocket.receive", "text": data}) + + def send_bytes(self, data: bytes) -> None: + self.send({"type": "websocket.receive", "bytes": data}) + + def send_json(self, data: Any, mode: Literal["text", "binary"] = "text") -> None: + text = json.dumps(data, separators=(",", ":"), ensure_ascii=False) + if mode == "text": + self.send({"type": "websocket.receive", "text": text}) + else: + self.send({"type": "websocket.receive", "bytes": text.encode("utf-8")}) + + def close(self, code: int = 1000, reason: str | None = None) -> None: + self.send({"type": "websocket.disconnect", "code": code, "reason": reason}) + + def receive(self) -> Message: + return self.portal.call(self._send_rx.receive) + + def receive_text(self) -> str: + message = self.receive() + self._raise_on_close(message) + return cast(str, message["text"]) + + def receive_bytes(self) -> bytes: + message = self.receive() + self._raise_on_close(message) + return cast(bytes, message["bytes"]) + + def receive_json(self, mode: Literal["text", "binary"] = "text") -> Any: + message = self.receive() + self._raise_on_close(message) + if mode == "text": + text = message["text"] + else: + text = message["bytes"].decode("utf-8") + return json.loads(text) + + +class _TestClientTransport(httpx.BaseTransport): + def __init__( + self, + app: ASGI3App, + portal_factory: _PortalFactoryType, + raise_server_exceptions: bool = True, + root_path: str = "", + *, + client: tuple[str, int], + app_state: dict[str, Any], + ) -> None: + self.app = app + self.raise_server_exceptions = raise_server_exceptions + self.root_path = root_path + self.portal_factory = portal_factory + self.app_state = app_state + self.client = client + + def handle_request(self, request: httpx.Request) -> httpx.Response: + scheme = request.url.scheme + host = request.url.raw_host.decode(encoding="ascii") + path = request.url.path + raw_path = request.url.raw_path + query = request.url.query.decode(encoding="ascii") + + default_port = {"http": 80, "ws": 80, "https": 443, "wss": 443}[scheme] + port = request.url.port + if port is None: + port = default_port + + # Include the 'host' header. + if "host" in request.headers: + headers: list[tuple[bytes, bytes]] = [] + else: # pragma: no cover + headers = [(b"host", request.url.netloc)] + + # Include other request headers. + headers += [(key.lower().encode(), value.encode()) for key, value in request.headers.multi_items()] + + scope: dict[str, Any] + + if scheme in {"ws", "wss"}: + subprotocol = request.headers.get("sec-websocket-protocol", None) + if subprotocol is None: + subprotocols: Sequence[str] = [] + else: + subprotocols = [value.strip() for value in subprotocol.split(",")] + scope = { + "type": "websocket", + "path": unquote(path), + "raw_path": raw_path.split(b"?", 1)[0], + "root_path": self.root_path, + "scheme": scheme, + "query_string": query.encode(), + "headers": headers, + "client": self.client, + "server": [host, port], + "subprotocols": subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + session = WebSocketTestSession(self.app, scope, self.portal_factory) + raise _Upgrade(session) + + scope = { + "type": "http", + "http_version": "1.1", + "method": request.method, + "path": unquote(path), + "raw_path": raw_path.split(b"?", 1)[0], + "root_path": self.root_path, + "scheme": scheme, + "query_string": query.encode(), + "headers": headers, + "client": self.client, + "server": [host, port], + "extensions": {"http.response.debug": {}, "http.response.trailers": {}}, + "state": self.app_state.copy(), + } + + request_complete = False + response_started = False + body_complete = False + trailers_expected = False + trailers: list[tuple[bytes, bytes]] = [] + response_complete: anyio.Event + raw_kwargs: dict[str, Any] = {"stream": io.BytesIO()} + debug_info: dict[str, Any] | None = None + + async def receive() -> Message: + nonlocal request_complete + + if request_complete: + if not response_complete.is_set(): + await response_complete.wait() + return {"type": "http.disconnect"} + + body = request.read() + if isinstance(body, str): + body_bytes: bytes = body.encode("utf-8") # pragma: no cover + elif body is None: + body_bytes = b"" # pragma: no cover + elif isinstance(body, GeneratorType): + try: # pragma: no cover + chunk = body.send(None) + if isinstance(chunk, str): + chunk = chunk.encode("utf-8") + return {"type": "http.request", "body": chunk, "more_body": True} + except StopIteration: # pragma: no cover + request_complete = True + return {"type": "http.request", "body": b""} + else: + body_bytes = body + + request_complete = True + return {"type": "http.request", "body": body_bytes} + + async def send(message: Message) -> None: + nonlocal raw_kwargs, response_started, debug_info, body_complete, trailers_expected + + if message["type"] == "http.response.start": + assert not response_started, 'Received multiple "http.response.start" messages.' + raw_kwargs["status_code"] = message["status"] + raw_kwargs["headers"] = [(key.decode(), value.decode()) for key, value in message.get("headers", [])] + response_started = True + trailers_expected = message.get("trailers", False) + elif message["type"] == "http.response.body": + assert response_started, 'Received "http.response.body" without "http.response.start".' + assert not response_complete.is_set(), 'Received "http.response.body" after response completed.' + assert not body_complete, 'Received "http.response.body" after body completed.' + body = message.get("body", b"") + more_body = message.get("more_body", False) + if request.method != "HEAD": + raw_kwargs["stream"].write(body) + if not more_body: + raw_kwargs["stream"].seek(0) + body_complete = True + if not trailers_expected: + response_complete.set() + elif message["type"] == "http.response.trailers": + assert trailers_expected, 'Received "http.response.trailers" without declaring trailers.' + assert body_complete, 'Received "http.response.trailers" before body completed.' + assert not response_complete.is_set(), 'Received "http.response.trailers" after response completed.' + trailers.extend(message.get("headers", [])) + if not message.get("more_trailers", False): + response_complete.set() + elif message["type"] == "http.response.debug": + debug_info = message["info"] + + try: + with self.portal_factory() as portal: + response_complete = portal.call(anyio.Event) + portal.call(self.app, scope, receive, send) + except BaseException as exc: + if self.raise_server_exceptions: + raise exc + + if self.raise_server_exceptions: + assert response_started, "TestClient did not receive any response." + elif not response_started: + raw_kwargs = { + "status_code": 500, + "headers": [], + "stream": io.BytesIO(), + } + + raw_kwargs["stream"] = httpx.ByteStream(raw_kwargs["stream"].read()) + + response = httpx.Response(**raw_kwargs, request=request) + if trailers_expected: + response.extensions["http.response.trailers"] = trailers + if debug_info is not None: + response.extensions["http.response.debug"] = debug_info + if "template" in debug_info: + response.template = debug_info["template"] # type: ignore[attr-defined] + if "context" in debug_info: + response.context = debug_info["context"] # type: ignore[attr-defined] + return response + + +class TestClient(httpx.Client): + __test__ = False + task: Future[None] + portal: anyio.from_thread.BlockingPortal | None = None + + def __init__( + self, + app: ASGIApp, + base_url: str = "http://testserver", + raise_server_exceptions: bool = True, + root_path: str = "", + backend: Literal["asyncio", "trio"] = "asyncio", + backend_options: dict[str, Any] | None = None, + cookies: httpx._types.CookieTypes | None = None, + headers: dict[str, str] | None = None, + follow_redirects: bool = True, + client: tuple[str, int] = ("testclient", 50000), + ) -> None: + self.async_backend = _AsyncBackend(backend=backend, backend_options=backend_options or {}) + if _is_asgi3(app): + asgi_app = app + else: + app = cast(ASGI2App, app) # type: ignore[assignment] + asgi_app = _WrapASGI2(app) # type: ignore[arg-type] + self.app = asgi_app + self.app_state: dict[str, Any] = {} + transport = _TestClientTransport( + self.app, + portal_factory=self._portal_factory, + raise_server_exceptions=raise_server_exceptions, + root_path=root_path, + app_state=self.app_state, + client=client, + ) + if headers is None: + headers = {} + headers.setdefault("user-agent", "testclient") + super().__init__( + base_url=base_url, + headers=headers, + transport=transport, + follow_redirects=follow_redirects, + cookies=cookies, + ) + + @contextlib.contextmanager + def _portal_factory(self) -> Generator[anyio.from_thread.BlockingPortal, None, None]: + if self.portal is not None: + yield self.portal + else: + with anyio.from_thread.start_blocking_portal(**self.async_backend) as portal: + yield portal + + def request( # type: ignore[override] + self, + method: str, + url: httpx._types.URLTypes, + *, + content: httpx._types.RequestContent | None = None, + data: _RequestData | None = None, + files: httpx._types.RequestFiles | None = None, + json: Any = None, + params: httpx._types.QueryParamTypes | None = None, + headers: httpx._types.HeaderTypes | None = None, + cookies: httpx._types.CookieTypes | None = None, + auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT, + follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT, + timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT, + extensions: dict[str, Any] | None = None, + ) -> httpx.Response: + if timeout is not httpx.USE_CLIENT_DEFAULT: + warnings.warn( + "You should not use the 'timeout' argument with the TestClient. " + "See https://github.com/Kludex/starlette/issues/1108 for more information.", + StarletteDeprecationWarning, + stacklevel=2, + ) + url = self._merge_url(url) + return super().request( + method, + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def websocket_connect( + self, + url: str, + subprotocols: Sequence[str] | None = None, + **kwargs: Any, + ) -> WebSocketTestSession: + url = urljoin("ws://testserver", url) + headers = kwargs.get("headers", {}) + headers.setdefault("connection", "upgrade") + headers.setdefault("sec-websocket-key", "testserver==") + headers.setdefault("sec-websocket-version", "13") + if subprotocols is not None: + headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) + kwargs["headers"] = headers + try: + super().request("GET", url, **kwargs) + except _Upgrade as exc: + session = exc.session + else: + raise RuntimeError("Expected WebSocket upgrade") # pragma: no cover + + return session + + def __enter__(self) -> Self: + with contextlib.ExitStack() as stack: + self.portal = portal = stack.enter_context(anyio.from_thread.start_blocking_portal(**self.async_backend)) + + @stack.callback + def reset_portal() -> None: + self.portal = None + + send = anyio.create_memory_object_stream[Message | None](math.inf) + receive = anyio.create_memory_object_stream[Message](math.inf) + for channel in (*send, *receive): + stack.callback(channel.close) + self.stream_send = StapledObjectStream(*send) + self.stream_receive = StapledObjectStream(*receive) + self.task = portal.start_task_soon(self.lifespan) + portal.call(self.wait_startup) + + @stack.callback + def wait_shutdown() -> None: + portal.call(self.wait_shutdown) + + self.exit_stack = stack.pop_all() + + return self + + def __exit__(self, *args: Any) -> None: + self.exit_stack.close() + + async def lifespan(self) -> None: + scope = {"type": "lifespan", "state": self.app_state} + try: + await self.app(scope, self.stream_receive.receive, self.stream_send.send) + finally: + await self.stream_send.send(None) + + async def wait_startup(self) -> None: + await self.stream_receive.send({"type": "lifespan.startup"}) + + async def receive() -> Any: + message = await self.stream_send.receive() + if message is None: + self.task.result() + return message + + message = await receive() + assert message["type"] in ( + "lifespan.startup.complete", + "lifespan.startup.failed", + ) + if message["type"] == "lifespan.startup.failed": + await receive() + + async def wait_shutdown(self) -> None: + async def receive() -> Any: + message = await self.stream_send.receive() + if message is None: + self.task.result() + return message + + await self.stream_receive.send({"type": "lifespan.shutdown"}) + message = await receive() + assert message["type"] in ( + "lifespan.shutdown.complete", + "lifespan.shutdown.failed", + ) + if message["type"] == "lifespan.shutdown.failed": + await receive() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py new file mode 100644 index 0000000..b767ae1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/types.py @@ -0,0 +1,26 @@ +from collections.abc import Awaitable, Callable, Mapping, MutableMapping +from contextlib import AbstractAsyncContextManager +from typing import TYPE_CHECKING, Any, TypeVar + +if TYPE_CHECKING: + from starlette.requests import Request + from starlette.responses import Response + from starlette.websockets import WebSocket + +AppType = TypeVar("AppType") + +Scope = MutableMapping[str, Any] +Message = MutableMapping[str, Any] + +Receive = Callable[[], Awaitable[Message]] +Send = Callable[[Message], Awaitable[None]] + +ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]] + +StatelessLifespan = Callable[[AppType], AbstractAsyncContextManager[None]] +StatefulLifespan = Callable[[AppType], AbstractAsyncContextManager[Mapping[str, Any]]] +Lifespan = StatelessLifespan[AppType] | StatefulLifespan[AppType] + +HTTPExceptionHandler = Callable[["Request", Exception], "Response | Awaitable[Response]"] +WebSocketExceptionHandler = Callable[["WebSocket", Exception], Awaitable[None]] +ExceptionHandler = HTTPExceptionHandler | WebSocketExceptionHandler diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py new file mode 100644 index 0000000..cf48e87 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/starlette/websockets.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import enum +import json +from collections.abc import AsyncIterator, Iterable +from typing import Any, cast + +from starlette.requests import HTTPConnection, StateT +from starlette.responses import Response +from starlette.types import Message, Receive, Scope, Send + + +class WebSocketState(enum.Enum): + CONNECTING = 0 + CONNECTED = 1 + DISCONNECTED = 2 + RESPONSE = 3 + + +class WebSocketDisconnect(Exception): + def __init__(self, code: int = 1000, reason: str | None = None) -> None: + self.code = code + self.reason = reason or "" + + +class WebSocketDisconnected(RuntimeError): + """ + Raised when attempting to use a disconnected WebSocket. + """ + + +class WebSocket(HTTPConnection[StateT]): + def __init__(self, scope: Scope, receive: Receive, send: Send) -> None: + super().__init__(scope) + assert scope["type"] == "websocket" + self._receive = receive + self._send = send + self.client_state = WebSocketState.CONNECTING + self.application_state = WebSocketState.CONNECTING + + async def receive(self) -> Message: + """ + Receive ASGI websocket messages, ensuring valid state transitions. + """ + if self.client_state == WebSocketState.CONNECTING: + message = await self._receive() + message_type = message["type"] + if message_type != "websocket.connect": + raise RuntimeError(f'Expected ASGI message "websocket.connect", but got {message_type!r}') + self.client_state = WebSocketState.CONNECTED + return message + elif self.client_state == WebSocketState.CONNECTED: + message = await self._receive() + message_type = message["type"] + if message_type not in {"websocket.receive", "websocket.disconnect"}: + raise RuntimeError( + f'Expected ASGI message "websocket.receive" or "websocket.disconnect", but got {message_type!r}' + ) + if message_type == "websocket.disconnect": + self.client_state = WebSocketState.DISCONNECTED + return message + else: + raise WebSocketDisconnected('Cannot call "receive" once a disconnect message has been received.') + + async def send(self, message: Message) -> None: + """ + Send ASGI websocket messages, ensuring valid state transitions. + """ + if self.application_state == WebSocketState.CONNECTING: + message_type = message["type"] + if message_type not in {"websocket.accept", "websocket.close", "websocket.http.response.start"}: + raise RuntimeError( + 'Expected ASGI message "websocket.accept", "websocket.close" or "websocket.http.response.start", ' + f"but got {message_type!r}" + ) + if message_type == "websocket.close": + self.application_state = WebSocketState.DISCONNECTED + elif message_type == "websocket.http.response.start": + self.application_state = WebSocketState.RESPONSE + else: + self.application_state = WebSocketState.CONNECTED + await self._send(message) + elif self.application_state == WebSocketState.CONNECTED: + message_type = message["type"] + if message_type not in {"websocket.send", "websocket.close"}: + raise RuntimeError( + f'Expected ASGI message "websocket.send" or "websocket.close", but got {message_type!r}' + ) + if message_type == "websocket.close": + self.application_state = WebSocketState.DISCONNECTED + try: + await self._send(message) + except OSError: + self.application_state = WebSocketState.DISCONNECTED + raise WebSocketDisconnect(code=1006) + elif self.application_state == WebSocketState.RESPONSE: + message_type = message["type"] + if message_type != "websocket.http.response.body": + raise RuntimeError(f'Expected ASGI message "websocket.http.response.body", but got {message_type!r}') + if not message.get("more_body", False): + self.application_state = WebSocketState.DISCONNECTED + await self._send(message) + else: + raise WebSocketDisconnected('Cannot call "send" once a close message has been sent.') + + async def accept( + self, + subprotocol: str | None = None, + headers: Iterable[tuple[bytes, bytes]] | None = None, + ) -> None: + headers = headers or [] + + if self.client_state == WebSocketState.CONNECTING: # pragma: no branch + # If we haven't yet seen the 'connect' message, then wait for it first. + await self.receive() + await self.send({"type": "websocket.accept", "subprotocol": subprotocol, "headers": headers}) + + def _raise_on_disconnect(self, message: Message) -> None: + if message["type"] == "websocket.disconnect": + raise WebSocketDisconnect(message["code"], message.get("reason")) + + async def receive_text(self) -> str: + if self.application_state != WebSocketState.CONNECTED: + raise WebSocketDisconnected('WebSocket is not connected. Need to call "accept" first.') + message = await self.receive() + self._raise_on_disconnect(message) + return cast(str, message["text"]) + + async def receive_bytes(self) -> bytes: + if self.application_state != WebSocketState.CONNECTED: + raise WebSocketDisconnected('WebSocket is not connected. Need to call "accept" first.') + message = await self.receive() + self._raise_on_disconnect(message) + return cast(bytes, message["bytes"]) + + async def receive_json(self, mode: str = "text") -> Any: + if mode not in {"text", "binary"}: + raise RuntimeError('The "mode" argument should be "text" or "binary".') + if self.application_state != WebSocketState.CONNECTED: + raise WebSocketDisconnected('WebSocket is not connected. Need to call "accept" first.') + message = await self.receive() + self._raise_on_disconnect(message) + + if mode == "text": + text = message["text"] + else: + text = message["bytes"].decode("utf-8") + return json.loads(text) + + async def iter_text(self) -> AsyncIterator[str]: + try: + while True: + yield await self.receive_text() + except WebSocketDisconnect: + pass + + async def iter_bytes(self) -> AsyncIterator[bytes]: + try: + while True: + yield await self.receive_bytes() + except WebSocketDisconnect: + pass + + async def iter_json(self) -> AsyncIterator[Any]: + try: + while True: + yield await self.receive_json() + except WebSocketDisconnect: + pass + + async def send_text(self, data: str) -> None: + await self.send({"type": "websocket.send", "text": data}) + + async def send_bytes(self, data: bytes) -> None: + await self.send({"type": "websocket.send", "bytes": data}) + + async def send_json(self, data: Any, mode: str = "text") -> None: + if mode not in {"text", "binary"}: + raise RuntimeError('The "mode" argument should be "text" or "binary".') + text = json.dumps(data, separators=(",", ":"), ensure_ascii=False) + if mode == "text": + await self.send({"type": "websocket.send", "text": text}) + else: + await self.send({"type": "websocket.send", "bytes": text.encode("utf-8")}) + + async def close(self, code: int = 1000, reason: str | None = None) -> None: + await self.send({"type": "websocket.close", "code": code, "reason": reason or ""}) + + async def send_denial_response(self, response: Response) -> None: + if "websocket.http.response" in self.scope.get("extensions", {}): + await response(self.scope, self.receive, self.send) + else: + raise RuntimeError("The server doesn't support the Websocket Denial Response extension.") + + +class WebSocketClose: + def __init__(self, code: int = 1000, reason: str | None = None) -> None: + self.code = code + self.reason = reason or "" + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + await send({"type": "websocket.close", "code": self.code, "reason": self.reason}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA new file mode 100644 index 0000000..97fd818 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA @@ -0,0 +1,73 @@ +Metadata-Version: 2.4 +Name: typing_extensions +Version: 4.16.0 +Summary: Backported and Experimental Type Hints for Python 3.9+ +Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing +Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-Expression: PSF-2.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Topic :: Software Development +License-File: LICENSE +Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues +Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md +Project-URL: Documentation, https://typing-extensions.readthedocs.io/ +Project-URL: Home, https://github.com/python/typing_extensions +Project-URL: Q & A, https://github.com/python/typing/discussions +Project-URL: Repository, https://github.com/python/typing_extensions + +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) – +[PyPI](https://pypi.org/project/typing-extensions/) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +`typing_extensions` is treated specially by static type checkers such as +mypy and pyright. Objects defined in `typing_extensions` are treated the same +way as equivalent forms in `typing`. + +`typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version will be incremented only for backwards-incompatible changes. +Therefore, it's safe to depend +on `typing_extensions` like this: `typing_extensions ~=x.y`, +where `x.y` is the first version that includes all features you need. +[This](https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release) +is equivalent to `typing_extensions >=x.y, <(x+1)`. Do not depend on `~= x.y.z` +unless you really know what you're doing; that defeats the purpose of +semantic versioning. + +## Included items + +See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a +complete listing of module contents. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md) +for how to contribute to `typing_extensions`. + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD new file mode 100644 index 0000000..b6d2d5e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD @@ -0,0 +1,5 @@ +typing_extensions.py,sha256=QEDKGh7L7gDROFwSqTCE0cW9RvC3dPB-WufpHE9V5pY,165012 +typing_extensions-4.16.0.dist-info/licenses/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936 +typing_extensions-4.16.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +typing_extensions-4.16.0.dist-info/METADATA,sha256=sFCEyh1Qh5hlF42f_5-r6rYb37HzYb-96VQh_8j5vkY,3310 +typing_extensions-4.16.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..f26bcf4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py new file mode 100644 index 0000000..ced7837 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_extensions.py @@ -0,0 +1,4422 @@ +import abc +import builtins +import collections +import collections.abc +import contextlib +import enum +import functools +import inspect +import io +import keyword +import operator +import sys +import types as _types +import typing +import warnings + +# Breakpoint: https://github.com/python/cpython/pull/119891 +if sys.version_info >= (3, 14): + import annotationlib + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', + 'Self', + 'Type', + 'TypeVar', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'Buffer', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'NamedTuple', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsIndex', + 'SupportsInt', + 'SupportsRound', + 'Reader', + 'Writer', + + # One-off things. + 'Annotated', + 'assert_never', + 'assert_type', + 'clear_overloads', + 'dataclass_transform', + 'deprecated', + 'disjoint_base', + 'Doc', + 'evaluate_forward_ref', + 'get_overloads', + 'final', + 'Format', + 'get_annotations', + 'get_args', + 'get_origin', + 'get_original_bases', + 'get_protocol_members', + 'get_type_hints', + 'IntVar', + 'is_protocol', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'override', + 'Protocol', + 'sentinel', + 'Sentinel', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeAliasType', + 'TypeForm', + 'TypeGuard', + 'TypeIs', + 'TYPE_CHECKING', + 'type_repr', + 'Never', + 'NoReturn', + 'ReadOnly', + 'Required', + 'NotRequired', + 'NoDefault', + 'NoExtraItems', + + # Pure aliases, have always been in typing + 'AbstractSet', + 'AnyStr', + 'BinaryIO', + 'Callable', + 'Collection', + 'Container', + 'Dict', + 'ForwardRef', + 'FrozenSet', + 'Generator', + 'Generic', + 'Hashable', + 'IO', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'List', + 'Mapping', + 'MappingView', + 'Match', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Optional', + 'Pattern', + 'Reversible', + 'Sequence', + 'Set', + 'Sized', + 'TextIO', + 'Tuple', + 'Union', + 'ValuesView', + 'cast', + 'no_type_check', +] + +# for backward compatibility +PEP_560 = True +GenericMeta = type +# Breakpoint: https://github.com/python/cpython/pull/116129 +_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta") + +# Added with bpo-45166 to 3.10.1+ and some 3.9 versions +_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__ + + +def _caller(depth=1, default='__main__'): + try: + return sys._getframemodulename(depth + 1) or default + except AttributeError: # For platforms without _getframemodulename() + pass + try: + return sys._getframe(depth + 1).f_globals.get('__name__', default) + except (AttributeError, ValueError): # For platforms without _getframe() + pass + return None + + +# Placeholder for sentinel methods, because sentinels can not have their own sentinels +_sentinel_placeholder = object() + +if hasattr(builtins, "sentinel"): # 3.15+ + sentinel = builtins.sentinel +else: + class sentinel: + """Create a unique sentinel object. + + *name* should be the name of the variable to which the return value + shall be assigned. + """ + + def __init__( + self, + __name: str = _sentinel_placeholder, + __repr: typing.Optional[str] = _sentinel_placeholder, + /, + *, + repr: typing.Optional[str] = None, + name: str = _sentinel_placeholder, + ) -> None: + if name is not _sentinel_placeholder: + warnings.warn( + "Passing 'name' as a keyword argument is deprecated; " + "pass it positionally instead.", + DeprecationWarning, + stacklevel=2, + ) + __name = name + if __name is _sentinel_placeholder: + raise TypeError("First parameter 'name' is required") + if __repr is not _sentinel_placeholder: + warnings.warn( + "Passing 'repr' as a positional argument is deprecated; " + "pass it by keyword instead.", + DeprecationWarning, + stacklevel=2, + ) + repr = __repr + + self._name = __name + self._repr = repr if repr is not None else __name + + # For pickling as a singleton: + self.__module__ = _caller() + + def __init_subclass__(cls): + warnings.warn( + "Subclassing sentinel is deprecated " + "and will be disallowed in Python 3.15", + DeprecationWarning, + stacklevel=2, + ) + super().__init_subclass__() + + def __setattr__(self, attr: str, value: object) -> None: + if attr not in {"_name", "_repr", "__module__"}: + warnings.warn( + f"Setting attribute {attr!r} on sentinel objects is deprecated " + "and will be disallowed in Python 3.15.", + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(attr, value) + + @property + def __name__(self) -> str: + return self._name + + @__name__.setter + def __name__(self, value: str) -> None: + self._name = value + + def __repr__(self) -> str: + return self._repr + + if sys.version_info < (3, 11): + # The presence of this method convinces typing._type_check + # that Sentinels are types. + def __call__(self, *args, **kwargs): + raise TypeError(f"{type(self).__name__!r} object is not callable") + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __reduce__(self) -> str: + """Reduce this sentinel to a singleton.""" + return self.__name__ # Module is taken from the __module__ attribute + +Sentinel = sentinel + +_marker = sentinel("sentinel") + + +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. + +# Breakpoint: https://github.com/python/cpython/pull/27342 +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +else: + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) + + +NoReturn = typing.NoReturn + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + + +# Breakpoint: https://github.com/python/cpython/pull/31841 +if sys.version_info >= (3, 11): + from typing import Any +else: + + class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing_extensions.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + if self is Any: + return "typing_extensions.Any" + return super().__repr__() + + class Any(metaclass=_AnyMeta): + """Special type indicating an unconstrained type. + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + checks. + """ + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return super().__new__(cls, *args, **kwargs) + + +ClassVar = typing.ClassVar + +# Vendored from cpython typing._SpecialFrom +# Having a separate class means that instances will not be rejected by +# typing._type_check. +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + +# Note that inheriting from this class means that the object will be +# rejected by typing._type_check, so do not use it if the special form +# is arguably valid as a type by itself. +class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + +Final = typing.Final + +# Breakpoint: https://github.com/python/cpython/pull/30530 +if sys.version_info >= (3, 11): + final = typing.final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +if hasattr(typing, "disjoint_base"): # 3.15 + disjoint_base = typing.disjoint_base +else: + def disjoint_base(cls): + """This decorator marks a class as a disjoint base. + + Child classes of a disjoint base cannot inherit from other disjoint bases that are + not parent classes of the disjoint base. + + For example: + + @disjoint_base + class Disjoint1: pass + + @disjoint_base + class Disjoint2: pass + + class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error + + Type checkers can use knowledge of disjoint bases to detect unreachable code + and determine when two types can overlap. + + See PEP 800.""" + cls.__disjoint_base__ = True + return cls + + +def IntVar(name): + return typing.TypeVar(name) + + +# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8 +# Breakpoint: https://github.com/python/cpython/pull/29334 +if sys.version_info >= (3, 10, 1): + Literal = typing.Literal +else: + def _flatten_literal_params(parameters): + """An internal helper for Literal creation: flatten Literals among parameters""" + params = [] + for p in parameters: + if isinstance(p, _LiteralGenericAlias): + params.extend(p.__args__) + else: + params.append(p) + return tuple(params) + + def _value_and_type_iter(params): + for p in params: + yield p, type(p) + + class _LiteralGenericAlias(typing._GenericAlias, _root=True): + def __eq__(self, other): + if not isinstance(other, _LiteralGenericAlias): + return NotImplemented + these_args_deduped = set(_value_and_type_iter(self.__args__)) + other_args_deduped = set(_value_and_type_iter(other.__args__)) + return these_args_deduped == other_args_deduped + + def __hash__(self): + return hash(frozenset(_value_and_type_iter(self.__args__))) + + class _LiteralForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, doc: str): + self._name = 'Literal' + self._doc = self.__doc__ = doc + + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + + parameters = _flatten_literal_params(parameters) + + val_type_pairs = list(_value_and_type_iter(parameters)) + try: + deduped_pairs = set(val_type_pairs) + except TypeError: + # unhashable parameters + pass + else: + # similar logic to typing._deduplicate on Python 3.9+ + if len(deduped_pairs) < len(val_type_pairs): + new_parameters = [] + for pair in val_type_pairs: + if pair in deduped_pairs: + new_parameters.append(pair[0]) + deduped_pairs.remove(pair) + assert not deduped_pairs, deduped_pairs + parameters = tuple(new_parameters) + + return _LiteralGenericAlias(self, parameters) + + Literal = _LiteralForm(doc="""\ + A type that can be used to indicate to type checkers + that the corresponding value has a value literally equivalent + to the provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to + the value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime + checking verifying that the parameter is actually a value + instead of a type.""") + + +_overload_dummy = typing._overload_dummy + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() + + +# This is not a real generic class. Don't use outside annotations. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator +Deque = typing.Deque +DefaultDict = typing.DefaultDict +OrderedDict = typing.OrderedDict +Counter = typing.Counter +ChainMap = typing.ChainMap +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +# Breakpoint: https://github.com/python/cpython/pull/118681 +if sys.version_info >= (3, 13, 0, "beta"): + from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator +else: + def _is_dunder(attr): + return attr.startswith('__') and attr.endswith('__') + + + class _SpecialGenericAlias(typing._SpecialGenericAlias, _root=True): + def __init__(self, origin, nparams, *, defaults, inst=True, name=None): + assert nparams > 0, "`nparams` must be a positive integer" + assert defaults, "Must always specify a non-empty sequence for `defaults`" + super().__init__(origin, nparams, inst=inst, name=name) + self._defaults = defaults + + def __setattr__(self, attr, val): + allowed_attrs = {'_name', '_inst', '_nparams', '_defaults'} + if _is_dunder(attr) or attr in allowed_attrs: + object.__setattr__(self, attr, val) + else: + setattr(self.__origin__, attr, val) + + @typing._tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) + if ( + len(params) < self._nparams + and len(params) + len(self._defaults) >= self._nparams + ): + params = (*params, *self._defaults[len(params) - self._nparams:]) + actual_len = len(params) + + if actual_len != self._nparams: + expected = f"at least {self._nparams - len(self._defaults)}" + raise TypeError( + f"Too {'many' if actual_len > self._nparams else 'few'}" + f" arguments for {self};" + f" actual {actual_len}, expected {expected}" + ) + return self.copy_with(params) + + _NoneType = type(None) + Generator = _SpecialGenericAlias( + collections.abc.Generator, 3, defaults=(_NoneType, _NoneType) + ) + AsyncGenerator = _SpecialGenericAlias( + collections.abc.AsyncGenerator, 2, defaults=(_NoneType,) + ) + ContextManager = _SpecialGenericAlias( + contextlib.AbstractContextManager, + 2, + name="ContextManager", + defaults=(typing.Optional[bool],) + ) + AsyncContextManager = _SpecialGenericAlias( + contextlib.AbstractAsyncContextManager, + 2, + name="AsyncContextManager", + defaults=(typing.Optional[bool],) + ) + + +_PROTO_ALLOWLIST = { + 'collections.abc': [ + 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', + 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', + 'Reversible', 'Buffer', + ], + 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], + 'io': ['Reader', 'Writer'], + 'typing_extensions': ['Buffer'], + 'os': ['PathLike'], +} + + +_EXCLUDED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | { + "__match_args__", "__protocol_attrs__", "__non_callable_proto_members__", + "__final__", +} + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in {'Protocol', 'Generic'}: + continue + annotations = getattr(base, '__annotations__', {}) + for attr in (*base.__dict__, *annotations): + if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS): + attrs.add(attr) + return attrs + + +# `__match_args__` attribute was removed from protocol members in 3.13, +# we want to backport this change to older Python versions. +# 3.14 additionally added `io.Reader`, `io.Writer` and `os.PathLike` to +# the list of allowed protocol allowlist. +# https://github.com/python/cpython/issues/127647 +if sys.version_info >= (3, 14): + Protocol = typing.Protocol +else: + def _allow_reckless_class_checks(depth=2): + """Allow instance and class checks for special stdlib modules. + The abc and functools modules indiscriminately call isinstance() and + issubclass() on the whole MRO of a user class, which may contain protocols. + """ + return _caller(depth) in {'abc', 'functools', None} + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + def _type_check_issubclass_arg_1(arg): + """Raise TypeError if `arg` is not an instance of `type` + in `issubclass(arg, )`. + + In most cases, this is verified by type.__subclasscheck__. + Checking it again unnecessarily would slow down issubclass() checks, + so, we don't perform this check unless we absolutely have to. + + For various error paths, however, + we want to ensure that *this* error message is shown to the user + where relevant, rather than a typing.py-specific error message. + """ + if not isinstance(arg, type): + # Same error message as for issubclass(1, int). + raise TypeError('issubclass() arg 1 must be a class') + + # Inheriting from typing._ProtocolMeta isn't actually desirable, + # but is necessary to allow typing.Protocol and typing_extensions.Protocol + # to mix without getting TypeErrors about "metaclass conflict" + class _ProtocolMeta(type(typing.Protocol)): + # This metaclass is somewhat unfortunate, + # but is necessary for several reasons... + # + # NOTE: DO NOT call super() in any methods in this class + # That would call the methods on typing._ProtocolMeta on Python <=3.11 + # and those are slow + def __new__(mcls, name, bases, namespace, **kwargs): + if name == "Protocol" and len(bases) < 2: + pass + elif {Protocol, typing.Protocol} & set(bases): + for base in bases: + if not ( + base in {object, typing.Generic, Protocol, typing.Protocol} + or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, []) + or is_protocol(base) + ): + raise TypeError( + f"Protocols can only inherit from other protocols, " + f"got {base!r}" + ) + return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs) + + def __init__(cls, *args, **kwargs): + abc.ABCMeta.__init__(cls, *args, **kwargs) + if getattr(cls, "_is_protocol", False): + cls.__protocol_attrs__ = _get_protocol_attrs(cls) + + def __subclasscheck__(cls, other): + if cls is Protocol: + return type.__subclasscheck__(cls, other) + if ( + getattr(cls, '_is_protocol', False) + and not _allow_reckless_class_checks() + ): + if not getattr(cls, '_is_runtime_protocol', False): + _type_check_issubclass_arg_1(other) + raise TypeError( + "Instance and class checks can only be used with " + "@runtime_checkable protocols" + ) + if ( + # this attribute is set by @runtime_checkable: + cls.__non_callable_proto_members__ + and cls.__dict__.get("__subclasshook__") is _proto_hook + ): + _type_check_issubclass_arg_1(other) + non_method_attrs = sorted(cls.__non_callable_proto_members__) + raise TypeError( + "Protocols with non-method members don't support issubclass()." + f" Non-method members: {str(non_method_attrs)[1:-1]}." + ) + return abc.ABCMeta.__subclasscheck__(cls, other) + + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if cls is Protocol: + return type.__instancecheck__(cls, instance) + if not getattr(cls, "_is_protocol", False): + # i.e., it's a concrete subclass of a protocol + return abc.ABCMeta.__instancecheck__(cls, instance) + + if ( + not getattr(cls, '_is_runtime_protocol', False) and + not _allow_reckless_class_checks() + ): + raise TypeError("Instance and class checks can only be used with" + " @runtime_checkable protocols") + + if abc.ABCMeta.__instancecheck__(cls, instance): + return True + + for attr in cls.__protocol_attrs__: + try: + val = inspect.getattr_static(instance, attr) + except AttributeError: + break + # this attribute is set by @runtime_checkable: + if val is None and attr not in cls.__non_callable_proto_members__: + break + else: + return True + + return False + + def __eq__(cls, other): + # Hack so that typing.Generic.__class_getitem__ + # treats typing_extensions.Protocol + # as equivalent to typing.Protocol + if abc.ABCMeta.__eq__(cls, other) is True: + return True + return cls is Protocol and other is typing.Protocol + + # This has to be defined, or the abc-module cache + # complains about classes with this metaclass being unhashable, + # if we define only __eq__! + def __hash__(cls) -> int: + return type.__hash__(cls) + + @classmethod + def _proto_hook(cls, other): + if not cls.__dict__.get('_is_protocol', False): + return NotImplemented + + for attr in cls.__protocol_attrs__: + for base in other.__mro__: + # Check if the members appears in the class dictionary... + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + + # ...or in annotations, if it is a sub-protocol. + annotations = getattr(base, '__annotations__', {}) + if ( + isinstance(annotations, collections.abc.Mapping) + and attr in annotations + and is_protocol(other) + ): + break + else: + return NotImplemented + return True + + class Protocol(typing.Generic, metaclass=_ProtocolMeta): + __doc__ = typing.Protocol.__doc__ + __slots__ = () + _is_protocol = True + _is_runtime_protocol = False + + def __init_subclass__(cls, *args, **kwargs): + super().__init_subclass__(*args, **kwargs) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', False): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # Prohibit instantiation for protocol classes + if cls._is_protocol and cls.__init__ is Protocol.__init__: + cls.__init__ = _no_init + + +# Breakpoint: https://github.com/python/cpython/pull/113401 +if sys.version_info >= (3, 13): + runtime_checkable = typing.runtime_checkable +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol. + + Such protocol can be used with isinstance() and issubclass(). + Raise TypeError if applied to a non-protocol class. + This allows a simple-minded structural check very similar to + one trick ponies in collections.abc such as Iterable. + + For example:: + + @runtime_checkable + class Closable(Protocol): + def close(self): ... + + assert isinstance(open('/some/file'), Closable) + + Warning: this will check only the presence of the required methods, + not their type signatures! + """ + if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False): + raise TypeError(f'@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + + # typing.Protocol classes on <=3.11 break if we execute this block, + # because typing.Protocol classes on <=3.11 don't have a + # `__protocol_attrs__` attribute, and this block relies on the + # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+ + # break if we *don't* execute this block, because *they* assume that all + # protocol classes have a `__non_callable_proto_members__` attribute + # (which this block sets) + if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2): + # PEP 544 prohibits using issubclass() + # with protocols that have non-method members. + # See gh-113320 for why we compute this attribute here, + # rather than in `_ProtocolMeta.__init__` + cls.__non_callable_proto_members__ = set() + for attr in cls.__protocol_attrs__: + try: + is_callable = callable(getattr(cls, attr, None)) + except Exception as e: + raise TypeError( + f"Failed to determine whether protocol member {attr!r} " + "is a method member" + ) from e + else: + if not is_callable: + cls.__non_callable_proto_members__.add(attr) + + return cls + + +# The "runtime" alias exists for backwards compatibility. +runtime = runtime_checkable + + +# Our version of runtime-checkable protocols is faster on Python <=3.11 +# Breakpoint: https://github.com/python/cpython/pull/112717 +if sys.version_info >= (3, 12): + SupportsInt = typing.SupportsInt + SupportsFloat = typing.SupportsFloat + SupportsComplex = typing.SupportsComplex + SupportsBytes = typing.SupportsBytes + SupportsIndex = typing.SupportsIndex + SupportsAbs = typing.SupportsAbs + SupportsRound = typing.SupportsRound +else: + @runtime_checkable + class SupportsInt(Protocol): + """An ABC with one abstract method __int__.""" + __slots__ = () + + @abc.abstractmethod + def __int__(self) -> int: + pass + + @runtime_checkable + class SupportsFloat(Protocol): + """An ABC with one abstract method __float__.""" + __slots__ = () + + @abc.abstractmethod + def __float__(self) -> float: + pass + + @runtime_checkable + class SupportsComplex(Protocol): + """An ABC with one abstract method __complex__.""" + __slots__ = () + + @abc.abstractmethod + def __complex__(self) -> complex: + pass + + @runtime_checkable + class SupportsBytes(Protocol): + """An ABC with one abstract method __bytes__.""" + __slots__ = () + + @abc.abstractmethod + def __bytes__(self) -> bytes: + pass + + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + @runtime_checkable + class SupportsAbs(Protocol[T_co]): + """ + An ABC with one abstract method __abs__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __abs__(self) -> T_co: + pass + + @runtime_checkable + class SupportsRound(Protocol[T_co]): + """ + An ABC with one abstract method __round__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +if hasattr(io, "Reader") and hasattr(io, "Writer"): + Reader = io.Reader + Writer = io.Writer +else: + @runtime_checkable + class Reader(Protocol[T_co]): + """Protocol for simple I/O reader instances. + + This protocol only supports blocking I/O. + """ + + __slots__ = () + + @abc.abstractmethod + def read(self, size: int = ..., /) -> T_co: + """Read data from the input stream and return it. + + If *size* is specified, at most *size* items (bytes/characters) will be + read. + """ + + @runtime_checkable + class Writer(Protocol[T_contra]): + """Protocol for simple I/O writer instances. + + This protocol only supports blocking I/O. + """ + + __slots__ = () + + @abc.abstractmethod + def write(self, data: T_contra, /) -> int: + """Write *data* to the output stream and return the number of items written.""" # noqa: E501 + + +_NEEDS_SINGLETONMETA = ( + not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems") +) + +if _NEEDS_SINGLETONMETA: + class SingletonMeta(type): + def __setattr__(cls, attr, value): + # TypeError is consistent with the behavior of NoneType + raise TypeError( + f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}" + ) + + +if hasattr(typing, "NoDefault"): + NoDefault = typing.NoDefault +else: + class NoDefaultType(metaclass=SingletonMeta): + """The type of the NoDefault singleton.""" + + __slots__ = () + + def __new__(cls): + return globals().get("NoDefault") or object.__new__(cls) + + def __repr__(self): + return "typing_extensions.NoDefault" + + def __reduce__(self): + return "NoDefault" + + NoDefault = NoDefaultType() + del NoDefaultType + +if hasattr(typing, "NoExtraItems"): + NoExtraItems = typing.NoExtraItems +else: + class NoExtraItemsType(metaclass=SingletonMeta): + """The type of the NoExtraItems singleton.""" + + __slots__ = () + + def __new__(cls): + return globals().get("NoExtraItems") or object.__new__(cls) + + def __repr__(self): + return "typing_extensions.NoExtraItems" + + def __reduce__(self): + return "NoExtraItems" + + NoExtraItems = NoExtraItemsType() + del NoExtraItemsType + +if _NEEDS_SINGLETONMETA: + del SingletonMeta + + +# Update this to something like >=3.13.0b1 if and when +# PEP 764 is implemented in CPython +_PEP_764_IMPLEMENTED = False + +if _PEP_764_IMPLEMENTED: + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. + # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11. + # Aaaand on 3.12 we add __orig_bases__ to TypedDict + # to enable better runtime introspection. + # On 3.13 we deprecate some odd ways of creating TypedDicts. + # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier. + # PEP 728 (Python 3.15+) adds the `extra_items` and `closed` keywords. + # PEP 764 (still pending) allows the `TypedDict` special form to be subscripted. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + # 3.10.0 and later + _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters + + def _get_typeddict_qualifiers(annotation_type): + while True: + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + else: + break + elif annotation_origin is Required: + yield Required + annotation_type, = get_args(annotation_type) + elif annotation_origin is NotRequired: + yield NotRequired + annotation_type, = get_args(annotation_type) + elif annotation_origin is ReadOnly: + yield ReadOnly + annotation_type, = get_args(annotation_type) + else: + break + + class _TypedDictMeta(type): + + def __new__(cls, name, bases, ns, *, total=True, closed=None, + extra_items=NoExtraItems): + """Create new typed dict class object. + + This method is called when TypedDict is subclassed, + or when TypedDict is instantiated. This way + TypedDict supports all three syntax forms described in its docstring. + Subclasses and instances of TypedDict return actual dictionaries. + """ + for base in bases: + if type(base) is not _TypedDictMeta and base is not typing.Generic: + raise TypeError('cannot inherit from both a TypedDict type ' + 'and a non-TypedDict base class') + if closed is not None and extra_items is not NoExtraItems: + raise TypeError(f"Cannot combine closed={closed!r} and extra_items") + + if any(issubclass(b, typing.Generic) for b in bases): + generic_base = (typing.Generic,) + else: + generic_base = () + + ns_annotations = ns.pop('__annotations__', None) + + # typing.py generally doesn't let you inherit from plain Generic, unless + # the name of the class happens to be "Protocol" + tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns) + tp_dict.__name__ = name + if tp_dict.__qualname__ == "Protocol": + tp_dict.__qualname__ = name + + if not hasattr(tp_dict, '__orig_bases__'): + tp_dict.__orig_bases__ = bases + + annotations = {} + own_annotate = None + if ns_annotations is not None: + own_annotations = ns_annotations + elif sys.version_info >= (3, 14): + if hasattr(annotationlib, "get_annotate_from_class_namespace"): + own_annotate = annotationlib.get_annotate_from_class_namespace(ns) + else: + # 3.14.0a7 and earlier + own_annotate = ns.get("__annotate__") + if own_annotate is not None: + own_annotations = annotationlib.call_annotate_function( + own_annotate, Format.FORWARDREF, owner=tp_dict + ) + else: + own_annotations = {} + else: + own_annotations = {} + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + if _TAKES_MODULE: + own_checked_annotations = { + n: typing._type_check(tp, msg, module=tp_dict.__module__) + for n, tp in own_annotations.items() + } + else: + own_checked_annotations = { + n: typing._type_check(tp, msg) + for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + readonly_keys = set() + mutable_keys = set() + extra_items_type = extra_items + + for base in bases: + base_dict = base.__dict__ + + if sys.version_info <= (3, 14): + annotations.update(base_dict.get('__annotations__', {})) + base_required = base_dict.get('__required_keys__', set()) + required_keys |= base_required + optional_keys -= base_required + + base_optional = base_dict.get('__optional_keys__', set()) + required_keys -= base_optional + optional_keys |= base_optional + + readonly_keys.update(base_dict.get('__readonly_keys__', ())) + mutable_keys.update(base_dict.get('__mutable_keys__', ())) + + # This was specified in an earlier version of PEP 728. Support + # is retained for backwards compatibility, but only for Python + # 3.13 and lower. + if (closed and sys.version_info < (3, 14) + and "__extra_items__" in own_checked_annotations): + annotation_type = own_checked_annotations.pop("__extra_items__") + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + if Required in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "Required" + ) + if NotRequired in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "NotRequired" + ) + extra_items_type = annotation_type + + annotations.update(own_checked_annotations) + for annotation_key, annotation_type in own_checked_annotations.items(): + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + + if Required in qualifiers: + is_required = True + elif NotRequired in qualifiers: + is_required = False + else: + is_required = total + + if is_required: + required_keys.add(annotation_key) + optional_keys.discard(annotation_key) + else: + optional_keys.add(annotation_key) + required_keys.discard(annotation_key) + + if ReadOnly in qualifiers: + mutable_keys.discard(annotation_key) + readonly_keys.add(annotation_key) + else: + mutable_keys.add(annotation_key) + readonly_keys.discard(annotation_key) + + # Breakpoint: https://github.com/python/cpython/pull/119891 + if sys.version_info >= (3, 14): + def __annotate__(format): + annos = {} + for base in bases: + if base is Generic: + continue + base_annotate = base.__annotate__ + if base_annotate is None: + continue + base_annos = annotationlib.call_annotate_function( + base_annotate, format, owner=base) + annos.update(base_annos) + if own_annotate is not None: + own = annotationlib.call_annotate_function( + own_annotate, format, owner=tp_dict) + if format != Format.STRING: + own = { + n: typing._type_check(tp, msg, module=tp_dict.__module__) + for n, tp in own.items() + } + elif format == Format.STRING: + own = annotationlib.annotations_to_string(own_annotations) + elif format in (Format.FORWARDREF, Format.VALUE): + own = own_checked_annotations + else: + raise NotImplementedError(format) + annos.update(own) + return annos + + tp_dict.__annotate__ = __annotate__ + else: + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + tp_dict.__readonly_keys__ = frozenset(readonly_keys) + tp_dict.__mutable_keys__ = frozenset(mutable_keys) + tp_dict.__total__ = total + tp_dict.__closed__ = closed + tp_dict.__extra_items__ = extra_items_type + return tp_dict + + __call__ = dict # static method + + def __subclasscheck__(cls, other): + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + + __instancecheck__ = __subclasscheck__ + + _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) + + def _create_typeddict( + typename, + fields, + /, + *, + typing_is_inline, + total, + closed, + extra_items, + **kwargs, + ): + if fields is _marker or fields is None: + if fields is _marker: + deprecated_thing = ( + "Failing to pass a value for the 'fields' parameter" + ) + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + + example = f"`{typename} = TypedDict({typename!r}, {{}})`" + deprecation_msg = ( + f"{deprecated_thing} is deprecated and will be disallowed in " + "Python 3.15. To create a TypedDict class with 0 fields " + "using the functional syntax, pass an empty dictionary, e.g. " + ) + example + "." + warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) + # Support a field called "closed" + if closed is not False and closed is not True and closed is not None: + kwargs["closed"] = closed + closed = None + # Or "extra_items" + if extra_items is not NoExtraItems: + kwargs["extra_items"] = extra_items + extra_items = NoExtraItems + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + if kwargs: + # Breakpoint: https://github.com/python/cpython/pull/104891 + if sys.version_info >= (3, 13): + raise TypeError("TypedDict takes no keyword arguments") + warnings.warn( + "The kwargs-based syntax for TypedDict definitions is deprecated " + "in Python 3.11, will be removed in Python 3.13, and may not be " + "understood by third-party type checkers.", + DeprecationWarning, + stacklevel=2, + ) + + ns = {'__annotations__': dict(fields)} + module = _caller(depth=4 if typing_is_inline else 2) + if module is not None: + # Setting correct module is necessary to make typed dict classes + # pickleable. + ns['__module__'] = module + + td = _TypedDictMeta(typename, (), ns, total=total, closed=closed, + extra_items=extra_items) + td.__orig_bases__ = (TypedDict,) + return td + + class _TypedDictSpecialForm(_SpecialForm, _root=True): + def __call__( + self, + typename, + fields=_marker, + /, + *, + total=True, + closed=None, + extra_items=NoExtraItems, + **kwargs + ): + return _create_typeddict( + typename, + fields, + typing_is_inline=False, + total=total, + closed=closed, + extra_items=extra_items, + **kwargs, + ) + + def __mro_entries__(self, bases): + return (_TypedDict,) + + @_TypedDictSpecialForm + def TypedDict(self, args): + """A simple typed namespace. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type such that a type checker will expect all + instances to have a certain set of keys, where each key is + associated with a value of a consistent type. This expectation + is not checked at runtime. + + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports an additional equivalent form:: + + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + By default, all keys must be present in a TypedDict. It is possible + to override this by specifying totality:: + + class Point2D(TypedDict, total=False): + x: int + y: int + + This means that a Point2D TypedDict can have any of the keys omitted. A type + checker is only expected to support a literal False or True as the value of + the total argument. True is the default, and makes all items defined in the + class body be required. + + The Required and NotRequired special forms can also be used to mark + individual keys as being required or not required:: + + class Point2D(TypedDict): + x: int # the "x" key must always be present (Required is the default) + y: NotRequired[int] # the "y" key can be omitted + + See PEP 655 for more details on Required and NotRequired. + """ + # This runs when creating inline TypedDicts: + if not isinstance(args, dict): + raise TypeError( + "TypedDict[...] should be used with a single dict argument" + ) + + return _create_typeddict( + "", + args, + typing_is_inline=True, + total=True, + closed=True, + extra_items=NoExtraItems, + ) + + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + return isinstance(tp, _TYPEDDICT_TYPES) + + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(val, typ, /): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return val + + +if hasattr(typing, "ReadOnly"): # 3.13+ + get_type_hints = typing.get_type_hints +else: # <=3.13 + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, typing._AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return _types.GenericAlias(t.__origin__, stripped_args) + if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + # Breakpoint: https://github.com/python/cpython/pull/30304 + if sys.version_info < (3, 11): + _clean_optional(obj, hint, globalns, localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + _NoneType = type(None) + + def _could_be_inserted_optional(t): + """detects Union[..., None] pattern""" + if not isinstance(t, typing._UnionGenericAlias): + return False + # Assume if last argument is not None they are user defined + if t.__args__[-1] is not _NoneType: + return False + return True + + # < 3.11 + def _clean_optional(obj, hints, globalns=None, localns=None): + # reverts injected Union[..., None] cases from typing.get_type_hints + # when a None default value is used. + # see https://github.com/python/typing_extensions/issues/310 + if not hints or isinstance(obj, type): + return + defaults = typing._get_defaults(obj) # avoid accessing __annotations___ + if not defaults: + return + original_hints = obj.__annotations__ + for name, value in hints.items(): + # Not a Union[..., None] or replacement conditions not fullfilled + if (not _could_be_inserted_optional(value) + or name not in defaults + or defaults[name] is not None + ): + continue + original_value = original_hints[name] + # value=NoneType should have caused a skip above but check for safety + if original_value is None: + original_value = _NoneType + # Forward reference + if isinstance(original_value, str): + if globalns is None: + if isinstance(obj, _types.ModuleType): + globalns = obj.__dict__ + else: + nsobj = obj + # Find globalns for the unwrapped object. + while hasattr(nsobj, '__wrapped__'): + nsobj = nsobj.__wrapped__ + globalns = getattr(nsobj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + + original_value = ForwardRef( + original_value, + is_argument=not isinstance(obj, _types.ModuleType) + ) + original_evaluated = typing._eval_type(original_value, globalns, localns) + # Compare if values differ. Note that even if equal + # value might be cached by typing._tp_cache contrary to original_evaluated + if original_evaluated != value or ( + # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias + hasattr(_types, "UnionType") + and isinstance(original_evaluated, _types.UnionType) + and not isinstance(value, _types.UnionType) + ): + hints[name] = original_evaluated + +# Python 3.9 has get_origin() and get_args() but those implementations don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +# Breakpoint: https://github.com/python/cpython/pull/25298 +if sys.version_info >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.9 +else: + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, typing._AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._BaseGenericAlias, _types.GenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, typing._AnnotatedAlias): + return (tp.__origin__, *tp.__metadata__) + if isinstance(tp, (typing._GenericAlias, _types.GenericAlias)): + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +else: + @_ExtensionsSpecialForm + def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") + + +def _set_default(type_param, default): + type_param.has_default = lambda: default is not NoDefault + type_param.__default__ = default + + +def _set_module(typevarlike): + # for pickling: + def_mod = _caller(depth=2) + if def_mod != 'typing_extensions': + typevarlike.__module__ = def_mod + + +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + __slots__ = () + __init__ = _set_default + + +# Classes using this metaclass must provide a _backported_typevarlike ClassVar +class _TypeVarLikeMeta(type): + def __instancecheck__(cls, __instance: Any) -> bool: + return isinstance(__instance, cls._backported_typevarlike) + + +if _PEP_696_IMPLEMENTED: + from typing import TypeVar +else: + # Add default and infer_variance parameters from PEP 696 and 695 + class TypeVar(metaclass=_TypeVarLikeMeta): + """Type variable.""" + + _backported_typevarlike = typing.TypeVar + + def __new__(cls, name, *constraints, bound=None, + covariant=False, contravariant=False, + default=NoDefault, infer_variance=False): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant, + infer_variance=infer_variance) + else: + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + if infer_variance and (covariant or contravariant): + raise ValueError("Variance cannot be specified with infer_variance.") + typevar.__infer_variance__ = infer_variance + + _set_default(typevar, default) + _set_module(typevar) + + def _tvar_prepare_subst(alias, args): + if ( + typevar.has_default() + and alias.__parameters__.index(typevar) == len(args) + ): + args += (typevar.__default__,) + return args + + typevar.__typing_prepare_subst__ = _tvar_prepare_subst + return typevar + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type") + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + + +if _PEP_696_IMPLEMENTED: + from typing import ParamSpec + +# 3.10+ +elif hasattr(typing, 'ParamSpec'): + + # Add default parameter - PEP 696 + class ParamSpec(metaclass=_TypeVarLikeMeta): + """Parameter specification.""" + + _backported_typevarlike = typing.ParamSpec + + def __new__(cls, name, *, bound=None, + covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented, can pass infer_variance to typing.TypeVar + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant, + infer_variance=infer_variance) + else: + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant) + paramspec.__infer_variance__ = bool(infer_variance) + + _set_default(paramspec, default) + _set_module(paramspec) + + def _paramspec_prepare_subst(alias, args): + params = alias.__parameters__ + i = params.index(paramspec) + if i == len(args) and paramspec.has_default(): + args = [*args, paramspec.__default__] + if i >= len(args): + raise TypeError(f"Too few arguments for {alias}") + # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. + if len(params) == 1 and not typing._is_param_expr(args[0]): + assert i == 0 + args = (args,) + # Convert lists to tuples to help other libraries cache the results. + elif isinstance(args[i], list): + args = (*args[:i], tuple(args[i]), *args[i + 1:]) + return args + + paramspec.__typing_prepare_subst__ = _paramspec_prepare_subst + return paramspec + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") + +# 3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list, _DefaultMixin): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + list.__init__(self, [self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + self.__infer_variance__ = bool(infer_variance) + self.__bound__ = bound + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__infer_variance__: + prefix = '' + elif self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") + + +# 3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + + # 3.9.0-1 + if not hasattr(typing, '_type_convert'): + def _type_convert(arg, module=None, *, allow_special_forms=False): + """For converting None to type(None), and strings to ForwardRef.""" + if arg is None: + return type(None) + if isinstance(arg, str): + if sys.version_info <= (3, 9, 6): + return ForwardRef(arg) + if sys.version_info <= (3, 9, 7): + return ForwardRef(arg, module=module) + return ForwardRef(arg, module=module, is_class=allow_special_forms) + return arg + else: + _type_convert = typing._type_convert + + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + __class__ = typing._GenericAlias + + def __init__(self, origin, args): + # Cannot use `super().__init__` here because of the `__class__` assignment + # in the class body (https://github.com/python/typing_extensions/issues/661) + list.__init__(self, args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + # 3.9 used by __getitem__ below + def copy_with(self, params): + if isinstance(params[-1], _ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + return self.__class__(self.__origin__, params) + + # 3.9; accessed during GenericAlias.__getitem__ when substituting + def __getitem__(self, args): + if self.__origin__ in (Generic, Protocol): + # Can't subscript Generic[...] or Protocol[...]. + raise TypeError(f"Cannot subscript already-subscripted {self}") + if not self.__parameters__: + raise TypeError(f"{self} is not a generic class") + + if not isinstance(args, tuple): + args = (args,) + args = _unpack_args(*(_type_convert(p) for p in args)) + params = self.__parameters__ + for param in params: + prepare = getattr(param, "__typing_prepare_subst__", None) + if prepare is not None: + args = prepare(self, args) + # 3.9 & typing.ParamSpec + elif isinstance(param, ParamSpec): + i = params.index(param) + if ( + i == len(args) + and getattr(param, '__default__', NoDefault) is not NoDefault + ): + args = [*args, param.__default__] + if i >= len(args): + raise TypeError(f"Too few arguments for {self}") + # Special case for Z[[int, str, bool]] == Z[int, str, bool] + if len(params) == 1 and not _is_param_expr(args[0]): + assert i == 0 + args = (args,) + elif ( + isinstance(args[i], list) + # 3.9 + # This class inherits from list do not convert + and not isinstance(args[i], _ConcatenateGenericAlias) + ): + args = (*args[:i], tuple(args[i]), *args[i + 1:]) + + alen = len(args) + plen = len(params) + if alen != plen: + raise TypeError( + f"Too {'many' if alen > plen else 'few'} arguments for {self};" + f" actual {alen}, expected {plen}" + ) + + subst = dict(zip(self.__parameters__, args)) + # determine new args + new_args = [] + for arg in self.__args__: + if isinstance(arg, type): + new_args.append(arg) + continue + if isinstance(arg, TypeVar): + arg = subst[arg] + if ( + (isinstance(arg, typing._GenericAlias) and _is_unpack(arg)) + or ( + hasattr(_types, "GenericAlias") + and isinstance(arg, _types.GenericAlias) + and getattr(arg, "__unpacked__", False) + ) + ): + raise TypeError(f"{arg} is not valid as type argument") + + elif isinstance(arg, + typing._GenericAlias + if not hasattr(_types, "GenericAlias") else + (typing._GenericAlias, _types.GenericAlias) + ): + subparams = arg.__parameters__ + if subparams: + subargs = tuple(subst[x] for x in subparams) + arg = arg[subargs] + new_args.append(arg) + return self.copy_with(tuple(new_args)) + +# 3.10+ +else: + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias + + # 3.10 + if sys.version_info < (3, 11): + + class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True): + # needed for checks in collections.abc.Callable to accept this class + __module__ = "typing" + + def copy_with(self, params): + if isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + if isinstance(params[-1], typing._ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + return super(typing._ConcatenateGenericAlias, self).copy_with(params) + + def __getitem__(self, args): + value = super().__getitem__(args) + if isinstance(value, tuple) and any(_is_unpack(t) for t in value): + return tuple(_unpack_args(*(n for n in value))) + return value + + +# 3.9.2 +class _EllipsisDummy: ... + + +# <=3.10 +def _create_concatenate_alias(origin, parameters): + if parameters[-1] is ... and sys.version_info < (3, 9, 2): + # Hack: Arguments must be types, replace it with one. + parameters = (*parameters[:-1], _EllipsisDummy) + if sys.version_info >= (3, 10, 3): + concatenate = _ConcatenateGenericAlias(origin, parameters, + _typevar_types=(TypeVar, ParamSpec), + _paramspec_tvars=True) + else: + concatenate = _ConcatenateGenericAlias(origin, parameters) + if parameters[-1] is not _EllipsisDummy: + return concatenate + # Remove dummy again + concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ... + for p in concatenate.__args__) + if sys.version_info < (3, 10): + # backport needs __args__ adjustment only + return concatenate + concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__ + if p is not _EllipsisDummy) + return concatenate + + +# <=3.10 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]), + parameters[-1]) + return _create_concatenate_alias(self, parameters) + + +# 3.11+; Concatenate does not accept ellipsis in 3.10 +# Breakpoint: https://github.com/python/cpython/pull/30969 +if sys.version_info >= (3, 11): + Concatenate = typing.Concatenate +# <=3.10 +else: + @_ExtensionsSpecialForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) + + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +else: + @_ExtensionsSpecialForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +# 3.13+ +if hasattr(typing, 'TypeIs'): + TypeIs = typing.TypeIs +# <=3.12 +else: + @_ExtensionsSpecialForm + def TypeIs(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type narrower function. ``TypeIs`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeIs[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeIs`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the intersection of the type inside ``TypeIs`` and the argument's + previously known type. + + For example:: + + def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]: + return hasattr(val, '__await__') + + def f(val: Union[int, Awaitable[int]]) -> int: + if is_awaitable(val): + assert_type(val, Awaitable[int]) + else: + assert_type(val, int) + + ``TypeIs`` also works with type variables. For more information, see + PEP 742 (Narrowing types with TypeIs). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +# 3.15+? +if hasattr(typing, 'TypeForm'): + TypeForm = typing.TypeForm +# <=3.14 +else: + class _TypeFormForm(_ExtensionsSpecialForm, _root=True): + # TypeForm(X) is equivalent to X but indicates to the type checker + # that the object is a TypeForm. + def __call__(self, obj, /): + return obj + + @_TypeFormForm + def TypeForm(self, parameters): + """A special form representing the value that results from the evaluation + of a type expression. This value encodes the information supplied in the + type expression, and it represents the type described by that type expression. + + When used in a type expression, TypeForm describes a set of type form objects. + It accepts a single type argument, which must be a valid type expression. + ``TypeForm[T]`` describes the set of all type form objects that represent + the type T or types that are assignable to T. + + Usage: + + def cast[T](typ: TypeForm[T], value: Any) -> T: ... + + reveal_type(cast(int, "x")) # int + + See PEP 747 for more information. + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + + + +if hasattr(typing, "LiteralString"): # 3.11+ + LiteralString = typing.LiteralString +else: + @_SpecialForm + def LiteralString(self, params): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Self"): # 3.11+ + Self = typing.Self +else: + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Never"): # 3.11+ + Never = typing.Never +else: + @_SpecialForm + def Never(self, params): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, 'Required'): # 3.11+ + Required = typing.Required + NotRequired = typing.NotRequired +else: # <=3.10 + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +if hasattr(typing, 'ReadOnly'): + ReadOnly = typing.ReadOnly +else: # <=3.12 + @_ExtensionsSpecialForm + def ReadOnly(self, parameters): + """A special typing construct to mark an item of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +_UNPACK_DOC = """\ +Type unpack operator. + +The type unpack operator takes the child types from some container type, +such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For +example: + + # For some generic class `Foo`: + Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str] + + Ts = TypeVarTuple('Ts') + # Specifies that `Bar` is generic in an arbitrary number of types. + # (Think of `Ts` as a tuple of an arbitrary number of individual + # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the + # `Generic[]`.) + class Bar(Generic[Unpack[Ts]]): ... + Bar[int] # Valid + Bar[int, str] # Also valid + +From Python 3.11, this can also be done using the `*` operator: + + Foo[*tuple[int, str]] + class Bar(Generic[*Ts]): ... + +The operator can also be used along with a `TypedDict` to annotate +`**kwargs` in a function signature. For instance: + + class Movie(TypedDict): + name: str + year: int + + # This function expects two keyword arguments - *name* of type `str` and + # *year* of type `int`. + def foo(**kwargs: Unpack[Movie]): ... + +Note that there is only some runtime checking of this operator. Not +everything the runtime allows may be accepted by static type checkers. + +For more information, see PEP 646 and PEP 692. +""" + + +# PEP 692 changed the repr of Unpack[] +# Breakpoint: https://github.com/python/cpython/pull/104048 +if sys.version_info >= (3, 12): + Unpack = typing.Unpack + + def _is_unpack(obj): + return get_origin(obj) is Unpack + +else: # <=3.11 + class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, getitem): + super().__init__(getitem) + self.__doc__ = _UNPACK_DOC + + class _UnpackAlias(typing._GenericAlias, _root=True): + if sys.version_info < (3, 11): + # needed for compatibility with Generic[Unpack[Ts]] + __class__ = typing.TypeVar + + @property + def __typing_unpacked_tuple_args__(self): + assert self.__origin__ is Unpack + assert len(self.__args__) == 1 + arg, = self.__args__ + if isinstance(arg, (typing._GenericAlias, _types.GenericAlias)): + if arg.__origin__ is not tuple: + raise TypeError("Unpack[...] must be used with a tuple type") + return arg.__args__ + return None + + @property + def __typing_is_unpacked_typevartuple__(self): + assert self.__origin__ is Unpack + assert len(self.__args__) == 1 + return isinstance(self.__args__[0], TypeVarTuple) + + def __getitem__(self, args): + if self.__typing_is_unpacked_typevartuple__: + return args + # Cannot use `super().__getitem__` here because of the `__class__` assignment + # in the class body on Python <=3.11 + # (https://github.com/python/typing_extensions/issues/661) + return typing._GenericAlias.__getitem__(self, args) + + @_UnpackSpecialForm + def Unpack(self, parameters): + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + + +def _unpack_args(*args): + newargs = [] + for arg in args: + subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) + if subargs is not None and (not (subargs and subargs[-1] is ...)): + newargs.extend(subargs) + else: + newargs.append(arg) + return newargs + + +if sys.version_info >= (3, 15): + from typing import TypeVarTuple + +elif hasattr(typing, "TypeVarTuple"): # 3.11+ + + # Add default parameter - PEP 696 and bound/variance parameters + class TypeVarTuple(metaclass=_TypeVarLikeMeta): + """Type variable tuple.""" + + _backported_typevarlike = typing.TypeVarTuple + + def __new__(cls, name, *, bound=None, + covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + + if _PEP_696_IMPLEMENTED: + # can pass default argument + tvt = typing.TypeVarTuple(name, default=default) + else: + tvt = typing.TypeVarTuple(name) + _set_default(tvt, default) + + tvt.__bound__ = bound + tvt.__covariant__ = bool(covariant) + tvt.__contravariant__ = bool(contravariant) + tvt.__infer_variance__ = bool(infer_variance) + + _set_module(tvt) + + def _typevartuple_prepare_subst(alias, args): + params = alias.__parameters__ + typevartuple_index = params.index(tvt) + for param in params[typevartuple_index + 1:]: + if isinstance(param, TypeVarTuple): + raise TypeError( + f"More than one TypeVarTuple parameter in {alias}" + ) + + alen = len(args) + plen = len(params) + left = typevartuple_index + right = plen - typevartuple_index - 1 + var_tuple_index = None + fillarg = None + for k, arg in enumerate(args): + if not isinstance(arg, type): + subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) + if subargs and len(subargs) == 2 and subargs[-1] is ...: + if var_tuple_index is not None: + raise TypeError( + "More than one unpacked " + "arbitrary-length tuple argument" + ) + var_tuple_index = k + fillarg = subargs[0] + if var_tuple_index is not None: + left = min(left, var_tuple_index) + right = min(right, alen - var_tuple_index - 1) + elif left + right > alen: + raise TypeError(f"Too few arguments for {alias};" + f" actual {alen}, expected at least {plen - 1}") + if left == alen - right and tvt.has_default(): + replacement = _unpack_args(tvt.__default__) + else: + replacement = args[left: alen - right] + + return ( + *args[:left], + *([fillarg] * (typevartuple_index - left)), + replacement, + *([fillarg] * (plen - right - left - typevartuple_index - 1)), + *args[alen - right:], + ) + + tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst + return tvt + + def __init_subclass__(self, *args, **kwds): + raise TypeError("Cannot subclass special typing classes") + +else: # <=3.10 + class TypeVarTuple(_DefaultMixin): + """Type variable tuple. + + Usage:: + + Ts = TypeVarTuple('Ts') + + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + class Array(Generic[*Ts]): ... + + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. + + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: + + class Array(Generic[*Ts]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape + + def get_shape(self) -> Tuple[*Ts]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + self.__infer_variance__ = bool(infer_variance) + self.__bound__ = bound + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + if self.__infer_variance__: + prefix = '' + elif self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + +if hasattr(typing, "reveal_type"): # 3.11+ + reveal_type = typing.reveal_type +else: # <=3.10 + def reveal_type(obj: T, /) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr) + return obj + + +if hasattr(typing, "_ASSERT_NEVER_REPR_MAX_LENGTH"): # 3.11+ + _ASSERT_NEVER_REPR_MAX_LENGTH = typing._ASSERT_NEVER_REPR_MAX_LENGTH +else: # <=3.10 + _ASSERT_NEVER_REPR_MAX_LENGTH = 100 + + +if hasattr(typing, "assert_never"): # 3.11+ + assert_never = typing.assert_never +else: # <=3.10 + def assert_never(arg: Never, /) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + value = repr(arg) + if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH: + value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...' + raise AssertionError(f"Expected code to be unreachable, but got: {value}") + + +# dataclass_transform exists in 3.11 but lacks the frozen_default parameter +# Breakpoint: https://github.com/python/cpython/pull/99958 +if sys.version_info >= (3, 12): # 3.12+ + dataclass_transform = typing.dataclass_transform +else: # <=3.11 + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + frozen_default: bool = False, + field_specifiers: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], + ... + ] = (), + **kwargs: typing.Any, + ) -> typing.Callable[[T], T]: + """Decorator that marks a function, class, or metaclass as providing + dataclass-like behavior. + + Example: + + from typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``frozen_default`` indicates whether the ``frozen`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. + + At runtime, this decorator records its arguments in the + ``__dataclass_transform__`` attribute on the decorated object. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "frozen_default": frozen_default, + "field_specifiers": field_specifiers, + "kwargs": kwargs, + } + return cls_or_fn + return decorator + + +if hasattr(typing, "override"): # 3.12+ + override = typing.override +else: # <=3.11 + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + def override(arg: _F, /) -> _F: + """Indicate that a method is intended to override a method in a base class. + + Usage: + + class Base: + def method(self) -> None: + pass + + class Child(Base): + @override + def method(self) -> None: + super().method() + + When this decorator is applied to a method, the type checker will + validate that it overrides a method with the same name on a base class. + This helps prevent bugs that may occur when a base class is changed + without an equivalent change to a child class. + + There is no runtime checking of these properties. The decorator + sets the ``__override__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + + See PEP 698 for details. + + """ + try: + arg.__override__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return arg + + +# Python 3.13.8+ and 3.14.1+ contain a fix for the wrapped __init_subclass__ +# Breakpoint: https://github.com/python/cpython/pull/138210 +if ((3, 13, 8) <= sys.version_info < (3, 14)) or sys.version_info >= (3, 14, 1): + deprecated = warnings.deprecated +else: + _T = typing.TypeVar("_T") + + class deprecated: + """Indicate that a class, function or overload is deprecated. + + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + + Usage: + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the + warning is emitted. If it is ``1`` (the default), the warning + is emitted at the direct caller of the deprecated object; if it + is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. + + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator + must be after the ``@overload`` decorator for the attribute to + exist on the overload as returned by ``get_overloads()``. + + See PEP 702 for details. + + """ + def __init__( + self, + message: str, + /, + *, + category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + if not isinstance(message, str): + raise TypeError( + "Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" + ) + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, arg: _T, /) -> _T: + # Make sure the inner functions created below don't + # retain a reference to self. + msg = self.message + category = self.category + stacklevel = self.stacklevel + if category is None: + arg.__deprecated__ = msg + return arg + elif isinstance(arg, type): + import functools + from types import MethodType + + original_new = arg.__new__ + + @functools.wraps(original_new) + def __new__(cls, /, *args, **kwargs): + if cls is arg: + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + if original_new is not object.__new__: + return original_new(cls, *args, **kwargs) + # Mirrors a similar check in object.__new__. + elif cls.__init__ is object.__init__ and (args or kwargs): + raise TypeError(f"{cls.__name__}() takes no arguments") + else: + return original_new(cls) + + arg.__new__ = staticmethod(__new__) + + if "__init_subclass__" in arg.__dict__: + # __init_subclass__ is directly present on the decorated class. + # Synthesize a wrapper that calls this method directly. + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python). + # Otherwise, it likely means it's a builtin such as + # object's implementation of __init_subclass__. + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ + + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + else: + def __init_subclass__(cls, *args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return super(arg, cls).__init_subclass__(*args, **kwargs) + + arg.__init_subclass__ = classmethod(__init_subclass__) + + arg.__deprecated__ = __new__.__deprecated__ = msg + __init_subclass__.__deprecated__ = msg + return arg + elif callable(arg): + import functools + import inspect + + @functools.wraps(arg) + def wrapper(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return arg(*args, **kwargs) + + if inspect.iscoroutinefunction(arg): + # Breakpoint: https://github.com/python/cpython/pull/99247 + if sys.version_info >= (3, 12): + wrapper = inspect.markcoroutinefunction(wrapper) + else: + import asyncio.coroutines + + wrapper._is_coroutine = asyncio.coroutines._is_coroutine + + arg.__deprecated__ = wrapper.__deprecated__ = msg + return wrapper + else: + raise TypeError( + "@deprecated decorator with non-None category must be applied to " + f"a class or callable, not {arg!r}" + ) + +# Breakpoint: https://github.com/python/cpython/pull/23702 +if sys.version_info < (3, 10): + def _is_param_expr(arg): + return arg is ... or isinstance( + arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias) + ) +else: + def _is_param_expr(arg): + return arg is ... or isinstance( + arg, + ( + tuple, + list, + ParamSpec, + _ConcatenateGenericAlias, + typing._ConcatenateGenericAlias, + ), + ) + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + + This gives a nice error message in case of count mismatch. + """ + # If substituting a single ParamSpec with multiple arguments + # we do not check the count + if (inspect.isclass(cls) and issubclass(cls, typing.Generic) + and len(cls.__parameters__) == 1 + and isinstance(cls.__parameters__[0], ParamSpec) + and parameters + and not _is_param_expr(parameters[0]) + ): + # Generic modifies parameters variable, but here we cannot do this + return + + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if ( + getattr(parameters[alen], '__default__', NoDefault) + is not NoDefault + ): + return + + num_default_tv = sum(getattr(p, '__default__', NoDefault) + is not NoDefault for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + # Breakpoint: https://github.com/python/cpython/pull/27515 + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}" + f" for {cls}; actual {alen}, expected {expect_val}") +else: + # Python 3.11+ + + def _check_generic(cls, parameters, elen): + """Check correct count for parameters of a generic cls (internal helper). + + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if ( + getattr(parameters[alen], '__default__', NoDefault) + is not NoDefault + ): + return + + num_default_tv = sum(getattr(p, '__default__', NoDefault) + is not NoDefault for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments" + f" for {cls}; actual {alen}, expected {expect_val}") + +if not _PEP_696_IMPLEMENTED: + typing._check_generic = _check_generic + + +def _has_generic_or_protocol_as_origin() -> bool: + try: + frame = sys._getframe(2) + # - Catch AttributeError: not all Python implementations have sys._getframe() + # - Catch ValueError: maybe we're called from an unexpected module + # and the call stack isn't deep enough + except (AttributeError, ValueError): + return False # err on the side of leniency + else: + # If we somehow get invoked from outside typing.py, + # also err on the side of leniency + if frame.f_globals.get("__name__") != "typing": + return False + origin = frame.f_locals.get("origin") + # Cannot use "in" because origin may be an object with a buggy __eq__ that + # throws an error. + return origin is typing.Generic or origin is Protocol or origin is typing.Protocol + + +_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)} + + +def _is_unpacked_typevartuple(x) -> bool: + if get_origin(x) is not Unpack: + return False + args = get_args(x) + return ( + bool(args) + and len(args) == 1 + and type(args[0]) in _TYPEVARTUPLE_TYPES + ) + + +# Python 3.11+ _collect_type_vars was renamed to _collect_parameters +if hasattr(typing, '_collect_type_vars'): + def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + + # A required TypeVarLike cannot appear after a TypeVarLike with a default + # if it was a direct call to `Generic[]` or `Protocol[]` + enforce_default_ordering = _has_generic_or_protocol_as_origin() + default_encountered = False + + # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple + type_var_tuple_encountered = False + + for t in types: + if _is_unpacked_typevartuple(t): + type_var_tuple_encountered = True + elif ( + isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias) + and t not in tvars + ): + if enforce_default_ordering: + has_default = getattr(t, '__default__', NoDefault) is not NoDefault + if has_default: + if type_var_tuple_encountered: + raise TypeError('Type parameter with a default' + ' follows TypeVarTuple') + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + elif isinstance(t, tuple): + # Collect nested type_vars + # tuple wrapped by _prepare_paramspec_params(cls, params) + for x in t: + for collected in _collect_type_vars([x]): + if collected not in tvars: + tvars.append(collected) + return tuple(tvars) + + typing._collect_type_vars = _collect_type_vars +else: + def _collect_parameters(args): + """Collect all type variables and parameter specifications in args + in order of first appearance (lexicographic order). + + For example:: + + assert _collect_parameters((T, Callable[P, T])) == (T, P) + """ + parameters = [] + + # A required TypeVarLike cannot appear after a TypeVarLike with default + # if it was a direct call to `Generic[]` or `Protocol[]` + enforce_default_ordering = _has_generic_or_protocol_as_origin() + default_encountered = False + + # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple + type_var_tuple_encountered = False + + for t in args: + if isinstance(t, type): + # We don't want __parameters__ descriptor of a bare Python class. + pass + elif isinstance(t, tuple): + # `t` might be a tuple, when `ParamSpec` is substituted with + # `[T, int]`, or `[int, *Ts]`, etc. + for x in t: + for collected in _collect_parameters([x]): + if collected not in parameters: + parameters.append(collected) + elif hasattr(t, '__typing_subst__'): + if t not in parameters: + if enforce_default_ordering: + has_default = ( + getattr(t, '__default__', NoDefault) is not NoDefault + ) + + if type_var_tuple_encountered and has_default: + raise TypeError('Type parameter with a default' + ' follows TypeVarTuple') + + if has_default: + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + parameters.append(t) + else: + if _is_unpacked_typevartuple(t): + type_var_tuple_encountered = True + for x in getattr(t, '__parameters__', ()): + if x not in parameters: + parameters.append(x) + + return tuple(parameters) + + if not _PEP_696_IMPLEMENTED: + typing._collect_parameters = _collect_parameters + +# Backport typing.NamedTuple as it exists in Python 3.13. +# In 3.11, the ability to define generic `NamedTuple`s was supported. +# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. +# On 3.12, we added __orig_bases__ to call-based NamedTuples +# On 3.13, we deprecated kwargs-based NamedTuples +# Breakpoint: https://github.com/python/cpython/pull/105609 +if sys.version_info >= (3, 13): + NamedTuple = typing.NamedTuple +else: + def _make_nmtuple(name, types, module, defaults=()): + fields = [n for n, t in types] + annotations = {n: typing._type_check(t, f"field {n} annotation must be a type") + for n, t in types} + nm_tpl = collections.namedtuple(name, fields, + defaults=defaults, module=module) + nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations + return nm_tpl + + _prohibited_namedtuple_fields = typing._prohibited + _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'}) + + class _NamedTupleMeta(type): + def __new__(cls, typename, bases, ns): + assert _NamedTuple in bases + for base in bases: + if base is not _NamedTuple and base is not typing.Generic: + raise TypeError( + 'can only inherit from a NamedTuple type and Generic') + bases = tuple(tuple if base is _NamedTuple else base for base in bases) + if "__annotations__" in ns: + types = ns["__annotations__"] + elif "__annotate__" in ns: + # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated + types = ns["__annotate__"](1) + else: + types = {} + default_names = [] + for field_name in types: + if field_name in ns: + default_names.append(field_name) + elif default_names: + raise TypeError(f"Non-default namedtuple field {field_name} " + f"cannot follow default field" + f"{'s' if len(default_names) > 1 else ''} " + f"{', '.join(default_names)}") + nm_tpl = _make_nmtuple( + typename, types.items(), + defaults=[ns[n] for n in default_names], + module=ns['__module__'] + ) + nm_tpl.__bases__ = bases + if typing.Generic in bases: + if hasattr(typing, '_generic_class_getitem'): # 3.12+ + nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem) + else: + class_getitem = typing.Generic.__class_getitem__.__func__ + nm_tpl.__class_getitem__ = classmethod(class_getitem) + # update from user namespace without overriding special namedtuple attributes + for key, val in ns.items(): + if key in _prohibited_namedtuple_fields: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special_namedtuple_fields: + if key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + try: + set_name = type(val).__set_name__ + except AttributeError: + pass + else: + try: + set_name(val, nm_tpl, key) + except BaseException as e: + msg = ( + f"Error calling __set_name__ on {type(val).__name__!r} " + f"instance {key!r} in {typename!r}" + ) + # BaseException.add_note() existed on py311, + # but the __set_name__ machinery didn't start + # using add_note() until py312. + # Making sure exceptions are raised in the same way + # as in "normal" classes seems most important here. + # Breakpoint: https://github.com/python/cpython/pull/95915 + if sys.version_info >= (3, 12): + e.add_note(msg) + raise + else: + raise RuntimeError(msg) from e + + if typing.Generic in bases: + nm_tpl.__init_subclass__() + return nm_tpl + + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) + + def _namedtuple_mro_entries(bases): + assert NamedTuple in bases + return (_NamedTuple,) + + def NamedTuple(typename, fields=_marker, /, **kwargs): + """Typed version of namedtuple. + + Usage:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has an extra __annotations__ attribute, giving a + dict that maps field names to types. (The field names are also in + the _fields attribute, which is part of the namedtuple API.) + An alternative equivalent functional syntax is also accepted:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + if fields is _marker: + if kwargs: + deprecated_thing = "Creating NamedTuple classes using keyword arguments" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "Use the class-based or functional syntax instead." + ) + else: + deprecated_thing = "Failing to pass a value for the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif fields is None: + if kwargs: + raise TypeError( + "Cannot pass `None` as the 'fields' parameter " + "and also specify fields using keyword arguments" + ) + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + if fields is _marker or fields is None: + warnings.warn( + deprecation_msg.format(name=deprecated_thing, remove="3.15"), + DeprecationWarning, + stacklevel=2, + ) + fields = kwargs.items() + nt = _make_nmtuple(typename, fields, module=_caller()) + nt.__orig_bases__ = (NamedTuple,) + return nt + + NamedTuple.__mro_entries__ = _namedtuple_mro_entries + + +if hasattr(collections.abc, "Buffer"): + Buffer = collections.abc.Buffer +else: + class Buffer(abc.ABC): # noqa: B024 + """Base class for classes that implement the buffer protocol. + + The buffer protocol allows Python objects to expose a low-level + memory buffer interface. Before Python 3.12, it is not possible + to implement the buffer protocol in pure Python code, or even + to check whether a class implements the buffer protocol. In + Python 3.12 and higher, the ``__buffer__`` method allows access + to the buffer protocol from Python code, and the + ``collections.abc.Buffer`` ABC allows checking whether a class + implements the buffer protocol. + + To indicate support for the buffer protocol in earlier versions, + inherit from this ABC, either in a stub file or at runtime, + or use ABC registration. This ABC provides no methods, because + there is no Python-accessible methods shared by pre-3.12 buffer + classes. It is useful primarily for static checks. + + """ + + # As a courtesy, register the most common stdlib buffer classes. + Buffer.register(memoryview) + Buffer.register(bytearray) + Buffer.register(bytes) + + +# Backport of types.get_original_bases, available on 3.12+ in CPython +if hasattr(_types, "get_original_bases"): + get_original_bases = _types.get_original_bases +else: + def get_original_bases(cls, /): + """Return the class's "original" bases prior to modification by `__mro_entries__`. + + Examples:: + + from typing import TypeVar, Generic + from typing_extensions import NamedTuple, TypedDict + + T = TypeVar("T") + class Foo(Generic[T]): ... + class Bar(Foo[int], float): ... + class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) + + assert get_original_bases(Bar) == (Foo[int], float) + assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(Eggs) == (NamedTuple,) + assert get_original_bases(Spam) == (TypedDict,) + assert get_original_bases(int) == (object,) + """ + try: + return cls.__dict__.get("__orig_bases__", cls.__bases__) + except AttributeError: + raise TypeError( + f'Expected an instance of type, not {type(cls).__name__!r}' + ) from None + + +# NewType is a class on Python 3.10+, making it pickleable +# The error message for subclassing instances of NewType was improved on 3.11+ +# Breakpoint: https://github.com/python/cpython/pull/30268 +if sys.version_info >= (3, 11): + NewType = typing.NewType +else: + class NewType: + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy callable that simply returns its argument. Usage:: + UserId = NewType('UserId', int) + def name_by_id(user_id: UserId) -> str: + ... + UserId('user') # Fails type check + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + num = UserId(5) + 1 # type: int + """ + + def __call__(self, obj, /): + return obj + + def __init__(self, name, tp): + self.__qualname__ = name + if '.' in name: + name = name.rpartition('.')[-1] + self.__name__ = name + self.__supertype__ = tp + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __mro_entries__(self, bases): + # We defined __mro_entries__ to get a better error message + # if a user attempts to subclass a NewType instance. bpo-46170 + supercls_name = self.__name__ + + class Dummy: + def __init_subclass__(cls): + subcls_name = cls.__name__ + raise TypeError( + f"Cannot subclass an instance of NewType. " + f"Perhaps you were looking for: " + f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`" + ) + + return (Dummy,) + + def __repr__(self): + return f'{self.__module__}.{self.__qualname__}' + + def __reduce__(self): + return self.__qualname__ + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + # PEP 604 methods + # It doesn't make sense to have these methods on Python <3.10 + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + +# Breakpoint: https://github.com/python/cpython/pull/149172 +if sys.version_info >= (3, 15): + TypeAliasType = typing.TypeAliasType +# <=3.14 +else: + # Breakpoint: https://github.com/python/cpython/pull/103764 + if sys.version_info >= (3, 12): + # 3.12-3.14 + def _is_unionable(obj): + """Corresponds to is_unionable() in unionobject.c in CPython.""" + return obj is None or isinstance(obj, ( + type, + _types.GenericAlias, + _types.UnionType, + typing.TypeAliasType, + TypeAliasType, + )) + else: + # <=3.11 + def _is_unionable(obj): + """Corresponds to is_unionable() in unionobject.c in CPython.""" + return obj is None or isinstance(obj, ( + type, + _types.GenericAlias, + _types.UnionType, + TypeAliasType, + )) + + if sys.version_info < (3, 10): + # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582, + # so that we emulate the behaviour of `types.GenericAlias` + # on the latest versions of CPython + _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({ + "__class__", + "__bases__", + "__origin__", + "__args__", + "__unpacked__", + "__parameters__", + "__typing_unpacked_tuple_args__", + "__mro_entries__", + "__reduce_ex__", + "__reduce__", + "__copy__", + "__deepcopy__", + }) + + class _TypeAliasGenericAlias(typing._GenericAlias, _root=True): + def __getattr__(self, attr): + if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS: + return object.__getattr__(self, attr) + return getattr(self.__origin__, attr) + + + class TypeAliasType: + """Create named, parameterized type aliases. + + This provides a backport of the new `type` statement in Python 3.12: + + type ListOrSet[T] = list[T] | set[T] + + is equivalent to: + + T = TypeVar("T") + ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) + + The name ListOrSet can then be used as an alias for the type it refers to. + + The type_params argument should contain all the type parameters used + in the value of the type alias. If the alias is not generic, this + argument is omitted. + + Static type checkers should only support type aliases declared using + TypeAliasType that follow these rules: + + - The first argument (the name) must be a string literal. + - The TypeAliasType instance must be immediately assigned to a variable + of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid, + as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)'). + + """ + + def __init__(self, name: str, value, *, type_params=()): + if not isinstance(name, str): + raise TypeError("TypeAliasType name must be a string") + if not isinstance(type_params, tuple): + raise TypeError("type_params must be a tuple") + self.__value__ = value + self.__type_params__ = type_params + + default_value_encountered = False + parameters = [] + for type_param in type_params: + if ( + not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec)) + # <=3.11 + # Unpack Backport passes isinstance(type_param, TypeVar) + or _is_unpack(type_param) + ): + raise TypeError(f"Expected a type param, got {type_param!r}") + has_default = ( + getattr(type_param, '__default__', NoDefault) is not NoDefault + ) + if default_value_encountered and not has_default: + raise TypeError(f"non-default type parameter '{type_param!r}'" + " follows default type parameter") + if has_default: + default_value_encountered = True + if isinstance(type_param, TypeVarTuple): + parameters.extend(type_param) + else: + parameters.append(type_param) + self.__parameters__ = tuple(parameters) + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + # Setting this attribute closes the TypeAliasType from further modification + self.__name__ = name + + def __setattr__(self, name: str, value: object, /) -> None: + if hasattr(self, "__name__") and name != "__module__": + self._raise_attribute_error(name) + super().__setattr__(name, value) + + def __delattr__(self, name: str, /) -> Never: + self._raise_attribute_error(name) + + def _raise_attribute_error(self, name: str) -> Never: + # Match the Python 3.12 error messages exactly + if name == "__name__": + raise AttributeError("readonly attribute") + elif name in {"__value__", "__type_params__", "__parameters__"}: + raise AttributeError( + f"attribute '{name}' of 'typing.TypeAliasType' objects " + "is not writable" + ) + else: + raise AttributeError( + f"'typing.TypeAliasType' object has no attribute '{name}'" + ) + + def __repr__(self) -> str: + return self.__name__ + + if sys.version_info < (3, 11): + def _check_single_param(self, param, recursion=0): + # Allow [], [int], [int, str], [int, ...], [int, T] + if param is ...: + return ... + if param is None: + return None + # Note in <= 3.9 _ConcatenateGenericAlias inherits from list + if isinstance(param, list) and recursion == 0: + return [self._check_single_param(arg, recursion+1) + for arg in param] + return typing._type_check( + param, f'Subscripting {self.__name__} requires a type.' + ) + + def _check_parameters(self, parameters): + if sys.version_info < (3, 11): + return tuple( + self._check_single_param(item) + for item in parameters + ) + return tuple(typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ) + + def __getitem__(self, parameters): + if not self.__type_params__: + raise TypeError("Only generic type aliases are subscriptable") + if not isinstance(parameters, tuple): + parameters = (parameters,) + # Using 3.9 here will create problems with Concatenate + if sys.version_info >= (3, 10): + return _types.GenericAlias(self, parameters) + type_vars = _collect_type_vars(parameters) + parameters = self._check_parameters(parameters) + alias = _TypeAliasGenericAlias(self, parameters) + # alias.__parameters__ is not complete if Concatenate is present + # as it is converted to a list from which no parameters are extracted. + if alias.__parameters__ != type_vars: + alias.__parameters__ = type_vars + return alias + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + "type 'typing_extensions.TypeAliasType' is not an acceptable base type" + ) + + # The presence of this method convinces typing._type_check + # that TypeAliasTypes are types. + def __call__(self): + raise TypeError("Type alias is not callable") + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + def __or__(self, right): + # For forward compatibility with 3.12, reject Unions + # that are not accepted by the built-in Union. + if not _is_unionable(right): + return NotImplemented + return typing.Union[self, right] + + def __ror__(self, left): + if not _is_unionable(left): + return NotImplemented + return typing.Union[left, self] + + +if hasattr(typing, "is_protocol"): + is_protocol = typing.is_protocol + get_protocol_members = typing.get_protocol_members +else: + def is_protocol(tp: type, /) -> bool: + """Return True if the given type is a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, is_protocol + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> is_protocol(P) + True + >>> is_protocol(int) + False + """ + return ( + isinstance(tp, type) + and getattr(tp, '_is_protocol', False) + and tp is not Protocol + and tp is not typing.Protocol + ) + + def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]: + """Return the set of members defined in a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, get_protocol_members + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> get_protocol_members(P) == frozenset({'a', 'b'}) + True + + Raise a TypeError for arguments that are not Protocols. + """ + if not is_protocol(tp): + raise TypeError(f'{tp!r} is not a Protocol') + if hasattr(tp, '__protocol_attrs__'): + return frozenset(tp.__protocol_attrs__) + return frozenset(_get_protocol_attrs(tp)) + + +if hasattr(typing, "Doc"): + Doc = typing.Doc +else: + class Doc: + """Define the documentation of a type annotation using ``Annotated``, to be + used in class attributes, function and method parameters, return values, + and variables. + + The value should be a positional-only string literal to allow static tools + like editors and documentation generators to use it. + + This complements docstrings. + + The string value passed is available in the attribute ``documentation``. + + Example:: + + >>> from typing_extensions import Annotated, Doc + >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ... + """ + def __init__(self, documentation: str, /) -> None: + self.documentation = documentation + + def __repr__(self) -> str: + return f"Doc({self.documentation!r})" + + def __hash__(self) -> int: + return hash(self.documentation) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Doc): + return NotImplemented + return self.documentation == other.documentation + + +_CapsuleType = getattr(_types, "CapsuleType", None) + +if _CapsuleType is None: + try: + import _socket + except ImportError: + pass + else: + _CAPI = getattr(_socket, "CAPI", None) + if _CAPI is not None: + _CapsuleType = type(_CAPI) + +if _CapsuleType is not None: + CapsuleType = _CapsuleType + __all__.append("CapsuleType") + + +if sys.version_info >= (3, 14): + from annotationlib import Format, get_annotations +else: + # Available since Python 3.14.0a3 + # PR: https://github.com/python/cpython/pull/124415 + class Format(enum.IntEnum): + VALUE = 1 + VALUE_WITH_FAKE_GLOBALS = 2 + FORWARDREF = 3 + STRING = 4 + + # Available since Python 3.14.0a1 + # PR: https://github.com/python/cpython/pull/119891 + def get_annotations(obj, *, globals=None, locals=None, eval_str=False, + format=Format.VALUE): + """Compute the annotations dict for an object. + + obj may be a callable, class, or module. + Passing in an object of any other type raises TypeError. + + Returns a dict. get_annotations() returns a new dict every time + it's called; calling it twice on the same object will return two + different but equivalent dicts. + + This is a backport of `inspect.get_annotations`, which has been + in the standard library since Python 3.10. See the standard library + documentation for more: + + https://docs.python.org/3/library/inspect.html#inspect.get_annotations + + This backport adds the *format* argument introduced by PEP 649. The + three formats supported are: + * VALUE: the annotations are returned as-is. This is the default and + it is compatible with the behavior on previous Python versions. + * FORWARDREF: return annotations as-is if possible, but replace any + undefined names with ForwardRef objects. The implementation proposed by + PEP 649 relies on language changes that cannot be backported; the + typing-extensions implementation simply returns the same result as VALUE. + * STRING: return annotations as strings, in a format close to the original + source. Again, this behavior cannot be replicated directly in a backport. + As an approximation, typing-extensions retrieves the annotations under + VALUE semantics and then stringifies them. + + The purpose of this backport is to allow users who would like to use + FORWARDREF or STRING semantics once PEP 649 is implemented, but who also + want to support earlier Python versions, to simply write: + + typing_extensions.get_annotations(obj, format=Format.FORWARDREF) + + """ + format = Format(format) + if format is Format.VALUE_WITH_FAKE_GLOBALS: + raise ValueError( + "The VALUE_WITH_FAKE_GLOBALS format is for internal use only" + ) + + if eval_str and format is not Format.VALUE: + raise ValueError("eval_str=True is only supported with format=Format.VALUE") + + if isinstance(obj, type): + # class + obj_dict = getattr(obj, '__dict__', None) + if obj_dict and hasattr(obj_dict, 'get'): + ann = obj_dict.get('__annotations__', None) + if isinstance(ann, _types.GetSetDescriptorType): + ann = None + else: + ann = None + + obj_globals = None + module_name = getattr(obj, '__module__', None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + obj_globals = getattr(module, '__dict__', None) + obj_locals = dict(vars(obj)) + unwrap = obj + elif isinstance(obj, _types.ModuleType): + # module + ann = getattr(obj, '__annotations__', None) + obj_globals = obj.__dict__ + obj_locals = None + unwrap = None + elif callable(obj): + # this includes types.Function, types.BuiltinFunctionType, + # types.BuiltinMethodType, functools.partial, functools.singledispatch, + # "class funclike" from Lib/test/test_inspect... on and on it goes. + ann = getattr(obj, '__annotations__', None) + obj_globals = getattr(obj, '__globals__', None) + obj_locals = None + unwrap = obj + elif hasattr(obj, '__annotations__'): + ann = obj.__annotations__ + obj_globals = obj_locals = unwrap = None + else: + raise TypeError(f"{obj!r} is not a module, class, or callable.") + + if ann is None: + return {} + + if not isinstance(ann, dict): + raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None") + + if not ann: + return {} + + if not eval_str: + if format is Format.STRING: + return { + key: value if isinstance(value, str) else typing._type_repr(value) + for key, value in ann.items() + } + return dict(ann) + + if unwrap is not None: + while True: + if hasattr(unwrap, '__wrapped__'): + unwrap = unwrap.__wrapped__ + continue + if isinstance(unwrap, functools.partial): + unwrap = unwrap.func + continue + break + if hasattr(unwrap, "__globals__"): + obj_globals = unwrap.__globals__ + + if globals is None: + globals = obj_globals + if locals is None: + locals = obj_locals or {} + + # "Inject" type parameters into the local namespace + # (unless they are shadowed by assignments *in* the local namespace), + # as a way of emulating annotation scopes when calling `eval()` + if type_params := getattr(obj, "__type_params__", ()): + locals = {param.__name__: param for param in type_params} | locals + + return_value = {key: + value if not isinstance(value, str) else eval(value, globals, locals) + for key, value in ann.items() } + return return_value + + +if hasattr(typing, "evaluate_forward_ref"): + evaluate_forward_ref = typing.evaluate_forward_ref +else: + # Implements annotationlib.ForwardRef.evaluate + def _eval_with_owner( + forward_ref, *, owner=None, globals=None, locals=None, type_params=None + ): + if forward_ref.__forward_evaluated__: + return forward_ref.__forward_value__ + if getattr(forward_ref, "__cell__", None) is not None: + try: + value = forward_ref.__cell__.cell_contents + except ValueError: + pass + else: + forward_ref.__forward_evaluated__ = True + forward_ref.__forward_value__ = value + return value + if owner is None: + owner = getattr(forward_ref, "__owner__", None) + + if ( + globals is None + and getattr(forward_ref, "__forward_module__", None) is not None + ): + globals = getattr( + sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None + ) + if globals is None: + globals = getattr(forward_ref, "__globals__", None) + if globals is None: + if isinstance(owner, type): + module_name = getattr(owner, "__module__", None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + globals = getattr(module, "__dict__", None) + elif isinstance(owner, _types.ModuleType): + globals = getattr(owner, "__dict__", None) + elif callable(owner): + globals = getattr(owner, "__globals__", None) + + # If we pass None to eval() below, the globals of this module are used. + if globals is None: + globals = {} + + if locals is None: + locals = {} + if isinstance(owner, type): + locals.update(vars(owner)) + + if type_params is None and owner is not None: + # "Inject" type parameters into the local namespace + # (unless they are shadowed by assignments *in* the local namespace), + # as a way of emulating annotation scopes when calling `eval()` + type_params = getattr(owner, "__type_params__", None) + + # Type parameters exist in their own scope, which is logically + # between the locals and the globals. We simulate this by adding + # them to the globals. + if type_params is not None: + globals = dict(globals) + for param in type_params: + globals[param.__name__] = param + + arg = forward_ref.__forward_arg__ + if arg.isidentifier() and not keyword.iskeyword(arg): + if arg in locals: + value = locals[arg] + elif arg in globals: + value = globals[arg] + elif hasattr(builtins, arg): + return getattr(builtins, arg) + else: + raise NameError(arg) + else: + code = forward_ref.__forward_code__ + value = eval(code, globals, locals) + forward_ref.__forward_evaluated__ = True + forward_ref.__forward_value__ = value + return value + + def evaluate_forward_ref( + forward_ref, + *, + owner=None, + globals=None, + locals=None, + type_params=None, + format=None, + _recursive_guard=frozenset(), + ): + """Evaluate a forward reference as a type hint. + + This is similar to calling the ForwardRef.evaluate() method, + but unlike that method, evaluate_forward_ref() also: + + * Recursively evaluates forward references nested within the type hint. + * Rejects certain objects that are not valid type hints. + * Replaces type hints that evaluate to None with types.NoneType. + * Supports the *FORWARDREF* and *STRING* formats. + + *forward_ref* must be an instance of ForwardRef. *owner*, if given, + should be the object that holds the annotations that the forward reference + derived from, such as a module, class object, or function. It is used to + infer the namespaces to use for looking up names. *globals* and *locals* + can also be explicitly given to provide the global and local namespaces. + *type_params* is a tuple of type parameters that are in scope when + evaluating the forward reference. This parameter must be provided (though + it may be an empty tuple) if *owner* is not given and the forward reference + does not already have an owner set. *format* specifies the format of the + annotation and is a member of the annotationlib.Format enum. + + """ + if format == Format.STRING: + return forward_ref.__forward_arg__ + if forward_ref.__forward_arg__ in _recursive_guard: + return forward_ref + + # Evaluate the forward reference + try: + value = _eval_with_owner( + forward_ref, + owner=owner, + globals=globals, + locals=locals, + type_params=type_params, + ) + except NameError: + if format == Format.FORWARDREF: + return forward_ref + else: + raise + + if isinstance(value, str): + value = ForwardRef(value) + + # Recursively evaluate the type + if isinstance(value, ForwardRef): + if getattr(value, "__forward_module__", True) is not None: + globals = None + return evaluate_forward_ref( + value, + globals=globals, + locals=locals, + type_params=type_params, owner=owner, + _recursive_guard=_recursive_guard, format=format + ) + if sys.version_info < (3, 12, 5) and type_params: + # Make use of type_params + locals = dict(locals) if locals else {} + for tvar in type_params: + if tvar.__name__ not in locals: # lets not overwrite something present + locals[tvar.__name__] = tvar + if sys.version_info < (3, 12, 5): + return typing._eval_type( + value, + globals, + locals, + recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, + ) + else: + return typing._eval_type( + value, + globals, + locals, + type_params, + recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, + ) + + +if sys.version_info >= (3, 14, 0, "beta"): + type_repr = annotationlib.type_repr +else: + def type_repr(value): + """Convert a Python value to a format suitable for use with the STRING format. + + This is intended as a helper for tools that support the STRING format but do + not have access to the code that originally produced the annotations. It uses + repr() for most objects. + + """ + if isinstance(value, (type, _types.FunctionType, _types.BuiltinFunctionType)): + if value.__module__ == "builtins": + return value.__qualname__ + return f"{value.__module__}.{value.__qualname__}" + if value is ...: + return "..." + return repr(value) + + +# Aliases for items that are in typing in all supported versions. +# We use hasattr() checks so this library will continue to import on +# future versions of Python that may remove these names. +_typing_names = [ + "AbstractSet", + "AnyStr", + "BinaryIO", + "Callable", + "Collection", + "Container", + "Dict", + "FrozenSet", + "Hashable", + "IO", + "ItemsView", + "Iterable", + "Iterator", + "KeysView", + "List", + "Mapping", + "MappingView", + "Match", + "MutableMapping", + "MutableSequence", + "MutableSet", + "Optional", + "Pattern", + "Reversible", + "Sequence", + "Set", + "Sized", + "TextIO", + "Tuple", + "Union", + "ValuesView", + "cast", + "no_type_check", + # This is private, but it was defined by typing_extensions for a long time + # and some users rely on it. + "_AnnotatedAlias", +] + +# Breakpoint: https://github.com/python/cpython/pull/133602 +if sys.version_info < (3, 15, 0): + _typing_names.append("no_type_check_decorator") + __all__.append("no_type_check_decorator") + +globals().update( + {name: getattr(typing, name) for name in _typing_names if hasattr(typing, name)} +) +# These are defined unconditionally because they are used in +# typing-extensions itself. +Generic = typing.Generic +ForwardRef = typing.ForwardRef +Annotated = typing.Annotated diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA new file mode 100644 index 0000000..886db47 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA @@ -0,0 +1,49 @@ +Metadata-Version: 2.5 +Name: typing-inspection +Version: 0.4.4 +Summary: Runtime typing introspection tools +Project-URL: Homepage, https://github.com/pydantic/typing-inspection +Project-URL: Documentation, https://pydantic.github.io/typing-inspection/dev/ +Project-URL: Source, https://github.com/pydantic/typing-inspection +Project-URL: Changelog, https://github.com/pydantic/typing-inspection/blob/main/HISTORY.md +Author-email: Victorien Plot +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Requires-Python: >=3.10 +Requires-Dist: typing-extensions>=4.15.0 +Description-Content-Type: text/markdown + +# typing-inspection + +[![CI](https://img.shields.io/github/actions/workflow/status/pydantic/typing-inspection/ci.yml?branch=main&logo=github&label=CI)](https://github.com/pydantic/typing-inspection/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/typing-inspection.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/typing-inspection) +[![PyPI](https://img.shields.io/pypi/v/typing-inspection.svg)](https://pypi.org/project/typing-inspection/) +[![Versions](https://img.shields.io/pypi/pyversions/typing-inspection.svg)](https://github.com/pydantic/typing-inspection) +[![License](https://img.shields.io/github/license/pydantic/typing-inspection.svg)](https://github.com/pydantic/typing-inspection/blob/main/LICENSE) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) + +`typing-inspection` provides tools to inspect type annotations at runtime. + +## Installation + +From [PyPI](https://pypi.org/project/typing-inspection/): + +```bash +pip install typing-inspection +``` + +The library can be imported from the `typing_inspection` module. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD new file mode 100644 index 0000000..cd59891 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD @@ -0,0 +1,9 @@ +typing_inspection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +typing_inspection/introspection.py,sha256=WIjEW9Yn2mktlSdpnj9Umt81ZRWWsWVncjEOEhgbQ5Y,22550 +typing_inspection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +typing_inspection/typing_objects.py,sha256=p3jWKQ1RWBycmiPgJjrypGBMdt71xNQKfNGjdbAEb_Y,17838 +typing_inspection/typing_objects.pyi,sha256=O9JN2S0CcNMdZP1Aq8HvhpP1Miqrmnius8W2L5kvhL4,9635 +typing_inspection-0.4.4.dist-info/METADATA,sha256=utPZ5jQu9nUM9RKOyUIye4vr3OC25Nb2fLFbJ65RGNo,2554 +typing_inspection-0.4.4.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87 +typing_inspection-0.4.4.dist-info/licenses/LICENSE,sha256=gEtZsl8sMb0nj5ICoZrkmjlFqiZkOH4tChKMfKzGHsM,1090 +typing_inspection-0.4.4.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL new file mode 100644 index 0000000..51abe1f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.32.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..e825ad5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Pydantic Services Inc. 2025 to present + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py new file mode 100644 index 0000000..458c4ee --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py @@ -0,0 +1,587 @@ +"""High-level introspection utilities, used to inspect type annotations.""" + +from __future__ import annotations + +import sys +import types +from collections.abc import Generator +from dataclasses import InitVar +from enum import Enum, IntEnum, auto +from typing import Any, Literal, NamedTuple, TypeAlias, cast + +from typing_extensions import assert_never, get_args, get_origin # noqa: UP035 + +from . import typing_objects + +__all__ = ( + 'AnnotationSource', + 'ForbiddenQualifier', + 'InspectedAnnotation', + 'Qualifier', + 'get_literal_values', + 'inspect_annotation', + 'is_union_origin', +) + +if sys.version_info >= (3, 14): + + def is_union_origin(obj: Any, /) -> bool: + """Return whether the provided origin is the union form. + + ```pycon + >>> is_union_origin(typing.Union) + True + >>> is_union_origin(get_origin(int | str)) + True + >>> is_union_origin(types.UnionType) + True + ``` + + !!! note + Since Python 3.14, both `Union[, , ...]` and ` | | ...` forms create instances + of the same [`typing.Union`][] class. As such, it is recommended to not use this function + anymore (provided that you only support Python 3.14 or greater), and instead use the + [`typing_objects.is_union()`][typing_inspection.typing_objects.is_union] function directly: + + ```python + from typing import Union, get_origin + + from typing_inspection import typing_objects + + typ = int | str # Or Union[int, str] + origin = get_origin(typ) + if typing_objects.is_union(origin): + ... + ``` + """ + return typing_objects.is_union(obj) + + +else: + + def is_union_origin(obj: Any, /) -> bool: + """Return whether the provided origin is the union form. + + ```pycon + >>> is_union_origin(typing.Union) + True + >>> is_union_origin(get_origin(int | str)) + True + >>> is_union_origin(types.UnionType) + True + ``` + + !!! note + Since Python 3.14, both `Union[, , ...]` and ` | | ...` forms create instances + of the same [`typing.Union`][] class. As such, it is recommended to not use this function + anymore (provided that you only support Python 3.14 or greater), and instead use the + [`typing_objects.is_union()`][typing_inspection.typing_objects.is_union] function directly: + + ```python + from typing import Union, get_origin + + from typing_inspection import typing_objects + + typ = int | str # Or Union[int, str] + origin = get_origin(typ) + if typing_objects.is_union(origin): + ... + ``` + """ + return typing_objects.is_union(obj) or obj is types.UnionType + + +_literal_allowed_types = int | bytes | str | bool | Enum | typing_objects.NoneType + + +def _literal_type_check(value: Any, /) -> None: + """Type check the provided literal value against the legal parameters.""" + if not isinstance(value, _literal_allowed_types) and value is not typing_objects.NoneType: + raise TypeError(f'{value} is not a valid literal value, must be one of: int, bytes, str, Enum, None.') + + +def get_literal_values( + annotation: Any, + /, + *, + type_check: bool = False, + unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'eager', +) -> Generator[Any]: + """Yield the values contained in the provided [`Literal`][typing.Literal] [special form][]. + + Args: + annotation: The [`Literal`][typing.Literal] [special form][] to unpack. + type_check: Whether to check if the literal values are [legal parameters][literal-legal-parameters]. + Raises a [`TypeError`][] otherwise. + unpack_type_aliases: What to do when encountering [PEP 695](https://peps.python.org/pep-0695/) + [type aliases][type-aliases]. Can be one of: + + - `'skip'`: Do not try to parse type aliases. Note that this can lead to incorrect results: + ```pycon + >>> type MyAlias = Literal[1, 2] + >>> list(get_literal_values(Literal[MyAlias, 3], unpack_type_aliases="skip")) + [MyAlias, 3] + ``` + + - `'lenient'`: Try to parse type aliases, and fallback to `'skip'` if the type alias can't be inspected + (because of an undefined forward reference). + + - `'eager'`: Parse type aliases and raise any encountered [`NameError`][] exceptions (the default): + ```pycon + >>> type MyAlias = Literal[1, 2] + >>> list(get_literal_values(Literal[MyAlias, 3], unpack_type_aliases="eager")) + [1, 2, 3] + ``` + + Note: + While `None` is [equivalent to][none] `type(None)`, the runtime implementation of [`Literal`][typing.Literal] + does not de-duplicate them. This function makes sure this de-duplication is applied: + + ```pycon + >>> list(get_literal_values(Literal[NoneType, None])) + [None] + ``` + + Example: + ```pycon + >>> type Ints = Literal[1, 2] + >>> list(get_literal_values(Literal[1, Ints], unpack_type_alias="skip")) + ["a", Ints] + >>> list(get_literal_values(Literal[1, Ints])) + [1, 2] + >>> list(get_literal_values(Literal[1.0], type_check=True)) + Traceback (most recent call last): + ... + TypeError: 1.0 is not a valid literal value, must be one of: int, bytes, str, Enum, None. + ``` + """ + # `literal` is guaranteed to be a `Literal[...]` special form, so use + # `__args__` directly instead of calling `get_args()`. + + if unpack_type_aliases == 'skip': + _has_none = False + # `Literal` parameters are already deduplicated, no need to do it ourselves. + # (we only check for `None` and `NoneType`, which should be considered as duplicates). + for arg in annotation.__args__: + if type_check: + _literal_type_check(arg) + if arg is None or arg is typing_objects.NoneType: + if not _has_none: + yield None + _has_none = True + else: + yield arg + else: + # We'll need to manually deduplicate parameters, see the `Literal` implementation in `typing`. + values_and_type: list[tuple[Any, type[Any]]] = [] + + for arg in annotation.__args__: + # Note: we could also check for generic aliases with a type alias as an origin. + # However, it is very unlikely that this happens as type variables can't appear in + # `Literal` forms, so the only valid (but unnecessary) use case would be something like: + # `type Test[T] = Literal['a']` (and then use `Test[SomeType]`). + if typing_objects.is_typealiastype(arg): + try: + alias_value = arg.__value__ + except NameError: + if unpack_type_aliases == 'eager': + raise + # unpack_type_aliases == "lenient": + if type_check: + _literal_type_check(arg) + values_and_type.append((arg, type(arg))) + else: + sub_args = get_literal_values( + alias_value, type_check=type_check, unpack_type_aliases=unpack_type_aliases + ) + values_and_type.extend((a, type(a)) for a in sub_args) # pyright: ignore[reportUnknownArgumentType] + else: + if type_check: + _literal_type_check(arg) + if arg is typing_objects.NoneType: + values_and_type.append((None, typing_objects.NoneType)) + else: + values_and_type.append((arg, type(arg))) # pyright: ignore[reportUnknownArgumentType] + + try: + dct = dict.fromkeys(values_and_type) + except TypeError: + # Unhashable parameters, the Python implementation allows them + yield from (p for p, _ in values_and_type) + else: + yield from (p for p, _ in dct) + + +Qualifier: TypeAlias = Literal['required', 'not_required', 'read_only', 'class_var', 'init_var', 'final'] +"""A [type qualifier][].""" + +_all_qualifiers: set[Qualifier] = set(get_args(Qualifier)) + + +# TODO at some point, we could switch to an enum flag, so that multiple sources +# can be combined. However, is there a need for this? +class AnnotationSource(IntEnum): + # TODO if/when https://peps.python.org/pep-0767/ is accepted, add 'read_only' + # to CLASS and NAMED_TUPLE (even though for named tuples it is redundant). + + """The source of an annotation, e.g. a class or a function. + + Depending on the source, different [type qualifiers][type qualifier] may be (dis)allowed. + """ + + ASSIGNMENT_OR_VARIABLE = auto() + """An annotation used in an assignment or variable annotation: + + ```python + x: Final[int] = 1 + y: Final[str] + ``` + + **Allowed type qualifiers:** [`Final`][typing.Final]. + """ + + CLASS = auto() + """An annotation used in the body of a class: + + ```python + class Test: + x: Final[int] = 1 + y: ClassVar[str] + ``` + + **Allowed type qualifiers:** [`ClassVar`][typing.ClassVar], [`Final`][typing.Final]. + """ + + DATACLASS = auto() + """An annotation used in the body of a dataclass: + + ```python + @dataclass + class Test: + x: Final[int] = 1 + y: InitVar[str] = 'test' + ``` + + **Allowed type qualifiers:** [`ClassVar`][typing.ClassVar], [`Final`][typing.Final], [`InitVar`][dataclasses.InitVar]. + """ # noqa: E501 + + TYPED_DICT = auto() + """An annotation used in the body of a [`TypedDict`][typing.TypedDict]: + + ```python + class TD(TypedDict): + x: Required[ReadOnly[int]] + y: ReadOnly[NotRequired[str]] + ``` + + **Allowed type qualifiers:** [`ReadOnly`][typing.ReadOnly], [`Required`][typing.Required], + [`NotRequired`][typing.NotRequired]. + """ + + NAMED_TUPLE = auto() + """An annotation used in the body of a [`NamedTuple`][typing.NamedTuple]. + + ```python + class NT(NamedTuple): + x: int + y: str + ``` + + **Allowed type qualifiers:** none. + """ + + FUNCTION = auto() + """An annotation used in a function, either for a parameter or the return value. + + ```python + def func(a: int) -> str: + ... + ``` + + **Allowed type qualifiers:** none. + """ + + ANY = auto() + """An annotation that might come from any source. + + **Allowed type qualifiers:** all. + """ + + BARE = auto() + """An annotation that is inspected as is. + + **Allowed type qualifiers:** none. + """ + + @property + def allowed_qualifiers(self) -> set[Qualifier]: + """The allowed [type qualifiers][type qualifier] for this annotation source.""" + # TODO use a match statement when Python 3.9 support is dropped. + if self is AnnotationSource.ASSIGNMENT_OR_VARIABLE: + return {'final'} + elif self is AnnotationSource.CLASS: + return {'final', 'class_var'} + elif self is AnnotationSource.DATACLASS: + return {'final', 'class_var', 'init_var'} + elif self is AnnotationSource.TYPED_DICT: + return {'required', 'not_required', 'read_only'} + elif self in (AnnotationSource.NAMED_TUPLE, AnnotationSource.FUNCTION, AnnotationSource.BARE): + return set() + elif self is AnnotationSource.ANY: + return _all_qualifiers + else: # pragma: no cover + assert_never(self) + + +class ForbiddenQualifier(Exception): + """The provided [type qualifier][] is forbidden.""" + + qualifier: Qualifier + """The forbidden qualifier.""" + + def __init__(self, qualifier: Qualifier, /) -> None: + self.qualifier = qualifier + + +class _UnknownTypeEnum(Enum): + UNKNOWN = auto() + + def __str__(self) -> str: + return 'UNKNOWN' + + def __repr__(self) -> str: + return '' + + +UNKNOWN = _UnknownTypeEnum.UNKNOWN +"""A sentinel value used when no [type expression][] is present.""" + +_UnknownType: TypeAlias = Literal[_UnknownTypeEnum.UNKNOWN] +"""The type of the [`UNKNOWN`][typing_inspection.introspection.UNKNOWN] sentinel value.""" + + +class InspectedAnnotation(NamedTuple): + """The result of the inspected annotation.""" + + type: Any | _UnknownType + """The final [type expression][], with [type qualifiers][type qualifier] and annotated metadata stripped. + + If no type expression is available, the [`UNKNOWN`][typing_inspection.introspection.UNKNOWN] sentinel + value is used instead. This is the case when a [type qualifier][] is used with no type annotation: + + ```python + ID: Final = 1 + + class C: + x: ClassVar = 'test' + ``` + """ + + qualifiers: set[Qualifier] + """The [type qualifiers][type qualifier] present on the annotation.""" + + metadata: list[Any] + """The annotated metadata.""" + + +def inspect_annotation( # noqa: PLR0915 + annotation: Any, + /, + *, + annotation_source: AnnotationSource, + unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'skip', +) -> InspectedAnnotation: + """Inspect an [annotation expression][], extracting any [type qualifier][] and metadata. + + An [annotation expression][] is a [type expression][] optionally surrounded by one or more + [type qualifiers][type qualifier] or by [`Annotated`][typing.Annotated]. This function will: + + - Unwrap the type expression, keeping track of the type qualifiers. + - Unwrap [`Annotated`][typing.Annotated] forms, keeping track of the annotated metadata. + + Args: + annotation: The annotation expression to be inspected. + annotation_source: The source of the annotation. Depending on the source (e.g. a class), different type + qualifiers may be (dis)allowed. To allow any type qualifier, use + [`AnnotationSource.ANY`][typing_inspection.introspection.AnnotationSource.ANY]. + unpack_type_aliases: What to do when encountering [PEP 695](https://peps.python.org/pep-0695/) + [type aliases][type-aliases]. Can be one of: + + - `'skip'`: Do not try to parse type aliases (the default): + ```pycon + >>> type MyInt = Annotated[int, 'meta'] + >>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='skip') + InspectedAnnotation(type=MyInt, qualifiers={}, metadata=[]) + ``` + + - `'lenient'`: Try to parse type aliases, and fallback to `'skip'` if the type alias + can't be inspected (because of an undefined forward reference): + ```pycon + >>> type MyInt = Annotated[Undefined, 'meta'] + >>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='lenient') + InspectedAnnotation(type=MyInt, qualifiers={}, metadata=[]) + >>> Undefined = int + >>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='lenient') + InspectedAnnotation(type=int, qualifiers={}, metadata=['meta']) + ``` + + - `'eager'`: Parse type aliases and raise any encountered [`NameError`][] exceptions. + + Returns: + The result of the inspected annotation, where the type expression, used qualifiers and metadata is stored. + + Example: + ```pycon + >>> inspect_annotation( + ... Final[Annotated[ClassVar[Annotated[int, 'meta_1']], 'meta_2']], + ... annotation_source=AnnotationSource.CLASS, + ... ) + ... + InspectedAnnotation(type=int, qualifiers={'class_var', 'final'}, metadata=['meta_1', 'meta_2']) + ``` + """ + allowed_qualifiers = annotation_source.allowed_qualifiers + qualifiers: set[Qualifier] = set() + metadata: list[Any] = [] + + while True: + annotation, _meta = _unpack_annotated(annotation, unpack_type_aliases=unpack_type_aliases) + if _meta: + metadata = _meta + metadata + continue + + origin = get_origin(annotation) + if origin is not None: + if typing_objects.is_classvar(origin): + if 'class_var' not in allowed_qualifiers: + raise ForbiddenQualifier('class_var') + qualifiers.add('class_var') + annotation = annotation.__args__[0] + elif typing_objects.is_final(origin): + if 'final' not in allowed_qualifiers: + raise ForbiddenQualifier('final') + qualifiers.add('final') + annotation = annotation.__args__[0] + elif typing_objects.is_required(origin): + if 'required' not in allowed_qualifiers: + raise ForbiddenQualifier('required') + qualifiers.add('required') + annotation = annotation.__args__[0] + elif typing_objects.is_notrequired(origin): + if 'not_required' not in allowed_qualifiers: + raise ForbiddenQualifier('not_required') + qualifiers.add('not_required') + annotation = annotation.__args__[0] + elif typing_objects.is_readonly(origin): + if 'read_only' not in allowed_qualifiers: + raise ForbiddenQualifier('not_required') + qualifiers.add('read_only') + annotation = annotation.__args__[0] + else: + # origin is not None but not a type qualifier nor `Annotated` (e.g. `list[int]`): + break + elif isinstance(annotation, InitVar): + if 'init_var' not in allowed_qualifiers: + raise ForbiddenQualifier('init_var') + qualifiers.add('init_var') + annotation = cast(Any, annotation.type) + else: + break + + # `Final`, `ClassVar` and `InitVar` are type qualifiers allowed to be used as a bare annotation: + if typing_objects.is_final(annotation): + if 'final' not in allowed_qualifiers: + raise ForbiddenQualifier('final') + qualifiers.add('final') + annotation = UNKNOWN + elif typing_objects.is_classvar(annotation): + if 'class_var' not in allowed_qualifiers: + raise ForbiddenQualifier('class_var') + qualifiers.add('class_var') + annotation = UNKNOWN + elif annotation is InitVar: + if 'init_var' not in allowed_qualifiers: + raise ForbiddenQualifier('init_var') + qualifiers.add('init_var') + annotation = UNKNOWN + + return InspectedAnnotation(annotation, qualifiers, metadata) + + +def _unpack_annotated_inner( + annotation: Any, unpack_type_aliases: Literal['lenient', 'eager'], check_annotated: bool +) -> tuple[Any, list[Any]]: + origin = get_origin(annotation) + if check_annotated and typing_objects.is_annotated(origin): + annotated_type = annotation.__origin__ + metadata = list(annotation.__metadata__) + + # The annotated type might be a PEP 695 type alias, so we need to recursively + # unpack it. Because Python already flattens `Annotated[Annotated[, ...], ...]` forms, + # we can skip the `is_annotated()` check in the next call: + annotated_type, sub_meta = _unpack_annotated_inner( + annotated_type, unpack_type_aliases=unpack_type_aliases, check_annotated=False + ) + metadata = sub_meta + metadata + return annotated_type, metadata + elif typing_objects.is_typealiastype(annotation): + try: + value = annotation.__value__ + except NameError: + if unpack_type_aliases == 'eager': + raise + else: + typ, metadata = _unpack_annotated_inner( + value, unpack_type_aliases=unpack_type_aliases, check_annotated=True + ) + if metadata: + # Having metadata means the type alias' `__value__` was an `Annotated` form + # (or, recursively, a type alias to an `Annotated` form). It is important to check + # for this, as we don't want to unpack other type aliases (e.g. `type MyInt = int`). + return typ, metadata + return annotation, [] + elif typing_objects.is_typealiastype(origin): + # When parameterized, PEP 695 type aliases become generic aliases + # (e.g. with `type MyList[T] = Annotated[list[T], ...]`, `MyList[int]` + # is a generic alias). + try: + value = origin.__value__ + except NameError: + if unpack_type_aliases == 'eager': + raise + else: + # While Python already handles type variable replacement for simple `Annotated` forms, + # we need to manually apply the same logic for PEP 695 type aliases: + # - With `MyList = Annotated[list[T], ...]`, `MyList[int] == Annotated[list[int], ...]` + # - With `type MyList[T] = Annotated[list[T], ...]`, `MyList[int].__value__ == Annotated[list[T], ...]`. + + try: + # To do so, we emulate the parameterization of the value with the arguments: + # with `type MyList[T] = Annotated[list[T], ...]`, to emulate `MyList[int]`, + # we do `Annotated[list[T], ...][int]` (which gives `Annotated[list[T], ...]`): + value = value[annotation.__args__] + except TypeError: + # Might happen if the type alias is parameterized, but its value doesn't have any + # type variables, e.g. `type MyInt[T] = int`. + pass + typ, metadata = _unpack_annotated_inner( + value, unpack_type_aliases=unpack_type_aliases, check_annotated=True + ) + if metadata: + return typ, metadata + return annotation, [] + + return annotation, [] + + +# This could eventually be made public: +def _unpack_annotated( + annotation: Any, /, *, unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'eager' +) -> tuple[Any, list[Any]]: + if unpack_type_aliases == 'skip': + if typing_objects.is_annotated(get_origin(annotation)): + return annotation.__origin__, list(annotation.__metadata__) + else: + return annotation, [] + + return _unpack_annotated_inner(annotation, unpack_type_aliases=unpack_type_aliases, check_annotated=True) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py new file mode 100644 index 0000000..610f592 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py @@ -0,0 +1,620 @@ +"""Low-level introspection utilities for [`typing`][] members. + +The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][] +variants, if they exists and are different. +""" +# ruff: noqa: UP006 + +import collections.abc +import contextlib +import re +import sys +import typing +import warnings +from textwrap import dedent +from types import FunctionType, GenericAlias, NoneType +from typing import Any, Final + +import typing_extensions +from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated + +__all__ = ( + 'DEPRECATED_ALIASES', + 'DEPRECATED_ALIASES_IDS', + 'NoneType', + 'is_annotated', + 'is_any', + 'is_classvar', + 'is_concatenate', + 'is_deprecated', + 'is_final', + 'is_forwardref', + 'is_generic', + 'is_literal', + 'is_literalstring', + 'is_namedtuple', + 'is_never', + 'is_newtype', + 'is_nodefault', + 'is_noextraitems', + 'is_noreturn', + 'is_notrequired', + 'is_paramspec', + 'is_paramspecargs', + 'is_paramspeckwargs', + 'is_readonly', + 'is_required', + 'is_self', + 'is_typealias', + 'is_typealiastype', + 'is_typeguard', + 'is_typeis', + 'is_typevar', + 'is_typevartuple', + 'is_union', + 'is_unpack', +) + +_IS_PY310 = sys.version_info[:2] == (3, 10) + + +def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType: + """Create a function checking that the function argument is the (unparameterized) typing `member`. + + The function will make sure to check against both the `typing` and `typing_extensions` + variants as depending on the Python version, the `typing_extensions` variant might be different. + For instance, on Python 3.9: + + ```pycon + >>> from typing import Literal as t_Literal + >>> from typing_extensions import Literal as te_Literal, get_origin + + >>> t_Literal is te_Literal + False + >>> get_origin(t_Literal[1]) + typing.Literal + >>> get_origin(te_Literal[1]) + typing_extensions.Literal + ``` + """ + in_typing = hasattr(typing, member) + in_typing_extensions = hasattr(typing_extensions, member) + + globals_: dict[str, Any] = {'Any': Any} + + if in_typing and in_typing_extensions: + # For performance reasons, cache the objects in `globals_` so the generated function avoids + # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`): + t_obj = getattr(typing, member) + te_obj = getattr(typing_extensions, member) + if t_obj is te_obj: + globals_['_obj'] = t_obj + check_code = 'obj is _obj' + else: + globals_['_t_obj'] = t_obj + globals_['_te_obj'] = te_obj + check_code = 'obj is _t_obj or obj is _te_obj' + elif in_typing and not in_typing_extensions: + globals_['_obj'] = getattr(typing, member) + check_code = 'obj is _obj' + elif not in_typing and in_typing_extensions: + globals_['_obj'] = getattr(typing_extensions, member) + check_code = 'obj is _obj' + else: + check_code = 'False' + + func_code = dedent(f""" + def {function_name}(obj: Any, /) -> bool: + return {check_code} + """) + + locals_: dict[str, Any] = {} + exec(func_code, globals_, locals_) + return locals_[function_name] + + +def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType: + """Create a function checking that the function is an instance of the typing `member`. + + The function will make sure to check against both the `typing` and `typing_extensions` + variants as depending on the Python version, the `typing_extensions` variant might be different. + """ + in_typing = hasattr(typing, member) + in_typing_extensions = hasattr(typing_extensions, member) + + globals_: dict[str, Any] = {'Any': Any} + + if in_typing and in_typing_extensions: + # For performance reasons, cache the objects in `globals_` so the generated function avoids + # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`): + t_obj = getattr(typing, member) + te_obj = getattr(typing_extensions, member) + if t_obj is te_obj: + globals_['_obj'] = t_obj + check_code = 'isinstance(obj, _obj)' + else: + globals_['_objs'] = (t_obj, te_obj) + check_code = 'isinstance(obj, _objs)' + elif in_typing and not in_typing_extensions: + globals_['_obj'] = getattr(typing, member) + check_code = 'isinstance(obj, _obj)' + elif not in_typing and in_typing_extensions: + globals_['_obj'] = getattr(typing_extensions, member) + check_code = 'isinstance(obj, _obj)' + else: + check_code = 'False' + + func_code = dedent(f""" + def {function_name}(obj: Any, /) -> 'TypeIs[{member}]': + return {check_code} + """) + + locals_: dict[str, Any] = {} + exec(func_code, globals_, locals_) + return locals_[function_name] + + +# Keep this ordered, as per `typing.__all__`: + +is_annotated = _compile_identity_check_function('Annotated', 'is_annotated') +is_annotated.__doc__ = """ +Return whether the argument is the [`Annotated`][typing.Annotated] [special form][]. + +```pycon +>>> is_annotated(Annotated) +True +>>> is_annotated(Annotated[int, ...]) +False +``` +""" + +is_any = _compile_identity_check_function('Any', 'is_any') +is_any.__doc__ = """ +Return whether the argument is the [`Any`][typing.Any] [special form][]. + +```pycon +>>> is_any(Any) +True +``` +""" + +is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar') +is_classvar.__doc__ = """ +Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][]. + +```pycon +>>> is_classvar(ClassVar) +True +>>> is_classvar(ClassVar[int]) +>>> False +``` +""" + +is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate') +is_concatenate.__doc__ = """ +Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][]. + +```pycon +>>> is_concatenate(Concatenate) +True +>>> is_concatenate(Concatenate[int, P]) +False +``` +""" + +is_final = _compile_identity_check_function('Final', 'is_final') +is_final.__doc__ = """ +Return whether the argument is the [`Final`][typing.Final] [type qualifier][]. + +```pycon +>>> is_final(Final) +True +>>> is_final(Final[int]) +False +``` +""" + + +# Unlikely to have a different version in `typing-extensions`, but keep it consistent. +# Also note that starting in 3.14, this is an alias to `annotationlib.ForwardRef`, but +# accessing it from `typing` doesn't seem to be deprecated. +is_forwardref = _compile_isinstance_check_function('ForwardRef', 'is_forwardref') +is_forwardref.__doc__ = """ +Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef]. + +```pycon +>>> is_forwardref(ForwardRef('T')) +True +``` +""" + + +is_generic = _compile_identity_check_function('Generic', 'is_generic') +is_generic.__doc__ = """ +Return whether the argument is the [`Generic`][typing.Generic] [special form][]. + +```pycon +>>> is_generic(Generic) +True +>>> is_generic(Generic[T]) +False +``` +""" + +is_literal = _compile_identity_check_function('Literal', 'is_literal') +is_literal.__doc__ = """ +Return whether the argument is the [`Literal`][typing.Literal] [special form][]. + +```pycon +>>> is_literal(Literal) +True +>>> is_literal(Literal["a"]) +False +``` +""" + + +# `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented. + +is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec') +is_paramspec.__doc__ = """ +Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec]. + +```pycon +>>> P = ParamSpec('P') +>>> is_paramspec(P) +True +``` +""" + +# Protocol? + +is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar') +is_typevar.__doc__ = """ +Return whether the argument is an instance of [`TypeVar`][typing.TypeVar]. + +```pycon +>>> T = TypeVar('T') +>>> is_typevar(T) +True +``` +""" + +is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple') +is_typevartuple.__doc__ = """ +Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple]. + +```pycon +>>> Ts = TypeVarTuple('Ts') +>>> is_typevartuple(Ts) +True +``` +""" + +is_union = _compile_identity_check_function('Union', 'is_union') +is_union.__doc__ = """ +Return whether the argument is the [`Union`][typing.Union] [special form][]. + +This function can also be used to check for the [`Optional`][typing.Optional] [special form][], +as at runtime, `Optional[int]` is equivalent to `Union[int, None]`. + +```pycon +>>> is_union(Union) +True +>>> is_union(Union[int, str]) +False +``` + +!!! warning + This does not check for unions using the [new syntax][types-union] (e.g. `int | str`). +""" + + +def is_namedtuple(obj: Any, /) -> bool: + """Return whether the argument is a named tuple type. + + This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the + [`collections.namedtuple`][] factory function. + + ```pycon + >>> class User(NamedTuple): + ... name: str + ... + >>> is_namedtuple(User) + True + >>> City = collections.namedtuple('City', []) + >>> is_namedtuple(City) + True + >>> is_namedtuple(NamedTuple) + False + ``` + """ + return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType] + + +# TypedDict? + +# BinaryIO? IO? TextIO? + +is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring') +is_literalstring.__doc__ = """ +Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][]. + +```pycon +>>> is_literalstring(LiteralString) +True +``` +""" + +is_never = _compile_identity_check_function('Never', 'is_never') +is_never.__doc__ = """ +Return whether the argument is the [`Never`][typing.Never] [special form][]. + +```pycon +>>> is_never(Never) +True +``` +""" + +is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype') +is_newtype.__doc__ = """ +Return whether the argument is a [`NewType`][typing.NewType]. + +```pycon +>>> UserId = NewType("UserId", int) +>>> is_newtype(UserId) +True +``` +""" + +is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault') +is_nodefault.__doc__ = """ +Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object. + +```pycon +>>> is_nodefault(NoDefault) +True +``` +""" + +is_noextraitems = _compile_identity_check_function('NoExtraItems', 'is_noextraitems') +is_noextraitems.__doc__ = """ +Return whether the argument is the `NoExtraItems` sentinel object. + +```pycon +>>> is_noextraitems(NoExtraItems) +True +``` +""" + +is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn') +is_noreturn.__doc__ = """ +Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][]. + +```pycon +>>> is_noreturn(NoReturn) +True +>>> is_noreturn(Never) +False +``` +""" + +is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired') +is_notrequired.__doc__ = """ +Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][]. + +```pycon +>>> is_notrequired(NotRequired) +True +``` +""" + +is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs') +is_paramspecargs.__doc__ = """ +Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs]. + +```pycon +>>> P = ParamSpec('P') +>>> is_paramspecargs(P.args) +True +``` +""" + +is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs') +is_paramspeckwargs.__doc__ = """ +Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs]. + +```pycon +>>> P = ParamSpec('P') +>>> is_paramspeckwargs(P.kwargs) +True +``` +""" + +is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly') +is_readonly.__doc__ = """ +Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][]. + +```pycon +>>> is_readonly(ReadOnly) +True +``` +""" + +is_required = _compile_identity_check_function('Required', 'is_required') +is_required.__doc__ = """ +Return whether the argument is the [`Required`][typing.Required] [special form][]. + +```pycon +>>> is_required(Required) +True +``` +""" + +is_self = _compile_identity_check_function('Self', 'is_self') +is_self.__doc__ = """ +Return whether the argument is the [`Self`][typing.Self] [special form][]. + +```pycon +>>> is_self(Self) +True +``` +""" + +# TYPE_CHECKING? + +is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias') +is_typealias.__doc__ = """ +Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][]. + +```pycon +>>> is_typealias(TypeAlias) +True +``` +""" + +is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard') +is_typeguard.__doc__ = """ +Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][]. + +```pycon +>>> is_typeguard(TypeGuard) +True +``` +""" + +is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis') +is_typeis.__doc__ = """ +Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][]. + +```pycon +>>> is_typeis(TypeIs) +True +``` +""" + +_is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner') + + +if _IS_PY310: + # Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0. + # On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`, + # `isinstance(Alias[int], TypeAliasType)` returns `True`. + # See https://github.com/python/cpython/issues/89828. + def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]': + return type(obj) is not GenericAlias and _is_typealiastype_inner(obj) +else: + is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype') + +is_typealiastype.__doc__ = """ +Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance. + +```pycon +>>> type MyInt = int +>>> is_typealiastype(MyInt) +True +>>> MyStr = TypeAliasType("MyStr", str) +>>> is_typealiastype(MyStr): +True +>>> type MyList[T] = list[T] +>>> is_typealiastype(MyList[int]) +False +``` +""" + +is_unpack = _compile_identity_check_function('Unpack', 'is_unpack') +is_unpack.__doc__ = """ +Return whether the argument is the [`Unpack`][typing.Unpack] [special form][]. + +```pycon +>>> is_unpack(Unpack) +True +>>> is_unpack(Unpack[Ts]) +False +``` +""" + + +if sys.version_info >= (3, 13): + _deprecated_types = (warnings.deprecated, typing_extensions.deprecated) + + def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]': + return isinstance(obj, _deprecated_types) + +else: + _deprecated_type = typing_extensions.deprecated + + def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]': + return isinstance(obj, _deprecated_type) + + +is_deprecated.__doc__ = """ +Return whether the argument is a [`deprecated`][warnings.deprecated] instance. + +This also includes the [`typing_extensions` backport][typing_extensions.deprecated]. + +```pycon +>>> is_deprecated(warnings.deprecated('message')) +True +>>> is_deprecated(typing_extensions.deprecated('message')) +True +``` +""" + + +# Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliased as `alias()`): +DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = { + typing.Hashable: collections.abc.Hashable, + typing.Awaitable: collections.abc.Awaitable, + typing.Coroutine: collections.abc.Coroutine, + typing.AsyncIterable: collections.abc.AsyncIterable, + typing.AsyncIterator: collections.abc.AsyncIterator, + typing.Iterable: collections.abc.Iterable, + typing.Iterator: collections.abc.Iterator, + typing.Reversible: collections.abc.Reversible, + typing.Sized: collections.abc.Sized, + typing.Container: collections.abc.Container, + typing.Collection: collections.abc.Collection, + # type ignore reason: https://github.com/python/typeshed/issues/6257: + typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType] + typing.AbstractSet: collections.abc.Set, + typing.MutableSet: collections.abc.MutableSet, + typing.Mapping: collections.abc.Mapping, + typing.MutableMapping: collections.abc.MutableMapping, + typing.Sequence: collections.abc.Sequence, + typing.MutableSequence: collections.abc.MutableSequence, + typing.Tuple: tuple, + typing.List: list, + typing.Deque: collections.deque, + typing.Set: set, + typing.FrozenSet: frozenset, + typing.MappingView: collections.abc.MappingView, + typing.KeysView: collections.abc.KeysView, + typing.ItemsView: collections.abc.ItemsView, + typing.ValuesView: collections.abc.ValuesView, + typing.Dict: dict, + typing.DefaultDict: collections.defaultdict, + typing.OrderedDict: collections.OrderedDict, + typing.Counter: collections.Counter, + typing.ChainMap: collections.ChainMap, + typing.Generator: collections.abc.Generator, + typing.AsyncGenerator: collections.abc.AsyncGenerator, + typing.Type: type, + # Defined in `typing.__getattr__`: + typing.Pattern: re.Pattern, + typing.Match: re.Match, + typing.ContextManager: contextlib.AbstractContextManager, + typing.AsyncContextManager: contextlib.AbstractAsyncContextManager, + # Skipped: `ByteString` (deprecated, removed in 3.14) +} +"""A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" + + +DEPRECATED_ALIASES_IDS: Final[dict[int, type[Any]]] = {id(k): v for k, v in DEPRECATED_ALIASES.items()} +"""A mapping between the [identity][id] of the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" + + +# Add the `typing_extensions` aliases: +for alias, target in list(DEPRECATED_ALIASES.items()): + if (te_alias := getattr(typing_extensions, alias.__name__, None)) is not None: + DEPRECATED_ALIASES[te_alias] = target diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi new file mode 100644 index 0000000..69c731b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi @@ -0,0 +1,421 @@ +# Stub file generated using: +# `stubgen --inspect-mode --include-docstrings -m typing_inspection.typing_objects` +# (manual edits need to be applied). +"""Low-level introspection utilities for [`typing`][] members. + +The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][] +variants, if they exists and are different. +""" + +import sys +from typing import Any, Final, ForwardRef, NewType, TypeVar + +from typing_extensions import ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeAliasType, TypeIs, TypeVarTuple, deprecated + +__all__ = [ + 'DEPRECATED_ALIASES', + 'DEPRECATED_ALIASES_IDS', + 'NoneType', + 'is_annotated', + 'is_any', + 'is_classvar', + 'is_concatenate', + 'is_deprecated', + 'is_final', + 'is_generic', + 'is_literal', + 'is_literalstring', + 'is_namedtuple', + 'is_never', + 'is_newtype', + 'is_nodefault', + 'is_noextraitems', + 'is_noreturn', + 'is_notrequired', + 'is_paramspec', + 'is_paramspecargs', + 'is_paramspeckwargs', + 'is_readonly', + 'is_required', + 'is_self', + 'is_typealias', + 'is_typealiastype', + 'is_typeguard', + 'is_typeis', + 'is_typevar', + 'is_typevartuple', + 'is_union', + 'is_unpack', +] + +if sys.version_info >= (3, 10): + from types import NoneType +else: + NoneType = type(None) + +def is_annotated(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Annotated`][typing.Annotated] [special form][]. + + ```pycon + >>> is_annotated(Annotated) + True + >>> is_annotated(Annotated[int, ...]) + False + ``` + """ + +def is_any(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Any`][typing.Any] [special form][]. + + ```pycon + >>> is_any(Any) + True + ``` + """ + +def is_classvar(obj: Any, /) -> bool: + """ + Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][]. + + ```pycon + >>> is_classvar(ClassVar) + True + >>> is_classvar(ClassVar[int]) + >>> False + ``` + """ + +def is_concatenate(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][]. + + ```pycon + >>> is_concatenate(Concatenate) + True + >>> is_concatenate(Concatenate[int, P]) + False + ``` + """ + +def is_final(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Final`][typing.Final] [type qualifier][]. + + ```pycon + >>> is_final(Final) + True + >>> is_final(Final[int]) + False + ``` + """ + +def is_forwardref(obj: Any, /) -> TypeIs[ForwardRef]: + """ + Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef]. + + ```pycon + >>> is_forwardref(ForwardRef('T')) + True + ``` + """ + +def is_generic(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Generic`][typing.Generic] [special form][]. + + ```pycon + >>> is_generic(Generic) + True + >>> is_generic(Generic[T]) + False + ``` + """ + +def is_literal(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Literal`][typing.Literal] [special form][]. + + ```pycon + >>> is_literal(Literal) + True + >>> is_literal(Literal["a"]) + False + ``` + """ + +def is_paramspec(obj: Any, /) -> TypeIs[ParamSpec]: + """ + Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec]. + + ```pycon + >>> P = ParamSpec('P') + >>> is_paramspec(P) + True + ``` + """ + +def is_typevar(obj: Any, /) -> TypeIs[TypeVar]: + """ + Return whether the argument is an instance of [`TypeVar`][typing.TypeVar]. + + ```pycon + >>> T = TypeVar('T') + >>> is_typevar(T) + True + ``` + """ + +def is_typevartuple(obj: Any, /) -> TypeIs[TypeVarTuple]: + """ + Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple]. + + ```pycon + >>> Ts = TypeVarTuple('Ts') + >>> is_typevartuple(Ts) + True + ``` + """ + +def is_union(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Union`][typing.Union] [special form][]. + + This function can also be used to check for the [`Optional`][typing.Optional] [special form][], + as at runtime, `Optional[int]` is equivalent to `Union[int, None]`. + + ```pycon + >>> is_union(Union) + True + >>> is_union(Union[int, str]) + False + ``` + + !!! warning + This does not check for unions using the [new syntax][types-union] (e.g. `int | str`). + """ + +def is_namedtuple(obj: Any, /) -> bool: + """Return whether the argument is a named tuple type. + + This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the + [`collections.namedtuple`][] factory function. + + ```pycon + >>> class User(NamedTuple): + ... name: str + ... + >>> is_namedtuple(User) + True + >>> City = collections.namedtuple('City', []) + >>> is_namedtuple(City) + True + >>> is_namedtuple(NamedTuple) + False + ``` + """ + +def is_literalstring(obj: Any, /) -> bool: + """ + Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][]. + + ```pycon + >>> is_literalstring(LiteralString) + True + ``` + """ + +def is_never(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Never`][typing.Never] [special form][]. + + ```pycon + >>> is_never(Never) + True + ``` + """ + +def is_newtype(obj: Any, /) -> TypeIs[NewType]: + """ + Return whether the argument is a [`NewType`][typing.NewType]. + + ```pycon + >>> UserId = NewType("UserId", int) + >>> is_newtype(UserId) + True + ``` + """ + +def is_nodefault(obj: Any, /) -> bool: + """ + Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object. + + ```pycon + >>> is_nodefault(NoDefault) + True + ``` + """ + +def is_noextraitems(obj: Any, /) -> bool: + """ + Return whether the argument is the `NoExtraItems` sentinel object. + + ```pycon + >>> is_noextraitems(NoExtraItems) + True + ``` + """ + +def is_noreturn(obj: Any, /) -> bool: + """ + Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][]. + + ```pycon + >>> is_noreturn(NoReturn) + True + >>> is_noreturn(Never) + False + ``` + """ + +def is_notrequired(obj: Any, /) -> bool: + """ + Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][]. + + ```pycon + >>> is_notrequired(NotRequired) + True + ``` + """ + +def is_paramspecargs(obj: Any, /) -> TypeIs[ParamSpecArgs]: + """ + Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs]. + + ```pycon + >>> P = ParamSpec('P') + >>> is_paramspecargs(P.args) + True + ``` + """ + +def is_paramspeckwargs(obj: Any, /) -> TypeIs[ParamSpecKwargs]: + """ + Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs]. + + ```pycon + >>> P = ParamSpec('P') + >>> is_paramspeckwargs(P.kwargs) + True + ``` + """ + +def is_readonly(obj: Any, /) -> bool: + """ + Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][]. + + ```pycon + >>> is_readonly(ReadOnly) + True + ``` + """ + +def is_required(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Required`][typing.Required] [special form][]. + + ```pycon + >>> is_required(Required) + True + ``` + """ + +def is_self(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Self`][typing.Self] [special form][]. + + ```pycon + >>> is_self(Self) + True + ``` + """ + +def is_typealias(obj: Any, /) -> bool: + """ + Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][]. + + ```pycon + >>> is_typealias(TypeAlias) + True + ``` + """ + +def is_typeguard(obj: Any, /) -> bool: + """ + Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][]. + + ```pycon + >>> is_typeguard(TypeGuard) + True + ``` + """ + +def is_typeis(obj: Any, /) -> bool: + """ + Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][]. + + ```pycon + >>> is_typeis(TypeIs) + True + ``` + """ + +def is_typealiastype(obj: Any, /) -> TypeIs[TypeAliasType]: + """ + Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance. + + ```pycon + >>> type MyInt = int + >>> is_typealiastype(MyInt) + True + >>> MyStr = TypeAliasType("MyStr", str) + >>> is_typealiastype(MyStr): + True + >>> type MyList[T] = list[T] + >>> is_typealiastype(MyList[int]) + False + ``` + """ + +def is_unpack(obj: Any, /) -> bool: + """ + Return whether the argument is the [`Unpack`][typing.Unpack] [special form][]. + + ```pycon + >>> is_unpack(Unpack) + True + >>> is_unpack(Unpack[Ts]) + False + ``` + """ + +def is_deprecated(obj: Any, /) -> TypeIs[deprecated]: + """ + Return whether the argument is a [`deprecated`][warnings.deprecated] instance. + + This also includes the [`typing_extensions` backport][typing_extensions.deprecated]. + + ```pycon + >>> is_deprecated(warnings.deprecated('message')) + True + >>> is_deprecated(typing_extensions.deprecated('deprecated')) + True + ``` + """ + +DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] +"""A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" + +DEPRECATED_ALIASES_IDS: Final[dict[int, type[Any]]] +"""A mapping between the [identity][id] of the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA new file mode 100644 index 0000000..4985a6e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA @@ -0,0 +1,189 @@ +Metadata-Version: 2.5 +Name: uvicorn +Version: 0.53.0 +Summary: The lightning-fast ASGI server. +Project-URL: Changelog, https://uvicorn.dev/release-notes +Project-URL: Funding, https://github.com/sponsors/Kludex +Project-URL: Homepage, https://uvicorn.dev/ +Project-URL: Source, https://github.com/Kludex/uvicorn +Author-email: Tom Christie +Maintainer-email: Marcelo Trylesinski +License-Expression: BSD-3-Clause +License-File: LICENSE.md +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.10 +Requires-Dist: click>=7.0 +Requires-Dist: h11>=0.8 +Requires-Dist: typing-extensions>=4.0; python_version < '3.11' +Provides-Extra: standard +Requires-Dist: httptools>=0.8.0; extra == 'standard' +Requires-Dist: python-dotenv>=0.13; extra == 'standard' +Requires-Dist: pyyaml>=5.1; extra == 'standard' +Requires-Dist: uvloop>=0.15.1; (sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')) and extra == 'standard' +Requires-Dist: watchfiles>=0.20; extra == 'standard' +Requires-Dist: websockets>=13.0; extra == 'standard' +Description-Content-Type: text/markdown + +

+ uvicorn +

+ +

+An ASGI web server, for Python. +

+ +--- + +[![Build Status](https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/uvicorn/actions) +[![Package version](https://badge.fury.io/py/uvicorn.svg)](https://pypi.python.org/pypi/uvicorn) +[![Supported Python Version](https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058)](https://pypi.org/project/uvicorn) +[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs) + +--- + +**Documentation**: [https://uvicorn.dev](https://uvicorn.dev) + +**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn) + +--- + +Uvicorn is an ASGI web server implementation for Python. + +Until recently Python has lacked a minimal low-level server/application interface for +async frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to +start building a common set of tooling usable across all async frameworks. + +Uvicorn currently supports HTTP/1.1, HTTP/2 and WebSockets. + +## Quickstart + +Install using `pip`: + +```shell +$ pip install uvicorn +``` + +This will install uvicorn with minimal (pure Python) dependencies. + +```shell +$ pip install 'uvicorn[standard]' +``` + +This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras". + +In this context, "Cython-based" means the following: + +- the event loop `uvloop` will be installed and used if possible. +- the http protocol will be handled by `httptools` if possible. + +Moreover, "optional extras" means that: + +- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible. +- the `--reload` flag in development mode will use `watchfiles`. +- `python-dotenv` will be installed should you want to use the `--env-file` option. +- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired. + +Create an application, in `example.py`: + +```python +async def app(scope, receive, send): + assert scope['type'] == 'http' + + await send({ + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-type', b'text/plain'), + ], + }) + await send({ + 'type': 'http.response.body', + 'body': b'Hello, world!', + }) +``` + +Run the server: + +```shell +$ uvicorn example:app +``` + +--- + +## Why ASGI? + +Most well established Python Web frameworks started out as WSGI-based frameworks. + +WSGI applications are a single, synchronous callable that takes a request and returns a response. +This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections, +which WSGI doesn't support well. + +Having an async concurrency model also allows for options such as lightweight background tasks, +and can be less of a limiting factor for endpoints that have long periods being blocked on network +I/O such as dealing with slow HTTP requests. + +--- + +## Alternative ASGI servers + +A strength of the ASGI protocol is that it decouples the server implementation +from the application framework. This allows for an ecosystem of interoperating +webservers and application frameworks. + +### Daphne + +The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne]. + +It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets. + +Any of the example applications given here can equally well be run using `daphne` instead. + +``` +$ pip install daphne +$ daphne app:App +``` + +### Hypercorn + +[Hypercorn][hypercorn] was initially part of the Quart web framework, before +being separated out into a standalone ASGI server. + +Hypercorn supports HTTP/1.1, HTTP/2, and WebSockets. + +It also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`. + +``` +$ pip install hypercorn +$ hypercorn app:App +``` + +### Mangum + +[Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway. + +### Granian + +[Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets. + +--- + +

Uvicorn is BSD licensed code.
Designed & crafted with care.

— 🦄 —

+ +[asgi]: https://asgi.readthedocs.io/en/latest/ +[daphne]: https://github.com/django/daphne +[hypercorn]: https://github.com/pgjones/hypercorn +[trio]: https://trio.readthedocs.io +[mangum]: https://github.com/jordaneremieff/mangum +[granian]: https://github.com/emmett-framework/granian diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD new file mode 100644 index 0000000..7cf1539 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD @@ -0,0 +1,51 @@ +uvicorn/__init__.py,sha256=YzDsuEOzmpGq7bIZ89KtaKf-YjzknovmzaIlek9fkDs,147 +uvicorn/__main__.py,sha256=DQizy6nKP0ywhPpnCHgmRDYIMfcqZKVEzNIWQZjqtVQ,62 +uvicorn/_ansi.py,sha256=s8RPpmPTO6QGRgEYbsNGeasSWqiEnCsTroyA8Z59Q00,617 +uvicorn/_compat.py,sha256=6X49c9ovzmMHir2S3syinOQMzSPBcp8MNuI9ZQrbYiU,2916 +uvicorn/_subprocess.py,sha256=fQ4JTajuXay6mVc8KQNGfXu6JHoab46aS6kyIMWJMPk,2776 +uvicorn/_types.py,sha256=JDM6E-hqZ6o_cSZvjDULjAv77PFH2pLe_Vgqshe8qH4,7677 +uvicorn/config.py,sha256=z5HG_64oOisgINqVS0zE7UfTJGywYq_Qn7d9mOTFyoc,25482 +uvicorn/importer.py,sha256=nRt0QQ3qpi264-n_mR0l55C2ddM8nowTNzT1jsWaam8,1128 +uvicorn/logging.py,sha256=kCB1n45B5O55SSApiv2x6UQG3LnFqNlGvcohGyAwblU,4256 +uvicorn/main.py,sha256=7Lg1RLSBJtcA6APR1VjvFYNGlhqC0Pxf1f532L76hd8,19049 +uvicorn/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +uvicorn/server.py,sha256=wb6dEzRnpCq4jXFo5AjvWpAldmGpAYmUJiFKPR_C0CY,13705 +uvicorn/workers.py,sha256=lMCubn1Wi-4h2jxrXNMmhOJZy7ZP8QxbZbP3_2O1iHE,3873 +uvicorn/lifespan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/lifespan/off.py,sha256=nfI6qHAUo_8-BEXMBKoHQ9wUbsXrPaXLCbDSS0vKSr8,332 +uvicorn/lifespan/on.py,sha256=iETPFj6SFhPDPcdeGxfa43LKImJU7MI5i7QUUepBcPE,5173 +uvicorn/loops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/loops/asyncio.py,sha256=a8wO4fB4pCk-RLICq7CnGr_cxQI7MBLJj-Go4UQldD8,333 +uvicorn/loops/auto.py,sha256=pdTsMcqkbE8fvdNgLs1y4c6hsuN598mZOSOlC0a23S4,566 +uvicorn/loops/uvloop.py,sha256=Ca-TL-W8tbdEW5uvL_GrMih7G8e_t_vhNevi2NKYjEU,236 +uvicorn/loops/zuvloop.py,sha256=6ei_QjRoIYqMXKB4UnZSkO6k6a8PqcjWV5mvzYkR160,239 +uvicorn/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/middleware/asgi2.py,sha256=YQrQNm3RehFts3mzk3k4yw8aD8Egtj0tRS3N45YkQa0,394 +uvicorn/middleware/message_logger.py,sha256=IHEZUSnFNaMFUFdwtZO3AuFATnYcSor-gVtOjbCzt8M,2859 +uvicorn/middleware/proxy_headers.py,sha256=pNZk80NNscLEwi8pGn6cIbaF0bSfJhCKn3pHjqqfW90,7421 +uvicorn/middleware/wsgi.py,sha256=VnbnkdCL3_ctLWzk1j39xvxysuoAB3kSYmi8TsOLpNs,7125 +uvicorn/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/utils.py,sha256=1hKdDCmo_GtGrK1zZfa70jL7Hr3mDE8RnL69O4rHQRg,2058 +uvicorn/protocols/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/http/auto.py,sha256=YfXGyzWTaaE2p_jkTPWrJCXsxEaQnC3NK0-G7Wgmnls,403 +uvicorn/protocols/http/auto_zttp_impl.py,sha256=cvqOUvV38tc5gD4tpNhiL7IeIKyIoCyCCGe9RtnQtKk,3272 +uvicorn/protocols/http/flow_control.py,sha256=050WVg31EvPOkHwynCoMP1zXFl_vO3U4durlc5vyp4U,1701 +uvicorn/protocols/http/h11_impl.py,sha256=oxVKbCz6quvhmseqJV3-m2t7SQ3h2pUnTAM6oJPowyI,20886 +uvicorn/protocols/http/httptools_impl.py,sha256=HU4cV530FoQzphoB8QxiizHBeQ5y3Zdp0rhjOyIhQDM,22564 +uvicorn/protocols/http/zttp_h2_impl.py,sha256=HdW4vaZC2uyMKbsobhzeLGgcR_9IhpFCWba6WeqaU6Q,19950 +uvicorn/protocols/http/zttp_impl.py,sha256=5jCnrVuza4pozajsiOhYVwQLXPKgCiwUVn14p0-z2YU,21835 +uvicorn/protocols/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/websockets/auto.py,sha256=QOnMKZfXHfOcZHdq1OITHwv7Awx1BTS3eJfZswuGEU0,611 +uvicorn/protocols/websockets/websockets_impl.py,sha256=ZpRwX6nSkMrojCaAgqAZjeAoMxZAoJW9bfUvvyOLsUE,15784 +uvicorn/protocols/websockets/websockets_sansio_impl.py,sha256=izKpehYIeIu6UlyLHg0-sKk80J0PT0G5uDzr1SczbEk,23861 +uvicorn/protocols/websockets/wsproto_impl.py,sha256=uzCxXnvNv0akCIWNY1q2DnuONcdmrYYDmgORsxeTc8M,21138 +uvicorn/supervisors/__init__.py,sha256=wT8eOEIqT1yWQgytZtv5taWMul7xoTIY0xm1m4oyPTw,507 +uvicorn/supervisors/basereload.py,sha256=C-YD18flOKKVRNTRsSCOEvyqGf3ZxpvuUvR0z0Y1Wpg,4018 +uvicorn/supervisors/multiprocess.py,sha256=U1QZdiJ_7Auazix6XOj1ku0Nj_N_PJPCoYuq6SNjZcI,10271 +uvicorn/supervisors/statreload.py,sha256=XPaEtVXaasII1kkG32UUG8oD7-fS0FwyXkQLF9QYPbA,1548 +uvicorn/supervisors/watchfilesreload.py,sha256=u-MuYm0LsZMN670u_TtSWiMBUA2z4j1sIe3O2A67Ep4,2923 +uvicorn-0.53.0.dist-info/METADATA,sha256=vPs7Yx0x4_98aZJCiwSAoJ1AfsgpkFqpMB9za9hUy0U,6607 +uvicorn-0.53.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87 +uvicorn-0.53.0.dist-info/entry_points.txt,sha256=FW1w-hkc9QgwaGoovMvm0ZY73w_NcycWdGAUfDsNGxw,46 +uvicorn-0.53.0.dist-info/licenses/LICENSE.md,sha256=7-Gs8-YvuZwoiw7HPlp3O3Jo70Mg_nV-qZQhTktjw3E,1526 +uvicorn-0.53.0.dist-info/RECORD,, diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL new file mode 100644 index 0000000..51abe1f --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.32.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt new file mode 100644 index 0000000..4b00fcb --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +uvicorn = uvicorn.main:main diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..a6bba14 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py new file mode 100644 index 0000000..f27ddd2 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py @@ -0,0 +1,5 @@ +from uvicorn.config import Config +from uvicorn.main import Server, main, run + +__version__ = "0.53.0" +__all__ = ["main", "run", "Config", "Server"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py new file mode 100644 index 0000000..8a1dc97 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py @@ -0,0 +1,4 @@ +import uvicorn + +if __name__ == "__main__": + uvicorn.main() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py new file mode 100644 index 0000000..8f60f40 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +_ANSI_COLORS = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} + + +def style(text: str, fg: str | None = None, bold: bool = False) -> str: + prefix = "" + if fg is not None: + prefix += f"\033[{_ANSI_COLORS[fg]}m" + if bold: + prefix += "\033[1m" + return f"{prefix}{text}\033[0m" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py new file mode 100644 index 0000000..118a19a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import asyncio +import sys +from collections.abc import Callable, Coroutine +from typing import Any, TypeVar + +__all__ = ["asyncio_run", "iscoroutinefunction"] + +if sys.version_info >= (3, 14): + from inspect import iscoroutinefunction +else: + from asyncio import iscoroutinefunction + +_T = TypeVar("_T") + +if sys.version_info >= (3, 12): + asyncio_run = asyncio.run +elif sys.version_info >= (3, 11): + + def asyncio_run( + main: Coroutine[Any, Any, _T], + *, + debug: bool = False, + loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None, + ) -> _T: + # asyncio.run from Python 3.12 + # https://docs.python.org/3/license.html#psf-license + with asyncio.Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(main) + +else: + # modified version of asyncio.run from Python 3.10 to add loop_factory kwarg + # https://docs.python.org/3/license.html#psf-license + def asyncio_run( + main: Coroutine[Any, Any, _T], + *, + debug: bool = False, + loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None, + ) -> _T: + try: + asyncio.get_running_loop() + except RuntimeError: + pass + else: + raise RuntimeError("asyncio.run() cannot be called from a running event loop") + + if not asyncio.iscoroutine(main): + raise ValueError(f"a coroutine was expected, got {main!r}") + + if loop_factory is None: + loop = asyncio.new_event_loop() + else: + loop = loop_factory() + try: + if loop_factory is None: + asyncio.set_event_loop(loop) + if debug is not None: + loop.set_debug(debug) + return loop.run_until_complete(main) + finally: + try: + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.run_until_complete(loop.shutdown_default_executor()) + finally: + if loop_factory is None: + asyncio.set_event_loop(None) + loop.close() + + def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None: + to_cancel = asyncio.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py new file mode 100644 index 0000000..c084ec9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py @@ -0,0 +1,84 @@ +""" +Some light wrappers around Python's multiprocessing, to deal with cleanly +starting child processes. +""" + +from __future__ import annotations + +import multiprocessing +import os +import sys +from collections.abc import Callable +from multiprocessing.context import SpawnProcess +from socket import socket + +from uvicorn.config import Config + +multiprocessing.allow_connection_pickling() +spawn = multiprocessing.get_context("spawn") + + +def get_subprocess( + config: Config, + target: Callable[..., None], + sockets: list[socket], +) -> SpawnProcess: + """ + Called in the parent process, to instantiate a new child process instance. + The child is not yet started at this point. + + * config - The Uvicorn configuration instance. + * target - A callable that accepts a list of sockets. In practice this will + be the `Server.run()` method. + * sockets - A list of sockets to pass to the server. Sockets are bound once + by the parent process, and then passed to the child processes. + """ + # We pass across the stdin fileno, and reopen it in the child process. + # This is required for some debugging environments. + try: + stdin_fileno = sys.stdin.fileno() + # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__. + except (AttributeError, OSError): + stdin_fileno = None + + kwargs = { + "config": config, + "target": target, + "sockets": sockets, + "stdin_fileno": stdin_fileno, + } + + return spawn.Process(target=subprocess_started, kwargs=kwargs) + + +def subprocess_started( + config: Config, + target: Callable[..., None], + sockets: list[socket], + stdin_fileno: int | None, +) -> None: + """ + Called when the child process starts. + + * config - The Uvicorn configuration instance. + * target - A callable that accepts a list of sockets. In practice this will + be the `Server.run()` method. + * sockets - A list of sockets to pass to the server. Sockets are bound once + by the parent process, and then passed to the child processes. + * stdin_fileno - The file number of sys.stdin, so that it can be reattached + to the child process. + """ + # Re-open stdin. + if stdin_fileno is not None: + sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage + + # Logging needs to be setup again for each child. + config.configure_logging() + + try: + # Now we can call into `Server.run(sockets=sockets)` + target(sockets=sockets) + except KeyboardInterrupt: # pragma: no cover + # suppress the exception to avoid a traceback from subprocess.Popen + # the parent already expects us to end, so no vital information is lost + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py new file mode 100644 index 0000000..822445a --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py @@ -0,0 +1,274 @@ +""" +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from __future__ import annotations + +import sys +import types +from collections.abc import Awaitable, Callable, Iterable, MutableMapping +from typing import Any, Literal, Protocol, TypedDict + +if sys.version_info >= (3, 11): # pragma: py-lt-311 + from typing import NotRequired +else: # pragma: py-gte-311 + from typing_extensions import NotRequired + +# WSGI +Environ = MutableMapping[str, Any] +ExcInfo = tuple[type[BaseException], BaseException, types.TracebackType | None] +StartResponse = Callable[[str, Iterable[tuple[str, str]], ExcInfo | None], None] +WSGIApp = Callable[[Environ, StartResponse], Iterable[bytes] | BaseException] + + +# ASGI +class ASGIVersions(TypedDict): + spec_version: str + version: Literal["2.0"] | Literal["3.0"] + + +class HTTPScope(TypedDict): + type: Literal["http"] + asgi: ASGIVersions + http_version: str + method: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[tuple[bytes, bytes]] + client: tuple[str, int] | None + server: tuple[str, int | None] | None + state: NotRequired[dict[str, Any]] + extensions: NotRequired[dict[str, dict[object, object]]] + + +class WebSocketScope(TypedDict): + type: Literal["websocket"] + asgi: ASGIVersions + http_version: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[tuple[bytes, bytes]] + client: tuple[str, int] | None + server: tuple[str, int | None] | None + subprotocols: Iterable[str] + state: NotRequired[dict[str, Any]] + extensions: NotRequired[dict[str, dict[object, object]]] + + +class LifespanScope(TypedDict): + type: Literal["lifespan"] + asgi: ASGIVersions + state: NotRequired[dict[str, Any]] + + +WWWScope = HTTPScope | WebSocketScope +Scope = HTTPScope | WebSocketScope | LifespanScope + + +class HTTPRequestEvent(TypedDict): + type: Literal["http.request"] + body: bytes + more_body: bool + + +class HTTPResponseDebugEvent(TypedDict): + type: Literal["http.response.debug"] + info: dict[str, object] + + +class HTTPResponseStartEvent(TypedDict): + type: Literal["http.response.start"] + status: int + headers: NotRequired[Iterable[tuple[bytes, bytes]]] + trailers: NotRequired[bool] + + +class HTTPResponseBodyEvent(TypedDict): + type: Literal["http.response.body"] + body: bytes + more_body: NotRequired[bool] + + +class HTTPResponseTrailersEvent(TypedDict): + type: Literal["http.response.trailers"] + headers: Iterable[tuple[bytes, bytes]] + more_trailers: bool + + +class HTTPServerPushEvent(TypedDict): + type: Literal["http.response.push"] + path: str + headers: Iterable[tuple[bytes, bytes]] + + +class HTTPDisconnectEvent(TypedDict): + type: Literal["http.disconnect"] + + +class WebSocketConnectEvent(TypedDict): + type: Literal["websocket.connect"] + + +class WebSocketAcceptEvent(TypedDict): + type: Literal["websocket.accept"] + subprotocol: NotRequired[str | None] + headers: NotRequired[Iterable[tuple[bytes, bytes]]] + + +class _WebSocketReceiveEventBytes(TypedDict): + type: Literal["websocket.receive"] + bytes: bytes + text: NotRequired[None] + + +class _WebSocketReceiveEventText(TypedDict): + type: Literal["websocket.receive"] + bytes: NotRequired[None] + text: str + + +WebSocketReceiveEvent = _WebSocketReceiveEventBytes | _WebSocketReceiveEventText + + +class _WebSocketSendEventBytes(TypedDict): + type: Literal["websocket.send"] + bytes: bytes + text: NotRequired[None] + + +class _WebSocketSendEventText(TypedDict): + type: Literal["websocket.send"] + bytes: NotRequired[None] + text: str + + +WebSocketSendEvent = _WebSocketSendEventBytes | _WebSocketSendEventText + + +class WebSocketResponseStartEvent(TypedDict): + type: Literal["websocket.http.response.start"] + status: int + headers: Iterable[tuple[bytes, bytes]] + + +class WebSocketResponseBodyEvent(TypedDict): + type: Literal["websocket.http.response.body"] + body: bytes + more_body: NotRequired[bool] + + +class WebSocketDisconnectEvent(TypedDict): + type: Literal["websocket.disconnect"] + code: int + reason: NotRequired[str | None] + + +class WebSocketCloseEvent(TypedDict): + type: Literal["websocket.close"] + code: NotRequired[int] + reason: NotRequired[str | None] + + +class LifespanStartupEvent(TypedDict): + type: Literal["lifespan.startup"] + + +class LifespanShutdownEvent(TypedDict): + type: Literal["lifespan.shutdown"] + + +class LifespanStartupCompleteEvent(TypedDict): + type: Literal["lifespan.startup.complete"] + + +class LifespanStartupFailedEvent(TypedDict): + type: Literal["lifespan.startup.failed"] + message: str + + +class LifespanShutdownCompleteEvent(TypedDict): + type: Literal["lifespan.shutdown.complete"] + + +class LifespanShutdownFailedEvent(TypedDict): + type: Literal["lifespan.shutdown.failed"] + message: str + + +WebSocketEvent = WebSocketReceiveEvent | WebSocketDisconnectEvent | WebSocketConnectEvent + + +ASGIReceiveEvent = ( + HTTPRequestEvent + | HTTPDisconnectEvent + | WebSocketConnectEvent + | WebSocketReceiveEvent + | WebSocketDisconnectEvent + | LifespanStartupEvent + | LifespanShutdownEvent +) + + +ASGISendEvent = ( + HTTPResponseStartEvent + | HTTPResponseBodyEvent + | HTTPResponseTrailersEvent + | HTTPServerPushEvent + | HTTPDisconnectEvent + | WebSocketAcceptEvent + | WebSocketSendEvent + | WebSocketResponseStartEvent + | WebSocketResponseBodyEvent + | WebSocketCloseEvent + | LifespanStartupCompleteEvent + | LifespanStartupFailedEvent + | LifespanShutdownCompleteEvent + | LifespanShutdownFailedEvent +) + + +ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]] +ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]] + + +class ASGI2Protocol(Protocol): + def __init__(self, scope: Scope) -> None: ... # pragma: no cover + + async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover + + +ASGI2Application = type[ASGI2Protocol] +ASGI3Application = Callable[[Scope, ASGIReceiveCallable, ASGISendCallable], Awaitable[None]] +ASGIApplication = ASGI2Application | ASGI3Application diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py new file mode 100644 index 0000000..37b3c0d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/config.py @@ -0,0 +1,618 @@ +from __future__ import annotations + +import asyncio +import inspect +import json +import logging +import logging.config +import os +import socket +import ssl +import sys +from collections.abc import Awaitable, Callable +from configparser import RawConfigParser +from pathlib import Path +from typing import IO, Any, Literal + +from uvicorn._ansi import style +from uvicorn._compat import iscoroutinefunction +from uvicorn._types import ASGIApplication +from uvicorn.importer import ImportFromStringError, import_from_string +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.middleware.asgi2 import ASGI2Middleware +from uvicorn.middleware.message_logger import MessageLoggerMiddleware +from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware +from uvicorn.middleware.wsgi import WSGIMiddleware + + +class UvicornDeprecationWarning(UserWarning): + """A custom deprecation warning for Uvicorn. + + Unlike the built-in DeprecationWarning, this inherits from UserWarning to ensure it is visible by default, helping + users discover deprecated features without needing to enable warnings explicitly. + + Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries + """ + + +HTTPProtocolType = Literal["auto", "h11", "httptools", "zttp"] +WSProtocolType = Literal["auto", "none", "websockets", "websockets-sansio", "wsproto"] +LifespanType = Literal["auto", "on", "off"] +LoopFactoryType = Literal["none", "auto", "asyncio", "uvloop", "zuvloop"] +InterfaceType = Literal["auto", "asgi3", "asgi2", "wsgi"] + +LOG_LEVELS: dict[str, int] = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG, + "trace": TRACE_LOG_LEVEL, +} +HTTP_PROTOCOLS: dict[str, str] = { + "auto": "uvicorn.protocols.http.auto:AutoHTTPProtocol", + "h11": "uvicorn.protocols.http.h11_impl:H11Protocol", + "httptools": "uvicorn.protocols.http.httptools_impl:HttpToolsProtocol", + "zttp": "uvicorn.protocols.http.zttp_impl:ZttpProtocol", +} +WS_PROTOCOLS: dict[str, str | None] = { + "auto": "uvicorn.protocols.websockets.auto:AutoWebSocketsProtocol", + "none": None, + "websockets": "uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol", + "websockets-sansio": "uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol", + "wsproto": "uvicorn.protocols.websockets.wsproto_impl:WSProtocol", +} +LIFESPAN: dict[str, str] = { + "auto": "uvicorn.lifespan.on:LifespanOn", + "on": "uvicorn.lifespan.on:LifespanOn", + "off": "uvicorn.lifespan.off:LifespanOff", +} +LOOP_FACTORIES: dict[str, str | None] = { + "none": None, + "auto": "uvicorn.loops.auto:auto_loop_factory", + "asyncio": "uvicorn.loops.asyncio:asyncio_loop_factory", + "uvloop": "uvicorn.loops.uvloop:uvloop_loop_factory", + "zuvloop": "uvicorn.loops.zuvloop:zuvloop_loop_factory", +} +INTERFACES: list[InterfaceType] = ["auto", "asgi3", "asgi2", "wsgi"] + +SSL_PROTOCOL_VERSION: int = ssl.PROTOCOL_TLS_SERVER + +STARTUP_FAILURE = 3 + +LOGGING_CONFIG: dict[str, Any] = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "%(levelprefix)s %(message)s", + "use_colors": None, + }, + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 + }, + }, + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + }, + "loggers": { + "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "uvicorn.error": {"level": "INFO"}, + "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, + }, +} + +logger = logging.getLogger("uvicorn.error") + + +def create_ssl_context( + certfile: str | os.PathLike[str], + keyfile: str | os.PathLike[str] | None, + password: str | None, + ssl_version: int, + cert_reqs: int, + ca_certs: str | os.PathLike[str] | None, + ciphers: str | None, + alpn_protocols: list[str] | None = None, +) -> ssl.SSLContext: + ctx = ssl.SSLContext(ssl_version) + get_password = (lambda: password) if password else None + ctx.load_cert_chain(certfile, keyfile, get_password) + ctx.verify_mode = ssl.VerifyMode(cert_reqs) + if ca_certs: + ctx.load_verify_locations(ca_certs) + if ciphers: + ctx.set_ciphers(ciphers) + if alpn_protocols: # pragma: no-zttp-h2 + ctx.set_alpn_protocols(alpn_protocols) + return ctx + + +def is_dir(path: Path) -> bool: + try: + if not path.is_absolute(): + path = path.resolve() + return path.is_dir() + except OSError: # pragma: full coverage + return False + + +def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]: + directories: list[Path] = list(set(map(Path, directories_list.copy()))) + patterns: list[str] = patterns_list.copy() + + current_working_directory = Path.cwd() + for pattern in patterns_list: + # Special case for the .* pattern, otherwise this would only match + # hidden directories which is probably undesired + if pattern == ".*": + continue # pragma: py-not-linux + patterns.append(pattern) + if is_dir(Path(pattern)): + directories.append(Path(pattern)) + else: + for match in current_working_directory.glob(pattern): + if is_dir(match): + directories.append(match) + + directories = list(set(directories)) + directories = list(map(Path, directories)) + directories = list(map(lambda x: x.resolve(), directories)) + directories = list({reload_path for reload_path in directories if is_dir(reload_path)}) + + children = [] + for j in range(len(directories)): + for k in range(j + 1, len(directories)): # pragma: full coverage + if directories[j] in directories[k].parents: + children.append(directories[k]) + elif directories[k] in directories[j].parents: + children.append(directories[j]) + + directories = list(set(directories).difference(set(children))) + + return list(set(patterns)), directories + + +def _normalize_dirs(dirs: list[str] | str | None) -> list[str]: + if dirs is None: + return [] + if isinstance(dirs, str): + return [dirs] + return list(set(dirs)) + + +class Config: + def __init__( + self, + app: ASGIApplication | Callable[..., Any] | str, + host: str = "127.0.0.1", + port: int = 8000, + uds: str | None = None, + fd: int | None = None, + loop: LoopFactoryType | str = "auto", + http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto", + http2: bool = False, + ws: type[asyncio.Protocol] | WSProtocolType | str = "auto", + ws_max_size: int = 16 * 1024 * 1024, + ws_max_queue: int = 32, + ws_ping_interval: float | None = 20.0, + ws_ping_timeout: float | None = 20.0, + ws_per_message_deflate: bool = True, + lifespan: LifespanType = "auto", + env_file: str | os.PathLike[str] | None = None, + log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, + log_level: str | int | None = None, + access_log: bool = True, + use_colors: bool | None = None, + interface: InterfaceType = "auto", + reload: bool = False, + reload_dirs: list[str] | str | None = None, + reload_delay: float = 0.25, + reload_includes: list[str] | str | None = None, + reload_excludes: list[str] | str | None = None, + workers: int | None = None, + proxy_headers: bool = True, + server_header: bool = True, + date_header: bool = True, + forwarded_allow_ips: list[str] | str | None = None, + root_path: str = "", + limit_concurrency: int | None = None, + limit_max_requests: int | None = None, + limit_max_requests_jitter: int = 0, + backlog: int = 2048, + timeout_keep_alive: int = 5, + timeout_notify: int = 30, + timeout_graceful_shutdown: int | None = None, + timeout_worker_healthcheck: int = 5, + callback_notify: Callable[..., Awaitable[None]] | None = None, + ssl_keyfile: str | os.PathLike[str] | None = None, + ssl_certfile: str | os.PathLike[str] | None = None, + ssl_keyfile_password: str | None = None, + ssl_version: int = SSL_PROTOCOL_VERSION, + ssl_cert_reqs: int = ssl.CERT_NONE, + ssl_ca_certs: str | os.PathLike[str] | None = None, + ssl_ciphers: str | None = None, + ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None, + headers: list[tuple[str, str]] | None = None, + factory: bool = False, + h11_max_incomplete_event_size: int | None = None, + reset_contextvars: bool = False, + ): + self.app = app + self.host = host + self.port = port + self.uds = uds + self.fd = fd + self.loop = loop + self.http = http + self.http2 = http2 + self.ws = ws + self.ws_max_size = ws_max_size + self.ws_max_queue = ws_max_queue + self.ws_ping_interval = ws_ping_interval + self.ws_ping_timeout = ws_ping_timeout + self.ws_per_message_deflate = ws_per_message_deflate + self.lifespan = lifespan + self.log_config = log_config + self.log_level = log_level + self.access_log = access_log + self.use_colors = use_colors + self.interface = interface + self.reload = reload + self.reload_delay = reload_delay + self.workers = workers or 1 + self.proxy_headers = proxy_headers + self.server_header = server_header + self.date_header = date_header + self.root_path = root_path + self.limit_concurrency = limit_concurrency + self.limit_max_requests = limit_max_requests + self.limit_max_requests_jitter = limit_max_requests_jitter + self.backlog = backlog + self.timeout_keep_alive = timeout_keep_alive + self.timeout_notify = timeout_notify + self.timeout_graceful_shutdown = timeout_graceful_shutdown + self.timeout_worker_healthcheck = timeout_worker_healthcheck + self.callback_notify = callback_notify + self.ssl_keyfile = ssl_keyfile + self.ssl_certfile = ssl_certfile + self.ssl_keyfile_password = ssl_keyfile_password + self.ssl_version = ssl_version + self.ssl_cert_reqs = ssl_cert_reqs + self.ssl_ca_certs = ssl_ca_certs + self.ssl_ciphers = ssl_ciphers + self.ssl_context_factory = ssl_context_factory + self.headers: list[tuple[str, str]] = headers or [] + self.encoded_headers: list[tuple[bytes, bytes]] = [] + self.factory = factory + self.h11_max_incomplete_event_size = h11_max_incomplete_event_size + self.reset_contextvars = reset_contextvars + + self.loaded = False + self.configure_logging() + + self.reload_dirs: list[Path] = [] + self.reload_dirs_excludes: list[Path] = [] + self.reload_includes: list[str] = [] + self.reload_excludes: list[str] = [] + + if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload: + logger.warning( + "Current configuration will not reload as not all conditions are met, please refer to documentation." + ) + + if self.should_reload: + reload_dirs = _normalize_dirs(reload_dirs) + reload_includes = _normalize_dirs(reload_includes) + reload_excludes = _normalize_dirs(reload_excludes) + + self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs) + + self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, []) + + reload_dirs_tmp = self.reload_dirs.copy() + + for directory in self.reload_dirs_excludes: + for reload_directory in reload_dirs_tmp: + if directory == reload_directory or directory in reload_directory.parents: + try: + self.reload_dirs.remove(reload_directory) + except ValueError: # pragma: full coverage + pass + + for pattern in self.reload_excludes: + if pattern in self.reload_includes: + self.reload_includes.remove(pattern) # pragma: full coverage + + if not self.reload_dirs: + if reload_dirs: + logger.warning( + "Provided reload directories %s did not contain valid " + + "directories, watching current working directory.", + reload_dirs, + ) + self.reload_dirs = [Path.cwd()] + + logger.info( + "Will watch for changes in these directories: %s", + sorted(list(map(str, self.reload_dirs))), + ) + + if env_file is not None: + from dotenv import load_dotenv + + logger.info("Loading environment from '%s'", env_file) + load_dotenv(dotenv_path=env_file) + + if workers is None and "WEB_CONCURRENCY" in os.environ: + self.workers = int(os.environ["WEB_CONCURRENCY"]) + + self.forwarded_allow_ips: list[str] | str + if forwarded_allow_ips is None: + self.forwarded_allow_ips = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1,::1") + else: + self.forwarded_allow_ips = forwarded_allow_ips # pragma: full coverage + + if self.reload and self.workers > 1: + logger.warning('"workers" flag is ignored when reloading is enabled.') + + @property + def asgi_version(self) -> Literal["2.0", "3.0"]: + mapping: dict[str, Literal["2.0", "3.0"]] = { + "asgi2": "2.0", + "asgi3": "3.0", + "wsgi": "3.0", + } + return mapping[self.interface] + + @property + def is_ssl(self) -> bool: + return bool(self.ssl_keyfile or self.ssl_certfile or self.ssl_context_factory) + + @property + def use_subprocess(self) -> bool: + return bool(self.reload or self.workers > 1) + + def configure_logging(self) -> None: + logging.addLevelName(TRACE_LOG_LEVEL, "TRACE") + + if self.log_config is not None: + if isinstance(self.log_config, os.PathLike): + self.log_config = os.fspath(self.log_config) + + if isinstance(self.log_config, dict): + if self.use_colors in (True, False): + self.log_config["formatters"]["default"]["use_colors"] = self.use_colors + self.log_config["formatters"]["access"]["use_colors"] = self.use_colors + logging.config.dictConfig(self.log_config) + elif isinstance(self.log_config, str) and self.log_config.endswith(".json"): + with open(self.log_config) as file: + loaded_config = json.load(file) + logging.config.dictConfig(loaded_config) + elif isinstance(self.log_config, str) and self.log_config.endswith((".yaml", ".yml")): + try: + import yaml + except ImportError as e: + raise ImportError( + "Install the PyYAML package or uvicorn[standard] to use `--log-config` with YAML files." + ) from e + + with open(self.log_config) as file: + loaded_config = yaml.safe_load(file) + logging.config.dictConfig(loaded_config) + else: + # See the note about fileConfig() here: + # https://docs.python.org/3/library/logging.config.html#configuration-file-format + logging.config.fileConfig(self.log_config, disable_existing_loggers=False) + + if self.log_level is not None: + if isinstance(self.log_level, str): + log_level = LOG_LEVELS[self.log_level.lower()] + else: + log_level = self.log_level + logging.getLogger("uvicorn.error").setLevel(log_level) + logging.getLogger("uvicorn.access").setLevel(log_level) + logging.getLogger("uvicorn.asgi").setLevel(log_level) + if self.access_log is False: + logging.getLogger("uvicorn.access").handlers = [] + logging.getLogger("uvicorn.access").propagate = False + + def load_app(self) -> Any: + """Import the app and return it. Exits on failure.""" + try: + return import_from_string(self.app) + except ImportFromStringError as exc: + logger.error("Error loading ASGI app. %s" % exc) + sys.exit(STARTUP_FAILURE) + + def load(self) -> None: + assert not self.loaded + + if self.http2: + if self.http != "zttp": + raise ValueError( + "HTTP/2 requires the `zttp` HTTP protocol. Install it with `pip install zttp`, then select it " + "with `http='zttp'`. See https://uvicorn.dev/concepts/http2/ for more information." + ) + http_protocol_class = import_from_string("uvicorn.protocols.http.auto_zttp_impl:AutoZttpProtocol") + self.http_protocol_class: type[asyncio.Protocol] = http_protocol_class + elif isinstance(self.http, str): + self.http_protocol_class = import_from_string(HTTP_PROTOCOLS.get(self.http, self.http)) + else: + self.http_protocol_class = self.http + + alpn_protocols: list[str] | None = getattr(self.http_protocol_class, "alpn_protocols", None) + + if self.ssl_context_factory is not None: + + def default_factory() -> ssl.SSLContext: + if not self.ssl_certfile: + raise RuntimeError( + "`default_ssl_context_factory()` requires `ssl_certfile` to be set on `Config`. " + "Either pass `ssl_certfile` (and optionally `ssl_keyfile`) or build the `SSLContext` " + "directly inside `ssl_context_factory` without calling the default factory." + ) + return create_ssl_context( + keyfile=self.ssl_keyfile, + certfile=self.ssl_certfile, + password=self.ssl_keyfile_password, + ssl_version=self.ssl_version, + cert_reqs=self.ssl_cert_reqs, + ca_certs=self.ssl_ca_certs, + ciphers=self.ssl_ciphers, + alpn_protocols=alpn_protocols, + ) + + context = self.ssl_context_factory(self, default_factory) + if not isinstance(context, ssl.SSLContext): + raise TypeError(f"`ssl_context_factory` must return an `ssl.SSLContext`, got {type(context).__name__}") + self.ssl: ssl.SSLContext | None = context + elif self.is_ssl: + assert self.ssl_certfile + self.ssl = create_ssl_context( + keyfile=self.ssl_keyfile, + certfile=self.ssl_certfile, + password=self.ssl_keyfile_password, + ssl_version=self.ssl_version, + cert_reqs=self.ssl_cert_reqs, + ca_certs=self.ssl_ca_certs, + ciphers=self.ssl_ciphers, + alpn_protocols=alpn_protocols, + ) + else: + self.ssl = None + + encoded_headers = [(key.lower().encode("latin1"), value.encode("latin1")) for key, value in self.headers] + self.encoded_headers = ( + [(b"server", b"uvicorn")] + encoded_headers + if b"server" not in dict(encoded_headers) and self.server_header + else encoded_headers + ) + + if isinstance(self.ws, str): + ws_protocol_class = import_from_string(WS_PROTOCOLS.get(self.ws, self.ws)) + self.ws_protocol_class: type[asyncio.Protocol] | None = ws_protocol_class + else: + self.ws_protocol_class = self.ws + + self.lifespan_class = import_from_string(LIFESPAN[self.lifespan]) + + self.loaded_app = self.load_app() + + try: + self.loaded_app = self.loaded_app() + except TypeError as exc: + if self.factory: + logger.error("Error loading ASGI app factory: %s", exc) + sys.exit(STARTUP_FAILURE) + else: + if not self.factory: + logger.warning( + "ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly." + ) + + if self.interface == "auto": + if inspect.isclass(self.loaded_app): + use_asgi_3 = hasattr(self.loaded_app, "__await__") + elif inspect.isfunction(self.loaded_app): + use_asgi_3 = iscoroutinefunction(self.loaded_app) + else: + call = getattr(self.loaded_app, "__call__", None) + use_asgi_3 = iscoroutinefunction(call) + self.interface = "asgi3" if use_asgi_3 else "asgi2" + + if self.interface == "wsgi": + self.loaded_app = WSGIMiddleware(self.loaded_app) + self.ws_protocol_class = None + elif self.interface == "asgi2": + self.loaded_app = ASGI2Middleware(self.loaded_app) + + if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL: + self.loaded_app = MessageLoggerMiddleware(self.loaded_app) + if self.proxy_headers: + self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips) + + self.loaded = True + + def setup_event_loop(self) -> None: + raise AttributeError( + "The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\n" + "None of those methods are supposed to be used directly. If you are doing it, please let me know here: " + "https://github.com/Kludex/uvicorn/discussions/2706. Thank you, and sorry for the inconvenience." + ) + + def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] | None: + if self.loop in LOOP_FACTORIES: + loop_factory: Callable[..., Any] | None = import_from_string(LOOP_FACTORIES[self.loop]) + else: + try: + return import_from_string(self.loop) + except ImportFromStringError as exc: + logger.error("Error loading custom loop setup function. %s" % exc) + sys.exit(STARTUP_FAILURE) + if loop_factory is None: + return None + return loop_factory(use_subprocess=self.use_subprocess) + + def bind_socket(self) -> socket.socket: + logger_args: list[str | int] + if self.uds is not None: # pragma: py-win32 + path = self.uds + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.bind(path) + uds_perms = 0o666 + os.chmod(self.uds, uds_perms) + except OSError as exc: # pragma: full coverage + logger.error(exc) + sys.exit(STARTUP_FAILURE) + + message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)" + sock_name_format = "%s" + color_message = "Uvicorn running on " + style(sock_name_format, bold=True) + " (Press CTRL+C to quit)" + logger_args = [self.uds] + elif self.fd is not None: # pragma: py-win32 + sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM) + message = "Uvicorn running on socket %s (Press CTRL+C to quit)" + fd_name_format = "%s" + color_message = "Uvicorn running on " + style(fd_name_format, bold=True) + " (Press CTRL+C to quit)" + logger_args = [sock.getsockname()] + else: + family = socket.AF_INET + addr_format = "%s://%s:%d" + + if self.host and ":" in self.host: # pragma: full coverage + # It's an IPv6 address. + family = socket.AF_INET6 + addr_format = "%s://[%s]:%d" + + sock = socket.socket(family=family) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind((self.host, self.port)) + except OSError as exc: # pragma: full coverage + logger.error(exc) + sys.exit(STARTUP_FAILURE) + + message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)" + color_message = "Uvicorn running on " + style(addr_format, bold=True) + " (Press CTRL+C to quit)" + protocol_name = "https" if self.is_ssl else "http" + logger_args = [protocol_name, self.host, sock.getsockname()[1]] + logger.info(message, *logger_args, extra={"color_message": color_message}) + sock.set_inheritable(True) + return sock + + @property + def should_reload(self) -> bool: + return isinstance(self.app, str) and self.reload diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py new file mode 100644 index 0000000..f77520e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py @@ -0,0 +1,34 @@ +import importlib +from typing import Any + + +class ImportFromStringError(Exception): + pass + + +def import_from_string(import_str: Any) -> Any: + if not isinstance(import_str, str): + return import_str + + module_str, _, attrs_str = import_str.partition(":") + if not module_str or not attrs_str: + message = 'Import string "{import_str}" must be in format ":".' + raise ImportFromStringError(message.format(import_str=import_str)) + + try: + module = importlib.import_module(module_str) + except ModuleNotFoundError as exc: + if exc.name != module_str: + raise exc from None + message = 'Could not import module "{module_str}".' + raise ImportFromStringError(message.format(module_str=module_str)) + + instance = module + try: + for attr_str in attrs_str.split("."): + instance = getattr(instance, attr_str) + except AttributeError: + message = 'Attribute "{attrs_str}" not found in module "{module_str}".' + raise ImportFromStringError(message.format(attrs_str=attrs_str, module_str=module_str)) + + return instance diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py new file mode 100644 index 0000000..74554b1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import Any + +from uvicorn import Config + + +class LifespanOff: + def __init__(self, config: Config) -> None: + self.should_exit = False + self.state: dict[str, Any] = {} + + async def startup(self) -> None: + pass + + async def shutdown(self) -> None: + pass diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py new file mode 100644 index 0000000..71ac394 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import asyncio +import logging +from asyncio import Queue +from typing import Any + +from uvicorn import Config +from uvicorn._types import ( + LifespanScope, + LifespanShutdownCompleteEvent, + LifespanShutdownEvent, + LifespanShutdownFailedEvent, + LifespanStartupCompleteEvent, + LifespanStartupEvent, + LifespanStartupFailedEvent, +) + +LifespanReceiveMessage = LifespanStartupEvent | LifespanShutdownEvent +LifespanSendMessage = ( + LifespanStartupFailedEvent + | LifespanShutdownFailedEvent + | LifespanStartupCompleteEvent + | LifespanShutdownCompleteEvent +) + + +STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol." + + +class LifespanOn: + def __init__(self, config: Config) -> None: + if not config.loaded: + config.load() + + self.config = config + self.logger = logging.getLogger("uvicorn.error") + self.startup_event = asyncio.Event() + self.shutdown_event = asyncio.Event() + self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue() + self.error_occurred = False + self.startup_failed = False + self.shutdown_failed = False + self.should_exit = False + self.state: dict[str, Any] = {} + + async def startup(self) -> None: + self.logger.info("Waiting for application startup.") + + loop = asyncio.get_event_loop() + main_lifespan_task = loop.create_task(self.main()) # noqa: F841 + # Keep a hard reference to prevent garbage collection + # See https://github.com/Kludex/uvicorn/pull/972 + startup_event: LifespanStartupEvent = {"type": "lifespan.startup"} + await self.receive_queue.put(startup_event) + await self.startup_event.wait() + + if self.startup_failed or (self.error_occurred and self.config.lifespan == "on"): + self.logger.error("Application startup failed. Exiting.") + self.should_exit = True + else: + self.logger.info("Application startup complete.") + + async def shutdown(self) -> None: + if self.error_occurred: + return + self.logger.info("Waiting for application shutdown.") + shutdown_event: LifespanShutdownEvent = {"type": "lifespan.shutdown"} + await self.receive_queue.put(shutdown_event) + await self.shutdown_event.wait() + + if self.shutdown_failed or (self.error_occurred and self.config.lifespan == "on"): + self.logger.error("Application shutdown failed. Exiting.") + self.should_exit = True + else: + self.logger.info("Application shutdown complete.") + + async def main(self) -> None: + try: + app = self.config.loaded_app + scope: LifespanScope = { + "type": "lifespan", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.0"}, + "state": self.state, + } + await app(scope, self.receive, self.send) + except BaseException as exc: + self.asgi = None + self.error_occurred = True + if self.startup_failed or self.shutdown_failed: + return + if self.config.lifespan == "auto": + msg = "ASGI 'lifespan' protocol appears unsupported." + self.logger.info(msg) + else: + msg = "Exception in 'lifespan' protocol\n" + self.logger.error(msg, exc_info=exc) + finally: + self.startup_event.set() + self.shutdown_event.set() + + async def send(self, message: LifespanSendMessage) -> None: + assert message["type"] in ( + "lifespan.startup.complete", + "lifespan.startup.failed", + "lifespan.shutdown.complete", + "lifespan.shutdown.failed", + ) + + if message["type"] == "lifespan.startup.complete": + assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.startup_event.set() + + elif message["type"] == "lifespan.startup.failed": + assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.startup_event.set() + self.startup_failed = True + if message.get("message"): + self.logger.error(message["message"]) + + elif message["type"] == "lifespan.shutdown.complete": + assert self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.shutdown_event.set() + + elif message["type"] == "lifespan.shutdown.failed": + assert self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.shutdown_event.set() + self.shutdown_failed = True + if message.get("message"): + self.logger.error(message["message"]) + + async def receive(self) -> LifespanReceiveMessage: + return await self.receive_queue.get() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py new file mode 100644 index 0000000..28c65fc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import http +import logging +import sys +from copy import copy +from typing import Literal + +from uvicorn._ansi import style as ansi_style + +TRACE_LOG_LEVEL = 5 + + +class ColourizedFormatter(logging.Formatter): + """ + A custom log formatter class that: + + * Outputs the LOG_LEVEL with an appropriate color. + * If a log call includes an `extra={"color_message": ...}` it will be used + for formatting the output, instead of the plain text message. + """ + + level_name_colors = { + TRACE_LOG_LEVEL: lambda level_name: ansi_style(str(level_name), fg="blue"), + logging.DEBUG: lambda level_name: ansi_style(str(level_name), fg="cyan"), + logging.INFO: lambda level_name: ansi_style(str(level_name), fg="green"), + logging.WARNING: lambda level_name: ansi_style(str(level_name), fg="yellow"), + logging.ERROR: lambda level_name: ansi_style(str(level_name), fg="red"), + logging.CRITICAL: lambda level_name: ansi_style(str(level_name), fg="bright_red"), + } + + def __init__( + self, + fmt: str | None = None, + datefmt: str | None = None, + style: Literal["%", "{", "$"] = "%", + use_colors: bool | None = None, + ): + if use_colors in (True, False): + self.use_colors = use_colors + else: + self.use_colors = sys.stdout.isatty() + super().__init__(fmt=fmt, datefmt=datefmt, style=style) + + def color_level_name(self, level_name: str, level_no: int) -> str: + def default(level_name: str) -> str: + return str(level_name) # pragma: no cover + + func = self.level_name_colors.get(level_no, default) + return func(level_name) + + def should_use_colors(self) -> bool: + return True # pragma: no cover + + def formatMessage(self, record: logging.LogRecord) -> str: + recordcopy = copy(record) + levelname = recordcopy.levelname + separator = " " * (8 - len(recordcopy.levelname)) + if self.use_colors: + levelname = self.color_level_name(levelname, recordcopy.levelno) + if "color_message" in recordcopy.__dict__: + recordcopy.msg = recordcopy.__dict__["color_message"] + recordcopy.__dict__["message"] = recordcopy.getMessage() + recordcopy.__dict__["levelprefix"] = levelname + ":" + separator + return super().formatMessage(recordcopy) + + +class DefaultFormatter(ColourizedFormatter): + def should_use_colors(self) -> bool: + return sys.stderr.isatty() # pragma: no cover + + +class AccessFormatter(ColourizedFormatter): + status_code_colours = { + 1: lambda code: ansi_style(str(code), fg="bright_white"), + 2: lambda code: ansi_style(str(code), fg="green"), + 3: lambda code: ansi_style(str(code), fg="yellow"), + 4: lambda code: ansi_style(str(code), fg="red"), + 5: lambda code: ansi_style(str(code), fg="bright_red"), + } + + def get_status_code(self, status_code: int) -> str: + try: + status_phrase = http.HTTPStatus(status_code).phrase + except ValueError: + status_phrase = "" + status_and_phrase = f"{status_code} {status_phrase}" + if self.use_colors: + + def default(code: int) -> str: + return status_and_phrase # pragma: no cover + + func = self.status_code_colours.get(status_code // 100, default) + return func(status_and_phrase) + return status_and_phrase + + def formatMessage(self, record: logging.LogRecord) -> str: + recordcopy = copy(record) + ( + client_addr, + method, + full_path, + http_version, + status_code, + ) = recordcopy.args # type: ignore[misc] + status_code = self.get_status_code(int(status_code)) # type: ignore[arg-type] + request_line = f"{method} {full_path} HTTP/{http_version}" + if self.use_colors: + request_line = ansi_style(request_line, bold=True) + recordcopy.__dict__.update( + { + "client_addr": client_addr, + "request_line": request_line, + "status_code": status_code, + } + ) + return super().formatMessage(recordcopy) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py new file mode 100644 index 0000000..ad6121e --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import asyncio +import sys +from collections.abc import Callable + + +def asyncio_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + if sys.platform == "win32" and not use_subprocess: + return asyncio.ProactorEventLoop + return asyncio.SelectorEventLoop diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py new file mode 100644 index 0000000..1908399 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + + +def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + try: + import uvloop # noqa + except ImportError: # pragma: no cover + from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory + + return loop_factory(use_subprocess=use_subprocess) + else: # pragma: no cover + from uvicorn.loops.uvloop import uvloop_loop_factory + + return uvloop_loop_factory(use_subprocess=use_subprocess) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py new file mode 100644 index 0000000..c6692c5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + +import uvloop + + +def uvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + return uvloop.new_event_loop diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py new file mode 100644 index 0000000..2bbbc4b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + +import zuvloop + + +def zuvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + return zuvloop.new_event_loop diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py new file mode 100644 index 0000000..854c10b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/main.py @@ -0,0 +1,656 @@ +from __future__ import annotations + +import asyncio +import logging +import os +import platform +import ssl +import sys +import warnings +from collections.abc import Callable +from configparser import RawConfigParser +from typing import IO, Any, get_args + +import click + +import uvicorn +from uvicorn._types import ASGIApplication +from uvicorn.config import ( + INTERFACES, + LIFESPAN, + LOG_LEVELS, + LOGGING_CONFIG, + SSL_PROTOCOL_VERSION, + STARTUP_FAILURE, + Config, + HTTPProtocolType, + InterfaceType, + LifespanType, + LoopFactoryType, + WSProtocolType, +) +from uvicorn.server import Server +from uvicorn.supervisors import ChangeReload, Multiprocess + +LEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys())) +LIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys())) +INTERFACE_CHOICES = click.Choice(INTERFACES) + + +def _metavar_from_type(_type: Any) -> str: + return f"[{'|'.join(key for key in get_args(_type) if key != 'none')}]" + + +logger = logging.getLogger("uvicorn.error") + + +def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + click.echo( + "Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( # noqa: UP032 + version=uvicorn.__version__, + py_implementation=platform.python_implementation(), + py_version=platform.python_version(), + system=platform.system(), + ) + ) + ctx.exit() + + +@click.command(context_settings={"auto_envvar_prefix": "UVICORN"}) +@click.argument("app", envvar="UVICORN_APP") +@click.option( + "--host", + type=str, + default="127.0.0.1", + help="Bind socket to this host.", + show_default=True, +) +@click.option( + "--port", + type=int, + default=8000, + help="Bind socket to this port. If 0, an available port will be picked.", + show_default=True, +) +@click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.") +@click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.") +@click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.") +@click.option( + "--reload-dir", + "reload_dirs", + multiple=True, + help="Set reload directories explicitly, instead of using the current working directory.", + type=click.Path(exists=True), +) +@click.option( + "--reload-include", + "reload_includes", + multiple=True, + help="Set glob patterns to include while watching for files. Includes '*.py' " + "by default; these defaults can be overridden with `--reload-exclude`. " + "This option has no effect unless watchfiles is installed.", +) +@click.option( + "--reload-exclude", + "reload_excludes", + multiple=True, + help="Set glob patterns to exclude while watching for files. Includes " + "'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden " + "with `--reload-include`. This option has no effect unless watchfiles is " + "installed.", +) +@click.option( + "--reload-delay", + type=float, + default=0.25, + show_default=True, + help="Delay between previous and next check if application needs to be. Defaults to 0.25s.", +) +@click.option( + "--workers", + default=None, + type=int, + help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment" + " variable if available, or 1. Not valid with --reload.", +) +@click.option( + "--loop", + type=str, + metavar=_metavar_from_type(LoopFactoryType), + default="auto", + help="Event loop factory implementation.", + show_default=True, +) +@click.option( + "--http", + type=str, + metavar=_metavar_from_type(HTTPProtocolType), + default="auto", + help="HTTP protocol implementation.", + show_default=True, +) +@click.option( + "--http2", + is_flag=True, + default=False, + help="Enable HTTP/2 support. Requires --http zttp.", + show_default=True, +) +@click.option( + "--ws", + type=str, + metavar=_metavar_from_type(WSProtocolType), + default="auto", + help="WebSocket protocol implementation.", + show_default=True, +) +@click.option( + "--ws-max-size", + type=int, + default=16777216, + help="WebSocket max size message in bytes", + show_default=True, +) +@click.option( + "--ws-max-queue", + type=int, + default=32, + help="The maximum length of the WebSocket message queue.", + show_default=True, +) +@click.option( + "--ws-ping-interval", + type=float, + default=20.0, + help="WebSocket ping interval in seconds.", + show_default=True, +) +@click.option( + "--ws-ping-timeout", + type=float, + default=20.0, + help="WebSocket ping timeout in seconds.", + show_default=True, +) +@click.option( + "--ws-per-message-deflate", + type=bool, + default=True, + help="WebSocket per-message-deflate compression", + show_default=True, +) +@click.option( + "--lifespan", + type=LIFESPAN_CHOICES, + default="auto", + help="Lifespan implementation.", + show_default=True, +) +@click.option( + "--interface", + type=INTERFACE_CHOICES, + default="auto", + help="Select ASGI3, ASGI2, or WSGI as the application interface.", + show_default=True, +) +@click.option( + "--env-file", + type=click.Path(exists=True), + default=None, + help="Environment configuration file.", + show_default=True, +) +@click.option( + "--log-config", + type=click.Path(exists=True), + default=None, + help="Logging configuration file. Supported formats: .ini, .json, .yaml.", + show_default=True, +) +@click.option( + "--log-level", + type=LEVEL_CHOICES, + default=None, + help="Log level. [default: info]", + show_default=True, +) +@click.option( + "--access-log/--no-access-log", + is_flag=True, + default=True, + help="Enable/Disable access log.", +) +@click.option( + "--use-colors/--no-use-colors", + is_flag=True, + default=None, + help="Enable/Disable colorized logging.", +) +@click.option( + "--proxy-headers/--no-proxy-headers", + is_flag=True, + default=True, + help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate url scheme and remote address info.", +) +@click.option( + "--server-header/--no-server-header", + is_flag=True, + default=True, + help="Enable/Disable default Server header.", +) +@click.option( + "--date-header/--no-date-header", + is_flag=True, + default=True, + help="Enable/Disable default Date header.", +) +@click.option( + "--forwarded-allow-ips", + type=str, + default=None, + help="Comma separated list of IP Addresses, IP Networks, or literals " + "(e.g. UNIX Socket path) to trust with proxy headers. Defaults to the " + "$FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1,::1'. " + "The literal '*' means trust everything.", +) +@click.option( + "--root-path", + type=str, + default="", + help="Set the ASGI 'root_path' for applications submounted below a given URL path.", +) +@click.option( + "--limit-concurrency", + type=int, + default=None, + help="Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses.", +) +@click.option( + "--backlog", + type=int, + default=2048, + help="Maximum number of connections to hold in backlog", +) +@click.option( + "--limit-max-requests", + type=int, + default=None, + help="Maximum number of requests to service before terminating the process.", +) +@click.option( + "--limit-max-requests-jitter", + type=int, + default=0, + help="Maximum jitter to add to limit_max_requests." + " Staggers worker restarts to avoid all workers restarting simultaneously.", + show_default=True, +) +@click.option( + "--timeout-keep-alive", + type=int, + default=5, + help="Close Keep-Alive connections if no new data is received within this timeout (in seconds).", + show_default=True, +) +@click.option( + "--timeout-graceful-shutdown", + type=int, + default=None, + help="Maximum number of seconds to wait for graceful shutdown.", +) +@click.option( + "--timeout-worker-healthcheck", + type=int, + default=5, + help="Maximum number of seconds to wait for a worker to respond to a healthcheck.", + show_default=True, +) +@click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True) +@click.option( + "--ssl-certfile", + type=str, + default=None, + help="SSL certificate file", + show_default=True, +) +@click.option( + "--ssl-keyfile-password", + type=str, + default=None, + help="SSL keyfile password", + show_default=True, +) +@click.option( + "--ssl-version", + type=int, + default=int(SSL_PROTOCOL_VERSION), + help="SSL version to use (see stdlib ssl module's)", + show_default=True, +) +@click.option( + "--ssl-cert-reqs", + type=int, + default=int(ssl.CERT_NONE), + help="Whether client certificate is required (see stdlib ssl module's)", + show_default=True, +) +@click.option( + "--ssl-ca-certs", + type=str, + default=None, + help="CA certificates file", + show_default=True, +) +@click.option( + "--ssl-ciphers", + type=str, + default=None, + help="Ciphers to use (see stdlib ssl module's). Defaults to OpenSSL's safe defaults.", + show_default=True, +) +@click.option( + "--header", + "headers", + multiple=True, + help="Specify custom default HTTP response headers as a Name:Value pair", +) +@click.option( + "--version", + is_flag=True, + callback=print_version, + expose_value=False, + is_eager=True, + help="Display the uvicorn version and exit.", +) +@click.option( + "--app-dir", + default="", + show_default=True, + help="Look for APP in the specified directory, by adding this to the PYTHONPATH." + " Defaults to the current working directory.", +) +@click.option( + "--h11-max-incomplete-event-size", + "h11_max_incomplete_event_size", + type=int, + default=None, + help="For h11, the maximum number of bytes to buffer of an incomplete event.", +) +@click.option( + "--reset-contextvars", + is_flag=True, + default=False, + help="Run each ASGI request in a fresh contextvars.Context. Hides context set in the lifespan.", + show_default=True, +) +@click.option( + "--factory", + is_flag=True, + default=False, + help="Treat APP as an application factory, i.e. a () -> callable.", + show_default=True, +) +def main( + app: str, + host: str, + port: int, + uds: str, + fd: int, + loop: LoopFactoryType | str, + http: HTTPProtocolType | str, + http2: bool, + ws: WSProtocolType | str, + ws_max_size: int, + ws_max_queue: int, + ws_ping_interval: float, + ws_ping_timeout: float, + ws_per_message_deflate: bool, + lifespan: LifespanType, + interface: InterfaceType, + reload: bool, + reload_dirs: list[str], + reload_includes: list[str], + reload_excludes: list[str], + reload_delay: float, + workers: int, + env_file: str, + log_config: str, + log_level: str, + access_log: bool, + proxy_headers: bool, + server_header: bool, + date_header: bool, + forwarded_allow_ips: str, + root_path: str, + limit_concurrency: int, + backlog: int, + limit_max_requests: int, + limit_max_requests_jitter: int, + timeout_keep_alive: int, + timeout_graceful_shutdown: int | None, + timeout_worker_healthcheck: int, + ssl_keyfile: str, + ssl_certfile: str, + ssl_keyfile_password: str, + ssl_version: int, + ssl_cert_reqs: int, + ssl_ca_certs: str, + ssl_ciphers: str | None, + headers: list[str], + use_colors: bool, + app_dir: str, + h11_max_incomplete_event_size: int | None, + reset_contextvars: bool, + factory: bool, +) -> None: + run( + app, + host=host, + port=port, + uds=uds, + fd=fd, + loop=loop, + http=http, + http2=http2, + ws=ws, + ws_max_size=ws_max_size, + ws_max_queue=ws_max_queue, + ws_ping_interval=ws_ping_interval, + ws_ping_timeout=ws_ping_timeout, + ws_per_message_deflate=ws_per_message_deflate, + lifespan=lifespan, + env_file=env_file, + log_config=LOGGING_CONFIG if log_config is None else log_config, + log_level=log_level, + access_log=access_log, + interface=interface, + reload=reload, + reload_dirs=reload_dirs or None, + reload_includes=reload_includes or None, + reload_excludes=reload_excludes or None, + reload_delay=reload_delay, + workers=workers, + proxy_headers=proxy_headers, + server_header=server_header, + date_header=date_header, + forwarded_allow_ips=forwarded_allow_ips, + root_path=root_path, + limit_concurrency=limit_concurrency, + backlog=backlog, + limit_max_requests=limit_max_requests, + limit_max_requests_jitter=limit_max_requests_jitter, + timeout_keep_alive=timeout_keep_alive, + timeout_graceful_shutdown=timeout_graceful_shutdown, + timeout_worker_healthcheck=timeout_worker_healthcheck, + ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + ssl_keyfile_password=ssl_keyfile_password, + ssl_version=ssl_version, + ssl_cert_reqs=ssl_cert_reqs, + ssl_ca_certs=ssl_ca_certs, + ssl_ciphers=ssl_ciphers, + headers=[header.split(":", 1) for header in headers], # type: ignore[misc] + use_colors=use_colors, + factory=factory, + app_dir=app_dir, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, + reset_contextvars=reset_contextvars, + ) + + +def run( + app: ASGIApplication | Callable[..., Any] | str, + *, + host: str = "127.0.0.1", + port: int = 8000, + uds: str | None = None, + fd: int | None = None, + loop: LoopFactoryType | str = "auto", + http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto", + http2: bool = False, + ws: type[asyncio.Protocol] | WSProtocolType | str = "auto", + ws_max_size: int = 16777216, + ws_max_queue: int = 32, + ws_ping_interval: float | None = 20.0, + ws_ping_timeout: float | None = 20.0, + ws_per_message_deflate: bool = True, + lifespan: LifespanType = "auto", + interface: InterfaceType = "auto", + reload: bool = False, + reload_dirs: list[str] | str | None = None, + reload_includes: list[str] | str | None = None, + reload_excludes: list[str] | str | None = None, + reload_delay: float = 0.25, + workers: int | None = None, + env_file: str | os.PathLike[str] | None = None, + log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, + log_level: str | int | None = None, + access_log: bool = True, + proxy_headers: bool = True, + server_header: bool = True, + date_header: bool = True, + forwarded_allow_ips: list[str] | str | None = None, + root_path: str = "", + limit_concurrency: int | None = None, + backlog: int = 2048, + limit_max_requests: int | None = None, + limit_max_requests_jitter: int = 0, + timeout_keep_alive: int = 5, + timeout_graceful_shutdown: int | None = None, + timeout_worker_healthcheck: int = 5, + ssl_keyfile: str | os.PathLike[str] | None = None, + ssl_certfile: str | os.PathLike[str] | None = None, + ssl_keyfile_password: str | None = None, + ssl_version: int = SSL_PROTOCOL_VERSION, + ssl_cert_reqs: int = ssl.CERT_NONE, + ssl_ca_certs: str | os.PathLike[str] | None = None, + ssl_ciphers: str | None = None, + ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None, + headers: list[tuple[str, str]] | None = None, + use_colors: bool | None = None, + app_dir: str | None = None, + factory: bool = False, + h11_max_incomplete_event_size: int | None = None, + reset_contextvars: bool = False, +) -> None: + if app_dir is not None: + sys.path.insert(0, app_dir) + + config = Config( + app, + host=host, + port=port, + uds=uds, + fd=fd, + loop=loop, + http=http, + http2=http2, + ws=ws, + ws_max_size=ws_max_size, + ws_max_queue=ws_max_queue, + ws_ping_interval=ws_ping_interval, + ws_ping_timeout=ws_ping_timeout, + ws_per_message_deflate=ws_per_message_deflate, + lifespan=lifespan, + interface=interface, + reload=reload, + reload_dirs=reload_dirs, + reload_includes=reload_includes, + reload_excludes=reload_excludes, + reload_delay=reload_delay, + workers=workers, + env_file=env_file, + log_config=log_config, + log_level=log_level, + access_log=access_log, + proxy_headers=proxy_headers, + server_header=server_header, + date_header=date_header, + forwarded_allow_ips=forwarded_allow_ips, + root_path=root_path, + limit_concurrency=limit_concurrency, + backlog=backlog, + limit_max_requests=limit_max_requests, + limit_max_requests_jitter=limit_max_requests_jitter, + timeout_keep_alive=timeout_keep_alive, + timeout_graceful_shutdown=timeout_graceful_shutdown, + timeout_worker_healthcheck=timeout_worker_healthcheck, + ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + ssl_keyfile_password=ssl_keyfile_password, + ssl_version=ssl_version, + ssl_cert_reqs=ssl_cert_reqs, + ssl_ca_certs=ssl_ca_certs, + ssl_ciphers=ssl_ciphers, + ssl_context_factory=ssl_context_factory, + headers=headers, + use_colors=use_colors, + factory=factory, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, + reset_contextvars=reset_contextvars, + ) + if config.reload or config.workers > 1: + if not isinstance(app, str): + logger = logging.getLogger("uvicorn.error") + logger.warning("You must pass the application as an import string to enable 'reload' or 'workers'.") + sys.exit(STARTUP_FAILURE) + else: + config.load_app() + + server = Server(config=config) + + try: + if config.should_reload: + sock = config.bind_socket() + ChangeReload(config, target=server.run, sockets=[sock]).run() + elif config.workers > 1: + sock = config.bind_socket() + Multiprocess(config, sockets=[sock]).run() + else: + server.run() + except KeyboardInterrupt: # pragma: full coverage + pass + finally: + if config.uds and os.path.exists(config.uds): + os.remove(config.uds) # pragma: py-win32 + + if not server.started and not config.should_reload and config.workers == 1: + sys.exit(STARTUP_FAILURE) + + +def __getattr__(name: str) -> Any: + if name == "ServerState": + warnings.warn( + "uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.", + DeprecationWarning, + ) + from uvicorn.server import ServerState + + return ServerState + raise AttributeError(f"module {__name__} has no attribute {name}") + + +if __name__ == "__main__": + main() # pragma: no cover diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py new file mode 100644 index 0000000..4e15d15 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py @@ -0,0 +1,15 @@ +from uvicorn._types import ( + ASGI2Application, + ASGIReceiveCallable, + ASGISendCallable, + Scope, +) + + +class ASGI2Middleware: + def __init__(self, app: "ASGI2Application"): + self.app = app + + async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable") -> None: + instance = self.app(scope) + await instance(receive, send) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py new file mode 100644 index 0000000..0174bcc --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py @@ -0,0 +1,87 @@ +import logging +from typing import Any + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + WWWScope, +) +from uvicorn.logging import TRACE_LOG_LEVEL + +PLACEHOLDER_FORMAT = { + "body": "<{length} bytes>", + "bytes": "<{length} bytes>", + "text": "<{length} chars>", + "headers": "<...>", +} + + +def message_with_placeholders(message: Any) -> Any: + """ + Return an ASGI message, with any body-type content omitted and replaced + with a placeholder. + """ + new_message = message.copy() + for attr in PLACEHOLDER_FORMAT.keys(): + if message.get(attr) is not None: + content = message[attr] + placeholder = PLACEHOLDER_FORMAT[attr].format(length=len(content)) + new_message[attr] = placeholder + return new_message + + +class MessageLoggerMiddleware: + def __init__(self, app: "ASGI3Application"): + self.task_counter = 0 + self.app = app + self.logger = logging.getLogger("uvicorn.asgi") + + def trace(message: Any, *args: Any, **kwargs: Any) -> None: + self.logger.log(TRACE_LOG_LEVEL, message, *args, **kwargs) + + self.logger.trace = trace # type: ignore + + async def __call__( + self, + scope: "WWWScope", + receive: "ASGIReceiveCallable", + send: "ASGISendCallable", + ) -> None: + self.task_counter += 1 + + task_counter = self.task_counter + client = scope.get("client") + prefix = "%s:%d - ASGI" % (client[0], client[1]) if client else "ASGI" + + async def inner_receive() -> "ASGIReceiveEvent": + message = await receive() + logged_message = message_with_placeholders(message) + log_text = "%s [%d] Receive %s" + self.logger.trace( # type: ignore + log_text, prefix, task_counter, logged_message + ) + return message + + async def inner_send(message: "ASGISendEvent") -> None: + logged_message = message_with_placeholders(message) + log_text = "%s [%d] Send %s" + self.logger.trace( # type: ignore + log_text, prefix, task_counter, logged_message + ) + await send(message) + + logged_scope = message_with_placeholders(scope) + log_text = "%s [%d] Started scope=%s" + self.logger.trace(log_text, prefix, task_counter, logged_scope) # type: ignore + try: + await self.app(scope, inner_receive, inner_send) + except BaseException as exc: + log_text = "%s [%d] Raised exception" + self.logger.trace(log_text, prefix, task_counter) # type: ignore + raise exc from None + else: + log_text = "%s [%d] Completed" + self.logger.trace(log_text, prefix, task_counter) # type: ignore diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py new file mode 100644 index 0000000..ebd210c --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import functools +import ipaddress + +from uvicorn._types import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope + + +class ProxyHeadersMiddleware: + """Middleware for handling known proxy headers + + This middleware can be used when a known proxy is fronting the application, + and is trusted to be properly setting the `X-Forwarded-Proto` and + `X-Forwarded-For` headers with the connecting client information. + + Modifies the `client` and `scheme` information so that they reference + the connecting client, rather that the connecting proxy. + + References: + - + - + """ + + def __init__(self, app: ASGI3Application, trusted_hosts: list[str] | str = "127.0.0.1") -> None: + self.app = app + self.trusted_hosts = _TrustedHosts(trusted_hosts) + + async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + if scope["type"] == "lifespan": + return await self.app(scope, receive, send) + + client_addr = scope.get("client") + client_host = client_addr[0] if client_addr else None + + if client_host in self.trusted_hosts: + x_forwarded_proto_value: bytes | None = None + x_forwarded_for_values: list[bytes] = [] + for name, value in scope["headers"]: + if name == b"x-forwarded-proto": + x_forwarded_proto_value = value + elif name == b"x-forwarded-for": + x_forwarded_for_values.append(value) + + if x_forwarded_proto_value is not None: + x_forwarded_proto = x_forwarded_proto_value.decode("latin1").strip() + + if x_forwarded_proto in {"http", "https", "ws", "wss"}: + if scope["type"] == "websocket": + scope["scheme"] = x_forwarded_proto.replace("http", "ws") + else: + scope["scheme"] = x_forwarded_proto + + if x_forwarded_for_values: + x_forwarded_for = b", ".join(x_forwarded_for_values).decode("latin1") + host, port = self.trusted_hosts.get_trusted_client_address(x_forwarded_for) + + if host: + # If the x-forwarded-for header is empty then host is an empty string. + # Only set the client if we actually got something usable. + # See: https://github.com/Kludex/uvicorn/issues/1068 + scope["client"] = (host, port) + + return await self.app(scope, receive, send) + + +def _parse_raw_hosts(value: str) -> list[str]: + return [item.strip() for item in value.split(",")] + + +def _parse_host_port(value: str) -> tuple[str, int]: + """Parse a forwarded host value into host and optional port. + + Accepts bare IPs, IPv4 `host:port`, and bracketed IPv6 `[host]:port`. + Any unrecognized or malformed value is treated conservatively and returned + without a port so trust checks do not silently normalize arbitrary input. + """ + + if value.startswith("["): + bracket_end = value.find("]") + if bracket_end == -1: + return value, 0 + + host = value[1:bracket_end] + remainder = value[bracket_end + 1 :] + if not remainder: + return host, 0 + if not remainder.startswith(":"): + return value, 0 + + try: + return host, int(remainder[1:]) + except ValueError: + return host, 0 + + if value.count(":") == 1: + host, port = value.rsplit(":", 1) + try: + return host, int(port) + except ValueError: + return value, 0 + + return value, 0 + + +class _TrustedHosts: + """Container for trusted hosts and networks""" + + def __init__(self, trusted_hosts: list[str] | str) -> None: + self.always_trust: bool = trusted_hosts in ("*", ["*"]) + + self.trusted_literals: set[str] = set() + self.trusted_hosts: set[ipaddress.IPv4Address | ipaddress.IPv6Address] = set() + self.trusted_networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set() + + # Notes: + # - We separate hosts from literals as there are many ways to write + # an IPv6 Address so we need to compare by object. + # - We don't convert IP Address to single host networks (e.g. /32 / 128) as + # it more efficient to do an address lookup in a set than check for + # membership in each network. + # - We still allow literals as it might be possible that we receive a + # something that isn't an IP Address e.g. a unix socket. + + if not self.always_trust: + if isinstance(trusted_hosts, str): + trusted_hosts = _parse_raw_hosts(trusted_hosts) + + for host in trusted_hosts: + # Note: because we always convert invalid IP types to literals it + # is not possible for the user to know they provided a malformed IP + # type - this may lead to unexpected / difficult to debug behaviour. + + if "/" in host: + # Looks like a network + try: + self.trusted_networks.add(ipaddress.ip_network(host)) + except ValueError: + # Was not a valid IP Network + self.trusted_literals.add(host) + else: + try: + self.trusted_hosts.add(ipaddress.ip_address(host)) + except ValueError: + # Was not a valid IP Address + self.trusted_literals.add(host) + + self._trusts = functools.lru_cache(maxsize=4096)(self._compute_trust) + + def __contains__(self, host: str | None) -> bool: + if self.always_trust: + return True + + if not host: + return False + + # Don't cache hosts longer than a DNS name (253); they can't be trusted and would pin huge cache keys. + if len(host) > 253: + return self._compute_trust(host) # pragma: no cover + + return self._trusts(host) + + def _compute_trust(self, host: str) -> bool: + try: + ip = ipaddress.ip_address(host) + return ip in self.trusted_hosts or any(ip in net for net in self.trusted_networks) + except ValueError: + return host in self.trusted_literals + + def get_trusted_client_address(self, x_forwarded_for: str) -> tuple[str, int]: + """Extract the client address from x_forwarded_for header. + + In general this is the first "untrusted" host in the forwarded for list. + """ + x_forwarded_for_hosts = _parse_raw_hosts(x_forwarded_for) + + if self.always_trust: + return _parse_host_port(x_forwarded_for_hosts[0]) + + # Note: each proxy appends to the header list so check it in reverse order + for host_port in reversed(x_forwarded_for_hosts): + host, port = _parse_host_port(host_port) + if host not in self: + return host, port + + # All hosts are trusted meaning that the client was also a trusted proxy + # See https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576 + return _parse_host_port(x_forwarded_for_hosts[0]) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py new file mode 100644 index 0000000..b2e3cb7 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py @@ -0,0 +1,199 @@ +from __future__ import annotations + +import asyncio +import concurrent.futures +import io +import sys +import warnings +from collections import deque +from collections.abc import Iterable + +from uvicorn._types import ( + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + Environ, + ExcInfo, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, + StartResponse, + WSGIApp, +) + + +def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO) -> Environ: + """ + Builds a scope and request message into a WSGI environ object. + """ + script_name = scope.get("root_path", "").encode("utf8").decode("latin1") + path_info = scope["path"].encode("utf8").decode("latin1") + if path_info.startswith(script_name): + path_info = path_info[len(script_name) :] + environ = { + "REQUEST_METHOD": scope["method"], + "SCRIPT_NAME": script_name, + "PATH_INFO": path_info, + "QUERY_STRING": scope["query_string"].decode("ascii"), + "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"], + "wsgi.version": (1, 0), + "wsgi.url_scheme": scope.get("scheme", "http"), + "wsgi.input": body, + "wsgi.errors": sys.stdout, + "wsgi.multithread": True, + "wsgi.multiprocess": True, + "wsgi.run_once": False, + } + + # Get server name and port - required in WSGI, not in ASGI + server = scope.get("server") + if server is None: + server = ("localhost", 80) + environ["SERVER_NAME"] = server[0] + environ["SERVER_PORT"] = server[1] + + # Get client IP address + client = scope.get("client") + if client is not None: + environ["REMOTE_ADDR"] = client[0] + + # Go through headers and make them into environ entries + for name, value in scope.get("headers", []): + name_str: str = name.decode("latin1") + if name_str == "content-length": + corrected_name = "CONTENT_LENGTH" + elif name_str == "content-type": + corrected_name = "CONTENT_TYPE" + else: + corrected_name = "HTTP_%s" % name_str.upper().replace("-", "_") + # HTTPbis say only ASCII chars are allowed in headers, but we latin1 + # just in case + value_str: str = value.decode("latin1") + if corrected_name in environ: + corrected_name_environ = environ[corrected_name] + assert isinstance(corrected_name_environ, str) + value_str = corrected_name_environ + "," + value_str + environ[corrected_name] = value_str + return environ + + +class _WSGIMiddleware: + def __init__(self, app: WSGIApp, workers: int = 10): + warnings.warn( + "Uvicorn's native WSGI implementation is deprecated, you should switch to a2wsgi (`pip install a2wsgi`).", + DeprecationWarning, + ) + self.app = app + self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers) + + async def __call__( + self, + scope: HTTPScope, + receive: ASGIReceiveCallable, + send: ASGISendCallable, + ) -> None: + assert scope["type"] == "http" + instance = WSGIResponder(self.app, self.executor, scope) + await instance(receive, send) + + +class WSGIResponder: + def __init__( + self, + app: WSGIApp, + executor: concurrent.futures.ThreadPoolExecutor, + scope: HTTPScope, + ): + self.app = app + self.executor = executor + self.scope = scope + self.status = None + self.response_headers = None + self.send_event = asyncio.Event() + self.send_queue: deque[ASGISendEvent | None] = deque() + self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.response_started = False + self.exc_info: ExcInfo | None = None + + async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + message: HTTPRequestEvent = await receive() # type: ignore[assignment] + body = io.BytesIO(message.get("body", b"")) + more_body = message.get("more_body", False) + if more_body: + body.seek(0, io.SEEK_END) + while more_body: + body_message: HTTPRequestEvent = ( + await receive() # type: ignore[assignment] + ) + body.write(body_message.get("body", b"")) + more_body = body_message.get("more_body", False) + body.seek(0) + environ = build_environ(self.scope, message, body) + self.loop = asyncio.get_event_loop() + wsgi = self.loop.run_in_executor(self.executor, self.wsgi, environ, self.start_response) + sender = self.loop.create_task(self.sender(send)) + try: + await asyncio.wait_for(wsgi, None) + finally: + self.send_queue.append(None) + self.send_event.set() + await asyncio.wait_for(sender, None) + if self.exc_info is not None: + raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2]) + + async def sender(self, send: ASGISendCallable) -> None: + while True: + if self.send_queue: + message = self.send_queue.popleft() + if message is None: + return + await send(message) + else: # pragma: no cover + await self.send_event.wait() + self.send_event.clear() + + def start_response( + self, + status: str, + response_headers: Iterable[tuple[str, str]], + exc_info: ExcInfo | None = None, + ) -> None: + self.exc_info = exc_info + if not self.response_started: + self.response_started = True + status_code_str, _ = status.split(" ", 1) + status_code = int(status_code_str) + headers = [(name.encode("ascii"), value.encode("ascii")) for name, value in response_headers] + http_response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": status_code, + "headers": headers, + } + self.send_queue.append(http_response_start_event) + self.loop.call_soon_threadsafe(self.send_event.set) + + def wsgi(self, environ: Environ, start_response: StartResponse) -> None: + for chunk in self.app(environ, start_response): # type: ignore + response_body: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": chunk, + "more_body": True, + } + self.send_queue.append(response_body) + self.loop.call_soon_threadsafe(self.send_event.set) + + empty_body: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"", + "more_body": False, + } + self.send_queue.append(empty_body) + self.loop.call_soon_threadsafe(self.send_event.set) + + +try: + from a2wsgi import WSGIMiddleware +except ModuleNotFoundError: # pragma: no cover + WSGIMiddleware = _WSGIMiddleware # type: ignore[misc, assignment] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py new file mode 100644 index 0000000..a14bec1 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import asyncio + +AutoHTTPProtocol: type[asyncio.Protocol] +try: + import httptools # noqa +except ImportError: # pragma: no cover + from uvicorn.protocols.http.h11_impl import H11Protocol + + AutoHTTPProtocol = H11Protocol +else: # pragma: no cover + from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol + + AutoHTTPProtocol = HttpToolsProtocol diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py new file mode 100644 index 0000000..208c1f5 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import asyncio +from typing import Any, ClassVar + +from uvicorn.config import Config +from uvicorn.protocols.http.zttp_h2_impl import ZttpH2Protocol +from uvicorn.protocols.http.zttp_impl import ZttpProtocol +from uvicorn.protocols.utils import is_ssl +from uvicorn.server import ServerState + +HTTP2_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + + +class AutoZttpProtocol(asyncio.Protocol): + """Dispatches each new connection to the HTTP/1.1 or HTTP/2 zttp protocol. + + Over TLS the choice is made from the ALPN result as soon as the connection + is made. On cleartext connections the first bytes are sniffed for the + HTTP/2 preface (prior-knowledge h2c); anything else is HTTP/1.1. + """ + + alpn_protocols: ClassVar[list[str]] = ["h2", "http/1.1"] + + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.server_state = server_state + self.app_state = app_state + self.loop = _loop or asyncio.get_event_loop() + self.connections = server_state.connections + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.timeout_task: asyncio.TimerHandle | None = None + self.buffer = b"" + + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.transport = transport + if is_ssl(transport): + ssl_object = transport.get_extra_info("ssl_object") + alpn = ssl_object.selected_alpn_protocol() if ssl_object is not None else None + self._switch(http2=alpn == "h2") + return + self.connections.add(self) + self.timeout_task = self.loop.call_later(self.config.timeout_keep_alive, self.transport.close) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + if self.timeout_task is not None: + self.timeout_task.cancel() + self.timeout_task = None + + def eof_received(self) -> None: + pass + + def shutdown(self) -> None: + self.transport.close() + + def data_received(self, data: bytes) -> None: + self.buffer += data + if len(self.buffer) < len(HTTP2_PREFACE) and HTTP2_PREFACE.startswith(self.buffer): + return + self.connections.discard(self) + if self.timeout_task is not None: + self.timeout_task.cancel() + self.timeout_task = None + self._switch(http2=self.buffer.startswith(HTTP2_PREFACE), initial_data=self.buffer) + + def _switch(self, http2: bool, initial_data: bytes = b"") -> None: + protocol_class: type[asyncio.Protocol] = ZttpH2Protocol if http2 else ZttpProtocol + protocol = protocol_class( # type: ignore[call-arg] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + _loop=self.loop, + ) + self.transport.set_protocol(protocol) + protocol.connection_made(self.transport) + if initial_data: + protocol.data_received(initial_data) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py new file mode 100644 index 0000000..2d1b5fa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py @@ -0,0 +1,54 @@ +import asyncio + +from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope + +CLOSE_HEADER = (b"connection", b"close") + +HIGH_WATER_LIMIT = 65536 + + +class FlowControl: + def __init__(self, transport: asyncio.Transport) -> None: + self._transport = transport + self.read_paused = False + self.write_paused = False + self._is_writable_event = asyncio.Event() + self._is_writable_event.set() + + async def drain(self) -> None: + await self._is_writable_event.wait() # pragma: full coverage + + def pause_reading(self) -> None: + if not self.read_paused: + self.read_paused = True + self._transport.pause_reading() + + def resume_reading(self) -> None: + if self.read_paused: + self.read_paused = False + self._transport.resume_reading() + + def pause_writing(self) -> None: + if not self.write_paused: # pragma: full coverage + self.write_paused = True + self._is_writable_event.clear() + + def resume_writing(self) -> None: + if self.write_paused: # pragma: full coverage + self.write_paused = False + self._is_writable_event.set() + + +async def service_unavailable(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + await send( + { + "type": "http.response.start", + "status": 503, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", b"19"), + (b"connection", b"close"), + ], + } + ) + await send({"type": "http.response.body", "body": b"Service Unavailable", "more_body": False}) diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py new file mode 100644 index 0000000..e863f8b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py @@ -0,0 +1,540 @@ +from __future__ import annotations + +import asyncio +import contextvars +import http +import logging +import sys +from collections.abc import Callable +from typing import Any, Literal +from urllib.parse import unquote + +import h11 +from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + + +def _get_status_phrase(status_code: int) -> bytes: + try: + return http.HTTPStatus(status_code).phrase.encode() + except ValueError: + return b"" + + +STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)} + + +class H11Protocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.conn = h11.Connection( + h11.SERVER, + config.h11_max_incomplete_event_size + if config.h11_max_incomplete_event_size is not None + else DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) + self.ws_protocol_class = config.ws_protocol_class + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: asyncio.TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Shared server state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + + # Per-request state + self.scope: HTTPScope = None # type: ignore[assignment] + self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment] + self.cycle: RequestResponseCycle = None # type: ignore[assignment] + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix) + + if self.cycle and not self.cycle.response_complete: + self.cycle.disconnected = True + if self.conn.our_state != h11.ERROR: + event = h11.ConnectionClosed() + try: + self.conn.send(event) + except h11.LocalProtocolError: + # Premature client disconnect + pass + + if self.cycle is not None: + self.cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def _get_upgrade(self) -> bytes | None: + connection = [] + upgrade = None + for name, value in self.headers: + if name == b"connection": + connection = [token.lower().strip() for token in value.split(b",")] + if name == b"upgrade": + upgrade = value.lower() + if b"upgrade" in connection: + return upgrade + return None + + def _should_upgrade_to_ws(self) -> bool: + if self.ws_protocol_class is None: + return False + return True + + def _unsupported_upgrade_warning(self) -> None: + msg = "Unsupported upgrade request." + self.logger.warning(msg) + if not self._should_upgrade_to_ws(): + msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501 + self.logger.warning(msg) + + def _should_upgrade(self) -> bool: + upgrade = self._get_upgrade() + if upgrade == b"websocket" and self._should_upgrade_to_ws(): + return True + if upgrade is not None: + self._unsupported_upgrade_warning() + return False + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + + self.conn.receive_data(data) + self.handle_events() + + def handle_events(self) -> None: + while True: + try: + event = self.conn.next_event() + except h11.RemoteProtocolError: + msg = "Invalid HTTP request received." + self.logger.warning(msg) + self.send_400_response(msg) + return + + if event is h11.NEED_DATA: + break + + elif event is h11.PAUSED: + # This case can occur in HTTP pipelining, so we need to + # stop reading any more data, and ensure that at the end + # of the active request/response cycle we handle any + # events that have been buffered up. + self.flow.pause_reading() + break + + elif isinstance(event, h11.Request): + # Pipelined HTTP requests and WebSocket upgrades may be processed after the keep-alive timer is armed. + self._unset_keepalive_if_required() + + self.headers = [(key.lower(), value) for key, value in event.headers] + raw_path, _, query_string = event.target.partition(b"?") + path = unquote(raw_path.decode("ascii")) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path + self.scope = { + "type": "http", + "asgi": {"version": self.asgi_version, "spec_version": "2.3"}, + "http_version": event.http_version.decode("ascii"), + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "method": event.method.decode("ascii"), + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string, + "headers": self.headers, + "state": self.app_state.copy(), + } + if self._should_upgrade(): + self.handle_websocket_upgrade(event) + return + + # Handle 503 responses when 'limit_concurrency' is exceeded. + if self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + self.cycle = RequestResponseCycle( + scope=self.scope, + conn=self.conn, + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + on_response=self.on_response_complete, + ) + if self.config.reset_contextvars: + # Opt-in workaround for https://github.com/python/cpython/issues/140947: + # asyncio can leak context vars between tasks. Hides context set in the + # lifespan or by external instrumentation. + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app)) + else: + task = self.loop.create_task(self.cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + elif isinstance(event, h11.Data): + if self.conn.our_state is h11.DONE: + continue + self.cycle.body += event.data + if len(self.cycle.body) > HIGH_WATER_LIMIT: + self.flow.pause_reading() + self.cycle.message_event.set() + + elif isinstance(event, h11.EndOfMessage): + if self.conn.our_state is h11.DONE: + self.transport.resume_reading() + self.conn.start_next_cycle() + continue + self.cycle.more_body = False + self.cycle.message_event.set() + if self.conn.their_state == h11.MUST_CLOSE: + break + + def handle_websocket_upgrade(self, event: h11.Request) -> None: + if self.logger.level <= TRACE_LOG_LEVEL: # pragma: full coverage + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix) + + self.connections.discard(self) + output = [event.method, b" ", event.target, b" HTTP/1.1\r\n"] + for name, value in self.headers: + output += [name, b": ", value, b"\r\n"] + output.append(b"\r\n") + protocol = self.ws_protocol_class( # type: ignore[call-arg, misc] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + ) + protocol.connection_made(self.transport) + protocol.data_received(b"".join(output)) + self.transport.set_protocol(protocol) + + def send_400_response(self, msg: str) -> None: + reason = STATUS_PHRASES[400] + headers: list[tuple[bytes, bytes]] = [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + ] + event = h11.Response(status_code=400, headers=headers, reason=reason) + output = self.conn.send(event) + self.transport.write(output) + + output = self.conn.send(event=h11.Data(data=msg.encode("ascii"))) + self.transport.write(output) + + output = self.conn.send(event=h11.EndOfMessage()) + self.transport.write(output) + + self.transport.close() + + def on_response_complete(self) -> None: + self.server_state.total_requests += 1 + + if self.transport.is_closing(): + return + + # Set a short Keep-Alive timeout. + self._unset_keepalive_if_required() + + self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler) + + # Unpause data reads if needed. + self.flow.resume_reading() + + # Unblock any pipelined events. + if self.conn.our_state is h11.DONE and self.conn.their_state is h11.DONE: + self.conn.start_next_cycle() + self.handle_events() + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + """ + if self.cycle is None or self.cycle.response_complete: + event = h11.ConnectionClosed() + self.conn.send(event) + self.transport.close() + else: + self.cycle.keep_alive = False + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: full coverage + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + if not self.transport.is_closing(): + event = h11.ConnectionClosed() + self.conn.send(event) + self.transport.close() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + conn: h11.Connection, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + on_response: Callable[..., None], + ) -> None: + self.scope = scope + self.conn = conn + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + + # Connection state + self.disconnected = False + self.keep_alive = True + self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.transport.close() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.transport.close() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.transport.close() + finally: + self.on_response = lambda: None + + async def send_500_response(self) -> None: + response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": 500, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + ], + } + await self.send(response_start_event) + response_body_event: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"Internal Server Error", + "more_body": False, + } + await self.send(response_body_event) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: full coverage + + if self.disconnected: + return # pragma: full coverage + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + self.waiting_for_100_continue = False + + status = message["status"] + headers = self.default_headers + list(message.get("headers", [])) + + if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers: + headers = headers + [CLOSE_HEADER] + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status, + ) + + # Write response status line and headers + reason = STATUS_PHRASES[status] + response = h11.Response(status_code=status, headers=headers, reason=reason) + output = self.conn.send(event=response) + self.transport.write(output) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + data = b"" if self.scope["method"] == "HEAD" else body + output = self.conn.send(event=h11.Data(data=data)) + self.transport.write(output) + + # Handle response completion + if not more_body: + self.response_complete = True + self.message_event.set() + output = self.conn.send(event=h11.EndOfMessage()) + self.transport.write(output) + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + if self.response_complete: + if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive: + self.conn.send(event=h11.ConnectionClosed()) + self.transport.close() + self.on_response() + + async def receive(self) -> ASGIReceiveEvent: + if self.waiting_for_100_continue and not self.transport.is_closing(): + headers: list[tuple[str, str]] = [] + event = h11.InformationalResponse(status_code=100, headers=headers, reason="Continue") + output = self.conn.send(event=event) + self.transport.write(output) + self.waiting_for_100_continue = False + + if not self.disconnected and not self.response_complete: + self.flow.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py new file mode 100644 index 0000000..dc95e23 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py @@ -0,0 +1,581 @@ +from __future__ import annotations + +import asyncio +import contextvars +import http +import logging +import re +import sys +import urllib +from asyncio.events import TimerHandle +from collections import deque +from collections.abc import Callable +from typing import Any, Literal + +import httptools + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + +HEADER_RE = re.compile(b'[\x00-\x1f\x7f()<>@,;:\\[\\]={} \t\\\\"]') +HEADER_VALUE_RE = re.compile(b"[\x00-\x08\x0a-\x1f\x7f]") + + +def _get_status_line(status_code: int) -> bytes: + try: + phrase = http.HTTPStatus(status_code).phrase.encode() + except ValueError: + phrase = b"" + return b"".join([b"HTTP/1.1 ", str(status_code).encode(), b" ", phrase, b"\r\n"]) + + +STATUS_LINE = {status_code: _get_status_line(status_code) for status_code in range(100, 600)} + + +class HttpToolsProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.parser = httptools.HttpRequestParser(self) + + try: + # Enable dangerous leniencies to allow server to a response on the first request from a pipelined request. + self.parser.set_dangerous_leniencies(lenient_data_after_close=True) + except AttributeError: # pragma: no cover + # httptools < 0.6.3 + pass + + self.ws_protocol_class = config.ws_protocol_class + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Global state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + self.pipeline: deque[tuple[RequestResponseCycle, ASGI3Application]] = deque() + + # Per-request state + self.scope: HTTPScope = None # type: ignore[assignment] + self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment] + self.expect_100_continue = False + self.cycle: RequestResponseCycle = None # type: ignore[assignment] + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix) + + if self.cycle and not self.cycle.response_complete: + self.cycle.disconnected = True + if self.cycle is not None: + self.cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + self.parser = None # type: ignore[assignment] + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def _get_upgrade(self) -> bytes | None: + connection = [] + upgrade = None + for name, value in self.headers: + if name == b"connection": + connection = [token.lower().strip() for token in value.split(b",")] + if name == b"upgrade": + upgrade = value.lower() + if b"upgrade" in connection: + return upgrade + return None # pragma: full coverage + + def _should_upgrade_to_ws(self) -> bool: + if self.ws_protocol_class is None: + return False + return True + + def _unsupported_upgrade_warning(self) -> None: + self.logger.warning("Unsupported upgrade request.") + if not self._should_upgrade_to_ws(): + msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501 + self.logger.warning(msg) + + def _should_upgrade(self) -> bool: + upgrade = self._get_upgrade() + return upgrade == b"websocket" and self._should_upgrade_to_ws() + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + + try: + self.parser.feed_data(data) + except httptools.HttpParserError: + msg = "Invalid HTTP request received." + self.logger.warning(msg) + self.send_400_response(msg) + return + except httptools.HttpParserUpgrade: + if self._should_upgrade(): + self.handle_websocket_upgrade() + else: + self._unsupported_upgrade_warning() + + def handle_websocket_upgrade(self) -> None: + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix) + + self.connections.discard(self) + method = self.scope["method"].encode() + output = [method, b" ", self.url, b" HTTP/1.1\r\n"] + for name, value in self.scope["headers"]: + output += [name, b": ", value, b"\r\n"] + output.append(b"\r\n") + protocol = self.ws_protocol_class( # type: ignore[call-arg, misc] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + ) + protocol.connection_made(self.transport) + protocol.data_received(b"".join(output)) + self.transport.set_protocol(protocol) + + def send_400_response(self, msg: str) -> None: + content = [STATUS_LINE[400]] + for name, value in self.server_state.default_headers: + content.extend([name, b": ", value, b"\r\n"]) # pragma: full coverage + content.extend( + [ + b"content-type: text/plain; charset=utf-8\r\n", + b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n", + b"connection: close\r\n", + b"\r\n", + msg.encode("ascii"), + ] + ) + self.transport.write(b"".join(content)) + self.transport.close() + + def on_message_begin(self) -> None: + self.url = b"" + self.expect_100_continue = False + self.headers = [] + self.scope = { # type: ignore[typeddict-item] + "type": "http", + "asgi": {"version": self.asgi_version, "spec_version": "2.3"}, + "http_version": "1.1", + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "root_path": self.root_path, + "headers": self.headers, + "state": self.app_state.copy(), + } + + # Parser callbacks + def on_url(self, url: bytes) -> None: + self.url += url + + def on_header(self, name: bytes, value: bytes) -> None: + name = name.lower() + if name == b"expect" and value.lower() == b"100-continue": + self.expect_100_continue = True + self.headers.append((name, value)) + + def on_headers_complete(self) -> None: + http_version = self.parser.get_http_version() + method = self.parser.get_method() + self.scope["method"] = method.decode("ascii") + if http_version != "1.1": + self.scope["http_version"] = http_version + if self.parser.should_upgrade() and self._should_upgrade(): + return + parsed_url = httptools.parse_url(self.url) + raw_path = parsed_url.path + path = raw_path.decode("ascii") + if "%" in path: + path = urllib.parse.unquote(path) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path + self.scope["path"] = full_path + self.scope["raw_path"] = full_raw_path + self.scope["query_string"] = parsed_url.query or b"" + + # Handle 503 responses when 'limit_concurrency' is exceeded. + if self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + existing_cycle = self.cycle + self.cycle = RequestResponseCycle( + scope=self.scope, + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + expect_100_continue=self.expect_100_continue, + keep_alive=http_version != "1.0" and self.parser.should_keep_alive(), + on_response=self.on_response_complete, + ) + if existing_cycle is None or existing_cycle.response_complete: + # Standard case - start processing the request. + self._start_asgi_task(self.cycle, app) + else: + # Pipelined HTTP requests need to be queued up. + self.flow.pause_reading() + self.pipeline.appendleft((self.cycle, app)) + + def _start_asgi_task(self, cycle: RequestResponseCycle, app: ASGI3Application) -> None: + if self.config.reset_contextvars: + # Opt-in workaround for https://github.com/python/cpython/issues/140947: + # asyncio can leak context vars between tasks. Hides context set in the + # lifespan or by external instrumentation. + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, cycle.run_asgi(app)) + else: + task = self.loop.create_task(cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + def on_body(self, body: bytes) -> None: + if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete: + return + self.cycle.body += body + if len(self.cycle.body) > HIGH_WATER_LIMIT: + self.flow.pause_reading() + self.cycle.message_event.set() + + def on_message_complete(self) -> None: + if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete: + return + self.cycle.more_body = False + self.cycle.message_event.set() + + def on_response_complete(self) -> None: + # Callback for pipelined HTTP requests to be started. + self.server_state.total_requests += 1 + + if self.transport.is_closing(): + return + + self._unset_keepalive_if_required() + + # Unpause data reads if needed. + self.flow.resume_reading() + + # Unblock any pipelined events. If there are none, arm the + # Keep-Alive timeout instead. + if self.pipeline: + cycle, app = self.pipeline.pop() + self._start_asgi_task(cycle, app) + else: + self.timeout_keep_alive_task = self.loop.call_later( + self.timeout_keep_alive, self.timeout_keep_alive_handler + ) + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + """ + if self.cycle is None or self.cycle.response_complete: + self.transport.close() + else: + self.cycle.keep_alive = False + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: full coverage + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + if not self.transport.is_closing(): + self.transport.close() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + expect_100_continue: bool, + keep_alive: bool, + on_response: Callable[..., None], + ): + self.scope = scope + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + + # Connection state + self.disconnected = False + self.keep_alive = keep_alive + self.waiting_for_100_continue = expect_100_continue + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + self.chunked_encoding: bool | None = None + self.expected_content_length = 0 + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.transport.close() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.transport.close() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.transport.close() + finally: + self.on_response = lambda: None + + async def send_500_response(self) -> None: + await self.send( + { + "type": "http.response.start", + "status": 500, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", b"21"), + (b"connection", b"close"), + ], + } + ) + await self.send({"type": "http.response.body", "body": b"Internal Server Error", "more_body": False}) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: full coverage + + if self.disconnected: + return # pragma: full coverage + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + self.waiting_for_100_continue = False + + status_code = message["status"] + headers = self.default_headers + list(message.get("headers", [])) + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status_code, + ) + + # Write response status line and headers + content = [STATUS_LINE[status_code]] + has_connection_close = False + + for name, value in headers: + if HEADER_RE.search(name): + raise RuntimeError("Invalid HTTP header name.") # pragma: full coverage + if HEADER_VALUE_RE.search(value): + raise RuntimeError("Invalid HTTP header value.") + + name = name.lower() + if name == b"content-length" and self.chunked_encoding is None: + self.expected_content_length = int(value.decode()) + self.chunked_encoding = False + elif name == b"transfer-encoding" and value.lower() == b"chunked": + self.expected_content_length = 0 + self.chunked_encoding = True + elif name == b"connection": + connection = [token.lower().strip() for token in value.split(b",")] + if b"close" in connection: + self.keep_alive = False + has_connection_close = True + content.extend([name, b": ", value, b"\r\n"]) + + if not self.keep_alive and not has_connection_close: + content.append(b"connection: close\r\n") + + if self.chunked_encoding is None and self.scope["method"] != "HEAD" and status_code not in (204, 304): + # Neither content-length nor transfer-encoding specified + self.chunked_encoding = True + content.append(b"transfer-encoding: chunked\r\n") + + content.append(b"\r\n") + self.transport.write(b"".join(content)) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + if self.scope["method"] == "HEAD": + self.expected_content_length = 0 + elif self.chunked_encoding: + if body: + content = [b"%x\r\n" % len(body), body, b"\r\n"] + else: + content = [] + if not more_body: + content.append(b"0\r\n\r\n") + self.transport.write(b"".join(content)) + else: + num_bytes = len(body) + if num_bytes > self.expected_content_length: + raise RuntimeError("Response content longer than Content-Length") + else: + self.expected_content_length -= num_bytes + self.transport.write(body) + + # Handle response completion + if not more_body: + if self.expected_content_length != 0: + raise RuntimeError("Response content shorter than Content-Length") + self.response_complete = True + self.message_event.set() + if not self.keep_alive: + self.transport.close() + self.on_response() + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + async def receive(self) -> ASGIReceiveEvent: + if self.waiting_for_100_continue and not self.transport.is_closing(): + self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n") + self.waiting_for_100_continue = False + + if not self.disconnected and not self.response_complete: + self.flow.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py new file mode 100644 index 0000000..3a7a0f9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py @@ -0,0 +1,521 @@ +from __future__ import annotations + +import asyncio +import contextvars +import logging +import sys +from collections.abc import Callable, Generator +from typing import Any, ClassVar, Literal +from urllib.parse import unquote + +import zttp +from zttp import Event + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + +# RFC 9113 section 8.2.2: connection-specific headers MUST NOT appear in +# HTTP/2 messages. zttp rejects them with LocalProtocolError, and an ASGI app +# or middleware tuned for HTTP/1.1 may still emit them, so strip them on the +# way out. +FORBIDDEN_HEADERS = frozenset({b"connection", b"keep-alive", b"proxy-connection", b"transfer-encoding", b"upgrade"}) + + +class ZttpH2Protocol(asyncio.Protocol): + alpn_protocols: ClassVar[list[str]] = ["h2"] + + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.conn: zttp.H2Connection = zttp.Connection(zttp.SERVER, protocol=zttp.HTTP2) + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: asyncio.TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Shared server state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + self.shutdown_requested = False + + # Per-stream state, keyed by HTTP/2 stream id + self.cycles: dict[int, RequestResponseCycle] = {} + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + self.conn.initiate_connection() + self.flush() + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP/2 connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP/2 connection lost", prefix) + + for cycle in self.cycles.values(): + if not cycle.response_complete: + cycle.disconnected = True + cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + + try: + self.conn.receive_data(data) + self.handle_events() + except zttp.RemoteProtocolError as exc: + msg = "Invalid HTTP/2 frame received: %s" + self.logger.warning(msg, exc) + self.flush() + self.transport.close() + return + + self.flush() + + # Frames that carry no stream-level event (SETTINGS, PING, + # WINDOW_UPDATE) cancelled the keep-alive timer above but never re-arm + # it, so re-arm now if the connection is idle. + if not self.cycles and self.timeout_keep_alive_task is None and not self.transport.is_closing(): + self.timeout_keep_alive_task = self.loop.call_later( + self.timeout_keep_alive, self.timeout_keep_alive_handler + ) + + def flush(self) -> None: + """Write out bytes zttp queued outside a cycle's send path - the + connection preface and DATA released by a WINDOW_UPDATE credit.""" + data = self.conn.data_to_send() + if data: + self.transport.write(data) + + def events(self) -> Generator[Event]: + """Yield every complete event currently available.""" + while True: + event = self.conn.next_event() + if event is zttp.NEED_DATA: + return + yield event + + def handle_events(self) -> None: + for event in self.events(): + if isinstance(event, zttp.Request): + self.handle_request(event) + elif isinstance(event, zttp.Data): + cycle = self.cycles.get(event.stream_id) + if cycle is None or cycle.response_complete: + continue + cycle.body += event.data + # Unreachable until zttp grows its 64 KiB inbound flow-control + # windows, which cap how much body can buffer per stream. + if len(cycle.body) > HIGH_WATER_LIMIT: # pragma: no cover + self.flow.pause_reading() + cycle.message_event.set() + elif isinstance(event, zttp.EndOfMessage): + cycle = self.cycles.get(event.stream_id) + if cycle is None or cycle.response_complete: + continue + cycle.more_body = False + cycle.message_event.set() + elif isinstance(event, zttp.RstStream): + self.handle_rst_stream(event) + elif isinstance(event, zttp.GoAway): + self.shutdown_requested = True + if not self.cycles: + self._close_connection() + # Settings, Ping and WindowUpdate need no action here: zttp tracks + # the send windows internally, and `flush` writes whatever bytes + # the new credit released. + + def handle_request(self, event: zttp.Request) -> None: + headers = ( + event.headers.to_list(lowercase_names=True) + if isinstance(event.headers, zttp.HeaderBlock) + else [(name.lower(), value) for name, value in event.headers] + ) + path = unquote(event.path.decode("ascii")) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + event.path + scope: HTTPScope = { + "type": "http", + "asgi": {"version": self.asgi_version, "spec_version": "2.3"}, + "http_version": "2", + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "method": event.method.decode("ascii"), + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": event.query, + "headers": headers, + "state": self.app_state.copy(), + } + + # Refuse new streams once a shutdown began, and handle 503 responses + # when 'limit_concurrency' is exceeded. + if self.shutdown_requested: + app = service_unavailable + elif self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + cycle = RequestResponseCycle( + scope=scope, + conn=self.conn, + stream=self.conn.stream(event.stream_id), + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + on_response=self.on_response_complete, + resume_reading=self.resume_reading_if_idle, + ) + self.cycles[event.stream_id] = cycle + + if self.config.reset_contextvars: + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, cycle.run_asgi(app)) + else: + task = self.loop.create_task(cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + def handle_rst_stream(self, event: zttp.RstStream) -> None: + cycle = self.cycles.pop(event.stream_id, None) + if cycle is None: + return + if not cycle.response_complete: + cycle.disconnected = True + cycle.message_event.set() + self.on_stream_closed() + + def resume_reading_if_idle(self) -> None: + """Resume reads only if no stream is over the body buffer high-water + mark; otherwise a slow stream's buffer would keep growing while a fast + peer ships frames on a sibling stream.""" + for cycle in self.cycles.values(): + if len(cycle.body) > HIGH_WATER_LIMIT: + return + self.flow.resume_reading() + + def on_response_complete(self, stream_id: int) -> None: + self.server_state.total_requests += 1 + self.cycles.pop(stream_id, None) + self.on_stream_closed() + + def on_stream_closed(self) -> None: + if self.transport.is_closing(): + return + + # The stream's body buffer is gone, so it can no longer hold back the + # read side of the transport. + self.resume_reading_if_idle() + + self._unset_keepalive_if_required() + + if not self.cycles: + if self.shutdown_requested: + self._close_connection() + return + self.timeout_keep_alive_task = self.loop.call_later( + self.timeout_keep_alive, self.timeout_keep_alive_handler + ) + + def _close_connection(self) -> None: + """Send GOAWAY and close the transport.""" + if self.transport.is_closing(): + return + self.conn.close() + self.flush() + self.transport.close() + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + + Closes immediately when idle; otherwise new streams are refused with + 503 and the connection closes once the last in-flight stream finishes. + """ + self.shutdown_requested = True + if not self.cycles: + self._close_connection() + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: no cover + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: no cover + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + self._close_connection() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + conn: zttp.H2Connection, + stream: zttp.Stream, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + on_response: Callable[[int], None], + resume_reading: Callable[[], None], + ) -> None: + self.scope = scope + self.conn = conn + self.stream = stream + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + self.resume_reading = resume_reading + + # Connection state + self.disconnected = False + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + self.bodyless = False + self.expected_content_length: int | None = None + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.abort_stream() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.abort_stream() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.abort_stream() + finally: + self.on_response(self.stream.stream_id) + self.on_response = lambda stream_id: None + + def abort_stream(self) -> None: + """Reset only this stream so sibling streams on the connection survive.""" + if self.transport.is_closing(): + return + self.stream.reset() + self.transport.write(self.conn.data_to_send()) + self.disconnected = True + + async def send_500_response(self) -> None: + response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": 500, + "headers": [(b"content-type", b"text/plain; charset=utf-8")], + } + await self.send(response_start_event) + response_body_event: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"Internal Server Error", + "more_body": False, + } + await self.send(response_body_event) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: no cover + + if self.disconnected: + return + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + + status = message["status"] + headers: list[tuple[bytes, bytes]] = [] + for name, value in list(self.default_headers) + list(message.get("headers", [])): + name = name.lower() + if name in FORBIDDEN_HEADERS: + continue + if name == b"te" and value.lower().strip() != b"trailers": + continue + if name == b"content-length": + self.expected_content_length = int(value.decode()) + headers.append((name, value)) + + self.bodyless = self.scope["method"] == "HEAD" or status in (204, 304) or status < 200 + if self.bodyless: + self.expected_content_length = None + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status, + ) + + # Write response headers + self.stream.send_response(status, headers) + self.transport.write(self.conn.data_to_send()) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + if self.bodyless: + body = b"" + elif self.expected_content_length is not None: + if len(body) > self.expected_content_length: + raise RuntimeError("Response content longer than Content-Length") + self.expected_content_length -= len(body) + if body: + self.stream.send_data(body) + self.transport.write(self.conn.data_to_send()) + + # Handle response completion + if not more_body: + if self.expected_content_length not in (None, 0): + raise RuntimeError("Response content shorter than Content-Length") + self.response_complete = True + self.message_event.set() + self.stream.end_message() + self.transport.write(self.conn.data_to_send()) + self.on_response(self.stream.stream_id) + self.on_response = lambda stream_id: None + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + async def receive(self) -> ASGIReceiveEvent: + if not self.disconnected and not self.response_complete: + self.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py new file mode 100644 index 0000000..b64f817 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py @@ -0,0 +1,537 @@ +from __future__ import annotations + +import asyncio +import contextvars +import logging +import sys +from collections.abc import Callable +from typing import Any, Literal +from urllib.parse import unquote + +import zttp + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + + +class ZttpProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.conn = zttp.Connection(zttp.SERVER) + self.ws_protocol_class = config.ws_protocol_class + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: asyncio.TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Shared server state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + + # Per-request state + self.scope: HTTPScope = None # type: ignore[assignment] + self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment] + self.cycle: RequestResponseCycle = None # type: ignore[assignment] + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix) + + if self.cycle and not self.cycle.response_complete: + self.cycle.disconnected = True + if self.cycle is not None: + self.cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def _should_upgrade_to_ws(self) -> bool: + if self.ws_protocol_class is None: + return False + return True + + def _unsupported_upgrade_warning(self) -> None: + self.logger.warning("Unsupported upgrade request.") + if not self._should_upgrade_to_ws(): + self.logger.warning( + "No supported WebSocket library detected. " + "Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." + ) + + def _should_upgrade(self) -> bool: + upgrade = self.conn.upgrade() + if upgrade is not None: + upgrade = upgrade.lower() + if upgrade == b"websocket" and self._should_upgrade_to_ws(): + return True + if upgrade is not None: + self._unsupported_upgrade_warning() + return False + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + try: + self.handle_events(self.conn.receive_event(data)) + except zttp.RemoteProtocolError: + self.handle_remote_protocol_error() + + def handle_events(self, event: zttp.Event) -> None: + while event is not zttp.NEED_DATA: + if isinstance(event, zttp.Request): + # Pipelined HTTP requests and WebSocket upgrades may be processed after the keep-alive timer is armed. + self._unset_keepalive_if_required() + + assert isinstance(event.headers, zttp.HeaderBlock) + self.headers = event.headers.to_list(lowercase_names=True) + path = event.path.decode("ascii") + if "%" in path: + path = unquote(path) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + event.path + self.scope = { + "type": "http", + "asgi": {"version": self.asgi_version, "spec_version": "2.3"}, + "http_version": event.http_version.decode("ascii"), + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "method": event.method.decode("ascii"), + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": event.query, + "headers": self.headers, + "state": self.app_state.copy(), + } + if self._should_upgrade(): + self.handle_websocket_upgrade(event) + return + + # Handle 503 responses when 'limit_concurrency' is exceeded. + if self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + self.cycle = RequestResponseCycle( + scope=self.scope, + conn=self.conn, + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + expect_100_continue=event.expect_continue, + on_response=self.on_response_complete, + ) + if event.end_stream: + self.cycle.more_body = False + self.cycle.message_event.set() + if self.config.reset_contextvars: + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app)) + else: + task = self.loop.create_task(self.cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + elif isinstance(event, zttp.Data): + if self.cycle is not None and not self.cycle.response_complete: + self.cycle.body += event.data + if len(self.cycle.body) > HIGH_WATER_LIMIT: + self.flow.pause_reading() + self.cycle.message_event.set() + + elif isinstance(event, zttp.EndOfMessage): + if self.cycle is not None: + self.cycle.more_body = False + self.cycle.message_event.set() + if self.cycle.response_complete: + self.conn.start_next_cycle() + + event = self.conn.next_event() + + def handle_remote_protocol_error(self) -> None: + msg = "Invalid HTTP request received." + self.logger.warning(msg) + self.send_400_response(msg) + + def handle_websocket_upgrade(self, event: zttp.Request) -> None: + if self.logger.level <= TRACE_LOG_LEVEL: # pragma: no cover + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix) + + self.connections.discard(self) + output = bytearray(event.method + b" " + event.target + b" HTTP/1.1\r\n") + for name, value in self.headers: + output += name + b": " + value + b"\r\n" + output += b"\r\n" + protocol = self.ws_protocol_class( # type: ignore[call-arg, misc] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + ) + protocol.connection_made(self.transport) + protocol.data_received(bytes(output)) + self.transport.set_protocol(protocol) + + def send_400_response(self, msg: str) -> None: + body = msg.encode("ascii") + headers: list[tuple[bytes, bytes]] = [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", str(len(body)).encode("ascii")), + (b"connection", b"close"), + ] + self.conn.send_response(400, headers) + self.conn.send_data(body) + self.conn.end_message() + self.transport.write(self.conn.data_to_send()) + self.transport.close() + + def on_response_complete(self) -> None: + self.server_state.total_requests += 1 + + if self.transport.is_closing(): + return + + # Set a short Keep-Alive timeout. + self._unset_keepalive_if_required() + + self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler) + + # Unpause data reads if needed. + self.flow.resume_reading() + + # Do not reset the parser until the complete request body has arrived. + # An application may respond before consuming it; any remaining body + # still belongs to the current request and must be discarded as such. + if self.cycle is not None and not self.cycle.more_body: + self.conn.start_next_cycle() + try: + self.handle_events(self.conn.next_event()) + except zttp.RemoteProtocolError: # pragma: no cover + self.handle_remote_protocol_error() + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + """ + if self.cycle is None or self.cycle.response_complete: + self.transport.close() + else: + self.cycle.keep_alive = False + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: no cover + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: no cover + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + if not self.transport.is_closing(): + self.transport.close() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + conn: zttp.H1Connection, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + expect_100_continue: bool, + on_response: Callable[..., None], + ) -> None: + self.scope = scope + self.conn = conn + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + + # Connection state + self.disconnected = False + self.keep_alive = True + self.waiting_for_100_continue = expect_100_continue + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + self.bodyless = False + self.chunked_encoding = False + self.expected_content_length = 0 + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.transport.close() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.transport.close() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.transport.close() + finally: + self.on_response = lambda: None + + async def send_500_response(self) -> None: + response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": 500, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + ], + } + await self.send(response_start_event) + response_body_event: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"Internal Server Error", + "more_body": False, + } + await self.send(response_body_event) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: no cover + + if self.disconnected: + return # pragma: no cover + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + self.waiting_for_100_continue = False + + status = message["status"] + headers = self.default_headers + list(message.get("headers", [])) + + bodyless = self.scope["method"] == "HEAD" or status in (204, 304) or status < 200 + + # RFC 9112 §6.1 forbids Transfer-Encoding on 1xx and 204 responses, and + # zttp refuses to serialize one there, so drop it instead of erroring. + # HEAD and 304 may keep it: it describes the body a GET would return. + if status < 200 or status == 204: + headers = [(name, value) for name, value in headers if name.lower() != b"transfer-encoding"] + + has_content_length = False + has_transfer_encoding = False + for name, value in headers: + name = name.lower() + if name == b"content-length": + has_content_length = True + self.expected_content_length = int(value.decode()) + elif name == b"transfer-encoding": + # zttp only accepts a sole, final `chunked` coding - anything + # else raises, like h11 - and it frames the body itself, so + # there is no Content-Length accounting to do here. + has_transfer_encoding = True + self.chunked_encoding = True + + # A response carrying both Content-Length and Transfer-Encoding is + # a framing conflict that zttp rejects, so drop the Content-Length. + if has_transfer_encoding and has_content_length: + headers = [(name, value) for name, value in headers if name.lower() != b"content-length"] + has_content_length = False + self.expected_content_length = 0 + + # zttp refuses to frame the body unless the response declares + # Content-Length or Transfer-Encoding, so add chunked encoding + # ourselves when the application provides neither. + if not bodyless and not has_transfer_encoding and not has_content_length: + self.chunked_encoding = True + headers = headers + [(b"transfer-encoding", b"chunked")] + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status, + ) + + # Write response status line and headers + self.bodyless = bodyless + self.conn.send_response(status, headers) + self.transport.write(self.conn.data_to_send()) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + if self.bodyless: + self.expected_content_length = 0 + elif self.chunked_encoding: + if body: + self.conn.send_data(body) + self.transport.write(self.conn.data_to_send()) + else: + if len(body) > self.expected_content_length: + raise RuntimeError("Response content longer than Content-Length") + self.expected_content_length -= len(body) + if body: + self.conn.send_data(body) + self.transport.write(self.conn.data_to_send()) + + # Handle response completion + if not more_body: + if self.expected_content_length != 0: + raise RuntimeError("Response content shorter than Content-Length") + self.response_complete = True + self.message_event.set() + self.conn.end_message() + self.transport.write(self.conn.data_to_send()) + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + if self.response_complete: + # `should_close()` covers both directions: the request's `Connection: close` + # or HTTP/1.0 default, and a `close` the response we just wrote declared. + if not self.keep_alive or self.conn.should_close(): + self.transport.close() + self.on_response() + + async def receive(self) -> ASGIReceiveEvent: + if self.waiting_for_100_continue and not self.transport.is_closing(): + self.conn.send_informational(100) + self.transport.write(self.conn.data_to_send()) + self.waiting_for_100_continue = False + + if not self.disconnected and not self.response_complete: + self.flow.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py new file mode 100644 index 0000000..a9a8906 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import asyncio +import socket +import urllib.parse + +from uvicorn._types import WWWScope + + +class ClientDisconnected(OSError): ... + + +def get_remote_addr(transport: asyncio.Transport) -> tuple[str, int] | None: + socket_info: socket.socket | None = transport.get_extra_info("socket") + if socket_info is not None: + try: + info = socket_info.getpeername() + return (str(info[0]), int(info[1])) if isinstance(info, tuple) else None + except OSError: # pragma: no cover + # This case appears to inconsistently occur with uvloop + # bound to a unix domain socket. + return None + + info = transport.get_extra_info("peername") + if info is not None and isinstance(info, list | tuple) and len(info) == 2: + return (str(info[0]), int(info[1])) + return None + + +def get_local_addr(transport: asyncio.Transport) -> tuple[str, int | None] | None: + socket_info: socket.socket | None = transport.get_extra_info("socket") + if socket_info is not None: + info = socket_info.getsockname() + if isinstance(info, tuple): + return (str(info[0]), int(info[1])) + if isinstance(info, str): + return (info, None) + return None + info = transport.get_extra_info("sockname") + if info is not None and isinstance(info, list | tuple) and len(info) == 2: + return (str(info[0]), int(info[1])) + if isinstance(info, str): + return (info, None) + return None + + +def is_ssl(transport: asyncio.Transport) -> bool: + return bool(transport.get_extra_info("sslcontext")) + + +def get_client_addr(scope: WWWScope) -> str: + client = scope.get("client") + if not client: + return "" + return "%s:%d" % client + + +def get_path_with_query_string(scope: WWWScope) -> str: + path_with_query_string = urllib.parse.quote(scope["path"]) + if scope["query_string"]: + path_with_query_string = "{}?{}".format(path_with_query_string, scope["query_string"].decode("ascii")) + return path_with_query_string diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py new file mode 100644 index 0000000..23db3d0 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + +AutoWebSocketsProtocol: Callable[..., asyncio.Protocol] | None +try: + import websockets # noqa +except ImportError: # pragma: no cover + try: + import wsproto # noqa + except ImportError: + AutoWebSocketsProtocol = None + else: + from uvicorn.protocols.websockets.wsproto_impl import WSProtocol + + AutoWebSocketsProtocol = WSProtocol +else: + from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol + + AutoWebSocketsProtocol = WebSocketsSansIOProtocol diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py new file mode 100644 index 0000000..8206a58 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py @@ -0,0 +1,390 @@ +from __future__ import annotations + +import asyncio +import http +import logging +import warnings +from collections.abc import Sequence +from typing import Any, Literal, cast +from urllib.parse import unquote + +import websockets +import websockets.legacy.handshake +from websockets import __version__ as websockets_version +from websockets.datastructures import Headers +from websockets.exceptions import ConnectionClosed +from websockets.extensions.base import ServerExtensionFactory +from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory +from websockets.legacy.server import HTTPResponse, WebSocketServerProtocol +from websockets.typing import Subprotocol + +from uvicorn._types import ( + ASGI3Application, + ASGISendEvent, + WebSocketConnectEvent, + WebSocketDisconnectEvent, + WebSocketReceiveEvent, + WebSocketScope, +) +from uvicorn.config import Config, UvicornDeprecationWarning +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + +warnings.warn( + "The `websockets` implementation is deprecated, and `--ws websockets` will point at the " + "`websockets-sansio` implementation in a future release. " + "Use `--ws websockets-sansio` or `--ws auto` to switch now.", + UvicornDeprecationWarning, +) + + +class Server: + closing = False + + def register(self, ws: WebSocketServerProtocol) -> None: + pass + + def unregister(self, ws: WebSocketServerProtocol) -> None: + pass + + def is_serving(self) -> bool: + return not self.closing + + +class WebSocketProtocol(WebSocketServerProtocol): + extra_headers: list[tuple[str, str]] + logger: logging.Logger | logging.LoggerAdapter[Any] + + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ): + if not config.loaded: + config.load() + + self.config = config + self.app = cast(ASGI3Application, config.loaded_app) + self.loop = _loop or asyncio.get_event_loop() + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # Connection events + self.scope: WebSocketScope + self.handshake_started_event = asyncio.Event() + self.handshake_completed_event = asyncio.Event() + self.closed_event = asyncio.Event() + self.initial_response: HTTPResponse | None = None + self.connect_sent = False + self.lost_connection_before_handshake = False + self.accepted_subprotocol: Subprotocol | None = None + + self.ws_server: Server = Server() # type: ignore[assignment] + + extensions: list[ServerExtensionFactory] = [] + if self.config.ws_per_message_deflate: + extensions.append(ServerPerMessageDeflateFactory()) + + super().__init__( + ws_handler=self.ws_handler, + ws_server=self.ws_server, # type: ignore[arg-type] + max_size=self.config.ws_max_size, + max_queue=self.config.ws_max_queue, + ping_interval=self.config.ws_ping_interval, + ping_timeout=self.config.ws_ping_timeout, + extensions=extensions, + logger=logging.getLogger("uvicorn.error"), + ) + self.server_header = None + self.extra_headers = [ + (name.decode("latin-1"), value.decode("latin-1")) for name, value in server_state.default_headers + ] + + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.isEnabledFor(TRACE_LOG_LEVEL): + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + super().connection_made(transport) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.remove(self) + + if self.logger.isEnabledFor(TRACE_LOG_LEVEL): + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.lost_connection_before_handshake = not self.handshake_completed_event.is_set() + self.handshake_completed_event.set() + super().connection_lost(exc) + if exc is None: + self.transport.close() + + def shutdown(self) -> None: + self.ws_server.closing = True + if self.handshake_completed_event.is_set(): + self.fail_connection(1012) + else: + self.send_500_response() + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + async def process_request(self, path: str, request_headers: Headers) -> HTTPResponse | None: + """ + This hook is called to determine if the websocket should return + an HTTP response and close. + + Our behavior here is to start the ASGI application, and then wait + for either `accept` or `close` in order to determine if we should + close the connection. + """ + path_portion, _, query_string = path.partition("?") + + websockets.legacy.handshake.check_request(request_headers) + + subprotocols: list[str] = [] + for header in request_headers.get_all("Sec-WebSocket-Protocol"): + subprotocols.extend([token.strip() for token in header.split(",")]) + + # websockets 17.0 documents that non-ASCII header values are encoded + # with ISO-8859-1. Earlier versions didn't document the behavior but + # we can see in the code that it used surrogate escape encoding. + # Move the pragma: no cover to the else: branch when 17.0 is released. + if websockets_version >= "17.0": # pragma: no cover + asgi_headers = [ + (name.encode("ascii"), value.encode("latin-1")) for name, value in request_headers.raw_items() + ] + else: + asgi_headers = [ + (name.encode("ascii"), value.encode("ascii", errors="surrogateescape")) + for name, value in request_headers.raw_items() + ] + path = unquote(path_portion) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + path_portion.encode("ascii") + + self.scope = { + "type": "websocket", + "asgi": {"version": self.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string.encode("ascii"), + "headers": asgi_headers, + "subprotocols": subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + await self.handshake_started_event.wait() + return self.initial_response + + def process_subprotocol( + self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None + ) -> Subprotocol | None: + """ + We override the standard 'process_subprotocol' behavior here so that + we return whatever subprotocol is sent in the 'accept' message. + """ + return self.accepted_subprotocol + + def send_500_response(self) -> None: + msg = b"Internal Server Error" + content = [ + b"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=utf-8\r\n", + b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n", + b"connection: close\r\n", + b"\r\n", + msg, + ] + self.transport.write(b"".join(content)) + # Allow handler task to terminate cleanly, as websockets doesn't cancel it by + # itself (see https://github.com/Kludex/uvicorn/issues/920) + self.handshake_started_event.set() + + async def ws_handler(self, protocol: WebSocketServerProtocol, path: str) -> Any: # type: ignore[override] + """ + This is the main handler function for the 'websockets' implementation + to call into. We just wait for close then return, and instead allow + 'send' and 'receive' events to drive the flow. + """ + self.handshake_completed_event.set() + await self.wait_closed() + + async def run_asgi(self) -> None: + """ + Wrapper around the ASGI callable, handling exceptions and unexpected + termination states. + """ + try: + result = await self.app(self.scope, self.asgi_receive, self.asgi_send) # type: ignore[func-returns-value] + except ClientDisconnected: # pragma: full coverage + self.closed_event.set() + except BaseException: + self.closed_event.set() + self.logger.exception("Exception in ASGI application\n") + if not self.handshake_started_event.is_set(): + self.send_500_response() + else: + await self.handshake_completed_event.wait() + else: + self.closed_event.set() + if not self.handshake_started_event.is_set(): + self.logger.error("ASGI callable returned without sending handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + await self.handshake_completed_event.wait() + self.transport.close() + + async def asgi_send(self, message: ASGISendEvent) -> None: + if not self.handshake_started_event.is_set(): + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.initial_response = None + self.accepted_subprotocol = cast(Subprotocol | None, message.get("subprotocol")) + if "headers" in message: + self.extra_headers.extend( + # ASGI spec requires bytes + # But for compatibility we need to convert it to strings + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in message["headers"] + ) + self.handshake_started_event.set() + + elif message["type"] == "websocket.close": + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.initial_response = (http.HTTPStatus.FORBIDDEN, [], b"") + self.handshake_started_event.set() + self.closed_event.set() + + elif message["type"] == "websocket.http.response.start": + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + # websockets requires the status to be an enum. look it up. + status = http.HTTPStatus(message["status"]) + headers = [ + (name.decode("latin-1"), value.decode("latin-1")) for name, value in message.get("headers", []) + ] + self.initial_response = (status, headers, b"") + self.handshake_started_event.set() + + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close', " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.closed_event.is_set() and self.initial_response is None: + await self.handshake_completed_event.wait() + + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + data = text_data if bytes_data is None else bytes_data + await self.send(data) # type: ignore[arg-type] + + elif message["type"] == "websocket.close": + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + await self.close(code, reason) + self.closed_event.set() + + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except ConnectionClosed as exc: + raise ClientDisconnected from exc + + elif self.initial_response is not None: + if message["type"] == "websocket.http.response.body": + body = self.initial_response[2] + message["body"] + self.initial_response = self.initial_response[:2] + (body,) + if not message.get("more_body", False): + self.closed_event.set() + else: + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError( + f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close' " + "or response already completed." + ) + + async def asgi_receive(self) -> WebSocketDisconnectEvent | WebSocketConnectEvent | WebSocketReceiveEvent: + if not self.connect_sent: + self.connect_sent = True + return {"type": "websocket.connect"} + + await self.handshake_completed_event.wait() + + if self.lost_connection_before_handshake: + # If the handshake failed or the app closed before handshake completion, + # use 1006 Abnormal Closure. + return {"type": "websocket.disconnect", "code": 1006} + + if self.closed_event.is_set(): + return {"type": "websocket.disconnect", "code": 1005} + + try: + data = await self.recv() + except ConnectionClosed: + self.closed_event.set() + if self.ws_server.closing: + return {"type": "websocket.disconnect", "code": 1012} + return {"type": "websocket.disconnect", "code": self.close_code or 1005, "reason": self.close_reason} + + if isinstance(data, str): + return {"type": "websocket.receive", "text": data} + return {"type": "websocket.receive", "bytes": data} diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py new file mode 100644 index 0000000..a444433 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py @@ -0,0 +1,556 @@ +from __future__ import annotations + +import asyncio +import email.utils +import logging +import random +import struct +import sys +from asyncio import TimerHandle +from asyncio.transports import BaseTransport, Transport +from http import HTTPStatus +from typing import Any, Literal, cast +from urllib.parse import unquote + +from websockets import __version__ as websockets_version +from websockets.datastructures import Headers +from websockets.exceptions import InvalidState +from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory +from websockets.frames import Frame, Opcode +from websockets.http11 import Request, Response +from websockets.server import ServerProtocol + +from uvicorn._types import ( + ASGIReceiveEvent, + ASGISendEvent, + WebSocketScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + +if sys.version_info >= (3, 11): # pragma: no cover + from typing import assert_never +else: # pragma: no cover + from typing_extensions import assert_never + + +def _get_status_phrase(status_code: int) -> str: + try: + return HTTPStatus(status_code).phrase + except ValueError: + return "" + + +STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)} + + +class WebSocketsSansIOProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() # pragma: no cover + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # WebSocket state + self.queue: asyncio.Queue[ASGIReceiveEvent] = asyncio.Queue() + self.handshake_initiated = False + self.handshake_complete = False + self.close_sent = False + self.disconnected = False + self.close_timer: TimerHandle | None = None + self.close_timeout = 10.0 + self.initial_response: tuple[int, list[tuple[str, str]], bytes] | None = None + + extensions = [] + if self.config.ws_per_message_deflate: + extensions = [ + ServerPerMessageDeflateFactory( + server_max_window_bits=12, + client_max_window_bits=12, + compress_settings={"memLevel": 5}, + ) + ] + self.conn = ServerProtocol( + extensions=extensions, + max_size=self.config.ws_max_size, + logger=logging.getLogger("uvicorn.error"), + ) + + self.read_paused = False + self.writable = asyncio.Event() + self.writable.set() + + # Keepalive state + self.ping_interval = config.ws_ping_interval + self.ping_timeout = config.ws_ping_timeout + self.ping_timer: TimerHandle | None = None + self.pong_timer: TimerHandle | None = None + self.pending_ping_payload: bytes | None = None + self.ping_sent_at: float = 0.0 + self.last_ping_rtt: float = 0.0 + + # Incoming message state + self.frames: list[bytes] = [] + self.curr_msg_data_type: Literal["text", "bytes"] = "bytes" + + def connection_made(self, transport: BaseTransport) -> None: + """Called when a connection is made.""" + transport = cast(Transport, transport) + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.stop_keepalive() + code = 1005 if self.handshake_complete else 1006 + self.queue.put_nowait({"type": "websocket.disconnect", "code": code}) + self.connections.remove(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.handshake_complete = True + self.disconnected = True + # Unblock any send() awaiting writable: asyncio never calls resume_writing() on a + # transport that is lost while paused, and the buffer will never drain now. + self.writable.set() + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + if exc is None: + self.transport.close() + + def eof_received(self) -> None: + pass + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.writable.clear() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.writable.set() # pragma: full coverage + + def shutdown(self) -> None: + self.stop_keepalive() + if self.close_sent: + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + self.transport.close() + return + if self.handshake_complete: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012}) + self.conn.send_close(1012) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + else: + self.send_500_response() + self.transport.close() + + def data_received(self, data: bytes) -> None: + self.conn.receive_data(data) + if self.conn.parser_exc is not None: # pragma: no cover + self.handle_parser_exception() + return + self.handle_events() + + def handle_events(self) -> None: + for event in self.conn.events_received(): + if isinstance(event, Request): + self.handle_connect(event) + if isinstance(event, Frame): + if event.opcode == Opcode.CONT: + self.handle_cont(event) # pragma: no cover + elif event.opcode == Opcode.TEXT: + self.handle_text(event) + elif event.opcode == Opcode.BINARY: + self.handle_bytes(event) + elif event.opcode == Opcode.PING: + self.handle_ping() + elif event.opcode == Opcode.PONG: + self.handle_pong(event) + elif event.opcode == Opcode.CLOSE: + self.handle_close(event) + else: + assert_never(event.opcode) # pragma: no cover + + # Event handlers + + def handle_connect(self, event: Request) -> None: + self.request = event + self.response = self.conn.accept(event) + self.handshake_initiated = True + if self.response.status_code != 101: + self.handshake_complete = True + self.close_sent = True + self.conn.send_response(self.response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.transport.close() + return + + # websockets 17.0 documents that non-ASCII header values are encoded + # with ISO-8859-1. Earlier versions didn't document the behavior but + # we can see in the code that it used surrogate escape encoding. + # Move the pragma: no cover to the else: branch when 17.0 is released. + if websockets_version >= "17.0": # pragma: no cover + headers = [(key.encode("ascii"), value.encode("latin-1")) for key, value in event.headers.raw_items()] + else: + headers = [ + (key.encode("ascii"), value.encode("ascii", errors="surrogateescape")) + for key, value in event.headers.raw_items() + ] + raw_path, _, query_string = event.path.partition("?") + subprotocols: list[str] = [] + for header in event.headers.get_all("Sec-WebSocket-Protocol"): + subprotocols.extend([token.strip() for token in header.split(",")]) + self.scope: WebSocketScope = { + "type": "websocket", + "asgi": {"version": self.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": self.root_path + unquote(raw_path), + "raw_path": self.root_path.encode("ascii") + raw_path.encode("ascii"), + "query_string": query_string.encode("ascii"), + "headers": headers, + "subprotocols": subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + self.queue.put_nowait({"type": "websocket.connect"}) + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + + def handle_cont(self, event: Frame) -> None: + self.frames.append(event.data) # type: ignore[arg-type] + if event.fin: + self.send_receive_event_to_app() + + def handle_text(self, event: Frame) -> None: + self.curr_msg_data_type = "text" + self.frames = [event.data] # type: ignore[list-item] + if event.fin: + self.send_receive_event_to_app() + + def handle_bytes(self, event: Frame) -> None: + self.curr_msg_data_type = "bytes" + self.frames = [event.data] # type: ignore[list-item] + if event.fin: + self.send_receive_event_to_app() + + def send_receive_event_to_app(self) -> None: + data = self.frames[0] if len(self.frames) == 1 else b"".join(self.frames) + self.frames = [] + if self.close_sent: + # The app is past `websocket.close`: discard the message rather than queueing + # it, so reads stay active until the peer's close reply arrives. + return + if self.curr_msg_data_type == "text": + try: + self.queue.put_nowait({"type": "websocket.receive", "text": data.decode()}) + except UnicodeDecodeError: # pragma: no cover + self.logger.exception("Invalid UTF-8 sequence received from client.") + self.conn.send_close(1007) + self.handle_parser_exception() + return + else: + self.queue.put_nowait({"type": "websocket.receive", "bytes": data}) + if not self.read_paused: + self.read_paused = True + self.transport.pause_reading() + + def handle_ping(self) -> None: + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + def handle_pong(self, event: Frame) -> None: + # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight + if self.pending_ping_payload is None or bytes(event.data) != self.pending_ping_payload: + return # pragma: no cover + + self.last_ping_rtt = self.loop.time() - self.ping_sent_at + self.pending_ping_payload = None + # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is + # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is + # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here. + if self.pong_timer is not None: + self.pong_timer.cancel() + self.pong_timer = None + self.schedule_ping() + + def start_keepalive(self) -> None: + if self.ping_interval is not None and self.ping_interval > 0: + self.schedule_ping() + + def stop_keepalive(self) -> None: + if self.ping_timer is not None: + self.ping_timer.cancel() + self.ping_timer = None + if self.pong_timer is not None: # pragma: no cover + self.pong_timer.cancel() + self.pong_timer = None + self.pending_ping_payload = None + + def schedule_ping(self) -> None: + assert self.ping_interval is not None + delay = max(0.0, self.ping_interval - self.last_ping_rtt) + self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping) + + def send_keepalive_ping(self) -> None: + self.ping_timer = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs. + # See https://github.com/python-websockets/websockets/blob/4d229bf9f583d593aa103287aee0a77c9fbc3a79/src/websockets/asyncio/connection.py#L624 + self.pending_ping_payload = struct.pack("!I", random.getrandbits(32)) + self.ping_sent_at = self.loop.time() + self.conn.send_ping(self.pending_ping_payload) + self.transport.write(b"".join(self.conn.data_to_send())) + if self.ping_timeout is not None: + self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout) + else: # pragma: no cover + self.schedule_ping() + + def keepalive_timeout(self) -> None: + self.pong_timer = None + self.pending_ping_payload = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix) + self.conn.fail(1011, "keepalive ping timeout") + self.transport.write(b"".join(self.conn.data_to_send())) + self.close_sent = True + self.transport.close() + + def handle_close(self, event: Frame) -> None: + if self.close_sent: + # The peer echoed our close frame: the closing handshake is complete. + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + self.transport.close() + return + if not self.transport.is_closing(): + assert self.conn.close_rcvd is not None + code = self.conn.close_rcvd.code + reason = self.conn.close_rcvd.reason + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.transport.close() + + def handle_parser_exception(self) -> None: # pragma: no cover + assert self.conn.close_sent is not None + code = self.conn.close_sent.code + reason = self.conn.close_sent.reason + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.close_sent = True + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + async def run_asgi(self) -> None: + try: + result = await self.app(self.scope, self.receive, self.send) + except ClientDisconnected: + pass # pragma: full coverage + except BaseException: + self.logger.exception("Exception in ASGI application\n") + self.send_500_response() + else: + if not self.handshake_complete: + self.logger.error("ASGI callable returned without completing handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + if self.close_timer is None: + self.transport.close() + + def send_500_response(self) -> None: + if self.initial_response or self.handshake_complete: + return + response = self.conn.reject(500, "Internal Server Error") + self.conn.send_response(response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + async def send(self, message: ASGISendEvent) -> None: + await self.writable.wait() + + if self.disconnected: + raise ClientDisconnected() + + if not self.handshake_complete and self.initial_response is None: + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + headers = [ + (name.decode("latin-1").lower(), value.decode("latin-1")) + for name, value in (self.default_headers + list(message.get("headers", []))) + ] + accepted_subprotocol = message.get("subprotocol") + if accepted_subprotocol: + headers.append(("Sec-WebSocket-Protocol", accepted_subprotocol)) + del self.response.headers["Date"] + self.response.headers.update(headers) + + if not self.transport.is_closing(): + self.handshake_complete = True + self.conn.send_response(self.response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.start_keepalive() + + elif message["type"] == "websocket.close": + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + response = self.conn.reject(HTTPStatus.FORBIDDEN, "") + self.conn.send_response(response) + output = self.conn.data_to_send() + self.close_sent = True + self.handshake_complete = True + self.transport.write(b"".join(output)) + self.transport.close() + elif message["type"] == "websocket.http.response.start" and self.initial_response is None: + if not (100 <= message["status"] < 600): + raise RuntimeError("Invalid HTTP status code '%d' in response." % message["status"]) + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + headers = [ + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in list(message.get("headers", [])) + ] + self.initial_response = (message["status"], headers, b"") + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close' " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.close_sent and self.initial_response is None: + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + if bytes_data is not None: + self.conn.send_binary(bytes_data) + elif text_data is not None: + self.conn.send_text(text_data.encode()) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + elif message["type"] == "websocket.close": + if not self.transport.is_closing(): + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + self.conn.send_close(code, reason) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.close_sent = True + if self.read_paused: + self.read_paused = False + self.transport.resume_reading() + self.close_timer = self.loop.call_later(self.close_timeout, self.transport.close) + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except InvalidState: + raise ClientDisconnected() + elif self.initial_response is not None: + if message["type"] == "websocket.http.response.body": + body = self.initial_response[2] + message["body"] + self.initial_response = self.initial_response[:2] + (body,) + if not message.get("more_body", False): + status_code = self.initial_response[0] + response_headers = Headers(self.initial_response[1]) + response_headers.setdefault("Date", email.utils.formatdate(usegmt=True)) + response_headers.setdefault("Connection", "close") + response_headers.setdefault("Content-Length", str(len(body))) + response_headers.setdefault("Content-Type", "text/plain; charset=utf-8") + response = Response(status_code, STATUS_PHRASES[status_code], response_headers, body) + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.conn.send_response(response) + output = self.conn.data_to_send() + self.close_sent = True + self.transport.write(b"".join(output)) + self.transport.close() + else: # pragma: no cover + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.") + + async def receive(self) -> ASGIReceiveEvent: + message = await self.queue.get() + if self.read_paused and self.queue.empty(): + self.read_paused = False + self.transport.resume_reading() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py new file mode 100644 index 0000000..086abfd --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py @@ -0,0 +1,500 @@ +from __future__ import annotations + +import asyncio +import logging +import random +import struct +from asyncio import TimerHandle +from io import BytesIO, StringIO +from typing import Any, Literal, cast +from urllib.parse import unquote + +import wsproto +from wsproto import ConnectionType, events +from wsproto.connection import ConnectionState +from wsproto.extensions import Extension, PerMessageDeflate +from wsproto.utilities import LocalProtocolError, RemoteProtocolError + +from uvicorn._types import ( + ASGI3Application, + ASGISendEvent, + WebSocketEvent, + WebSocketReceiveEvent, + WebSocketScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + + +class FrameTooLargeError(Exception): + """Raised when accumulated websocket message bytes exceed `ws_max_size`.""" + + +class WebsocketBuffer: + def __init__(self, max_length: int) -> None: + self.value: BytesIO | StringIO | None = None + self.length = 0 + self.max_length = max_length + + def extend(self, event: events.TextMessage | events.BytesMessage) -> None: + if self.value is None: + self.value = StringIO() if isinstance(event, events.TextMessage) else BytesIO() + self.value.write(event.data) # type: ignore[arg-type] + # `ws_max_size` is a byte budget, so count UTF-8 bytes for text. + self.length += len(event.data.encode()) if isinstance(event, events.TextMessage) else len(event.data) + if self.length > self.max_length: + raise FrameTooLargeError + + def clear(self) -> None: + self.value = None + self.length = 0 + + def to_message(self) -> WebSocketReceiveEvent: + if isinstance(self.value, StringIO): + return {"type": "websocket.receive", "text": self.value.getvalue()} + assert isinstance(self.value, BytesIO) + return {"type": "websocket.receive", "bytes": self.value.getvalue()} + + +class WSProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() # pragma: full coverage + + self.config = config + self.app = cast(ASGI3Application, config.loaded_app) + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.root_path = config.root_path + self.asgi_version = config.asgi_version + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # WebSocket state + self.queue: asyncio.Queue[WebSocketEvent] = asyncio.Queue() + self.handshake_complete = False + self.close_sent = False + self.disconnected = False + self.close_timer: TimerHandle | None = None + self.close_timeout = 10.0 + + # Rejection state + self.response_started = False + + self.conn = wsproto.WSConnection(connection_type=ConnectionType.SERVER) + + self.read_paused = False + self.writable = asyncio.Event() + self.writable.set() + + # Keepalive state + self.ping_interval = config.ws_ping_interval + self.ping_timeout = config.ws_ping_timeout + self.ping_timer: TimerHandle | None = None + self.pong_timer: TimerHandle | None = None + self.pending_ping_payload: bytes | None = None + self.ping_sent_at: float = 0.0 + self.last_ping_rtt: float = 0.0 + + # Buffer + self.buffer = WebsocketBuffer(self.config.ws_max_size) + + # Protocol interface + + def connection_made(self, transport: asyncio.Transport) -> None: # type: ignore[override] + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.stop_keepalive() + code = 1005 if self.handshake_complete else 1006 + self.queue.put_nowait({"type": "websocket.disconnect", "code": code}) + self.connections.remove(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.handshake_complete = True + self.disconnected = True + # asyncio never calls resume_writing() when a paused transport is lost. + self.writable.set() + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + if exc is None: + self.transport.close() + + def eof_received(self) -> None: + pass + + def data_received(self, data: bytes) -> None: + try: + self.conn.receive_data(data) + except RemoteProtocolError as err: + # TODO: Remove `type: ignore` when wsproto fixes the type annotation. + self.transport.write(self.conn.send(err.event_hint)) # type: ignore[arg-type] # noqa: E501 + self.transport.close() + except LocalProtocolError: + # Data received after the close handshake completed, e.g. a pong the client + # sent while the server's close was in flight. + self.transport.close() + else: + self.handle_events() + + def handle_events(self) -> None: + for event in self.conn.events(): + if self.close_sent: + if isinstance(event, events.CloseConnection): + self.handle_close(event) + continue + if isinstance(event, events.Request): + self.handle_connect(event) + elif isinstance(event, (events.TextMessage, events.BytesMessage)): + self.handle_message(event) + elif isinstance(event, events.CloseConnection): + self.handle_close(event) + elif isinstance(event, events.Ping): + self.handle_ping(event) + elif isinstance(event, events.Pong): + self.handle_pong(event) + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.writable.clear() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.writable.set() # pragma: full coverage + + def shutdown(self) -> None: + self.stop_keepalive() + if self.close_sent: + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + self.transport.close() + return + if self.handshake_complete: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012}) + output = self.conn.send(wsproto.events.CloseConnection(code=1012)) + self.transport.write(output) + else: + self.send_500_response() + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + # Event handlers + + def handle_connect(self, event: events.Request) -> None: + headers = [(b"host", event.host.encode())] + headers += [(key.lower(), value) for key, value in event.extra_headers] + raw_path, _, query_string = event.target.partition("?") + path = unquote(raw_path) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path.encode("ascii") + self.scope: WebSocketScope = { + "type": "websocket", + "asgi": {"version": self.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string.encode("ascii"), + "headers": headers, + "subprotocols": event.subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + self.queue.put_nowait({"type": "websocket.connect"}) + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + + def handle_message(self, event: events.TextMessage | events.BytesMessage) -> None: + try: + self.buffer.extend(event) + except FrameTooLargeError: + self.close_sent = True + reason = f"Message exceeds the maximum size ({self.config.ws_max_size} bytes)" + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1009, "reason": reason}) + if not self.transport.is_closing(): + self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1009, reason=reason))) + self.transport.close() + return + if event.message_finished: + self.queue.put_nowait(self.buffer.to_message()) + self.buffer.clear() + if not self.read_paused: + self.read_paused = True + self.transport.pause_reading() + + def handle_close(self, event: events.CloseConnection) -> None: + if self.close_sent: + # The peer echoed our close frame: the closing handshake is complete. + if self.close_timer is not None: + self.close_timer.cancel() + self.close_timer = None + self.transport.close() + return + if self.conn.state == ConnectionState.REMOTE_CLOSING: + self.transport.write(self.conn.send(event.response())) + self.queue.put_nowait({"type": "websocket.disconnect", "code": event.code, "reason": event.reason}) + self.transport.close() + + def handle_ping(self, event: events.Ping) -> None: + self.transport.write(self.conn.send(event.response())) + + def handle_pong(self, event: events.Pong) -> None: + # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight. + if self.pending_ping_payload is None or bytes(event.payload) != self.pending_ping_payload: + return # pragma: no cover + + self.last_ping_rtt = self.loop.time() - self.ping_sent_at + self.pending_ping_payload = None + # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is + # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is + # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here. + if self.pong_timer is not None: + self.pong_timer.cancel() + self.pong_timer = None + self.schedule_ping() + + def start_keepalive(self) -> None: + if self.ping_interval is not None and self.ping_interval > 0: + self.schedule_ping() + + def stop_keepalive(self) -> None: + if self.ping_timer is not None: + self.ping_timer.cancel() + self.ping_timer = None + if self.pong_timer is not None: # pragma: no cover + self.pong_timer.cancel() + self.pong_timer = None + self.pending_ping_payload = None + + def schedule_ping(self) -> None: + assert self.ping_interval is not None + delay = max(0.0, self.ping_interval - self.last_ping_rtt) + self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping) + + def send_keepalive_ping(self) -> None: + self.ping_timer = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs. + self.pending_ping_payload = struct.pack("!I", random.getrandbits(32)) + self.ping_sent_at = self.loop.time() + self.transport.write(self.conn.send(wsproto.events.Ping(payload=self.pending_ping_payload))) + if self.ping_timeout is not None: + self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout) + else: # pragma: no cover + self.schedule_ping() + + def keepalive_timeout(self) -> None: + self.pong_timer = None + self.pending_ping_payload = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix) + reason = "keepalive ping timeout" + self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1011, reason=reason))) + self.close_sent = True + self.transport.close() + + def send_500_response(self) -> None: + if self.response_started or self.handshake_complete: + return # we cannot send responses anymore + headers: list[tuple[bytes, bytes]] = [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + (b"content-length", b"21"), + ] + output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True)) + output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error")) + self.transport.write(output) + + async def run_asgi(self) -> None: + try: + result = await self.app(self.scope, self.receive, self.send) # type: ignore[func-returns-value] + except ClientDisconnected: + pass # pragma: full coverage + except BaseException: + self.logger.exception("Exception in ASGI application\n") + self.send_500_response() + else: + if not self.handshake_complete: + self.logger.error("ASGI callable returned without completing handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + if self.close_timer is None: + self.transport.close() + + async def send(self, message: ASGISendEvent) -> None: + await self.writable.wait() + + if self.disconnected: + raise ClientDisconnected() + + if not self.handshake_complete: + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + subprotocol = message.get("subprotocol") + extra_headers = self.default_headers + list(message.get("headers", [])) + extensions: list[Extension] = [] + if self.config.ws_per_message_deflate: + extensions.append(PerMessageDeflate()) + if not self.transport.is_closing(): + self.handshake_complete = True + output = self.conn.send( + wsproto.events.AcceptConnection( + subprotocol=subprotocol, + extensions=extensions, + extra_headers=extra_headers, + ) + ) + self.transport.write(output) + self.start_keepalive() + + elif message["type"] == "websocket.close": + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.handshake_complete = True + self.close_sent = True + event = events.RejectConnection(status_code=403, headers=[]) + output = self.conn.send(event) + self.transport.write(output) + self.transport.close() + + elif message["type"] == "websocket.http.response.start": + # ensure status code is in the valid range + if not (100 <= message["status"] < 600): + msg = "Invalid HTTP status code '%d' in response." + raise RuntimeError(msg % message["status"]) + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + self.handshake_complete = True + event = events.RejectConnection( + status_code=message["status"], + headers=list(message["headers"]), + has_body=True, + ) + output = self.conn.send(event) + self.transport.write(output) + self.response_started = True + + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close' " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.close_sent and not self.response_started: + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + data = text_data if bytes_data is None else bytes_data + output = self.conn.send(wsproto.events.Message(data=data)) # type: ignore + if not self.transport.is_closing(): + self.transport.write(output) + + elif message["type"] == "websocket.close": + self.close_sent = True + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + output = self.conn.send(wsproto.events.CloseConnection(code=code, reason=reason)) + if not self.transport.is_closing(): + self.transport.write(output) + if self.read_paused: + self.read_paused = False + self.transport.resume_reading() + self.close_timer = self.loop.call_later(self.close_timeout, self.transport.close) + + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except LocalProtocolError as exc: + raise ClientDisconnected from exc + elif self.response_started: + if message["type"] == "websocket.http.response.body": + body_finished = not message.get("more_body", False) + reject_data = events.RejectData(data=message["body"], body_finished=body_finished) + output = self.conn.send(reject_data) + self.transport.write(output) + + if body_finished: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.close_sent = True + self.transport.close() + + else: + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.") + + async def receive(self) -> WebSocketEvent: + message = await self.queue.get() + if self.read_paused and self.queue.empty(): + self.read_paused = False + self.transport.resume_reading() + return message diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed @@ -0,0 +1 @@ + diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py new file mode 100644 index 0000000..0e822a6 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/server.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import asyncio +import contextlib +import functools +import logging +import os +import platform +import random +import signal +import socket +import sys +import threading +import time +from collections.abc import Generator, Sequence +from email.utils import formatdate +from types import FrameType +from typing import TYPE_CHECKING, TypeAlias + +from uvicorn._ansi import style +from uvicorn._compat import asyncio_run +from uvicorn.config import STARTUP_FAILURE, Config + +if TYPE_CHECKING: + from uvicorn.protocols.http.auto_zttp_impl import AutoZttpProtocol + from uvicorn.protocols.http.h11_impl import H11Protocol + from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol + from uvicorn.protocols.http.zttp_h2_impl import ZttpH2Protocol + from uvicorn.protocols.http.zttp_impl import ZttpProtocol + from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol + from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol + from uvicorn.protocols.websockets.wsproto_impl import WSProtocol + + Protocols: TypeAlias = ( + H11Protocol + | HttpToolsProtocol + | ZttpProtocol + | ZttpH2Protocol + | AutoZttpProtocol + | WSProtocol + | WebSocketProtocol + | WebSocketsSansIOProtocol + ) + +HANDLED_SIGNALS = ( + signal.SIGINT, # Unix signal 2. Sent by Ctrl+C. + signal.SIGTERM, # Unix signal 15. Sent by `kill `. +) +if sys.platform == "win32": # pragma: py-not-win32 + HANDLED_SIGNALS += (signal.SIGBREAK,) # Windows signal 21. Sent by Ctrl+Break. + +logger = logging.getLogger("uvicorn.error") + + +class ServerState: + """ + Shared servers state that is available between all protocol instances. + """ + + def __init__(self) -> None: + self.total_requests = 0 + self.connections: set[Protocols] = set() + self.tasks: set[asyncio.Task[None]] = set() + self.default_headers: list[tuple[bytes, bytes]] = [] + + +class Server: + def __init__(self, config: Config) -> None: + self.config = config + self.server_state = ServerState() + + self.started = False + self.should_exit = False + self.force_exit = False + self.last_notified = 0.0 + + self._captured_signals: list[int] = [] + + @functools.cached_property + def limit_max_requests(self) -> int | None: + if self.config.limit_max_requests is None: + return None + return self.config.limit_max_requests + random.randint(0, self.config.limit_max_requests_jitter) + + def run(self, sockets: list[socket.socket] | None = None) -> None: + return asyncio_run(self.serve(sockets=sockets), loop_factory=self.config.get_loop_factory()) + + async def serve(self, sockets: list[socket.socket] | None = None) -> None: + with self.capture_signals(): + await self._serve(sockets) + + async def _serve(self, sockets: list[socket.socket] | None = None) -> None: + process_id = os.getpid() + + config = self.config + if not config.loaded: + config.load() + + self.lifespan = config.lifespan_class(config) + + message = "Started server process [%d]" + color_message = "Started server process [" + style("%d", fg="cyan") + "]" + logger.info(message, process_id, extra={"color_message": color_message}) + + await self.startup(sockets=sockets) + if not self.should_exit: + await self.main_loop() + if self.started: + await self.shutdown(sockets=sockets) + + message = "Finished server process [%d]" + color_message = "Finished server process [" + style("%d", fg="cyan") + "]" + logger.info(message, process_id, extra={"color_message": color_message}) + + async def startup(self, sockets: list[socket.socket] | None = None) -> None: + await self.lifespan.startup() + if self.lifespan.should_exit: + sys.exit(STARTUP_FAILURE) + + config = self.config + + def create_protocol( + _loop: asyncio.AbstractEventLoop | None = None, + ) -> asyncio.Protocol: + return config.http_protocol_class( # type: ignore[call-arg] + config=config, + server_state=self.server_state, + app_state=self.lifespan.state, + _loop=_loop, + ) + + loop = asyncio.get_running_loop() + + listeners: Sequence[socket.SocketType] + if sockets is not None: # pragma: full coverage + # Explicitly passed a list of open sockets. + # We use this when the server is run from a Gunicorn worker. + + def _share_socket( + sock: socket.SocketType, + ) -> socket.SocketType: # pragma py-not-win32 + # Windows requires the socket be explicitly shared across + # multiple workers (processes). + from socket import fromshare # type: ignore[attr-defined] + + sock_data = sock.share(os.getpid()) # type: ignore[attr-defined] + return fromshare(sock_data) + + self.servers: list[asyncio.base_events.Server] = [] + for sock in sockets: + is_windows = platform.system() == "Windows" + if config.workers > 1 and is_windows: # pragma: py-not-win32 + sock = _share_socket(sock) # type: ignore[assignment] + server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog) + self.servers.append(server) + listeners = sockets + + elif config.fd is not None: # pragma: py-win32 + # Use an existing socket, from a file descriptor. + sock = socket.fromfd(config.fd, socket.AF_UNIX, socket.SOCK_STREAM) + server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog) + assert server.sockets is not None # mypy + listeners = server.sockets + self.servers = [server] + + elif config.uds is not None: # pragma: py-win32 + # Create a socket using UNIX domain socket. + uds_perms = 0o666 + if os.path.exists(config.uds): + uds_perms = os.stat(config.uds).st_mode # pragma: full coverage + server = await loop.create_unix_server( + create_protocol, path=config.uds, ssl=config.ssl, backlog=config.backlog + ) + os.chmod(config.uds, uds_perms) + assert server.sockets is not None # mypy + listeners = server.sockets + self.servers = [server] + + else: + # Standard case. Create a socket from a host/port pair. + try: + server = await loop.create_server( + create_protocol, + host=config.host, + port=config.port, + ssl=config.ssl, + backlog=config.backlog, + ) + except OSError as exc: + logger.error(exc) + await self.lifespan.shutdown() + sys.exit(STARTUP_FAILURE) + + assert server.sockets is not None + listeners = server.sockets + self.servers = [server] + + if sockets is None: + self._log_started_message(listeners) + else: + # We're most likely running multiple workers, so a message has already been + # logged by `config.bind_socket()`. + pass # pragma: full coverage + + self.started = True + + def _log_started_message(self, listeners: Sequence[socket.SocketType]) -> None: + config = self.config + + if config.fd is not None: # pragma: py-win32 + sock = listeners[0] + logger.info( + "Uvicorn running on socket %s (Press CTRL+C to quit)", + sock.getsockname(), + ) + + elif config.uds is not None: # pragma: py-win32 + logger.info("Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds) + + else: + addr_format = "%s://%s:%d" + host = "0.0.0.0" if config.host is None else config.host + if ":" in host: + # It's an IPv6 address. + addr_format = "%s://[%s]:%d" + + port = config.port + if port == 0: + port = listeners[0].getsockname()[1] + + protocol_name = "https" if config.ssl else "http" + message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)" + color_message = "Uvicorn running on " + style(addr_format, bold=True) + " (Press CTRL+C to quit)" + logger.info( + message, + protocol_name, + host, + port, + extra={"color_message": color_message}, + ) + + async def main_loop(self) -> None: + counter = 0 + should_exit = await self.on_tick(counter) + while not should_exit: + counter += 1 + counter = counter % 864000 + await asyncio.sleep(0.1) + should_exit = await self.on_tick(counter) + + async def on_tick(self, counter: int) -> bool: + # Update the default headers, once per second. + if counter % 10 == 0: + current_time = time.time() + current_date = formatdate(current_time, usegmt=True).encode() + + if self.config.date_header: + date_header = [(b"date", current_date)] + else: + date_header = [] + + self.server_state.default_headers = date_header + self.config.encoded_headers + + # Callback to `callback_notify` once every `timeout_notify` seconds. + if self.config.callback_notify is not None: + if current_time - self.last_notified > self.config.timeout_notify: # pragma: full coverage + self.last_notified = current_time + await self.config.callback_notify() + + # Determine if we should exit. + if self.should_exit: + return True + + max_requests = self.limit_max_requests + if max_requests is not None and self.server_state.total_requests >= max_requests: + logger.info("Maximum request limit of %d exceeded. Terminating process.", max_requests) + return True + + return False + + async def shutdown(self, sockets: list[socket.socket] | None = None) -> None: + logger.info("Shutting down") + + # Stop accepting new connections. + for server in self.servers: + server.close() + for sock in sockets or []: + sock.close() # pragma: full coverage + + # Request shutdown on all existing connections. + for connection in list(self.server_state.connections): + connection.shutdown() + await asyncio.sleep(0.1) + + # When 3.10 is not supported anymore, use `async with asyncio.timeout(...):`. + try: + await asyncio.wait_for( + self._wait_tasks_to_complete(), + timeout=self.config.timeout_graceful_shutdown, + ) + except asyncio.TimeoutError: + logger.error( + "Cancel %s running task(s), timeout graceful shutdown exceeded", + len(self.server_state.tasks), + ) + for t in self.server_state.tasks: + t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded") + + # Send the lifespan shutdown event, and wait for application shutdown. + if not self.force_exit: + await self.lifespan.shutdown() + + async def _wait_tasks_to_complete(self) -> None: + # Wait for existing connections to finish sending responses. + if self.server_state.connections and not self.force_exit: + msg = "Waiting for connections to close. (CTRL+C to force quit)" + logger.info(msg) + while self.server_state.connections and not self.force_exit: + await asyncio.sleep(0.1) + + # Wait for existing tasks to complete. + if self.server_state.tasks and not self.force_exit: + msg = "Waiting for background tasks to complete. (CTRL+C to force quit)" + logger.info(msg) + while self.server_state.tasks and not self.force_exit: + await asyncio.sleep(0.1) + + for server in self.servers: + await server.wait_closed() + + @contextlib.contextmanager + def capture_signals(self) -> Generator[None, None, None]: + # Signals can only be listened to from the main thread. + if threading.current_thread() is not threading.main_thread(): + yield + return + # always use signal.signal, even if loop.add_signal_handler is available + # this allows to restore previous signal handlers later on + original_handlers = {sig: signal.signal(sig, self.handle_exit) for sig in HANDLED_SIGNALS} + try: + yield + finally: + for sig, handler in original_handlers.items(): + signal.signal(sig, handler) + # If we did gracefully shut down due to a signal, try to + # trigger the expected behaviour now; multiple signals would be + # done LIFO, see https://stackoverflow.com/questions/48434964 + for captured_signal in reversed(self._captured_signals): + signal.raise_signal(captured_signal) + + def handle_exit(self, sig: int, frame: FrameType | None) -> None: + self._captured_signals.append(sig) + if self.should_exit and sig == signal.SIGINT: + self.force_exit = True # pragma: full coverage + else: + self.should_exit = True diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py new file mode 100644 index 0000000..cfceb6b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from uvicorn.supervisors.basereload import BaseReload +from uvicorn.supervisors.multiprocess import Multiprocess + +if TYPE_CHECKING: + ChangeReload: type[BaseReload] +else: + try: + from uvicorn.supervisors.watchfilesreload import WatchFilesReload as ChangeReload + except ImportError: # pragma: no cover + from uvicorn.supervisors.statreload import StatReload as ChangeReload + +__all__ = ["Multiprocess", "ChangeReload"] diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py new file mode 100644 index 0000000..d8cb477 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import logging +import os +import signal +import sys +import threading +from collections.abc import Callable, Iterator +from pathlib import Path +from socket import socket +from types import FrameType + +from uvicorn._ansi import style +from uvicorn._subprocess import get_subprocess +from uvicorn.config import Config + +HANDLED_SIGNALS = ( + signal.SIGINT, # Unix signal 2. Sent by Ctrl+C. + signal.SIGTERM, # Unix signal 15. Sent by `kill `. +) + +logger = logging.getLogger("uvicorn.error") + + +class BaseReload: + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + self.config = config + self.target = target + self.sockets = sockets + self.should_exit = threading.Event() + self.pid = os.getpid() + self.is_restarting = False + self.reloader_name: str | None = None + + def signal_handler(self, sig: int, frame: FrameType | None) -> None: # pragma: full coverage + """ + A signal handler that is registered with the parent process. + """ + if sys.platform == "win32" and self.is_restarting: + self.is_restarting = False + else: + self.should_exit.set() + + def run(self) -> None: + self.startup() + for changes in self: + if changes: + logger.warning( + "%s detected changes in %s. Reloading...", + self.reloader_name, + ", ".join(map(_display_path, changes)), + ) + self.restart() + + self.shutdown() + + def pause(self) -> None: + if self.should_exit.wait(self.config.reload_delay): + raise StopIteration() + + def __iter__(self) -> Iterator[list[Path] | None]: + return self + + def __next__(self) -> list[Path] | None: + return self.should_restart() + + def startup(self) -> None: + message = f"Started reloader process [{self.pid}] using {self.reloader_name}" + color_message = "Started reloader process [{}] using {}".format( + style(str(self.pid), fg="cyan", bold=True), + style(str(self.reloader_name), fg="cyan", bold=True), + ) + logger.info(message, extra={"color_message": color_message}) + + for sig in HANDLED_SIGNALS: + signal.signal(sig, self.signal_handler) + + self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets) + self.process.start() + + def restart(self) -> None: + if sys.platform == "win32": # pragma: py-not-win32 + self.is_restarting = True + assert self.process.pid is not None + os.kill(self.process.pid, signal.CTRL_C_EVENT) + + # This is a workaround to ensure the Ctrl+C event is processed + sys.stdout.write(" ") # This has to be a non-empty string + sys.stdout.flush() + else: # pragma: py-win32 + self.process.terminate() + self.process.join() + + self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets) + self.process.start() + + def shutdown(self) -> None: + if sys.platform == "win32": + self.should_exit.set() # pragma: py-not-win32 + else: + self.process.terminate() # pragma: py-win32 + self.process.join() + + for sock in self.sockets: + sock.close() + + message = f"Stopping reloader process [{str(self.pid)}]" + color_message = "Stopping reloader process [{}]".format(style(str(self.pid), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + def should_restart(self) -> list[Path] | None: + raise NotImplementedError("Reload strategies should override should_restart()") + + +def _display_path(path: Path) -> str: + try: + return f"'{path.relative_to(Path.cwd())}'" + except ValueError: + return f"'{path}'" diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py new file mode 100644 index 0000000..ef2dd2d --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py @@ -0,0 +1,282 @@ +from __future__ import annotations + +import logging +import os +import pickle +import signal +import threading +import time +from multiprocessing import Pipe +from socket import socket + +from uvicorn._ansi import style +from uvicorn._subprocess import get_subprocess +from uvicorn.config import STARTUP_FAILURE, Config +from uvicorn.server import Server + +SIGNALS = { + getattr(signal, f"SIG{x}"): x + for x in "INT TERM BREAK HUP QUIT TTIN TTOU USR1 USR2 WINCH".split() + if hasattr(signal, f"SIG{x}") +} + +logger = logging.getLogger("uvicorn.error") + + +class Process: + def __init__( + self, + config: Config, + sockets: list[socket], + ) -> None: + self.config = config + self._server: Server | None = None + + self.parent_conn, self.child_conn = Pipe() + self.process = get_subprocess(config, self.target, sockets) + + def _healthcheck(self, timeout: float) -> bool | None: + """Receives a timeout and returns the worker's startup flag, or None if it does not answer in time.""" + try: + self.parent_conn.send(b"ping") + if self.parent_conn.poll(timeout): + started: bool = self.parent_conn.recv() + return started + return None + except (OSError, EOFError, pickle.UnpicklingError): + return None + + def ping(self, timeout: float = 5) -> bool: + """Receives a timeout and returns True if the worker answers a healthcheck in time.""" + return self._healthcheck(timeout) is not None + + def is_ready(self, timeout: float = 5) -> bool: + """Receives a timeout and returns True if the worker answers and its server has finished startup.""" + return self._healthcheck(timeout) is True + + def pong(self) -> None: + """Receives one healthcheck ping and replies with whether the server has finished startup.""" + self.child_conn.recv() + self.child_conn.send(self.server.started) + + def always_pong(self) -> None: + while True: + self.pong() + + def target(self, sockets: list[socket] | None = None) -> None: # pragma: no cover + if os.name == "nt": # pragma: py-not-win32 + # Windows doesn't support SIGTERM, so we use SIGBREAK instead. + # And then we raise SIGTERM when SIGBREAK is received. + # https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/signal?view=msvc-170 + signal.signal( + signal.SIGBREAK, # type: ignore[attr-defined] + lambda sig, frame: signal.raise_signal(signal.SIGTERM), + ) + + threading.Thread(target=self.always_pong, daemon=True).start() + self.server.run(sockets) + + def is_alive(self, timeout: float = 5) -> bool: + if not self.process.is_alive(): + return False # pragma: full coverage + + return self.ping(timeout) + + def wait_until_ready(self, timeout: float, should_exit: threading.Event | None = None) -> bool: + """Receives a timeout and returns True if the worker starts serving, or False on exit, shutdown, or timeout.""" + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if should_exit is not None and should_exit.is_set(): + return False + if not self.process.is_alive(): + return False + if self.is_ready(timeout=1): + return True + time.sleep(0.1) + return False + + def start(self) -> None: + self.process.start() + + def terminate(self) -> None: + if self.process.exitcode is None: # Process is still running + assert self.process.pid is not None + if os.name == "nt": # pragma: py-not-win32 + # Windows doesn't support SIGTERM. + # So send SIGBREAK, and then in process raise SIGTERM. + os.kill(self.process.pid, signal.CTRL_BREAK_EVENT) # type: ignore[attr-defined] + else: + os.kill(self.process.pid, signal.SIGTERM) + logger.info(f"Terminated child process [{self.process.pid}]") + + self.parent_conn.close() + self.child_conn.close() + + def kill(self) -> None: + # In Windows, the method will call `TerminateProcess` to kill the process. + # In Unix, the method will send SIGKILL to the process. + self.process.kill() + self.parent_conn.close() + self.child_conn.close() + + def join(self) -> None: + logger.info(f"Waiting for child process [{self.process.pid}]") + self.process.join() + + @property + def server(self) -> Server: + if self._server is None: + self._server = Server(config=self.config) + return self._server + + @property + def pid(self) -> int | None: + return self.process.pid + + @property + def exitcode(self) -> int | None: + return self.process.exitcode + + +class Multiprocess: + def __init__( + self, + config: Config, + sockets: list[socket], + ) -> None: + self.config = config + self.sockets = sockets + + self.processes_num = config.workers + self.processes: list[Process] = [] + + self.should_exit = threading.Event() + + self.signal_queue: list[int] = [] + for sig in SIGNALS: + signal.signal(sig, lambda sig, frame: self.signal_queue.append(sig)) + + def init_processes(self) -> None: + for _ in range(self.processes_num): + process = Process(self.config, self.sockets) + process.start() + self.processes.append(process) + + def terminate_all(self) -> None: + for process in self.processes: + process.terminate() + + def join_all(self) -> None: + for process in self.processes: + process.join() + + def restart_all(self) -> None: + """Replaces each worker, bringing its replacement into service before retiring the old worker.""" + for idx, old_process in enumerate(self.processes): + if self.should_exit.is_set(): + return + + new_process = Process(self.config, self.sockets) + new_process.start() + + if not new_process.wait_until_ready(self.config.timeout_worker_healthcheck, self.should_exit): + new_process.kill() + new_process.join() + if not self.should_exit.is_set(): + logger.error( + f"New child process [{new_process.pid}] was not ready in time; " + f"keeping worker [{old_process.pid}] and aborting the restart." + ) + return + + old_process.terminate() + old_process.join() + self.processes[idx] = new_process + + def run(self) -> None: + message = f"Started parent process [{os.getpid()}]" + color_message = "Started parent process [{}]".format(style(str(os.getpid()), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + self.init_processes() + + while not self.should_exit.wait(0.5): + self.handle_signals() + self.keep_subprocess_alive() + + self.terminate_all() + self.join_all() + + message = f"Stopping parent process [{os.getpid()}]" + color_message = "Stopping parent process [{}]".format(style(str(os.getpid()), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + def keep_subprocess_alive(self) -> None: + if self.should_exit.is_set(): + return # parent process is exiting, no need to keep subprocess alive + + for idx, process in enumerate(self.processes): + if process.is_alive(timeout=self.config.timeout_worker_healthcheck): + continue + + process.kill() # process is hung, kill it + process.join() + + if process.exitcode == STARTUP_FAILURE: + # The worker failed before it started serving, so the app, TLS or socket + # bind is broken and would fail the same way on every restart. + # See https://github.com/encode/uvicorn/discussions/2440. + logger.error(f"Child process [{process.pid}] failed to start, stopping the parent process.") + self.should_exit.set() + return + + if self.should_exit.is_set(): + return # pragma: full coverage + + logger.info(f"Child process [{process.pid}] died") + process = Process(self.config, self.sockets) + process.start() + self.processes[idx] = process + + def handle_signals(self) -> None: + for sig in tuple(self.signal_queue): + self.signal_queue.remove(sig) + sig_name = SIGNALS[sig] + sig_handler = getattr(self, f"handle_{sig_name.lower()}", None) + if sig_handler is not None: + sig_handler() + else: # pragma: no cover + logger.debug(f"Received signal {sig_name}, but no handler is defined for it.") + + def handle_int(self) -> None: + logger.info("Received SIGINT, exiting.") + self.should_exit.set() + + def handle_term(self) -> None: + logger.info("Received SIGTERM, exiting.") + self.should_exit.set() + + def handle_break(self) -> None: # pragma: py-not-win32 + logger.info("Received SIGBREAK, exiting.") + self.should_exit.set() + + def handle_hup(self) -> None: # pragma: py-win32 + logger.info("Received SIGHUP, restarting processes.") + self.restart_all() + + def handle_ttin(self) -> None: # pragma: py-win32 + logger.info("Received SIGTTIN, increasing the number of processes.") + self.processes_num += 1 + process = Process(self.config, self.sockets) + process.start() + self.processes.append(process) + + def handle_ttou(self) -> None: # pragma: py-win32 + logger.info("Received SIGTTOU, decreasing number of processes.") + if self.processes_num <= 1: + logger.info("Already reached one process, cannot decrease the number of processes anymore.") + return + self.processes_num -= 1 + process = self.processes.pop() + process.terminate() + process.join() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py new file mode 100644 index 0000000..28693f9 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import logging +from collections.abc import Callable, Iterator +from pathlib import Path +from socket import socket + +from uvicorn.config import Config +from uvicorn.supervisors.basereload import BaseReload + +logger = logging.getLogger("uvicorn.error") + + +class StatReload(BaseReload): + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + super().__init__(config, target, sockets) + self.reloader_name = "StatReload" + self.mtimes: dict[Path, float] = {} + + if config.reload_excludes or config.reload_includes: + logger.warning("--reload-include and --reload-exclude have no effect unless watchfiles is installed.") + + def should_restart(self) -> list[Path] | None: + self.pause() + + for file in self.iter_py_files(): + try: + mtime = file.stat().st_mtime + except OSError: # pragma: nocover + continue + + old_time = self.mtimes.get(file) + if old_time is None: + self.mtimes[file] = mtime + continue + elif mtime > old_time: + return [file] + return None + + def restart(self) -> None: + self.mtimes = {} + return super().restart() + + def iter_py_files(self) -> Iterator[Path]: + for reload_dir in self.config.reload_dirs: + for path in list(reload_dir.rglob("*.py")): + yield path.resolve() diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py new file mode 100644 index 0000000..6009905 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path +from socket import socket + +from watchfiles import watch + +from uvicorn.config import Config +from uvicorn.supervisors.basereload import BaseReload + + +class FileFilter: + def __init__(self, config: Config): + default_includes = ["*.py"] + self.includes = [default for default in default_includes if default not in config.reload_excludes] + self.includes.extend(config.reload_includes) + self.includes = list(set(self.includes)) + + default_excludes = [".*", ".py[cod]", ".sw.*", "~*"] + self.excludes = [default for default in default_excludes if default not in config.reload_includes] + self.exclude_dirs = [] + for e in config.reload_excludes: + p = Path(e) + try: + is_dir = p.is_dir() + except OSError: # pragma: no cover + # gets raised on Windows for values like "*.py" + is_dir = False + + if is_dir: + self.exclude_dirs.append(p) + else: + self.excludes.append(e) # pragma: full coverage + self.excludes = list(set(self.excludes)) + + def __call__(self, path: Path) -> bool: + for include_pattern in self.includes: + if path.match(include_pattern): + if str(path).endswith(include_pattern): + return True # pragma: full coverage + + for exclude_dir in self.exclude_dirs: + if exclude_dir in path.parents: + return False + + for exclude_pattern in self.excludes: + if path.match(exclude_pattern): + return False # pragma: full coverage + + return True + return False + + +class WatchFilesReload(BaseReload): + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + super().__init__(config, target, sockets) + self.reloader_name = "WatchFiles" + self.reload_dirs: list[Path] = [] + for directory in config.reload_dirs: + self.reload_dirs.append(directory) + + self.watch_filter = FileFilter(config) + self.watcher = watch( + *self.reload_dirs, + watch_filter=None, + stop_event=self.should_exit, + # using yield_on_timeout here mostly to make sure tests don't + # hang forever, won't affect the class's behavior + yield_on_timeout=True, + ignore_permission_denied=True, + ) + + def should_restart(self) -> list[Path] | None: + self.pause() + + changes = next(self.watcher) + if changes: + unique_paths = {Path(c[1]) for c in changes} + return [p for p in unique_paths if self.watch_filter(p)] + return None diff --git a/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py new file mode 100644 index 0000000..827d091 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import asyncio +import logging +import signal +import sys +import warnings +from typing import Any + +from gunicorn.arbiter import Arbiter +from gunicorn.workers.base import Worker + +from uvicorn._compat import asyncio_run +from uvicorn.config import Config +from uvicorn.server import Server + +warnings.warn( + "The `uvicorn.workers` module is deprecated. Please use `uvicorn-worker` package instead.\n" + "For more details, see https://github.com/Kludex/uvicorn-worker.", + DeprecationWarning, +) + + +class UvicornWorker(Worker): + """ + A worker class for Gunicorn that interfaces with an ASGI consumer callable, + rather than a WSGI callable. + """ + + CONFIG_KWARGS: dict[str, Any] = {"loop": "auto", "http": "auto"} + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + logger = logging.getLogger("uvicorn.error") + logger.handlers = self.log.error_log.handlers + logger.setLevel(self.log.error_log.level) + logger.propagate = False + + logger = logging.getLogger("uvicorn.access") + logger.handlers = self.log.access_log.handlers + logger.setLevel(self.log.access_log.level) + logger.propagate = False + + config_kwargs: dict = { + "app": None, + "log_config": None, + "timeout_keep_alive": self.cfg.keepalive, + "timeout_notify": self.timeout, + "callback_notify": self.callback_notify, + "limit_max_requests": self.max_requests, + "forwarded_allow_ips": self.cfg.forwarded_allow_ips, + } + + if self.cfg.is_ssl: + ssl_kwargs = { + "ssl_keyfile": self.cfg.ssl_options.get("keyfile"), + "ssl_certfile": self.cfg.ssl_options.get("certfile"), + "ssl_keyfile_password": self.cfg.ssl_options.get("password"), + "ssl_version": self.cfg.ssl_options.get("ssl_version"), + "ssl_cert_reqs": self.cfg.ssl_options.get("cert_reqs"), + "ssl_ca_certs": self.cfg.ssl_options.get("ca_certs"), + "ssl_ciphers": self.cfg.ssl_options.get("ciphers"), + } + config_kwargs.update(ssl_kwargs) + + if self.cfg.settings["backlog"].value: + config_kwargs["backlog"] = self.cfg.settings["backlog"].value + + config_kwargs.update(self.CONFIG_KWARGS) + + self.config = Config(**config_kwargs) + + def init_signals(self) -> None: + # Reset signals so Gunicorn doesn't swallow subprocess return codes + # other signals are set up by Server.install_signal_handlers() + # See: https://github.com/Kludex/uvicorn/issues/894 + for s in self.SIGNALS: + signal.signal(s, signal.SIG_DFL) + + signal.signal(signal.SIGUSR1, self.handle_usr1) + # Don't let SIGUSR1 disturb active requests by interrupting system calls + signal.siginterrupt(signal.SIGUSR1, False) + + def _install_sigquit_handler(self) -> None: + """Install a SIGQUIT handler on workers. + + - https://github.com/Kludex/uvicorn/issues/1116 + - https://github.com/benoitc/gunicorn/issues/2604 + """ + + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGQUIT, self.handle_exit, signal.SIGQUIT, None) + + async def _serve(self) -> None: + self.config.app = self.wsgi + server = Server(config=self.config) + self._install_sigquit_handler() + await server.serve(sockets=self.sockets) + if not server.started: + sys.exit(Arbiter.WORKER_BOOT_ERROR) + + def run(self) -> None: + return asyncio_run(self._serve(), loop_factory=self.config.get_loop_factory()) + + async def callback_notify(self) -> None: + self.notify() + + +class UvicornH11Worker(UvicornWorker): + CONFIG_KWARGS = {"loop": "asyncio", "http": "h11"} diff --git a/source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py b/source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py new file mode 100755 index 0000000..600aa3b --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Front-Door/bin/front_door_mcp.py @@ -0,0 +1,749 @@ +#!/usr/bin/env python3 +import base64 +import hashlib +import json +import os +import re +import socket +import time +import urllib.error +import urllib.request +from pathlib import Path + +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from mcp.server.fastmcp import FastMCP, Image + +HOME = Path.home() +ADAPTER_SOCKET = HOME / ".local/share/framebridge/tool-adapter/adapter.sock" +JOBS_ROOT = HOME / ".local/share/framebridge/fbterm/jobs" +HUB_URL = os.environ.get("FRAMEBRIDGE_HUB_MCP_URL", "http://127.0.0.1:8877/mcp") +BROKER_CONTROL = os.environ.get("FRAMEBRIDGE_BROKER_CONTROL", "http://127.0.0.1:28765") +ASUS_NODE_ID = os.environ.get("FRAMEBRIDGE_ASUS_NODE_ID", "4b4157f9-3c98-4d24-bff4-840825d50951") +GERAMEG_NODE_ID = os.environ.get("FRAMEBRIDGE_GERAMEG_NODE_ID", "76885f27-da2e-4cca-bbde-6ec981e91e68") +HOST = os.environ.get("FRAMEBRIDGE_FRONT_DOOR_HOST", "127.0.0.1") +PORT = int(os.environ.get("FRAMEBRIDGE_FRONT_DOOR_PORT", "8865")) +JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$") +SENSITIVE = re.compile(r"(?i)(secret|token|password|passwd|api[_-]?key|private[_-]?key|cookie|authorization|credential)") +KEY_BEGIN = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----") +KEY_END = re.compile(r"-----END [A-Z0-9 ]*PRIVATE KEY-----") + +mcp = FastMCP( + "FrameBridge GERANAS Front Door", + instructions=( + "Front Door privado de FrameBridge en GERANAS. " + "framebridge_exec enruta por target: ASUS y GERAMEG mediante broker_outbound, GERANAS via FBTERM local, autorizado explicitamente; sin target mantiene ASUS. " + "Las seis herramientas Fleet son READ-only y se enrutan al Hub local." + ), + host=HOST, + port=PORT, + streamable_http_path="/mcp", +) + +def adapter_request(payload: dict) -> dict: + if not ADAPTER_SOCKET.is_socket(): + return {"ok": False, "adapter_error": "ADAPTER_SOCKET_NOT_AVAILABLE"} + raw=(json.dumps(payload, ensure_ascii=True, separators=(",",":"))+"\n").encode() + s=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.settimeout(float(payload.get("timeout_s",120))+60.0) + data=bytearray() + try: + s.connect(str(ADAPTER_SOCKET)) + s.sendall(raw) + s.shutdown(socket.SHUT_WR) + while True: + chunk=s.recv(65536) + if not chunk: + break + data.extend(chunk) + if len(data)>2_000_000: + raise RuntimeError("ADAPTER_RESPONSE_TOO_LARGE") + finally: + s.close() + return json.loads(bytes(data).decode()) + +def sanitize_text(text: str) -> str: + out=[] + in_key=False + for line in text.splitlines(): + if KEY_BEGIN.search(line): + in_key=True + out.append("[REDACTED PRIVATE KEY BLOCK]") + continue + if in_key: + if KEY_END.search(line): + in_key=False + continue + if SENSITIVE.search(line) and ("=" in line or ":" in line): + out.append("[REDACTED SENSITIVE LINE]") + else: + out.append(line) + return "\n".join(out) + +def stdout_result(job_id: str) -> dict: + p=JOBS_ROOT/job_id/"stdout.bin" + if not p.is_file(): + return {"stdout":"","stdout_sanitized":False,"stdout_truncated":False,"stdout_omitted_reason":"NOT_AVAILABLE"} + b=p.read_bytes() + truncated=len(b)>1_048_576 + if truncated: + b=b[:1_048_576] + text=sanitize_text(b.decode("utf-8","replace")) + if len(text)>4096: + text=text[:4096] + truncated=True + return {"stdout":text,"stdout_sanitized":True,"stdout_truncated":truncated,"stdout_omitted_reason":None} + +@mcp.tool() +def framebridge_status() -> dict: + ok=ADAPTER_SOCKET.is_socket() + mode=oct(ADAPTER_SOCKET.stat().st_mode & 0o777) if ok else None + return { + "ok":ok, + "host":"geranas", + "adapter_socket":str(ADAPTER_SOCKET), + "socket_mode":mode, + "mcp_transport":"streamable-http", + "mcp_bind":f"{HOST}:{PORT}", + "fleet_hub":HUB_URL, + "fleet_write":False, + "workspace":False, + } + +def broker_post(path: str, payload: dict, timeout_s: float=10.0) -> dict: + req=urllib.request.Request( + BROKER_CONTROL.rstrip("/")+"/"+path.lstrip("/"), + data=json.dumps(payload,sort_keys=True,separators=(",",":")).encode("utf-8"), + headers={"Content-Type":"application/json","Accept":"application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req,timeout=timeout_s) as resp: + out=json.loads(resp.read(2_000_000).decode("utf-8")) + except urllib.error.HTTPError as exc: + try: + out=json.loads(exc.read(65536).decode("utf-8")) + except Exception: + out={"ok":False,"error":"BROKER_HTTP_"+str(exc.code)} + except Exception as exc: + return {"ok":False,"error":"BROKER_CALL_FAILED","detail":type(exc).__name__+":"+str(exc)[:300]} + return out if isinstance(out,dict) else {"ok":False,"error":"BROKER_RESPONSE_INVALID"} + + +def asus_exec_request( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", +) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode("utf-8") + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="asus-mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { + "node_id":ASUS_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "script":script, + "sha256":digest, + "timeout_s":timeout_s, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"ASUS_EXEC_QUEUE_FAILED","broker":queued} + request_id=str(queued.get("request_id") or "") + if not request_id: + return {"ok":False,"error":"ASUS_EXEC_REQUEST_ID_MISSING"} + + deadline=time.monotonic()+timeout_s+75 + while time.monotonic() dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode("utf-8") + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="gerameg-mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + queued=broker_post( + "/fbpair/v1/control/admin-exec", + { + "node_id":GERAMEG_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "script":script, + "sha256":digest, + "timeout_s":timeout_s, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"GERAMEG_EXEC_QUEUE_FAILED","broker":queued} + request_id=str(queued.get("request_id") or "") + if not request_id: + return {"ok":False,"error":"GERAMEG_EXEC_REQUEST_ID_MISSING"} + + deadline=time.monotonic()+timeout_s+75 + while time.monotonic() dict: + """Administrative/lab exec on GERANAS local FBTERM.""" + return _framebridge_exec_geranas_impl( + script=script, + authorized=authorized, + timeout_s=timeout_s, + job_id=job_id, + sha256=sha256, + include_stdout=include_stdout, + ) + + +def _framebridge_exec_geranas_impl( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, +) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(script,str) or not script: + return {"ok":False,"error":"SCRIPT_REQUIRED"} + raw=script.encode() + if len(raw)>262144: + return {"ok":False,"error":"SCRIPT_TOO_LARGE"} + digest=hashlib.sha256(raw).hexdigest() + if sha256 and sha256.lower()!=digest: + return {"ok":False,"error":"SCRIPT_SHA256_MISMATCH","computed_sha256":digest} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not 1<=timeout_s<=600: + return {"ok":False,"error":"INVALID_TIMEOUT"} + if not job_id: + job_id="mcp-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + req={ + "version":1, + "job_id":job_id, + "mode":"EXEC", + "authorized":True, + "timeout_s":timeout_s, + "sha256":digest, + "script":script, + } + result=adapter_request(req) + result["mcp_job_id"]=job_id + result["mcp_script_sha256"]=digest + if isinstance(result.get("result_final"),str): + result["result_final"]=sanitize_text(result["result_final"]) + if include_stdout is True: + result.update(stdout_result(job_id)) + else: + result.update({"stdout":"","stdout_sanitized":False,"stdout_truncated":False,"stdout_omitted_reason":"NOT_REQUESTED"}) + return result + + +def _cloud_dsm_rpc(payload: dict) -> dict: + """Private GERANAS local socket; root-only approval never exposed through MCP.""" + import socket as _socket + import json as _json + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_STREAM) + sock.settimeout(38) + try: + sock.connect("/run/framebridge-dsm/cloud.sock") + body = (_json.dumps(payload, separators=(",",":"),ensure_ascii=True) + "\n").encode("utf8") + if len(body)>12000: + return {"ok":False,"error":"CLOUD_REQUEST_TOO_LARGE","target":"CLOUD"} + sock.sendall(body) + result = b"" + while len(result)<32768 and b"\n" not in result: + part=sock.recv(8192) + if not part:break + result+=part + if b"\n" not in result: + return {"ok":False,"error":"CLOUD_RESPONSE_INCOMPLETE","target":"CLOUD"} + output = _json.loads(result.split(b"\n",1)[0]) + if not isinstance(output,dict): + return {"ok":False,"error":"CLOUD_RESPONSE_INVALID","target":"CLOUD"} + output["target"]="CLOUD" + return output + except (OSError,ValueError,UnicodeError) as e: + return {"ok":False,"error":"CLOUD_DSM_UNAVAILABLE", + "error_type":type(e).__name__,"target":"CLOUD"} + finally: + sock.close() + + +@mcp.tool() +def framebridge_cloud_dsm(action: str, authorized: bool = False, params_json: str = "{}") -> dict: + """CLOUD XPEnology agentless DSM: persistent agentless DSM with temporary owner 2FA; typed reads and Owner-MFA gated writes. + action: status,system_info,file_station_info,shares,list_folder,users, + create_folder,rename_folder,pending. + params_json: JSON object (no passwords/OTP). Writes require local owner OTP approval. + """ + import json as _json + try: + params = _json.loads(params_json) + if not isinstance(params,dict) or len(params_json)>2000: + return {"ok":False,"error":"CLOUD_PARAMETERS_INVALID"} + except (ValueError,TypeError): + return {"ok":False,"error":"CLOUD_PARAMETERS_JSON_INVALID"} + if action=="approve": + return {"ok":False,"error":"OWNER_APPROVAL_LOCAL_TTY_ONLY"} + return _cloud_dsm_rpc({"action":action,"authorized":bool(authorized),"params":params}) + + +@mcp.tool() +def framebridge_exec( + script: str, + authorized: bool, + timeout_s: int = 120, + job_id: str = "", + sha256: str = "", + include_stdout: bool = False, + target: str = "ASUS", +) -> dict: + """Generic administrative/lab exec. target=ASUS or GERAMEG uses broker; target=GERANAS uses local FBTERM. Legacy calls without target remain ASUS. CLOUD uses only typed JSON actions, never shell commands.""" + if target == "CLOUD": + import json as _json + try: + payload = _json.loads(script) + if not isinstance(payload,dict) or len(script)>2000: + return {"ok":False,"error":"CLOUD_REQUIRES_JSON_ACTION"} + except (TypeError,ValueError): + return {"ok":False,"error":"CLOUD_ACCEPTS_ONLY_TYPED_JSON_ACTION_NOT_SHELL"} + if payload.get("action")=="approve": + return {"ok":False,"error":"CLOUD_OWNER_APPROVAL_LOCAL_TTY_ONLY"} + return _cloud_dsm_rpc({"action":payload.get("action"), + "params":payload.get("params",{}), + "authorized":bool(authorized)}) + if target == "GERANAS": + result = _framebridge_exec_geranas_impl( + script=script, + authorized=authorized, + timeout_s=timeout_s, + job_id=job_id, + sha256=sha256, + include_stdout=include_stdout, + ) + if isinstance(result, dict): + result = dict(result) + result["target"] = "GERANAS" + return result + if target not in ("ASUS", "GERAMEG"): + return {"ok": False, "error": "UNSUPPORTED_TARGET", "allowed_targets": ["ASUS", "GERANAS", "GERAMEG", "CLOUD"]} + exec_request = asus_exec_request if target == "ASUS" else gerameg_exec_request + result=exec_request( + script=script, + authorized=authorized, + timeout_s=timeout_s, + job_id=job_id, + sha256=sha256, + ) + if include_stdout is not True and isinstance(result,dict): + result=dict(result) + result.pop("stdout",None) + return result + + +def _cua_frame_fields(frame_output: str) -> dict: + result={} + safe_lines=[] + for line in str(frame_output or "").splitlines(): + if "=" not in line: + safe_lines.append(line) + continue + key,value=line.split("=",1) + key=key.strip() + if key=="BASE64": + result["image_base64"]=value.strip() + continue + if key=="MIME": + result["image_mime"]=value.strip() + if key in { + "RESULTADO_FINAL","VISUAL_CAPTURE_GATE","CUA_PROTOCOL_VERSION", + "CHAT_AUTHORIZATION_GATE","CHAT_AUTHORIZATION_MODE","ACTION","SEMANTIC", + "COORD_SPACE","COORD_SPACE_WIDTH","COORD_SPACE_HEIGHT", + "REQUESTED_X","REQUESTED_Y","REQUESTED_X1","REQUESTED_Y1", + "REQUESTED_X2","REQUESTED_Y2","ROOT_X","ROOT_Y","ROOT_X1","ROOT_Y1", + "ROOT_X2","ROOT_Y2","BUTTON","KEY","DIRECTION","CLICKS","CHAR_COUNT", + "TYPE_MODE","WIDTH","HEIGHT","SOURCE_X","SOURCE_Y","SOURCE_WIDTH", + "SOURCE_HEIGHT","DISPLAY_WIDTH","DISPLAY_HEIGHT","CAPTURE_SCOPE", + "COORD_SPACE_DEFAULT","COORD_SPACE_AVAILABLE","WINDOW_X","WINDOW_Y", + "WINDOW_WIDTH","WINDOW_HEIGHT","ACTIVE_WINDOW_ID","ACTIVE_WINDOW_ID_BEFORE", + "ACTIVE_WINDOW_ID_AFTER","WINDOW_TITLE","ACTIVE_WINDOW_TITLE_BEFORE", + "ACTIVE_WINDOW_TITLE_AFTER","CURSOR_X","CURSOR_Y","SHA256","BYTES","MIME", + "EXPECTED_WINDOW_GATE","EXPECTED_SOURCE_GEOMETRY_GATE", + }: + result[key.lower()]=sanitize_text(value.strip()) + safe_lines.append(line) + result["frame_output"]=sanitize_text("\n".join( + line for line in safe_lines if not line.startswith("BASE64=") + )) + return result + + +def asus_cua_request(cua: dict, authorized: bool, timeout_s: int, job_id: str) -> dict: + if authorized is not True: + return {"ok":False,"error":"AUTHORIZED_TRUE_REQUIRED"} + if not isinstance(cua,dict): + return {"ok":False,"error":"CUA_PAYLOAD_REQUIRED"} + action=str(cua.get("action") or "").upper().strip() + if action not in {"OBSERVE","CLICK","DOUBLE_CLICK","TYPE","KEY","SCROLL","DRAG"}: + return {"ok":False,"error":"CUA_ACTION_UNSUPPORTED"} + try: + timeout_s=int(timeout_s) + except Exception: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not 1<=timeout_s<=180: + return {"ok":False,"error":"INVALID_CUA_TIMEOUT"} + if not job_id: + seed=json.dumps({"action":action,"time":time.time_ns()},sort_keys=True).encode() + digest=hashlib.sha256(seed).hexdigest() + job_id="cua-geranas-"+time.strftime("%Y%m%d-%H%M%S")+"-"+digest[:8] + if not JOB_RE.fullmatch(job_id): + return {"ok":False,"error":"INVALID_JOB_ID"} + + queued=broker_post( + "/fbpair/v1/control/admin-cua", + { + "node_id":ASUS_NODE_ID, + "payload":{ + "authorized":True, + "job_id":job_id, + "timeout_s":timeout_s, + "cua":cua, + }, + }, + timeout_s=10.0, + ) + if queued.get("ok") is not True: + return {"ok":False,"error":"ASUS_CUA_QUEUE_FAILED","broker":queued} + rid=str(queued.get("request_id") or "") + deadline=time.monotonic()+timeout_s+75 + while time.monotonic()0: payload["preview_width"]=int(preview_width) + if preview_height>0: payload["preview_height"]=int(preview_height) + if expected_window_id: payload["expected_window_id"]=str(expected_window_id) + if expected_source_width>0: payload["expected_source_width"]=int(expected_source_width) + if expected_source_height>0: payload["expected_source_height"]=int(expected_source_height) + + if action in {"CLICK","DOUBLE_CLICK"}: + if x<0 or y<0: return {"ok":False,"error":"CLICK_COORDINATES_REQUIRED"} + payload.update({"x":int(x),"y":int(y),"button":str(button).lower()}) + elif action=="TYPE": + if not text: return {"ok":False,"error":"TYPE_TEXT_REQUIRED"} + tm=str(type_mode).upper().strip() + if tm not in {"KEYS","CLIPBOARD_EXACT"}: return {"ok":False,"error":"TYPE_MODE_INVALID"} + payload.update({"text":str(text),"type_mode":tm}) + elif action=="KEY": + if not key: return {"ok":False,"error":"KEY_REQUIRED"} + payload["key"]=str(key) + elif action=="SCROLL": + payload["direction"]=str(direction).upper() + payload["steps"]=int(steps) + payload["clicks"]=int(clicks) + if x>=0 and y>=0: + payload.update({"x":int(x),"y":int(y)}) + elif x>=0 or y>=0: + return {"ok":False,"error":"SCROLL_POINT_INCOMPLETE"} + elif action=="DRAG": + if min(x1,y1,x2,y2)<0: return {"ok":False,"error":"DRAG_COORDINATES_REQUIRED"} + payload.update({ + "x1":int(x1),"y1":int(y1),"x2":int(x2),"y2":int(y2), + "button":str(button).lower(),"steps":int(steps),"duration_ms":int(duration_ms), + }) + + result=asus_cua_request(payload,True,timeout_s,job_id) + if bool(observe_after) and action!="OBSERVE" and bool(result.get("ok")): + post=dict(payload) + post.clear() + post={"action":"OBSERVE","capture_scope":observe_scope} + if expected_window_id: + post["expected_window_id"]=str(expected_window_id) + post_id=(str(job_id or "cua")[:72]+"-post")[:80] + post_result=asus_cua_request(post,True,timeout_s,post_id) + result["post_ok"]=bool(post_result.get("ok")) + result["post_resultado_final"]=post_result.get("resultado_final") + for key_name in ( + "image_base64","image_mime","source_width","source_height", + "display_width","display_height","capture_scope","active_window_id", + "window_title","sha256","bytes" + ): + if key_name in post_result: + result[key_name]=post_result[key_name] + + image_b64=result.get("image_base64") + mime=str(result.get("image_mime") or "") + if isinstance(image_b64,str) and image_b64: + try: + data=base64.b64decode(image_b64,validate=True) + fmt="jpeg" if mime=="image/jpeg" else ("png" if mime=="image/png" else None) + if fmt: + meta=dict(result) + meta.pop("image_base64",None) + return [meta, Image(data=data,format=fmt)] + except Exception: + result["image_decode_error"]="INVALID_BASE64" + return result + + +async def hub_call(tool: str, args: dict) -> dict: + try: + async with streamablehttp_client(HUB_URL) as streams: + async with ClientSession(streams[0], streams[1]) as session: + await session.initialize() + result=await session.call_tool(tool,args) + except Exception as exc: + return {"ok":False,"error":"FLEET_HUB_CALL_FAILED","detail":str(exc)[:500]} + if bool(getattr(result,"isError",False)): + return {"ok":False,"error":"FLEET_HUB_TOOL_ERROR"} + structured=getattr(result,"structuredContent",None) + if isinstance(structured,dict): + return structured + for item in getattr(result,"content",[]): + txt=getattr(item,"text",None) + if isinstance(txt,str): + try: + obj=json.loads(txt) + except Exception: + continue + if isinstance(obj,dict): + return obj + return {"ok":True,"result":obj} + return {"ok":False,"error":"FLEET_HUB_EMPTY_RESULT"} + +@mcp.tool() +async def framebridge_fleet_nodes() -> dict: + return await hub_call("framebridge_fleet_nodes",{}) + +@mcp.tool() +async def framebridge_fleet_node_system_status(node: str) -> dict: + return await hub_call("framebridge_fleet_node_system_status",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_hardware_summary(node: str) -> dict: + return await hub_call("framebridge_fleet_node_hardware_summary",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_storage_inspect(node: str) -> dict: + return await hub_call("framebridge_fleet_node_storage_inspect",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_network_inspect(node: str) -> dict: + return await hub_call("framebridge_fleet_node_network_inspect",{"node":node}) + +@mcp.tool() +async def framebridge_fleet_node_service_status(node: str, service: str) -> dict: + return await hub_call("framebridge_fleet_node_service_status",{"node":node,"service":service}) + +from gerameg_cua_frontdoor import register_tools as register_gerameg_cua_tools +register_gerameg_cua_tools(mcp, gerameg_exec_request) + +if __name__ == "__main__": + mcp.run(transport="streamable-http") diff --git a/source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py b/source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py new file mode 100644 index 0000000..a1dc7a4 --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py @@ -0,0 +1,177 @@ +"""Siete herramientas CUA-TTY ligadas a GERAMEG, sobre FBEXEC original.""" +from __future__ import annotations +import base64 +import hashlib +import json +import re +import shlex +import uuid + +RPC_PATH = ".local/lib/framebridge-gerameg-original/cua_tty_rpc.py" +PROTOCOL = "FB-CUA-TTY-RPC/1" +MAX_BYTES = 65536 +CHUNK_BYTES = 2100 +SID = re.compile(r"[A-Za-z0-9_-]{12,80}\Z") +REPLY_ID = re.compile(r"[0-9a-f]{32}\Z") +SHA = re.compile(r"[0-9a-f]{64}\Z") +KEYS = frozenset({"UP", "DOWN", "LEFT", "RIGHT", "ENTER", "TAB", "ESC", "BACKSPACE", "CTRL_C"}) +READ = {"readOnlyHint": True, "destructiveHint": False, "idempotentHint": False, "openWorldHint": False} +CHANGE = {**READ, "readOnlyHint": False} +STOP = {**CHANGE, "destructiveHint": True} + + +class CuaError(RuntimeError): + pass + + +def require(ok, code): + if not ok: + raise CuaError(code) + + +def encode(value): + return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("ascii") + + +def _remote_script(argv): + """Resolve the helper under the remote execution account's home directory.""" + command = shlex.join(["/usr/bin/python3", "-B"]) + command += ' "${HOME:?}/' + RPC_PATH + '"' + if argv: + command += " " + shlex.join(argv) + return command + "\n" + + +def register_tools(mcp, exec_request): + """exec_request es DI del bootstrap original, nunca un parámetro MCP.""" + + def remote(argv): + script = _remote_script(argv) + job_id = "cua-gerameg-" + uuid.uuid4().hex + digest = hashlib.sha256(script.encode("utf-8")).hexdigest() + result = exec_request(script=script, authorized=True, timeout_s=15, job_id=job_id, sha256=digest) + require(type(result) is dict and result.get("ok") is True, "CUA_TTY_EXEC_FAILED") + exit_code = result.get("exit_code") + require(type(exit_code) in (int, str) and str(exit_code) == "0", "CUA_TTY_EXEC_FAILED") + require(result.get("target") == "GERAMEG" and result.get("mcp_job_id") == job_id and result.get("mcp_script_sha256") == digest, "CUA_TTY_EXEC_BINDING_INVALID") + stdout = result.get("stdout") + require(type(stdout) is str and len(stdout) <= 4096 and result.get("stdout_truncated") is not True, "CUA_TTY_STDOUT_INVALID") + try: + envelope = json.loads(stdout) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_ENVELOPE_INVALID") from None + require(type(envelope) is dict and envelope.get("protocol") == PROTOCOL, "CUA_TTY_ENVELOPE_INVALID") + if envelope.get("ok") is not True: + code = envelope.get("error") + if type(code) is str and re.fullmatch(r"[A-Z0-9_]{1,80}", code): + raise CuaError("CUA_TTY_" + code) + raise CuaError("CUA_TTY_RPC_FAILED") + return envelope + + def unpack(envelope): + total, digest = envelope.get("bytes"), envelope.get("sha256") + require(type(total) is int and 0 < total <= MAX_BYTES and type(digest) is str and SHA.fullmatch(digest) is not None, "CUA_TTY_MANIFEST_INVALID") + if envelope.get("kind") == "inline": + text = envelope.get("data_b64") + require(type(text) is str and len(text) <= 3000, "CUA_TTY_RESPONSE_INVALID") + try: + data = base64.b64decode(text, validate=True) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_RESPONSE_INVALID") from None + elif envelope.get("kind") == "file": + reply_id = envelope.get("reply_id") + require(type(reply_id) is str and REPLY_ID.fullmatch(reply_id) is not None, "CUA_TTY_REPLY_ID_INVALID") + pieces = [] + for offset in range(0, total, CHUNK_BYTES): + part = remote(["read", reply_id, str(offset)]) + require(part.get("kind") == "chunk" and part.get("reply_id") == reply_id and type(part.get("offset")) is int and part["offset"] == offset, "CUA_TTY_CHUNK_BINDING_INVALID") + require(type(part.get("bytes")) is int and part["bytes"] == total and part.get("sha256") == digest, "CUA_TTY_CHUNK_MANIFEST_INVALID") + text = part.get("data_b64") + require(type(text) is str and len(text) <= 2800, "CUA_TTY_CHUNK_INVALID") + try: + chunk = base64.b64decode(text, validate=True) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_CHUNK_INVALID") from None + expected = min(CHUNK_BYTES, total - offset) + require(type(part.get("length")) is int and part["length"] == expected and len(chunk) == expected, "CUA_TTY_CHUNK_LENGTH_INVALID") + pieces.append(chunk) + data = b"".join(pieces) + else: + raise CuaError("CUA_TTY_ENVELOPE_KIND_INVALID") + require(len(data) == total and hashlib.sha256(data).hexdigest() == digest, "CUA_TTY_RESPONSE_HASH_MISMATCH") + try: + reply = json.loads(data) + except (ValueError, UnicodeError): + raise CuaError("CUA_TTY_RESPONSE_INVALID") from None + require(type(reply) is dict and type(reply.get("ok")) is bool, "CUA_TTY_RESPONSE_INVALID") + return reply, total, digest + + def call(op, authorized, **kwargs): + if authorized is not True: + return {"ok": False, "target": "GERAMEG", "error": "AUTHORIZED_TRUE_REQUIRED"} + try: + payload = base64.b64encode(encode({"op": op, **kwargs})).decode("ascii") + reply, total, digest = unpack(remote(["invoke", payload])) + if reply.get("ok") is not True: + error = reply.get("error") + code = error if type(error) is str and re.fullmatch(r"[A-Z0-9_]{1,80}", error) else "CUA_TTY_REQUEST_DENIED" + return {"ok": False, "target": "GERAMEG", "error": code} + require(type(reply.get("result")) is dict, "CUA_TTY_RESPONSE_INVALID") + return {"ok": True, "target": "GERAMEG", "result": reply["result"], "response_bytes": total, "response_sha256": digest} + except CuaError as exc: + return {"ok": False, "target": "GERAMEG", "error": str(exc)} + except Exception: + return {"ok": False, "target": "GERAMEG", "error": "CUA_TTY_BACKEND_UNAVAILABLE"} + + def valid_sid(session_id): + return type(session_id) is str and SID.fullmatch(session_id) is not None + + def denied(authorized, valid=True, error="CUA_TTY_ARGUMENT_INVALID"): + if authorized is not True: + return {"ok": False, "target": "GERAMEG", "error": "AUTHORIZED_TRUE_REQUIRED"} + if not valid: + return {"ok": False, "target": "GERAMEG", "error": error} + return None + + @mcp.tool(annotations=dict(READ)) + def framebridge_cua_tty_status(authorized: bool) -> dict: + """Estado CUA-TTY de GERAMEG; requiere autorización explícita del chat.""" + return call("status", authorized) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_open_demo(authorized: bool) -> dict: + """Abre demo-readonly sin privilegios en GERAMEG; no abre una shell general.""" + return call("open_demo", authorized) + + @mcp.tool(annotations=dict(READ)) + def framebridge_cua_tty_observe(session_id: str, authorized: bool) -> dict: + """Lee la pantalla textual de una sesión CUA-TTY de GERAMEG.""" + return denied(authorized, valid_sid(session_id), "CUA_TTY_SESSION_INVALID") or call("observe", authorized, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_resize(session_id: str, rows: int, cols: int, authorized: bool) -> dict: + """Redimensiona una sesión de GERAMEG dentro de 5–100 filas y 20–220 columnas.""" + valid = valid_sid(session_id) and type(rows) is int and type(cols) is int and 5 <= rows <= 100 and 20 <= cols <= 220 + return denied(authorized, valid) or call("resize", authorized, session_id=session_id, rows=rows, cols=cols) + + @mcp.tool(annotations=dict(STOP)) + def framebridge_cua_tty_abort(session_id: str, authorized: bool) -> dict: + """Cierra exclusivamente la sesión CUA-TTY indicada de GERAMEG.""" + return denied(authorized, valid_sid(session_id), "CUA_TTY_SESSION_INVALID") or call("abort", authorized, session_id=session_id) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_key_send(session_id: str, key: str, authorized: bool) -> dict: + """Envía UP/DOWN/LEFT/RIGHT/ENTER/TAB/ESC/BACKSPACE/CTRL_C en GERAMEG.""" + valid = valid_sid(session_id) and type(key) is str and key in KEYS + return denied(authorized, valid) or call("key_send", authorized, session_id=session_id, key=key) + + @mcp.tool(annotations=dict(CHANGE)) + def framebridge_cua_tty_text_type(session_id: str, text: str, authorized: bool) -> dict: + """Escribe 1–64 caracteres sin controles en la demo de GERAMEG.""" + valid = valid_sid(session_id) and type(text) is str and 1 <= len(text) <= 64 and not any(ord(ch) < 32 or ord(ch) == 127 for ch in text) + if valid: + try: + text.encode("utf-8") + except UnicodeEncodeError: + valid = False + return denied(authorized, valid) or call("text_type", authorized, session_id=session_id, text=text) diff --git a/source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run b/source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run new file mode 100755 index 0000000..4342daa --- /dev/null +++ b/source/sources/original-geranas/FrameBridge-MCP-Private/bin/framebridge-mcp-private-run @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set +e +umask 077 +CFG="/home/framebridge/FrameBridge-MCP-Private/config.env" +HEALTH_FILE="/run/framebridge-mcp-private/health.url" +CRED_ID="framebridge-control-plane-api-key" +[ -r "$CFG" ] || exit 20 +[ -n "$CREDENTIALS_DIRECTORY" ] || exit 21 +CRED_FILE="$CREDENTIALS_DIRECTORY/$CRED_ID" +[ -s "$CRED_FILE" ] || exit 22 +set -a +. "$CFG" +set +a +[ -n "$FRAMEBRIDGE_TUNNEL_ID" ] || exit 23 +[ -n "$FRAMEBRIDGE_MCP_URL" ] || exit 23 +[ -n "$FRAMEBRIDGE_TUNNEL_BIN" ] || exit 23 +rm -f "$HEALTH_FILE" +exec "$FRAMEBRIDGE_TUNNEL_BIN" run --control-plane.tunnel-id "$FRAMEBRIDGE_TUNNEL_ID" --control-plane.api-key "file:$CRED_FILE" --mcp.server-url "channel=main,url=$FRAMEBRIDGE_MCP_URL" --health.listen-addr 127.0.0.1:0 --health.url-file "$HEALTH_FILE" --log.level info --log.format struct-text diff --git a/source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client b/source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client new file mode 100755 index 0000000..a91546f Binary files /dev/null and b/source/sources/original-geranas/FrameBridge-MCP-Private/bin/tunnel-client differ diff --git a/source/sources/original-geranas/MANIFEST.json b/source/sources/original-geranas/MANIFEST.json new file mode 100644 index 0000000..38f5b7b --- /dev/null +++ b/source/sources/original-geranas/MANIFEST.json @@ -0,0 +1,4838 @@ +{ + "origin": "Community derivative of the recorded source capture", + "files": [ + { + "path": ".local/share/framebridge/tool-adapter/bin/framebridge_tool_adapter.py", + "bytes": 10211, + "sha256": "2afb9bf8f789f42732a6aa6e50b5897174bfa1522ff032bc83533679c37551ec" + }, + { + "path": "FrameBridge-Broker-RC1/dev/fbpair_dev_broker.py", + "bytes": 44615, + "sha256": "d520f142ace34a728787899d8cdaa02be4ab2ee98aec16e845603b1cf9d10330" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/__init__.py", + "bytes": 30, + "sha256": "ed39d756ed2534063851d43fda2c1394010d19d8a0314541cba526b56181eac4" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/chatgpt_auth.py", + "bytes": 9536, + "sha256": "210a280b990be282aa459251d6f41bd068192af4eb6d57b84ae0a99035ed3209" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/classic_tty.py", + "bytes": 6382, + "sha256": "f195566a43eaefd329080c12f4095cdbfa49f8548cbd6afeb5fd83c70ae832f6" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_chat.py", + "bytes": 4911, + "sha256": "c1ab1f4c394b32a945f6881c0fbab9c697fcca47cbc6b618a02a195e2a47b583" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/console_tty.py", + "bytes": 2541, + "sha256": "27950dd566fab369bc155bf7d841699795fa7ef6838f293fb85882647ab0981d" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/first_run.py", + "bytes": 4306, + "sha256": "654197d18d339b7bfca269131f6cdeaea47ae8e37c7c7627c64bd9717742f22b" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/memory_store.py", + "bytes": 25639, + "sha256": "c6967bb40014db050ba93457d0d9c4b007d70c077fb3c3bed4eacf057fa138dd" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_lifecycle.py", + "bytes": 13394, + "sha256": "7ce7f2ec4af8a1527b3e4c4265f792ab9f4c19e2d3d8d5a5408caf95008a7ac1" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/network_security.py", + "bytes": 17696, + "sha256": "3ee930c6ff4d7ffdbe7157b1f5c960e2f0a55e4f163b2142a4cb939b8593fd19" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/node_identity.py", + "bytes": 7709, + "sha256": "dbc712dfbbebd540d9e9c138ff9fcf5661f4c9c9ad7fd6bf31dc16135360d35f" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/outbound_agent.py", + "bytes": 10132, + "sha256": "05732b60cb77684867f065203f27448dc9d95cbd2fb6c3cec8bc4a70f97daf70" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing.py", + "bytes": 18518, + "sha256": "72d5acf12771ef63ca7c901cb6000a40ad3c11c26d186de23e5d0713a21bdf13" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_client.py", + "bytes": 13679, + "sha256": "21ba5e2c594919dfc326361a7b5a227273b78bb05b47a800c0206a3bc4edfb4b" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/pairing_window.py", + "bytes": 2890, + "sha256": "201152a71337d3935af74398e092221c40bf915e79e05186cf1d2a93858f665e" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/plan_engine.py", + "bytes": 65742, + "sha256": "a23c40066d025a6f5d360fa65c2e8e5f21773a425ee5d5513361fa1025c7fb0b" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/priv_helper.py", + "bytes": 79901, + "sha256": "4be811b4a3b54d9dc4361ab7783c91547faeedc265c5a801729d3d0cbdaf2638" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/product_core.py", + "bytes": 17743, + "sha256": "76635f444318f376c0bdc8a65ce289cbeaa412a788934cd76d7262ca8d818751" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/qr_terminal.py", + "bytes": 2821, + "sha256": "326fb7a9b2920b7018975c3b19f4f9b17e60a28c7062efc1809fd5692a99f49b" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_capabilities.py", + "bytes": 27416, + "sha256": "d1ef2a59ecd30447fb666ce359681ea0896f9d7a4b9aeba5b2e7e00163845d24" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_cli.py", + "bytes": 29962, + "sha256": "41f1877a50d25f46f4a70496fc7edd8a750e9c9e3c71a5a51dbf3100b0c3b68b" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/seed_core.py", + "bytes": 11182, + "sha256": "038e6f1f2183acf572e75eaba4e3c639dccecddcebed38521fe6bf5301245bea" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/totp_auth.py", + "bytes": 1847, + "sha256": "b25adbe0228ff9cb7ebf6f22818ac6377f3bd400b9978e36f60b9c6474a4f258" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/__init__.py", + "bytes": 58, + "sha256": "3d59115f22e975989afff8452a43f5346df435d7775c6521b38461922b7b6e0f" + }, + { + "path": "FrameBridge-Broker-RC1/src/framebridge_linux_chatgpt/vendor/qrcodegen.py", + "bytes": 39349, + "sha256": "9f4ed1dd201dcb92b1bc0d6e14f46c754bcff0ce48580c5d7e8ace8f6926c8ef" + }, + { + "path": "FrameBridge-Fleet-Hub/app/fleet_hub_mcp_server.py", + "bytes": 1207, + "sha256": "280a5cb2fc9394a941e4b94c9c4d4a77ce5de8426fc4c1ac98a9abd05f6717a0" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/__init__.py", + "bytes": 37, + "sha256": "9cfca695c6d3fc63b8e1d4e7b8ca3ca9f6eb12c04db4db7fe7d8e5dea4ec2173" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_gateway_adapter.py", + "bytes": 1985, + "sha256": "304e17c9cae45f042b77f19d70f5e9c10d260622cf27ce8d72abcf2636afeae3" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_hub.py", + "bytes": 2965, + "sha256": "8301ea08c402a77f23ab354b5d600cd808120f6572ad8b6830e255e367f6fa89" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_mcp_surface.py", + "bytes": 3373, + "sha256": "cfa2ad51014acf991e4fd1bfd0c5d3b57a310160b9fd27f84bb0a8a6196738ac" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_registry.py", + "bytes": 2830, + "sha256": "afcfd55a6c45fde01077c942a5c6c63d0a825b5a36f86eeaef8929d38789b75a" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_linux_chatgpt/fleet_routing.py", + "bytes": 4977, + "sha256": "fb6b2f38c7e51468f9137918251b4751ac047245154f65d8f62be6eacf4f4248" + }, + { + "path": "FrameBridge-Fleet-Hub/app/framebridge_node_gateway.py", + "bytes": 44827, + "sha256": "7d26f28cf3bd19046337d644d13dcea424c27fc2ada01d1e16b8b0043dfb1428" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/_cffi_backend.cpython-313-x86_64-linux-gnu.so", + "bytes": 348776, + "sha256": "dac8151545022cc3561fbb7a8fffacf68fdb7273a9418552d6a18dda5e986a34" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types/__init__.py", + "bytes": 13273, + "sha256": "a7104a4d439b27a9f74fc0be236b9ba1b7831e6044026802a205abc1298a9bc8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types/test_cases.py", + "bytes": 6310, + "sha256": "d867472acb57b81ce97f78972828e6bc5669ca4db33fd1e6f645c6183846068e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/METADATA", + "bytes": 15009, + "sha256": "614985b278f601b8ef863f82783b991cc3ada4b89f592eb71c7c48c5f2ef2298" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/RECORD", + "bytes": 585, + "sha256": "114b36a7ba292e2b78ecd901d104c3fade4699ccfb684a5d9179749cbaeaeaaa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/annotated_types-0.8.0.dist-info/licenses/LICENSE", + "bytes": 1083, + "sha256": "fe1049884b1a0d9342901e88e07f32925d24b3121d9972b6a6805fb9824b095d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/__init__.py", + "bytes": 7602, + "sha256": "39fae86118c8530fc9163d19c21d4901a6a331083856ab6802dd17a0e1c34701" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_backends/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_backends/_asyncio.py", + "bytes": 107767, + "sha256": "56acbb3b5ca6984a714e23f37a14ae042553ce21802cd57e05bb0845a297b20e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_backends/_trio.py", + "bytes": 45715, + "sha256": "1acf17cd7d08781be37bc20466d266a4dca52825000a12afec8fd8662df09668" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_asyncio_selector_thread.py", + "bytes": 5626, + "sha256": "d8f77114cddcb34d8aa7a052a696ef9914fbabb6acade4d6e45801c44b1f941a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_concurrency_utils.py", + "bytes": 4522, + "sha256": "92f45732b9c3aa041306465cf9fdbbf7e9419d933861b493a17aa19c05027d27" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_contextmanagers.py", + "bytes": 7215, + "sha256": "6089c109a6e211e4be51a3ff25dc5ad426850bbd444cf5bc1d94c720cf11b1cf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_eventloop.py", + "bytes": 6658, + "sha256": "0726547890fd6a531f7324ec791a392334ced0896d12e97f1b2abd8aa4a3a839" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_exceptions.py", + "bytes": 5618, + "sha256": "0786612e40504f8b48eaf4a1bf8f8626e8b8f76209b282034f0d454091249802" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_fileio.py", + "bytes": 34163, + "sha256": "abef9fb7d87963f1512f38409e862b5dfded3ae67c67220dd80f6609f31b6517" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_futures.py", + "bytes": 6177, + "sha256": "610ab9ba19c29ca40c403eb6b4e2dfcc49bb7545d8dc6428ac9747ea085f8084" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_resources.py", + "bytes": 450, + "sha256": "2dde5db827bd37080f7c8ec420e87708c201610eebaba65011aec2152d2ef447" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_signals.py", + "bytes": 1016, + "sha256": "9a34c107685328d3d1954d088678a379079da563861110e23234a5250b05aee8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_sockets.py", + "bytes": 35289, + "sha256": "0bc6fedf0e47f67d05a3883b5fc589be4d26e821a1e8d540005b627811872b78" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_streams.py", + "bytes": 1806, + "sha256": "15ccc5c08803a6790ad1b3835895cca6c50961dbc00f4e24694686cc953c0cad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_subprocesses.py", + "bytes": 7938, + "sha256": "4cc06e18d20bcfee3bc39b7dbdac89d46a42a434fa2f1b0ca212431427b02117" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_synchronization.py", + "bytes": 21658, + "sha256": "820b1def840f77ce3a7da0361d0793d3b38d4391d0d52efc832f26bc1dea0abc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_tasks.py", + "bytes": 15391, + "sha256": "27676133f26adc6aec7e0fde9cd5a9473d92c5f335e6e2edfa911fbe842adee0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_tempfile.py", + "bytes": 19726, + "sha256": "220a0108568f86b16fb05f4a1dc09cae70919fe52a31fc345917520d68310222" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_testing.py", + "bytes": 2340, + "sha256": "651b9c2e1e64aa188558024c31b77cc99eae22fcf4aeadd6c0bef695da2cc405" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_core/_typedattr.py", + "bytes": 2607, + "sha256": "82df43ce7f882f4416d786873c30fc3c0b6af7915828d36ea47c9fdfbc5918f4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/_lazyimport.py", + "bytes": 6082, + "sha256": "2c28e761a9c87d813d1be0effc4b2a34b0ee551a693627166630937d2f7eb095" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/__init__.py", + "bytes": 3050, + "sha256": "5c856f088d9cea8c9a39ca977bde24dabcd4e7fb3bf99367971193937e07e13d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_eventloop.py", + "bytes": 11127, + "sha256": "d3e530801df9d6b928702b7c9b77e34e670dc761719f9da7528ba31b90f332f5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_resources.py", + "bytes": 900, + "sha256": "86b6f42d75201d2a778bc1ba5bc249721773297820d66d13f8beee7579afdb2d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_sockets.py", + "bytes": 13162, + "sha256": "8d05a73d7ee20c80f2bf2bd591bb858637b3c38a08d96820cdb5ba565dbe7aab" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_streams.py", + "bytes": 7620, + "sha256": "20c78e3cfbad15745bc9e02eb715751c97bb154419629daa2ab249a4fd430afd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_subprocesses.py", + "bytes": 2123, + "sha256": "083a94d69d07b814b4a57533056fe16df86285325133aee7fffeea666a0f53ce" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_tasks.py", + "bytes": 7070, + "sha256": "1c13e94fbd9a3b2d1bc0c0310422a69d7b11df6f30098463626de2695c367d0a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/abc/_testing.py", + "bytes": 2145, + "sha256": "03d63c4400a520f9f503468d1c137c628691cbf30a3a96c51846d31c05e007a5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/from_thread.py", + "bytes": 19415, + "sha256": "89965b7dc3724a2e3001cc75fdcc40404e09d18642f3ca9130862a43a485f6c7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/functools.py", + "bytes": 11797, + "sha256": "4f8252f085eafb1d52d0b6e8da8c05f25f5fcdad8aca93b5e3b40b7b2cf8723b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/itertools.py", + "bytes": 16255, + "sha256": "4d2eff5026b663772082d68b9561db8cec9fe9084f7d051d3ddb8d5be75d5d31" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/lowlevel.py", + "bytes": 6280, + "sha256": "4325a4bdf7ad6c21f498792abf5cb11b039a899edfcb18696641d63973d5e495" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/pytest_plugin.py", + "bytes": 14516, + "sha256": "a0b5f23acb6f4595b75556ea20e0e166c3ede234b3d941a1e3861e1b69c95f1d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/buffered.py", + "bytes": 6746, + "sha256": "bbb8420fc48dad81dcbad1ba2b9c048b2d45f3cfa98b3823bc414cdb62aad82c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/file.py", + "bytes": 4524, + "sha256": "ea3ba32369be409213aaa2856a6aeea57fc336c53bcb6173de898b67138bb98d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/memory.py", + "bytes": 10793, + "sha256": "ae9c4dcc331a83d3c23b29c1a8deb82b2b807c03c1e35bc5c913fe369a3e944d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/stapled.py", + "bytes": 4845, + "sha256": "509d9182dd0d526335568bd134ea19a2273deb495c6c38dbdef85f660ddbfd9d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/text.py", + "bytes": 5765, + "sha256": "05c540189c35551bed22a9effa8d116f4a701fba7cbf096f976d711eae76d9a8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/streams/tls.py", + "bytes": 15818, + "sha256": "7ea2e6c6f50b14ef6bbf11b2277dd53eb38837b1377a2535abdd019262a2810f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/to_interpreter.py", + "bytes": 7100, + "sha256": "0139e84fa91e8bdc0399066d5d30374ac63808aed72abf136b8ff1e62ea96d3c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/to_process.py", + "bytes": 9850, + "sha256": "a7a9fc8f49eb789525d4312447ff4f2fa291a0ab4da85c4e4539fca9906f9fde" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio/to_thread.py", + "bytes": 2750, + "sha256": "f0c3d5aeb42af2db46bd5db571277af2b32da396e1c49d3e8bd0aea576a0e46b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/METADATA", + "bytes": 4733, + "sha256": "043aecb571af7189e8a6c528f5d8e32545f1b2cdfaaf761d51e6d5b67ea3ca81" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/RECORD", + "bytes": 4250, + "sha256": "f4042bec513d826bf50bbb4a6f09805f50d5e7968a337d9bcad375e17db309c9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/WHEEL", + "bytes": 91, + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/entry_points.txt", + "bytes": 39, + "sha256": "fdde98bbaba269998d7b40b2768c22ac4f429a0ef350bda0d3cb50a684b742f7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/licenses/LICENSE", + "bytes": 1081, + "sha256": "5361ac9dc58f2ef5fd2e9b09c68297c17f04950909bbc8023bdb82eacf22c2b0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/anyio-4.15.1.dist-info/top_level.txt", + "bytes": 6, + "sha256": "420952322597f3fe5da685401081dd118cefa8531d4985a60a3ead630d884e44" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/__init__.py", + "bytes": 2057, + "sha256": "7ce608bedd5e192a90adee2e092dec256299d26c00b82f140faa9b6b9392f7f5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/__init__.pyi", + "bytes": 11325, + "sha256": "a55188980515a2fab6fd3625febfc72189c697239aa02b84871a3e12fb279d27" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_cmp.py", + "bytes": 4117, + "sha256": "dcd9f54e3c659546225ff9c9a159c4917a0393484cd4360a8f90c4ec665ee22d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_cmp.pyi", + "bytes": 368, + "sha256": "53efd153f5193b23d4110cd713a44c61041c8e4651636e704c7f7db0dd2cecc3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_compat.py", + "bytes": 2829, + "sha256": "c7483b88450e9c15490bbdb3c850a06f578aab2c52fbb7f62c69cdc99febf79b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_config.py", + "bytes": 843, + "sha256": "746ab7c51e9f819105e9406dfcbd13f9e507201e22e37911987d0fdbcb09570f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_funcs.py", + "bytes": 16479, + "sha256": "231e481134dfcf9174d45fb5d8c17f0921689889f687c6faec4555cf6802b411" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_make.py", + "bytes": 106129, + "sha256": "1fb387d9e592e429c1ccb5233451355b299f3eb985d6bf1fbf644f76df4cb980" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_next_gen.py", + "bytes": 26274, + "sha256": "050b42525cf08368161d3617050beb118067cc152b0ef3b9eeed0fcba5023e17" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_typing_compat.pyi", + "bytes": 469, + "sha256": "5c33f9e13527f992a10fad9339079b9b3af0172a268540a8191a5c95be9a9510" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_version_info.py", + "bytes": 2222, + "sha256": "c3847e1580b734afe43241945899583f87150251fd1d1682fae9b77dc8d89073" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/_version_info.pyi", + "bytes": 209, + "sha256": "c7f3372f75ae07baff50b5c05a3c7de7db9d290e072c1f25fa1b1cd450c636f9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/converters.py", + "bytes": 3861, + "sha256": "1a50de3b33de4c581e0412db8fd1b9ec4cf994093af2e85200b45827f7b17bce" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/converters.pyi", + "bytes": 643, + "sha256": "a2b5366df7fe56341ada4303caf9cc415ef7a094f6591c90bb0e19475ca6d5b1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/exceptions.py", + "bytes": 1990, + "sha256": "6f8bcc6e7a10dd5a7059986aad88bfb2c5d508af28d9ce074124b4f5c49433da" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/exceptions.pyi", + "bytes": 539, + "sha256": "cd9abc6c2527280cbd983b41130e366613e1014207a1329e7434089c90fcf373" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/filters.py", + "bytes": 1795, + "sha256": "64188a58ba774742df099b2aed7d75a67f565fc36c952db05cce23b272ce19cf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/filters.pyi", + "bytes": 208, + "sha256": "dc9e411be753c65b41935ffe46e351507aefdaabb5bfcbf868334043bfe689f0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/setters.py", + "bytes": 1617, + "sha256": "e7e75c4fadc640adf938d1334a07d70a46c1ee93e4691faabf5e669b83d54b34" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/setters.pyi", + "bytes": 584, + "sha256": "367564685535041e0907c13826e5f2af34d482fb4ca63f29df005d258eee8b1e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/validators.py", + "bytes": 21553, + "sha256": "9b7411cd94c036be1fd82e1e55d528160d753605d66a4f166ade2a4131a1bdcb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attr/validators.pyi", + "bytes": 4087, + "sha256": "80cd59987681724c98588d848aba51373aa6dc1d7d730e48aba0782a7a3d6023" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/__init__.py", + "bytes": 1183, + "sha256": "47168064dc1888487e7dcbc72d936943f0d62a78bbdccfe3c443c47ed8aad597" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/__init__.pyi", + "bytes": 9416, + "sha256": "da057bf60f54c49a69192338f2101927a87f3076fbd1d66824bdf564d25e6582" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/converters.py", + "bytes": 76, + "sha256": "f244258eb5707d24d1bbc20dc0493c488d1e1abce659fb6c4fbaccd04ab2a213" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/exceptions.py", + "bytes": 76, + "sha256": "0020829a0d7dfaf0c56833d8f6f165d7df523d709030dfdb10db380c02e3ce6b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/filters.py", + "bytes": 73, + "sha256": "54e50c66e83db8453a754b80d1d1758c89d42b43cbddf2e03f45414b977e0831" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/setters.py", + "bytes": 73, + "sha256": "78bd5889d6105774f687dfd2608652651d4501c1c66f54ee093cb41342efd925" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs/validators.py", + "bytes": 76, + "sha256": "c5ccbac03e53b5391d086d5fe1759ba1c3d23b47da06393921f5497cfe92523d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/METADATA", + "bytes": 8754, + "sha256": "4cd40e690f23bf37cbcad34efd6758e062df1df07c86833f7e3ce95bf1fa38cc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/RECORD", + "bytes": 2570, + "sha256": "ece8bf0367609a064951bcf56a1697d8c61b674d0ce7a7d57e0a93777d86b4af" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "41c708c5adba6e097513ab8ccb9f1d7865a2fb469e22491a9e01dcc64da459fc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/attrs-26.1.0.dist-info/licenses/LICENSE", + "bytes": 1109, + "sha256": "882115c95dfc2af1eeb6714f8ec6d5cbcabf667caff8729f42420da63f714e9f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/__init__.py", + "bytes": 94, + "sha256": "7a52e5f4205f9b3d12a31898ceaa7e3f6837d19cc230700bcae1a9d91d1486c1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/__main__.py", + "bytes": 243, + "sha256": "c410688fdd394d45812d118034e71fee88ba7beddd30fe1c1281bd3b232cd758" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/cacert.pem", + "bytes": 240216, + "sha256": "9cc2a774b5198dcff14d9be1e66091f538975d867ce029a96bce15a55dfd730f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/core.py", + "bytes": 3394, + "sha256": "5c55f2727746e697f7edac9e17c377d8752e0da7ecca191531b3b80403d61dad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/tests/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi/tests/test_certify.py", + "bytes": 467, + "sha256": "7c179d1edf37f7e912b133663735fb46c6338d5d0e5f8915d11c3bc287d227b8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/METADATA", + "bytes": 2474, + "sha256": "ef5af1638fbb23676ac3c5777dfcfc2cd9c348fe4172ed5ba3d277655b248090" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/RECORD", + "bytes": 945, + "sha256": "ffbaaaa116c13b3c0337a451c316aab71c19aec40b67f4fc971e4685565bddc5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/WHEEL", + "bytes": 91, + "sha256": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/licenses/LICENSE", + "bytes": 989, + "sha256": "e93716da6b9c0d5a4a1df60fe695b370f0695603d21f6f83f053e42cfc10caf7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/certifi-2026.7.22.dist-info/top_level.txt", + "bytes": 8, + "sha256": "28cbb8bd409fb232eb90f6d235d81d7a44bea552730402453bffe723c345ebe5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/__init__.py", + "bytes": 511, + "sha256": "c47b2db28c5f197e242d4dd4af95b7ebb0da65763fc76972cafd06f93e942cda" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_cffi_errors.h", + "bytes": 3583, + "sha256": "5b383ed01bb5fcad8610ad29494e5be90d86000b9d71a17cec7346f0ba05996c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_cffi_gen_src.py", + "bytes": 7044, + "sha256": "c94479de91526ec6b7b2c17bfd53aa5d368a78be402531ccfddc99f48198f6cc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_cffi_include.h", + "bytes": 15234, + "sha256": "cd8e8a168a3f6e96b4d8f6a042cd61cde5bd3b9eac9e2a25a32c6abd1280a2df" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_embedding.h", + "bytes": 17554, + "sha256": "3e101d00c70a0a5013559fa505c65e0c4735d85e85a9575a1ed689d8f920e84f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_imp_emulation.py", + "bytes": 2960, + "sha256": "4714441bccc06c8d913c6070c3dd2eff97e2f2c59d6a1a5d8a93a83f3929ec2d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/_shimmed_dist_utils.py", + "bytes": 2230, + "sha256": "0638f6c26f3265bbc5bd6131e4011f9aa6aa6726458587e8c8b2d01e45d9b9aa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/api.py", + "bytes": 42013, + "sha256": "7165f5665b1c98ec9392fb61881a5458b712441b6a9699fe9a806ee1eb019b32" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/backend_ctypes.py", + "bytes": 42283, + "sha256": "4fb092735e0fe6fca5a22e61ca8c7af2a841597d3bf68c075da1836d8fd4edba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/cffi_opcode.py", + "bytes": 5723, + "sha256": "e7ed5c53aa691c6cecb2d17f7a88250fff2d08393bebf6619338f77d6e1289cd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/commontypes.py", + "bytes": 2805, + "sha256": "ecdeb33ed08596fc57316847574f29b148dd6082b65e0b0ddf2a39760b9afefe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/cparser.py", + "bytes": 44754, + "sha256": "598fa8c785064ae552613b609b4d8598681b99cb9a6812b79a816591d1529ac5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/error.py", + "bytes": 877, + "sha256": "bfac53892e14d24bc3732e21fc10d1a39bf7f5942e8fe20c4582efe444dd759b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/ffiplatform.py", + "bytes": 3584, + "sha256": "6afc458dd8a460626812d9893bb7b0566c06fd511597a119fd668d859602aafe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/gen_src.py", + "bytes": 132, + "sha256": "4d4fd9543c282e71cf95a97cfe63e6a71ab2589ed4ef9da34dcdd4b13984d502" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/lock.py", + "bytes": 747, + "sha256": "97d4d37703083298ba8c39091a742013d72f4c847b0809ed209afc1061edde96" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/model.py", + "bytes": 21752, + "sha256": "700f9b10089b70751bb5ba9f80cab408b42314f02357425247333229588d805e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/parse_c_type.h", + "bytes": 5976, + "sha256": "39dc107f033d92dababe5081e377b11509b10c1b63d8c04d74af0b625d79b63c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/pkgconfig.py", + "bytes": 4365, + "sha256": "bb2b75d7b4d079e6b77636a03355b9828f11a67c921938280b337c407c997657" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/recompiler.py", + "bytes": 65140, + "sha256": "4c9545d3b985cd819357ab33a76ccfac977674928737e781fd2ba22621a5f4ac" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/setuptools_ext.py", + "bytes": 9566, + "sha256": "a59ae74ea54290632abbc37123191d6c8b65eb981e84c3f52e8ada51d4fbfb85" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/vengine_cpy.py", + "bytes": 43030, + "sha256": "ad1a2e610e819650085cd8d91933d45b9e2b07de76aa2de0726d7a4965d628b7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/vengine_gen.py", + "bytes": 26931, + "sha256": "67f2aa685eabe789f98b886341385975bfb50c14c48a69d4e32e04fcac520838" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi/verifier.py", + "bytes": 11154, + "sha256": "c48cd5f220cc859d7eaa15f74515f80d6a36934ed6a5c271761840157023ae66" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/METADATA", + "bytes": 2527, + "sha256": "558bafb9093a8e790175d9ecff9688382981d744877dad86fd6770db0f4dc918" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/RECORD", + "bytes": 2413, + "sha256": "f60d0155aed8caaeacffe21e0118508f904c59c24a4d082988573379dad8d4b4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/WHEEL", + "bytes": 151, + "sha256": "ea6b375e7cc19dab7fb19cd9ac389dcbeb6e8c2ca910f839cca523ea3de14dba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/entry_points.txt", + "bytes": 132, + "sha256": "a6b6108011f8e27050fe5269bba88c8051b414697880d3b0b6fbe3a03a0b001c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/licenses/LICENSE", + "bytes": 1123, + "sha256": "5ba24ddc57067f9249add644c3afc41a5d6dc37e23433ef759d95df370b0af63" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cffi-2.1.1.dist-info/top_level.txt", + "bytes": 19, + "sha256": "ac4ed6477ad97cd2b1588f7e8e7ea1b0708097b303901f859ae41bc568c57a14" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/__init__.py", + "bytes": 5145, + "sha256": "5abfc54d37d47cc788b7e7a05e9514787f8c5a0b7db429d0f24d748ac89964ca" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/_compat.py", + "bytes": 17939, + "sha256": "8db1da2965cf3e5dc66c6c53833cbc613ea1cd2440b9d0eb6aa8360eb46d4913" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/_termui_impl.py", + "bytes": 32805, + "sha256": "88b97d9b7a5b947b10a16d8ab269837bc84c7e55c28ba72ea5f2fb339112336a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/_textwrap.py", + "bytes": 6270, + "sha256": "ed9d0ded59a7fbae933524d4c29e8e5c96dc5174666044fd87d736b9c0fca104" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/_utils.py", + "bytes": 996, + "sha256": "782642b7026db180f941892435627c318ffc001223cbc31cce030cc95637dab4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/_winconsole.py", + "bytes": 8543, + "sha256": "292c5f35b3256116ba18e26e08b82c8363dbddd56480934fa8b24f69ef8f6b5d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/core.py", + "bytes": 147845, + "sha256": "4c65a613c1c407dce907a4e123b12cec5fe0f62088a8b9f86fabd4b60c4b6d78" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/decorators.py", + "bytes": 21680, + "sha256": "16069357615691fcdfc9c794bb515f6009eb35aad6f7a76c017bdce06dae8d55" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/exceptions.py", + "bytes": 11862, + "sha256": "1ef498df81b86ae8ff6d8451f3e4fc094f09c2aff5f8e72c454e1ecdfa337849" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/formatting.py", + "bytes": 10444, + "sha256": "f125b628692f8dfcfd43535b7a88cc1ee64137471f9d0243b389aa0cfea85e6b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/globals.py", + "bytes": 1923, + "sha256": "80cf8d87a0383341c1fd2824685e4ce2770618c0c773f7e51d7bbdfe88781845" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/parser.py", + "bytes": 19052, + "sha256": "a09f9f53fde6bf1ba022e36d1da0804d9e7a9601261d18208c9892a84818ae30" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/shell_completion.py", + "bytes": 26172, + "sha256": "18522f75ddffe655e943978d1953255c447842867d3fd67fbdc1543d3df7da09" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/termui.py", + "bytes": 35370, + "sha256": "8ec8022b8e4528f873ec413194a5d1614e96da50b6c4a71f6260b326762a7e9c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/testing.py", + "bytes": 27584, + "sha256": "76f4e649bc53000d667c8c0deff1591970fa034b0f085a207165839b2301019a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/types.py", + "bytes": 46842, + "sha256": "1e988904a7a7f68abad630c7fb9c038a4345fa2e6701c976a6e51f10bdfb52dd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click/utils.py", + "bytes": 21483, + "sha256": "f43d5743dc07240e612feefa0a69d2b1697b6900149f8f809ffcd97ecb482dbf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/METADATA", + "bytes": 2567, + "sha256": "e87bce0bd194de70dfb8e708e0a6b0483009f25611804eaf87bfeb63e6c20501" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/RECORD", + "bytes": 1640, + "sha256": "3ac6566ea107d9a43b9756f87cfcdaa2ffd93ab2c7cf4021c21bfb7ec53c33b4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/WHEEL", + "bytes": 82, + "sha256": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/click-8.5.0.dist-info/licenses/LICENSE.txt", + "bytes": 1475, + "sha256": "9a8ad106a394e853bfe21f42f4e72d592819a22805d991b5f3275029292b658d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/__about__.py", + "bytes": 445, + "sha256": "49425154c5ad66852ef967592df63ba7fb36c66e5842334b163f3462b50ff45d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/__init__.py", + "bytes": 364, + "sha256": "9ad86e52b4dde0544e0a9518ad322a863cfab3a4fd763019ad5ee7675a0c9b6a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/cobblestone.py", + "bytes": 693, + "sha256": "4fff85d3cf8051fde58daca965e92535357585639b1f9ee8a38e66552ce4f707" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/exceptions.py", + "bytes": 1087, + "sha256": "f37e445882dcd9fc31c3e83214cae27220b64aa8558844c1d742c14b2b670c87" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/fernet.py", + "bytes": 7687, + "sha256": "dac2c23ce99e8e13956ee75c4b742c3353c8b876f109e45c46d5a49dc437982a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/__init__.py", + "bytes": 455, + "sha256": "e48c2b2d6ad5a7402312bff815d586fd5d39ecd489198fd6e1e80d36cb9cb748" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/_oid.py", + "bytes": 18150, + "sha256": "3a17b69d413a5e32b1d5417a4c0b06532daff06509f958bd0d2e32c52ba4b795" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/__init__.py", + "bytes": 812, + "sha256": "62a613a071858cb38a8a0442d341c68fa86b8c4c1bbb548fa408aeebebbe7daa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/asn1/asn1.py", + "bytes": 19152, + "sha256": "f6671fae676f2bb2ad8e1458a1f3330235b4bbb9efa9b7ecbcf7db84d40ad414" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/__init__.py", + "bytes": 361, + "sha256": "3b98ef28541d6675e129ea89f87b6e95a10bf4d8bb9abd660f3658e641e56212" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/__init__.py", + "bytes": 305, + "sha256": "a778e625f9c26a0f62139b1d32b37a56f544bb9e6ee3ac5a4bf223a08d12ae60" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/backends/openssl/backend.py", + "bytes": 10475, + "sha256": "d0d73315b8b01b17eaf6a8b6f281d8af4f2bfebed5420e005c78dbd2814fc2a0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi", + "bytes": 2285, + "sha256": "b949e7bceabfb9a99e62704da66b2fdd244054ed16406a63239f1a47594be0ac" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi", + "bytes": 228, + "sha256": "4ffe53a583bc6e08483e3010dc1b40001ab0f82718b241e9f76c26e4f358e9d7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi", + "bytes": 354, + "sha256": "06b1a30bc27a9f0b92fabdc455c757241f27768b5f63d99b41839fa5b3c6d070" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/cobblestone.pyi", + "bytes": 1325, + "sha256": "f6551fd56d614ebb33109e18a527093483d20fd04e33ba3e0bcdbe9ad4831bda" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi", + "bytes": 3866, + "sha256": "30198e6732d37663c9ebed4ae80ab73d3b872753068117c8bb484881a83ba1c1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi", + "bytes": 640, + "sha256": "7b15ebdb1c3fd29075924f777186ccdcca216f3a149233a6b3564c522d2e4191" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi", + "bytes": 4020, + "sha256": "54f556b8a1c8f4432cd3d64b458006bd1d08cf49823261335c0920247a2fa683" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi", + "bytes": 1578, + "sha256": "3ba18ecaf0f1b7aca589c0766851aebb945c199cbd5232a762a191f2bab3bddc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi", + "bytes": 4582, + "sha256": "eddfbec5d734beecd17b84b6bae41a1a6fbd1862afe0d7122d070bc9671a0fab" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi", + "bytes": 1315, + "sha256": "2e13f31d649726ae20ac02579facd2bd249d57e698208b1c1c3c083e524618fb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi", + "bytes": 564, + "sha256": "9cf1f45f9ed1629b00911a305698d0887139eba4e1513c7b617aec69d9ab9879" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi", + "bytes": 1564, + "sha256": "6774c2f86d38f931ed49d00e3cb3358761bb9a5e5b75ad4495951c9f9c29ba19" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi", + "bytes": 1299, + "sha256": "a81b64823d9a95bb76a8572767d503ae1ce83610953bb1d36f2e5549fd445cc2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi", + "bytes": 1691, + "sha256": "cc9cb4a516b99fefe9d9d9b8e4fc44081ffe07a49567234a7e3c450e93ea4f7f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi", + "bytes": 532, + "sha256": "5577d77791ba8548af837f7d4750d87665b77936f411e6f30d3aa3442da0691c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi", + "bytes": 514, + "sha256": "631e3d96a7678ec0fb6f188357591c68cac392dba0e5ebe2e5aeb37ab322cb6b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi", + "bytes": 1076, + "sha256": "6c57b74f5dd47b24489d424c0fe47902b19318ba960e25a8d8bbf6286cd46dfe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi", + "bytes": 702, + "sha256": "057667ecd0e32f72406d85b4490f298358b20b90db4175615008b0b22f03151f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi", + "bytes": 2914, + "sha256": "a7f8462e7e981fe11aac91755796d4b14b638a9be2100a5c4793b4b141c92ed7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi", + "bytes": 6619, + "sha256": "1e288b74407d9ea32c4ef330885a694297cfcfa7c7533f7f8298c407f5c03970" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi", + "bytes": 912, + "sha256": "b5e22df0ce9910c26b9f8b375b45275b40d9fb7d0977af169d2b0a286d76e25d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/keywrap.pyi", + "bytes": 733, + "sha256": "9860891284389718b8091393044b94cdbf54e24cbc9f9d8bde20ef08e5cd0512" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi", + "bytes": 1421, + "sha256": "1fce170d6d134372c785d1890f9b8f6834f98a31222ba319cad5d873cac135fe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi", + "bytes": 835, + "sha256": "6f2a1ebace3e9483a55a5122450742734d2a0a45e9381e6978163aca8f6293cb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi", + "bytes": 585, + "sha256": "fd25bd36d4391439406dd72516d5a94f89469b1288287a4dfb88fc27607361f4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi", + "bytes": 1364, + "sha256": "d8e4023525e4c6073edeec35c620829682104d5ea9f7f90aefd62ed2b85980f7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi", + "bytes": 523, + "sha256": "7b09f81a94326fbccfc04fa78bb1adc9080c0b403598bbaa62c4b2aafea723fb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi", + "bytes": 505, + "sha256": "8ee4d94e6962f233bfe5572e7e0faf1efc7fb42c9ece648b221ffd3d4dd37332" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi", + "bytes": 1605, + "sha256": "bc411de700e266f6fc64615ace22c26962f3030a06fedbcf5312d0c39fee3a5f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi", + "bytes": 1601, + "sha256": "b711812628ea66c84472aadae9bc8f35b9e2b08765c733922073f6865f5aacfb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi", + "bytes": 757, + "sha256": "3cf86577e5a43bbe3789714f79b786d0bb602b4693cc6763708b1acb51a6e461" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust/x509.pyi", + "bytes": 10211, + "sha256": "2d54f886d04db1ee608a3421e0685535811b00ed04d40360136f2d5aac378382" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/_rust.abi3.so", + "bytes": 14389392, + "sha256": "3d6423664bffe926fd91d1913d1c5ddda5c31ee7ff1cae06fc8b71c47c9501a1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/_conditional.py", + "bytes": 5611, + "sha256": "bdfce2fb11dd4ad01c2508cbaaa3a54a8b3de0bf03feda85b417bb3afc3e2ad6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/bindings/openssl/binding.py", + "bytes": 3706, + "sha256": "16cb8bc5b4e7aea5bad3e7405ad6a482e7deabb4e7a9a41a8b53dcec3a30ae3a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py", + "bytes": 3752, + "sha256": "5923e3336b177eb2c35ebb4de51ecdeab0355c5edbf772357bf96f0f8cb1da80" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/decrepit/ciphers/modes.py", + "bytes": 1649, + "sha256": "3aafcf1300a47b938b73339faffbf300e27ac0fc7eae5b2f1c05cf06e8796fda" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_asymmetric.py", + "bytes": 532, + "sha256": "46181ca2e501e874e214306b4752f1aa4323a54c4888dbd0d6bff3263446eaa4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py", + "bytes": 1522, + "sha256": "121de2ee5c1e7477e2d1e2d2b07f773d9c502b363d23a9642baeef2f8579b4e7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_modes.py", + "bytes": 3075, + "sha256": "fc4880383f096fa5a915796ec247144f7102cff4c7f318c6bdb4251a4366e7d4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/_serialization.py", + "bytes": 4440, + "sha256": "862d3125b941019f29999c769566be96bba9871befbc6de0f4334ad06f0e75fb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py", + "bytes": 216, + "sha256": "c0709b59f69e7daf9f93a4c74b0f6d87d7c952c4ad268ef6e39c1f141aa676e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dh.py", + "bytes": 5793, + "sha256": "467b767cb683a501234125fc7cdb659935ce2478e5b7f0ca419d5595f85d8dae" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py", + "bytes": 4482, + "sha256": "910cd3562a8023d3e210b34e05ca3f9fa31f3f737d7a120f91276e3a9ba9b743" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ec.py", + "bytes": 10183, + "sha256": "2e495508b2f16db433aa213a1d16984d16ae2cca61614c37b44e9cf28048c584" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py", + "bytes": 2998, + "sha256": "99b7df2efb3edcf8020de835ad05d55e1ec62e7b6554be004e390811ce29b793" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py", + "bytes": 4002, + "sha256": "9f474aeb2f952f1bb0fb3beb0f1b8f4f8e7df21ca6817fb5472fe3abd6944211" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py", + "bytes": 13803, + "sha256": "2c731a694d26eba12c4ab4a87567f87b691d99d0d14c41910b5cbe7c22b118ad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py", + "bytes": 7901, + "sha256": "c2f5647854fee287fe55c500111dbecc8ff98574faaf04c93cc090f2017d1930" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/padding.py", + "bytes": 3678, + "sha256": "8db81a8ef18763090f8cc0e58c7bb99713019437e60b15947b6005d92e8a135d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py", + "bytes": 8492, + "sha256": "94813b9467b8e3d5b95112d3282a6f1467ab213a2e2a8655f46c2e4f349cb12a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/types.py", + "bytes": 2588, + "sha256": "4a32614eb9c544b9533dd62b043d69a0c8ba723606f9f8747d221a427dd0522c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/utils.py", + "bytes": 821, + "sha256": "42cf117bd1853e35bfb4da7fef721e2448cf09fd2c94e22c58ec68eaaca64fa9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py", + "bytes": 3888, + "sha256": "950720524f8f8ae6e3f329c535641e6267b7b5864c80742a9112a42033872f3a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/asymmetric/x448.py", + "bytes": 3913, + "sha256": "6f7ee283b93ba681ba4890ebcb8da3f849428f9ab8790d17e421663744a7f33f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/__init__.py", + "bytes": 680, + "sha256": "7b21179a393afc265768e3d80ebeef018197af6f50bf38162f6fb8092a252c5b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/aead.py", + "bytes": 634, + "sha256": "173972c7bc3c29841a9330e9d735a026722beb6ce066b815875bb68787b1079e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py", + "bytes": 3496, + "sha256": "2067c2c9c6093a1d53be8237e2b7f0f0a7c9ff19add69fc3a4186dd8582171ea" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/base.py", + "bytes": 4253, + "sha256": "6810bb1c7041a22c5e6e6a5aad5af4525383b37543d00ec1ea8cff01a4630eff" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/ciphers/modes.py", + "bytes": 6003, + "sha256": "1fb0194a02624930428b482b2fdecdc5a912f79d09c852982ecadaf2d8c73f14" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/cmac.py", + "bytes": 338, + "sha256": "b33fece87fdc6273afc7e54d59d20a85185edd89a9f33f09d03dc206a397de08" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/constant_time.py", + "bytes": 422, + "sha256": "c5dba7593d277fc3af29d72a52186514a6b21a9e3f3e0549454d96d702d2aff0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hashes.py", + "bytes": 7916, + "sha256": "4abb7081cdffa45bd97c88518fd7b5a7859d8be17f3e995e18796a240460b4e8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hmac.py", + "bytes": 423, + "sha256": "469077cfdcf9b248ab090ae6ef341bb67a7da4b327023ae54d4bcaa85e5a0c37" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/hpke.py", + "bytes": 865, + "sha256": "46c1e2b2c0b997e7533e7d69d896dcac8ac09c306b2ea339ba0045486a662aee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/__init__.py", + "bytes": 1037, + "sha256": "bf7ca2601194dbbd84a233576f07d865d6db7c8f5c54e0821b79d78530dd6b79" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/argon2.py", + "bytes": 632, + "sha256": "649c7e7a7525000e28edbe3a0b5c25cc6fd7010416d3022416050a92b33fc00f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py", + "bytes": 591, + "sha256": "0459ce292ef6b71285d3242f012af435aeee873bcb1e95027242b988d002fda4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/hkdf.py", + "bytes": 543, + "sha256": "33494011f468738929a78fa7c038fdc81faf359ba420e60442b5255ac04d9fda" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py", + "bytes": 737, + "sha256": "0b7c3df6f8c5aa1ab3031cc5387e33cef5681e46f9002e8bcfa00440657b77f8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py", + "bytes": 468, + "sha256": "5dc86493df6cf8c93a6fa2920a8baa764f0557288d54e48521e9e69d6ded2e04" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/scrypt.py", + "bytes": 590, + "sha256": "5f259475452686e23d57a4ea00f3afba3092306bf55d0760d1adb52160a63be5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py", + "bytes": 456, + "sha256": "40a8e145e86e4ec02cb4bdfce1b29fbe3bafd727dd6a6007561b014567a7d95c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/keywrap.py", + "bytes": 735, + "sha256": "b7db7c299bb367852cdf4012521236ecc64f3cf2b2e041114439002be94ced34" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/padding.py", + "bytes": 1865, + "sha256": "413f94f8dbd5d9e4063b5c153db0e234635273d91e4434be8a0e5c40eacbcf41" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/poly1305.py", + "bytes": 355, + "sha256": "3f910f415f9107f1493da869834d6ed13b384bf3e7026b2ba312065db19e451a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/__init__.py", + "bytes": 2217, + "sha256": "7d772ee3dba02fdaf1869465ff06639cca84f430a484231cb11c5f8ffa93bde8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/base.py", + "bytes": 651, + "sha256": "48006bbe8d12f3a41ca01963ec3a6378b5b2da5cd032bca28a827a0eb1184150" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py", + "bytes": 5104, + "sha256": "992f5c14d1b869fcefb1ea1ce5ed4c5a863656c91f2fc37c63f3858e5ebb96e6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py", + "bytes": 13998, + "sha256": "98533b385b99f1c0b1506528a380a87fa06708773f21bc750108e37f19cf971a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/serialization/ssh.py", + "bytes": 53592, + "sha256": "162b177bf9d429d3c67ea10d5612a99a86b399a23ca87f067be5466dcd1dca4c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/__init__.py", + "bytes": 258, + "sha256": "b66319181fa0e08535afb94816a012534d7dcebd2e3e9ff010161cc1d0c22820" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/hotp.py", + "bytes": 3256, + "sha256": "8af668e41adc08658bb2aaa5e27655d175c28f218f8bf8877c3165b4694e2709" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/hazmat/primitives/twofactor/totp.py", + "bytes": 1652, + "sha256": "9b92cfa512f4d24a78cd8f204e3af9e477f3f5a3253d2e7790799591240299d6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/utils.py", + "bytes": 4635, + "sha256": "21aed2d106734b47b3c2920741f63c1b57bea381a3ac78a31175e3c00af58ae2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/__init__.py", + "bytes": 8253, + "sha256": "d0a54d4e646d758357fe2fdec2c4cdcdb4e6d9ed6ac9fa0e85ce1e9a883984be" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/base.py", + "bytes": 28311, + "sha256": "50830a9309acc75d01477fe09c14410814f0429d7e3bcc247d59bb6b31fefe61" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/certificate_transparency.py", + "bytes": 797, + "sha256": "26aa0e203865c089eb60c156e88167efb589d2f885f8f07fae5655defb3d3187" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/extensions.py", + "bytes": 77975, + "sha256": "fa25dcc169c82f5ce5e4c977055245904a3fef59dc5def387a1beacd6d6c2b83" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/general_name.py", + "bytes": 7836, + "sha256": "b0ffeb575d50969b24e31dd75c6258fccbf443fb3d76d8de2dc907b23a4b4284" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/name.py", + "bytes": 15566, + "sha256": "a08db0e95758f33830eeaa4d865c44c79d765b40215835d0ac361fe44b0cd8f6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/ocsp.py", + "bytes": 12699, + "sha256": "61ecba35d155d4c3e3a29db8323fd57a78c4a60de451a328a5258e2b401b781b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/oid.py", + "bytes": 931, + "sha256": "054ce05d71955a296406474a3d39bd478a84944f600061e07583cc640a7b3c9a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography/x509/verification.py", + "bytes": 958, + "sha256": "811d82d9cf97650b5b959853e53e6f8d228eb426fbe1e7f66a53d59847301653" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/METADATA", + "bytes": 4343, + "sha256": "8e9b5570da4686848ee5c6bb83fffc224bcddf9793136731dd050ce0c05fffe2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/RECORD", + "bytes": 12212, + "sha256": "6bc9222d2719f3d44ad3c6ec9e90306cf91ee7113b409a9a3c62684510c08ffa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/WHEEL", + "bytes": 109, + "sha256": "39b425ce6c898e83f8b8cdeeb65dae690d3ab9f2382ca9c3860e77bf6d34077a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE", + "bytes": 197, + "sha256": "3e0c7c091a948b82533ba98fd7cbb40432d6f1a9acbf85f5922d2f99a93ae6bb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.APACHE", + "bytes": 11360, + "sha256": "aac73b3148f6d1d7111dbca32099f68d26c644c6813ae1e4f05f6579aa2663fe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/licenses/LICENSE.BSD", + "bytes": 1532, + "sha256": "602c4c7482de6479dd2e9793cda275e5e63d773dacd1eca689232ab7008fb4fb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json", + "bytes": 45685, + "sha256": "9bdac29ee4d9bfdb4241e04f0fdbd12e5b62fe31706bd3a8bed28c8cafcb55a0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/cryptography-50.0.1.dist-info/sboms/sbom.json", + "bytes": 1206, + "sha256": "4bd5cfec10cb95e3842dc5a52d63ab62e1b2dd1b6e697b4587903ec8de53f720" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/__init__.py", + "bytes": 1230, + "sha256": "6e163e88aeb0c0765a996208e9e482b820165d435d9f64d016610eb97494ed23" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/__main__.py", + "bytes": 129, + "sha256": "3744612c6ee71c8aad9491f0c1ea48a3ecdb24f371f6c7b0082446639dbc87fe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/cli.py", + "bytes": 6865, + "sha256": "8c547ed95b551f492d85cc9c81c55e476aaebed00392c14a4ffdda3b8007cbbb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/ipython.py", + "bytes": 1326, + "sha256": "74740177d3dc7425298466faed7c32dc6914a8c49d7fd5d4522358c53677b585" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/main.py", + "bytes": 15179, + "sha256": "697eb33eee75d78b73db61eaf96bf9a6547548c1811c93501b67e056e5470dea" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/parser.py", + "bytes": 5394, + "sha256": "6052533a6cd4e0ad5052fe88e5936853059c28ed3fec0f098cd29a7cb376dca4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/py.typed", + "bytes": 26, + "sha256": "f0f8f2675695a10a5156fb7bd66bafbaae6a13e8d315990af862c792175e6e67" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/variables.py", + "bytes": 2348, + "sha256": "083d2a5cebefa41deae51a4140c0fda97eaf1d7ed2c96f92ba2c063054a5b74f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/dotenv/version.py", + "bytes": 22, + "sha256": "0be0ff596ad59010e64260292dc9b4b16361d82808af059f73cfec079bef53e4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/__init__.py", + "bytes": 1507, + "sha256": "88ed4ace448ee36c99e9f7e0f953206f1fd9553586518d349d16045a7facde46" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_abnf.py", + "bytes": 4815, + "sha256": "c9b8b1af4c6cba99e403a18503232e6ee5c5e9373595bfe117cf743600ac7cd7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_connection.py", + "bytes": 26863, + "sha256": "93d61155fea4a19a9bb6d056dfac5259a26959d6706bec50554f401c4a3d0ee2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_events.py", + "bytes": 11792, + "sha256": "23deda5e86a5d56bbb7642f9e3c04034150290e21b7bec790a2a1803d201635e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_headers.py", + "bytes": 10412, + "sha256": "3fb0fe9413711f074b64f2e29a6630acf1bef59923125bef25925d6408123fee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_readers.py", + "bytes": 8590, + "sha256": "6b8472a4e454082dddd2afe4c4fb8120cee34c3f222ede57f754c7d05b1db8de" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_receivebuffer.py", + "bytes": 5252, + "sha256": "c6bb29b1db0d81617145f41c4d7c51f11add8d15d74cad08a39710616a49d56b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_state.py", + "bytes": 13231, + "sha256": "ff92c6fc1191f0509c1507813c7f93307826ffe07e1147160a74287fff578c51" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_util.py", + "bytes": 4888, + "sha256": "2d69248d7c89685940cba2eddfdc3bdd44ad925153e68bdcbe8d13918ed162e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_version.py", + "bytes": 686, + "sha256": "1954ac6cf48f0dc3ae17aa6d7c88979d52686849b7ca05db327aa5aff1a26a1c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/_writers.py", + "bytes": 5081, + "sha256": "a052a6e8fb637877db8f844b5fb541eca0dcd60218e778171b7fc747d96d9930" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11/py.typed", + "bytes": 7, + "sha256": "b28c3db284f03fd4ff80401049587b19bf3ce79874e0dc2686cd967be2518193" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/METADATA", + "bytes": 8348, + "sha256": "28f326098ac09fcba79b8f180f96087c841fe2456215cb7b872a9c7d5cd19d64" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/RECORD", + "bytes": 1244, + "sha256": "1f1d4d9080c9f2a995417616ead6ea614da41b477958ed0ba0e45a82c6212721" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/WHEEL", + "bytes": 91, + "sha256": "0a6c85234931e5c7443132e238d4116c64308c8a11d5a21807b782010e0a3c9d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/licenses/LICENSE.txt", + "bytes": 1124, + "sha256": "37db5bb85926db28a427a25867f10b1232003aea1be69ccb851138adb8e6f361" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/h11-0.16.0.dist-info/top_level.txt", + "bytes": 4, + "sha256": "17b742e23977cde87c4c61c43da589acc6deba859b4b7efd1b0762f98bdd722b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/__init__.py", + "bytes": 3445, + "sha256": "f644ff92a0a10822544c7c30db866647f7b371d6e94585a4b03fa060dce464ff" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_api.py", + "bytes": 3146, + "sha256": "ba7666783b1c8415821823c2c244b75a8b7d7ae2ba6e0fe42b12ed193c70db5e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/__init__.py", + "bytes": 1221, + "sha256": "116765dafe2d867007cc9a6a8d4e21d9af03522180bcd896ae48a2f697e14dfd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection.py", + "bytes": 8449, + "sha256": "e8e70f5ea3047dcd01537f3ffa21d4343775bca4937365154f4f57a8d6ff04e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/connection_pool.py", + "bytes": 17307, + "sha256": "0ce210dacd9909ff6a7f0c61ccca6b4cf2ea08bf0ec465e2285eaa447c6f5726" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/http11.py", + "bytes": 13880, + "sha256": "faa33d6d5ecf8d2405e6fc6cdfbf9e5173881706c88cf7196cd962b97f53b41c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/http2.py", + "bytes": 23936, + "sha256": "6b35f57dc9ad5da2308e9bad165678bddf7627cc708c639af5ac7d408bf8dfde" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/http_proxy.py", + "bytes": 14701, + "sha256": "db3564ae5bfe0ecfab5866aa69796b84426201014fa36dc14f71aafec5a80575" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/interfaces.py", + "bytes": 4455, + "sha256": "8d389a58bf37a60a460bdce2bfdd197f068a34c987c2639a97368a8ae4f1013a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_async/socks_proxy.py", + "bytes": 13841, + "sha256": "94b2a02e58203df84596a8b438378190e5afb7d0a0841054caab27b14d6dc7a4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/anyio.py", + "bytes": 5252, + "sha256": "c7c3e01215d10bc6d5aac77393f609c7c63a77d4ee6f4e82b944b09db9adaa86" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/auto.py", + "bytes": 1662, + "sha256": "cced77e8f2999ac6930caf87464f38780f8c520f3fdb025fe0dbe62b8df6022a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/base.py", + "bytes": 3042, + "sha256": "69286045d6673264611561deb63ba694cef77fc2b3d58380c8250fff8907b250" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/mock.py", + "bytes": 4077, + "sha256": "7abf53e37eae49eecd2eb7e22dae31e8dbaa8398af43af770b1598096b206c7e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/sync.py", + "bytes": 7977, + "sha256": "6e113877d88af549b176c76c826d847ca9d76819acd9682019853eee3994ba35" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_backends/trio.py", + "bytes": 5996, + "sha256": "2c7bb8fccaf932cc109a64f7c84e282e07fd6ff2497de552e018c3c5e25ced1a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_exceptions.py", + "bytes": 1184, + "sha256": "968a022a06b7fd8558bb7b3e7772fd44c3d127286c63b7e2bae1b1be4383d1cd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_models.py", + "bytes": 17623, + "sha256": "20ed8271771da68bd170b4dd185181e91c8165d126da1fd33a6a02738ac42a1a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_ssl.py", + "bytes": 187, + "sha256": "b2baa648d53888e52f585f92ac9bdbf06fd811b1c510b39740ec1d0c80534bd7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/__init__.py", + "bytes": 1141, + "sha256": "2410c8817b7995ad4b089d6c2d078a863285a4b9e936bf12beceb3da78b77e08" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection.py", + "bytes": 8238, + "sha256": "f5ec4639bdcf07e329d93d7eb1c91278bdadfad27ff7e357a2657c8a19960a05" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/connection_pool.py", + "bytes": 16955, + "sha256": "6be4fc2d3b14c5ceebd16c356ad7c7483a163e34347c0f1497b4b7f85d0c8fbd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http11.py", + "bytes": 13476, + "sha256": "205a1b0f531de491652462969e1d7f4377a98a452bc88f2aa34f6ff0c8892040" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http2.py", + "bytes": 23400, + "sha256": "031538ca172aebc067e6fa9d258b625ca6148fb9ef8586f1cf7bf8ad3e319ef0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/http_proxy.py", + "bytes": 14463, + "sha256": "fda97fe9cacab8466edb0cafbb8f7745922625d0499e3e6818a3632ce24bd99a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/interfaces.py", + "bytes": 4344, + "sha256": "b275ce378daf5031cee49049be8f03e155a4d966ade38cf63975c70eb8db97de" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_sync/socks_proxy.py", + "bytes": 13614, + "sha256": "cde8195bd4a7aa3dbff7dd831496bcfc2a6939506454be0083076e4644ad6a44" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_synchronization.py", + "bytes": 9434, + "sha256": "cd28b5de6002a250679278d90649d40ba84a343413e02ea28e77b367eaf44f6b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_trace.py", + "bytes": 3952, + "sha256": "724e99a08cd84e474d0087eae4c19e2aa0570edaa3397faa7d8c266456eb19ca" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/_utils.py", + "bytes": 1537, + "sha256": "fd12e05d8380602df9d229002d5e7d199ebc209adda1c459c4fb3d3e39b37456" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/METADATA", + "bytes": 21529, + "sha256": "fe2d4fda6199128978779e0cf27f3c045c531863fcdd987eacc74f3a18d41c21" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/RECORD", + "bytes": 2947, + "sha256": "56c7eb698ca4b47f9eb3ccbae04bdd802a651114200a9fadbff5665029d441db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/WHEEL", + "bytes": 87, + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpcore-1.0.9.dist-info/licenses/LICENSE.md", + "bytes": 1518, + "sha256": "fdcb59154c74cbaba16a11242f7740bea9f23d6feb5547917d8c5f94a80392a5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/__init__.py", + "bytes": 2171, + "sha256": "0ac6997bac998f4ac783adf6d8058a587193315afdb718047c3e4fdff46bcfad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/__version__.py", + "bytes": 108, + "sha256": "2e853261e3974e2786cee3ffeb850bd30c5db718eebbf41b3af13b34e83e22a5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_api.py", + "bytes": 11743, + "sha256": "aff660b388c8a5c3c92ea2b975b6d26b2aa8fe254c2856b164277ea0e1f12c4b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_auth.py", + "bytes": 11891, + "sha256": "62bdd0c1a5122b5eeb198c7eee3f85762705233cf863d14557ed45e3eed15d7e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_client.py", + "bytes": 65714, + "sha256": "c43f941baefe58c91e96d00039e1868fe719d91453026d7db1647194563bff8d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_config.py", + "bytes": 8547, + "sha256": "a4fa7653ec2271f70ab05f8a6111352d876dddee84446788a1767c1a3a372d67" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_content.py", + "bytes": 8161, + "sha256": "2c61b3ac94d1dcebcde0c6f519554d2d7917247fbaa0a97002db4ef69e70ff28" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_decoders.py", + "bytes": 12041, + "sha256": "a74757f08d0d1077b1b37506a784ac66eb62321b17aeb5a5ebe1a7a956f488a3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_exceptions.py", + "bytes": 8527, + "sha256": "6f15bb7f1ce054c01d3536f04f4567ab4e202435b5fe023f1858903ee3328cbd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_main.py", + "bytes": 15626, + "sha256": "0a0f4631a6e24d3fecc1a0df5202118ad4b0c4b44c4b05033a6ecb7521aa940e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_models.py", + "bytes": 44700, + "sha256": "e3ffc6bb2bf580bc6e6428708a6f247d036220e709f5a72b785392babbd97e6b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_multipart.py", + "bytes": 9843, + "sha256": "28e1c465997aa28860f663da2b2caedf8e6aab5ac92e0df94d41b7600cd70776" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_status_codes.py", + "bytes": 5639, + "sha256": "0d89fedae7c180c797cb9b3cc77fd307bc2302e00031ec136a43eb9b9ad712c7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/__init__.py", + "bytes": 275, + "sha256": "19b52805200ea7bcfe97ef63e5884c851dc3308727e8554b863d3bd8edcd9e7a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/asgi.py", + "bytes": 5501, + "sha256": "1d17e20d830fb78c101f6805807660e1cfa2dec6e5a3a6cbe464ea7044fec73f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/base.py", + "bytes": 2523, + "sha256": "9194bf54c6d58987c5e7bd29a205022756ee973fb26df2f9d4faacf7292d79b5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/default.py", + "bytes": 13983, + "sha256": "03379a454c95c0271c4f2c8d25ec437f49f57457f3cf2769748420bf0ecf2e79" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/mock.py", + "bytes": 1232, + "sha256": "3d3a34779ebb4484d7c46ae48ba90deffebbc300317f088c0dcb972626428c4a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_transports/wsgi.py", + "bytes": 4825, + "sha256": "35c3d7dd76a9fc4c0215958efce692c9036e227098c7540c35b3bc1807a2ea36" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_types.py", + "bytes": 2965, + "sha256": "272878d4642aec039ebfc20e58a0c083bcc26ef1c07ee7e65b983f3e24ed12b6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_urlparse.py", + "bytes": 18546, + "sha256": "640987e3b38d7e4c6bae3f8f3d88467a21e36fa0232824be00d58837838bfca6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_urls.py", + "bytes": 21550, + "sha256": "757f7d551d4348e1e9828f40abb3f360ee0529dc6a2a3c32369f20adff1d1cdd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/_utils.py", + "bytes": 8285, + "sha256": "fd355ea802afc4990a1ddcfb74579be2cd0b66a41781e1645d281f88704aff5a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/METADATA", + "bytes": 7052, + "sha256": "febb9b0f8f3e80d57c8199c304f35c4336e8581d1d18d7983c92766b82793b25" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/RECORD", + "bytes": 2223, + "sha256": "0ecebd0f1f2f26e90b04b235c20c996fe53f9fbf24cf0a84c8cd2e668d3b0ec5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/WHEEL", + "bytes": 87, + "sha256": "0b615483066088b6f39d1fa4d1bff9937022ff568048e5c3b2cde5cc252c52e8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/entry_points.txt", + "bytes": 37, + "sha256": "da55647509b12c0d6934c812376795f3da3dd070997b3866370b32eabc8a0d20" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx-0.28.1.dist-info/licenses/LICENSE.md", + "bytes": 1508, + "sha256": "4ec59d544f12b5f539a3a716fd321ac58ccd8030b465221f2c880200cdf28d8d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/__init__.py", + "bytes": 282, + "sha256": "1be17c8e924ba38789c0abcab7a7a34d060f8411e09a4b194cdb9a4324910237" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/_api.py", + "bytes": 2919, + "sha256": "0fba5f91a8e6747966b3be4e57c1e974eace12be4d6d64250f031f485709f055" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/_decoders.py", + "bytes": 4005, + "sha256": "9dad57c9506208b6d113cb60d28ef5dd4e46cfe9bd7c1c583df48c9aeff19ecf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/_exceptions.py", + "bytes": 62, + "sha256": "b05641513d7120cd43bd843a724a73ae1046f5df157df152645308121bf5379f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/_models.py", + "bytes": 1221, + "sha256": "e16c06781106311da76d139367b3f2565bbb72199683bb5b909c97c9de434f1d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/METADATA", + "bytes": 9671, + "sha256": "d4c6d804ee5ac46e9b3b6595c27ab8ae2ba7cfb5f692c78b16ff463e4bd1a81e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/RECORD", + "bytes": 863, + "sha256": "b1f8d8a24f649aa6fcc97bd34012eb84f341d20878d3e7c32ffa3b9f929e3976" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/WHEEL", + "bytes": 91, + "sha256": "ff309ddcdd65ebd02bc724dbf2bcc4a0ff53a5b61792a44548d383e4ebb19d3b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/licenses/LICENSE", + "bytes": 1072, + "sha256": "beec67e4ee83a7af26f242bed9987aa863d6d2dfb776106aa2360c4187f31901" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/httpx_sse-0.4.3.dist-info/top_level.txt", + "bytes": 10, + "sha256": "85b2b7930047f5dfab11942f27ac816b0743ca520a7c9f1f87408ce0454db34e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/__init__.py", + "bytes": 944, + "sha256": "8514c3ed53136a3596ebdf512fa487bbdd7da5a99adcaed82e0363d2c306d3af" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/__main__.py", + "bytes": 83, + "sha256": "e0930aeba5a3e2e2d94ca6c5fac4f72c0c4eb2be9bba283becf98e909091471c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/cli.py", + "bytes": 4245, + "sha256": "2e01972a25e67afdfba5d824013c01e808893916539c0db33f72f37c42b8896a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/codec.py", + "bytes": 8002, + "sha256": "7dff4247d75e6e265cbea1261f10d122ba9299a371429cfa018e091ec0b7602d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/compat.py", + "bytes": 1376, + "sha256": "4ff7995ce52dd1be5381ae7a70fc9b42ef420982b41532b599d88f6fdea76bc0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/core.py", + "bytes": 32580, + "sha256": "3dd4c3ffee4ff223207fac65f3812a1e6895ae8ad1e7a6ed60115768f3551520" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/idnadata.py", + "bytes": 45763, + "sha256": "23a9a8c1d15de9c634d0bf213d779810f485083fe1b0f7e09044ad04a69cc7eb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/intranges.py", + "bytes": 1851, + "sha256": "838f6c70b4a4a89b4084b98e3836bb8551eb8928e66fad2d893b04a287097412" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/package_data.py", + "bytes": 21, + "sha256": "ae759534a3c582a00ac856cfca464a39ee8251d393208d8af06b605bc20e02a9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna/uts46data.py", + "bytes": 237174, + "sha256": "770e849bfa156c71828a89440a57fe9aea70735f103ae4616ee4f682e13d6c00" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/METADATA", + "bytes": 7207, + "sha256": "dbd8c14c1e4ca1e0c9824a6dbc7cbbf78884f38eb52d91148cd3025f671e4b85" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/RECORD", + "bytes": 1185, + "sha256": "8e2670b3fc1bbf1e2cf99050b062fda50203881e43420bbc6772dad2cde2d548" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/WHEEL", + "bytes": 81, + "sha256": "96a37c8d7b7f425a05386731f644adbd66ebca17c45e8236176cfe614466df9d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/entry_points.txt", + "bytes": 38, + "sha256": "ec7de718e1daa778e72c4e5eeeaed8c2bf55abc6b107b5888f9f82ff8376be1c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/idna-3.20.dist-info/licenses/LICENSE.md", + "bytes": 1541, + "sha256": "1a9a4f0e3d479a27240ddd59a9137a66ab4a0f9dfdc8ca6188cc0bfd85187f04" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/__init__.py", + "bytes": 3941, + "sha256": "a7e470e134bfd0e3c76482722260d6b1d5a099de823d61cc5cbabb06e4314c67" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/__main__.py", + "bytes": 115, + "sha256": "88bb197f6ba9501de294129394c9f22be1c7b760a79df930c318bf73a80bbd20" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_format.py", + "bytes": 15568, + "sha256": "96bb3553b892076657f59d846a5b113987909b577d91e7f0d822e8d3c1bc3f8b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_keywords.py", + "bytes": 14949, + "sha256": "afcfc3aea01f9fa40bc109e65c4820bde89253e51a20bda9da7b8f20d7a57c57" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_legacy_keywords.py", + "bytes": 15191, + "sha256": "dad5aec113d66d84bb10097cc01202feb69b1aebf527875f60ba8d10fa528610" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_types.py", + "bytes": 5456, + "sha256": "7ff9e44c36eb8dadc4ddfb083530bbdf2fe03174d4db5d08fc7eea2b434ddf47" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_typing.py", + "bytes": 617, + "sha256": "a572e71b2de336d2144ead37ee451fab4c1ed7bd3ce5e1ed2700c9dadfbe9901" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/_utils.py", + "bytes": 10659, + "sha256": "5efebfc0a2ac941265c328fdfa3d9cf090c5c3ee33e1a5859d57b6a57f21ed4e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/__init__.py", + "bytes": 70, + "sha256": "034b10af10c15474b243ef2bbb72f5d613177f7abd815b81f71fd88076f847d3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/const_vs_enum.py", + "bytes": 830, + "sha256": "0d5162dd60ea05a95914e89b9e3a57d6e4d2af74716b670f805728c1ded492bb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/contains.py", + "bytes": 786, + "sha256": "81ec50a14ac23b0102a1f6edd7d05ea2c419ed614be8f0dd917e3d0d6c0194e8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/import_benchmark.py", + "bytes": 779, + "sha256": "d834483954baa7e6ea2e7ffcdb607f5f9164211bec2765750989d5d9daeecc3b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232/issue.json", + "bytes": 117105, + "sha256": "79a3ce663311bb9bbc4692abb00f6e93bb9c3d94b9b64286e03fe190e4d0dc79" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/issue232.py", + "bytes": 521, + "sha256": "dcb2d82c89411909d5bb2ca8da202ffb1932e4fe8f4451c0371e3ecc82103a81" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/json_schema_test_suite.py", + "bytes": 320, + "sha256": "3ef7da6e9518705e3fedcb180d37136ae103f2b9c510661b758e51a935c3d3cb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/nested_schemas.py", + "bytes": 1892, + "sha256": "9a8d3b771f82220992388eb608db38839c6ed45cc79252c1a644280f159870ab" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/subcomponents.py", + "bytes": 1113, + "sha256": "7c4ca2333b1678ada977b0c4182bae63ebf31ae9f08477334415849c144b288a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/unused_registry.py", + "bytes": 940, + "sha256": "87047038d73d71e7cfb58ce4a17fd36113b71b25288eb8afd3e61068adef9e3d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_applicator_schemas.py", + "bytes": 3342, + "sha256": "1159b9f84b4e105a0b3ff56dda3e12ac2c25c74e4d84fa8db9943a2c830fd437" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/useless_keywords.py", + "bytes": 867, + "sha256": "6e3ff32abd6855cb4596ac9968e6c2b184e01638a23603f30b486bd58f3af261" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/benchmarks/validator_creation.py", + "bytes": 285, + "sha256": "52451094b027bacb27aff29d08875a77ac71da95f140b998b6c5e88a2aca7964" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/cli.py", + "bytes": 8445, + "sha256": "6aff743ada52c6e8a4a37140c841edc6923e352b97dd632da10a48c74f686c96" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/exceptions.py", + "bytes": 15256, + "sha256": "6f8da150339f3c5729ac8e1994da630de2cabfc93dbce86059cb768f20e5c739" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/protocols.py", + "bytes": 7198, + "sha256": "960cabc951ea1628d2151033f79721dd5376173fa84c5ca18fd39b732e4ffc22" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/_suite.py", + "bytes": 8374, + "sha256": "da4d17f7537b74e1e141ce66ad8bf8d0e29fd707a2a23e91301a9680b4faf8f2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/fuzz_validate.py", + "bytes": 1114, + "sha256": "7d403bc932488a1682c09a659147a165ec81f381dc5c472ab58e683c95c83bb2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_cli.py", + "bytes": 28544, + "sha256": "03cf6be4b387cbea5e2cf640e580e439a3135aacd03bfc364eef16173fefa613" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_deprecations.py", + "bytes": 15754, + "sha256": "c86ea6911247a531db5a8c692c2e72e47edf93c7ab18eb3c7fff55e2d081121f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_exceptions.py", + "bytes": 24301, + "sha256": "aec12a763927533ddd13225bebf0e232874f1d7b094c45b30ebc54b1c83c1b9a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_format.py", + "bytes": 3188, + "sha256": "7959b948c69617694e3cedbc6cf03036f92242f1c240acbe327b808047217c47" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_jsonschema_test_suite.py", + "bytes": 8461, + "sha256": "b407f192733ae4e47d2f20cf1eed699046a899b2e08d12e727a14f8963ddc638" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_types.py", + "bytes": 6977, + "sha256": "705e752930e676cc74e8cac87387d72add17f5f22c560c39ba14fc09a9956bc5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_utils.py", + "bytes": 4163, + "sha256": "b1aa3be28d4fc9833105fab07a8910378f021524baca1264e459027cc2793ec2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/test_validators.py", + "bytes": 87949, + "sha256": "bb397e6a0c8ba193d3b9fa2e2074b114f7cf62b7487633708c7703ff93725381" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/tests/typing/test_all_concrete_validators_match_protocol.py", + "bytes": 1238, + "sha256": "2395d4979658512668dc03e4175d25f0c9df934f685caaaf5d6b6d186777b039" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema/validators.py", + "bytes": 47159, + "sha256": "97f171b995b4b0ca05530a9366a1252fb1753f99f3cb7b1068b6d9aab1096fa5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/METADATA", + "bytes": 7592, + "sha256": "05b189866ca98a96da3934bf6b83687ced3ff85325273b77e16b705f6d8b26d2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/RECORD", + "bytes": 3827, + "sha256": "382a461bc5151888cac603114697e4522c3cd6ee2622e4c23929e372556ae180" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/entry_points.txt", + "bytes": 51, + "sha256": "bceeeb5f816cff1215272da99c0b4a8134b17e99e8cc0550d038c29a93319d61" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema-4.26.0.dist-info/licenses/COPYING", + "bytes": 1057, + "sha256": "4f92a015a13c4d1a040bef018aa13430b4f1bc73b41b16bb846c346766de7439" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/__init__.py", + "bytes": 386, + "sha256": "aa84c1d83298eeabcdac684c3c7ea0b6600944b8a599543e7c56704faaf2ab0d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/_core.py", + "bytes": 1140, + "sha256": "b4585cd42325789dc024e2bf6e3c4ea4541376bb140a51467c5c4f054fc279b3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/metaschema.json", + "bytes": 1785, + "sha256": "7b761b3e121f0a0ca1ea2a0b8e2cc856bcf801604b8268d71bacaa202bc0c13b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator", + "bytes": 1860, + "sha256": "6895100e99726fbb107058512bbec3ecfd4b24e8fd2facee3e505ee72b0d4c31" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content", + "bytes": 517, + "sha256": "9b7d4f55a4e2fdb02c430068fdffabc732addce238da3f1df2690249c3353274" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core", + "bytes": 1531, + "sha256": "b5a2c4957f6495d0a5081f0409ebe8a14e4139acb203fc748471f8ede2af5b2c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format", + "bytes": 403, + "sha256": "50ebbfe790611a849b8cc400a09c1d0e0fb6ab5c0d43a0f22201fd3625056bf4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data", + "bytes": 892, + "sha256": "d47e2445dd6a82271a298d83cc6c6cb8d4ae1e15e0dc56becd37a163ecf01286" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation", + "bytes": 2834, + "sha256": "1e526c1d335a734805fc82cf5798c12b960ad7da2517c66cda5a1b09359c3c1c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/metaschema.json", + "bytes": 2452, + "sha256": "41da76f5afb7ce062d248f762463a92f7ca47e4e0f905b224ba6afeef91ded0f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator", + "bytes": 1659, + "sha256": "c4a6e4147b91fef7fea6dc058cb1bf93402f7414b76578a8b16aaf1dad6aacef" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content", + "bytes": 519, + "sha256": "08343747764e4a5814262793cf4d652057a7913863c5950d43297e8e1fdac5b6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core", + "bytes": 1564, + "sha256": "c2d12a8e4dd11d336dfc83a3f663aa4c69f0b49b3beb094ffeb25b5316f4803d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation", + "bytes": 448, + "sha256": "abc775adfefd89d22358170d9bf93f4ebd2349563bbbedd60f02bef7c812bcc0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion", + "bytes": 445, + "sha256": "c52242b9a1bb786b26c3e82c7add428c31f9c96e575dce99e56ea5feaa6da20c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data", + "bytes": 892, + "sha256": "8f76d6e14f41b9b92ef933b708cdc5144c8b5268651ad11918485fb1754f1c76" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated", + "bytes": 506, + "sha256": "2dbfbcb73994b670b0976492adee1fffb46c21682784d2f5a4ca561f9e2d0cb4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation", + "bytes": 2834, + "sha256": "7010a31e541f32d2be721e2de348df75c9b36876a3ed304877fc0abda1d37a58" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft3/metaschema.json", + "bytes": 2600, + "sha256": "2cf75f64436fb5be374a2eaa27ab8b7e1fd651c9b46daea6c67b02fd64e2458b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft4/metaschema.json", + "bytes": 4357, + "sha256": "e1489d0b4755f02793302591d3fcb8f07b6893a82a94f24895f8e4edf11b82e2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft6/metaschema.json", + "bytes": 4437, + "sha256": "c29dfce9f54835c3a06c03b3c5d5ec0eda77706568f9c4df7cfbc7566a51006d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/schemas/draft7/metaschema.json", + "bytes": 4819, + "sha256": "3d5392088261606c559b603f385329c9f1ab45b5d667eb990687453b055d405e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications/tests/test_jsonschema_specifications.py", + "bytes": 1106, + "sha256": "5a46d8456e80e85a19d2b8af4a17ea54b482b00887276c7c4f1a840894d73d36" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/METADATA", + "bytes": 2907, + "sha256": "35abd41757f32b4ea24756920ded47b70173d77cbc052a5a6d3ab583b2e8d89d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/RECORD", + "bytes": 3224, + "sha256": "497d6b8e47548e7fb8e66a8fcefcb717ed1458602e09662719066ce7dfc017dd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/WHEEL", + "bytes": 87, + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jsonschema_specifications-2025.9.1.dist-info/licenses/COPYING", + "bytes": 1057, + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/__init__.py", + "bytes": 1805, + "sha256": "0564795b435a412848db0d678100fd138aa3096075e8cce364751c7e427d60fd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/algorithms.py", + "bytes": 40244, + "sha256": "bec5299c75db1d4bd7a5f9f1c382c85fc648025715b7e522b320e161d3aeb55a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/api_jwk.py", + "bytes": 6328, + "sha256": "5df9913bd4846fe7d257b9c00c39f8bc611c75f83274c98ae50338a7cb20c63f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/api_jws.py", + "bytes": 17421, + "sha256": "a0c578d4e09c823afe019ed3e0b06fb184c665ff34cfe404c68229ae3272269d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/api_jwt.py", + "bytes": 22244, + "sha256": "88fe0067b597c726ab53a49963207822a1a8efdbc138603146bd4324600cda18" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/exceptions.py", + "bytes": 2375, + "sha256": "3540f48760343221d5e94f933edfc0af4a6b9c01f1c892748ba2c7216cddf154" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/help.py", + "bytes": 1784, + "sha256": "1c1dd9b2b2557254745d8fac0c7f8bbe5e4aeda7b9422746894e96c93027621b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/jwk_set_cache.py", + "bytes": 961, + "sha256": "037f79442909503c4a2e3fcce91654912e34e407837a9130ced538938c434a85" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/jwks_client.py", + "bytes": 11764, + "sha256": "00ba62de23c8c80d90fb1f73539a8fbf32c47a959d8d0fbaf51e46800c419ab7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/types.py", + "bytes": 3174, + "sha256": "478e81927a35574f1faaec6756bef502f5bab843e2947265aa4817e8e6d9167c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/utils.py", + "bytes": 4067, + "sha256": "e4e3d4faf0b15c47a049940088e256434b6566920ec7163c172ce47684c29ac0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/jwt/warnings.py", + "bytes": 330, + "sha256": "8904ef3fe34f0d003eb6e84f86389f124611a8c520b5ef7d88e3cc25cfc32c99" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/__init__.py", + "bytes": 3224, + "sha256": "f66ae17959a1420edb67624dcd79da20860543e1d513b7d5c86ec0250cf8af72" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/cli/__init__.py", + "bytes": 107, + "sha256": "349ac92144268dce85b52c6f18db75cde5e748b8fcf8c962f3a95a3834ed4331" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/cli/claude.py", + "bytes": 5086, + "sha256": "30a52e646ac198c4ac943db078b23153b45d417665d8ebee30cabeffe1009606" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/cli/cli.py", + "bytes": 15179, + "sha256": "c3a996066732e32ee7c4b4036b8a926f1295d0159e26a53f48565fdea74eb9bb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/__main__.py", + "bytes": 2588, + "sha256": "fda5d8277c498901944711047ef57ee0c02ac51270205a6d15a2c7c8981fbd15" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/__init__.py", + "bytes": 488, + "sha256": "86f94f7256839371241ebfe7aedeadc77ee2bffad0f12420a2c3343a1252790e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/exceptions.py", + "bytes": 264, + "sha256": "b940c75ff3f44b747fb6dee8012f002d0ad480e895d8381cd1040075a681716f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/extensions/client_credentials.py", + "bytes": 20897, + "sha256": "5ef09aac14270780d112e6045728196ded0ff568fbcfc752c7fdd2a9a8cc2b34" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/oauth2.py", + "bytes": 27638, + "sha256": "e27f07f0ce7edab0971fd9015fcd590e4296e1712163937c537074d7c70862e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/auth/utils.py", + "bytes": 12069, + "sha256": "326e38de48fda47634f665187e59abf3d5bc8dec596a5bc34bde9bff7d6d5081" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/__init__.py", + "bytes": 219, + "sha256": "afddb740f32b1d06cb41803f178f0c2f2d98f0e0dd3d6261c7c4aece15dc6840" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/task_handlers.py", + "bytes": 10791, + "sha256": "c83ac2a220a16b9881055a63a99ebaba4dd9a03890ce7a94dbdc4a198e0387f4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/experimental/tasks.py", + "bytes": 7055, + "sha256": "2cb11b7e623008d9343c2a4f4a784ae94d1d71aab110d6ddccb417786b2a5632" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/session.py", + "bytes": 23495, + "sha256": "037d437b93b086c0510fe885df696b680ac943fbd81c9624618ac8fd7b9d017f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/session_group.py", + "bytes": 17423, + "sha256": "c17b78b1923e5915ddcb93cfbfe84dc1184cbb312128c9107f76c5bda3884c47" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/sse.py", + "bytes": 8257, + "sha256": "7e85c6beddbdee340e155d009c555f421a91655967a754448d6ed1d419751457" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/stdio/__init__.py", + "bytes": 9403, + "sha256": "a23227cfe1e3456f16ab41311486a399cf547fcc6b645399bf9bf3a6dc6cf654" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/streamable_http.py", + "bytes": 28324, + "sha256": "8eb01c3d4da89c6e73d494a96029dbcf2fe05d0bfffe76071b1e409afdb62f5b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/client/websocket.py", + "bytes": 3486, + "sha256": "987eb30ff9d19ccaf5d6e4857434f2d9367661e4bd4b5f2ff89276ad0eed5dc9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/os/__init__.py", + "bytes": 43, + "sha256": "baa68621f085e08d083de4ac10464c37066e0c5f7553458a0086c370accf44ba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/os/posix/__init__.py", + "bytes": 40, + "sha256": "97fda3f23861830ebdb3ff9e1f87420e4e10631365c4a188a5a382bdc6efd509" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/os/posix/utilities.py", + "bytes": 1930, + "sha256": "99311857be0cf73f6cd9d79759cb695064d2d4c36d90eafb2180975379b194d2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/os/win32/__init__.py", + "bytes": 42, + "sha256": "ef1c88eca5a96610d2de898610172edf6d3443e0e322c4bb73e6bebeb9f6f18e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/os/win32/utilities.py", + "bytes": 10808, + "sha256": "3398fd345f50860087a9078762973dcf492c16c372d45e37bd8a909772d7abe6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/__init__.py", + "bytes": 202, + "sha256": "8de323024bc286128b132d5fa5e289e1dfdb4f5be79e6b869b5d68b50ee77018" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/__main__.py", + "bytes": 1328, + "sha256": "29a9144aab85ed1e38520c20dcde5540226c3e7b0456b6f093e5274477ab570c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/__init__.py", + "bytes": 51, + "sha256": "c66c41cc6018bde8ce133c6abd9c62464a28b48765a69e56ac23623cabee29d9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/errors.py", + "bytes": 224, + "sha256": "f188e57a324f97ea8c8d9adb2530f330f04e00e2139f40dad290b94169fd41d1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/__init__.py", + "bytes": 58, + "sha256": "4f6b9e4165692c36cc20088e7113b2ba6c51c2a6bc3e8e6503dcddfc26edd947" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/authorize.py", + "bytes": 9728, + "sha256": "7c0cfe2fee50bfa29e1e3a6804e01413ab18813b86d3274f03bcb6c636f8a483" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/metadata.py", + "bytes": 853, + "sha256": "9963e4aecd437c6f90cead67654d87cf0c52c24f0afef5def99a440d9d24fd00" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/register.py", + "bytes": 5928, + "sha256": "0ad1fd734de866d260528055afe560ac0a09e965c1c36446364e67ef478194f7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/revoke.py", + "bytes": 3113, + "sha256": "0c85639f4f64eea4e4017ae44ec44156547e5ccf6bb264c9cca957c8d749a831" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/handlers/token.py", + "bytes": 10223, + "sha256": "cade6452f07cce106038da586493904c5ca82590610b450a3d4137190324a2db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/json_response.py", + "bytes": 377, + "sha256": "10cbf7a801fadaf46ec3c5c11dd154de4629d12f0977a0e34467f073b25cf575" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/__init__.py", + "bytes": 42, + "sha256": "5e14548bfc3e89efd01bb5d3e447ea346f8d3d9bd7b49b601d207fb4f558348c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/auth_context.py", + "bytes": 1696, + "sha256": "d2160352bd862f456a4d542d0826420e8a762701a8c7f22e0f45c16506eda53d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/bearer_auth.py", + "bytes": 4423, + "sha256": "faddc0af96cdb8826d612945d82b2f980a33117daec92a38721a7647381bc38b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/middleware/client_auth.py", + "bytes": 4723, + "sha256": "948206b9f923f2bc243558e1eb7b4f1f550e63987805cd3cceebc69ae6d4cf8d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/provider.py", + "bytes": 9846, + "sha256": "8ada75d2433a4c671018bff3314569436f052b7a7eabfcc52b95770893681c11" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/routes.py", + "bytes": 9131, + "sha256": "8f80796f63f0d9526708eab76d6dadde646f831277a08e70cd09f4cf7025679d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/auth/settings.py", + "bytes": 1031, + "sha256": "363f2d3425c0347302997ea7c426be194ac0f649c9f5ff330ae33130314de03e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/elicitation.py", + "bytes": 6917, + "sha256": "7aeaa3a2d8a906ccec8ab759babaf67db29e99163047bfb39599628f031d48d6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/__init__.py", + "bytes": 373, + "sha256": "0edc8bc5e9dea8d9d473864329e0fd2811445fd92eda24703aab0c13d9391af7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/request_context.py", + "bytes": 8448, + "sha256": "3001fb67e5e87b4bc437370fb6ee3f9551b2c03647a7eec569427c6d80edf09a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/session_features.py", + "bytes": 7870, + "sha256": "7f0c086fe523da18639eb13664e7c42f963952035af2083c4a2225e50a4df180" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_context.py", + "bytes": 23021, + "sha256": "33def4b4afd933f9a57b48117e0493bdde4dced50c5b95b1df209813145fa3c2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_result_handler.py", + "bytes": 8379, + "sha256": "1324dcb1871dc332b8f7fcfc46705f457d5640a685704cca177b67a4421472f8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/experimental/task_support.py", + "bytes": 3691, + "sha256": "80fd8d88421ac9ca51b0b6b44e1406f1514ea0c1946a88749ab9f804d7d00a14" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/__init__.py", + "bytes": 297, + "sha256": "e049e578061d3c5fe180f7b17eadb3c4646d53f15ddd9ae35de80a724ba3f8af" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/exceptions.py", + "bytes": 435, + "sha256": "abd7635039a9c2618459c1c8f2ae14cc906d7fbb3b52d80bfbe381ece686cf24" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/__init__.py", + "bytes": 99, + "sha256": "e01b0cc68628969a3183be319249330852fcbef7642d52797083cdb35343d49a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/base.py", + "bytes": 7088, + "sha256": "b39fb3a428df77e2cb6ea3286b6a099f3528554196e045618bcb9fbd64a31e7b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/prompts/manager.py", + "bytes": 1835, + "sha256": "21722ffff9ae390857822a4298a589e1cc8f8e36b05a48e679a85ee86dbda325" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/__init__.py", + "bytes": 464, + "sha256": "7b84b7ebd8c1a09b74edecfdfd968979fcc67f3e24afd9c6c06e0a3ccccc1dcf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/base.py", + "bytes": 1852, + "sha256": "6b0edde3a75f3060b3f5f60db52871089f4f9673469a87afe097cfa6cb42f312" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/resource_manager.py", + "bytes": 4007, + "sha256": "7ffc92edfe3e86cf91af288b1e5c5799febc6c77787199c83e49976f0ce16958" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/templates.py", + "bytes": 4947, + "sha256": "74b7137e8115ea7bbc0a40eaed6b0d235aa6c0615a9f898c7b0bd852ba0b3cf0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/resources/types.py", + "bytes": 7214, + "sha256": "83a6df5c534619fa0e1a2ab7ed332f1bcee4c57ae08d2815e94ccb4b833a2f3e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/server.py", + "bytes": 52706, + "sha256": "abd25bd81df8a0308eeba90cde1b728307d852c7efe0abb9e0a5c6acf5b7b9b7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/__init__.py", + "bytes": 96, + "sha256": "65ba318723090e5f3b4af8e8bfc630358c0d662e793fd5619a94e30592deb279" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/base.py", + "bytes": 4740, + "sha256": "87626ba624d33218ffb7542140dc888453e33130fdefeadc8909d47bc600ca54" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/tools/tool_manager.py", + "bytes": 2972, + "sha256": "acd8c9ebfba0e8764426e60868deeaa931a012a8824e16ba4f479e4991bd2a76" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/__init__.py", + "bytes": 31, + "sha256": "fa2989f12fab5e66d73167836a695d3fe7470ea00f83fc3098f573f8b357d781" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/context_injection.py", + "bytes": 2028, + "sha256": "0559b84af8b944afa796c7306194fbe56b8b3dc711009fc835d671c5548cfa58" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/func_metadata.py", + "bytes": 22121, + "sha256": "ba7c3361e4eef09455c6a6197be8e208476d89ad8aa4e9ca1d6893117ba00637" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/logging.py", + "bytes": 1063, + "sha256": "7bc9011c0607074d209ed31369630deae4f89308a239d2f5e2a0d7119534c7e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/fastmcp/utilities/types.py", + "bytes": 3546, + "sha256": "c5746c86eac64488d59ebd6554fc1c66899248de3fc2fc6935bda34ba4540b0e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/__init__.py", + "bytes": 93, + "sha256": "992eec96b5685b31cc62a0c70c827ba11671c0f646fc8eb751542224ddf5d586" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/experimental.py", + "bytes": 10545, + "sha256": "b34068c41b8c2975e620d89286a04ea38e7a3d6c8a17826e7fd3c6f32744e15d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/func_inspection.py", + "bytes": 2466, + "sha256": "b8b12963920781689c21f6d86da23868361f520311c6e15d4eedb476ebc7da9c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/helper_types.py", + "bytes": 251, + "sha256": "2d50b437a24c3f29d5b387cc2527c19aa2888a01cd2ec44d841bdb534eab83a0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/lowlevel/server.py", + "bytes": 33499, + "sha256": "910890555395de8303deaef64963b056443f0e0bdd1d22f61a51481d70c3bdaa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/models.py", + "bytes": 422, + "sha256": "c3ec443f2fea19db36fdb1297a337a89a59e87452f32d2aac255f33cdfc2859d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/session.py", + "bytes": 27555, + "sha256": "034a41d4bb109d6809528c100e1532e390591d8dbfaa2dbd7ee276ea32cf7349" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/sse.py", + "bytes": 10826, + "sha256": "42acc4f605a91b7e477d0a3a81bb029993d5e2e774d84b5971ec0d193c0abc5f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/stdio.py", + "bytes": 3358, + "sha256": "2e4162811642e2e203500d898e7b0a9603ae2e1f4858504d5601dcb3dec9b0cf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http.py", + "bytes": 46836, + "sha256": "ef9937b156267017bab74ce69345e088eb80ab7452302af9d23f36e3c5b2ed6d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/streamable_http_manager.py", + "bytes": 12183, + "sha256": "3a8168328e0250a0956d1815cec239f8cb0b4b8aa3eeb59230176b91e9bcbaee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/transport_security.py", + "bytes": 4938, + "sha256": "4fca0b135961d965d6e9c56f3e23544a61b95a6c4cbdd0825e49bb25d4af988b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/validation.py", + "bytes": 3633, + "sha256": "a30616302120e4a2deb275e635bc9a4e27c9c89289629efd16ccd780b3ec1bb7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/server/websocket.py", + "bytes": 2352, + "sha256": "b30f5a4a6ca139996a2be8cf41df514b4df39fd5da2d1e03e872fabe2f03b416" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/_httpx_utils.py", + "bytes": 2906, + "sha256": "6dfbf2a6ae73b1bbcbe1d82d35f5fe969f11fc5aa6cc3b5610aeb52bb63e4a8b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/auth.py", + "bytes": 6446, + "sha256": "2a774c43eb57514199cd7c387f1ff64052e1706073402422f70f0c17a78d4955" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/auth_utils.py", + "bytes": 3006, + "sha256": "babc4d0fd809778f261c8d178308cbc7912ad8694c06bb38cbd1de6d242dc98e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/context.py", + "bytes": 1188, + "sha256": "40c447c1708826cbdfe14def720a9201769a50430a85f8fff5794223e9149271" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/exceptions.py", + "bytes": 2433, + "sha256": "8251d85855667489e11d19e5c2ba6be70fe200b2ccd0e94d6403377633410d78" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/__init__.py", + "bytes": 209, + "sha256": "061843d7be8bbff3662d9de72ba9f7cd5c826c2c448f954cc0fbaa8e6ddfa9cb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/__init__.py", + "bytes": 433, + "sha256": "8d80a6fa66efbc46e5cdc49612f40c4e56d10f789426cb3e3c6178b0c23d38c7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/capabilities.py", + "bytes": 3571, + "sha256": "c243bfbb8ebdf28e7262d57c44f0918f4a1a2967d02cc93b974e7e2027631a6a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/context.py", + "bytes": 2875, + "sha256": "6a7dd7067a1c294c1d94a5fc851ef5c28c330e2c412b0b147bdb82057aecab5c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/helpers.py", + "bytes": 5517, + "sha256": "385ee86c75e9568426aea7edf997045ce03d6d8f311ef9cc7ddd981b3df178ba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/in_memory_task_store.py", + "bytes": 7558, + "sha256": "72608fa39f573903defa4179b86d5877eea71d90da3a1ac464965cdf9bf723c5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/message_queue.py", + "bytes": 7436, + "sha256": "bdfe439b06fe5e4a72b5b2ba4e2c68b855a3f1c8b4c3178dd924e98fb214a22c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/polling.py", + "bytes": 1369, + "sha256": "e8109ba3b858186d7d6c6fd125462f9d82c9eb958759b589f60c2b8bb022c3cd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/resolver.py", + "bytes": 1801, + "sha256": "e55bcf7b197070bd5366ada3ffb7c074cce26256c70cd1e4d545b89758551f53" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/experimental/tasks/store.py", + "bytes": 3808, + "sha256": "e79d8a6e9ed4a8ba08aee3d99eb224645bb99b48a2433d2ab8db8738a23e62db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/memory.py", + "bytes": 3951, + "sha256": "0ed3c56df6a5075e0d9b98594b9cd92c0c2b224ab064555c054c05701014d533" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/message.py", + "bytes": 1550, + "sha256": "2bbc32fdf09a3e8ee9d2bf3fd8f776db701bd10f4330006e0a2b4adf1ab47102" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/metadata_utils.py", + "bytes": 1712, + "sha256": "f53a7d6f43e47e30619b64a5c079e87755b805feaf641d29e49d84ed2bf6e54e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/progress.py", + "bytes": 1756, + "sha256": "62ce15fc34d0858b2e83a292e816f71384f4083c8e39a21e7085c020c16cd6d5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/response_router.py", + "bytes": 2043, + "sha256": "7a7f4cefae301873645a5e9d9fd2a2eda9884427251da2ac7db9d37e92fd291d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/session.py", + "bytes": 24086, + "sha256": "3792799b86369a7632d6e8f2efecb118bbbc7cd69abb54bb14f6b079cb09d7f4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/tool_name_validation.py", + "bytes": 4531, + "sha256": "99312f833b0cb246b2ca294b68890fee521a0388def1c9e48fdc5821cea1b60a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/shared/version.py", + "bytes": 156, + "sha256": "18ac0ce561c0c1dd839ababb4f2ac01c41968533bff54289254982efdd4970c9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp/types.py", + "bytes": 64908, + "sha256": "6824109cd1eabaf43c5dab829a83015b1b88ec9ae149765e6814afaf23f9bde5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/METADATA", + "bytes": 89485, + "sha256": "7da3cc41bd1cf5402b16a31067d59d185c597558df187cb623bd4e43e841970b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/RECORD", + "bytes": 10027, + "sha256": "53b0b63e36dfb85b54dca597781a6c32cb5aac2ce7952b20599beeb1e2939e6f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "58b82a17209f9bf2804afe161f2632d0fde933f9bb2792fd936b2474a2e2ac2f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/entry_points.txt", + "bytes": 42, + "sha256": "8271816e8df2185d1824d5df669824020d7e090b4ac67cf1ae5039ddcf2f8980" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/mcp-1.26.0.dist-info/licenses/LICENSE", + "bytes": 1071, + "sha256": "5e13dbbc1d120fc2a03cecde7c91424ae2d7de11b63d58ded2f4431e261ee50d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/multipart/__init__.py", + "bytes": 1474, + "sha256": "81b63b5bd35476c1596afa7e88a42ba0de05c689df66ace89242d6e41de39d9d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/multipart/decoders.py", + "bytes": 40, + "sha256": "5ef900c1353d5053e25e4734864be8bc77f45ba1f7bcafb689e5a585abf4da14" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/multipart/exceptions.py", + "bytes": 42, + "sha256": "e83fd7fac7a23a63009487a21a53c652cf3ebe972f20625e4327053036f57fb0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/multipart/multipart.py", + "bytes": 41, + "sha256": "f1f0c7d788ff54c6eb721ff9f309738badd7340446bfcd2439903237bd9a1bb0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/__init__.py", + "bytes": 2829, + "sha256": "a6156247202e52682a13890d99a0aaa66e56544048bf351215aa41bf85d7df1a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/_ast_gen.py", + "bytes": 11292, + "sha256": "1311f9626e2993b7503c422442bf512629b97deced7414304813efa6f13ee413" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/_c_ast.cfg", + "bytes": 4255, + "sha256": "95de5ecc4f72cc82452150147f0edecc94a5322e275ca342cdf9aa8cec904cda" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/ast_transforms.py", + "bytes": 5899, + "sha256": "5f032c6ab73968375d35680888a9b8fa33963116226fdeb2350528d3fbb6f160" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/c_ast.py", + "bytes": 32954, + "sha256": "bb091c6561df5c3408c3a583bc22f5ee258cffed11f94443a843263e35cb3807" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/c_generator.py", + "bytes": 20661, + "sha256": "4552893e0bafd82be8bc71d27d092a8a672452bf70194f4fa1fc421a3db9d500" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/c_lexer.py", + "bytes": 25155, + "sha256": "075568a9b6213d690e2495827a6e0e638ce3d08c6b3e4b4165915ed98f3b9148" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser/c_parser.py", + "bytes": 89798, + "sha256": "dc504a18b8e3942deff1a7f00ff727311ebb226a18232ef76b858a411ea15fb8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/METADATA", + "bytes": 8229, + "sha256": "f547a60b0ab54cc2f242213d1f87960ad80df7dd5dfeb98d17a444d48bfb6b93" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/RECORD", + "bytes": 1028, + "sha256": "0e588c3b8b949767736c5b87aca74eeb1c97be53dc3d08a7b8bc7098bce392f7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/WHEEL", + "bytes": 92, + "sha256": "a842dba36b35633977f599ab0226d70368e33cb5187e756150d4e5c85d6bab46" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/licenses/LICENSE", + "bytes": 1543, + "sha256": "0c846399369ea76ddd7b5c44fe6d16497415fcf015f5cbb508c24bf98b81c5b1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pycparser-3.0.dist-info/top_level.txt", + "bytes": 10, + "sha256": "73e94f712ef82fff0aa07ec813a3d0179a1fca2ad140d57856191b48520f7963" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/__init__.py", + "bytes": 15812, + "sha256": "e62127278c07bf5384cdd2903f368a69929f3b8a524000bae4e0eb608ebf4bc6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_config.py", + "bytes": 14839, + "sha256": "473cacd49a1f7e7e097331257180eab192d18018079f69585aa0d1e79a0048f0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_metadata.py", + "bytes": 5162, + "sha256": "63f836b778bbba5b8afb05c266fcc97d114c3d4336dda0582df69ee05cc13f2d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_core_utils.py", + "bytes": 6487, + "sha256": "d63aeee156c9d31eb747a76d55cb8e23e74a4130bf77f9529c9584050cc63444" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_dataclasses.py", + "bytes": 13188, + "sha256": "660a9c9b559a24b0704d0402e6618a368c23953817de67d7fc9e5edaf776e253" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators.py", + "bytes": 33620, + "sha256": "443106fc96aee4d88971f3b4c60753ec43a0b14d4b816218957ef0379ad8b55b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_decorators_v1.py", + "bytes": 6185, + "sha256": "b5f75f76940a6384765c23b0a876d97a840cbabe95362811adf86e757047c77f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_discriminated_union.py", + "bytes": 26255, + "sha256": "24bc7f3152dea7b331975cdba5d3598ef1c3733f89dce116e9671d575f3875c3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_docs_extraction.py", + "bytes": 4127, + "sha256": "7f2ce74801e1e40ce88675d94ad5741ef1fe9d16dabcd1cfca0fa49f1f92fc41" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_fields.py", + "bytes": 31557, + "sha256": "85779bfb3a1d1b04c3bc6f4e2b5ba6d7c3fae1bedca876f418e9e03f88eb81c6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_forward_ref.py", + "bytes": 611, + "sha256": "e67dd8efedc028b9fcfc54b761cecabad2e23d48725e602d90464e6859e89f03" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generate_schema.py", + "bytes": 136348, + "sha256": "3e3a2051ac085e67fc2ee2cd7bdb1e26987b58b5b8309efe181b17c02b100bd4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_generics.py", + "bytes": 23393, + "sha256": "0978dc22796f722f157a36969f57f7f64bf401c7c92f4479db7a912c56550feb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_git.py", + "bytes": 809, + "sha256": "2303e1dc33df6b65eadeb06e07d371f25b91d80d62ebd41d7937d659722e0958" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_import_utils.py", + "bytes": 402, + "sha256": "4d18710f93ae63a094a2c8a805d05c254b34a26ec820e36265d60057bcd0f233" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_internal_dataclass.py", + "bytes": 144, + "sha256": "fdb79d7355dbbaeca044688b66a914930c05a506a847584a2e5479d359f2c926" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_known_annotated_metadata.py", + "bytes": 16805, + "sha256": "3f29d020543ad7fff819caf39f40f910d96583b8cfabf731a0b4e6b8540163cf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_mock_val_ser.py", + "bytes": 8885, + "sha256": "c2645115206fa9f70b6c8e353ec165881e2ed80677989a597a21116c3df1293a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_model_construction.py", + "bytes": 38928, + "sha256": "2682a663826b0c1bb79c6fed088ac982d249135baaeafdbd4ef4f3e449471398" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_namespace_utils.py", + "bytes": 12878, + "sha256": "865dfe4d102bf365368d3c8f3f7b7e42ab2f28b8abc6d90b7cd7eb37e7e9d31f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_repr.py", + "bytes": 5172, + "sha256": "8d07e726ec83c50a5244d846dbd208f4f5fc7b836fdaa599ac4c3696a8a1dd41" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_gather.py", + "bytes": 9052, + "sha256": "f2727eb8ce98e33eb1a5ab271a89c432e6ed3555ff9b179e44316677fa8c54b0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_schema_generation_shared.py", + "bytes": 4842, + "sha256": "17fadb41bae4a289a0c6cb24747a4fd23509ed309f7b40400c0124aba089e273" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_serializers.py", + "bytes": 1491, + "sha256": "6085af4a6011e5f9db19258239076e5add72078650638d9e02bb9cf9e9eb6fa7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_signature.py", + "bytes": 6808, + "sha256": "8bf6fac2d96e895599461d5963c52f076519ce23f52a3a925d9802f87c70393d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_typing_extra.py", + "bytes": 31574, + "sha256": "743c6a178ea5ceea82a0a2eb007fe4f791036dac8478a6f69478ee4c9e4e0686" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_utils.py", + "bytes": 15959, + "sha256": "80de3c06c47e143ac90e26c29a8ebdb6d420ebb59bb85addcbfd4d40bddcbcb2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validate_call.py", + "bytes": 5366, + "sha256": "383fc1b291da2fd14acd9f57ae7761884b8c9e3177491209507b70bfac947df5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_internal/_validators.py", + "bytes": 20563, + "sha256": "ec64e35d758530b89be1dc50f8779a8879406624760761bc6f2098306ae6438f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/_migration.py", + "bytes": 12160, + "sha256": "545ef72d1094cf722b6f9c55b75de36f7340717567105e93d7e27438b7de6790" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/alias_generators.py", + "bytes": 2124, + "sha256": "28cd67deee097cb48197552e620de1a181f35c90fecaf82b9eaf2ed5c73087f0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/aliases.py", + "bytes": 4937, + "sha256": "be1087ca84969d7f8427ec166f9aa3e31c91b2c8065a74d07f3429e06499f6e8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/annotated_handlers.py", + "bytes": 4407, + "sha256": "59fc854aac28108157061ed3ef73f270a9682350e25f8e465a2d3e24eb024786" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/class_validators.py", + "bytes": 148, + "sha256": "8bf5778fe3d87462d22e68ff20965e913463ba73bc488573f0b325aae3f23fb1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/color.py", + "bytes": 21481, + "sha256": "033a867d540717ddbf65cb4372e7b40cce324e9d8fead7a49304483535ab2c8a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/config.py", + "bytes": 44533, + "sha256": "a353faec53162101befd17c6b4a8ac9641f0a38a579bfea37ec478d4ff3cbfbf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/dataclasses.py", + "bytes": 18963, + "sha256": "e17f567b48e3d4a2f006dbd84a40055cda27e3aceba4199164035fe0b7f06d78" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/datetime_parse.py", + "bytes": 150, + "sha256": "402f9680cc4caffc10fe6357512ec055ffb684b121befb0f6353d0ca148639d9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/decorator.py", + "bytes": 145, + "sha256": "617fa3500a6ee4029a55628fa1a57e9fee25ed46d2ebd184b7d45ade1b3398a2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/class_validators.py", + "bytes": 10281, + "sha256": "9f48d840e71be58422c346fb617ca2ecd3e261d57bba3591e0ac99ba68d33e89" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/config.py", + "bytes": 2663, + "sha256": "93f96c564e7ba5ac4b24e701b9e1f4edcbb53a012059d541c66ea57da0b70825" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/copy_internals.py", + "bytes": 7618, + "sha256": "1a177ebe431de446020a0c821ad3ca3b9f27a7d704281402eaa9017881052368" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/decorator.py", + "bytes": 10845, + "sha256": "4c19ba6c9ef026c36287ff16ab92330dcc0fdf69bdfef7f1b3de9d7ac2ee934d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/json.py", + "bytes": 4657, + "sha256": "1e55821b7e5146bc72cee4d2e8b4d089907046198365699ea901fcacb5bac00f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/parse.py", + "bytes": 2511, + "sha256": "1b377a6ff83ccc95dcb84ed046ae5a761c7f10c25a8577dca57085d1182baaa2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/deprecated/tools.py", + "bytes": 3330, + "sha256": "36b9bda054595a9f3e8e57ef3e02482c4b32c29e18cd90f9d972063c32f11dc2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/env_settings.py", + "bytes": 148, + "sha256": "e881de79612a9543d152fdd5f805e217f5bdd5f836270fccdced25df827f7b20" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/error_wrappers.py", + "bytes": 150, + "sha256": "44aea6a804dcf72303f8a043f48252f47a4a0a9ad61ddf30a3ce019e6fb77d1f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/errors.py", + "bytes": 6013, + "sha256": "0eb1023c25a1498ad0f016b83bc84acc800cda2f460474d75802f36850e92dfe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/experimental/__init__.py", + "bytes": 104, + "sha256": "413eeb298743b0289327d1848e6b107561d2730a4aaeb3641aaeafa8e34fe914" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/experimental/arguments_schema.py", + "bytes": 1866, + "sha256": "1059e35ffba5a7eb4fc948d05f9a6642dba0d4e14bfc071cf1b70c6cb77e7d56" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/experimental/missing_sentinel.py", + "bytes": 127, + "sha256": "8507a382d174d3052e40c9e2f78dbd7af83e797c88c292af8ec0fca1fa9f8fec" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/experimental/pipeline.py", + "bytes": 23956, + "sha256": "6aea16ea5ea0d450b8d4b7223e6b08d4cea771fd344b375ef41dc2fb2bdbf662" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/fields.py", + "bytes": 82023, + "sha256": "6bc66125f23c143e934030fbf9c58c9b5657950bdad3031d4f6132e55bd57be3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/functional_serializers.py", + "bytes": 18117, + "sha256": "cf04408d9bac391b446edc69554db4920f05a5e9b2673e05abac099359a9c184" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/functional_validators.py", + "bytes": 31724, + "sha256": "ee9fb88ef3ffffd8d9caf71d5e289d7b9a406bf26533b7c9d41824a9b8f14963" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/generics.py", + "bytes": 144, + "sha256": "d19a99f4ef5a9e7223ff7986051aa9b3886d7b2ddbe65575f9ddad5313cc3270" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/json.py", + "bytes": 140, + "sha256": "647f11908ee1e01cfece9f0e7530316c952856f72853e8e131d459d01fa4d317" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/json_schema.py", + "bytes": 125955, + "sha256": "75ade143dbd03cb1213ecac39d49628df4357ee0825621959d4cceac064b803b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/main.py", + "bytes": 85334, + "sha256": "35b842cfe92ef300e060b40c062ef93afee1cb075cf0b7b0037a1434867c2554" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/mypy.py", + "bytes": 61074, + "sha256": "ca99e187b867f8bfa693ab16e0272b755d82e65c8ccdc33a153063db51ae7ec1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/networks.py", + "bytes": 42102, + "sha256": "80207dea0b741bbb625438559c97e92abd4044be6a907f923d992e364986d8ee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/parse.py", + "bytes": 141, + "sha256": "c2477cd9d82dbd6b43f3de54fc8e84d61b6a3251a1052604577f5cb8149ea370" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/plugin/__init__.py", + "bytes": 7333, + "sha256": "6bb4f0dfae94e8ade496d08235963bea773db2cfbbb86190e344d769df4eca94" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_loader.py", + "bytes": 2213, + "sha256": "8408e04a58e82211b1dc0569229baac3948fb6d04d35e181493aea48c9cd8899" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/plugin/_schema_validator.py", + "bytes": 5815, + "sha256": "b616d9cfffb9b183f3c6c0b9b962b91837d87269abb1e702f15c3f034da68705" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/root_model.py", + "bytes": 6394, + "sha256": "70e328796769e77e8a17ce6e448716fa8cf53bb7791910a9c7d1d34b8fb5b9ff" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/schema.py", + "bytes": 142, + "sha256": "56aaa3beafcb9daa5592779b51ddc1a7f275a768176599d94602f8f1b5441bba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/tools.py", + "bytes": 141, + "sha256": "88742977c489e43093b4f5796ad015d3a4fcf5b8d268c15e659d8b5fd95ab196" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/type_adapter.py", + "bytes": 36123, + "sha256": "4f4e60f1641ccc1b1553fdf945d2b1823222ed8ecb222a7aee9177348ed7e061" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/types.py", + "bytes": 105961, + "sha256": "7e961c1a702770ae108da166de366da98c31258cc1eeb2493df025f3d7a6cf24" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/typing.py", + "bytes": 138, + "sha256": "3fb7de037e4cc1370bb11d6e2fb75bd12fa8c9d071a1b9976b9e580e80606a34" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/utils.py", + "bytes": 141, + "sha256": "d799d1d90a6a4c1165415e1336d4c8b4cc93271fdf6f257e80f988118d46a287" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/__init__.py", + "bytes": 2946, + "sha256": "4b140f925801b385c727013a059f6aa1ec18a0688dc98527987cc414265b7e72" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/_hypothesis_plugin.py", + "bytes": 14847, + "sha256": "e444b9c56bb0d45400b3298b7b3cbc4209d5cf466955f5378e4993ef81f6ed54" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/annotated_types.py", + "bytes": 3157, + "sha256": "ba4d8d000c6a88d10b2a388787287129a20e87c1759585bf2f3ad6dd7ee86417" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/class_validators.py", + "bytes": 14672, + "sha256": "50b39a21481850306c1cbec411511aadc33e52e6ca52082837c8526c3a27b051" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/color.py", + "bytes": 16844, + "sha256": "8990012d8a7a395a28d801643fd229ae2ff049cebe2a496ef18ba149aaed7798" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/config.py", + "bytes": 6532, + "sha256": "6ba3f459eafdc7871bc0a5bb5eff29a1252a33858ff912d6c17e9832962af400" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/dataclasses.py", + "bytes": 18172, + "sha256": "efce1caaf2276f020f5abf6eb1fa57ddc87bcf8b77276b532ba374ebbff0935a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/datetime_parse.py", + "bytes": 7724, + "sha256": "e10cb5910a6adeb3556497881de48f0e9b92d81be1a7528fb73246d71bbe1f4d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/decorator.py", + "bytes": 10339, + "sha256": "cda6b1c71a163c29bcd7c0f56ecd3285aa518d79b7d95f06d191d609d335b970" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/env_settings.py", + "bytes": 14105, + "sha256": "03d557c2d465d36018fa31f40b4a2ecb954dc377e2e85fe9933b870e380000e3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/error_wrappers.py", + "bytes": 5196, + "sha256": "ebadb931fc3daa40b63708ad07f2450167bc07e5efeb3054eeb2fd936f2d7f2a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/errors.py", + "bytes": 17726, + "sha256": "988c0f103e6f18ce50e6fe02e19d493e5753447fa89af132947ea4b0c84e98fc" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/fields.py", + "bytes": 50649, + "sha256": "56a5890ab894362132a6d5eba03549e74d49a550347a7d9500d724b2a5cbd9bf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/generics.py", + "bytes": 17829, + "sha256": "633c8a4d937ac79435446277590f6337ebb01c9c4bdd6e2aa19ab07195eac568" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/json.py", + "bytes": 3390, + "sha256": "590e47cbf848a5f751dd84bc93a37613a28c273b1d6c18bf95d58e3c9695f353" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/main.py", + "bytes": 45697, + "sha256": "bd10754dba64ccf3773f98a326573e72336e3be1dc88d3a90b86fc2b7cd99df7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/mypy.py", + "bytes": 38860, + "sha256": "0a5f1745f0a621c544de3e40114e760bc88387c95c5fffc3de1cf68c85b1300b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/networks.py", + "bytes": 22124, + "sha256": "1d836d2807ce98e9ca269b0383583a488923f563e153ffa2f25e5ed89281a46e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/parse.py", + "bytes": 1821, + "sha256": "049b5daa2651b5abfd55114298ec6863e2889909a33f2f80fcda288e21546716" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/schema.py", + "bytes": 47801, + "sha256": "6aa06e03ef9cabc8005648a6e4124f140487cce67c745b66157fdf346afa8a9e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/tools.py", + "bytes": 2881, + "sha256": "d650dd5c79348cbe6e3f7bb9442600bd402518296000efb8e6592af63edfc810" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/types.py", + "bytes": 35455, + "sha256": "07397e45c9e2b4f0479eac308fd8afec98c7b8dd46a67587db874a905de5f5ef" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/typing.py", + "bytes": 20102, + "sha256": "a2fc2d2e911909b9e08596877d2349ba9cdeb731cd90b5e39ffeba9204e7215e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/utils.py", + "bytes": 26014, + "sha256": "d4fa8e225cfa3955b019db371c12a5c3812de9ab05a2ed1452902da3b8c5382b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/validators.py", + "bytes": 22187, + "sha256": "9725249f5316847c65097e597c48058ff0801e88e888f71a41e31d10cf57be25" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/v1/version.py", + "bytes": 1039, + "sha256": "6291d63902ad1a8c5fca29331ab7265c954a515601f4405da0247df784314a71" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/validate_call_decorator.py", + "bytes": 4416, + "sha256": "54b022e21a05a630bed5e2f41e2c5869a59a1053ba86d19c0dcb60b2a6b95482" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/validators.py", + "bytes": 146, + "sha256": "a706c825755bd42576980138c3f1067cd8fb0f0cec29a5b0fed4cbe9cbe24eeb" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/version.py", + "bytes": 3985, + "sha256": "b8a3b18742e3899919d0a7cb9a5a4f611018fbf369217f228f138b0cd856f26f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic/warnings.py", + "bytes": 4822, + "sha256": "dd0c90a3a94ddf970eece5c36c511759c35a3e518245aade815640f86fa56702" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/METADATA", + "bytes": 110178, + "sha256": "0685830d13647bd6f3f05526043d32a8b2a221d86b53773053d6c52bb17a9c07" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/RECORD", + "bytes": 9339, + "sha256": "5bc5a9f4e6cc10a7340d5d4272c21201435a36e3e2d5fae55a7af2c66645ad7b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/WHEEL", + "bytes": 87, + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic-2.13.5.dist-info/licenses/LICENSE", + "bytes": 1129, + "sha256": "a9e186f3ca16b5eef84318e7a701721351a00cb7b8ae3a4394b67b49e3529ef3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core/__init__.py", + "bytes": 5115, + "sha256": "9cad6292b75254af606a970aae9bff6e5ae9f0b08089cd63acafa391b60794d2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so", + "bytes": 4692808, + "sha256": "c63bccc94fa837683c2bc9f006f3dbe87f0252893135e055e12b7bf8213228ad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core/_pydantic_core.pyi", + "bytes": 45932, + "sha256": "1ff946f222a2bf70697dddeb8b163bc73b3aee013eb347a202a25a49b9c5f12b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core/core_schema.py", + "bytes": 155574, + "sha256": "d4f337d727ff906a66fc7ec62e734546420f01018f2fdce1eab7f64b1a215718" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/METADATA", + "bytes": 6573, + "sha256": "29768c038c0bb3564c0a87ef50ef78d18a2372474bdef0ba356a8c0fd4bd6028" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/RECORD", + "bytes": 911, + "sha256": "694a070a118c205e857f2204d8e08952a32810375d3663ef506edfb0e0eefed2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/WHEEL", + "bytes": 147, + "sha256": "d4bd81bf36f65d2f43263a7ce2c7f6ac2263dfd371c8bd0941fe7b57c69e535b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/licenses/LICENSE", + "bytes": 1080, + "sha256": "2afdd30d54b4d62b6f488a6bcc1546e84ec5061f13f4209c03d012348783795a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_core-2.46.5.dist-info/sboms/pydantic-core.cyclonedx.json", + "bytes": 125376, + "sha256": "145b4b747f7396e8d50ca32c8dad5482b9ed09704e860675fa9b531b636f83f9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/__init__.py", + "bytes": 1781, + "sha256": "176e01d2ed94fef3e872667082aa4df3726dc1f15201bb25dd578f58024205b1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/exceptions.py", + "bytes": 426, + "sha256": "a3d580a6957d9bff37df81cfd8bab1cdbad57cce161d5e7a0aa8ade7bb602f80" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/main.py", + "bytes": 47390, + "sha256": "1f129674507a76ece2518e1ecf14ede414b61e56fef3cd29c6c30ed47f860bf2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/__init__.py", + "bytes": 2404, + "sha256": "b0e750bb32dba41bc958a58653220afd978bbc99c0bc5885340444fcf5475c9e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/base.py", + "bytes": 26340, + "sha256": "e57f88ad7bded3dd101febb7f4d455715079137854b0dbb1a9338e03787eff84" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/__init__.py", + "bytes": 1281, + "sha256": "804e972f3ea16caa5cfec4a274043e50cd34d6e5945e0ef1194f88447ac2e948" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/aws.py", + "bytes": 2729, + "sha256": "8e27a587ee67fe497c6fc80639dde4ab732bd549305b9263502aaab5c3b3c38b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/azure.py", + "bytes": 5689, + "sha256": "963021a5b4360843720419bc4ad6415d5844859378ac3a817113f8262f247580" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/cli.py", + "bytes": 79106, + "sha256": "7bf2c7befc90c915e6a78b36a4b25d5430af32c478e8b95ac8cad2240cc518ee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/dotenv.py", + "bytes": 8328, + "sha256": "a2e9745472944b4505bebe5e15c47980f0c83fbed01cca47ebdeb74450f3b9a9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/env.py", + "bytes": 16235, + "sha256": "bf2576e89b0ee4b84b3a13d7f3a93aa442b56507ad4a31d01f9908acd4d0615e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/gcp.py", + "bytes": 15238, + "sha256": "ca43f9b0b77a97de5ec80877a07cfb31ed2d9e5a8d667626fd846ec32c4fae93" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/json.py", + "bytes": 1673, + "sha256": "f6ba21415fe72b4cfe5e7da7e9d2d4eb7f8ac773bbaf14a46b292431551d828b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/nested_secrets.py", + "bytes": 9260, + "sha256": "93aa67b50ffc1867e947365330d02ddc5ed168037ae178e4bf4f1bda235190e3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/pyproject.py", + "bytes": 2153, + "sha256": "f8d628d291c4d0bb9e6dc087bb97b6c79b983d88d3e42721f56e670f6df0c26a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/secrets.py", + "bytes": 5188, + "sha256": "f1cded57d1725b7e8fe534e3896538b05ed721839b90b320c63edaf162fea67b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/toml.py", + "bytes": 3351, + "sha256": "1422cee0abaebe10cf9b8723132d7c0c63e0860e80433abcb07cf0998b9d4e2a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/providers/yaml.py", + "bytes": 5726, + "sha256": "aca9083f1d8317edfea6b9a89e2535dc6af1458163b04c7ce04bd36cb8e2643e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/types.py", + "bytes": 2617, + "sha256": "e971fff9749155befc2db0c548320071f03b1aa3f6db7ee8cd497066a5234da4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/sources/utils.py", + "bytes": 14570, + "sha256": "d71843179706f8967162351fff10d36668a833e2cea11196841ba4eefddc8b74" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/utils.py", + "bytes": 1703, + "sha256": "64f5c6a03e4c53740b97a4e54ff3f656a3f5d6432a6fdaeaa749dcde7b022d17" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings/version.py", + "bytes": 19, + "sha256": "8d276b4910e726674348e8b9504037f5dfebdcebf58e7e6422baac6670f5f8fa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/METADATA", + "bytes": 3885, + "sha256": "5ac12e408df62f1d4c7643146d9df04ace749f1fe5cf28b3b794be0b2834ca6d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/RECORD", + "bytes": 2541, + "sha256": "995aa1bcceaf274cfa39cb0ec1ebdce36e1a5b4e6509814edf273bc9c51f0a10" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "942926c567d0b1273d09f20295879abd37507845f6b683eab9f87d808df91100" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pydantic_settings-2.15.0.dist-info/licenses/LICENSE", + "bytes": 1103, + "sha256": "eb355a753e020346d33d83bf9769135b89fc610568ac531a01c295bcee7fd998" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/METADATA", + "bytes": 3449, + "sha256": "b854eb461aaeef80469612210786ac1fbf6d1064f1d51af5ee2619b7bbddb1ba" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/RECORD", + "bytes": 1419, + "sha256": "dc42c0305636473846f34ad98d7dffb83efed9f86511eb733270bac9955272f6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/WHEEL", + "bytes": 91, + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/AUTHORS.rst", + "bytes": 322, + "sha256": "925ce43461029eedbf558ec0b7cf7fc4b045def5120ebb97937c70814071cf07" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/licenses/LICENSE", + "bytes": 1085, + "sha256": "797a7a20231d4c433e9f1911db1731d06b5828b98f499819a034f7c0f56f5ce5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/pyjwt-2.14.0.dist-info/top_level.txt", + "bytes": 4, + "sha256": "44fe431cdc896ccab691ad0599f4e0a12690ce1ededebe57b825823bc6b4d24f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/METADATA", + "bytes": 29008, + "sha256": "2380ea9700796b7398fd8156e22f9028afe5eebf918eb84a6bd5ebb0a8fdd3b5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/RECORD", + "bytes": 1182, + "sha256": "8c0efcb6cb08885e10db3802de2387cab09a1f30e16681d25cb404b0998438e0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/WHEEL", + "bytes": 91, + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/entry_points.txt", + "bytes": 47, + "sha256": "c91975ac26ecc1bd6741343f8194650b0e507da6f3b541a77c658b8655c535d2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/licenses/LICENSE", + "bytes": 1556, + "sha256": "80619b7049f08c81683ad0e01f08f257a840652dd71ee83146d36658c7d2c2b9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_dotenv-1.2.3.dist-info/top_level.txt", + "bytes": 7, + "sha256": "7b2a941f848724dafa6a139897122e9d3af85e29c4f19e5a8d62ddacadebd03f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart/__init__.py", + "bytes": 937, + "sha256": "aabe42368f87e12de550253ad7f876fc57334ae51d8f32bfd44e1a6b4ea414fa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart/decoders.py", + "bytes": 6676, + "sha256": "7499b0491c68c1de57a77b3ce9a22953c7ff639e4aaf41f87f9c20bd27227443" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart/exceptions.py", + "bytes": 954, + "sha256": "53494d063851e1d408f7bcd5d36690a542d06c38e32eac9693dd21e4dac2c560" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart/multipart.py", + "bytes": 79407, + "sha256": "3b3cb7649a83cc96ba7f80a0b22538c4ef4339be4f3d5c944ab4fbd9185f9fa2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/METADATA", + "bytes": 2131, + "sha256": "e9d399d7dea5eddf0e4d24b5c24687a82e7e50bf2c2b30f8f8a06a64d887dd86" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/RECORD", + "bytes": 1078, + "sha256": "4e738efd15b92779335bad395dfa34ff4a0deefb0e459d988af28a7342042c0b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/WHEEL", + "bytes": 87, + "sha256": "99f7cfcbcc019d9427d959c9514e6313df4ab316927e2c8c1d5f58b7468b571b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/python_multipart-0.0.32.dist-info/licenses/LICENSE.txt", + "bytes": 11358, + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/__init__.py", + "bytes": 207, + "sha256": "e4864a5da007fc55b20894646939f55dca6d2df83d72f78b6fdbb99d853124ab" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/_attrs.py", + "bytes": 791, + "sha256": "6e04fe28c84354b786b56c4cfd218a61e2da6411734f691454574090e7178bc8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/_attrs.pyi", + "bytes": 573, + "sha256": "836c17f9a2c4a9002f594facd2a20dd8e30602a8ce03447bd91fee49f7cef3f3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/_core.py", + "bytes": 24732, + "sha256": "d923c67da4efcc0dec46a6e3ff8476c02fbe7e25e8942515a4b14b249eb3759f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/exceptions.py", + "bytes": 4176, + "sha256": "cc581a120e9688a793e7c310b8a36c8060e71ecce9dba738a11782eac17d8073" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/jsonschema.py", + "bytes": 18615, + "sha256": "8d767ab7ac7da13bd31cc202d2d4726858d6e6bdaa78739b62ebbb52149f2b74" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/retrieval.py", + "bytes": 2729, + "sha256": "727f4478075a6936e7303742e094488621295c38df95174708e2c5af0d5984b0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/test_core.py", + "bytes": 37854, + "sha256": "79aa74080688db7be33086d5c848fddaa2dd769de21c7dc0c42e7908a50de0b5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/test_exceptions.py", + "bytes": 934, + "sha256": "ede39d1f2a1b5ccb7bfa1e409e71fbbbc8b0dae1cf687ed4e01b3d2612e08d6a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/test_jsonschema.py", + "bytes": 11687, + "sha256": "e109e35163803009f9c9e03c66d95d264848e78bf02962560742c3ccd771e717" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/test_referencing_suite.py", + "bytes": 2335, + "sha256": "c03eaf78c7cbb14bb4b3830b8e6ee94bc720e1c21f2fb14c044367824ef7cc22" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/tests/test_retrieval.py", + "bytes": 3719, + "sha256": "bdc6e77c0e13a957aa5335b4ef7c0efa72dea9522fd2b41958d5afd33f649b8f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing/typing.py", + "bytes": 1445, + "sha256": "5a351b9d9ea33c0777d5c9c214069e588544373c87b47749c8e95e96fd4663bd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/METADATA", + "bytes": 2845, + "sha256": "c93b00e6ab1524fd60848376604c189a114374776475c25a31d57be47b29de6a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/RECORD", + "bytes": 1582, + "sha256": "4cff26d74dfce1cd571ccbee5f58e2dabf748bbe7de1fcc8e4ff487b3a1711f1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "aad0b0a12256807936d52d4a6f88a1773236ae527564a688bab4e3fe780e8724" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/referencing-0.37.0.dist-info/licenses/COPYING", + "bytes": 1057, + "sha256": "42dcd63495f87b4eb7c7757afa379bb55a53f94afd7a5f657d9adf57236e515c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds/__init__.py", + "bytes": 99, + "sha256": "c373205d6ee5a530880b0d0a5dbc36d105f9ef7fe46ec12758878cb0202e82f3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds/__init__.pyi", + "bytes": 2602, + "sha256": "6a6ef1ea831afe9bbf92fd4d96896a25b3ebebf52f0afa32c462ab0c648c2849" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds/rpds.cpython-313-x86_64-linux-gnu.so", + "bytes": 965608, + "sha256": "bddb35d7ceaef748a4661a73774e1ff8d5ae76969b31e1cc7f757fb424bd8ed5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/METADATA", + "bytes": 4147, + "sha256": "f8be7b17da3980b2c48596941411a43c70ac41b1e2d530aa531f2c15eab2eb1c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/RECORD", + "bytes": 741, + "sha256": "b58c58bc22829a94012121b8c94f2b1132cebf7558076d47908f9b155151b54e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/WHEEL", + "bytes": 147, + "sha256": "ec165fa06dd8f506937c232afdfb13d3be2bdaab9cee2698ac4526fefb922094" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/licenses/LICENSE", + "bytes": 1057, + "sha256": "314e4e91be3baa93c0fb4bccc9e4e97cd643eb839b065af921782c2175fe9909" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/rpds_py-2026.6.3.dist-info/sboms/rpds-py.cyclonedx.json", + "bytes": 20594, + "sha256": "80a3bfbae1607e51b2569566a469b8a6d9a984d4923c0824086418a6c8a3e763" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette/__init__.py", + "bytes": 219, + "sha256": "f62c60d28bfe807afc2d24a8b7291bea40e15f85aa36b4ffd3e4c3d6b2e4805d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette/_utils.py", + "bytes": 1439, + "sha256": "735f7bd66c765a5aff30dede9228fa25837e7b78bd2106d8de5e0ca8343f5440" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette/event.py", + "bytes": 2801, + "sha256": "c1e3e392d3be9f48ec82e5c69a5bb7850a284167c960dad46bc883fd10ded8ad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette/sse.py", + "bytes": 21365, + "sha256": "33bab3673aee1de2502e82e80934387fba095eee7f5da0384536b06b22386d5d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/METADATA", + "bytes": 15343, + "sha256": "0d2b7e974db957ca4aa20053918516a6f28a8433eff401728483cbb235e483a4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/RECORD", + "bytes": 928, + "sha256": "ace3156aa60015301e9f266374345be15656c5804490878640840cfd054a62ed" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/WHEEL", + "bytes": 91, + "sha256": "61532836a2b3111b7ec23519c09df7c41180c27165fb872a6d8913b566b88ad1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/AUTHORS", + "bytes": 395, + "sha256": "c2ff63d2ecf128bbda8ab1b2016830a5da124202fbb97168685bb9946aed3f1a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/licenses/LICENSE", + "bytes": 1511, + "sha256": "80af6bfccbebd2c14d4aab96695cab9950e177190cc80f5ef819e99996883e8e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/sse_starlette-3.4.11.dist-info/top_level.txt", + "bytes": 14, + "sha256": "f96bd5e4d31692afc9efaeac64fbac2aabfca4448cf83c6e822f7a2e9f687abe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/__init__.py", + "bytes": 22, + "sha256": "944131e65c229d46f2f98365ef0f9514651c8dc89bb09f44bb3809d96ee872b5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/_exception_handler.py", + "bytes": 2257, + "sha256": "f159bdd797d661ef712e8ff36b9890e56cab02f141bb690410973b5eb793c8e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/_utils.py", + "bytes": 4211, + "sha256": "353351ab1bb6e15e06c15ab9827850d281419519eb8dbcb06ca6a1cde8b0af0d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/applications.py", + "bytes": 5763, + "sha256": "81887e0baf7dda5811ba4b7a67e49cbbab7ae97b321ba72e475c797f989690e9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/authentication.py", + "bytes": 5341, + "sha256": "ac9c74c2f935c6c5627cbb3288757a3ff6f7a0085eac14575ba4245cac3700f3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/background.py", + "bytes": 1122, + "sha256": "0fe5e952915181406765e035b4783330e82e6dee8809564594e03830bd2ad53f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/concurrency.py", + "bytes": 1701, + "sha256": "3e9e245d060ef39716c58ed754db7995c62f8581d4aaec8b82e09e73b84a2a83" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/config.py", + "bytes": 4426, + "sha256": "195c24a34a464aeb31b901cedf9b4bfcdb7f0292f47e65582f72221c76292d6e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/convertors.py", + "bytes": 2304, + "sha256": "175aec7b701a70df6bb1f2674deb839e36cde75affa2e1dcdd62f26248e45ffa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/datastructures.py", + "bytes": 23200, + "sha256": "35dc5f4f5bbd6be348a051a8cf6e7b7af335e29d6cabfff8ea2329f57a3ffbad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/endpoints.py", + "bytes": 5304, + "sha256": "288fad313765aa2e2b535791f619cea6d772185c5cfd3d5a77038a22078e066d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/exceptions.py", + "bytes": 1492, + "sha256": "fb4b0d98d34b6b8ea0f55eb1a6ca8eaaa3f00d124ca1ee2f12a0c06c25502535" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/formparsers.py", + "bytes": 12374, + "sha256": "add3dab420c2c13f328f59837853801e49d5ecee31df053470266ce066d568e2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/__init__.py", + "bytes": 1349, + "sha256": "04e4673a3d27d262f5396514b353cb4eed520129af4fc90410303a71bba1b8fa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/authentication.py", + "bytes": 1800, + "sha256": "f914b0c09d29224accd2ffb6229f075d5076e31b52a7b39bcd1dce7d30bcc120" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/base.py", + "bytes": 10500, + "sha256": "e4e77f966c82bdf2348ec1035a2413887cbd0c46c06f7b4a00f79d276e2d8a5b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/body_limit.py", + "bytes": 4609, + "sha256": "2f5d84bc50bef6bc8efc0ff69e0743a6de96695930840b6a8ae24f87286ac80f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/cors.py", + "bytes": 7688, + "sha256": "fce9d679b52dd9670f38216daacdcab159c6995cdb5256ee5162dc58d25a5c4f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/errors.py", + "bytes": 8126, + "sha256": "135e006618f26dc7d11fbd81ca2690ecc76dd00c27619aa2bfacc6b42d05d313" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/exceptions.py", + "bytes": 2784, + "sha256": "ece812522060c074b854c9a9696970db5b8328d0e638cccb559ad60cb0d4a96a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/gzip.py", + "bytes": 8805, + "sha256": "ee28df06ccd3e21eacc9b96471df3da657b8dd6a980601fe842c9cb0ab96b66b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/httpsredirect.py", + "bytes": 1023, + "sha256": "7e88112b2a4cc7780ace81e026928f2084c674ac4575408402ab244479dd6414" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/opentelemetry.py", + "bytes": 5719, + "sha256": "9403f7058bce8ee9ef9cf04af4671069947b305eb056660b6e973be7ee788702" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/sessions.py", + "bytes": 5175, + "sha256": "f18a683533b4dbe296694bed25d35eee5284c4ac58b990fe848442dd376d05fe" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/trustedhost.py", + "bytes": 2456, + "sha256": "5a478228ffe5a15839201a638ccca85971274b4511426a27f0879ef9d4f3e3a5" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/middleware/wsgi.py", + "bytes": 5536, + "sha256": "f3fc6375436303e0dc598d28c1aab289994672f9a5e14308075f2b1b1b5d36f8" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/requests.py", + "bytes": 12708, + "sha256": "8691b01ae49ecfa085fe6419343678f73b77c92fc63a411106a77d8e9bf0ac44" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/responses.py", + "bytes": 22090, + "sha256": "e58a5be9477e30b9680bb270e0f1b0fc6e252ae1ba65765e70d3946732d327f7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/routing.py", + "bytes": 30472, + "sha256": "15215c88f4187c98cf498e757b188c0e06fbe80d95f78b7630c4e51f92611af4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/schemas.py", + "bytes": 5494, + "sha256": "7bd2a708737528157d17c325e78c02e7922d1c4e4b112e565972d5118520a2dd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/staticfiles.py", + "bytes": 9071, + "sha256": "50fde40ad8f302cd59b1d96eb8fecd2c4ab9e4447f148eb92cc7ca55baec3018" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/status.py", + "bytes": 6430, + "sha256": "5d87159fe694e76d93609f012719745949dfe89ff7c814d0cfb4ea8327bc28ee" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/templating.py", + "bytes": 5447, + "sha256": "cbe013950acd0e82cc66ce61ac7cd00536a9102043afdc0097e6ecf01964e63c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/testclient.py", + "bytes": 21597, + "sha256": "9e85a77aeb913c0913b2a9de977ae617554ecd08a645ec89bca2e0809fff761b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/types.py", + "bytes": 1041, + "sha256": "f3bc13bb0175d37e90c51b1ede62f9a8eb92f62e1af7168864ca938d8fa5befa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette/websockets.py", + "bytes": 8518, + "sha256": "99f948f4b53095ff77feeba323cc3eb58fa7a12fb375111bcda52678e5433fd7" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/METADATA", + "bytes": 6610, + "sha256": "f7870fc19ab7c2e364887057340b9fb47e9bda74d24b06c58ca399494abfe019" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/RECORD", + "bytes": 3385, + "sha256": "b9631f5f5194ad373e7c54846ffe8a0003e9380c7aa3acc1963ee2269d831aaa" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/starlette-1.7.0.dist-info/licenses/LICENSE.md", + "bytes": 1518, + "sha256": "dcb95677a02240243187e964f941847d19b17821cf99e5afae684fab328c19bf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/METADATA", + "bytes": 3310, + "sha256": "b05084ca1d50879865178d9fff9fabeab61bdfb1f361bfbde95421ffc8f9be46" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/RECORD", + "bytes": 427, + "sha256": "44dcf97810bea7b84bcd83890b6674bbcc2f4c5ef899a4887f15d524bdf6bb97" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/WHEEL", + "bytes": 82, + "sha256": "1b68144734c4b66791f27add5d425f3620775585718a03d0f9b110ba3a4d88db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_extensions-4.16.0.dist-info/licenses/LICENSE", + "bytes": 13936, + "sha256": "3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_extensions.py", + "bytes": 165012, + "sha256": "4040ca1a1ecbee00d1385c12a93084d1c5bd46f0b774f07e5ae7e91c4f55e696" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection/introspection.py", + "bytes": 22550, + "sha256": "5888c45bd627da692d9527699e3f549adf35651596b1656772310e12181b4396" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection/py.typed", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.py", + "bytes": 17838, + "sha256": "a778d6290d51581c9c9a23e0263af2a4604c76def5c4d40a7cd1a375b0046ff6" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection/typing_objects.pyi", + "bytes": 9635, + "sha256": "3bd24dd92d0270d31d64fd40abc1ef8693f5322aab9a78aeb3c5b62f992f84be" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/METADATA", + "bytes": 2554, + "sha256": "bad3d9e6342ef6750cf5128ec942327b8bebdce0b6e4d6f67cb15b27ae5118da" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/RECORD", + "bytes": 784, + "sha256": "9bb32e43a7b1ef8c82b8f855089cecfea282157ab49728812a464e41200fee6a" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/WHEEL", + "bytes": 87, + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/typing_inspection-0.4.4.dist-info/licenses/LICENSE", + "bytes": 1090, + "sha256": "804b59b25f2c31bd278f9202a19ae49a3945aa2664387e2d0a128c7cacc61ec3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/__init__.py", + "bytes": 147, + "sha256": "6330ecb843b39a91aaedb219f3d2ad68a7fe623ce49e8be6cda2257a4f5f903b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/__main__.py", + "bytes": 62, + "sha256": "0d08b3cba9ca3f4cb084fa6708782644360831f72a64a544ccd2164198eab554" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/_ansi.py", + "bytes": 617, + "sha256": "b3c44fa663d33ba4064601186ec34679ab125aa8849c2b13ae8c80f19e7d434d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/_compat.py", + "bytes": 2916, + "sha256": "e97e3d73da2fce63078abd92decca29ce40ccd23c1729f0c36e23d650adb6225" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/_subprocess.py", + "bytes": 2776, + "sha256": "7d0e094da8ee5dacba99573c2903467d7bba247a1a6f8e9a4ba93220c58930f9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/_types.py", + "bytes": 7677, + "sha256": "24333a13e86a67aa3f71266f8c350b8c0bfbecf147da92defd582ab217bca87e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/config.py", + "bytes": 25482, + "sha256": "cf91c6ffae283a2b2020da954b4cc4ed47d3246cb062afd09fb77d98e4c5ca87" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/importer.py", + "bytes": 1128, + "sha256": "9d1b74410deaa62dbae3e9ff991d25e790b675d33c9e8c133734f58ec59a6a6f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/off.py", + "bytes": 332, + "sha256": "9df23aa87014a3ff3e0445cc04aa0743dc146ec5eb3da5cb09b0d24b4bca4abf" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/lifespan/on.py", + "bytes": 5173, + "sha256": "8844cf163e921613c33dc75e1b17dae372ca226254ecc2398bb41451ea4170f1" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/logging.py", + "bytes": 4256, + "sha256": "9020759f8e41e4ee794920298afdb1e94406dcb9c5a8d946bdca211b20306e55" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/loops/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/loops/asyncio.py", + "bytes": 333, + "sha256": "6bcc0ee1f078a4293e44b202abb0a71abfdcc5023b3012c98fe1a8e14425743f" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/loops/auto.py", + "bytes": 566, + "sha256": "a5d4ec31caa46c4f1fbdd3602ecd72e1cea1b2e379f7c9993923a50b46b6dd2e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/loops/uvloop.py", + "bytes": 236, + "sha256": "09af932fe5bcb5b7445b9baf2ff1ab32287b1bc7bfb7fbe135ebe2d8d2988c45" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/loops/zuvloop.py", + "bytes": 239, + "sha256": "e9e8bf423468218a8c5ca07852765290eea4e9af0fa9c8d65799afcd8911d7ad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/main.py", + "bytes": 19049, + "sha256": "ecb83544b48126d700e803d1d558ef158346961a82d0fc5fd5fe77d8befa85df" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/asgi2.py", + "bytes": 394, + "sha256": "610ad0366dd17a116db379b3937938cb0f1a0fc120b63d2d452dcde3962441ad" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/message_logger.py", + "bytes": 2859, + "sha256": "2071195129c535a305505770b593b702e1404e761c4a8afe815b4e8db0b3b7c3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/proxy_headers.py", + "bytes": 7421, + "sha256": "a4d664f3434db1c2c4c22f291a7e9c21b685d1b49f26108a9f7a478eaa9f5bdd" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/middleware/wsgi.py", + "bytes": 7125, + "sha256": "5676e791d08bdff72d2d6ce4d63dfdc6fc72b2ea000779126268bc4ec38ba4db" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto.py", + "bytes": 403, + "sha256": "61f5c6cb359369a136a7f8e44cf5ab2425ecc446909c2dcd2b4f86ed68269e5b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/auto_zttp_impl.py", + "bytes": 3272, + "sha256": "72fa8e52f577f2d739803e2da4d8622fb21e20ac88a02c820867bd46d9d0b4a9" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/flow_control.py", + "bytes": 1701, + "sha256": "d39d16560df512f3ce907c329c2a0c3f5cd7165fef3b753876eae5739bf2a785" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/h11_impl.py", + "bytes": 20886, + "sha256": "a3154a6c2cfaaaebe19ac7aa255dfe9b6b7b490de1da95274c033aa093e8c322" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/httptools_impl.py", + "bytes": 22564, + "sha256": "1d4e1c579df4168433a61a01f10c628b31c1790e72dd9769d2b8633b22214033" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_h2_impl.py", + "bytes": 19950, + "sha256": "1dd5b8bda642daec8c29bb286e1cde2c681c47ff4886914259b6ba59ea9a53a4" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/http/zttp_impl.py", + "bytes": 21835, + "sha256": "e630a7ad5bb36b8a68cda8ec88e85857040b5cf2a00a2c14567d78a74fb3d985" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/utils.py", + "bytes": 2058, + "sha256": "d6129d0c29a8fc6b46acad7365f6bbd232fb1ebde60c4f119cbebd3b8ac74118" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/__init__.py", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/auto.py", + "bytes": 611, + "sha256": "40e9cc2997d71df39c64776ad4e2131f0bfb030c750534b77897d9b30b86114d" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_impl.py", + "bytes": 15784, + "sha256": "6694705fa9d290cae88c268082a0198de028331640a095bd6df52fbf238bb141" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py", + "bytes": 23861, + "sha256": "8b32a97a1608788bba525c8b1e0d3eb0a93cd09d0f4f41b9b83cebd527336c49" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/protocols/websockets/wsproto_impl.py", + "bytes": 21138, + "sha256": "bb30b15e7bcdbf46a408858d635ab60e7b8e35c766ad86039a0391b3179373c3" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/py.typed", + "bytes": 1, + "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/server.py", + "bytes": 13705, + "sha256": "c1be9d133467a42ab88d7168e408ef5a90257661a901899426214a3d1fc2d026" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/__init__.py", + "bytes": 507, + "sha256": "c13f1e38422a4f5c96420cad66dbf9b5a58cba5ef1a13218d319b59b8a323d3c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/basereload.py", + "bytes": 4018, + "sha256": "0be603d7c7e538a29544d4d1b1208e12fcaa19fdd9c69bee52f474cf46355a98" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/multiprocess.py", + "bytes": 10271, + "sha256": "53541976227fec0b9ace2c7a5ce8f592ed0d8ff37f3c93c2a18baae9236365c2" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/statreload.py", + "bytes": 1548, + "sha256": "5cf684b555da6ac208d64906df65141bca03efe7d2d05c325e440b17d4183db0" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/supervisors/watchfilesreload.py", + "bytes": 2923, + "sha256": "bbe32e626d0bb1930debbd2efd3b525a2301500db3e23d6c21edced80ebb129e" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn/workers.py", + "bytes": 3873, + "sha256": "94c0ae6e7d568bee21da3c6b5cd32684e259cbb64ff10c5b65b3f7ff63b58871" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/METADATA", + "bytes": 6607, + "sha256": "bcfb3b631d31e3ff7c6992428b0480a09d407ec829905aa9301f736bd854cb45" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/RECORD", + "bytes": 4316, + "sha256": "6bee89df69b191027a3a2c0a779a706965b50158588e00041fc85c05b5f0de51" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/WHEEL", + "bytes": 87, + "sha256": "ccec20e2307acd7da453dd7437e70c6b08e2bc3ead3bc3445af135da37b56d59" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/entry_points.txt", + "bytes": 46, + "sha256": "156d70fa191cf50830686a28bccbe6d1963bdf0fcd7327167460147c3b0d1b1c" + }, + { + "path": "FrameBridge-Fleet-Hub/vendor/uvicorn-0.53.0.dist-info/licenses/LICENSE.md", + "bytes": 1526, + "sha256": "efe1acf3e62fb99c288b0ec73e5a773b7268ef4320fe757ea994214e4b63c371" + }, + { + "path": "FrameBridge-Front-Door/bin/front_door_mcp.py", + "bytes": 29408, + "sha256": "165d7a9f9caadd5c4d385c2764ef124fc6568a0211a8f8dbc8c784ecf55eac88" + }, + { + "path": "FrameBridge-Front-Door/bin/gerameg_cua_frontdoor.py", + "bytes": 9309, + "sha256": "9f228a3b7371f43c3434ff9f9bb7d700310fa90a6178d96a495852fc13d26666" + }, + { + "path": "FrameBridge-MCP-Private/bin/framebridge-mcp-private-run", + "bytes": 816, + "sha256": "61fc24212d60599589b451e6babe6aad81c8f5f0401e4b168409df6bc3d72f68" + }, + { + "path": "FrameBridge-MCP-Private/bin/tunnel-client", + "bytes": 21307552, + "sha256": "472eb9dd9dd625b4e6023c3b4a5736b3a2e5a1b6dbe9338e001887a64ec992a6" + } + ], + "historical_origin": "GERANAS-live-20261021", + "community_derivative": true, + "community_version": "0.3.1~community.20261026.7" +} diff --git a/source/tests/test_adaptive_runtime.py b/source/tests/test_adaptive_runtime.py new file mode 100644 index 0000000..e759840 --- /dev/null +++ b/source/tests/test_adaptive_runtime.py @@ -0,0 +1,158 @@ +"""Adaptive text/graphical selection; inert CUA stubs never send user input.""" +import base64 +import hashlib +import json +import os +from pathlib import Path +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch +from framebridge_expanded import adapter +from framebridge_expanded import config +from framebridge_expanded import display_context as dc +from framebridge_expanded import runtime + + +def physical(**changes): + data=dict(kind='physical-x11',physical=True,session_id='fixture-session',uid=os.getuid(), + leader=123,active_vt='tty7',seat='seat0',vtnr=7,display=':0',authority='/private/fixture.Xauthority') + data.update(changes) + return data + + +def capture_output(valid=True): + if not valid: return 'VISUAL_CAPTURE_GATE=PASS\nMIME=image/jpeg\nBASE64=invalid' + pixels=b'\xff\xd8synthetic-unit-fixture\xff\xd9' + return '\n'.join(['RESULTADO_FINAL=CUA_VISUAL_OBSERVE_PASS','VISUAL_CAPTURE_GATE=PASS','MIME=image/jpeg','BYTES='+str(len(pixels)), + 'SHA256='+hashlib.sha256(pixels).hexdigest(),'BASE64='+base64.b64encode(pixels).decode()]) + + +class AdaptiveDisplayTests(unittest.TestCase): + def test_auto_missing_graphical_tools_selects_text_and_clears_inherited_display(self): + env={'DISPLAY':':99','XAUTHORITY':'/unrelated'} + with patch.object(dc,'_physical',return_value=physical()), patch.object(dc,'missing_graphical_commands',return_value=['xdotool']): + dc.configure(env,'auto','/profile') + self.assertEqual(dc.describe(env)['kind'],'text') + self.assertNotIn('DISPLAY',env) + self.assertNotIn('XAUTHORITY',env) + self.assertFalse(dc.private_display_required(env,'auto',False)) + + def test_text_to_graphical_and_back_preserves_profile_and_requires_new_observation(self): + env={};dc.bind_text(env,'/profile') + with patch.object(dc,'_physical',return_value=physical()),patch.object(dc,'missing_graphical_commands',return_value=[]): + dc.prepare(env) + self.assertEqual(dc.check(env)['kind'],'physical-x11') + self.assertTrue(dc.describe(env)['needs_observation']) + dc.prepare(env) + self.assertTrue(dc.describe(env)['needs_observation']) + with patch.object(dc,'_physical',side_effect=dc.DisplayContextError('NO_SESSION')): + dc.prepare(env) + self.assertEqual(dc.describe(env)['kind'],'text') + self.assertEqual(dc.describe(env)['profile'],'/profile') + with self.assertRaisesRegex(dc.DisplayContextError,'CUA_DISPLAY_UNAVAILABLE'): + dc.check(env) + + def stub(self,env,valid=True): + calls=[] + engine=SimpleNamespace() + def native(payload,timeout_s=60): + calls.append(payload) + return {'executed':True,'output':capture_output(valid)} + engine.native_cua_roundtrip=native + def handle(request,job): + response=engine.native_cua_roundtrip(request.get('cua',{})) + return {'ok':response['executed']} + engine.handle_cua_request=handle + adapter.install_adapter_guard(engine,env=env) + return engine,calls + + def test_transition_blocks_input_until_successful_observation_then_allows_input(self): + env={};dc.bind_text(env,'/profile');engine,calls=self.stub(env) + with patch.object(dc,'_physical',return_value=physical()),patch.object(dc,'missing_graphical_commands',return_value=[]): + blocked=engine.handle_cua_request({'cua':{'action':'CLICK'}},'job1') + self.assertFalse(blocked['performed']);self.assertEqual(calls,[]) + self.assertEqual(blocked['error'],'CUA_DISPLAY_CHANGED_OBSERVE_REQUIRED') + observed=engine.handle_cua_request({'cua':{'action':'OBSERVE'}},'job2') + self.assertTrue(observed['ok']);self.assertFalse(dc.describe(env).get('needs_observation',False)) + self.assertTrue(engine.handle_cua_request({'cua':{'action':'CLICK'}},'job3')['ok']) + self.assertEqual(len(calls),2) + + def test_invalid_observation_does_not_unlock_input_after_transition(self): + env={};dc.bind_text(env,'/profile');engine,calls=self.stub(env,valid=False) + with patch.object(dc,'_physical',return_value=physical()),patch.object(dc,'missing_graphical_commands',return_value=[]): + engine.handle_cua_request({'cua':{'action':'OBSERVE'}},'job1') + self.assertTrue(dc.describe(env)['needs_observation']) + blocked=engine.handle_cua_request({'cua':{'action':'CLICK'}},'job2') + self.assertFalse(blocked['performed']);self.assertEqual(len(calls),1) + + def test_session_changes_during_observation_keep_input_locked(self): + env={};dc.bind_text(env,'/profile');engine,calls=self.stub(env) + with patch.object(dc,'_physical',side_effect=[physical(),physical(),physical(session_id='different')]),patch.object(dc,'missing_graphical_commands',return_value=[]): + result=engine.handle_cua_request({'cua':{'action':'OBSERVE'}},'job1') + self.assertFalse(result['ok']) + self.assertEqual(result['error'],'CUA_FOREGROUND_SESSION_CHANGED') + self.assertTrue(dc.describe(env)['needs_observation']) + + def test_session_change_at_observation_release_returns_safe_block_and_keeps_input_locked(self): + env={};dc.bind_text(env,'/profile');engine,calls=self.stub(env) + with patch.object(dc,'_physical',side_effect=[physical(),physical(),physical(),physical(session_id='different')]),patch.object(dc,'missing_graphical_commands',return_value=[]): + result=engine.handle_cua_request({'cua':{'action':'OBSERVE'}},'job1') + self.assertFalse(result['ok']) + self.assertFalse(result['performed']) + self.assertFalse(result['input_may_have_been_sent']) + self.assertEqual(result['error'],'CUA_FOREGROUND_SESSION_CHANGED') + self.assertTrue(dc.describe(env)['needs_observation']) + with patch.object(dc,'_physical',return_value=physical(session_id='different')),patch.object(dc,'missing_graphical_commands',return_value=[]): + blocked=engine.handle_cua_request({'cua':{'action':'CLICK'}},'job2') + self.assertFalse(blocked['performed']) + self.assertEqual(blocked['error'],'CUA_DISPLAY_CHANGED_OBSERVE_REQUIRED') + self.assertEqual(len(calls),1) + + +class AdaptiveCoreTests(unittest.TestCase): + def setUp(self): + self.temp=tempfile.TemporaryDirectory(prefix='fb-adaptive-test-') + self.state=Path(self.temp.name) + config.save_config(self.state,config.default_config()) + self.env={'PATH':'/fixture/bin'} + dc.bind_text(self.env,self.state) + + def tearDown(self): self.temp.cleanup() + + def doctor(self,missing): + def which(name,path=None): return None if name in missing else '/fixture/bin/'+name + with patch.object(runtime,'environment',return_value=dict(self.env)),patch.object(runtime.shutil,'which',side_effect=which),patch('framebridge_expanded.network.inspect',return_value={}): + return runtime.doctor(self.state) + + def test_optional_gui_dependencies_do_not_block_text_tools(self): + result=self.doctor(set(dc.GRAPHICAL_COMMANDS+dc.VIRTUAL_COMMANDS+('magick','convert','gnome-terminal','xterm'))) + self.assertTrue(result['ok']) + self.assertEqual(result['missing_commands'],[]) + self.assertTrue(result['capabilities']['fbexec']) + self.assertTrue(result['capabilities']['fbterm']) + self.assertTrue(result['capabilities']['cua_tty_module']) + self.assertFalse(result['capabilities']['graphical_cua']) + self.assertTrue(result['optional_missing_commands']['graphical_cua']) + + def test_missing_core_command_still_blocks_startup(self): + result=self.doctor({'bash'}) + self.assertFalse(result['ok']);self.assertIn('bash',result['missing_commands']) + + def test_explicit_private_desktop_requires_its_optional_components(self): + current=config.load_config(self.state);current['headless']='yes';config.save_config(self.state,current) + result=self.doctor({'Xvfb'}) + self.assertFalse(result['ok']);self.assertIn('Xvfb',result['missing_commands']) + + def test_text_manager_starts_core_session_without_xvfb_or_window_manager(self): + spawned=[] + def spawn(command,env,state,role): + spawned.append((command,role)) + return SimpleNamespace(poll=lambda:None),{'role':role} + with patch.object(runtime,'environment',return_value=dict(self.env)),patch.object(runtime,'_display_ok',return_value=False),patch.object(runtime,'_spawn',side_effect=spawn),patch.object(runtime,'_atomic'),patch.object(runtime,'_terminate'),patch.object(runtime.signal,'signal'),patch.object(runtime,'_STOP',True): + runtime._manager(self.state) + self.assertEqual([role for _,role in spawned],['private-session']) + self.assertEqual(spawned[0][0][0],'dbus-run-session') + + +if __name__=='__main__': unittest.main() diff --git a/source/tests/test_agent_guide.py b/source/tests/test_agent_guide.py new file mode 100644 index 0000000..9cde68d --- /dev/null +++ b/source/tests/test_agent_guide.py @@ -0,0 +1,116 @@ +"""Pure guide/catalog checks; no server, connection or node is started.""" +import ast +import copy +import json +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest import mock +from framebridge_expanded import __version__ +from framebridge_expanded import guide + +SOURCE=Path(__file__).resolve().parents[1] +FRONT=SOURCE/'portable/front_door_mcp.py' + +class FakeMCP: + def __init__(self,name,**kwargs): + self.name=name;self.options=kwargs + self._mcp_server=SimpleNamespace(version=None) + self._tool_manager=SimpleNamespace(_tools={}) + self.annotations={} + def tool(self,**kwargs): + def register(fn): + self._tool_manager._tools[fn.__name__]=fn + self.annotations[fn.__name__]=kwargs.get('annotations') + return fn + return register + +def guide_surface(): + tree=ast.parse(FRONT.read_text()) + names={'_registered_operations','framebridge_help','framebridge_status'} + keep=[] + for node in tree.body: + if isinstance(node,ast.Assign) and ( + any(isinstance(t,ast.Name) and t.id=='mcp' for t in node.targets) + or any(isinstance(t,ast.Attribute) and t.attr=='version' for t in node.targets)): + keep.append(copy.deepcopy(node)) + elif isinstance(node,ast.FunctionDef) and node.name in names: + keep.append(copy.deepcopy(node)) + ns={'FastMCP':FakeMCP,'ToolAnnotations':lambda **kw:kw,'PRODUCT_VERSION':__version__, + 'GUIDE_SCHEMA':guide.GUIDE_SCHEMA,'get_guide':guide.get_guide, + 'socket':SimpleNamespace(gethostname=lambda:'fixture-receiver'),'HOST':'127.0.0.1','PORT':20865, + 'ADAPTER_SOCKET':SimpleNamespace(is_socket=lambda:False)} + exec(compile(ast.fix_missing_locations(ast.Module(body=keep,type_ignores=[])),str(FRONT),'exec'),ns) + return ns + +class GuideTests(unittest.TestCase): + def test_guide_versions_topics_and_bounds(self): + self.assertEqual(len(guide.topics()),10) + for topic in (*guide.topics(),'all'): + value=guide.get_guide(topic) + self.assertTrue(value['ok']) + self.assertEqual(value['version'],__version__) + self.assertTrue(value['read_only']) + self.assertFalse(value['capabilities_verified']) + self.assertLess(len(json.dumps(value).encode()),65536) + self.assertRegex(value['guide_sha256'],r'^[a-f0-9]{64}$') + def test_guide_reads_no_profile_or_network(self): + with mock.patch('builtins.open',side_effect=AssertionError('no file reads')), \ + mock.patch('subprocess.run',side_effect=AssertionError('no processes')), \ + mock.patch('socket.socket',side_effect=AssertionError('no sockets')): + self.assertTrue(guide.get_guide('start')['ok']) + def test_invalid_topic_is_bounded_actionable(self): + self.assertEqual(guide.get_guide('missing')['error'],'HELP_TOPIC_UNKNOWN') + self.assertEqual(guide.get_guide('x'*65)['error'],'HELP_TOPIC_INVALID') + with self.assertRaises(ValueError):guide.render_text('missing') + def test_guides_have_no_fixed_owner_paths_or_account(self): + text=json.dumps(guide.export_guide()) + for marker in ['/home/gerabh','/home/gera/','gerabh1128@','GERAMEG','ASUS','GERANAS']: + self.assertNotIn(marker,text) + def test_human_agent_and_transcript_boundary_are_explicit(self): + text=guide.render_text('all') + self.assertIn('human operators and authorized agents',text) + self.assertIn('terminal transcript',text) + self.assertIn('not an authentic full-screen image',text) + self.assertIn('normal operating-system authentication',text) + self.assertIn('does not restrict GNU GPL freedoms',text) + def test_export_matches_installed_web_inputs(self): + data=json.loads((SOURCE/'docs/COMMAND-GUIDE.json').read_text()) + self.assertEqual(data,guide.export_guide()) + self.assertIn('FrameBridge '+__version__,(SOURCE/'docs/framebridge.1').read_text()) + self.assertNotIn('Reconnecting requires entering the runtime key again', + (SOURCE/'docs/framebridge.1').read_text()) + def test_development_guidance_preserves_license_freedom(self): + text=guide.render_text('security') + self.assertIn('separate source clone',text) + self.assertIn('do not present a modified build as an official release',text) + self.assertIn('GNU GPLv3-only',text) + +class MCPGuideSurfaceTests(unittest.TestCase): + def test_init_invites_learning_and_has_product_version(self): + ns=guide_surface();mcp=ns['mcp'] + self.assertIn("framebridge_help(topic='start')",mcp.options['instructions']) + self.assertEqual(mcp._mcp_server.version,__version__) + self.assertEqual(mcp.name,'FrameBridge') + def test_help_is_registered_readonly_and_bounded(self): + ns=guide_surface();value=ns['framebridge_help']('start') + self.assertTrue(value['ok']);self.assertEqual(value['receiver'],'fixture-receiver') + self.assertIn('framebridge_help',value['registered_operations']) + self.assertTrue(ns['mcp'].annotations['framebridge_help']['readOnlyHint']) + self.assertNotIn('adapter_socket',value) + def test_status_query_success_is_not_adapter_readiness(self): + ns=guide_surface();value=ns['framebridge_status']() + self.assertTrue(value['query_success']) + self.assertFalse(value['adapter_socket_present']) + self.assertEqual(value['adapter_ready'],'not_checked') + self.assertEqual(value['version'],__version__) + self.assertNotIn('adapter_socket',value) + def test_historical_lab_tools_are_not_publicly_decorated(self): + tree=ast.parse(FRONT.read_text()) + byname={n.name:n for n in tree.body if isinstance(n,(ast.FunctionDef,ast.AsyncFunctionDef))} + for name in ['framebridge_exec_geranas','framebridge_cloud_dsm']: + self.assertFalse(byname[name].decorator_list) + self.assertTrue(byname['framebridge_exec'].decorator_list) + self.assertTrue(byname['framebridge_help'].decorator_list) + +if __name__=='__main__':unittest.main() diff --git a/source/tests/test_autostart.py b/source/tests/test_autostart.py new file mode 100644 index 0000000..8b31dda --- /dev/null +++ b/source/tests/test_autostart.py @@ -0,0 +1,660 @@ +import fcntl +import io +import json +import os +from pathlib import Path +import signal +import stat +import subprocess +import tempfile +import unittest +from unittest import mock +from framebridge_expanded import autostart, config, runtime, connection_background + +ID = "tunnel_0123456789abcdef0123456789abcdef" +MARKER = "FRAMEBRIDGE-SYNTHETIC-NOT-A-REAL-KEY-AUTOSTART" + + +class AutostartTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.home = self.root / "home" + self.home.mkdir(mode=0o700) + self.state = self.home / "profile" + self.settings = config.default_config() + self.settings.update(tunnel_id=ID, headless="no") + config.save_config(self.state, self.settings) + self.addCleanup(mock.patch.stopall) + mock.patch.object(Path, "home", return_value=self.home).start() + self.calls = [] + self.system_calls = [] + self.command = mock.patch.object(autostart, "_command", side_effect=self.fake_command).start() + mock.patch.object(autostart, "_systemctl", side_effect=self.fake_systemctl).start() + mock.patch.object(autostart, "_linger", return_value=False).start() + mock.patch.object(autostart, "_enabled_units", return_value=[]).start() + mock.patch.object(autostart.os, "geteuid", return_value=1000).start() + mock.patch.object(autostart.resource, "setrlimit").start() + self.inputs = mock.patch("builtins.input", side_effect=["y"]).start() + self.key_prompt = mock.patch.object(autostart.getpass, "getpass", return_value=MARKER).start() + mock.patch.object(autostart, "status", return_value={"configured": True}).start() + mock.patch("sys.stdout", new_callable=io.StringIO).start() + + def fake_command(self, args, **kwargs): + self.calls.append((args, kwargs)) + if args[:2] == ["systemd-creds", "--version"]: + return subprocess.CompletedProcess(args, 0, b"systemd 257\n", b"") + if "encrypt" in args: + return subprocess.CompletedProcess(args, 0, b"CIPHERTEXT-ONLY-" * 20, b"") + return subprocess.CompletedProcess(args, 0, b"", b"") + + def fake_systemctl(self, *args, **kwargs): + self.system_calls.append(args) + return subprocess.CompletedProcess(args, 0, b"", b"") + + def test_enable_passes_key_only_by_anonymous_input_and_enables_boot_fallback(self): + before = (self.state / "config.json").read_bytes() + autostart.enable(self.state) + state, directory, unit = autostart._paths(self.state) + self.assertNotEqual(before, (self.state / "config.json").read_bytes()) + self.assertEqual("auto", config.load_config(self.state)["headless"]) + encryption = [(a, kw) for a, kw in self.calls if "encrypt" in a] + self.assertEqual(1, len(encryption)) + args, options = encryption[0] + self.assertEqual(MARKER.encode(), options["data"]) + self.assertNotIn(MARKER, " ".join(args)) + self.assertIn("--with-key=host", args) + self.assertIn("--uid=" + str(os.getuid()), args) + for path in [directory / "credential.cred", directory / "installation.json", unit]: + self.assertEqual(0o600, stat.S_IMODE(path.stat().st_mode)) + self.assertNotIn(MARKER.encode(), path.read_bytes()) + self.assertNotIn("LoadCredentialEncrypted=", unit.read_text()) + self.assertIn("StandardInput=null", unit.read_text()) + self.assertIn(("enable", "--now", unit.name), self.system_calls) + autostart.enable(self.state) + self.assertEqual(1, self.key_prompt.call_count) + self.inputs.assert_called_once_with("Enable automatic startup? [y/N]: ") + + def test_boot_fallback_restores_only_unchanged_profile_on_disable(self): + self.inputs.side_effect = ["y"] + autostart.enable(self.state) + self.assertEqual("auto", config.load_config(self.state)["headless"]) + result = autostart.disable(self.state) + self.assertTrue(result["desktop_setting_restored"]) + self.assertTrue(result["linger_restored"]) + self.assertEqual("no", config.load_config(self.state)["headless"]) + _, directory, unit = autostart._paths(self.state) + self.assertFalse(unit.exists()) + self.assertFalse((directory / "credential.cred").exists()) + + def test_disable_keeps_desktop_changes_made_after_enable(self): + self.inputs.side_effect = ["y"] + autostart.enable(self.state) + settings = config.load_config(self.state) + settings["name"] = "changed-later" + config.save_config(self.state, settings) + result = autostart.disable(self.state) + self.assertFalse(result["desktop_setting_restored"]) + self.assertEqual("changed-later", config.load_config(self.state)["name"]) + self.assertEqual("auto", config.load_config(self.state)["headless"]) + + def test_modified_unit_is_not_stopped_or_removed(self): + autostart.enable(self.state) + _, _, unit = autostart._paths(self.state) + unit.write_text(unit.read_text() + "# external edit\n") + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "changed outside"): + autostart.disable(self.state) + self.assertEqual([], self.system_calls) + self.assertTrue(unit.exists()) + + def test_encryption_failure_never_creates_unit_or_disables_an_existing_service(self): + def failing(args, **kwargs): + if "encrypt" in args: + raise RuntimeError("Credential encryption failed.") + return self.fake_command(args, **kwargs) + self.command.side_effect = failing + with self.assertRaisesRegex(RuntimeError, "encryption failed"): + autostart.enable(self.state) + _, directory, unit = autostart._paths(self.state) + self.assertFalse(unit.exists()) + self.assertFalse((directory / "credential.cred").exists()) + self.assertFalse(any(x[0] == "disable" for x in self.system_calls)) + + def test_cancel_never_requests_credentials_or_privileges(self): + self.inputs.side_effect = ["cancel"] + result = autostart.enable(self.state) + self.assertEqual("cancelled", result["state"]) + self.key_prompt.assert_not_called() + self.assertFalse(any(a[0] == "sudo" for a, _ in self.calls)) + + def test_default_no_keeps_profile_unchanged_without_privileges_or_key(self): + before = (self.state / "config.json").read_bytes() + self.inputs.side_effect = [""] + result = autostart.enable(self.state) + self.assertEqual({"configured": False, "state": "cancelled"}, result) + self.assertEqual(before, (self.state / "config.json").read_bytes()) + self.key_prompt.assert_not_called() + self.assertFalse(any(args[0] == "sudo" for args, _ in self.calls)) + self.assertEqual([], self.system_calls) + _, directory, unit = autostart._paths(self.state) + self.assertFalse(unit.exists()) + self.assertFalse((directory / "credential.cred").exists()) + + def test_natural_confirmation_is_case_insensitive_and_requested_once(self): + self.inputs.side_effect = [" YeS "] + autostart.enable(self.state) + autostart.enable(self.state) + self.inputs.assert_called_once_with("Enable automatic startup? [y/N]: ") + self.key_prompt.assert_called_once_with("Connection key (hidden): ") + + def test_setup_shows_bounded_progress_without_claiming_connected(self): + output = io.StringIO() + with mock.patch("sys.stdout", output): + autostart.enable(self.state) + text = output.getvalue() + self.assertLess(text.index("[1/3]"), text.index("[2/3]")) + self.assertLess(text.index("[2/3]"), text.index("[3/3]")) + self.assertLessEqual(len(text.splitlines()), 6) + self.assertTrue(all(len(line) <= 80 for line in text.splitlines())) + self.assertNotIn(MARKER, text) + self.assertNotIn("connected", text.lower()) + self.assertNotIn("systemd", text) + self.assertNotIn("physical-only", text) + self.assertNotIn("Root access", text) + + def test_eof_or_interrupt_at_confirmation_cancels_without_authentication(self): + for cancellation in (EOFError, KeyboardInterrupt): + with self.subTest(cancellation=cancellation.__name__): + self.inputs.side_effect = cancellation + result = autostart.enable(self.state) + self.assertEqual("cancelled", result["state"]) + self.assertFalse(result["configured"]) + self.key_prompt.assert_not_called() + self.assertFalse(any(args[0] == "sudo" for args, _ in self.calls)) + self.assertEqual([], self.system_calls) + + def _durable_settings(self, identifier=ID): + return {"account_label": "owner@example.test", "tunnel_id": identifier, + "control_plane_organization_id": "", "control_plane_url_path": ""} + + def _durable_connect(self, key=MARKER, settings=None): + with mock.patch.object(connection_background, "status", return_value={"alive": False}), mock.patch.object(connection_background, "disconnect", return_value={"ok": True, "alive": False}): + return autostart.configure_connection(self.state, settings or self._durable_settings(), key) + + def test_durable_connection_uses_supplied_key_once_without_extra_questions(self): + self._durable_connect() + self.inputs.assert_not_called() + self.key_prompt.assert_not_called() + encryptions = [(args, kw) for args, kw in self.calls if "encrypt" in args] + self.assertEqual(1, len(encryptions)) + self.assertEqual(MARKER.encode(), encryptions[0][1]["data"]) + self.assertNotIn(MARKER, " ".join(encryptions[0][0])) + _, directory, unit = autostart._paths(self.state) + for path in (self.state / "config.json", directory / "credential.cred", directory / "installation.json", unit): + self.assertNotIn(MARKER.encode(), path.read_bytes()) + self.assertEqual("auto", config.load_config(self.state)["headless"]) + self.assertIn(("enable", "--now", unit.name), self.system_calls) + + def test_interrupt_reading_status_after_commit_keeps_enabled_service_and_reports_it(self): + with mock.patch.object(autostart, "status", side_effect=KeyboardInterrupt): + result = self._durable_connect() + self.assertEqual({"configured": True, "enabled": True, "interrupted_after_commit": True}, result) + _, directory, unit = autostart._paths(self.state) + self.assertTrue(unit.exists()) + self.assertTrue((directory / "credential.cred").exists()) + self.assertTrue((directory / "installation.json").exists()) + self.assertIn(("enable", "--now", unit.name), self.system_calls) + self.assertNotIn(("disable", "--now", unit.name), self.system_calls) + + def test_durable_connection_can_create_default_profile_in_the_transaction(self): + (self.state / "config.json").unlink() + self._durable_connect() + selected = config.load_config(self.state) + self.assertEqual("standalone", selected["role"]) + self.assertEqual("auto", selected["headless"]) + self.assertEqual("owner@example.test", selected["account_label"]) + self.assertEqual(ID, selected["tunnel_id"]) + + def test_durable_encryption_failure_leaves_current_profile_and_service_alone(self): + original = (self.state / "config.json").read_bytes() + def failed(args, **kwargs): + if "encrypt" in args: + raise RuntimeError("Protection failed.") + return self.fake_command(args, **kwargs) + self.command.side_effect = failed + with mock.patch.object(connection_background, "disconnect") as disconnect: + with self.assertRaisesRegex(RuntimeError, "Protection failed"): + self._durable_connect() + self.assertEqual(original, (self.state / "config.json").read_bytes()) + self.assertFalse(any(args[0] in {"stop", "enable", "disable"} for args in self.system_calls)) + _, directory, unit = autostart._paths(self.state) + self.assertFalse(unit.exists()) + self.assertFalse((directory / "credential.cred").exists()) + + def test_durable_replacement_encrypts_before_stop_and_replaces_public_identity(self): + self._durable_connect() + events = [] + original_command = self.fake_command + def command(args, **kwargs): + if "encrypt" in args: + events.append("encrypt") + return subprocess.CompletedProcess(args, 0, b"REPLACEMENT-CIPHERTEXT-" * 20, b"") + return original_command(args, **kwargs) + def service(*args, **kwargs): + events.append(args[0]) + if args[0] == "show": + return subprocess.CompletedProcess(args, 0, b"ActiveState=active\nUnitFileState=enabled\n", b"") + return self.fake_systemctl(*args, **kwargs) + live = {"alive": True} + def disconnect(state): + events.append("disconnect") + live["alive"] = False + return {"ok": True, "alive": False} + self.command.side_effect = command + new_id = "tunnel_replacement0123456789" + with mock.patch.object(autostart, "_systemctl", side_effect=service), mock.patch.object(connection_background, "status", side_effect=lambda state: dict(live)), mock.patch.object(connection_background, "disconnect", side_effect=disconnect): + autostart.configure_connection(self.state, self._durable_settings(new_id), MARKER + "-NEW") + self.assertLess(events.index("encrypt"), events.index("stop")) + self.assertLess(events.index("disconnect"), events.index("enable")) + self.assertEqual(new_id, config.load_config(self.state)["tunnel_id"]) + _, directory, _ = autostart._paths(self.state) + self.assertEqual(b"REPLACEMENT-CIPHERTEXT-" * 20, (directory / "credential.cred").read_bytes()) + + def test_durable_replacement_failure_restores_exact_files_and_restarts_old_unit(self): + self._durable_connect() + _, directory, unit = autostart._paths(self.state) + paths = (self.state / "config.json", unit, directory / "credential.cred", directory / "installation.json", directory / "config-before.json") + originals = {p: p.read_bytes() for p in paths} + calls = [] + def failed(*args, **kwargs): + calls.append(args) + if args[0] == "show": + return subprocess.CompletedProcess(args, 0, b"ActiveState=active\nUnitFileState=enabled\n", b"") + if args[:2] == ("enable", "--now"): + raise RuntimeError("New service did not start.") + return self.fake_systemctl(*args, **kwargs) + with mock.patch.object(autostart, "_systemctl", side_effect=failed): + with self.assertRaisesRegex(RuntimeError, "New service did not start"): + self._durable_connect(MARKER + "-NEW", self._durable_settings("tunnel_changed0123456789")) + self.assertEqual(originals, {p: p.read_bytes() for p in paths}) + self.assertIn(("start", unit.name), calls) + self.assertIn(("enable", unit.name), calls) + + def test_durable_connection_rejects_changed_existing_service_before_authentication(self): + self._durable_connect() + _, _, unit = autostart._paths(self.state) + unit.write_bytes(unit.read_bytes() + b"# external change\n") + self.calls.clear() + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "changed outside"): + self._durable_connect(MARKER + "-NEW") + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_durable_invalid_key_or_public_fields_have_no_authentication_side_effect(self): + for key, settings in (("bad key", self._durable_settings()), (MARKER + "é", self._durable_settings()), (MARKER, {**self._durable_settings(), "private_key": "not-accepted"})): + with self.subTest(key_length=len(key)): + with self.assertRaises(ValueError): + self._durable_connect(key, settings) + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_durable_failure_reports_previous_manual_connection_needs_reconnecting(self): + live = {"alive": True} + def disconnect(state): + live["alive"] = False + return {"ok": True, "alive": False} + def failed(*args, **kwargs): + if args[:2] == ("enable", "--now"): + raise RuntimeError("Start failed.") + return self.fake_systemctl(*args, **kwargs) + before = (self.state / "config.json").read_bytes() + with mock.patch.object(connection_background, "status", side_effect=lambda state: dict(live)), mock.patch.object(connection_background, "disconnect", side_effect=disconnect), mock.patch.object(autostart, "_systemctl", side_effect=failed): + with self.assertRaisesRegex(RuntimeError, "previous manually started connection was stopped"): + autostart.configure_connection(self.state, self._durable_settings(), MARKER) + self.assertEqual(before, (self.state / "config.json").read_bytes()) + + def test_durable_rotation_then_disable_preserves_new_connection_settings(self): + self._durable_connect() + replacement = self._durable_settings("tunnel_next0123456789") + replacement["account_label"] = "new@example.test" + self._durable_connect(MARKER + "-NEW", replacement) + autostart.disable(self.state) + selected = config.load_config(self.state) + self.assertEqual("no", selected["headless"]) + self.assertEqual(replacement["tunnel_id"], selected["tunnel_id"]) + self.assertEqual(replacement["account_label"], selected["account_label"]) + + def test_durable_rollback_attempts_old_restart_even_after_disable_failure(self): + self._durable_connect() + _, _, unit = autostart._paths(self.state) + calls = [] + def failed(*args, **kwargs): + calls.append(args) + if args[0] == "show": + return subprocess.CompletedProcess(args, 0, b"ActiveState=active\nUnitFileState=enabled\n", b"") + if args[:2] == ("enable", "--now"): + raise RuntimeError("Start failed.") + return subprocess.CompletedProcess(args, 1 if args[:2] == ("disable", "--now") else 0, b"", b"") + with mock.patch.object(autostart, "_systemctl", side_effect=failed): + with self.assertRaisesRegex(RuntimeError, "restoration could not be fully verified"): + self._durable_connect(MARKER + "-NEW") + self.assertIn(("start", unit.name), calls) + self.assertIn(("enable", unit.name), calls) + + def test_resume_saved_stopped_connection_asks_for_no_key_or_privilege_when_linger_exists(self): + self._durable_connect() + _, directory, unit = autostart._paths(self.state) + old_cipher = (directory / "credential.cred").read_bytes() + old_unit = unit.read_bytes() + self.calls.clear() + self.system_calls.clear() + with mock.patch.object(autostart, "_linger", return_value=True), mock.patch.object(connection_background, "disconnect", return_value={"ok": True, "alive": False}), mock.patch.object(connection_background, "status", return_value={"alive": False}): + autostart.resume_connection(self.state) + self.inputs.assert_not_called() + self.key_prompt.assert_not_called() + self.assertEqual([], self.calls) + self.assertEqual(old_cipher, (directory / "credential.cred").read_bytes()) + self.assertEqual(old_unit, unit.read_bytes()) + self.assertIn(("enable", "--now", unit.name), self.system_calls) + + def test_resume_active_verified_service_returns_without_restarting(self): + self._durable_connect() + self.calls.clear() + self.system_calls.clear() + ready = {"configured": True, "enabled": True, "service_active": True, "runtime": {"running": True, "ready": True}, "connection": {"alive": True, "verified": True}} + with mock.patch.object(autostart, "_linger", return_value=True), mock.patch.object(autostart, "status", return_value=ready): + self.assertEqual(ready, autostart.resume_connection(self.state)) + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_resume_rejects_public_tunnel_change_before_service_actions(self): + self._durable_connect() + changed = config.load_config(self.state) + changed["tunnel_id"] = "tunnel_changed0123456789" + config.save_config(self.state, changed) + self.calls.clear() + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "--replace"): + autostart.resume_connection(self.state) + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_resume_rejects_changed_cipher_before_service_actions(self): + self._durable_connect() + _, directory, _ = autostart._paths(self.state) + cipher = directory / "credential.cred" + cipher.write_bytes(cipher.read_bytes() + b"changed") + self.calls.clear() + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "--replace"): + autostart.resume_connection(self.state) + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_resume_legacy_without_any_profile_binding_requires_replace(self): + self._durable_connect() + _, directory, _ = autostart._paths(self.state) + record = autostart._manifest(directory) + record.pop("connection_settings_sha256") + record.pop("changed_config_sha256") + autostart._json(directory / "installation.json", record, replace=True) + self.calls.clear() + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "--replace"): + autostart.resume_connection(self.state) + self.assertEqual([], self.calls) + self.assertEqual([], self.system_calls) + + def test_resume_legacy_exact_profile_binding_can_be_verified_without_new_key(self): + self._durable_connect() + _, directory, _ = autostart._paths(self.state) + record = autostart._manifest(directory) + record.pop("connection_settings_sha256") + autostart._json(directory / "installation.json", record, replace=True) + ready = {"configured": True, "enabled": True, "service_active": True, "runtime": {"running": True, "ready": True}, "connection": {"alive": True, "verified": True}} + with mock.patch.object(autostart, "_linger", return_value=True), mock.patch.object(autostart, "status", return_value=ready): + self.assertEqual(ready, autostart.resume_connection(self.state)) + self.key_prompt.assert_not_called() + + def test_node_autostart_needs_no_chatgpt_key(self): + settings = config.default_config(role="node") + settings.update(headless="yes", broker_url="https://example.test:20878") + config.save_config(self.state, settings) + self.inputs.side_effect = ["yes"] + autostart.enable(self.state) + _, directory, unit = autostart._paths(self.state) + self.key_prompt.assert_not_called() + self.assertNotIn("LoadCredentialEncrypted", unit.read_text()) + self.assertFalse((directory / "credential.cred").exists()) + + def test_private_file_reader_rejects_symlink_hardlink_fifo_and_public_file(self): + original = self.root / "original" + original.write_bytes(b"safe") + original.chmod(0o600) + linked = self.root / "linked" + linked.symlink_to(original) + with self.assertRaises(OSError): + autostart._read(linked) + linked.unlink() + os.link(original, linked) + with self.assertRaises(RuntimeError): + autostart._read(original) + linked.unlink() + os.mkfifo(linked, 0o600) + with self.assertRaises(RuntimeError): + autostart._read(linked) + original.chmod(0o644) + with self.assertRaises(RuntimeError): + autostart._read(original) + + def test_configuration_lock_rejects_concurrent_enable(self): + _, directory, _ = autostart._paths(self.state) + fd = os.open(directory / "configuration.lock", os.O_RDWR | os.O_CREAT, 0o600) + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + with self.assertRaisesRegex(RuntimeError, "already in progress"): + autostart.enable(self.state) + self.key_prompt.assert_not_called() + finally: + os.close(fd) + + def test_unit_quotes_specifiers_and_dollars_without_shell_or_missing_package_loop(self): + text = autostart.unit_text(Path('/tmp/a % b ${HOME} "quoted"'), Path('/tmp/key % file')) + self.assertIn("%%", text) + self.assertIn("$${HOME}", text) + self.assertIn("ConditionPathExists=", text) + self.assertIn("LimitCORE=0", text) + self.assertIn("RestartSec=15", text) + self.assertNotIn("/bin/sh", text) + with self.assertRaises(ValueError): + autostart.unit_text(Path("/tmp/a\nExecStart=bad"), Path("/tmp/key")) + + def test_encrypted_credential_uses_os_broker_pipe_without_plaintext_files(self): + import hashlib + _, directory, _ = autostart._paths(self.state) + cipher = b"SYNTHETIC-CIPHERTEXT-ONLY" * 8 + autostart._write(directory / "credential.cred", cipher) + autostart._json(directory / "installation.json", {"schema": 1, "node": False, "credential_sha256": hashlib.sha256(cipher).hexdigest()}) + def decrypt(args, **kwargs): + self.assertEqual(["systemd-creds", "decrypt", "--user", "--name=chatgpt-runtime-key", "-", "-"], args) + self.assertEqual(cipher, kwargs["data"]) + self.assertEqual(15, kwargs["timeout"]) + self.assertNotIn(MARKER, " ".join(args)) + return subprocess.CompletedProcess(args, 0, MARKER.encode(), b"") + self.command.side_effect = decrypt + self.assertEqual(MARKER, autostart._credential(self.state)) + for path in directory.iterdir(): + if path.is_file(): + self.assertNotIn(MARKER.encode(), path.read_bytes()) + + def test_changed_cipher_is_rejected_before_decryption(self): + import hashlib + _, directory, _ = autostart._paths(self.state) + cipher = b"SYNTHETIC-CIPHERTEXT-ONLY" * 8 + autostart._write(directory / "credential.cred", cipher) + autostart._json(directory / "installation.json", {"schema": 1, "node": False, "credential_sha256": hashlib.sha256(cipher).hexdigest()}) + (directory / "credential.cred").write_bytes(cipher + b"changed") + self.command.reset_mock() + with self.assertRaisesRegex(RuntimeError, "changed outside"): + autostart._credential(self.state) + self.command.assert_not_called() + + def test_decrypt_failure_reports_no_plaintext_or_broker_output(self): + import hashlib + _, directory, _ = autostart._paths(self.state) + cipher = b"SYNTHETIC-CIPHERTEXT-ONLY" * 8 + autostart._write(directory / "credential.cred", cipher) + autostart._json(directory / "installation.json", {"schema": 1, "node": False, "credential_sha256": hashlib.sha256(cipher).hexdigest()}) + self.command.side_effect = RuntimeError("Autostart command failed: systemd-creds. No command output was exported.") + with self.assertRaisesRegex(RuntimeError, "No command output was exported"): + autostart._credential(self.state) + + + def test_disconnect_pauses_worker_without_stopping_local_services_until_unit_stop(self): + settings = config.load_config(self.state) + settings["headless"] = "auto" + config.save_config(self.state, settings) + states = [] + checks = 0 + def wait(delay): + nonlocal checks + checks += 1 + runtime_stop.assert_not_called() + self.assertIn("disconnected", states) + autostart._STOP = True + with mock.patch.object(runtime, "start", return_value={"running": True, "ready": True}), mock.patch.object(runtime, "status", return_value={"running": True}), mock.patch.object(runtime, "stop") as runtime_stop, mock.patch.object(connection_background, "start", return_value=1) as start, mock.patch.object(connection_background, "status", return_value={"alive": False, "verified": False, "state": "stopped"}), mock.patch.object(connection_background, "disconnect") as disconnect, mock.patch.object(autostart, "_credential", return_value=MARKER), mock.patch.object(autostart, "_worker_status", side_effect=lambda directory, name: states.append(name)), mock.patch.object(autostart.time, "sleep", side_effect=wait): + self.assertEqual(0, autostart.worker(self.state)) + self.assertEqual(1, checks) + self.assertEqual(1, start.call_args.kwargs["wait_timeout"]) + runtime_stop.assert_called_once() + disconnect.assert_called_once() + + def test_failed_tunnel_requests_service_restart_and_cleans_up(self): + settings = config.load_config(self.state) + settings["headless"] = "auto" + config.save_config(self.state, settings) + with mock.patch.object(runtime, "start", return_value={"running": True, "ready": True}), mock.patch.object(runtime, "stop") as runtime_stop, mock.patch.object(connection_background, "start", return_value=1), mock.patch.object(connection_background, "status", return_value={"alive": False, "state": "error"}), mock.patch.object(connection_background, "disconnect") as disconnect, mock.patch.object(autostart, "_credential", return_value=MARKER): + self.assertEqual(1, autostart.worker(self.state)) + runtime_stop.assert_called_once() + disconnect.assert_called_once() + + def test_physical_desktop_wait_requires_no_new_key_prompt(self): + from framebridge_expanded.display_context import DisplayContextError + states = [] + def wait(delay): + autostart._STOP = True + with mock.patch.object(runtime, "start", side_effect=DisplayContextError("PHYSICAL_X11_UNAVAILABLE_NO_VIRTUAL_FALLBACK")), mock.patch.object(runtime, "stop"), mock.patch.object(autostart, "_credential") as credential, mock.patch.object(autostart, "_worker_status", side_effect=lambda directory, name: states.append(name)), mock.patch.object(autostart.time, "sleep", side_effect=wait): + self.assertEqual(0, autostart.worker(self.state)) + self.assertIn("waiting_for_graphical_login", states) + credential.assert_not_called() + self.key_prompt.assert_not_called() + + def test_enable_interrupt_rolls_back_unit_blob_and_exact_profile(self): + before = (self.state / "config.json").read_bytes() + def interrupted(*args, **kwargs): + if args[:2] == ("enable", "--now"): + raise KeyboardInterrupt() + return self.fake_systemctl(*args, **kwargs) + with mock.patch.object(autostart, "_systemctl", side_effect=interrupted): + result = autostart.enable(self.state) + self.assertEqual({"configured": False, "state": "cancelled"}, result) + _, directory, unit = autostart._paths(self.state) + self.assertEqual(before, (self.state / "config.json").read_bytes()) + self.assertFalse(unit.exists()) + self.assertFalse((directory / "credential.cred").exists()) + self.assertFalse((directory / "installation.json").exists()) + + def test_disable_privilege_cancellation_preserves_complete_installation(self): + autostart.enable(self.state) + _, directory, unit = autostart._paths(self.state) + unit_before = unit.read_bytes() + cipher_before = (directory / "credential.cred").read_bytes() + def cancelled(args, **kwargs): + if args == ["sudo", "-v"]: + raise KeyboardInterrupt() + return self.fake_command(args, **kwargs) + self.command.side_effect = cancelled + with self.assertRaises(KeyboardInterrupt): + autostart.disable(self.state) + self.assertEqual(unit_before, unit.read_bytes()) + self.assertEqual(cipher_before, (directory / "credential.cred").read_bytes()) + self.assertTrue((directory / "installation.json").exists()) + + def test_unconfigured_manual_stop_does_not_create_autostart_directory(self): + other = self.home / "other-profile" + config.save_config(other, config.default_config()) + self.assertFalse(autostart.mark_manual_stop(other)) + self.assertFalse((other / "autostart").exists()) + + def test_runtime_crash_requests_service_restart(self): + with mock.patch.object(runtime, "start", return_value={"running": True, "ready": True}), mock.patch.object(runtime, "status", return_value={"running": False}), mock.patch.object(runtime, "stop") as runtime_stop, mock.patch.object(connection_background, "start", return_value=0), mock.patch.object(connection_background, "status", return_value={"alive": True, "verified": True, "state": "connected"}), mock.patch.object(connection_background, "disconnect") as disconnect, mock.patch.object(autostart, "_credential", return_value=MARKER): + self.assertEqual(1, autostart.worker(self.state)) + runtime_stop.assert_called_once() + disconnect.assert_called_once() + + def test_explicit_stop_intent_prevents_automatic_restart(self): + _, directory, _ = autostart._paths(self.state) + autostart._json(directory / "installation.json", {"schema": 1, "node": False}) + def explicitly_stopped(state): + self.assertTrue(autostart.mark_manual_stop(state)) + return {"running": False} + with mock.patch.object(runtime, "start", return_value={"running": True, "ready": True}), mock.patch.object(runtime, "status", side_effect=explicitly_stopped), mock.patch.object(runtime, "stop") as runtime_stop, mock.patch.object(connection_background, "start", return_value=0), mock.patch.object(connection_background, "status", return_value={"alive": True, "verified": True, "state": "connected"}), mock.patch.object(connection_background, "disconnect"), mock.patch.object(autostart, "_credential", return_value=MARKER): + self.assertEqual(0, autostart.worker(self.state)) + runtime_stop.assert_called_once() + self.assertTrue(autostart.manual_stop_requested(self.state)) + + def test_stale_manual_stop_is_cleared_when_a_new_worker_starts(self): + _, directory, _ = autostart._paths(self.state) + autostart._json(directory / "installation.json", {"schema": 1, "node": False}) + self.assertTrue(autostart.mark_manual_stop(self.state)) + self.assertTrue(autostart.manual_stop_requested(self.state)) + with mock.patch.object(runtime, "start", return_value={"running": True, "ready": True}) as start, mock.patch.object(runtime, "status", return_value={"running": False}), mock.patch.object(runtime, "stop"), mock.patch.object(connection_background, "start", return_value=0), mock.patch.object(connection_background, "status", return_value={"alive": True, "verified": True, "state": "connected"}), mock.patch.object(connection_background, "disconnect"), mock.patch.object(autostart, "_credential", return_value=MARKER): + self.assertEqual(1, autostart.worker(self.state)) + start.assert_called_once() + self.assertFalse(autostart.manual_stop_requested(self.state)) + + def test_stop_for_current_boot_cancels_restart_job_but_preserves_unit_and_credential(self): + autostart.enable(self.state) + _, directory, unit = autostart._paths(self.state) + cipher = (directory / "credential.cred").read_bytes() + original_unit = unit.read_bytes() + self.system_calls.clear() + result = autostart.stop_for_current_boot(self.state) + self.assertEqual("stopped_for_current_boot", result["state"]) + self.assertEqual([("stop", unit.name)], self.system_calls) + self.assertEqual(original_unit, unit.read_bytes()) + self.assertEqual(cipher, (directory / "credential.cred").read_bytes()) + self.assertTrue((directory / "installation.json").exists()) + + def test_stop_for_current_boot_refuses_changed_unit(self): + autostart.enable(self.state) + _, _, unit = autostart._paths(self.state) + unit.write_text(unit.read_text() + "# foreign change\n") + self.system_calls.clear() + with self.assertRaisesRegex(RuntimeError, "changed outside"): + autostart.stop_for_current_boot(self.state) + self.assertEqual([], self.system_calls) + self.assertTrue(unit.exists()) + + def test_stop_for_current_boot_unconfigured_does_not_create_directories(self): + other = self.home / "unmanaged" + config.save_config(other, config.default_config()) + self.assertFalse(autostart.stop_for_current_boot(other)["configured"]) + self.assertFalse((other / "autostart").exists()) + self.assertEqual([], self.system_calls) + + def test_cli_stop_cancels_service_restart_before_stopping_runtime(self): + from framebridge_expanded import cli + events = [] + def stop_unit(state): + events.append("unit-stop") + def stop_runtime(state): + self.assertEqual(["unit-stop"], events) + events.append("runtime-stop") + return {"running": False} + with mock.patch.object(autostart, "stop_for_current_boot", side_effect=stop_unit), mock.patch.object(runtime, "stop", side_effect=stop_runtime), mock.patch.object(cli, "_show"): + self.assertEqual(0, cli.main(["--state-dir", str(self.state), "stop"])) + self.assertEqual(["unit-stop", "runtime-stop"], events) diff --git a/source/tests/test_cli.py b/source/tests/test_cli.py new file mode 100644 index 0000000..c66a34b --- /dev/null +++ b/source/tests/test_cli.py @@ -0,0 +1,353 @@ +import contextlib +import io +import os +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +from framebridge_expanded import cli +from framebridge_expanded.config import default_config, load_config, save_config + +ROOT = Path(__file__).resolve().parents[1] + + +class CliTests(unittest.TestCase): + def directory(self): + return tempfile.TemporaryDirectory(prefix="cli-test-", dir=ROOT / "qa") + + def answers(self, role="standalone", confirm="yes"): + def ask(prompt): + if prompt.startswith("Role "): + return role + if prompt.startswith("Computer name"): + return "expanded-test" + if prompt.startswith("ChatGPT email"): + return "tester@example.com" + if prompt.startswith("TLS certificate setup"): + return "existing" + if prompt.startswith("Central broker URL"): + return "https://central.example" + if prompt.startswith("Save this configuration?"): + return confirm + return "" + return ask + + def test_help_has_english_commands_and_never_loads_runtime(self): + output = io.StringIO() + with contextlib.redirect_stdout(output), mock.patch.object(cli, "_module") as modules: + with self.assertRaises(SystemExit) as exited: + cli.main(["--help"]) + self.assertEqual(exited.exception.code, 0) + self.assertIn("setup", output.getvalue()) + self.assertIn("connect", output.getvalue()) + modules.assert_not_called() + + def test_no_argument_menu_uses_real_newlines(self): + output = io.StringIO() + with contextlib.redirect_stdout(output), mock.patch.object(cli.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="0"): + self.assertEqual(cli.main([]), 0) + self.assertIn("FrameBridge\n1. Connect\n2. Status\n3. Doctor", output.getvalue()) + self.assertNotIn("\\n", output.getvalue()) + + def test_noninteractive_no_arguments_is_read_only_help(self): + with mock.patch.object(cli.sys.stdin, "isatty", return_value=False), mock.patch.object(cli, "_module") as modules, contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(cli.main([]), 0) + modules.assert_not_called() + + def test_state_environment_priority_and_alias(self): + with mock.patch.dict(os.environ, {"FRAMEBRIDGE_EXPANDED_STATE": "/tmp/canonical", "FRAMEBRIDGE_STATE_DIR": "/tmp/alias"}): + self.assertEqual(cli.resolve_state(), Path("/tmp/canonical")) + self.assertEqual(cli.resolve_state("/tmp/explicit"), Path("/tmp/explicit")) + with mock.patch.dict(os.environ, {}, clear=True): + os.environ["FRAMEBRIDGE_STATE_DIR"] = "/tmp/alias" + self.assertEqual(cli.resolve_state(), Path("/tmp/alias")) + + def test_setup_roles_save_public_settings_without_start(self): + for role in ("standalone", "central", "node"): + with self.subTest(role=role), self.directory() as td, contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "_module") as modules: + state = Path(td) / "state" + self.assertEqual(cli.setup(state, ask=self.answers(role)), 0) + config = load_config(state) + self.assertEqual(config["role"], role) + self.assertEqual(config["account_label"], "tester@example.com") + self.assertEqual(config["ports"]["mcp"], 20865) + self.assertEqual((state / "config.json").stat().st_mode & 0o777, 0o600) + modules.assert_not_called() + serialized = (state / "config.json").read_text() + self.assertNotIn("api_key", serialized) + self.assertNotIn("runtime_key", serialized) + + def test_setup_new_profile_selects_auto_without_a_desktop_question(self): + prompts = [] + answer = self.answers() + def ask(prompt): + prompts.append(prompt) + return answer(prompt) + with self.directory() as td, contextlib.redirect_stdout(io.StringIO()) as output: + state = Path(td) / "state" + self.assertEqual(cli.setup(state, ask=ask), 0) + self.assertEqual(load_config(state)["headless"], "auto") + self.assertFalse(any("desktop" in prompt.lower() for prompt in prompts)) + self.assertIn("Desktop: automatic detection.", output.getvalue()) + + def test_setup_keeps_existing_explicit_desktop_setting(self): + for mode in ("yes", "no", True, False): + with self.subTest(mode=mode), self.directory() as td, contextlib.redirect_stdout(io.StringIO()) as output: + state = Path(td) / "state" + settings = default_config(); settings["headless"] = mode + save_config(state, settings) + runtime = mock.Mock(); runtime.status.return_value = {"running": False, "processes": []} + with mock.patch.object(cli, "_module", return_value=runtime): + self.assertEqual(cli.setup(state, ask=self.answers()), 0) + self.assertEqual(load_config(state)["headless"], mode) + self.assertIn("setting preserved.", output.getvalue()) + self.assertNotIn("Desktop: automatic detection.", output.getvalue()) + + def autostart_enable_output(self, result): + autostart = mock.Mock(); autostart.enable.return_value = result + with mock.patch.object(cli, "_module", return_value=autostart), contextlib.redirect_stdout(io.StringIO()) as output: + code = cli.main(["autostart", "enable"]) + return code, output.getvalue() + + def test_autostart_enable_distinguishes_configured_from_connected(self): + result = {"configured": True, "enabled": True, "service_state": "activating", + "runtime": {"ready": False, "display_context": {"kind": "text"}}, + "connection": {"state": "checking", "verified": False}} + code, output = self.autostart_enable_output(result) + self.assertEqual(code, 0) + self.assertIn("Automatic startup enabled.", output) + self.assertIn("Tools: starting.", output) + self.assertIn("Desktop: text mode.", output) + self.assertIn("Connection: checking in the background.", output) + self.assertNotIn("Connection: ready.", output) + self.assertNotIn("CUA ready", output) + self.assertNotIn('"configured"', output) + + def test_autostart_enable_reports_verified_graphical_capabilities(self): + result = {"configured": True, "enabled": True, "service_state": "active", + "runtime": {"ready": True, "display_context": {"kind": "physical-x11"}, + "capabilities": {"fbexec": True, "fbterm": True, "cua_tty_module": True, "graphical_cua": True}}, + "connection": {"state": "ready", "verified": True}} + code, output = self.autostart_enable_output(result) + self.assertEqual(code, 0) + self.assertIn("Tools: FBEXEC, FBTERM, CUA-TTY ready.", output) + self.assertIn("Desktop: detected; CUA ready.", output) + self.assertIn("Connection: ready.", output) + + def test_autostart_enable_failed_service_is_not_a_success_exit(self): + code, output = self.autostart_enable_output({"configured": True, "enabled": True, + "service_state": "failed", "runtime": {"status_unavailable": True}, + "connection": {"state": "error", "verified": False}}) + self.assertEqual(code, 1) + self.assertIn("Service: failed", output) + self.assertIn("Tools: status unavailable.", output) + self.assertIn("Connection: needs attention.", output) + self.assertNotIn("CUA ready", output) + + def test_autostart_enable_cancel_has_no_success_summary(self): + code, output = self.autostart_enable_output({"state": "cancelled"}) + self.assertEqual(code, 130) + self.assertEqual(output, "Automatic startup was not enabled.\n") + + def test_setup_decline_does_not_create_profile(self): + with self.directory() as td, contextlib.redirect_stdout(io.StringIO()): + state = Path(td) / "state" + self.assertEqual(cli.setup(state, ask=self.answers(confirm="no")), 0) + self.assertFalse((state / "config.json").exists()) + + def test_setup_rejects_running_profile_before_prompts(self): + with self.directory() as td: + state = Path(td) + save_config(state, default_config(name="existing")) + before = (state / "config.json").read_bytes() + runtime = mock.Mock() + runtime.status.return_value = {"running": True, "processes": []} + ask = mock.Mock() + with mock.patch.object(cli, "_module", return_value=runtime): + with self.assertRaisesRegex(RuntimeError, "Stop this profile"): + cli.setup(state, ask=ask) + ask.assert_not_called() + self.assertEqual((state / "config.json").read_bytes(), before) + + def connection_runtime(self, base): + runtime = mock.Mock() + runtime.PREFIX = Path(base) / "payload" + binary = runtime.PREFIX / "original/geranas/FrameBridge-MCP-Private/bin/tunnel-client" + binary.parent.mkdir(parents=True) + binary.write_bytes(b"synthetic test binary") + runtime.status.return_value = {"running": False, "ready": False} + return runtime + + def test_connection_rejects_another_profiles_listening_port(self): + with self.directory() as td: + state = Path(td) / "profile" + save_config(state, default_config(name="stopped-profile")) + before = (state / "config.json").read_bytes() + runtime = self.connection_runtime(td) + automatic = mock.Mock() + listener = mock.MagicMock() + listener.__enter__.return_value.bind.side_effect = OSError("address in use") + def connect(*args, **kwargs): + return kwargs["configure_connection"](state, {"account_label": "test@example.com", "tunnel_id": "tunnel_new"}, "SYNTHETIC-MCP-ACCESS-KEY") + with mock.patch.object(cli, "_module", side_effect=lambda name: runtime if name == "runtime" else automatic), mock.patch.object(cli.socket, "socket", return_value=listener), mock.patch.object(cli.tunnel, "connect_chatgpt", side_effect=connect): + with self.assertRaisesRegex(RuntimeError, "MCP port 20865 is already in use"): + cli.connect_chatgpt(state) + automatic.configure_connection.assert_not_called() + self.assertEqual(before, (state / "config.json").read_bytes()) + + def test_new_connection_needs_no_prior_profile_or_runtime_start(self): + with self.directory() as td: + state = Path(td) / "new-profile" + runtime = self.connection_runtime(td) + with mock.patch.object(cli, "_module", return_value=runtime), mock.patch.object(cli.tunnel, "connect_chatgpt", return_value=130) as connect: + self.assertEqual(130, cli.connect_chatgpt(state)) + self.assertFalse(state.exists()) + runtime.start.assert_not_called() + runtime.status.assert_not_called() + public = connect.call_args.args[3] + self.assertEqual("standalone", public["role"]) + self.assertEqual("auto", public["headless"]) + self.assertTrue(callable(connect.call_args.kwargs["configure_connection"])) + + def test_cancel_new_profile_at_hidden_key_leaves_no_profile_or_services(self): + with self.directory() as td: + state = Path(td) / "new-profile" + runtime = self.connection_runtime(td) + automatic = mock.Mock() + with mock.patch.object(cli, "_module", side_effect=lambda name: runtime if name == "runtime" else automatic), mock.patch.object(cli.tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(cli.tunnel, "_hidden", side_effect=["tunnel_valid", KeyboardInterrupt]), contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(130, cli.connect_chatgpt(state)) + self.assertFalse(state.exists()) + automatic.configure_connection.assert_not_called() + runtime.start.assert_not_called() + + def test_existing_managed_connection_dispatches_resume_without_wizard(self): + with self.directory() as td: + state = Path(td) / "profile" + save_config(state, default_config()) + (state / "autostart").mkdir() + (state / "autostart/installation.json").write_text("{}") + runtime = self.connection_runtime(td) + automatic = mock.Mock() + with mock.patch.object(cli, "_module", side_effect=lambda name: runtime if name == "runtime" else automatic), mock.patch.object(cli.tunnel, "resume_managed_connection", return_value=0) as resume, mock.patch.object(cli.tunnel, "connect_chatgpt") as wizard: + self.assertEqual(0, cli.connect_chatgpt(state)) + resume.assert_called_once_with(state, automatic.resume_connection) + wizard.assert_not_called() + + def test_replace_bypasses_resume_and_uses_normal_three_field_wizard(self): + with self.directory() as td: + state = Path(td) / "profile" + save_config(state, default_config()) + (state / "autostart").mkdir() + (state / "autostart/installation.json").write_text("{}") + runtime = self.connection_runtime(td) + with mock.patch.object(cli, "_module", return_value=runtime), mock.patch.object(cli.tunnel, "resume_managed_connection") as resume, mock.patch.object(cli.tunnel, "connect_chatgpt", return_value=0) as wizard: + self.assertEqual(0, cli.connect_chatgpt(state, replace=True)) + resume.assert_not_called() + self.assertTrue(callable(wizard.call_args.kwargs["configure_connection"])) + + def test_replace_dispatch_and_foreground_are_mutually_exclusive(self): + with contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "connect_chatgpt", return_value=0) as connect: + self.assertEqual(0, cli.main(["--state-dir", "/tmp/profile", "connect", "chatgpt", "--replace"])) + connect.assert_called_once_with(Path("/tmp/profile"), foreground=False, replace=True) + with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as error: + cli.main(["connect", "chatgpt", "--replace", "--foreground"]) + self.assertEqual(2, error.exception.code) + + def test_node_profile_cannot_tunnel_a_central_profiles_port(self): + with self.directory() as td: + state = Path(td) + save_config(state, default_config("node", "node-profile")) + with mock.patch.object(cli, "_module") as modules, mock.patch.object(cli.tunnel, "connect_chatgpt") as connect: + with self.assertRaisesRegex(RuntimeError, "central profile"): + cli.connect_chatgpt(state) + modules.assert_not_called() + connect.assert_not_called() + + def test_lifecycle_dispatch_receives_explicit_profile(self): + runtime = mock.Mock() + runtime.status.return_value = {"ok": True, "running": False} + with contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "_module", return_value=runtime): + self.assertEqual(cli.main(["--state-dir", str(ROOT / "qa/example"), "status"]), 0) + runtime.status.assert_called_once_with(ROOT / "qa/example") + + def test_tty_dispatch_uses_authorized_typed_payload(self): + tty = mock.Mock() + tty.request.return_value = {"ok": True} + with contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "_module", return_value=tty): + self.assertEqual(cli.main(["--state-dir", str(ROOT / "qa/example"), "tty", "key", "session-1", "Enter"]), 0) + tty.request.assert_called_once_with(ROOT / "qa/example", {"authorized": True, "op": "key.send", "session_id": "session-1", "key": "Enter"}) + + def test_node_enrollment_does_not_start_agent(self): + with self.directory() as td: + state = Path(td) + config = default_config("node", "node-test") + config["broker_url"] = "https://central.example" + save_config(state, config) + node = mock.Mock() + node.enroll.return_value = {"ok": True, "state": "pending"} + with contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "_module", return_value=node): + self.assertEqual(cli.main(["--state-dir", str(state), "nodes", "enroll"]), 0) + node.enroll.assert_called_once_with("https://central.example", "node-test", state_dir=state) + node.run.assert_not_called() + + def test_nodes_list_scopes_and_restores_environment(self): + with self.directory() as td: + state = Path(td) + save_config(state, default_config(name="list-test")) + broker = mock.Mock() + fleet = mock.Mock() + def discover(): + self.assertEqual(os.environ["FRAMEBRIDGE_EXPANDED_STATE"], str(state)) + return {"ok": True, "nodes": []} + fleet.discover_nodes.side_effect = discover + before = os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") + with contextlib.redirect_stdout(io.StringIO()), mock.patch.object(cli, "_module", side_effect=lambda name: broker if name == "broker" else fleet): + self.assertEqual(cli.main(["--state-dir", str(state), "nodes", "list"]), 0) + broker.configure.assert_called_once_with(None, state_dir=state) + self.assertEqual(os.environ.get("FRAMEBRIDGE_EXPANDED_STATE"), before) + + def test_nodes_approval_requires_expected_uuid(self): + parser = cli.build_parser() + with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): + parser.parse_args(["nodes", "approve", "request-1"]) + + + def test_central_wizard_creates_lan_tls_only_after_review(self): + with self.directory() as td, contextlib.redirect_stdout(io.StringIO()) as output: + state = Path(td) / "state" + original = self.answers("central") + def ask(prompt): + if prompt.startswith("Broker listen host"): + return "0.0.0.0" + if prompt.startswith("TLS certificate setup"): + return "generate" + if prompt.startswith("Certificate DNS name"): + return "central.example.test" + if prompt.startswith("Advertised broker IP address"): + return "192.0.2.20" + return original(prompt) + self.assertEqual(cli.setup(state, ask=ask), 0) + config = load_config(state) + self.assertEqual(config["broker_url"], "https://192.0.2.20:20878") + self.assertEqual(config["broker_control_url"], "https://127.0.0.1:20878") + self.assertEqual(config["broker_listen_host"], "0.0.0.0") + self.assertTrue(Path(config["tls_ca"]).is_file()) + self.assertFalse((state / "manager.json").exists()) + self.assertIn("No service or ChatGPT connection was started.", output.getvalue()) + self.assertNotIn("PRIVATE KEY", output.getvalue()) + + def test_certificates_create_dispatch_uses_explicit_profile(self): + with self.directory() as td, contextlib.redirect_stdout(io.StringIO()) as output: + state = Path(td) + save_config(state, default_config("central", "cert-cli")) + self.assertEqual(cli.main(["--state-dir", str(state), "certificates", "create", + "--host", "cert.example.test", "--address", "192.0.2.30", + "--listen-host", "0.0.0.0"]), 0) + self.assertEqual(load_config(state)["broker_url"], "https://192.0.2.30:20878") + self.assertIn("ca_fingerprint_sha256", output.getvalue()) + self.assertNotIn("PRIVATE KEY", output.getvalue()) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_cli_community_contract.py b/source/tests/test_cli_community_contract.py new file mode 100644 index 0000000..7d5aa7f --- /dev/null +++ b/source/tests/test_cli_community_contract.py @@ -0,0 +1,232 @@ +"""Command contract tests: mocked interfaces only, no external connections.""" +import contextlib +import io +import json +from pathlib import Path +import tempfile +from types import SimpleNamespace +import unittest +from unittest import mock +from framebridge_expanded import cli +from framebridge_expanded.config import default_config, save_config + + +class CommunityCliContractTests(unittest.TestCase): + def call(self, args, modules=None): + stdout, stderr = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): + if modules is None: + code = cli.main(args) + else: + with mock.patch.object(cli, "_module", side_effect=lambda name: modules[name]): + code = cli.main(args) + return code, stdout.getvalue(), stderr.getvalue() + + def aggregate(self): + return {"ok": True, "runtime":{"role":"standalone", "ready":True, "running":True}, + "connection":{"ok":True,"verified":False,"state":"closed"}, + "boot":{"configured":True,"enabled":True,"linger":True,"encrypted_credential_present":True, + "service_active":True,"service_state":"active"}, + "desired_connection":"disconnected"} + + def test_short_and_legacy_connect_share_flags(self): + for args in (["connect", "--replace"], ["connect", "chatgpt", "--replace"]): + value=cli.build_parser().parse_args(args) + self.assertTrue(value.replace) + self.assertFalse(value.foreground) + + def test_common_json_and_profile_options_survive_nested_parsers(self): + for args in (["--json","--state-dir","/qa/custom","service","boot","disable"], + ["service","boot","disable","--json","--state-dir","/qa/custom"]): + value=cli.build_parser().parse_args(args) + self.assertTrue(value.json) + self.assertEqual(value.state_dir,"/qa/custom") + self.assertEqual(value.boot_action,"disable") + + def test_plain_status_separates_live_connection_and_boot(self): + life=SimpleNamespace(status=mock.Mock(return_value=self.aggregate())) + code,text,_=self.call(["status"],{"lifecycle":life}) + self.assertEqual(code,0) + self.assertIn("Local tools: ready",text) + self.assertIn("ChatGPT connection: closed",text) + self.assertIn("Automatic startup: enabled",text) + self.assertNotIn("connected (verified)",text) + + def test_unavailable_status_is_not_presented_as_stopped_or_disabled(self): + value={"ok":True,"runtime":{"status_unavailable":True},"connection":{"state":"unavailable"},"boot":{},"desired_connection":"unavailable"} + life=SimpleNamespace(status=mock.Mock(return_value=value)) + code,text,_=self.call(["status"],{"lifecycle":life}) + self.assertEqual(code,0) + self.assertIn("Local tools: unavailable",text) + self.assertIn("Automatic startup: unavailable",text) + self.assertNotIn("Local tools: stopped",text) + + def test_json_status_is_one_versioned_object(self): + life=SimpleNamespace(status=mock.Mock(return_value=self.aggregate())) + code,text,_=self.call(["status","--json"],{"lifecycle":life}) + value=json.loads(text) + self.assertEqual(code,0) + self.assertEqual(value["schema_version"],1) + self.assertEqual(value["version"],"0.3.1") + self.assertEqual(value["result"]["desired_connection"],"disconnected") + self.assertFalse(value["result"]["connection"]["verified"]) + + def test_scoped_status_uses_same_model_and_includes_intent(self): + life=SimpleNamespace(status=mock.Mock(return_value=self.aggregate())) + code,text,_=self.call(["--json","status","connection"],{"lifecycle":life}) + data=json.loads(text)["result"] + self.assertEqual(code,0) + self.assertEqual(data["state"],"closed") + self.assertEqual(data["desired_connection"],"disconnected") + self.assertNotIn("runtime",data) + + def test_doctor_is_distinct_and_does_not_use_status(self): + runtime=SimpleNamespace(doctor=mock.Mock(return_value={"ok":False,"compatible_debian_13_amd64":True, + "display":"text tools","missing_commands":["tmux"],"issues":[]})) + code,text,_=self.call(["doctor"],{"runtime":runtime}) + self.assertEqual(code,1) + self.assertIn("Missing command: tmux",text) + self.assertIn("use fb status",text) + + def test_json_new_connect_never_prompts_or_starts_services(self): + with tempfile.TemporaryDirectory() as td, mock.patch("builtins.input") as ask, \ + mock.patch.object(cli,"connect_chatgpt") as connect, mock.patch.object(cli,"_module") as modules: + code,text,_=self.call(["--state-dir",td,"connect","--json"]) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["error"],"INTERACTION_REQUIRED") + ask.assert_not_called();connect.assert_not_called();modules.assert_not_called() + + def test_structured_foreground_next_action_preserves_temporary_mode(self): + with tempfile.TemporaryDirectory() as td,mock.patch.object(cli,"connect_chatgpt") as connect: + code,text,_=self.call(["--state-dir",td,"connect","--foreground","--json"]) + self.assertEqual(code,2) + self.assertTrue(json.loads(text)["result"]["next_action"].endswith(" connect --foreground")) + connect.assert_not_called() + + def test_menu_cancel_returns_cleanly_without_dispatch(self): + with mock.patch.object(cli.sys.stdin,"isatty",return_value=True),mock.patch("builtins.input",side_effect=KeyboardInterrupt): + code,text,errors=self.call([]) + self.assertEqual(code,130) + self.assertIn("Cancelled.",errors) + + def test_json_setup_reports_interaction_without_reading_prompts(self): + with mock.patch("builtins.input") as ask,mock.patch.object(cli,"setup") as setup: + code,text,_=self.call(["setup","--json"]) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["error"],"INTERACTION_REQUIRED") + ask.assert_not_called();setup.assert_not_called() + + def test_saved_json_connect_keeps_human_progress_off_stdout(self): + life=SimpleNamespace(status=mock.Mock(return_value=self.aggregate())) + with tempfile.TemporaryDirectory() as td: + state=Path(td);(state/"autostart").mkdir();(state/"autostart/installation.json").write_text("{}") + def connect(*args,**kwargs): + print("Checking the saved connection.") + return 0 + with mock.patch.object(cli,"connect_chatgpt",side_effect=connect): + code,text,errors=self.call(["--state-dir",td,"connect","--json"],{"lifecycle":life}) + self.assertEqual(code,0) + self.assertEqual(json.loads(text)["result"]["runtime"]["ready"],True) + self.assertIn("Checking the saved connection.",errors) + self.assertNotIn("Checking",text) + + def test_canonical_disconnect_and_legacy_have_explicit_different_routes(self): + canonical=SimpleNamespace(disconnect=mock.Mock(return_value={"ok":True})) + legacy=SimpleNamespace(disconnect=mock.Mock(return_value={"ok":True})) + modules={"lifecycle":canonical,"connection_background":legacy} + self.assertEqual(self.call(["disconnect"],modules)[0],0) + canonical.disconnect.assert_called_once();legacy.disconnect.assert_not_called() + self.assertEqual(self.call(["disconnect","chatgpt"],modules)[0],0) + legacy.disconnect.assert_called_once() + + def test_noninteractive_service_start_does_not_invent_authentication_gate(self): + life=SimpleNamespace(service_start=mock.Mock(return_value={"ok":True,"state":"started","scope":"local_tools"})) + code,text,_=self.call(["service","start","--json"],{"lifecycle":life}) + self.assertEqual(code,0) + self.assertEqual(json.loads(text)["result"]["scope"],"local_tools") + life.service_start.assert_called_once() + + def test_first_node_service_start_reports_interaction_before_provisioning(self): + life=SimpleNamespace(status=mock.Mock(return_value={"boot":{"configured":False}}),service_start=mock.Mock()) + with tempfile.TemporaryDirectory() as td: + state=Path(td);config=default_config();config["role"]="node";save_config(state,config) + code,text,_=self.call(["--state-dir",td,"service","start","--json"],{"lifecycle":life}) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["error"],"INTERACTION_REQUIRED") + life.service_start.assert_not_called() + + def test_configured_node_service_start_remains_noninteractive(self): + life=SimpleNamespace(status=mock.Mock(return_value={"boot":{"configured":True}}),service_start=mock.Mock(return_value={"ok":True,"state":"start_requested"})) + with tempfile.TemporaryDirectory() as td: + state=Path(td);config=default_config();config["role"]="node";save_config(state,config) + code,text,_=self.call(["--state-dir",td,"service","start","--json"],{"lifecycle":life}) + self.assertEqual(code,0) + self.assertEqual(json.loads(text)["result"]["state"],"start_requested") + life.service_start.assert_called_once() + + def test_boot_disable_and_legacy_autostart_disable_are_not_retargeted(self): + life=SimpleNamespace(boot_disable=mock.Mock(return_value={"ok":True,"state":"boot-disabled"})) + auto=SimpleNamespace(disable=mock.Mock(return_value={"ok":True,"state":"disabled"}), + status=mock.Mock(return_value={"service_state":"inactive"})) + modules={"lifecycle":life,"autostart":auto} + self.call(["service","boot","disable"],modules) + life.boot_disable.assert_called_once();auto.disable.assert_not_called() + self.call(["autostart","disable"],modules) + auto.disable.assert_called_once() + + def test_forget_requires_explicit_confirmation_without_calling_destructive_api(self): + life=SimpleNamespace(forget_credentials=mock.Mock()) + code,text,_=self.call(["service","credentials","forget","--json"],{"lifecycle":life}) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["error"],"CONFIRMATION_REQUIRED") + life.forget_credentials.assert_not_called() + + def test_forget_confirmation_instruction_preserves_custom_profile(self): + life=SimpleNamespace(forget_credentials=mock.Mock()) + code,text,_=self.call(["--state-dir","/qa/profile with spaces","service","credentials","forget","--json"],{"lifecycle":life}) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["next_action"],"fb --state-dir '/qa/profile with spaces' service credentials forget --confirm") + life.forget_credentials.assert_not_called() + + def test_explicit_confirm_forget_calls_one_narrow_api(self): + life=SimpleNamespace(forget_credentials=mock.Mock(return_value={"ok":True,"state":"forgotten"})) + self.assertEqual(self.call(["service","credentials","forget","--confirm","--json"],{"lifecycle":life})[0],0) + life.forget_credentials.assert_called_once() + + def test_console_login_subcommand_calls_only_login_policy(self): + startup=SimpleNamespace(status=mock.Mock(return_value={"ok":True,"enabled":False})) + code,text,_=self.call(["console","login","status","--json"],{"console_startup":startup}) + self.assertEqual(code,0) + self.assertFalse(json.loads(text)["result"]["enabled"]) + startup.status.assert_called_once() + + def test_terminal_service_group_routes_same_explicit_target(self): + args=cli.build_parser().parse_args(["tty","service","status","--node","N2"]) + with mock.patch.object(cli,"_target_tty",return_value={"ok":True}) as target,contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(cli.tty_command(args,Path("/qa/profile")),0) + target.assert_called_once_with(Path("/qa/profile"),"N2",service="status") + + def test_noninteractive_typed_text_without_text_never_prompts(self): + with mock.patch("builtins.input") as ask,mock.patch.object(cli,"tty_command") as tty: + code,text,_=self.call(["tty","type","a"*32,"--json"]) + self.assertEqual(code,2) + self.assertEqual(json.loads(text)["result"]["error"],"INTERACTION_REQUIRED") + ask.assert_not_called();tty.assert_not_called() + + def test_help_uses_same_versioned_guide_without_runtime(self): + guide=SimpleNamespace(get_guide=mock.Mock(return_value={"ok":True,"topic":"commands","text":"Command guide"})) + code,text,_=self.call(["help","commands","--json"],{"guide":guide}) + self.assertEqual(code,0) + self.assertEqual(json.loads(text)["result"]["topic"],"commands") + guide.get_guide.assert_called_once_with("commands") + + def test_failed_pause_does_not_claim_success(self): + life=SimpleNamespace(disconnect=mock.Mock(return_value={"ok":False,"error":"TEST_FAILURE"})) + code,text,errors=self.call(["disconnect"],{"lifecycle":life}) + self.assertEqual(code,1) + self.assertNotIn("paused until",text) + self.assertIn("TEST_FAILURE",errors) + + +if __name__=="__main__": + unittest.main() diff --git a/source/tests/test_cli_extensions.py b/source/tests/test_cli_extensions.py new file mode 100644 index 0000000..08e913b --- /dev/null +++ b/source/tests/test_cli_extensions.py @@ -0,0 +1,98 @@ +"""Routing regression tests for package CLI additions; no external services.""" +import contextlib +import io +import os +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest import mock + +from framebridge_expanded import cli + +class CliExtensionTests(unittest.TestCase): + SID = "c" * 32 + + def parsed(self, *parts): + return cli.build_parser().parse_args(list(parts)) + + def test_sudo_parser_keeps_node_and_console_variables(self): + for mode in ("on", "status", "off"): + args = self.parsed("tty", "sudo", mode, self.SID, "--node", "NodeCase") + self.assertEqual((args.tty_command, args.sudo_mode, args.session_id, args.node), + ("sudo", mode, self.SID, "NodeCase")) + + def test_sudo_command_routes_exact_session_and_target(self): + args = self.parsed("tty", "sudo", "on", self.SID, "--node", "NodeCase") + with mock.patch.object(cli, "_target_tty", return_value={"ok": True}) as target, \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(cli.tty_command(args, Path("/qa/profile")), 0) + target.assert_called_once_with(Path("/qa/profile"), "NodeCase", + payload={"authorized": True, "op": "sudo", "session_id": self.SID, "mode": "on"}) + + def test_remote_request_uses_profile_and_existing_fleet_target(self): + seen = [] + def execute_node(node, **kwargs): + seen.append((node, os.environ.get("FRAMEBRIDGE_EXPANDED_STATE"), kwargs)) + return {"ok": True} + def request(execute, target, payload): + return execute(target=target, script="printf bounded", authorized=True, + timeout_s=30, job_id="qa", sha256="d" * 64, include_stdout=True) + modules = { + "fleet": SimpleNamespace(execute_node=execute_node), + "tty_mcp": SimpleNamespace(_remote_request=request), + } + with mock.patch.dict(os.environ, {"FRAMEBRIDGE_EXPANDED_STATE": "/previous/profile"}), \ + mock.patch.object(cli, "_module", side_effect=lambda name: modules[name]): + value = cli._target_tty(Path("/qa/explicit"), "NodeCase", payload={"op": "observe"}) + self.assertEqual(os.environ["FRAMEBRIDGE_EXPANDED_STATE"], "/previous/profile") + self.assertTrue(value["ok"]) + self.assertEqual(seen[0][:2], ("NodeCase", "/qa/explicit")) + self.assertNotIn("target", seen[0][2]) + + def test_remote_transport_failure_restores_absent_profile_without_local_fallback(self): + modules = { + "fleet": SimpleNamespace(execute_node=mock.Mock()), + "tty_mcp": SimpleNamespace(_remote_request=mock.Mock(side_effect=RuntimeError("transport failed"))), + } + with mock.patch.dict(os.environ, {}, clear=True), \ + mock.patch.object(cli, "_module", side_effect=lambda name: modules[name]): + with self.assertRaisesRegex(RuntimeError, "transport failed"): + cli._target_tty(Path("/qa/explicit"), "NodeCase", payload={"op": "status"}) + self.assertNotIn("FRAMEBRIDGE_EXPANDED_STATE", os.environ) + modules["fleet"].execute_node.assert_not_called() + + def test_remote_service_action_uses_wire_cli_instead_of_local_daemon(self): + seen = [] + def wire(execute, target, arguments): + seen.append((target, arguments, os.environ.get("FRAMEBRIDGE_EXPANDED_STATE"))) + return {"ok": True} + modules = { + "fleet": SimpleNamespace(execute_node=mock.Mock()), + "tty_mcp": SimpleNamespace(_exec_json=wire), + } + with mock.patch.object(cli, "_module", side_effect=lambda name: modules[name]): + cli._target_tty(Path("/qa/profile"), "NodeCase", service="status") + self.assertEqual(seen, [("NodeCase", ["status"], "/qa/profile")]) + + def test_local_request_keeps_explicit_profile(self): + request = mock.Mock(return_value={"ok": True}) + with mock.patch.object(cli, "_module", return_value=SimpleNamespace(request=request)): + cli._target_tty(Path("/qa/profile"), "LOCAL", payload={"op": "status"}) + request.assert_called_once_with(Path("/qa/profile"), {"op": "status"}) + + def test_failed_target_result_is_not_reported_as_success(self): + args = self.parsed("tty", "close", self.SID, "--node", "NodeCase") + with mock.patch.object(cli, "_target_tty", return_value={"ok": False, "error": "REMOTE_FAILED"}), \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(cli.tty_command(args, Path("/qa/profile")), 1) + + def test_text_transcript_observe_retains_node_argument(self): + args = self.parsed("tty", "observe", self.SID, "--node", "NodeCase") + with mock.patch.object(cli, "_target_tty", return_value={"ok": True}) as target, \ + contextlib.redirect_stdout(io.StringIO()): + cli.tty_command(args, Path("/qa/profile")) + self.assertEqual(target.call_args.kwargs["payload"]["op"], "observe") + self.assertEqual(target.call_args.args[1], "NodeCase") + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_connection_background.py b/source/tests/test_connection_background.py new file mode 100644 index 0000000..95b42f0 --- /dev/null +++ b/source/tests/test_connection_background.py @@ -0,0 +1,204 @@ +import io +import json +import os +from pathlib import Path +import pty +import signal +import subprocess +import sys +import tempfile +import time +import unittest +from unittest import mock +from framebridge_expanded import connection_background as background +from framebridge_expanded import tunnel + +ID = "tunnel_background_fixture" +MCP = "http://127.0.0.1:20865/mcp" +FAKE = "SYNTHETIC_BACKGROUND_ONLY_0123456789" +CLIENT = r''' +import datetime,http.server,json,os,sys,time +from pathlib import Path +started=datetime.datetime.now(datetime.timezone.utc).isoformat() +identifier=sys.argv[sys.argv.index('--control-plane.tunnel-id')+1] +mcp=sys.argv[sys.argv.index('--mcp.server-url')+1].removeprefix('url=') +class Handler(http.server.BaseHTTPRequestHandler): + def log_message(self,*args): pass + def do_GET(self): + if self.path=='/readyz': body=b'ready' + elif self.path=='/healthz': body=b'live' + elif self.path=='/api/status': body=json.dumps({'control_plane_tunnel_id':identifier,'mcp_server_url':mcp,'started_at':started,'tunnel_metadata':{'ID':identifier}}).encode() + elif self.path=='/metrics': body=('commands_poll_last_successful_timestamp_seconds '+str(time.time())+'\n').encode() + else: self.send_response(404);self.end_headers();return + self.send_response(200);self.end_headers();self.wfile.write(body) +server=http.server.HTTPServer(('127.0.0.1',0),Handler) +Path(sys.argv[sys.argv.index('--health.url-file')+1]).write_text('http://127.0.0.1:'+str(server.server_port)) +print('TECHNICAL_FIXTURE '+os.environ['FRAMEBRIDGE_EXPANDED_RUNTIME_KEY'],flush=True) +server.serve_forever() +''' + + +def command(code=CLIENT): + return [sys.executable, "-u", "-c", code, "--control-plane.tunnel-id", ID, + "--mcp.server-url", "url=" + MCP] + + +class BackgroundTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="fb-background-test-") + self.state = Path(self.temp.name) + + def tearDown(self): + background.disconnect(self.state) + self.temp.cleanup() + + def start(self, wait=10): + output = io.StringIO() + rc = background.start(command(), FAKE, self.state, output=output, wait_timeout=wait) + self.assertEqual(rc, 0, output.getvalue()) + return output.getvalue() + + def test_real_detached_worker_ready_private_logs_and_no_credential_on_disk(self): + output = self.start() + current = background.status(self.state) + self.assertTrue(current["alive"]) + self.assertTrue(current["verified"]) + self.assertIn("terminal can be closed", output) + self.assertNotIn(FAKE, output) + self.assertNotIn("TECHNICAL_FIXTURE", output) + worker = Path('/proc') / str(current['pid']) + self.assertNotIn(FAKE.encode(), (worker / 'cmdline').read_bytes()) + self.assertNotIn(FAKE.encode(), (worker / 'environ').read_bytes()) + log = self.state / 'logs/chatgpt/connection.log' + deadline = time.monotonic()+3 + while 'TECHNICAL_FIXTURE' not in log.read_text() and time.monotonic()= fail_at: + raise dc.DisplayContextError("CUA_FOREGROUND_SESSION_CHANGED") + return {} + return check + + def test_queue_then_context_change_blocks_first_primitive(self): + calls = [] + cua = self.fake(calls, lambda engine: engine._run(["xdotool", "key", "a"])) + state = nh.install_cua_guard(cua, env={}, checker=self.checker(2)) + with self.assertRaises(CuaError): + cua.handle_cua_message({}) + self.assertEqual(calls, []) + result = nh.decorate_response({"executed": False}, state, {}) + self.assertFalse(result["input_may_have_been_sent"]) + self.assertFalse(result["performed"]) + self.assertFalse(result["do_not_replay"]) + + def test_context_change_between_query_and_input_blocks_input(self): + calls = [] + def action(cua): + cua._run(["xdotool", "getactivewindow"]) + cua._run(["xdotool", "click", "1"]) + cua = self.fake(calls, action) + state = nh.install_cua_guard(cua, env={}, checker=self.checker(3)) + with self.assertRaises(CuaError): + cua.handle_cua_message({}) + self.assertEqual(calls, [["xdotool", "getactivewindow"]]) + self.assertFalse(state["input_started"]) + + def test_postflight_failure_after_input_retains_uncertain_outcome(self): + calls = [] + cua = self.fake(calls, lambda engine: engine._run(["xdotool", "type", "x"])) + state = nh.install_cua_guard(cua, env={}, checker=self.checker(3)) + with self.assertRaises(CuaError): + cua.handle_cua_message({}) + result = nh.decorate_response({"executed": True, "performed": True}, state, {}) + self.assertFalse(result["executed"]) + self.assertTrue(result["input_may_have_been_sent"]) + self.assertTrue(result["do_not_replay"]) + self.assertNotEqual(result.get("performed"), False) + self.assertTrue(json.loads(result["output"])["result_discarded"]) + + def test_observe_late_failure_does_not_claim_input(self): + calls = [] + cua = self.fake(calls, lambda engine: engine._run(["import", "-window", "root", "fixture.png"])) + state = nh.install_cua_guard(cua, env={}, checker=self.checker(3)) + with self.assertRaises(CuaError): + cua.handle_cua_message({}) + result = nh.decorate_response({"executed": False}, state, {}) + self.assertFalse(result["input_may_have_been_sent"]) + self.assertFalse(result["performed"]) + + def test_successful_input_is_reported_performed(self): + calls = [] + cua = self.fake(calls, lambda engine: engine._run(["/usr/bin/xdotool", "key", "a"])) + state = nh.install_cua_guard(cua, env={}, checker=lambda env: {}) + cua.handle_cua_message({}) + result = nh.decorate_response({"executed": True}, state, {}) + self.assertTrue(result["performed"]) + self.assertTrue(result["input_may_have_been_sent"]) + self.assertFalse(result["do_not_replay"]) + + def test_captured_pointer_helper_rechecks_after_its_internal_query(self): + calls = [] + cua = self.fake(calls) + cua._run_v3_base = cua._run + def helper(argv, timeout=15): + cua._run_v3_base(["xdotool", "getmouselocation", "--shell"]) + return cua._run_v3_base(argv, timeout=timeout) + cua._run = helper + cua.handle_cua_message = lambda msg: cua._run(["xdotool", "mousemove", "1", "1"]) + state = nh.install_cua_guard(cua, env={}, checker=self.checker(4)) + with self.assertRaises(CuaError): + cua.handle_cua_message({}) + self.assertEqual(calls, [["xdotool", "getmouselocation", "--shell"]]) + self.assertFalse(state["input_started"]) + + def test_metadata_does_not_leak_to_next_non_cua_response(self): + state = {"active": True, "input_started": True, "failed_code": ""} + first = nh.decorate_response({"executed": True}, state, {}) + self.assertTrue(first["input_may_have_been_sent"]) + second = {"executed": True, "channel": "CONTROL-DIRECT"} + self.assertEqual(nh.decorate_response(second, state, {}), second) + self.assertNotIn("input_may_have_been_sent", second) + +class NativeFramingTests(unittest.TestCase): + def test_captured_host_protocol_rejects_unbound_display_without_input(self): + root = Path(__file__).resolve().parents[1] + with tempfile.TemporaryDirectory(prefix="fb-native-guard-") as home: + env = {"HOME": home, "PATH": os.environ.get("PATH", "/usr/bin:/bin"), + "PYTHONDONTWRITEBYTECODE": "1", "LC_ALL": "C.UTF-8", + "FRAMEBRIDGE_EXPANDED_PREFIX": str(root), dc.BINDING: "{}"} + message = json.dumps({"op": "cua", "action": "OBSERVE", "authorized": "YES", + "target": "ASUS", "id": "r2-native-test"}).encode() + proc = subprocess.run([sys.executable, str(root / "packaging/framebridge-native-expanded")], + input=struct.pack("I", data[offset:offset + 4])[0] + kind = data[offset + 4:offset + 8] + body = data[offset + 8:offset + 8 + length] + crc = struct.unpack(">I", data[offset + 8 + length:offset + 12 + length])[0] + assert crc == zlib.crc32(kind + body) & 0xffffffff + if kind == b"IHDR": + w, h, depth, color, compression, filtering, interlace = struct.unpack(">IIBBBBB", body) + assert (compression, filtering, interlace) == (0, 0, 0) + assert (color == 2 and depth == 8) or (color == 3 and depth in (1, 2, 4, 8)) + size = (w, h) + elif kind == b"PLTE": + assert len(body) > 0 and len(body) % 3 == 0 + palette = [body[i:i + 3] for i in range(0, len(body), 3)] + elif kind == b"IDAT": + rows += body + elif kind == b"IEND": + assert body == b"" + seen_end = True + offset += length + 12 + assert offset == len(data) and size and seen_end + scan = zlib.decompress(rows) + width, height = size + row_bytes = width * 3 if color == 2 else (width * depth + 7) // 8 + assert len(scan) == height * (1 + row_bytes) + pixels = bytearray() + for y in range(height): + start = y * (1 + row_bytes) + assert scan[start] == 0 + row = scan[start + 1:start + 1 + row_bytes] + if color == 2: + pixels.extend(row) + else: + assert palette is not None and len(palette) <= 1 << depth + for x in range(width): + shift = 8 - depth * (1 + x % (8 // depth)) + index = (row[x // (8 // depth)] >> shift) & ((1 << depth) - 1) + assert index < len(palette) + pixels.extend(palette[index]) + return size, bytes(pixels) + + +class CaptureTests(unittest.TestCase): + def assert_code(self, code, function, *args, **kwargs): + with self.assertRaises(screen.CaptureError) as caught: + function(*args, **kwargs) + self.assertEqual(caught.exception.code, code) + + def test_real_bgra_viewport_ignores_padding_and_hidden_pixels(self): + g = geometry(width=2, height=2, virtual_width=4, virtual_height=4, + xoffset=1, yoffset=1, stride=20, memory_bytes=80) + raw = bytearray(b"\xa5" * g.memory_bytes) + expected = bytearray() + for y in range(4): + for x in range(4): + r, green, b = 10 * y + x, 20 * y + x, 30 * y + x + start = y * g.stride + x * 4 + raw[start:start + 4] = bytes((b, green, r, 255)) + if 1 <= y <= 2 and 1 <= x <= 2: + expected.extend((r, green, b)) + self.assertEqual(screen.decode_framebuffer(bytes(raw), g), bytes(expected)) + + def test_real_big_endian_rgb24_with_row_padding(self): + g = geometry(width=2, height=2, virtual_width=2, virtual_height=2, + bpp=24, stride=8, memory_bytes=16, byteorder="big") + raw = bytes((1, 2, 3, 4, 5, 6, 0xa5, 0xa5, 7, 8, 9, 10, 11, 12, 0xa5, 0xa5)) + self.assertEqual(screen.decode_framebuffer(raw, g), bytes(range(1, 13))) + + def test_rgb565_channel_scaling(self): + g = geometry(width=4, virtual_width=4, bpp=16, stride=8, memory_bytes=8, + red=(11, 5), green=(5, 6), blue=(0, 5)) + raw = struct.pack("<4H", 0xf800, 0x07e0, 0x001f, 0xffff) + self.assertEqual(screen.decode_framebuffer(raw, g), + bytes((255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255))) + + def test_partial_framebuffer_is_rejected(self): + self.assert_code("FRAMEBUFFER_READ_SHORT", screen.decode_framebuffer, b"\0" * 7, geometry()) + + def test_overlapping_rgb_fields_are_rejected(self): + self.assert_code("COLOR_LAYOUT_OVERLAP", screen.validate_geometry, + geometry(green=(16, 8))) + + def test_ten_bit_channels_are_rejected_instead_of_silently_quantized(self): + self.assert_code("COLOR_LAYOUT_UNSUPPORTED", screen.decode_framebuffer, b"\xff" * 8, + geometry(red=(20, 10), green=(10, 10), blue=(0, 10))) + + def test_viewport_outside_virtual_screen_is_rejected(self): + self.assert_code("VIEWPORT_INVALID", screen.validate_geometry, + geometry(xoffset=1)) + + def test_stride_cannot_hide_missing_pixels(self): + self.assert_code("FRAMEBUFFER_LAYOUT_INVALID", screen.validate_geometry, + geometry(stride=4)) + + def test_png_preserves_each_pixel_without_overlays(self): + pixels = bytes((1, 2, 3, 255, 0, 0, 0, 255, 0, 0, 0, 255)) + self.assertEqual(png_pixels(screen.encode_png(pixels, 2, 2)), ((2, 2), pixels)) + + def test_png_supports_more_than_256_distinct_colors(self): + pixels = b"".join(bytes((i % 256, i // 256, i % 113)) for i in range(1024)) + self.assertGreater(len({pixels[i:i + 3] for i in range(0, len(pixels), 3)}), 256) + self.assertEqual(png_pixels(screen.encode_png(pixels, 32, 32)), ((32, 32), pixels)) + + def assert_indexed_pixels(self, color_count, expected_depth): + # Odd row widths exercise unused packed bits and independent row starts. + width = color_count + (2 if color_count % 2 else 1) + height = 2 + colors = [bytes((i, 255 - i, i * 37 % 256)) for i in range(color_count)] + pixels = b"".join(colors[i % color_count] for i in range(width * height)) + png = screen.encode_png(pixels, width, height, prefer_indexed=True) + self.assertEqual((png[24], png[25]), (expected_depth, 3)) + self.assertEqual(png_pixels(png), ((width, height), pixels)) + + def test_palette_one_bit_storage_preserves_one_and_two_colors(self): + for count in (1, 2): + with self.subTest(count=count): + self.assert_indexed_pixels(count, 1) + + def test_palette_two_bit_storage_preserves_three_and_four_colors(self): + for count in (3, 4): + with self.subTest(count=count): + self.assert_indexed_pixels(count, 2) + + def test_palette_four_bit_storage_preserves_five_and_sixteen_colors(self): + for count in (5, 16): + with self.subTest(count=count): + self.assert_indexed_pixels(count, 4) + + def test_palette_eight_bit_storage_preserves_seventeen_and_256_colors(self): + for count in (17, 256): + with self.subTest(count=count): + self.assert_indexed_pixels(count, 8) + + def test_palette_attempt_at_257_colors_falls_back_without_pixel_loss(self): + pixels = b"".join(bytes((i % 256, i // 256, i * 37 % 256)) for i in range(257)) + self.assertEqual(len({pixels[i:i + 3] for i in range(0, len(pixels), 3)}), 257) + png = screen.encode_png(pixels, 257, 1, prefer_indexed=True) + self.assertEqual((png[24], png[25]), (8, 2)) + self.assertEqual(png_pixels(png), ((257, 1), pixels)) + + def test_default_rgb_format_stays_truecolor_even_for_palette_sized_input(self): + pixels = bytes((1, 2, 3, 4, 5, 6)) + png = screen.encode_png(pixels, 2, 1) + self.assertEqual((png[24], png[25]), (8, 2)) + self.assertEqual(png_pixels(png), ((2, 1), pixels)) + + def tty_session(self, **changes): + s = {"Id": "2", "User": "1000", "Leader": "1234", "Active": "yes", "Remote": "no", + "Type": "tty", "Seat": "seat0", "VTNr": "1", "TTY": "tty1"} + s.update(changes) + return s + + def test_foreground_identifies_actual_active_vt(self): + s = self.tty_session() + selected = screen.choose_foreground("tty1", [s, self.tty_session(Id="3", Active="no")], "seat0") + self.assertEqual((selected.active_vt, selected.session_id, selected.leader), ("tty1", "2", 1234)) + + def test_active_graphical_session_never_uses_framebuffer_fallback(self): + for mode in ("x11", "wayland"): + with self.subTest(mode=mode): + self.assert_code("GRAPHICAL_CAPTURE_REQUIRED", screen.choose_foreground, + "tty1", [self.tty_session(Type=mode)], "seat0") + + def test_unmatched_vt_is_rejected(self): + self.assert_code("FOREGROUND_SESSION_UNVERIFIED", screen.choose_foreground, + "tty2", [self.tty_session()], "seat0") + + def test_remote_session_does_not_bind_local_display(self): + self.assert_code("FOREGROUND_SESSION_UNVERIFIED", screen.choose_foreground, + "tty1", [self.tty_session(Remote="yes")], "seat0") + + def test_foreground_switch_during_capture_is_rejected(self): + before = screen.choose_foreground("tty1", [self.tty_session()], "seat0") + screen.require_unchanged(before, before) + self.assert_code("FOREGROUND_CHANGED_DURING_CAPTURE", screen.require_unchanged, + before, replace(before, session_id="4")) + + def x11_session(self, **changes): + s = self.tty_session(Type="x11", User=str(os.getuid()), TTY="tty7", VTNr="7", Display=":0") + s.update(changes) + return s + + def test_display_selector_accepts_same_user_active_local_x11_vt(self): + f = screen.choose_display_foreground("tty7", [self.x11_session()], "seat0") + self.assertEqual((f.session_type, f.uid, f.display, f.vtnr), ("x11", os.getuid(), ":0", 7)) + + def test_lightdm_empty_tty_keeps_verified_numeric_vt(self): + f = screen.choose_display_foreground("tty7", [self.x11_session(TTY="")], "seat0") + self.assertEqual((f.active_vt, f.tty, f.vtnr, f.display), ("tty7", "", 7, ":0")) + + def test_lightdm_empty_tty_never_accepts_wrong_vt_user_or_remote_session(self): + cases = [({"VTNr": "8"}, "GRAPHICAL_VT_UNVERIFIED"), + ({"User": str(os.getuid() + 1)}, "X11_SESSION_IDENTITY_MISMATCH"), + ({"Remote": "yes"}, "FOREGROUND_SESSION_UNVERIFIED")] + for changes, code in cases: + with self.subTest(changes=changes): + s = self.x11_session(TTY="", **changes) + self.assert_code(code, screen.choose_display_foreground, "tty7", [s], "seat0") + + def test_lightdm_explicit_contradictory_tty_is_rejected(self): + self.assert_code("GRAPHICAL_VT_UNVERIFIED", screen.choose_display_foreground, + "tty7", [self.x11_session(TTY="tty8")], "seat0") + + def test_lightdm_missing_tty_metadata_is_not_treated_as_empty(self): + s = self.x11_session() + del s["TTY"] + self.assert_code("GRAPHICAL_VT_UNVERIFIED", screen.choose_display_foreground, + "tty7", [s], "seat0") + + def test_lightdm_capture_identity_rejects_vt_switch_or_session_replacement(self): + before = screen.choose_display_foreground("tty7", [self.x11_session(TTY="")], "seat0") + for after in [replace(before, active_vt="tty8", vtnr=8), + replace(before, session_id="replacement"), + replace(before, display=":1"), replace(before, uid=before.uid + 1)]: + with self.subTest(after=after): + self.assert_code("FOREGROUND_CHANGED_DURING_CAPTURE", + screen.require_unchanged, before, after) + + def test_display_selector_keeps_text_vt_identity(self): + f = screen.choose_display_foreground("tty1", [self.tty_session()], "seat0") + self.assertEqual((f.session_type, f.display, f.active_vt), ("tty", "", "tty1")) + + def test_display_selector_refuses_wayland_instead_of_stale_fbdev(self): + self.assert_code("WAYLAND_CAPTURE_UNSUPPORTED", screen.choose_display_foreground, + "tty7", [self.x11_session(Type="wayland")], "seat0") + + def test_display_selector_refuses_network_and_invalid_display_names(self): + for display in ("host:0", "localhost:0", "tcp/localhost:0", ":0;anything", ""): + with self.subTest(display=display): + self.assert_code("X11_DISPLAY_UNVERIFIED", screen.choose_display_foreground, + "tty7", [self.x11_session(Display=display)], "seat0") + + def test_display_selector_requires_matching_vt_and_identity(self): + self.assert_code("GRAPHICAL_VT_UNVERIFIED", screen.choose_display_foreground, + "tty8", [self.x11_session()], "seat0") + self.assert_code("X11_SESSION_IDENTITY_MISMATCH", screen.choose_display_foreground, + "tty7", [self.x11_session(User=str(os.getuid() + 1))], "seat0") + + def test_display_selector_requires_one_active_seat_session(self): + self.assert_code("FOREGROUND_SESSION_UNVERIFIED", screen.choose_display_foreground, + "tty7", [self.x11_session(), self.x11_session(Id="3")], "seat0") + + def test_x11_color_masks_accept_contiguous_fields_only(self): + self.assertEqual(screen._mask_field(0xff0000), (16, 8)) + self.assertEqual(screen._mask_field(0x07e0), (5, 6)) + self.assertEqual(screen._mask_field(1), (0, 1)) + for mask in (0, -1, 0b101, 0xff00ff): + with self.subTest(mask=mask): + self.assert_code("X11_COLOR_MASK_INVALID", screen._mask_field, mask) + + def test_root_display_manager_environment_is_never_read(self): + f = screen.Foreground("tty7", "19", 1000, 3668, "x11", "", "seat0", 7, ":0") + leader = MagicMock() + leader.stat.return_value = SimpleNamespace(st_uid=0) + with patch.object(screen, "Path") as path: + path.return_value.__truediv__.return_value = leader + self.assertEqual(screen._x11_leader_environment(f, ":0"), {}) + leader.__truediv__.assert_not_called() + + def test_foreign_user_session_leader_is_rejected(self): + f = screen.Foreground("tty7", "19", 1000, 3668, "x11", "", "seat0", 7, ":0") + leader = MagicMock() + leader.stat.return_value = SimpleNamespace(st_uid=1001) + with patch.object(screen, "Path") as path: + path.return_value.__truediv__.return_value = leader + self.assert_code("X11_LEADER_IDENTITY_MISMATCH", + screen._x11_leader_environment, f, ":0") + leader.__truediv__.assert_not_called() + + def test_user_leader_context_rejects_a_different_display(self): + f = screen.Foreground("tty7", "19", os.getuid(), 3668, "x11", "", "seat0", 7, ":0") + leader = MagicMock() + leader.stat.return_value = SimpleNamespace(st_uid=f.uid) + leader.__truediv__ = Mock(return_value=SimpleNamespace(read_bytes=lambda: b"DISPLAY=:1\0")) + with patch.object(screen, "Path") as path: + path.return_value.__truediv__.return_value = leader + self.assert_code("X11_DISPLAY_IDENTITY_MISMATCH", + screen._x11_leader_environment, f, ":0") + + def private_home_context(self, home, **changes): + f = screen.choose_display_foreground("tty7", [self.x11_session(TTY="")], "seat0") + f = replace(f, **changes) + account = SimpleNamespace(pw_uid=f.uid, pw_dir=str(home)) + return f, account + + def test_lightdm_root_leader_falls_back_to_validated_same_user_authority(self): + with tempfile.TemporaryDirectory() as home: + authority = Path(home) / ".Xauthority" + authority.touch(mode=0o600) + f, account = self.private_home_context(home) + with patch.dict(os.environ, {}, clear=True), \ + patch.object(screen, "_x11_leader_environment", return_value={}), \ + patch.object(screen.pwd, "getpwuid", return_value=account): + self.assertEqual(screen._x11_context(f, None, None), (":0", str(authority))) + + def test_canonical_home_fallback_ignores_callers_home(self): + with tempfile.TemporaryDirectory() as home: + authority = Path(home) / ".Xauthority" + authority.touch(mode=0o600) + f, account = self.private_home_context(home) + with patch.dict(os.environ, {"HOME": "/unrelated-user"}, clear=True), \ + patch.object(screen, "_x11_leader_environment", return_value={}), \ + patch.object(screen.pwd, "getpwuid", return_value=account): + self.assertEqual(screen._x11_context(f, None, None)[1], str(authority)) + + def test_authority_fallback_rejects_missing_world_readable_or_symlink_file(self): + with tempfile.TemporaryDirectory() as home: + authority = Path(home) / ".Xauthority" + target = Path(home) / "target" + f, account = self.private_home_context(home) + with patch.dict(os.environ, {}, clear=True), \ + patch.object(screen, "_x11_leader_environment", return_value={}), \ + patch.object(screen.pwd, "getpwuid", return_value=account): + self.assert_code("X11_AUTHORITY_UNAVAILABLE", screen._x11_context, f, None, None) + authority.touch(mode=0o644) + # Ensure the negative fixture stays world-readable under a private umask. + authority.chmod(0o644) + self.assert_code("X11_AUTHORITY_UNTRUSTED", screen._x11_context, f, None, None) + authority.unlink() + target.touch(mode=0o600) + authority.symlink_to(target) + self.assert_code("X11_AUTHORITY_UNTRUSTED", screen._x11_context, f, None, None) + + def test_authority_fallback_rejects_foreign_file_owner(self): + with tempfile.TemporaryDirectory() as home: + authority = Path(home) / ".Xauthority" + authority.touch(mode=0o600) + f, account = self.private_home_context(home, uid=os.getuid() + 1) + with patch.dict(os.environ, {}, clear=True), \ + patch.object(screen.os, "getuid", return_value=f.uid), \ + patch.object(screen, "_x11_leader_environment", return_value={}), \ + patch.object(screen.pwd, "getpwuid", return_value=account): + self.assert_code("X11_AUTHORITY_UNTRUSTED", screen._x11_context, f, None, None) + + def test_authority_fallback_rejects_missing_account_or_relative_home(self): + f, account = self.private_home_context("relative-home") + with patch.dict(os.environ, {}, clear=True), \ + patch.object(screen, "_x11_leader_environment", return_value={}): + with patch.object(screen.pwd, "getpwuid", side_effect=KeyError): + self.assert_code("X11_AUTHORITY_UNAVAILABLE", screen._x11_context, f, None, None) + with patch.object(screen.pwd, "getpwuid", return_value=account): + self.assert_code("X11_AUTHORITY_PATH_INVALID", screen._x11_context, f, None, None) + + def test_context_rejects_explicit_display_or_user_mismatch(self): + f, _ = self.private_home_context("/unused") + self.assert_code("X11_DISPLAY_IDENTITY_MISMATCH", screen._x11_context, f, ":1", None) + self.assert_code("X11_SESSION_IDENTITY_MISMATCH", screen._x11_context, + replace(f, uid=f.uid + 1), None, None) + + def artifact(self, directory, data=b"0123456789"): + cid = "1" * 32 + digest = hashlib.sha256(data).hexdigest() + manifest = {"capture_id": cid, "sha256": digest, "bytes": len(data), + "synthetic": False, "pixel_source_verified": True} + screen._write_new(Path(directory) / (cid + ".png"), data) + screen._write_new(Path(directory) / (cid + ".json"), json.dumps(manifest).encode()) + return cid, digest, manifest + + def test_artifact_write_cannot_overwrite_existing_file(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "one.png" + screen._write_new(path, b"first") + self.assert_code("CAPTURE_ARTIFACT_CREATE_FAILED", screen._write_new, path, b"second") + self.assertEqual(path.read_bytes(), b"first") + self.assertEqual(path.stat().st_mode & 0o777, 0o600) + + def test_chunks_reassemble_exact_immutable_bytes_and_digest(self): + with tempfile.TemporaryDirectory() as directory: + cid, digest, _ = self.artifact(directory) + results = [screen.read_chunk(directory, cid, i, chunk_bytes=4, expected_sha256=digest) + for i in range(3)] + self.assertEqual(b"".join(base64.b64decode(p["data"], validate=True) for p in results), + b"0123456789") + self.assertEqual([p["chunk_bytes"] for p in results], [4, 4, 2]) + self.assertEqual([p["more"] for p in results], [True, True, False]) + self.assertTrue(all(p["sha256"] == digest and p["total_chunks"] == 3 for p in results)) + + def test_chunk_index_and_boolean_are_rejected(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + self.assert_code("CHUNK_INDEX_OUT_OF_RANGE", screen.read_chunk, directory, cid, 1) + self.assert_code("CHUNK_BOUNDS_INVALID", screen.read_chunk, directory, cid, True) + self.assert_code("CHUNK_BOUNDS_INVALID", screen.read_chunk, directory, cid, 0, + chunk_bytes=screen.MAX_CHUNK_BYTES + 1) + + def test_capture_id_cannot_escape_artifact_directory(self): + with tempfile.TemporaryDirectory() as directory: + self.assert_code("CAPTURE_ID_INVALID", screen.read_chunk, directory, "../target", 0) + + def test_mutated_image_bytes_are_rejected(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + (Path(directory) / (cid + ".png")).write_bytes(b"changed") + self.assert_code("CAPTURE_SHA256_MISMATCH", screen.read_chunk, directory, cid, 0) + + def test_expected_digest_is_enforced(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + self.assert_code("EXPECTED_SHA256_MISMATCH", screen.read_chunk, directory, cid, 0, + expected_sha256="0" * 64) + + def test_image_symlink_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + p = Path(directory) / (cid + ".png") + target = Path(directory) / "target.png" + p.rename(target) + p.symlink_to(target) + self.assert_code("CAPTURE_ARTIFACT_UNAVAILABLE", screen.read_chunk, directory, cid, 0) + + def test_hard_linked_image_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + os.link(Path(directory) / (cid + ".png"), Path(directory) / "alias.png") + self.assert_code("CAPTURE_ARTIFACT_UNTRUSTED", screen.read_chunk, directory, cid, 0) + + def test_artifact_directory_cannot_be_a_symlink(self): + with tempfile.TemporaryDirectory() as directory: + target = Path(directory) / "real" + target.mkdir(mode=0o700) + link = Path(directory) / "linked" + link.symlink_to(target, target_is_directory=True) + self.assert_code("CAPTURE_DIRECTORY_UNTRUSTED", screen._directory, link) + + def test_synthetic_manifest_is_not_accepted_as_real_image(self): + with tempfile.TemporaryDirectory() as directory: + cid, _, manifest = self.artifact(directory) + manifest["synthetic"] = True + (Path(directory) / (cid + ".json")).write_text(json.dumps(manifest)) + self.assert_code("CAPTURE_MANIFEST_INVALID", screen.read_chunk, directory, cid, 0) + + def test_manifest_requires_json_object_not_null_or_array(self): + for invalid in (None, [], ["capture_id"], "text", 3): + with self.subTest(invalid=invalid), tempfile.TemporaryDirectory() as directory: + cid, _, _ = self.artifact(directory) + (Path(directory) / (cid + ".json")).write_text(json.dumps(invalid)) + self.assert_code("CAPTURE_MANIFEST_INVALID", screen.read_chunk, directory, cid, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_control.py b/source/tests/test_screen_control.py new file mode 100644 index 0000000..a515fae --- /dev/null +++ b/source/tests/test_screen_control.py @@ -0,0 +1,163 @@ +import copy +from datetime import datetime, timezone, timedelta +import hashlib +import os +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from framebridge_expanded.real_screen import screen_control as m + + +class ScreenControlTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.path = Path(self.temp.name) / "screen.png" + self.path.write_bytes(b"immutable-observed-frame-fixture") + self.binding = {"version": 1, "backend": "visible-tmux", "node": "TESTNODE", + "uid": os.getuid(), "active_vt": "tty1", "client_tty": "/dev/tty1", + "socket": {"path": "/run/user/1000/console.sock", "inode": 12}, + "server": {"pid": 100, "start_ticks": 20}, + "client": {"pid": 101, "start_ticks": 21}, + "session_id": "$0", "window_id": "@0", "pane_id": "%1", + "pane": {"pid": 102, "start_ticks": 22}, + "pane_geometry": [120, 0, 120, 66], "pane_in_mode": False} + self.manifest = {"capture_id": "a" * 32, "source": "linux-framebuffer", + "screen_type": "tty", "active_vt": "tty1", "node": "TESTNODE", + "uid": os.getuid(), "synthetic": False, "pixel_source_verified": True, + "path": str(self.path), "sha256": hashlib.sha256(self.path.read_bytes()).hexdigest(), + "completed_at": datetime.now(timezone.utc).isoformat(), + "binding": copy.deepcopy(self.binding)} + + def tearDown(self): + self.temp.cleanup() + + def refused(self, change, code="SCREEN_BINDING_CHANGED"): + different = copy.deepcopy(self.binding) + change(different) + with patch.object(m, "snapshot_binding", return_value=different), patch.object(m, "_tmux") as send: + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "key.send", key="ENTER") + self.assertEqual(e.exception.code, code) + send.assert_not_called() + + def test_bind_returns_copy(self): + with patch.object(m, "snapshot_binding", return_value=self.binding): + result = m.bind_screen(self.manifest) + self.assertIsNot(result, self.manifest) + self.assertEqual(result["binding"], self.binding) + + def test_bind_vt_mismatch(self): + self.manifest["active_vt"] = "tty2" + with patch.object(m, "snapshot_binding", return_value=self.binding): + with self.assertRaises(m.ControlError) as e: + m.bind_screen(self.manifest) + self.assertEqual(e.exception.code, "SCREEN_CAPTURE_VT_MISMATCH") + + def test_bind_node_mismatch(self): + self.manifest["node"] = "OTHER" + with patch.object(m, "snapshot_binding", return_value=self.binding): + with self.assertRaises(m.ControlError) as e: + m.bind_screen(self.manifest) + self.assertEqual(e.exception.code, "SCREEN_CAPTURE_NODE_MISMATCH") + + def test_wrong_source(self): + self.manifest["source"] = "rendered-text" + with self.assertRaises(m.ControlError) as e: + m.bind_screen(self.manifest) + self.assertEqual(e.exception.code, "SCREEN_CONTROL_MODE_UNSUPPORTED") + + def test_literal_text_no_shell_or_enter(self): + text = "printf '%s' '$(touch never)'" + with patch.object(m, "snapshot_binding", return_value=self.binding), patch.object(m, "_tmux", return_value="") as send: + result = m.act(self.manifest, "text.type", text=text) + self.assertTrue(result["performed"]) + send.assert_called_once_with(self.binding["socket"]["path"], "send-keys", "-t", "%1", "-l", "--", text) + + def test_explicit_key(self): + with patch.object(m, "snapshot_binding", return_value=self.binding), patch.object(m, "_tmux", return_value="") as send: + result = m.act(self.manifest, "key.send", key="ENTER") + self.assertTrue(result["performed"]) + send.assert_called_once_with(self.binding["socket"]["path"], "send-keys", "-t", "%1", "Enter") + + def test_text_newline_rejected(self): + with patch.object(m, "_tmux") as send: + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "text.type", text="pwd\n") + self.assertEqual(e.exception.code, "SCREEN_TEXT_CONTROL_CHARACTER") + send.assert_not_called() + + def test_unknown_key_rejected(self): + with patch.object(m, "_tmux") as send: + with self.assertRaises(m.ControlError): + m.act(self.manifest, "key.send", key="ALT_F2") + send.assert_not_called() + + def test_changed_vt_no_input(self): + self.refused(lambda b: b.update(active_vt="tty2")) + + def test_changed_pane_no_input(self): + self.refused(lambda b: b["pane"].update(pid=222)) + + def test_pid_reuse_no_input(self): + self.refused(lambda b: b["pane"].update(start_ticks=23)) + + def test_changed_socket_no_input(self): + self.refused(lambda b: b["socket"].update(inode=13)) + + def test_changed_geometry_no_input(self): + self.refused(lambda b: b.update(pane_geometry=[0, 0, 240, 66])) + + def test_hidden_client_no_fallback(self): + identity = {"path": "/run/user/1000/console.sock", "inode": 12} + client_row = "101|/dev/tty2|$0|240|67|0\n" + with patch.object(m, "_active_vt", return_value="tty1"), patch.object(m, "_server_sockets", return_value=[(identity, {"pid": 100})]), patch.object(m, "_tmux", return_value=client_row) as run: + with self.assertRaises(m.ControlError) as e: + m.snapshot_binding() + self.assertEqual(e.exception.code, "SCREEN_VISIBLE_TMUX_NOT_FOUND") + self.assertEqual(run.call_count, 1) + + def test_postflight_change_reports_performed(self): + other = copy.deepcopy(self.binding) + other["pane_id"] = "%2" + with patch.object(m, "snapshot_binding", side_effect=[self.binding, self.binding, other]), patch.object(m, "_tmux", return_value="") as send: + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "key.send", key="ENTER") + self.assertEqual(e.exception.code, "SCREEN_CHANGED_AFTER_INPUT") + self.assertTrue(e.exception.details["performed"]) + send.assert_called_once() + + def test_tampered_capture_no_input(self): + self.path.write_bytes(b"changed") + with patch.object(m, "_tmux") as send: + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "key.send", key="ENTER") + self.assertEqual(e.exception.code, "SCREEN_CAPTURE_SHA256_MISMATCH") + send.assert_not_called() + + def test_capture_id_required(self): + self.manifest.pop("capture_id") + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "key.send", key="ENTER") + self.assertEqual(e.exception.code, "SCREEN_CAPTURE_ID_REQUIRED") + + def test_copy_mode_does_not_type_into_hidden_application(self): + self.binding["pane_in_mode"] = True + self.manifest["binding"] = copy.deepcopy(self.binding) + self.refused(lambda b: None, "SCREEN_TMUX_MODE_INPUT_UNSUPPORTED") + + def test_optional_explicit_age_limit(self): + self.manifest["completed_at"] = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat() + with self.assertRaises(m.ControlError) as e: + m.act(self.manifest, "key.send", key="ENTER", max_age_s=30) + self.assertEqual(e.exception.code, "SCREEN_CAPTURE_EXPIRED") + + def test_no_arbitrary_age_expiry_by_default(self): + self.manifest["completed_at"] = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat() + with patch.object(m, "snapshot_binding", return_value=self.binding), patch.object(m, "_tmux", return_value=""): + self.assertTrue(m.act(self.manifest, "key.send", key="ENTER")["performed"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_frontdoor.py b/source/tests/test_screen_frontdoor.py new file mode 100644 index 0000000..1fe8bdc --- /dev/null +++ b/source/tests/test_screen_frontdoor.py @@ -0,0 +1,315 @@ +import base64 +import hashlib +import importlib.util +import json +import math +import random +import shlex +import struct +import unittest +import zlib +from pathlib import Path + +from framebridge_expanded.real_screen.screen_frontdoor import CHUNK_BYTES, ScreenClient, ScreenError, register_framebridge_screen_tools + + +CAPTURE_ID = "1" * 32 +SESSION_ID = "screen_" + "2" * 32 +NODE_ID = "12345678-1234-1234-1234-123456789abc" +OTHER_NODE_ID = "87654321-4321-4321-4321-cba987654321" + + +def png(width=100, height=40): + def chunk(kind, data): + return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data)) + source = random.Random(20261022) + scanlines = b"".join(b"\0" + source.randbytes(width * 3) for _ in range(height)) + return (b"\x89PNG\r\n\x1a\n" + + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)) + + chunk(b"IDAT", zlib.compress(scanlines)) + chunk(b"IEND", b"")) + + +class Image: + def __init__(self, data, format): + self.data = data + self.format = format + + +class MCP: + def __init__(self): + self.tools = {} + self.annotations = {} + + def tool(self, annotations): + def register(fn): + self.tools[fn.__name__] = fn + self.annotations[fn.__name__] = annotations + return fn + return register + + +class Executor: + def __init__(self, variant=None, hostname="fb-central-lab"): + self.data = png() + self.variant = variant + self.hostname = hostname + self.requests = [] + + def __call__(self, **kwargs): + request = json.loads(base64.b64decode(shlex.split(kwargs["script"])[-1], validate=True)) + self.requests.append(request) + digest = hashlib.sha256(self.data).hexdigest() + manifest = {"capture_id": CAPTURE_ID, "node": self.hostname, + "bytes": len(self.data), "sha256": digest, "width": 100, "height": 40, + "source": "linux-framebuffer", "screen_type": "tty", "active_vt": "tty1", + "session_id": SESSION_ID, "full_screen": True, "authentic_pixels": True, + "path": "/private/internal.png"} + if request.get("session_id"): + manifest["requested_session_visible"] = request["session_id"] == SESSION_ID + if self.variant == "node": + manifest["node"] = "invalid hostname" + elif self.variant == "source": + manifest["source"] = "terminal-text-renderer" + elif self.variant == "provenance": + manifest["authentic_pixels"] = False + elif self.variant == "dimensions": + manifest["width"] = 99 + elif self.variant == "x11": + manifest["source"] = "x11-root-XGetImage" + manifest["screen_type"] = "x11" + if request["op"] == "read_chunk": + index = request["index"] + data = self.data[index * CHUNK_BYTES:(index + 1) * CHUNK_BYTES] + result = {"capture_id": CAPTURE_ID, "index": index, + "total_chunks": math.ceil(len(self.data) / CHUNK_BYTES), + "bytes": len(self.data), "sha256": digest, "encoding": "base64", + "data": base64.b64encode(data).decode(), + "more": (index + 1) * CHUNK_BYTES < len(self.data)} + if self.variant == "chunk_index": + result["index"] = index + 1 + elif self.variant == "chunk_hash": + result["sha256"] = "0" * 64 + elif self.variant == "chunk_more": + result["more"] = not result["more"] + elif self.variant == "chunk_encoding": + result["data"] = "?invalid?" + elif self.variant == "chunk_length": + result["data"] = base64.b64encode(data[:-1]).decode() + elif self.variant == "image_hash": + changed = bytes([data[0] ^ 1]) + data[1:] + result["data"] = base64.b64encode(changed).decode() + else: + result = manifest + error = (self.variant in ("error_exit1", "uncertain_input") or + request["op"] in ("key", "text") and + request.get("session_id", SESSION_ID) != SESSION_ID) + envelope = {"ok": False, "error": {"code": "SESSION_NOT_VISIBLE"}} if error else {"ok": True, "result": result} + if self.variant == "uncertain_input": + envelope["error"] = {"code": "INPUT_SENT_OBSERVE_FAILED", "performed": True, + "capture_consumed": True, "observe_again": True, + "capture_id": CAPTURE_ID, "cause": "SCREEN_IO_FAILED", + "text": "MUST NOT ESCAPE"} + outer = {"ok": not error, "exit_code": "1" if error else "0", + "target": kwargs["target"], "node_id": NODE_ID, "mcp_job_id": kwargs["job_id"], + "mcp_script_sha256": kwargs["sha256"], + "stdout": json.dumps(envelope), "stdout_truncated": self.variant == "truncated"} + if self.variant == "outer_job": + outer["mcp_job_id"] = "different" + elif self.variant == "outer_hash": + outer["mcp_script_sha256"] = "0" * 64 + elif self.variant == "outer_target": + outer["target"] = "OTHER" + elif self.variant == "outer_target_chunk" and request["op"] == "read_chunk": + outer["target"] = "OTHER" + elif self.variant == "outer_node_id_chunk" and request["op"] == "read_chunk": + outer["node_id"] = OTHER_NODE_ID + elif self.variant == "outer_node_id_missing": + outer.pop("node_id") + elif self.variant == "outer_node_id_invalid": + outer["node_id"] = "invalid" + return outer + + +class ScreenTests(unittest.TestCase): + def client(self, variant=None): + executor = Executor(variant) + return ScreenClient(executor, Image), executor + + def test_native_image_return_preserves_bytes_and_omits_internal_path(self): + client, executor = self.client() + result = client.call("observe", "gerameg", True) + self.assertEqual(result[0]["node"], "GERAMEG") + self.assertEqual(result[1].data, executor.data) + self.assertEqual(result[1].format, "png") + self.assertNotIn("path", result[0]["result"]) + self.assertNotIn("image_base64", json.dumps(result[0])) + self.assertGreater(len(executor.requests), 2) + + def test_authorization_required_before_any_exec(self): + client, executor = self.client() + self.assertEqual(client.call("observe", "GERAMEG", False)["error"], "AUTHORIZED_TRUE_REQUIRED") + self.assertEqual(executor.requests, []) + + def test_invalid_node_cannot_reach_shell(self): + client, executor = self.client() + self.assertEqual(client.call("observe", "GERAMEG;id", True)["error"], "SCREEN_NODE_INVALID") + self.assertEqual(executor.requests, []) + + def test_visible_action_requires_capture_id(self): + client, executor = self.client() + self.assertEqual(client.call("key", "GERAMEG", True, key="ENTER")["error"], "SCREEN_CAPTURE_ID_REQUIRED") + self.assertEqual(executor.requests, []) + + def test_hidden_reference_observe_shows_full_screen_and_visibility_false(self): + client, executor = self.client() + result = client.call("observe", "GERAMEG", True, session_id="hidden_virtual_session") + self.assertFalse(result[0]["result"]["requested_session_visible"]) + self.assertIsInstance(result[1], Image) + + def test_hidden_action_error_survives_exit1_without_image(self): + client, executor = self.client() + result = client.call("key", "GERAMEG", True, capture_id=CAPTURE_ID, + session_id="hidden_virtual_session", key="ENTER") + self.assertEqual(result["error"], "SESSION_NOT_VISIBLE") + self.assertEqual(len(executor.requests), 1) + + def test_backend_structured_error_survives_failure_flag_and_exit1(self): + client, executor = self.client("error_exit1") + self.assertEqual(client.call("observe", "GERAMEG", True)["error"], "SESSION_NOT_VISIBLE") + + def test_key_action_returns_new_real_image(self): + client, executor = self.client() + result = client.call("key", "GERAMEG", True, capture_id=CAPTURE_ID, key="CTRL_L") + self.assertEqual(executor.requests[0]["op"], "key") + self.assertEqual(result[1].data, executor.data) + + def test_uncertain_input_details_preserved_without_payload(self): + client, executor = self.client("uncertain_input") + result = client.call("key", "GERAMEG", True, capture_id=CAPTURE_ID, key="ENTER") + self.assertEqual(result["error"], "INPUT_SENT_OBSERVE_FAILED") + self.assertIs(result["performed"], True) + self.assertIs(result["capture_consumed"], True) + self.assertIs(result["observe_again"], True) + self.assertEqual(result["capture_id"], CAPTURE_ID) + self.assertNotIn("text", result) + self.assertEqual(len(executor.requests), 1) + + def test_utf8_text_limit_matches_backend(self): + client, executor = self.client() + result = client.call("text", "GERAMEG", True, capture_id=CAPTURE_ID, text="界" * 1400) + self.assertEqual(result["error"], "SCREEN_TEXT_INVALID") + self.assertEqual(executor.requests, []) + + def test_text_is_base64_transport_and_not_shell_interpolation(self): + client, executor = self.client() + text = "literal $(touch /tmp/no) `id` ' quote" + result = client.call("text", "GERAMEG", True, capture_id=CAPTURE_ID, text=text) + self.assertEqual(executor.requests[0]["text"], text) + self.assertIsInstance(result[1], Image) + + def test_integrity_rejections(self): + expected = {"outer_job": "SCREEN_EXEC_BINDING_INVALID", + "outer_hash": "SCREEN_EXEC_BINDING_INVALID", + "outer_target": "SCREEN_EXEC_BINDING_INVALID", + "truncated": "SCREEN_STDOUT_INVALID", "node": "SCREEN_IMAGE_HOSTNAME_INVALID", + "outer_target_chunk": "SCREEN_EXEC_BINDING_INVALID", + "outer_node_id_chunk": "SCREEN_CHUNK_NODE_MISMATCH", + "outer_node_id_missing": "SCREEN_EXEC_NODE_ID_INVALID", + "outer_node_id_invalid": "SCREEN_EXEC_NODE_ID_INVALID", + "source": "SCREEN_PIXEL_SOURCE_INVALID", "provenance": "SCREEN_PIXEL_PROVENANCE_INVALID", + "dimensions": "SCREEN_PNG_INVALID", "chunk_index": "SCREEN_CHUNK_BINDING_INVALID", + "chunk_hash": "SCREEN_CHUNK_MANIFEST_INVALID", "chunk_more": "SCREEN_CHUNK_MANIFEST_INVALID", + "chunk_encoding": "SCREEN_CHUNK_INVALID", "chunk_length": "SCREEN_CHUNK_LENGTH_INVALID", + "image_hash": "SCREEN_IMAGE_HASH_MISMATCH"} + for variant, code in expected.items(): + with self.subTest(variant=variant): + client, _ = self.client(variant) + self.assertEqual(client.call("observe", "GERAMEG", True)["error"], code) + + def test_generic_node_is_passed_to_existing_executor(self): + client, executor = self.client() + self.assertEqual(client.call("observe", "ASUS", True)[0]["node"], "ASUS") + + def test_exact_x11_capture_source_is_accepted(self): + client, executor = self.client("x11") + result = client.call("observe", "ASUS", True) + self.assertEqual(result[0]["result"]["source"], "x11-root-XGetImage") + self.assertEqual(result[1].data, executor.data) + + def test_trusted_alias_or_uuid_resolver_is_injected(self): + executor = Executor() + client = ScreenClient(executor, Image, lambda node: "GERAMEG") + self.assertEqual(client.call("observe", "node-alias", True)[0]["node"], "GERAMEG") + + def test_registration_annotations_and_callable_functions(self): + mcp = MCP() + register_framebridge_screen_tools(mcp, Executor(), image_factory=Image) + self.assertEqual(set(mcp.tools), {"framebridge_screen_observe", "framebridge_screen_key", "framebridge_screen_text"}) + self.assertTrue(mcp.annotations["framebridge_screen_observe"]["readOnlyHint"]) + self.assertFalse(mcp.annotations["framebridge_screen_key"]["readOnlyHint"]) + self.assertIsInstance(mcp.tools["framebridge_screen_observe"]("GERAMEG", True)[1], Image) + + def test_local_route_preserves_distinct_real_hostname_and_uuid(self): + executor = Executor(hostname="fb-central-lab") + client = ScreenClient(executor, Image, lambda alias: "LOCAL") + result = client.call("observe", "LOCAL", True) + self.assertEqual(result[0]["node"], "LOCAL") + self.assertEqual(result[0]["hostname"], "fb-central-lab") + self.assertEqual(result[0]["node_id"], NODE_ID) + self.assertEqual(result[0]["result"]["node"], "fb-central-lab") + self.assertEqual(result[0]["result"]["transport_node"], "LOCAL") + self.assertEqual(result[1].data, executor.data) + + def test_remote_uuid_alias_and_hostname_remain_distinct(self): + executor = Executor(hostname="fb-node-lab") + client = ScreenClient(executor, Image, lambda alias: "fb-node") + result = client.call("observe", NODE_ID, True) + self.assertEqual(result[0]["requested_node"], NODE_ID) + self.assertEqual(result[0]["node"], "fb-node") + self.assertEqual(result[0]["hostname"], "fb-node-lab") + self.assertEqual(result[0]["result"]["node"], "fb-node-lab") + self.assertEqual(result[0]["result"]["transport_node"], "fb-node") + self.assertEqual(result[1].data, executor.data) + + def test_screen_action_keeps_capture_and_session_with_distinct_hostname(self): + executor = Executor(hostname="fb-node-lab") + client = ScreenClient(executor, Image, lambda alias: "fb-node") + result = client.call("key", "fb-node", True, capture_id=CAPTURE_ID, + session_id=SESSION_ID, key="ENTER") + self.assertEqual(executor.requests[0]["capture_id"], CAPTURE_ID) + self.assertEqual(executor.requests[0]["session_id"], SESSION_ID) + self.assertEqual(result[0]["hostname"], "fb-node-lab") + self.assertEqual(result[1].data, executor.data) + + def test_arbitrary_dictionary_cannot_be_treated_as_authenticated_manifest(self): + client, _ = self.client() + verified = client.remote("LOCAL", {"op": "observe"}) + with self.assertRaisesRegex(ScreenError, "SCREEN_IMAGE_NODE_MISMATCH"): + client.image("LOCAL", dict(verified)) + + def test_another_client_cannot_supply_authenticated_manifest(self): + first, _ = self.client() + second, _ = self.client() + verified = first.remote("LOCAL", {"op": "observe"}) + with self.assertRaisesRegex(ScreenError, "SCREEN_IMAGE_NODE_MISMATCH"): + second.image("LOCAL", verified) + + def test_authenticated_hostname_cannot_be_changed_after_transport_validation(self): + client, _ = self.client() + verified = client.remote("LOCAL", {"op": "observe"}) + verified["node"] = "forged-hostname" + with self.assertRaisesRegex(ScreenError, "SCREEN_IMAGE_NODE_MISMATCH"): + client.image("LOCAL", verified) + + def test_hostname_must_be_a_bounded_nonempty_string(self): + for hostname in ("", None, 123, "invalid hostname", "x" * 81): + with self.subTest(hostname=hostname): + executor = Executor(hostname=hostname) + client = ScreenClient(executor, Image, lambda alias: "LOCAL") + self.assertEqual(client.call("observe", "LOCAL", True)["error"], + "SCREEN_IMAGE_HOSTNAME_INVALID") + + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_presentation.py b/source/tests/test_screen_presentation.py new file mode 100644 index 0000000..e7cb77f --- /dev/null +++ b/source/tests/test_screen_presentation.py @@ -0,0 +1,161 @@ +"""Exercise original-artifact replay and the actual bundled MCP SDK contract.""" +import asyncio +import base64 +import hashlib +import json +import os +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from mcp.server.fastmcp import FastMCP, Image +from mcp.types import CallToolResult + +from framebridge_expanded.real_screen import screen_rpc as rpc +from framebridge_expanded.real_screen.screen_capture import CaptureError +from framebridge_expanded.real_screen.screen_frontdoor import ScreenClient +from framebridge_expanded.real_screen.screen_presentation import ( + TEMPLATE_URI, VIEWER_HTML, register_screen_presentation, +) +from test_screen_frontdoor import Executor, CAPTURE_ID, NODE_ID, png + + +class SavedCaptureRpcTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.directory = Path(self.temp.name) + self.data = png() + self.manifest = { + "capture_id": CAPTURE_ID, "node": "fb-central-lab", "uid": os.getuid(), + "source": "linux-framebuffer", "screen_type": "tty", "active_vt": "tty1", + "bytes": len(self.data), "sha256": hashlib.sha256(self.data).hexdigest(), + "width": 100, "height": 40, "synthetic": False, "pixel_source_verified": True, + "full_screen": True, "authentic_pixels": True, "control_available": True, + "_action_consumed": True, "captured_at": "2026-10-24T12:00:00Z", + } + self.png = self.directory / (CAPTURE_ID + ".png") + self.png.write_bytes(self.data) + self.png.chmod(0o600) + for suffix in (".json", ".bound.json"): + rpc.write_record(self.directory / (CAPTURE_ID + suffix), self.manifest) + self.initial = {p.name: (p.read_bytes(), p.stat().st_mtime_ns) + for p in self.directory.iterdir()} + + def test_replay_survives_client_restart_and_consumed_input_without_observation(self): + with patch.object(rpc, "observe", side_effect=AssertionError("new capture forbidden")), \ + patch.object(rpc, "act", side_effect=AssertionError("input forbidden")): + result = rpc.dispatch({"op": "read_capture", "capture_id": CAPTURE_ID}, self.directory) + self.assertTrue(result["ok"]) + result = result["result"] + self.assertEqual(result["sha256"], self.manifest["sha256"]) + self.assertEqual(result["captured_at"], self.manifest["captured_at"]) + self.assertTrue(result["historical_capture"]) + self.assertFalse(result["control_available"]) + self.assertNotIn("_action_consumed", result) + self.assertNotIn("path", result) + self.assertEqual(self.initial, {p.name: (p.read_bytes(), p.stat().st_mtime_ns) + for p in self.directory.iterdir()}) + + def test_replay_rejects_changed_png(self): + self.png.write_bytes(self.data + b"corruption") + with self.assertRaisesRegex(CaptureError, "CAPTURE_SHA256_MISMATCH"): + rpc.dispatch({"op": "read_capture", "capture_id": CAPTURE_ID}, self.directory) + + def test_replay_rejects_symlink_record(self): + record = self.directory / (CAPTURE_ID + ".bound.json") + record.unlink() + record.symlink_to(self.directory / (CAPTURE_ID + ".json")) + with self.assertRaises((OSError, rpc.ScreenRpcError)): + rpc.dispatch({"op": "read_capture", "capture_id": CAPTURE_ID}, self.directory) + + def test_replay_rejects_unrecognized_fields_and_unknown_reference(self): + with self.assertRaisesRegex(rpc.ScreenRpcError, "UNKNOWN_REQUEST_FIELDS"): + rpc.validate_request({"op": "read_capture", "capture_id": CAPTURE_ID, "text": "x"}) + with self.assertRaises((OSError, rpc.ScreenRpcError)): + rpc.dispatch({"op": "read_capture", "capture_id": "f" * 32}, self.directory) + + +class ScreenPresentationSdkTests(unittest.TestCase): + def setUp(self): + self.executor = Executor() + self.client = ScreenClient(self.executor, Image, lambda name: name) + self.mcp = FastMCP("screen-presentation-test") + self.show = register_screen_presentation(self.mcp, self.client) + + def test_actual_sdk_preserves_original_png_and_private_ui_metadata(self): + response = asyncio.run(self.mcp.call_tool( + "framebridge_screen_show", + {"node": "LOCAL", "capture_id": CAPTURE_ID, "authorized": True})) + self.assertIsInstance(response, CallToolResult) + images = [c for c in response.content if c.type == "image"] + self.assertEqual(len(images), 1) + self.assertEqual(images[0].mimeType, "image/png") + self.assertEqual(base64.b64decode(images[0].data), self.executor.data) + self.assertEqual(response.meta["framebridgeImage"]["data"], images[0].data) + self.assertEqual(response.structuredContent["node_id"], NODE_ID) + self.assertEqual(response.structuredContent["result"]["capture_id"], CAPTURE_ID) + self.assertEqual(response.structuredContent["result"]["sha256"], + hashlib.sha256(self.executor.data).hexdigest()) + self.assertEqual(self.executor.requests[0], + {"op": "read_capture", "capture_id": CAPTURE_ID}) + self.assertTrue(all(r["op"] in {"read_capture", "read_chunk"} for r in self.executor.requests)) + + def test_actual_descriptor_declares_ui_readonly_and_structured_schema(self): + tools = asyncio.run(self.mcp.list_tools()) + self.assertEqual(len(tools), 1) + tool = tools[0] + self.assertEqual(tool.meta["ui"]["resourceUri"], TEMPLATE_URI) + self.assertEqual(tool.meta["openai/outputTemplate"], TEMPLATE_URI) + self.assertTrue(tool.annotations.readOnlyHint) + self.assertFalse(tool.annotations.destructiveHint) + self.assertIn("ok", tool.outputSchema["properties"]) + self.assertIn("result", tool.outputSchema["properties"]) + + def test_resource_contains_offline_viewer_and_integrity_validation(self): + resources = asyncio.run(self.mcp.list_resources()) + self.assertEqual(len(resources), 1) + self.assertEqual(str(resources[0].uri), TEMPLATE_URI) + contents = list(asyncio.run(self.mcp.read_resource(TEMPLATE_URI))) + self.assertEqual(contents[0].mime_type, "text/html;profile=mcp-app") + self.assertEqual(contents[0].content, VIEWER_HTML) + self.assertEqual(contents[0].meta["ui"]["csp"]["connectDomains"], []) + self.assertNotIn("https://", VIEWER_HTML) + self.assertNotIn("fetch(", VIEWER_HTML) + self.assertIn('crypto.subtle.digest("SHA-256",bytes)', VIEWER_HTML) + self.assertIn("ui/notifications/tool-result", VIEWER_HTML) + self.assertIn("window.openai", VIEWER_HTML) + self.assertIn("This saved capture does not show subsequent screen changes", VIEWER_HTML) + + def test_unauthorized_replay_never_calls_backend(self): + response = self.show("LOCAL", CAPTURE_ID, False) + self.assertTrue(response.isError) + self.assertEqual(response.structuredContent["error"], "AUTHORIZED_TRUE_REQUIRED") + self.assertEqual(self.executor.requests, []) + + def test_replay_requires_exact_capture_reference(self): + response = self.show("LOCAL", "f" * 32, True) + self.assertTrue(response.isError) + self.assertEqual(response.structuredContent["error"], "SCREEN_CAPTURE_REFERENCE_MISMATCH") + self.assertEqual(len(self.executor.requests), 1) + + def test_replay_rejects_invalid_capture_before_backend(self): + response = self.show("LOCAL", "../invalid", True) + self.assertTrue(response.isError) + self.assertEqual(response.structuredContent["error"], "SCREEN_CAPTURE_ID_REQUIRED") + self.assertEqual(self.executor.requests, []) + + def test_replay_preserves_existing_uuid_binding_validation(self): + def corrupted(**kwargs): + result = self.executor(**kwargs) + result.pop("node_id", None) + return result + response = register_screen_presentation(FastMCP("binding-test"), ScreenClient(corrupted, Image))( + "LOCAL", CAPTURE_ID, True) + self.assertTrue(response.isError) + self.assertFalse(response.structuredContent["ok"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_profile_routing.py b/source/tests/test_screen_profile_routing.py new file mode 100644 index 0000000..d58292e --- /dev/null +++ b/source/tests/test_screen_profile_routing.py @@ -0,0 +1,23 @@ +import unittest +from unittest.mock import patch +from framebridge_expanded import fleet +from framebridge_expanded.real_screen.screen_frontdoor import resolve_profile_node, ScreenError + +class CanonicalScreenNodeTests(unittest.TestCase): + def test_remote_uuid_is_bound_to_confirmed_executor_alias(self): + uid = "12345678-1234-1234-1234-123456789abc" + with patch.object(fleet, "resolve_node", return_value={"transport": "broker_outbound", "alias": "worker-1", "node_id": uid}) as resolve: + self.assertEqual(resolve_profile_node(uid), "worker-1") + resolve.assert_called_once_with(uid) + + def test_receiver_identity_is_canonical_local(self): + with patch.object(fleet, "resolve_node", return_value={"transport": "local", "alias": "central"}): + self.assertEqual(resolve_profile_node("central"), "LOCAL") + + def test_unknown_alias_has_no_hostname_or_ssh_fallback(self): + with patch.object(fleet, "resolve_node", side_effect=fleet.FleetError("NODE_NOT_FOUND")): + with self.assertRaisesRegex(ScreenError, "NODE_NOT_FOUND"): + resolve_profile_node("unconfirmed-host") + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_registration.py b/source/tests/test_screen_registration.py new file mode 100644 index 0000000..89360a9 --- /dev/null +++ b/source/tests/test_screen_registration.py @@ -0,0 +1,106 @@ +"""Exercise the actual portable screen registration callback and its UUID contract. + +The bounded executor is a fixture. This is not VM, GUI, TTY or input acceptance. +""" +import ast +from pathlib import Path +import unittest + +from framebridge_expanded.real_screen.screen_frontdoor import register_framebridge_screen_tools +from test_screen_frontdoor import Executor, Image, MCP, NODE_ID + + +PORTABLE = Path(__file__).resolve().parents[1] / "portable/front_door_mcp.py" + + +class PortableScreenRegistrationTests(unittest.TestCase): + def registration(self, target="LOCAL", corrupt=None): + fixture = Executor(hostname="fb-central-lab" if target == "LOCAL" else "fb-node-lab") + calls, legacy_calls = [], [] + + def expanded(alias, script, authorized, timeout_s=120, job_id="", sha256="", include_stdout=False): + args = dict(script=script, authorized=authorized, timeout_s=timeout_s, + job_id=job_id, sha256=sha256, include_stdout=include_stdout, target=alias) + calls.append(args) + result = fixture(**args) + if corrupt == "uuid": + result.pop("node_id") + elif corrupt == "hash": + result["mcp_script_sha256"] = "0" * 64 + return result + + def legacy(**kwargs): + # Model the real legacy LOCAL envelope which lacks the Fleet UUID. + legacy_calls.append(kwargs) + if kwargs["target"] != "LOCAL": + remote = dict(kwargs) + return expanded(remote.pop("target"), **remote) + result = fixture(**kwargs) + result.pop("node_id") + return result + + tree = ast.parse(PORTABLE.read_text()) + definitions = [item for item in tree.body + if isinstance(item, ast.FunctionDef) and item.name == "_screen_exec_request"] + registrations = [item for item in tree.body + if isinstance(item, ast.Assign) and any( + isinstance(name, ast.Name) and name.id == "_real_screen_client" + for name in item.targets)] + self.assertEqual(len(registrations), 1) + scope = dict(mcp=MCP(), Image=Image, expanded_execute_node=expanded, expanded_execute_screen_node=expanded, + framebridge_exec=legacy, + register_framebridge_screen_tools=register_framebridge_screen_tools, + resolve_profile_node=lambda node: node) + # Load the definition and registration from production source rather + # than recreating the adapter inside this test. + module = ast.Module(body=definitions + registrations, type_ignores=[]) + exec(compile(ast.fix_missing_locations(module), str(PORTABLE), "exec"), scope) + return scope["mcp"], fixture, calls, legacy_calls, scope["_real_screen_client"] + + def assert_observe(self, target): + mcp, fixture, calls, legacy_calls, _ = self.registration(target) + response = mcp.tools["framebridge_screen_observe"](target, True) + self.assertIsInstance(response, list) + metadata, image = response + self.assertTrue(metadata["ok"]) + self.assertEqual(metadata["requested_node"], target) + self.assertEqual(metadata["node_id"], NODE_ID) + self.assertEqual(metadata["hostname"], fixture.hostname) + self.assertEqual(image.data, fixture.data) + self.assertEqual(image.format, "png") + if target == "LOCAL": + self.assertFalse(legacy_calls) + self.assertGreater(len(calls), 1) # Manifest plus bound image chunks. + self.assertTrue(all(item["target"] == target for item in calls)) + self.assertTrue(all(item["authorized"] is True and item["include_stdout"] is True + and item["timeout_s"] == 30 for item in calls)) + + def test_actual_registration_preserves_local_fleet_uuid(self): + self.assert_observe("LOCAL") + + def test_actual_registration_preserves_remote_alias_and_hostname(self): + self.assert_observe("fb-node") + + def test_actual_registration_still_rejects_missing_uuid(self): + mcp, _, _, _, _ = self.registration(corrupt="uuid") + response = mcp.tools["framebridge_screen_observe"]("LOCAL", True) + self.assertFalse(response["ok"]) + self.assertEqual(response["error"], "SCREEN_EXEC_NODE_ID_INVALID") + + def test_actual_registration_still_rejects_wrong_script_binding(self): + mcp, _, _, _, _ = self.registration(corrupt="hash") + response = mcp.tools["framebridge_screen_observe"]("LOCAL", True) + self.assertFalse(response["ok"]) + self.assertEqual(response["error"], "SCREEN_EXEC_BINDING_INVALID") + + def test_unauthorized_observation_never_reaches_executor(self): + mcp, _, calls, legacy_calls, _ = self.registration() + response = mcp.tools["framebridge_screen_observe"]("LOCAL", False) + self.assertFalse(response["ok"]) + self.assertEqual(response["error"], "AUTHORIZED_TRUE_REQUIRED") + self.assertFalse(calls) + self.assertFalse(legacy_calls) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_rpc.py b/source/tests/test_screen_rpc.py new file mode 100644 index 0000000..b249b61 --- /dev/null +++ b/source/tests/test_screen_rpc.py @@ -0,0 +1,127 @@ +import base64 +import hashlib +import json +import os +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from framebridge_expanded.real_screen import screen_rpc as rpc + + +class ScreenRpcTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.directory = Path(self.temp.name) + self.counter = 0 + self.binding = {'backend': 'visible-tmux', 'pane_id': '%1', 'node': 'TEST', 'uid': os.getuid()} + self.actions = [] + self.patches = [ + patch.object(rpc, 'snapshot_binding', return_value=self.binding), + patch.object(rpc, 'capture_screen', side_effect=self.capture), + patch.object(rpc, 'bind_screen', side_effect=lambda m: {**m, 'binding': self.binding}), + patch.object(rpc, 'read_chunk', side_effect=self.chunk), + patch.object(rpc, 'act', side_effect=self.action), + patch.object(rpc.time, 'sleep'), + ] + for p in self.patches: + p.start() + self.addCleanup(p.stop) + + def capture(self, directory): + self.counter += 1 + capture_id = f'{self.counter:032x}' + data = b'actual-pixel-test-fixture-' + capture_id.encode() + path = directory / (capture_id + '.png') + path.write_bytes(data) + return {'capture_id': capture_id, 'source': 'linux-framebuffer', 'screen_type': 'tty', + 'node': 'TEST', 'uid': os.getuid(), 'width': 1920, 'height': 1080, + 'path': str(path), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest(), + 'active_vt': 'tty1', 'synthetic': False, 'pixel_source_verified': True} + + def chunk(self, directory, capture_id, index, **kwargs): + data = (directory / (capture_id + '.png')).read_bytes() + digest = hashlib.sha256(data).hexdigest() + if digest != kwargs['expected_sha256']: + raise rpc.CaptureError('CAPTURE_SHA256_MISMATCH') + return {'capture_id': capture_id, 'index': index, 'bytes': len(data), 'sha256': digest, + 'total_chunks': 1, 'encoding': 'base64', 'data': base64.b64encode(data).decode(), 'more': False} + + def action(self, manifest, kind, **kwargs): + self.actions.append((manifest['capture_id'], kind, kwargs)) + return {'performed': True} + + def observe(self, **kwargs): + return rpc.dispatch({'op': 'observe', **kwargs}, self.directory)['result'] + + def test_observation_uses_capture_pixels_not_terminal_text(self): + m = self.observe(session_id='hidden_terminal') + self.assertTrue(m['full_screen'] and m['authentic_pixels']) + self.assertFalse(m['requested_session_visible']) + self.assertNotIn('path', m) + self.assertTrue(m['control_available']) + self.assertEqual(m['width'], 1920) + + def test_hidden_terminal_id_cannot_receive_input_for_visible_image(self): + m = self.observe() + with self.assertRaisesRegex(rpc.ScreenRpcError, 'SESSION_NOT_VISIBLE'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'session_id': 'hidden_terminal', 'key': 'ENTER'}, self.directory) + self.assertEqual(self.actions, []) + + def test_input_returns_new_observation_and_cannot_replay_old_capture(self): + m = self.observe() + after = rpc.dispatch({'op': 'text', 'capture_id': m['capture_id'], 'session_id': m['session_id'], 'text': 'visible marker'}, self.directory)['result'] + self.assertNotEqual(after['capture_id'], m['capture_id']) + self.assertEqual(len(self.actions), 1) + with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_ALREADY_USED'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + self.assertEqual(len(self.actions), 1) + + def test_unknown_send_result_does_not_replay_input(self): + m = self.observe() + with patch.object(rpc, 'act', side_effect=rpc.ControlError('SCREEN_BINDING_CHANGED')): + with self.assertRaisesRegex(rpc.ScreenRpcError, 'SCREEN_BINDING_CHANGED'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_ALREADY_USED'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + + def test_target_change_during_capture_returns_no_bound_image(self): + with patch.object(rpc, 'bind_screen', side_effect=lambda m: {**m, 'binding': {**self.binding, 'pane_id': '%0'}}): + with self.assertRaisesRegex(rpc.ScreenRpcError, 'VISIBLE_TARGET_CHANGED_DURING_CAPTURE'): + self.observe() + self.assertEqual(list(self.directory.glob('*.bound.json')), []) + + def test_sent_input_is_reported_if_following_capture_fails(self): + m = self.observe() + with patch.object(rpc, 'capture_screen', side_effect=rpc.CaptureError('ACTIVE_VT_CHANGED')): + with self.assertRaises(rpc.ScreenRpcError) as caught: + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + self.assertEqual(caught.exception.code, 'INPUT_SENT_OBSERVE_FAILED') + self.assertTrue(caught.exception.details['performed']) + self.assertEqual(len(self.actions), 1) + + def test_view_can_succeed_without_safe_input_target(self): + with patch.object(rpc, 'snapshot_binding', side_effect=rpc.ControlError('SCREEN_VISIBLE_TMUX_NOT_FOUND')): + m = self.observe() + self.assertFalse(m['control_available']) + with self.assertRaisesRegex(rpc.ScreenRpcError, 'VISIBLE_INPUT_UNAVAILABLE'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + self.assertEqual(self.actions, []) + + def test_changed_png_cannot_drive_input(self): + m = self.observe() + (self.directory / (m['capture_id'] + '.png')).write_bytes(b'different pixels') + with self.assertRaisesRegex(rpc.CaptureError, 'CAPTURE_SHA256_MISMATCH'): + rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory) + self.assertEqual(self.actions, []) + + def test_read_requires_expected_image_hash(self): + m = self.observe() + with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_SHA256_MISMATCH'): + rpc.dispatch({'op': 'read_chunk', 'capture_id': m['capture_id'], 'index': 0, 'expected_sha256': '0' * 64}, self.directory) + + +if __name__ == '__main__': + unittest.main() diff --git a/source/tests/test_screen_rpc_x11.py b/source/tests/test_screen_rpc_x11.py new file mode 100644 index 0000000..50d0410 --- /dev/null +++ b/source/tests/test_screen_rpc_x11.py @@ -0,0 +1,16 @@ +import unittest +from pathlib import Path +from unittest.mock import patch +from framebridge_expanded.real_screen import screen_rpc as rpc + +class X11InputRoutingTests(unittest.TestCase): + def test_x11_input_is_rejected_with_graphical_cua_instruction(self): + for op, value in (("text", {"text": "test"}), ("key", {"key": "ENTER"})): + request = {"op": op, "capture_id": "a" * 32, **value} + with self.subTest(op=op), patch.object(rpc, "load_capture", return_value={"screen_type": "x11"}), patch.object(rpc, "act") as hidden_action: + with self.assertRaisesRegex(rpc.ScreenRpcError, "USE_GRAPHICAL_CUA_FOR_X11_INPUT"): + rpc.dispatch(request, Path("/unused/fixture")) + hidden_action.assert_not_called() + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_transport.py b/source/tests/test_screen_transport.py new file mode 100644 index 0000000..4c5a799 --- /dev/null +++ b/source/tests/test_screen_transport.py @@ -0,0 +1,379 @@ +"""Regression tests for the explicit typed screen RPC transport.""" +import ast +from pathlib import Path +import unittest + +PORTABLE = Path(__file__).resolve().parents[1] / "portable/front_door_mcp.py" + +class PortableTypedRouteTests(unittest.TestCase): + def test_screen_callback_uses_explicit_typed_route(self): + calls = [] + def typed(*args): + calls.append(("typed", args)) + return {"typed": True} + def generic(*args): + calls.append(("generic", args)) + return {"typed": False} + source = ast.parse(PORTABLE.read_text()) + definitions = [n for n in source.body if isinstance(n, ast.FunctionDef) and n.name == "_screen_exec_request"] + self.assertEqual(len(definitions), 1) + scope = {"expanded_execute_node": generic, "expanded_execute_screen_node": typed} + exec(compile(ast.Module(body=definitions, type_ignores=[]), str(PORTABLE), "exec"), scope) + result = scope["_screen_exec_request"](script="canonical-screen-fixture", authorized=True, timeout_s=30, + job_id="screen-fixture", sha256="fixture-hash", + include_stdout=True, target="worker-1") + self.assertEqual([c[0] for c in calls], ["typed"]) + self.assertTrue(result["typed"]) + + + +import base64 +import copy +import hashlib +import json +import os +import subprocess +import time +from types import SimpleNamespace +from unittest.mock import Mock, patch + +from framebridge_expanded import fleet, broker, node +from framebridge_expanded.real_screen import screen_transport as transport +from framebridge_expanded.real_screen.screen_frontdoor import ScreenClient, ScreenError, RPC_COMMAND +from framebridge_expanded.real_screen.screen_rpc import encode + +NODE_ID = "12345678-1234-1234-1234-123456789abc" +CAPTURE_ID = "a" * 32 + + +def make_payload(request=None): + request = {"op": "observe"} if request is None else request + argument = base64.b64encode(encode(request)).decode("ascii") + script = RPC_COMMAND + argument + "\n" + return {"version": 1, "mode": "EXEC", "authorized": True, + "request_type": "screen_rpc", "script": script, + "sha256": hashlib.sha256(script.encode()).hexdigest(), + "job_id": "screen-fixture", "timeout_s": 1, "include_stdout": True} + + +def response(payload, envelope=None): + envelope = {"ok": True, "result": {"fixture": True}} if envelope is None else envelope + return {"ok": envelope["ok"], "job_id": payload["job_id"], + "command_sha256": payload["sha256"], "exit_code": "0" if envelope["ok"] else "1", + "stdout": encode(envelope).decode() + "\n", "stdout_truncated": False} + + +class TypedPayloadTests(unittest.TestCase): + def test_canonical_operation_roundtrips_without_shell(self): + payload = make_payload({"op": "status"}) + request, argument = transport.validate_payload(payload) + self.assertEqual(request, {"op": "status"}) + self.assertEqual(base64.b64decode(argument), encode(request)) + + def test_common_authorization_and_shape_rejections(self): + valid = make_payload() + mutations = [{"authorized": False}, {"version": True}, {"version": 2}, + {"mode": "CUA"}, {"job_id": "invalid job"}, {"timeout_s": True}, + {"timeout_s": 31}, {"timeout_s": 0}, {"include_stdout": False}, + {"sha256": "0" * 64}, {"request_type": "SCREEN_RPC"}, + {"unexpected": "not accepted"}] + for mutation in mutations: + with self.subTest(mutation=mutation): + payload = dict(valid, **mutation) + with self.assertRaises(transport.ScreenTransportError): + transport.validate_payload(payload) + payload = dict(valid) + del payload["authorized"] + with self.assertRaises(transport.ScreenTransportError): + transport.validate_payload(payload) + + def test_no_noncanonical_script_or_unknown_operation(self): + prefix = RPC_COMMAND + bad = ["echo arbitrary\n", prefix + "not-base64\n", + make_payload()["script"] + "echo extra\n", + make_payload()["script"].rstrip("\n") + " extra\n", + prefix + base64.b64encode(b'{ "op" : "status" }').decode() + "\n", + prefix + base64.b64encode(encode({"op": "exec", "script": "echo bad"})).decode() + "\n", + prefix + base64.b64encode(encode({"op": "status", "path": "/tmp/other"})).decode() + "\n", + prefix + "'" + base64.b64encode(encode({"op": "status"})).decode() + "'\n", + prefix + base64.b64encode(b"x" * 16385).decode() + "\n"] + for script in bad: + with self.subTest(script=script[:100]): + payload = make_payload() + payload.update(script=script, sha256=hashlib.sha256(script.encode()).hexdigest()) + with self.assertRaises(transport.ScreenTransportError): + transport.validate_payload(payload) + + def test_existing_input_schema_is_preserved(self): + for request in [{"op": "key", "capture_id": CAPTURE_ID, "key": "ENTER"}, + {"op": "text", "capture_id": CAPTURE_ID, "text": "printf"}, + {"op": "read_chunk", "capture_id": CAPTURE_ID, "index": 0, "expected_sha256": "f" * 64}]: + with self.subTest(op=request["op"]): + parsed, _ = transport.validate_payload(make_payload(request)) + self.assertEqual(parsed, request) + with self.assertRaises(transport.ScreenTransportError): + transport.validate_payload(make_payload({"op": "text", "capture_id": CAPTURE_ID, "text": "\x00"})) + + def test_adapter_intercepts_only_explicit_type(self): + original = Mock(return_value={"visible_engine": True}) + adapter = SimpleNamespace(handle=original) + transport.install_adapter_route(adapter) + generic = make_payload() + del generic["request_type"] + self.assertEqual(adapter.handle(generic), {"visible_engine": True}) + original.assert_called_once_with(generic) + original.reset_mock() + with self.assertRaises(transport.ScreenTransportError): + adapter.handle(dict(make_payload(), authorized=False)) + original.assert_not_called() + + def test_malformed_typed_case_cannot_fall_back(self): + original = Mock(side_effect=AssertionError("no visible FBTERM fallback")) + adapter = SimpleNamespace(handle=original) + transport.install_adapter_route(adapter) + for mutation in [{"request_type": " SCREEN_RPC "}, {"script": "echo arbitrary\n"}, + {"sha256": "0" * 64}, {"mode": "INVALID"}, {"version": False}]: + with self.subTest(mutation=mutation): + with self.assertRaises(transport.ScreenTransportError): + adapter.handle(dict(make_payload(), **mutation)) + original.assert_not_called() + + def test_success_and_child_error_envelopes_remain_exact(self): + payload = make_payload() + good = {"ok": True, "result": {"fixture": True}} + error = {"ok": False, "error": {"code": "INPUT_SENT_OBSERVE_FAILED", "performed": True, + "capture_consumed": True, "observe_again": True, + "capture_id": CAPTURE_ID, "cause": "SCREEN_IO_FAILED"}} + for envelope in [good, error]: + with self.subTest(ok=envelope["ok"]): + runner = Mock(return_value=(0 if envelope["ok"] else 1, encode(envelope) + b"\n", b"")) + result = transport.handle_request(payload, runner=runner) + self.assertEqual(result["stdout"].encode(), encode(envelope) + b"\n") + self.assertEqual(result["job_id"], payload["job_id"]) + self.assertEqual(result["command_sha256"], payload["sha256"]) + self.assertEqual(result["ok"], envelope["ok"]) + runner.assert_called_once_with(transport.validate_payload(payload)[1], 1) + + def test_input_timeout_is_unknown_and_never_replayed(self): + payload = make_payload({"op": "key", "capture_id": CAPTURE_ID, "key": "ENTER"}) + runner = Mock(side_effect=transport.ScreenProcessError("SCREEN_RPC_TIMEOUT", started=True)) + result = transport.handle_request(payload, runner=runner) + error = json.loads(result["stdout"])["error"] + self.assertEqual(error["code"], "SCREEN_RPC_TIMEOUT") + self.assertTrue(error["observe_again"]) + self.assertEqual(error["capture_id"], CAPTURE_ID) + self.assertNotIn("performed", error) + self.assertNotIn("capture_consumed", error) + runner.assert_called_once() + + def test_prelaunch_or_readonly_timeout_does_not_invent_input(self): + for request, started in [({"op": "observe"}, True), + ({"op": "key", "capture_id": CAPTURE_ID, "key": "ENTER"}, False)]: + with self.subTest(request=request, started=started): + runner = Mock(side_effect=transport.ScreenProcessError("SCREEN_RPC_PROCESS_FAILED", started=started)) + result = transport.handle_request(make_payload(request), runner=runner) + error = json.loads(result["stdout"])["error"] + self.assertNotIn("performed", error) + self.assertNotIn("capture_consumed", error) + self.assertNotIn("observe_again", error) + runner.assert_called_once() + + def test_invalid_or_oversized_input_reply_is_uncertain(self): + payload = make_payload({"op": "text", "capture_id": CAPTURE_ID, "text": "printf"}) + for output in [b"not JSON\n", b"x" * 4097]: + with self.subTest(size=len(output)): + runner = Mock(return_value=(0, output, b"")) + result = transport.handle_request(payload, runner=runner) + error = json.loads(result["stdout"])["error"] + self.assertTrue(error["observe_again"]) + self.assertNotIn("performed", error) + self.assertNotIn("capture_consumed", error) + self.assertLess(len(result["stdout"].encode()), 4096) + runner.assert_called_once() + + +class BoundedSubprocessTests(unittest.TestCase): + def launch_fixture(self, text, argument, timeout=1): + real_popen = subprocess.Popen + children, calls = [], [] + def fixture(argv, **kwargs): + calls.append((argv, kwargs)) + child = real_popen(["/usr/bin/python3", "-B", "-c", text], **kwargs) + children.append(child) + return child + with patch.object(transport.subprocess, "Popen", side_effect=fixture): + try: + result = transport._run_rpc(argument, timeout) + return result, children, calls + finally: + self.assertTrue(all(child.poll() is not None for child in children)) + + def test_fixed_argv_inherits_normal_environment_without_shell(self): + argument = transport.validate_payload(make_payload())[1] + output = encode({"ok": True, "result": {"fixture": True}}) + b"\n" + result, children, calls = self.launch_fixture("import sys;sys.stdout.buffer.write(" + repr(output) + ")", argument) + self.assertEqual(result, (0, output, b"")) + argv, kwargs = calls[0] + self.assertEqual(argv, ["/usr/bin/python3", "-B", "-m", "framebridge_expanded.real_screen.screen_rpc", argument]) + self.assertNotIn("shell", kwargs) + self.assertNotIn("env", kwargs) + self.assertTrue(kwargs["start_new_session"]) + self.assertTrue(kwargs["close_fds"]) + self.assertEqual(len(children), 1) + + def test_output_limit_does_not_accumulate_or_return_large_output(self): + for stream in ["stdout", "stderr"]: + with self.subTest(stream=stream): + with self.assertRaisesRegex(transport.ScreenProcessError, "SCREEN_RPC_OUTPUT_LIMIT"): + self.launch_fixture("import sys;sys." + stream + ".buffer.write(b'x'*100000);sys." + stream + ".flush()", "fixture") + + def test_wall_timeout_kills_and_reaps_single_child(self): + started = time.monotonic() + with self.assertRaisesRegex(transport.ScreenProcessError, "SCREEN_RPC_TIMEOUT"): + self.launch_fixture("import time;time.sleep(5)", "fixture", timeout=1) + self.assertLess(time.monotonic() - started, 3) + + def test_timeout_cleans_owned_group_after_leader_exits(self): + import signal + import tempfile + real_popen = subprocess.Popen + children = [] + with tempfile.TemporaryDirectory(prefix="framebridge-screen-test-") as folder: + pidfile = Path(folder) / "descendant.pid" + script = ("import os,time\npid=os.fork()\n" + "if pid==0:\n time.sleep(5)\n os._exit(0)\n" + "open(" + repr(str(pidfile)) + ",'w').write(str(pid))\nos._exit(0)\n") + def launch(argv, **kwargs): + child = real_popen(["/usr/bin/python3", "-B", "-c", script], **kwargs) + children.append(child) + return child + try: + with patch.object(transport.subprocess, "Popen", side_effect=launch): + with self.assertRaisesRegex(transport.ScreenProcessError, "SCREEN_RPC_TIMEOUT"): + transport._run_rpc("fixture", 1) + descendant = int(pidfile.read_text()) + def state(): + try: + return (Path("/proc") / str(descendant) / "stat").read_text().rsplit(")", 1)[1].split()[0] + except FileNotFoundError: + return None + deadline = time.monotonic() + 0.5 + while state() not in (None, "Z") and time.monotonic() < deadline: + time.sleep(0.01) + self.assertIn(state(), (None, "Z"), "owned descendant survived the RPC timeout") + finally: + # Keep the deliberately failing pre-fix regression free of live orphans. + for child in children: + try: + os.killpg(child.pid, signal.SIGKILL) + except ProcessLookupError: + pass + child.wait(timeout=1) + + + +class TypedFleetRouteTests(unittest.TestCase): + def local_node(self): + return {"transport": "local", "alias": "central", "node_id": NODE_ID} + + def remote_node(self): + return {"transport": "broker_outbound", "alias": "worker-1", "node_id": NODE_ID, + "operational_transport_ready": True} + + def execute(self, payload): + return fleet.execute_screen_node("worker-1", payload["script"], True, payload["timeout_s"], + payload["job_id"], payload["sha256"], True) + + def test_local_uses_typed_adapter_and_keeps_job_hash_uuid(self): + payload = make_payload({"op": "status"}) + def adapter(value): + self.assertEqual(value, payload) + return response(value) + with patch.object(fleet, "resolve_node", return_value=self.local_node()), \ + patch("framebridge_expanded.runtime.adapter_request", side_effect=adapter) as call: + result = self.execute(payload) + self.assertTrue(result["ok"]) + self.assertEqual(result["target"], "LOCAL") + self.assertEqual(result["node_id"], NODE_ID) + self.assertEqual(result["mcp_job_id"], payload["job_id"]) + self.assertEqual(result["mcp_script_sha256"], payload["sha256"]) + call.assert_called_once() + + def test_remote_admin_exec_preserves_type_through_broker_node(self): + payload = make_payload({"op": "status"}) + calls = [] + def adapter(value): + self.assertEqual(value, payload) + return response(value) + def request(node_id, capability, value, timeout_s): + calls.append((node_id, capability, value, timeout_s)) + req = {"node_id": node_id, "expires_at": time.time() + 30, + "request_id": "request-fixture", "operation": capability, "payload": value} + with patch.object(node, "_context", return_value=(None, None, None, {"node_id": NODE_ID})): + return node.dispatch_request(req, adapter_request=adapter) + with patch.object(fleet, "resolve_node", return_value=self.remote_node()), \ + patch.object(broker, "request", side_effect=request): + result = self.execute(payload) + self.assertTrue(result["ok"]) + self.assertEqual(result["node_id"], NODE_ID) + self.assertEqual(result["target"], "worker-1") + self.assertEqual(len(calls), 1) + self.assertEqual(calls[0][1], "admin.exec") + self.assertEqual(calls[0][2]["request_type"], "screen_rpc") + + def test_old_adapter_is_reported_without_shell_retry(self): + remote = self.remote_node() + legacy = {"ok": False, "adapter_error": "ValueError:UNSUPPORTED_REQUEST_TYPE"} + def execute(**kwargs): + target = kwargs.pop("target") + return fleet.execute_screen_node(target, **kwargs) + client = ScreenClient(execute, image_factory=lambda **kwargs: None) + with patch.object(fleet, "resolve_node", return_value=remote), \ + patch.object(broker, "request", return_value=legacy) as request, \ + patch.object(fleet, "execute_node", side_effect=AssertionError("no legacy fallback")) as generic: + with self.assertRaisesRegex(ScreenError, "SCREEN_RPC_UNSUPPORTED_ON_NODE"): + client.remote("worker-1", {"op": "status"}) + request.assert_called_once() + generic.assert_not_called() + + def test_bad_job_or_hash_response_is_rejected(self): + payload = make_payload() + for field in ["job_id", "command_sha256"]: + with self.subTest(field=field): + result = response(payload) + result[field] = "invalid" + with patch.object(fleet, "resolve_node", return_value=self.remote_node()), \ + patch.object(broker, "request", return_value=result): + final = self.execute(payload) + self.assertFalse(final["ok"]) + self.assertIn("BINDING_INVALID", final["error"]) + + def test_offline_or_malformed_typed_payload_never_routes(self): + payload = make_payload() + for malformed in [dict(payload, script="echo not a screen RPC\n"), + dict(payload, sha256="0" * 64)]: + with self.subTest(malformed=malformed), patch.object(fleet, "resolve_node") as resolve: + result = self.execute(malformed) + self.assertFalse(result["ok"]) + resolve.assert_not_called() + with patch.object(fleet, "resolve_node", return_value=dict(self.remote_node(), operational_transport_ready=False)), \ + patch.object(broker, "request") as request: + result = self.execute(payload) + self.assertEqual(result["error"], "NODE_OFFLINE") + request.assert_not_called() + + def test_generic_execute_remains_without_typed_marker(self): + payload = make_payload({"op": "status"}) + calls = [] + def adapter(value): + calls.append(value) + self.assertNotIn("request_type", value) + return response(value) + with patch.object(fleet, "resolve_node", return_value=self.local_node()), \ + patch("framebridge_expanded.runtime.adapter_request", side_effect=adapter): + result = fleet.execute_node("LOCAL", payload["script"], True, 1, payload["job_id"], payload["sha256"], True) + self.assertTrue(result["ok"]) + self.assertEqual(len(calls), 1) + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_screen_viewer_js.py b/source/tests/test_screen_viewer_js.py new file mode 100644 index 0000000..d7e93ac --- /dev/null +++ b/source/tests/test_screen_viewer_js.py @@ -0,0 +1,85 @@ +"""Run the actual viewer JS under GJS with a small DOM fixture. + +This tests JS state and integrity handling, not browser decoding, CSP or ChatGPT UI. +""" +import base64 +import json +import shutil +import subprocess +import unittest + +from framebridge_expanded.real_screen.screen_presentation import VIEWER_HTML +from test_screen_frontdoor import png + + +@unittest.skipUnless(shutil.which("gjs"), "GJS is not installed on this test host") +class ScreenViewerJavaScriptTests(unittest.TestCase): + def test_original_bytes_recovery_errors_and_inflight_teardown(self): + data = png() + import hashlib + payload = { + "structuredContent": { + "ok": True, "node": "LOCAL", "hostname": "test-fixture", + "result": { + "capture_id": "1" * 32, "sha256": hashlib.sha256(data).hexdigest(), + "bytes": len(data), "width": 100, "height": 40, + "captured_at": "2026-10-24T12:00:00Z", + }, + }, + "_meta": {"framebridgeImage": {"mimeType": "image/png", "data": base64.b64encode(data).decode()}}, + } + script = VIEWER_HTML.split("", 1)[0] + bootstrap = r""" +const GLib=imports.gi.GLib; +const listeners={},elements={}; +class Element { + constructor(){this.hidden=true;this.disabled=true;this.textContent="";this.attrs={};const classes=new Set();this.classList={add:x=>classes.add(x),remove:x=>classes.delete(x),contains:x=>classes.has(x)};} + setAttribute(k,v){this.attrs[k]=v;} removeAttribute(k){delete this.attrs[k];if(k==="src"||k==="href")this[k]="";} + async decode(){const b=GLib.base64_decode(this.src.split(",")[1]);const d=new DataView(b.buffer,b.byteOffset,b.byteLength);this.naturalWidth=d.getUint32(16);this.naturalHeight=d.getUint32(20);} +} +const fakeDocument={getElementById:id=>elements[id]||(elements[id]=new Element()),body:{scrollHeight:640}}; +const fakeWindow={parent:{postMessage:()=>{}},addEventListener:(name,fn)=>{listeners[name]=fn;}}; +globalThis.setTimeout=()=>0; +globalThis.atob=value=>Array.from(GLib.base64_decode(value),x=>String.fromCharCode(x)).join(""); +async function realDigest(name,bytes){if(name!=="SHA-256")throw Error("wrong algorithm");const hex=GLib.compute_checksum_for_data(GLib.ChecksumType.SHA256,bytes);return Uint8Array.from(hex.match(/../g),x=>parseInt(x,16)).buffer;} +globalThis.crypto={subtle:{digest:realDigest}}; +let checks=0; +function check(v,m){if(!v)throw Error(m);checks++;} +""" + cases = r""" +return (async()=>{ + const fixture=PAYLOAD,original="data:image/png;base64,"+fixture._meta.framebridgeImage.data; + await render(fixture); + check(img.src===original,"original bytes changed");check(!img.hidden&&!viewport.hidden,"valid image hidden"); + check(statusEl.textContent.includes("Original PNG verified"),"integrity success missing"); + check(details.textContent.includes(fixture.structuredContent.result.sha256),"hash not displayed"); + check(download.href===original,"download bytes changed"); + native.onclick();check(viewport.classList.contains("native"),"native size unavailable"); + fit.onclick();check(!viewport.classList.contains("native"),"fit unavailable"); + await render({structuredContent:{ok:false,error:"TEST_ERROR"}}); + check(img.hidden&&viewport.hidden,"error did not hide image");check(active===null,"error retained active capture"); + check(!img.src&&!download.href&&!details.textContent,"error retained stale image data"); + await render(fixture);check(!img.hidden&&img.src===original,"same original did not recover after error"); + const bad=JSON.parse(JSON.stringify(fixture));bad.structuredContent.result.sha256="0".repeat(64); + await render(bad);check(img.hidden&&statusEl.textContent.includes("SHA-256 mismatch"),"corrupt digest not rejected"); + await render(fixture);check(!img.hidden,"valid image did not recover after bad digest"); + const slow=JSON.parse(JSON.stringify(fixture));slow.structuredContent.result.capture_id="2".repeat(32); + let finish;crypto.subtle.digest=(...args)=>new Promise(resolve=>{finish=()=>realDigest(...args).then(resolve);}); + const inflight=render(slow);check(typeof finish==="function","digest did not start"); + listeners.message({source:window.parent,data:{jsonrpc:"2.0",method:"ui/notifications/teardown",params:{}}}); + await finish();await inflight; + check(img.hidden&&viewport.hidden&&!img.src,"inflight render revived after teardown"); + check(fit.disabled&&native.disabled&&full.disabled,"teardown left controls enabled"); + return checks; +})(); +""" + executable = bootstrap + "\nconst run = function(window,document,crypto,atob,setTimeout){" + script + cases.replace("PAYLOAD", json.dumps(payload)) + "};\n" + executable += 'const loop=new GLib.MainLoop(null,false);let failed=false;run(fakeWindow,fakeDocument,globalThis.crypto,globalThis.atob,globalThis.setTimeout).then(n=>{print("PASS "+n+" viewer state assertions");loop.quit();}).catch(e=>{failed=true;printerr(String(e.stack||e));loop.quit();});loop.run();if(failed)imports.system.exit(1);' + result = subprocess.run([shutil.which("gjs"), "-c", executable], + capture_output=True, text=True, timeout=8) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("PASS 16 viewer state assertions", result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_tls.py b/source/tests/test_tls.py new file mode 100644 index 0000000..9cc8f24 --- /dev/null +++ b/source/tests/test_tls.py @@ -0,0 +1,141 @@ +"""Real TLS and certificate-bootstrap checks in isolated application profiles.""" +import hashlib +import ipaddress +import os +from pathlib import Path +import socket +import ssl +import stat +import tempfile +import threading +import unittest +from unittest import mock + +from framebridge_expanded import broker, node, tls +from framebridge_expanded.config import default_config, load_config, save_config + +ROOT = Path(__file__).resolve().parents[1] + + +class CertificateTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="tls-test-", dir=ROOT / "qa") + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + + def profile(self, name, role="central"): + state = self.root / name + save_config(state, default_config(role, name)) + return state + + def test_certificate_sans_fingerprint_private_modes_and_config(self): + state = self.profile("cert-central") + result = tls.create_certificates(state, "central.example.test", "192.0.2.10") + cfg = load_config(state) + x509, hashes, serialization, *_ = tls._crypto() + ca = x509.load_pem_x509_certificate(Path(result["ca_public_file"]).read_bytes()) + cert = x509.load_pem_x509_certificate(Path(result["server_certificate_file"]).read_bytes()) + self.assertEqual(result["ca_fingerprint_sha256"], ca.fingerprint(hashes.SHA256()).hex()) + self.assertEqual(result["server_fingerprint_sha256"], cert.fingerprint(hashes.SHA256()).hex()) + san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + self.assertEqual(set(san.get_values_for_type(x509.DNSName)), {"central.example.test", "localhost"}) + self.assertEqual(set(san.get_values_for_type(x509.IPAddress)), { + ipaddress.ip_address("192.0.2.10"), ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("::1")}) + self.assertTrue(ca.extensions.get_extension_for_class(x509.BasicConstraints).value.ca) + self.assertFalse(cert.extensions.get_extension_for_class(x509.BasicConstraints).value.ca) + self.assertEqual(stat.S_IMODE((state / "tls").stat().st_mode), 0o700) + for path in (state / "tls").iterdir(): + expected = 0o644 if path.name == "ca-public.pem" else 0o600 + self.assertEqual(stat.S_IMODE(path.stat().st_mode), expected, path.name) + self.assertEqual(cfg["tls_ca"], result["ca_public_file"]) + self.assertEqual(cfg["broker_url"], "https://192.0.2.10:20878") + self.assertEqual(cfg["broker_control_url"], "https://127.0.0.1:20878") + self.assertEqual(cfg["broker_listen_host"], "127.0.0.1") + self.assertFalse(result["private_key_values_exported"]) + self.assertFalse(result["openai_credentials_created_or_saved"]) + self.assertFalse(any("private.key" in key for key in result)) + + def test_existing_material_is_never_overwritten(self): + state = self.profile("no-overwrite") + tls.create_certificates(state, "central.test", "127.0.0.1") + before = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in (state / "tls").iterdir()} + with self.assertRaises(FileExistsError): + tls.create_certificates(state, "replacement.test", "127.0.0.1") + after = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in (state / "tls").iterdir()} + self.assertEqual(before, after) + + def test_invalid_or_active_profile_never_creates_material(self): + standalone = self.profile("standalone", "standalone") + with self.assertRaises(ValueError): + tls.create_certificates(standalone, "central.test", "127.0.0.1") + state = self.profile("active") + with mock.patch("framebridge_expanded.runtime.status", return_value={"running": True}): + with self.assertRaises(RuntimeError): + tls.create_certificates(state, "central.test", "127.0.0.1") + with self.assertRaises(ValueError): + tls.create_certificates(state, "bad host", "127.0.0.1") + with self.assertRaises(ValueError): + tls.create_certificates(state, "central.test", "0.0.0.0") + self.assertFalse((state / "tls").exists()) + + def test_real_https_node_enrollment_and_wrong_ca_rejection(self): + # Override only with a local, assigned address to exercise an actual LAN route. + address = os.environ.get("FRAMEBRIDGE_TLS_TEST_ADDRESS", "127.0.0.1") + ipaddress.ip_address(address) + with socket.socket() as probe: + probe.bind(("0.0.0.0", 0)) + port = probe.getsockname()[1] + central = self.profile("https-central") + cfg = load_config(central) + cfg["ports"]["broker"] = port + if port in (cfg["ports"]["mcp"], cfg["ports"]["hub"]): + self.skipTest("Ephemeral broker port overlaps a default service port.") + save_config(central, cfg) + public = tls.create_certificates(central, "central.test", address, listen_host="0.0.0.0") + server = broker.make_server("0.0.0.0", port, central) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + joining = self.profile("https-node", "node") + nc = load_config(joining) + nc.update(broker_url=public["broker_url"], tls_ca=public["ca_public_file"]) + save_config(joining, nc) + enrolled = node.enroll(public["broker_url"], "https-node", joining) + broker.configure(state_dir=central) + pending = broker.list_pending() + entry = next(p for p in pending if p["node_id"] == enrolled["node_id"]) + self.assertEqual(entry["fingerprint"], enrolled["fingerprint"]) + approved = broker.approve(entry["request_id"], enrolled["node_id"], central) + polled = node.poll_enrollment(joining) + self.assertEqual(polled["state"], "paired") + self.assertFalse(polled.get("broker_confirm_required", False)) + self.assertTrue(any(p["node_id"] == enrolled["node_id"] for p in broker.list_confirmed_nodes())) + + wrong = self.profile("wrong-authority") + wrong_public = tls.create_certificates(wrong, "other.test", address) + rejected = self.profile("wrong-ca-node", "node") + rc = load_config(rejected) + rc.update(broker_url=public["broker_url"], tls_ca=wrong_public["ca_public_file"]) + save_config(rejected, rc) + before = broker.list_pending() + with self.assertRaises(Exception) as failure: + node.enroll(public["broker_url"], "wrong-ca-node", rejected) + # Check the verification error, rather than accepting any transport failure. + chain = [] + error = failure.exception + for _ in range(6): + if error is None: + break + chain.append(str(error)) + error = error.__cause__ or error.__context__ + self.assertIn("CERTIFICATE_VERIFY_FAILED", " ".join(chain)) + self.assertEqual(before, broker.list_pending()) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + self.assertFalse(thread.is_alive()) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_tty_mcp.py b/source/tests/test_tty_mcp.py new file mode 100644 index 0000000..d6cd934 --- /dev/null +++ b/source/tests/test_tty_mcp.py @@ -0,0 +1,297 @@ +"""Real MCP transport tests with real local Bash consoles and a bounded executor boundary. + +The TEST_NODE executor runs on this QA host in another private profile; this +tests remote RPC framing, not a second machine. Actual multi-node VM coverage is separate. +""" +import asyncio +import json +import os +from pathlib import Path +import shlex +import socket +import subprocess +import sys +import tempfile +import time +import unittest + +WORK = Path(__file__).resolve().parents[1] +VENDOR = WORK / "sources/original-geranas/FrameBridge-Fleet-Hub/vendor" +sys.path.insert(0, str(WORK / "expanded")) +sys.path.insert(0, str(VENDOR)) +os.environ["PYTHONPATH"] = str(WORK / "expanded") + os.pathsep + str(VENDOR) +os.environ["FRAMEBRIDGE_EXPANDED_PREFIX"] = str(WORK) +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from framebridge_expanded import tty +from framebridge_expanded.tty_mcp import TOOL_NAMES, _remote_request, register_tools +from unittest import mock +from framebridge_expanded.real_screen.screen_frontdoor import ScreenClient + + +SERVER = r''' +from pathlib import Path +import json,os,subprocess +from mcp.server.fastmcp import FastMCP +from framebridge_expanded.tty_mcp import register_tools +port=int(os.environ["QA_TTY_MCP_PORT"]) +mcp=FastMCP("Real CUA-TTY QA",host="127.0.0.1",port=port,streamable_http_path="/mcp") +def execute(script,authorized,timeout_s,job_id,sha256,include_stdout,target): + if authorized is not True or target!="TEST_NODE": + return {"ok":False,"error":"QA_TARGET_INVALID"} + env=os.environ.copy() + env["FRAMEBRIDGE_EXPANDED_STATE"]=env["QA_TTY_REMOTE_STATE"] + p=subprocess.run(["/bin/bash","--noprofile","--norc","-c",script],env=env, + stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout_s) + with open(os.environ["QA_TTY_CALL_LOG"],"a") as log: + log.write(json.dumps({"read":" framebridge_expanded.tty read " in (" "+script+" "), + "stdout_bytes":len(p.stdout.encode())})+"\n") + return {"ok":True,"stdout":p.stdout[:4096],"stdout_truncated":len(p.stdout)>4096, + "exit_code":str(p.returncode)} +class ScreenFixture: + def call(self, op, target, authorized, **arguments): + return {"ok":True,"test_fixture":"screen-delegation-only", + "op":op,"target":target,"arguments":arguments} +register_tools(mcp,execute,screen_client=ScreenFixture()) +mcp.run(transport="streamable-http") +''' + + +class RealMcpTtyTests(unittest.IsolatedAsyncioTestCase): + @classmethod + def setUpClass(cls): + cls.root = Path(tempfile.mkdtemp(prefix="tty-mcp-", dir=WORK / "qa")) + cls.local = cls.root / "local" + cls.remote = cls.root / "remote" + cls.local.mkdir(mode=0o700) + cls.remote.mkdir(mode=0o700) + assert tty.start(cls.local).get("ok") + assert tty.start(cls.remote).get("ok") + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + cls.port = sock.getsockname()[1] + child = cls.root / "server.py" + child.write_text(SERVER) + env = os.environ.copy() + env["PYTHONPATH"] = str(WORK / "expanded") + os.pathsep + str(VENDOR) + env.pop("DISPLAY", None) + env.pop("XDG_SESSION_TYPE", None) + env["FRAMEBRIDGE_EXPANDED_STATE"] = str(cls.local) + env["QA_TTY_REMOTE_STATE"] = str(cls.remote) + env["QA_TTY_MCP_PORT"] = str(cls.port) + cls.call_log = cls.root / "executor-calls.jsonl" + env["QA_TTY_CALL_LOG"] = str(cls.call_log) + log = (cls.root / "mcp-server.log").open("ab") + cls.server = subprocess.Popen([sys.executable, str(child)], env=env, + stdin=subprocess.DEVNULL, stdout=log, + stderr=subprocess.STDOUT) + log.close() + ready = False + deadline = time.monotonic() + 8 + while cls.server.poll() is None and time.monotonic() < deadline: + with socket.socket() as sock: + if sock.connect_ex(("127.0.0.1", cls.port)) == 0: + ready = True + break + time.sleep(0.05) + if not ready: + tty.stop(cls.local) + tty.stop(cls.remote) + cls.server.wait(timeout=2) + raise AssertionError("Private MCP server failed to start") + + @classmethod + def tearDownClass(cls): + cls.server.terminate() + try: + cls.server.wait(timeout=3) + except subprocess.TimeoutExpired: + cls.server.kill() + cls.server.wait(timeout=2) + assert tty.stop(cls.local).get("ok") + assert tty.stop(cls.remote).get("ok") + + async def call(self, session, name, args): + result = await session.call_tool(name, args) + self.assertFalse(result.isError) + value = getattr(result, "structuredContent", None) + if not isinstance(value, dict): + value = next(json.loads(c.text) for c in result.content if c.type == "text") + return value + + def remote_request(self, payload): + def execute(script, authorized, timeout_s, job_id, sha256, include_stdout, target): + if authorized is not True or target != "TEST_NODE": + return {"ok": False, "error": "QA_TARGET_INVALID"} + env = os.environ.copy() + env["FRAMEBRIDGE_EXPANDED_STATE"] = str(self.remote) + process = subprocess.run(["/bin/bash", "--noprofile", "--norc", "-c", script], + env=env, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=timeout_s) + with self.call_log.open("a") as log: + log.write(json.dumps({"read": " framebridge_expanded.tty read " in (" " + script + " "), + "stdout_bytes": len(process.stdout.encode())}) + "\n") + return {"ok": True, "stdout": process.stdout[:4096], + "stdout_truncated": len(process.stdout) > 4096, "exit_code": str(process.returncode)} + return _remote_request(execute, "TEST_NODE", payload) + + async def test_lifecycle_over_mcp_and_persistent_bash_over_raw_rpc(self): + async with streamablehttp_client("http://127.0.0.1:" + str(self.port) + "/mcp") as streams: + async with ClientSession(streams[0], streams[1]) as session: + await session.initialize() + listing = await session.list_tools() + self.assertEqual(sorted(t.name for t in listing.tools), sorted(TOOL_NAMES)) + denied = await self.call(session, "cua_tty_open", {"authorized": False}) + self.assertEqual(denied["error"], "AUTHORIZED_TRUE_REQUIRED") + status = await self.call(session, "cua_tty_status", {"authorized": True}) + self.assertTrue(status["ok"]) + opened = await self.call(session, "cua_tty_open", {"authorized": True}) + self.assertTrue(opened["ok"]) + sid = opened["result"]["session_id"] + try: + marker = self.local / "mcp-local-marker.txt" + command = "printf local_mcp_real > " + shlex.quote(str(marker)) + typed = tty.request(self.local, {"op": "text.type", "authorized": True, + "session_id": sid, "text": command}) + self.assertTrue(typed["ok"]) + keyed = tty.request(self.local, {"op": "key.send", "authorized": True, + "session_id": sid, "key": "ENTER"}) + self.assertTrue(keyed["ok"]) + deadline = time.monotonic() + 3 + while not marker.exists() and time.monotonic() < deadline: + await asyncio.sleep(0.05) + self.assertEqual(marker.read_text(), "local_mcp_real") + resized = await self.call(session, "cua_tty_resize", + {"authorized": True, "session_id": sid, "rows": 33, "cols": 120}) + self.assertTrue(resized["ok"]) + observed = await self.call(session, "cua_tty_observe", + {"authorized": True, "session_id": sid}) + self.assertEqual(observed["test_fixture"], "screen-delegation-only") + self.assertEqual(observed["op"], "observe") + self.assertEqual(observed["arguments"]["session_id"], sid) + transcript = tty.request(self.local, {"op": "observe", "authorized": True, "session_id": sid}) + self.assertTrue(transcript["result"]["real_terminal"]) + self.assertEqual(transcript["result"]["screen"]["rows"], 33) + finally: + closed = await self.call(session, "cua_tty_close", + {"authorized": True, "session_id": sid}) + self.assertTrue(closed["result"]["cleanup_verified"]) + + async def test_remote_executor_framing_preserves_screen_larger_than_stdout_limit(self): + async with streamablehttp_client("http://127.0.0.1:" + str(self.port) + "/mcp") as streams: + async with ClientSession(streams[0], streams[1]) as session: + await session.initialize() + opened = await self.call(session, "cua_tty_open", + {"authorized": True, "target": "TEST_NODE", "rows": 32, "cols": 160}) + self.assertTrue(opened["ok"], opened) + sid = opened["result"]["session_id"] + try: + marker = self.remote / "random-screen-done.txt" + code = ("import random,string; from pathlib import Path; r=random.Random(23); " + "[print(''.join(r.choice(string.ascii_letters+string.digits) for _ in range(150))) " + "for _ in range(28)]; Path(" + repr(str(marker)) + ").write_text('done')") + command = "python3 -c " + shlex.quote(code) + self.assertTrue(self.remote_request( + {"op": "text.type", "authorized": True, "session_id": sid, "text": command})["ok"]) + self.assertTrue(self.remote_request( + {"op": "key.send", "authorized": True, "session_id": sid, "key": "ENTER"})["ok"]) + deadline = time.monotonic() + 3 + while not marker.exists() and time.monotonic() < deadline: + await asyncio.sleep(0.05) + self.assertEqual(marker.read_text(), "done") + # Raw observe is a terminal text transcript. Physical MCP observe + # is separately delegated to ScreenClient and never used here. + value = self.remote_request({"op": "observe", "authorized": True, "session_id": sid}) + self.assertTrue(value["ok"], value) + self.assertTrue(value["result"]["real_terminal"]) + self.assertGreater(len(json.dumps(value)), 4096) + records = [json.loads(line) for line in self.call_log.read_text().splitlines()] + self.assertTrue(any(r["read"] for r in records)) + self.assertTrue(all(r["stdout_bytes"] < 4096 for r in records)) + finally: + closed = await self.call(session, "cua_tty_close", + {"authorized": True, "target": "TEST_NODE", "session_id": sid}) + self.assertTrue(closed["result"]["cleanup_verified"]) + + +class FixtureMcp: + def __init__(self): + self.tools = {} + + def tool(self, **_kwargs): + def register(function): + self.tools[function.__name__] = function + return function + return register + +class McpScreenBindingTests(unittest.TestCase): + SID = "a" * 32 + CAPTURE = "b" * 32 + + def tools(self, error="SESSION_NOT_VISIBLE", *, wrong_target=False): + calls = [] + def execute(**kwargs): + calls.append(kwargs) + return {"ok": True, "exit_code": "0", "stdout_truncated": False, + "target": "different-node" if wrong_target else kwargs["target"], + "node_id": "12345678-1234-1234-1234-123456789abc", + "mcp_job_id": kwargs["job_id"], "mcp_script_sha256": kwargs["sha256"], + "stdout": json.dumps({"ok": False, "error": error})} + client = ScreenClient(execute, image_factory=lambda **kwargs: kwargs, resolve_node=lambda node: node) + mcp = FixtureMcp() + register_tools(mcp, execute, screen_client=client) + return mcp.tools, calls + + def test_seven_existing_names_remain_registered(self): + tools, _calls = self.tools() + self.assertEqual(set(tools), set(TOOL_NAMES)) + + def test_missing_capture_rejected_before_any_backend_execution(self): + tools, calls = self.tools() + with mock.patch.object(tty, "request") as raw: + value = tools["cua_tty_key_send"](self.SID, "ENTER", True, "NodeCase") + self.assertEqual(value["error"], "SCREEN_CAPTURE_ID_REQUIRED") + self.assertEqual(calls, []) + raw.assert_not_called() + + def test_unapproved_screen_observation_does_not_reach_backend(self): + tools, calls = self.tools() + value = tools["cua_tty_observe"](self.SID, False, "NodeCase") + self.assertEqual(value["error"], "AUTHORIZED_TRUE_REQUIRED") + self.assertEqual(calls, []) + + def test_hidden_session_error_has_no_raw_pty_fallback(self): + tools, calls = self.tools() + with mock.patch.object(tty, "request") as raw: + value = tools["cua_tty_text_type"](self.SID, "not executed", True, "NodeCase", self.CAPTURE) + self.assertEqual(value["error"], "SESSION_NOT_VISIBLE") + self.assertEqual(calls[0]["target"], "NodeCase") + raw.assert_not_called() + + def test_mismatched_executor_target_cannot_satisfy_screen_request(self): + tools, _calls = self.tools(wrong_target=True) + value = tools["cua_tty_key_send"](self.SID, "ENTER", True, "NodeCase", self.CAPTURE) + self.assertEqual(value["error"], "SCREEN_EXEC_BINDING_INVALID") + + def test_graphical_input_rejection_is_preserved_without_hidden_fallback(self): + tools, _calls = self.tools(error="USE_GRAPHICAL_CUA") + with mock.patch.object(tty, "request") as raw: + value = tools["cua_tty_key_send"](self.SID, "ENTER", True, "NodeCase", self.CAPTURE) + self.assertEqual(value["error"], "USE_GRAPHICAL_CUA") + raw.assert_not_called() + + def test_observe_delegation_preserves_requested_session_and_node(self): + record = [] + class ScreenFixture: + def call(self, *args, **kwargs): + record.append((args, kwargs)) + return {"test_fixture": "delegation-only"} + mcp = FixtureMcp() + register_tools(mcp, lambda **_kw: self.fail("raw execute is not the observe path"), + screen_client=ScreenFixture()) + value = mcp.tools["cua_tty_observe"](self.SID, True, "NodeCase") + self.assertEqual(value, {"test_fixture": "delegation-only"}) + self.assertEqual(record, [(("observe", "NodeCase", True), {"session_id": self.SID})]) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_tty_runtime.py b/source/tests/test_tty_runtime.py new file mode 100644 index 0000000..4486186 --- /dev/null +++ b/source/tests/test_tty_runtime.py @@ -0,0 +1,151 @@ +"""Real headless Bash/tmux integration tests; only test-owned profiles are closed.""" +import json +import os +from pathlib import Path +import shlex +import stat +import sys +import tempfile +import time +import unittest + +WORK = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(WORK / "expanded")) +os.environ["PYTHONPATH"] = str(WORK / "expanded") +os.environ["FRAMEBRIDGE_EXPANDED_PREFIX"] = str(WORK) +from framebridge_expanded import tty + + +class RealTtyTests(unittest.TestCase): + def setUp(self): + self.state = Path(tempfile.mkdtemp(prefix="tty-test-", dir=WORK / "qa")) + self.sessions = [] + self.previous_display = os.environ.pop("DISPLAY", None) + self.previous_x11 = os.environ.pop("XDG_SESSION_TYPE", None) + self.assertTrue(tty.start(self.state).get("ok")) + + def tearDown(self): + for sid in self.sessions: + tty.request(self.state, {"op": "close", "authorized": True, "session_id": sid}) + self.assertTrue(tty.stop(self.state).get("ok")) + self.assertFalse((tty.runtime_path(self.state) / "agent.sock").exists()) + if self.previous_display is not None: + os.environ["DISPLAY"] = self.previous_display + if self.previous_x11 is not None: + os.environ["XDG_SESSION_TYPE"] = self.previous_x11 + + def call(self, op, **args): + return tty.request(self.state, {"op": op, "authorized": True, **args}) + + def open(self, **args): + value = self.call("open", **args) + self.assertTrue(value.get("ok"), value) + self.sessions.append(value["result"]["session_id"]) + return value["result"] + + def enter(self, sid, text): + self.assertTrue(self.call("text.type", session_id=sid, text=text).get("ok")) + self.assertTrue(self.call("key.send", session_id=sid, key="ENTER").get("ok")) + + def await_file(self, path): + deadline = time.monotonic() + 3 + while not path.exists() and time.monotonic() < deadline: + time.sleep(0.05) + self.assertTrue(path.is_file(), str(path)) + return path.read_text() + + def test_real_persistent_bash_and_unicode_without_x11(self): + self.assertNotIn("DISPLAY", os.environ) + opened = self.open(rows=30, cols=100) + self.assertTrue(opened["real_terminal"]) + self.assertEqual(opened["backend"], "tmux") + self.assertTrue(Path(opened["tty"]).exists()) + self.assertGreater(opened["pid"], 1) + sid = opened["session_id"] + directory = self.state / "working-directory" + directory.mkdir() + self.enter(sid, "export FRAMEBRIDGE_TTY_VALUE=" + shlex.quote("persistent ñ 漢字") + + "; cd " + shlex.quote(str(directory))) + marker = self.state / "unicode-and-state.txt" + self.enter(sid, 'printf %s "$FRAMEBRIDGE_TTY_VALUE" > ' + shlex.quote(str(marker))) + self.assertEqual(self.await_file(marker), "persistent ñ 漢字") + observed = self.call("observe", session_id=sid) + self.assertTrue(observed["ok"]) + self.assertEqual(observed["result"]["pid"], opened["pid"]) + self.assertEqual(observed["result"]["cwd"], str(directory)) + self.assertGreaterEqual(observed["result"]["sequence"], 4) + + def test_control_c_interrupts_a_real_foreground_process(self): + sid = self.open()["session_id"] + self.enter(sid, "sleep 30") + deadline = time.monotonic() + 2 + sleeping = False + while time.monotonic() < deadline: + value = self.call("observe", session_id=sid) + if value["result"]["current_command"] == "sleep": + sleeping = True + break + time.sleep(0.05) + self.assertTrue(sleeping) + self.assertTrue(self.call("key.send", session_id=sid, key="CTRL_C")["ok"]) + marker = self.state / "interrupted.txt" + self.enter(sid, "printf interrupted > " + shlex.quote(str(marker))) + self.assertEqual(self.await_file(marker), "interrupted") + + def test_real_resize_can_exceed_the_old_machine_specific_bounds(self): + sid = self.open()["session_id"] + self.assertTrue(self.call("resize", session_id=sid, rows=110, cols=240)["ok"]) + observed = self.call("observe", session_id=sid)["result"] + self.assertEqual((observed["screen"]["rows"], observed["screen"]["cols"]), (110, 240)) + + def test_authorization_and_socket_identity_are_explicit(self): + before = self.call("status")["result"]["active_sessions"] + denied = tty.request(self.state, {"op": "open", "authorized": False}) + self.assertEqual(denied.get("error"), "AUTHORIZED_TRUE_REQUIRED") + denied = tty.request(self.state, {"op": "open", "authorized": 1}) + self.assertEqual(denied.get("error"), "AUTHORIZED_TRUE_REQUIRED") + self.assertEqual(self.call("status")["result"]["active_sessions"], before) + path = tty.runtime_path(self.state) / "agent.sock" + st = path.stat() + self.assertTrue(stat.S_ISSOCK(st.st_mode)) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o600) + self.assertEqual(st.st_uid, os.geteuid()) + status = self.call("status")["result"] + self.assertEqual(status["input_policy"], "same-uid-authorized") + self.assertNotIn("owner_device_connected", status) + self.assertNotIn("owner_signature_required", status) + + def test_errors_do_not_expose_arbitrary_payloads_or_paths(self): + self.assertEqual(self.call("open", program="demo-readonly").get("error"), + "PROGRAM_NOT_SUPPORTED") + self.assertEqual(self.call("observe", session_id="../../outside").get("error"), + "SESSION_ID_INVALID") + sid = self.open()["session_id"] + self.assertEqual(self.call("key.send", session_id=sid, key="INVALID_PRIVATE_TEXT").get("error"), + "KEY_NOT_ALLOWLISTED") + closed = self.call("close", session_id=sid) + self.assertTrue(closed["result"]["cleanup_verified"]) + self.sessions.remove(sid) + self.assertEqual(self.call("observe", session_id=sid).get("error"), "SESSION_NOT_FOUND") + + def test_configurable_session_limit_replaces_the_old_constructor_cap(self): + self.assertTrue(tty.stop(self.state)["ok"]) + (self.state / "tty.json").write_text(json.dumps({"limits": {"max_sessions": 5}})) + self.assertTrue(tty.start(self.state)["ok"]) + for _ in range(5): + self.open(rows=10, cols=40) + self.assertEqual(self.call("status")["result"]["active_sessions"], 5) + self.assertEqual(self.call("open").get("error"), "SESSION_LIMIT") + + def test_configured_input_limit_is_reported_and_enforced(self): + self.assertTrue(tty.stop(self.state)["ok"]) + (self.state / "tty.json").write_text(json.dumps({"limits": {"max_text_bytes": 100}})) + self.assertTrue(tty.start(self.state)["ok"]) + sid = self.open()["session_id"] + self.assertEqual(self.call("status")["result"]["limits"]["max_text_bytes"], 100) + self.assertEqual(self.call("text.type", session_id=sid, text="x" * 101).get("error"), + "TEXT_INVALID_OR_TOO_LARGE") + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_tty_sudo.py b/source/tests/test_tty_sudo.py new file mode 100644 index 0000000..d2c7891 --- /dev/null +++ b/source/tests/test_tty_sudo.py @@ -0,0 +1,372 @@ +"""Owned transient-unit tests; never invokes sudo or changes system policy.""" +import os +from pathlib import Path +import shutil +import sys +import tempfile +import time +import unittest +from unittest import mock +import uuid + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "expanded")) +os.environ["FRAMEBRIDGE_EXPANDED_PREFIX"] = str(ROOT) +from framebridge_expanded import tty_sudo_control as control + + +class SudoControlTests(unittest.TestCase): + def setUp(self): + runtime = Path("/run/user") / str(os.getuid()) + self.temp = tempfile.TemporaryDirectory(prefix="fb-sudo-qa-", dir=runtime) + self.base = Path(self.temp.name) + self.controller = control.SudoConsoleController(self.base, timeout=5) + self.records = [] + self.addCleanup(self.cleanup) + + def cleanup(self): + for record in reversed(self.records): + try: + self.controller.close(record) + except control.SudoControlError as error: + if error.code not in {"TTY_SUDO_BACKEND_FAILED", "TTY_SUDO_PROCESS_NOT_FOUND", + "TTY_SUDO_CONSOLE_EXITED", "TTY_SUDO_UNIT_NOT_RUNNING"}: + raise + self.temp.cleanup() + + def launch(self, **kwargs): + record = self.controller.launch(uuid.uuid4().hex, **kwargs) + self.records.append(record) + return record + + def wait(self, record, predicate, seconds=2): + deadline = time.monotonic() + seconds + while time.monotonic() < deadline: + value = self.controller.status(record) + if predicate(value): + return value + time.sleep(0.03) + self.fail("Expected controlled console state was not reached.") + + def send(self, record, command): + self.controller._tmux(record, "send-keys", "-t", record["pane"], "-l", command) + self.controller._tmux(record, "send-keys", "-t", record["pane"], "Enter") + + def test_roundtrip_real_nnp_and_recreation_preserves_cwd_size(self): + cwd = self.base / "directory" + cwd.mkdir() + off = self.launch(cwd=cwd, rows=31, cols=97) + self.assertEqual((off["no_new_privs"], off["server_no_new_privs"]), (1, 1)) + self.assertTrue(off["prompt_verified"]) + on = self.controller.set_mode(off, "on") + self.assertEqual((on["no_new_privs"], on["server_no_new_privs"]), (0, 0)) + self.assertNotEqual(on["pid"], off["pid"]) + self.assertEqual((on["cwd"], on["rows"], on["cols"]), (str(cwd), 31, 97)) + self.assertIsNone(control._proc(off["pid"])) + blocked = self.controller.set_mode(on, "off") + self.assertEqual((blocked["no_new_privs"], blocked["server_no_new_privs"]), (1, 1)) + self.assertNotEqual(blocked["pid"], on["pid"]) + self.assertFalse(blocked["sudo_authorization_granted"]) + self.assertEqual(blocked["sudo_authentication"], "system_policy") + + def test_same_mode_is_idempotent_for_managed_console(self): + record = self.launch() + same = self.controller.set_mode(record, "off") + self.assertFalse(same["recreated"]) + self.assertEqual(record["pid"], same["pid"]) + + def test_builtin_read_is_busy_without_a_child_process(self): + record = self.launch() + self.send(record, "read -r __fb_public_test") + busy = self.wait(record, lambda v: v["busy"] and not v["prompt_verified"]) + self.assertFalse(busy["busy_pids"]) + with self.assertRaises(control.SudoControlError) as error: + self.controller.set_mode(record, "on") + self.assertEqual(error.exception.code, "TTY_SUDO_PROMPT_UNVERIFIED") + self.controller._tmux(record, "send-keys", "-t", record["pane"], "C-c") + self.wait(record, lambda v: not v["busy"]) + + def test_background_job_blocks_transition(self): + record = self.launch() + self.send(record, "sleep 30 &") + self.wait(record, lambda v: bool(v["busy_pids"])) + with self.assertRaises(control.SudoControlError) as error: + self.controller.set_mode(record, "on") + self.assertEqual(error.exception.code, "TTY_SUDO_CONSOLE_BUSY") + + def test_reparented_setsid_disowned_job_is_found_by_cgroup(self): + record = self.launch() + self.send(record, "setsid -f /bin/sleep 30 /dev/null 2>&1 & disown") + busy = self.wait(record, lambda v: bool(v["busy_pids"])) + processes = control._processes() + descendants = control._descendants(processes, record["pid"]) + self.assertTrue(set(busy["busy_pids"]) - descendants) + with self.assertRaises(control.SudoControlError): + self.controller.set_mode(record, "on") + + def test_stopped_job_blocks_transition(self): + record = self.launch() + self.send(record, "sleep 30") + self.wait(record, lambda v: bool(v["busy_pids"])) + self.controller._tmux(record, "send-keys", "-t", record["pane"], "C-z") + busy = self.wait(record, lambda v: any( + control._proc(p) and control._proc(p)["state"] == "T" for p in v["busy_pids"])) + with self.assertRaises(control.SudoControlError): + self.controller.set_mode(record, "on") + self.assertTrue(busy["busy"]) + + def test_candidate_is_rolled_back_if_original_becomes_busy(self): + record = self.launch() + original = self.controller._idle + calls = 0 + def idle(value): + nonlocal calls + calls += 1 + if calls == 2: + raise control.SudoControlError("TTY_SUDO_CONSOLE_BUSY") + return original(value) + with mock.patch.object(self.controller, "_idle", side_effect=idle): + with self.assertRaises(control.SudoControlError): + self.controller.set_mode(record, "on") + current = self.controller.status(record) + self.assertEqual((current["pid"], current["mode"]), (record["pid"], "off")) + self.assertEqual(len(list((self.base / "sudo").glob("*.sock"))), 1) + + def test_postkill_cleanup_failure_keeps_verified_replacement(self): + record = self.launch() + original = self.controller._close + def close(value, require_idle=False): + result = original(value, require_idle=require_idle) + if value["pid"] == record["pid"]: + raise control.SudoControlError("TTY_SUDO_BACKEND_TIMEOUT") + return result + with mock.patch.object(self.controller, "_close", side_effect=close): + replacement = self.controller.set_mode(record, "on") + self.assertTrue(replacement["cleanup_pending"]) + self.assertFalse(replacement["transition_complete"]) + self.assertEqual(replacement["cleanup_error"], "TTY_SUDO_BACKEND_TIMEOUT") + self.assertIsNone(control._proc(record["pid"])) + self.assertIsNotNone(control._proc(replacement["pid"])) + self.assertEqual(replacement["no_new_privs"], 0) + with mock.patch.object(self.controller, "_old_cleanup_complete", return_value=False): + state = self.controller.status(record) + self.assertTrue(state["cleanup_pending"]) + self.assertEqual(state["pid"], replacement["pid"]) + with self.assertRaises(control.SudoControlError) as error: + self.controller.set_mode(record, "off") + self.assertEqual(error.exception.code, "TTY_SUDO_TRANSITION_CLEANUP_PENDING") + with self.assertRaises(control.SudoControlError): + self.controller.close(record) + state = self.controller.status(record) + self.assertFalse(state["cleanup_pending"]) + self.assertTrue(state["transition_complete"]) + self.assertNotIn("pending_old_record", state) + + def test_legacy_status_does_not_fake_prompt_verification(self): + record = self.launch() + legacy = {key: record[key] for key in ("name", "pane")} + legacy.update(session_id=uuid.uuid4().hex, legacy_socket=record["tmux_socket"]) + state = self.controller.status(legacy) + self.assertEqual(state["no_new_privs"], 1) + self.assertFalse(state["prompt_verified"]) + with self.assertRaises(control.SudoControlError) as error: + self.controller.set_mode(legacy, "on") + self.assertEqual(error.exception.code, "TTY_SUDO_PROMPT_UNVERIFIED") + + def test_only_own_unit_names_are_accepted(self): + with self.assertRaises(control.SudoControlError) as error: + self.controller._unit("unrelated-existing.service") + self.assertEqual(error.exception.code, "TTY_SUDO_UNIT_NOT_OWNED") + + def test_pid_birth_change_fails_closed(self): + record = self.launch() + self.controller._records[record["session_id"]]["birth"] = "not-the-birth" + try: + with self.assertRaises(control.SudoControlError) as error: + self.controller.status(record) + self.assertEqual(error.exception.code, "TTY_SUDO_PROCESS_IDENTITY_CHANGED") + finally: + self.controller._records[record["session_id"]]["birth"] = record["birth"] + + def test_elevated_process_metadata_refuses_change_and_close(self): + record = self.launch() + original = control._processes + def processes(): + data = original() + data[987654321] = {"pid": 987654321, "ppid": record["pid"], + "sid": 987654321, "tty": 0, "uids": (0, 0, 0, 0)} + return data + with mock.patch.object(control, "_processes", side_effect=processes): + with self.assertRaises(control.SudoControlError) as error: + self.controller.set_mode(record, "on") + self.assertEqual(error.exception.code, "TTY_SUDO_ELEVATED_PROCESS_PRESENT") + with self.assertRaises(control.SudoControlError): + self.controller.close(record) + + def test_marker_contains_only_generation_and_state(self): + record = self.launch() + self.send(record, "printf '%s\\n' PUBLIC_INPUT_MARKER") + self.wait(record, lambda v: not v["busy"]) + value = Path(record["prompt_marker"]).read_text() + self.assertEqual(value, record["generation"] + ":idle\n") + rc = Path(record["rcfile"]).read_text() + self.assertNotIn("PUBLIC_INPUT_MARKER", rc) + self.assertNotIn("BASH_COMMAND", rc) + self.assertEqual(Path(record["rcfile"]).stat().st_mode & 0o777, 0o600) + + def test_exit_still_cleans_up_owned_server_and_scope(self): + record = self.launch() + self.send(record, "exit") + deadline = time.monotonic() + 2 + while time.monotonic() < deadline: + process = control._proc(record["pid"]) + if process is None or process["state"] in {"Z", "X"}: + break + time.sleep(0.03) + result = self.controller.close(record) + self.assertTrue(result["closed"]) + self.assertFalse(Path(record["tmux_socket"]).exists()) + self.assertIsNone(control._proc(record["server_pid"])) + self.records.remove(record) + + def test_invalid_mode_does_not_launch(self): + with self.assertRaises(control.SudoControlError): + self.controller.launch(uuid.uuid4().hex, mode="automatic-root") + self.assertFalse(list((self.base / "sudo").glob("*.sock"))) + + + +class PortableSudoLimitsTests(SudoControlTests): + # Reuse lifecycle helpers, but do not repeat the inherited test methods. + def test_expanded_large_geometry_survives_mode_change(self): + record = self.launch(rows=500, cols=1000) + self.assertEqual((record["rows"], record["cols"]), (500, 1000)) + enabled = self.controller.set_mode(record, "on") + self.assertEqual((enabled["rows"], enabled["cols"], enabled["no_new_privs"]), (500, 1000, 0)) + + def test_profile_limits_reject_before_starting_a_unit(self): + controller = control.SudoConsoleController( + self.base / "profile", min_rows=3, max_rows=8, min_cols=11, max_cols=30) + with mock.patch.object(controller, "_run", side_effect=AssertionError("unit must not start")): + for rows, cols in [(2, 20), (9, 20), (4, 10), (4, 31), (True, 20)]: + with self.assertRaises(control.SudoControlError) as error: + controller.launch(uuid.uuid4().hex, rows=rows, cols=cols) + self.assertEqual(error.exception.code, "TTY_SUDO_SIZE_INVALID") + + def test_actual_nnp_mismatch_rolls_back_candidate(self): + with mock.patch.object(self.controller, "_inspect", + return_value={"no_new_privs": 0, "busy": False}): + with self.assertRaises(control.SudoControlError) as error: + self.controller.launch(uuid.uuid4().hex, mode="off") + self.assertEqual(error.exception.code, "TTY_SUDO_PRIVILEGE_STATE_MISMATCH") + self.assertFalse(list((self.base / "sudo").glob("*.sock"))) + self.assertFalse(self.controller._records) + + def test_profile_runtime_isolation(self): + other = control.SudoConsoleController(self.base / "other") + self.assertNotEqual(other.base, self.controller.base) + self.assertNotEqual(other.prefix, self.controller.prefix) + + + def test_private_profile_bus_does_not_redirect_user_manager(self): + private = self.base / "private-runtime" + private.mkdir(mode=0o700) + with mock.patch.dict(os.environ, { + "XDG_RUNTIME_DIR": str(private), + "DBUS_SESSION_BUS_ADDRESS": "unix:path=" + str(private / "not-the-user-manager")}): + record = self.launch(mode="off") + self.assertEqual(record["no_new_privs"], 1) + self.assertEqual(record["server_no_new_privs"], 1) + self.assertEqual(self.controller.user_runtime, Path("/run/user") / str(os.getuid())) + + + + def test_profile_graphical_context_is_explicitly_forwarded_to_unit(self): + import shlex + private = self.base / "profile-desktop" + private.mkdir(mode=0o700) + expected = { + "DISPLAY": ":193", + "XAUTHORITY": str(private / "authority-not-used"), + "DBUS_SESSION_BUS_ADDRESS": "unix:path=" + str(private / "private-bus-not-used"), + "XDG_RUNTIME_DIR": str(private), + "LD_LIBRARY_PATH": str(private / "libraries"), + "LIBGL_DRIVERS_PATH": str(private / "drivers"), + "GBM_BACKENDS_PATH": str(private / "gbm"), + "__EGL_VENDOR_LIBRARY_FILENAMES": str(private / "egl-not-used.json"), + "__GLX_VENDOR_LIBRARY_NAME": "mesa", + "GALLIUM_DRIVER": "llvmpipe", + "MESA_LOADER_DRIVER_OVERRIDE": "zink", + "VK_DRIVER_FILES": str(private / "vk-not-used.json"), + "VK_ICD_FILENAMES": str(private / "vk-compat-not-used.json"), + "DRI_PRIME": "0", + } + forbidden = "FRAMEBRIDGE_TEST_NOT_FORWARDED" + with mock.patch.dict(os.environ, dict(expected, **{forbidden: "MUST_NOT_FORWARD"})): + controller = control.SudoConsoleController(self.base / "graphical-controller") + record = controller.launch(uuid.uuid4().hex, mode="off") + self.addCleanup(controller.close, record) + self.assertEqual(record["no_new_privs"], 1) + self.assertEqual(record["server_no_new_privs"], 1) + self.assertEqual(controller.session_environment, expected) + result = controller._run([ + controller.systemctl, "--user", "show", record["unit_name"], + "--property=Environment", "--value"]) + unit_environment = dict(value.split("=", 1) + for value in shlex.split(result.stdout.decode())) + for name, value in expected.items(): + self.assertEqual(unit_environment.get(name), value) + self.assertNotIn(forbidden, unit_environment) + self.assertEqual(controller.user_bus_address, + "unix:path=/run/user/" + str(os.getuid()) + "/bus") + + + +# Do not run inherited acceptance cases twice. +for _name in tuple(name for name in dir(SudoControlTests) if name.startswith("test_")): + setattr(PortableSudoLimitsTests, _name, None) + + +class PortableSudoRpcTests(unittest.TestCase): + def setUp(self): + import threading + from framebridge_expanded import tty + self.tty = tty + class FakeManager: + _lock = threading.RLock() + def reap(self): pass + def touch(self, sid): self.last_touch = sid + def sudo(self, sid, mode): + return {"session_id": sid, "mode": mode, "no_new_privs": 1 if mode == "off" else 0} + self.manager = FakeManager() + self.sid = "a" * 32 + self.payload = {"op": "sudo", "mode": "on", "session_id": self.sid, "authorized": True} + + def test_authorized_typed_sudo_dispatch(self): + reply = self.tty.dispatch(self.manager, self.payload) + self.assertTrue(reply["ok"]) + self.assertEqual(reply["result"]["session_id"], self.sid) + self.assertEqual(reply["result"]["mode"], "on") + self.assertEqual(self.manager.last_touch, self.sid) + + def test_missing_authorization_and_extra_keys_are_rejected(self): + for payload in [dict(self.payload, authorized=False), dict(self.payload, command="sudo id")]: + with self.assertRaises(self.tty.TerminalDenied): + self.tty.dispatch(self.manager, payload) + + def test_cleanup_pending_keeps_verified_replacement_in_failure_reply(self): + self.manager.sudo = lambda sid, mode: {"session_id": sid, "cleanup_pending": True, "mode": mode} + reply = self.tty.dispatch(self.manager, self.payload) + self.assertFalse(reply["ok"]) + self.assertEqual(reply["error"], "TTY_SUDO_TRANSITION_CLEANUP_PENDING") + self.assertEqual(reply["result"]["session_id"], self.sid) + status = self.tty.dispatch(self.manager, dict(self.payload, mode="status")) + self.assertTrue(status["ok"]) + + def test_controller_error_is_public_fixed_code(self): + error = control.SudoControlError("TTY_SUDO_CONSOLE_BUSY") + self.assertEqual(self.tty._safe_error(error), "TTY_SUDO_CONSOLE_BUSY") + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_tunnel.py b/source/tests/test_tunnel.py new file mode 100644 index 0000000..9a31e8e --- /dev/null +++ b/source/tests/test_tunnel.py @@ -0,0 +1,323 @@ +import io +import os +from pathlib import Path +import signal +import sys +import time +import subprocess +import tempfile +import unittest +from unittest import mock + +from framebridge_expanded import tunnel + +ID = "tunnel_0123456789abcdef0123456789abcdef" +ROOT = Path(__file__).resolve().parents[1] + + +class ParserTests(unittest.TestCase): + def test_id_and_documented_commands(self): + commands = [ + ID, + "'" + ID + "'", + "tunnel-client run --control-plane.tunnel-id " + ID, + "/opt/client/tunnel-client run --control-plane.tunnel-id=" + ID + " --control-plane.api-key env:OLD_KEY --mcp.server-url url=http://127.0.0.1:8898/mcp", + "tunnel-client init --profile demo --sample sample_mcp_remote_no_auth --tunnel-id " + ID + " --mcp-server-url http://127.0.0.1:8898/mcp", + "tunnel-client runtimes connect --alias demo --tunnel-id " + ID + " --runtime-api-key env:OLD_KEY --json", + "framebridge-original tunnel -- run --control-plane.tunnel-id " + ID, + ] + for command in commands: + with self.subTest(command=command): + self.assertEqual(tunnel.parse_tunnel_input(command).tunnel_id, ID) + + def test_rejections_never_echo_hidden_input(self): + secret = "sk-test-DO_NOT_DISCLOSE-0123456789" + bad = [ + "sudo tunnel-client run --control-plane.tunnel-id " + ID, + "env OPENAI_API_KEY=" + secret + " tunnel-client run --control-plane.tunnel-id " + ID, + "tunnel-client run --control-plane.tunnel-id " + ID + "; echo bad", + "tunnel-client run --control-plane.tunnel-id $(" + secret + ")", + "tunnel-client run --control-plane.tunnel-id " + ID + " | cat", + "tunnel-client run --control-plane.tunnel-id " + ID + " --config /private/profile.yaml", + "tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.api-key " + secret, + "tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.api-key file:/private/key", + "tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.base-url https://example.invalid", + "tunnel-client run --control-plane.tunnel-id " + ID + " --tunnel-id " + ID, + "tunnel-client run --control-plane.tunnel-id", + "tunnel-client run --control-plane.tunnel-id '" + secret, + ] + for command in bad: + with self.subTest(command=command): + with self.assertRaises(tunnel.TunnelInputError) as error: + tunnel.parse_tunnel_input(command) + self.assertNotIn(secret, str(error.exception)) + self.assertNotIn(command, str(error.exception)) + + def test_rebuild_uses_packaged_binary_and_profile_endpoint(self): + spec = tunnel.parse_tunnel_input("tunnel-client run --control-plane.tunnel-id " + ID + " --mcp.server-url https://mcp.example.com/mcp") + args = tunnel.build_command("/packaged/tunnel-client", "/own/state", "http://127.0.0.1:20865/mcp", spec) + self.assertEqual(args[0], "/packaged/tunnel-client") + self.assertIn("url=http://127.0.0.1:20865/mcp", args) + self.assertNotIn("https://mcp.example.com/mcp", args) + self.assertIn("env:" + tunnel.KEY_ENV, args) + + def test_inherited_runtime_settings_removed_without_global_mutation(self): + original = {"PATH": "/usr/bin", "DISPLAY": ":77", "HTTPS_PROXY": "http://proxy.example", "CA_BUNDLE": "/ca.pem", + "TUNNEL_CLIENT_CONFIG": "/production/config", "CONTROL_PLANE_TUNNEL_ID": "production", + "MCP_SERVER_URL": "https://production", "MCP_COMMAND": "production-command", + "HEALTH_URL_FILE": "/production/health", "LOG_HTTP_RAW_UNSAFE": "true", + "CLOUDFLARED_TUNNEL_TOKEN": "not-used", "OPENAI_API_KEY": "not-used"} + snapshot = dict(original) + env = tunnel.child_environment("FAKE_RUNTIME_KEY", original) + self.assertEqual(original, snapshot) + for key in snapshot: + if key.startswith(tunnel._RUNTIME_PREFIXES): + self.assertNotIn(key, env) + self.assertEqual(env["HTTPS_PROXY"], original["HTTPS_PROXY"]) + self.assertEqual(env[tunnel.KEY_ENV], "FAKE_RUNTIME_KEY") + + def test_child_default_profile_paths_are_private_and_scoped(self): + with tempfile.TemporaryDirectory(prefix="tunnel-test-", dir=ROOT / "qa") as td: + env = tunnel.child_environment("FAKE_ONLY", {"HOME": "/old/home", "XDG_CONFIG_HOME": "/old/config"}, state=Path(td)) + self.assertEqual(env["HOME"], str(Path(td) / "chatgpt-home")) + self.assertEqual(env["XDG_CONFIG_HOME"], str(Path(td) / "chatgpt-home/.config")) + self.assertEqual(Path(env["TMPDIR"]).stat().st_mode & 0o777, 0o700) + self.assertFalse(any(p.is_file() for p in Path(td).rglob("*"))) + + def test_saved_public_context_is_reused(self): + public = {"tunnel_id": ID, "control_plane_organization_id": "org_test", "control_plane_url_path": "/v1/tunnel"} + with mock.patch.object(tunnel, "_hidden", side_effect=["", "FAKE_ONLY"]), mock.patch("framebridge_expanded.connection_background.run_foreground", return_value=0) as run: + rc = tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", public, output=io.StringIO(), foreground=True) + self.assertEqual(rc, 0) + self.assertIn("org_test", run.call_args.args[0]) + self.assertIn("/v1/tunnel", run.call_args.args[0]) + + def test_non_tty_fails_before_secret_prompt(self): + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=False), mock.patch.object(tunnel.getpass, "getpass") as prompt: + with self.assertRaises(tunnel.TunnelInputError): + tunnel._hidden("Hidden: ") + prompt.assert_not_called() + + +class ForegroundTests(unittest.TestCase): + def command(self, code): + return [sys.executable, "-u", "-c", code, + "--control-plane.tunnel-id", ID, + "--mcp.server-url", "url=http://127.0.0.1:20865/mcp"] + + def test_fake_key_only_in_child_environment_and_private_log(self): + secret = "FAKE_RUNTIME_ONLY_0123456789" + seen = {} + real_popen = subprocess.Popen + def spawn(args, **kwargs): + seen["args"] = list(args) + seen["env"] = dict(kwargs["env"]) + return real_popen(args, **kwargs) + code = "import os,time;print('TECHNICAL_DEBUG '+os.environ['" + tunnel.KEY_ENV + "'],flush=True);time.sleep(.3)" + with tempfile.TemporaryDirectory(prefix="tunnel-output-") as td: + output = io.StringIO() + with mock.patch("framebridge_expanded.connection_monitor.probe", return_value=("ready", "Verified synthetic test state")): + result = tunnel.run_foreground(self.command(code), secret, state=td, output=output, popen=spawn) + data = (Path(td) / "logs/chatgpt/connection.log").read_text() + self.assertEqual(result, 0) + self.assertEqual(seen["env"][tunnel.KEY_ENV], secret) + self.assertNotIn(secret, repr(seen["args"])) + self.assertNotIn(secret, output.getvalue()) + self.assertNotIn("TECHNICAL_DEBUG", output.getvalue()) + self.assertIn("TECHNICAL_DEBUG [REDACTED]", data) + self.assertNotIn(secret, data) + self.assertIn("[OK]", output.getvalue()) + self.assertIn("successful tool call", output.getvalue()) + self.assertEqual((Path(td) / "logs/chatgpt/connection.log").stat().st_mode & 0o777, 0o600) + + def test_ctrl_c_stops_only_owned_child(self): + real_popen = subprocess.Popen + seen = {} + def spawn(args, **kwargs): + seen["process"] = real_popen(args, **kwargs) + return seen["process"] + with tempfile.TemporaryDirectory(prefix="tunnel-stop-") as td: + with mock.patch("framebridge_expanded.connection_monitor.probe", side_effect=KeyboardInterrupt): + rc = tunnel.run_foreground(self.command("import time;time.sleep(60)"), "FAKE_ONLY", state=td, + output=io.StringIO(), popen=spawn) + self.assertEqual(rc, 130) + self.assertIsNotNone(seen["process"].poll()) + self.assertFalse(any((Path(td) / "connection-runtime").iterdir())) + + def test_interactive_flow_saves_only_public_values(self): + secret = "FAKE_RUNTIME_ONLY_0123456789" + saved = [] + output = io.StringIO() + with mock.patch.object(tunnel, "_hidden", side_effect=[ID, secret]), mock.patch("framebridge_expanded.connection_background.run_foreground", return_value=0) as run: + rc = tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", {"account_label": "test@example.com"}, saved.append, output=output, foreground=True) + self.assertEqual(rc, 0) + self.assertEqual(saved[0]["tunnel_id"], ID) + self.assertNotIn(secret, repr(saved)) + self.assertNotIn(secret, output.getvalue()) + self.assertEqual(run.call_args.args[1], secret) + self.assertNotIn(secret, repr(run.call_args.args[0])) + + +class ManagedWizardTests(unittest.TestCase): + @staticmethod + def ready(): + return {"configured": True, "enabled": True, "service_active": True, + "service_state": "active", "runtime": {"running": True, "ready": True}, + "connection": {"alive": True, "verified": True, "state": "ready"}} + + def invoke(self, configure, *, public=None, output=None, wait_timeout=0): + return tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", public, + output=output or io.StringIO(), configure_connection=configure, + wait_timeout=wait_timeout) + + def test_three_fields_are_ordered_and_only_public_settings_are_committed(self): + events = [] + def label(prompt): + events.append(prompt) + return "test@example.com" + answers = iter([ID, "SYNTHETIC-MCP-ACCESS-KEY"]) + def hidden(prompt): + events.append(prompt) + return next(answers) + seen = [] + def configure(state, public, credential): + events.append("commit") + seen.append((state, public, credential)) + return self.ready() + output = io.StringIO() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", side_effect=label), mock.patch.object(tunnel, "_hidden", side_effect=hidden): + rc = self.invoke(configure, output=output) + self.assertEqual(0, rc) + self.assertTrue(events[0].startswith("ChatGPT account (email label)")) + self.assertTrue(events[1].startswith("Tunnel ID (hidden")) + self.assertEqual("MCP server access key (hidden): ", events[2]) + self.assertEqual("commit", events[3]) + self.assertEqual("test@example.com", seen[0][1]["account_label"]) + self.assertEqual(ID, seen[0][1]["tunnel_id"]) + self.assertNotIn("SYNTHETIC-MCP-ACCESS-KEY", repr(seen[0][1])) + self.assertNotIn("SYNTHETIC-MCP-ACCESS-KEY", output.getvalue()) + self.assertEqual("Connecting...\n[OK] Connected.\n", output.getvalue()) + + def test_cancel_at_each_field_never_calls_committer(self): + for field in range(3): + with self.subTest(field=field): + configure = mock.Mock() + label = mock.Mock(return_value="test@example.com") + hidden = mock.Mock(side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]) + if field == 0: + label.side_effect = KeyboardInterrupt + elif field == 1: + hidden.side_effect = KeyboardInterrupt + else: + hidden.side_effect = [ID, KeyboardInterrupt] + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", label), mock.patch.object(tunnel, "_hidden", hidden): + rc = self.invoke(configure) + self.assertEqual(130, rc) + configure.assert_not_called() + + def test_full_command_in_normal_hidden_id_field_is_rejected_without_echo(self): + configure = mock.Mock() + pasted = "tunnel-client run --tunnel-id " + ID + " --api-key SYNTHETIC-PASTED-SECRET" + output = io.StringIO() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", return_value=pasted) as hidden: + with self.assertRaises(tunnel.TunnelInputError) as error: + self.invoke(configure, output=output) + self.assertNotIn("SYNTHETIC-PASTED-SECRET", str(error.exception) + output.getvalue()) + self.assertEqual(1, hidden.call_count) + configure.assert_not_called() + + def test_invalid_key_does_not_commit_and_is_not_echoed(self): + configure = mock.Mock() + output = io.StringIO() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC INVALID KEY"]): + with self.assertRaises(tunnel.TunnelInputError) as error: + self.invoke(configure, output=output) + self.assertNotIn("SYNTHETIC INVALID KEY", str(error.exception) + output.getvalue()) + configure.assert_not_called() + + def test_saved_public_defaults_preserve_tunnel_context_but_key_is_requested(self): + public = {"account_label": "test@example.com", "tunnel_id": ID, + "control_plane_organization_id": "org_test", "control_plane_url_path": "/v1/tunnel"} + configure = mock.Mock(return_value=self.ready()) + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value=""), mock.patch.object(tunnel, "_hidden", side_effect=["", "SYNTHETIC-MCP-ACCESS-KEY"]) as hidden: + self.assertEqual(0, self.invoke(configure, public=public)) + self.assertEqual(2, hidden.call_count) + self.assertEqual(public, configure.call_args.args[1]) + + def test_pending_returns_prompt_nonzero_without_false_success_or_reboot(self): + pending = self.ready() + pending["connection"] = {"alive": True, "verified": False, "state": "connecting"} + output = io.StringIO() + self.assertEqual(1, tunnel._wait_managed_connection("/state", pending, output, wait_timeout=0)) + self.assertIn("[PENDING]", output.getvalue()) + self.assertNotIn("[OK]", output.getvalue()) + self.assertNotIn("reboot", output.getvalue().lower()) + + def test_failed_service_cannot_use_stale_verified_connection_as_success(self): + for failure in ({"service_state": "failed", "service_active": False}, {"service_state": "failed", "service_active": True}, {"worker": {"state": "failed"}}): + with self.subTest(failure=failure): + state = self.ready() + state.update(failure) + output = io.StringIO() + self.assertEqual(1, tunnel._wait_managed_connection("/state", state, output, wait_timeout=0)) + self.assertIn("[ERROR]", output.getvalue()) + self.assertNotIn("[OK]", output.getvalue()) + + def test_readiness_is_polled_and_key_is_requested_only_once(self): + from framebridge_expanded import autostart + starting = self.ready() + starting["connection"] = {"alive": False, "verified": False, "state": "connecting"} + configure = mock.Mock(return_value=starting) + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]) as hidden, mock.patch.object(tunnel.time, "sleep"), mock.patch.object(autostart, "status", return_value=self.ready()) as status: + self.assertEqual(0, self.invoke(configure, wait_timeout=2)) + self.assertEqual(2, hidden.call_count) + self.assertEqual("MCP server access key (hidden): ", hidden.call_args.args[0]) + status.assert_called_once_with("/state") + + def test_interrupt_during_wait_reports_saved_background_connection(self): + from framebridge_expanded import autostart + starting = self.ready() + starting["connection"] = {"alive": False, "verified": False, "state": "connecting"} + output = io.StringIO() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]), mock.patch.object(tunnel.time, "sleep"), mock.patch.object(autostart, "status", side_effect=KeyboardInterrupt): + self.assertEqual(130, self.invoke(mock.Mock(return_value=starting), output=output, wait_timeout=2)) + self.assertIn("continues in the background", output.getvalue()) + self.assertNotIn("Connection cancelled", output.getvalue()) + + def test_interrupt_after_commit_does_not_report_connection_cancelled(self): + marker = {"configured": True, "enabled": True, "interrupted_after_commit": True} + configure = mock.Mock(return_value=marker) + output = io.StringIO() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]): + self.assertEqual(130, self.invoke(configure, output=output)) + self.assertIn("continues in the background", output.getvalue()) + self.assertNotIn("cancelled", output.getvalue().lower()) + self.assertNotIn("[OK]", output.getvalue()) + + def test_resume_status_interrupted_after_commit_is_not_reported_cancelled(self): + marker = {"configured": True, "enabled": True, "interrupted_after_commit": True} + output = io.StringIO() + self.assertEqual(130, tunnel.resume_managed_connection("/state", mock.Mock(return_value=marker), output=output, wait_timeout=0)) + self.assertIn("continues in the background", output.getvalue()) + self.assertNotIn("cancelled", output.getvalue().lower()) + + def test_non_tty_fails_before_any_input_or_commit(self): + configure = mock.Mock() + with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=False), mock.patch("builtins.input") as label, mock.patch.object(tunnel, "_hidden") as hidden: + with self.assertRaises(tunnel.TunnelInputError): + self.invoke(configure) + label.assert_not_called() + hidden.assert_not_called() + configure.assert_not_called() + + def test_resume_uses_no_setup_prompts(self): + resume = mock.Mock(return_value=self.ready()) + with mock.patch("builtins.input") as label, mock.patch.object(tunnel, "_hidden") as hidden: + self.assertEqual(0, tunnel.resume_managed_connection("/state", resume, output=io.StringIO(), wait_timeout=0)) + label.assert_not_called() + hidden.assert_not_called() + resume.assert_called_once_with("/state") + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_uninstall.py b/source/tests/test_uninstall.py new file mode 100644 index 0000000..01e06dd --- /dev/null +++ b/source/tests/test_uninstall.py @@ -0,0 +1,109 @@ +import hashlib +import os +from pathlib import Path +import pwd +import subprocess +import tempfile +from types import SimpleNamespace +import unittest +from unittest import mock +from framebridge_expanded import uninstall + + +class UninstallTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.home = Path(self.temp.name) + self.directory = self.home / ".config/systemd/user" + self.directory.mkdir(parents=True) + + def unit(self, state=None): + state = str(state or self.home / "state") + name = "framebridge-autostart-" + hashlib.sha256(state.encode()).hexdigest()[:16] + ".service" + path = self.directory / name + quoted_state = state.replace("$", "$$").replace("%", "%%").replace("\\", "\\\\").replace('"', '\\"') + path.write_text('# FrameBridge managed autostart v1\n[Service]\nEnvironment="PYTHONPATH=' + uninstall.MODULE_ROOT + '"\nExecStart="/usr/bin/python3" "-B" "-m" "framebridge_expanded.autostart" "_run" "--state" "' + quoted_state + '"\n[Install]\nWantedBy=default.target\n') + return path + + def test_owned_service_matches_even_with_escaped_state(self): + path = self.unit(self.home / "a space $ and % value") + self.assertTrue(uninstall.managed_unit(path, os.getuid())) + + def test_wrong_owner_is_not_accepted(self): + self.assertFalse(uninstall.managed_unit(self.unit(), os.getuid() + 1)) + + def test_foreign_command_and_missing_marker_are_not_accepted(self): + p = self.unit() + p.write_text(p.read_text().replace('"framebridge_expanded.autostart"', '"another_application"')) + self.assertFalse(uninstall.managed_unit(p, os.getuid())) + p = self.unit();p.write_text(p.read_text().replace('# FrameBridge managed autostart v1', '# foreign')) + self.assertFalse(uninstall.managed_unit(p, os.getuid())) + + def test_symlink_and_wrong_hash_are_not_accepted(self): + p = self.unit();data = p.read_text();p.unlink();target = self.home / "outside";target.write_text(data);p.symlink_to(target) + self.assertFalse(uninstall.managed_unit(p, os.getuid())) + p.unlink();p.write_text(data);other = self.directory / ("framebridge-autostart-" + "f" * 16 + ".service");p.rename(other) + self.assertFalse(uninstall.managed_unit(other, os.getuid())) + + def test_live_cleanup_stops_and_disables_only_owned_unit(self): + p = self.unit();foreign = self.directory / "unrelated.service";foreign.write_text("unrelated") + run = mock.Mock(return_value=SimpleNamespace(returncode=0, stdout=b"")) + self.assertEqual(uninstall.cleanup_user(self.home, run=run, bus_available=True), 1) + calls = [c.args[0] for c in run.call_args_list] + self.assertEqual(calls[0], ["/usr/bin/systemctl", "--user", "stop", p.name]) + self.assertEqual(calls[1], ["/usr/bin/systemctl", "--user", "--no-reload", "disable", p.name]) + self.assertEqual(foreign.read_text(), "unrelated") + self.assertTrue(p.is_file()) + + def test_actual_offline_disable_preserves_profile_and_cipher(self): + p = self.unit();wants = self.directory / "default.target.wants";wants.mkdir();link = wants / p.name;link.symlink_to(Path("..") / p.name) + profile = self.home / "profile.json";cipher = self.home / "credential.cred";profile.write_text('profile fixture');cipher.write_bytes(b'encrypted fixture only') + self.assertEqual(uninstall.cleanup_user(self.home, bus_available=False), 1) + self.assertFalse(link.is_symlink()) + self.assertEqual(profile.read_text(), 'profile fixture') + self.assertEqual(cipher.read_bytes(), b'encrypted fixture only') + self.assertTrue(p.is_file()) + + def test_failed_stop_still_attempts_disable_and_reports_failure(self): + self.unit() + def invoke(args, **kwargs): + if "stop" in args:return SimpleNamespace(returncode=1, stdout=b"") + if "show" in args:return SimpleNamespace(returncode=0, stdout=b"active\n") + return SimpleNamespace(returncode=0, stdout=b"") + run = mock.Mock(side_effect=invoke) + with self.assertRaises(RuntimeError):uninstall.cleanup_user(self.home, run=run, bus_available=True) + self.assertTrue(any("disable" in c.args[0] for c in run.call_args_list)) + + def test_no_managed_services_makes_no_calls(self): + run = mock.Mock();self.assertEqual(uninstall.cleanup_user(self.home, run=run), 0);run.assert_not_called() + + def test_all_users_drops_privileges_and_includes_service_accounts(self): + accounts = [SimpleNamespace(pw_name="testservice", pw_uid=104, pw_dir="/var/lib/testservice"), SimpleNamespace(pw_name="testperson", pw_uid=1000, pw_dir="/home/testperson"), SimpleNamespace(pw_name="nobody", pw_uid=65534, pw_dir="/nonexistent")] + run = mock.Mock(return_value=SimpleNamespace(returncode=0)) + with mock.patch.object(uninstall.os, "getuid", return_value=0):uninstall.cleanup_all_users(run=run, accounts=accounts) + self.assertEqual(run.call_count, 2) + for call, name in zip(run.call_args_list, ["testservice", "testperson"]): + args = call.args[0] + self.assertEqual(args[:7], ["/usr/sbin/runuser", "-u", name, "--", "/usr/bin/env", "-i", "HOME=" + ("/var/lib/testservice" if name == "testservice" else "/home/testperson")]) + self.assertEqual(args[-4:], ["-I", "-S", str(Path(uninstall.__file__).resolve()), "--user"]) + + def test_root_dispatch_continues_after_one_user_failure(self): + accounts = [SimpleNamespace(pw_name="first", pw_uid=1000, pw_dir="/home/first"), SimpleNamespace(pw_name="second", pw_uid=1001, pw_dir="/home/second")] + run = mock.Mock(side_effect=[SimpleNamespace(returncode=1), SimpleNamespace(returncode=0)]) + with mock.patch.object(uninstall.os, "getuid", return_value=0): + with self.assertRaises(RuntimeError):uninstall.cleanup_all_users(run=run, accounts=accounts) + self.assertEqual(run.call_count, 2) + + def test_nonroot_cannot_dispatch_other_users(self): + with mock.patch.object(uninstall.os, "getuid", return_value=1000): + with self.assertRaises(RuntimeError):uninstall.cleanup_all_users(accounts=[]) + + def test_stop_timeout_still_disables_unit(self): + self.unit() + def invoke(args, **kwargs): + if "stop" in args:raise subprocess.TimeoutExpired(args, 35) + return SimpleNamespace(returncode=0, stdout=b"") + run = mock.Mock(side_effect=invoke) + with self.assertRaises(RuntimeError):uninstall.cleanup_user(self.home, run=run, bus_available=True) + self.assertTrue(any("disable" in c.args[0] for c in run.call_args_list)) diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..4b5163e --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,4 @@ +dist/ +qa/ +release.json +__pycache__/ diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..5eb2800 --- /dev/null +++ b/website/README.md @@ -0,0 +1,3 @@ +# FrameBridge website source + +The website implementation follows the project GPL-3.0-only license. Project video is delivered separately. Copy release.example.json to release.json, add the supplied conceptual preview to assets/framebridge-preview.mp4, then run python3 -I -S build_site.py and python3 -I -S qa_site.py. The generator reads ../source/docs/COMMAND-GUIDE.json in this source layout. Production signing metadata must be supplied by a maintainer; never fabricate fingerprints or signatures. diff --git a/website/assets/favicon.svg b/website/assets/favicon.svg new file mode 100644 index 0000000..aa24a01 --- /dev/null +++ b/website/assets/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/assets/logo.svg b/website/assets/logo.svg new file mode 100644 index 0000000..7707388 --- /dev/null +++ b/website/assets/logo.svg @@ -0,0 +1 @@ +FrameBridge terminal prompt>framebridge \ No newline at end of file diff --git a/website/assets/video-poster.svg b/website/assets/video-poster.svg new file mode 100644 index 0000000..e61672e --- /dev/null +++ b/website/assets/video-poster.svg @@ -0,0 +1 @@ +FRAMEBRIDGE / CONCEPTUAL PREVIEW>framebridgeA conversation becomes a workflow.Conceptual animation · Not a screen recording \ No newline at end of file diff --git a/website/build_site.py b/website/build_site.py new file mode 100644 index 0000000..88d9b3e --- /dev/null +++ b/website/build_site.py @@ -0,0 +1,160 @@ +#!/usr/bin/python3 +"""Build the self-hosted FrameBridge website with the Python standard library.""" +from pathlib import Path +import html,json,re,shutil,sys + +BASE=Path(__file__).resolve().parent +ROOT=BASE.parent +DIST=BASE/'dist' +META=json.loads((BASE/'release.json').read_text()) +META.setdefault('git_url','/git/framebridge/framebridge') +META.setdefault('git_clone_url','https://framebridge.betancourt.one/git/framebridge/framebridge.git') +E=lambda value:html.escape(str(value),quote=True) +READY=META.get('state')=='ready' +if READY: + assert re.fullmatch(r'[a-fA-F0-9]{64}',META.get('sha256','')),'Ready release requires the exact package SHA-256.' + assert re.fullmatch(r'[a-fA-F0-9]{40}|[a-fA-F0-9]{64}',META.get('signing_fingerprint','')),'Ready release requires a public signing fingerprint.' + assert META.get('source_archive_url') and META.get('package_signature_url') and META.get('size_bytes'),'Ready release requires source, signature and size metadata.' + deps=META.get('dependency_sources',[]) + assert len(deps)>=2 and all(item.get('url','').startswith('/downloads/') and re.fullmatch(r'[a-fA-F0-9]{64}',item.get('sha256','')) and item.get('size_bytes') for item in deps),'Ready release requires all dependency-source archives and their exact checksums.' +for name in ['git_url','package_url','package_signature_url','checksums_url','checksums_signature_url','source_archive_url','signing_key_url']: + value=META.get(name,'') + assert not value or value.startswith('/') or value.startswith('https://framebridge.betancourt.one/'),'Only project-owned HTTPS or root-relative artifact URLs are allowed.' +for path in ['assets','docs','download','blog/2026-10-26-opening-framebridge','community','support']: + (DIST/path).mkdir(parents=True,exist_ok=True) +for p in (BASE/'src').iterdir(): + if p.is_file():shutil.copyfile(p,DIST/'assets'/p.name) +for p in (BASE/'assets').iterdir(): + if p.is_file():shutil.copyfile(p,DIST/'assets'/p.name) +(DIST/'assets/release.json').write_text(json.dumps(META,indent=2)+'\n') + +def brand(): + return 'framebridge' + +def header(current): + items=[('Docs','/docs/','docs'),('Development','/blog/','blog'),('Source',META['git_url'],'source'),('Support','/support/','support'),('Download','/download/','download')] + links=[] + for title,url,key in items: + links.append(''+E(title)+'') + return '' + +def footer(): + return '''
''' + +def page(title,body,current='',light=False,description='FrameBridge connects your own ChatGPT account to Linux systems you control.'): + return ''+E(title)+' · FrameBridge'+header(current)+body+footer()+'' + +def code(text): + return '
'+E(text)+'
' + +release_label='Official release' if READY else 'Release preparation' +home='''
Linux systems · Your ChatGPT account

Your computers.
A working conversation.

Bring commands, persistent terminals and a compatible desktop into your conversation with ChatGPT. Work on Linux systems you control, through one bridge.

GNU GPLv3 · Debian 13 · Manual connection to your own account

FRAMEBRIDGE / EXAMPLE WORKFLOW

$fb connect

Connect your own account through the guided terminal flow.

$fb status

See tools, connection and startup state in one place.

$fb console

Return to your work view: Bash and FBTERM activity.

Illustrative commands. This is not a screen capture.

Local executionPersistent terminalsGraphical + text toolsCentral + nodes
+

From a blinking cursor
to a connected workflow.

A conceptual look at independent tasks, consoles and connected computers. The software runs your tools on your systems; the ChatGPT model remains hosted.

Conceptual animation — not a screen recording.Original RC7 concept preview. Features and commands are documented for the current release below.

+

Start with one machine.
Extend when you need to.

A standalone computer can provide local tools. A central installation can also connect approved nodes.

Where work happens
Your ChatGPT accountFrameBridgeYour Linux systems

No phone pairing is required. Initial account consent and credentials stay in the normal browser or hidden terminal prompts.

  1. 01

    Install and connect

    Install the Debian package, then use the guided text interface to connect your own account. Advanced role setup is optional for ordinary standalone use.

  2. 02

    Choose the right tool

    Use FBEXEC for commands, persistent terminals for ongoing shell work, and graphical CUA when a compatible desktop is available.

  3. 03

    Keep the context

    Return to a live terminal session without rebuilding its shell state between calls. Running processes do not survive an operating-system reboot.

  4. 04

    Add approved nodes

    Use the central instance for ChatGPT transport and select enrolled nodes explicitly. Nodes do not need a second ChatGPT tunnel.

+

Execution

Run authorized shell tasks on the selected machine and inspect their recorded results.

FBEXEC

Terminals

Keep a real Bash session across calls. Inspect text, send input and return to ongoing work.

FBTERM / fb tty

Desktop and text

Discover compatible graphical CUA automatically. Use text-terminal tools when a graphical desktop is unavailable.

CUA / CUA-TTY

People and agents

The same command-line interface serves human operators and authorized ChatGPT agents, with discoverable usage guidance.

fb / framebridge

Independent FBEXEC tasks have been used together in the laboratory when their objectives and changes did not overlap. This is observed practice, not an isolation guarantee. Use one controller at a time for each shared graphical desktop.

+

Free software.
Verifiable releases.

Read the code, adapt it and contribute. Official downloads are identified by their published checksums and release signatures.

  • Version'''+E(META['version'])+'''
  • Platform'''+E(META['distribution'])+'''
  • LicenseGNU GPLv3 only
  • Channel'''+E(release_label)+'''
+

A small project.
An open invitation.

Good tools improve through use, careful fixes and clear documentation. Report a reproducible issue, improve the guide or propose a change in Git.

How to contribute
''' +(DIST/'index.html').write_text(page('Your computers. A working conversation.',home)) + +notice='' if READY else '
Release preparation. Download and installation commands will appear here once the package, corresponding source and signing information have been finalized. No placeholder checksum or key should be trusted.
' +facts='
Package
'+E(META['package_filename'])+'
Version
'+E(META['version'])+'
Platform
'+E(META['distribution'])+' · '+E(META['architecture'])+'
License
GNU GPLv3 only, with separate third-party notices
' +if READY:facts+='
Package SHA-256
'+E(META['sha256'])+'
Signing-key fingerprint
'+E(META['signing_fingerprint'])+'
' +facts+='
' +manual='' +apt='' +if READY: + def absolute(value): + return value if value.startswith('https://') else META['site_url']+value + manual_commands='''sudo apt install ca-certificates curl gnupg +( + set -eu + verify_dir="$(mktemp -d)" + trap 'rm -rf "$verify_dir"' EXIT + curl -fsSL "'''+absolute(META['signing_key_url'])+'''" -o "$verify_dir/project.asc" + expected="'''+META['signing_fingerprint'].upper()+'''" + actual="$(gpg --show-keys --with-colons "$verify_dir/project.asc" 2>/dev/null | awk -F: '$1=="fpr" {print $10; exit}')" + test "$actual" = "$expected" || { printf '%s\\n' "Signing-key fingerprint mismatch."; exit 1; } + gpg --batch --dearmor --output "$verify_dir/project.gpg" "$verify_dir/project.asc" + curl -fsSL "'''+absolute(META['package_url'])+'''" -o "$verify_dir/'''+META['package_filename']+'''" + curl -fsSL "'''+absolute(META['package_signature_url'])+'''" -o "$verify_dir/package.asc" + printf '%s %s\\n' "'''+META['sha256']+'''" "$verify_dir/'''+META['package_filename']+'''" | sha256sum --check + gpgv --keyring "$verify_dir/project.gpg" "$verify_dir/package.asc" "$verify_dir/'''+META['package_filename']+'''" + sudo apt install "$verify_dir/'''+META['package_filename']+'''" +)''' + manual='

Package signature · SHA256SUMS · Signed checksums

Download, verify and install

This complete terminal flow checks the expected signing-key fingerprint, package checksum and detached signature before asking APT to install the package and resolve its dependencies.

'+code(manual_commands) + source_items=''.join('
  • '+E(item['label'])+'
    '+E(item['filename'])+'
    SHA-256: '+E(item['sha256'])+'
  • ' for item in META['dependency_sources']) + manual+='

    Complete source materials

    The matching FrameBridge source archive above and the following dependency-source materials are distributed together for this release. Preserve their license notices when redistributing.

      '+source_items+'
    ' + apt_commands='''sudo apt install ca-certificates curl gnupg +( + set -eu + key_file="$(mktemp)" + trap 'rm -f "$key_file"' EXIT + curl -fsSL "'''+absolute(META['signing_key_url'])+'''" -o "$key_file" + expected="'''+META['signing_fingerprint'].upper()+'''" + actual="$(gpg --show-keys --with-colons "$key_file" 2>/dev/null | awk -F: '$1=="fpr" {print $10; exit}')" + test "$actual" = "$expected" || { printf '%s\\n' "Signing-key fingerprint mismatch."; exit 1; } + sudo install -d -m 0755 /etc/apt/keyrings + sudo install -m 0644 "$key_file" /etc/apt/keyrings/framebridge.asc + printf '%s\\n' "deb [signed-by=/etc/apt/keyrings/framebridge.asc] '''+META['apt_base']+''' trixie main" | sudo tee /etc/apt/sources.list.d/framebridge.list >/dev/null + sudo apt update + sudo apt install '''+META['package_name']+''' +)''' + apt='

    Install from the signed APT repository

    This project repository distributes FrameBridge; it is not a mirror of the full Debian archive. The signing key below is scoped to this repository with Signed-By.

    '+code(apt_commands)+'

    The fingerprint pins the key expected by this release page. Obtain it through this HTTPS site or another trusted project channel. Do not bypass APT signature errors.

    ' +download='
    '+notice+manual+apt+'

    After installation

    Run the normal connection flow in a terminal on the computer you are connecting:

    '+code('fb connect\nfb status')+'

    Use your own account. Complete browser consent normally and enter credentials only at the hidden terminal prompts. Keep secrets out of chats, issue reports and copied command arguments.

    For diagnosis, use fb doctor. See the versioned command guide for advanced roles and operation details.

    ' +(DIST/'download/index.html').write_text(page('Download and verify',download,'download')) + +community='''

    Start with the existing behavior

    Read the versioned guide, check the installed version and look for an existing issue. A reproducible report should name the command, expected result, actual result and relevant environment.

    Remove credentials and personal data from logs before sharing them. Never include passwords, access keys, private keys, tokens or cookies.

    Propose a focused change

    Use an issue to explain the problem or a pull request to propose a patch. Include the reason for the change and proportionate validation. Keep unrelated edits separate so they can be reviewed.

    '''+('

    Clone the project

    '+code('git clone '+META['git_clone_url']) if META.get('git_clone_url') else '')+'''

    Open the project in Git

    Official releases and free forks

    Maintainers review which changes become official FrameBridge releases. Published release checksums and signatures identify those artifacts.

    GNU GPLv3 also allows private modifications and independently distributed forks, subject to its terms. Contributing changes upstream is welcome; it is not a condition for exercising those rights. Modified builds should be clearly marked and must not be presented as official project releases.

    Read the GNU GPLv3 license. Third-party components retain their existing notices and licenses.

    Using an agent to contribute

    The command interface and MCP guide are designed for both people and authorized agents. Normal operation uses installed commands and tools. Editing or rebuilding the application is a development task that should follow the owner’s instructions.

    Project credits

    Programming: ChatGPT
    Original concept and project coordination: Gerardo Betancourt

    FrameBridge is an independent project. The credit describes how the project was developed and does not imply endorsement by OpenAI.

    ''' +(DIST/'community/index.html').write_text(page('Contribute',community,'source')) + +post='''

    FrameBridge began with a practical goal: use a conversation with ChatGPT to do useful work on Linux computers we control. Commands, persistent terminals and a compatible desktop should fit into the same working context.

    Make the interface easier to understand

    The current development work brings everyday commands into a shorter vocabulary, keeps status observation separate from diagnosis, and explains the effects of connection and startup controls. The long command framebridge and its short alias fb use the same interface.

    The agent also needs a reliable starting point. A versioned guide and a brief MCP invitation are intended to help it discover how the installed software works, which computer receives an operation and what a command will change.

    Keep the evidence honest

    The preview video is a conceptual animation, not a recording of a live session. Real demonstrations and captures must remain distinguishable from illustrations. Independent tasks have been used together in the laboratory, but that experience does not establish automatic isolation or exclusive desktop control.

    Open source, with identifiable releases

    The project is being prepared for GNU GPLv3 distribution. Sources and package artifacts should be published together, with signatures and checksums identifying official releases. Community members remain free to adapt and redistribute the software under the license.

    The first contribution can be small: a clearer explanation, a reproducible issue or a focused fix. Review gives those changes a path into a later official version.

    Read the contribution guide →

    ''' +(DIST/'blog/2026-10-26-opening-framebridge/index.html').write_text(page('Opening FrameBridge',post,'blog')) +support='''

    What support can help with

    • Hosting and distribution: keeping the source code, documentation and release files available.
    • Maintenance: investigating reported problems and keeping the software usable.
    • Documentation: improving clear instructions for people and agents.
    • Development: building and reviewing focused improvements.

    Funding channel

    Funding details will be published here when a channel is available.

    Contributions are voluntary. FrameBridge remains free software under GNU GPLv3 only, whether or not you provide financial support.

    Support without a payment

    A useful bug report, a clearer explanation or a carefully scoped patch can be valuable contributions. You can also help by sharing honest demonstrations that distinguish real recordings from conceptual illustrations.

    Read the contribution guide →

    Review stays independent

    Financial support does not grant control over official releases, bypass code review or security checks, or imply a project endorsement. Official changes follow the same review and release process.

    Explore the source code →

    ''' +(DIST/'support/index.html').write_text(page('Support FrameBridge',support,'support')) + +blog='''
    ''' +(DIST/'blog/index.html').write_text(page('Development journal',blog,'blog')) + +guide_path=ROOT/'repository/source/docs/COMMAND-GUIDE.json' +if not guide_path.exists(): + guide_path=ROOT/'source/docs/COMMAND-GUIDE.json' +guides={} +guide_data={} +if guide_path.exists(): + guide_data=json.loads(guide_path.read_text()) + guides=guide_data.get('topics',{}) + assert isinstance(guides,dict) and all(isinstance(v,dict) for v in guides.values()) + assert guide_data.get('version')==META['version'],'Guide and release versions must match.' + (DIST/'assets/command-guide.json').write_text(json.dumps(guide_data,indent=2)+'\n') +order=[x for x in ['start','commands','connection','lifecycle','nodes','terminals','screens','diagnostics','security'] if x in guides] +order += [x for x in guides if x not in order] + +def docs_nav(current): + entries='
  • Guide overview
  • ' + for topic in order: + entries+='
  • '+E(guides[topic].get('title',topic.title()))+'
  • ' + return '' + +def paragraphs(body): + if isinstance(body,list):body='\n\n'.join(str(x) for x in body) + return ''.join('

    '+E(x).replace('\n','
    ')+'

    ' for x in str(body).split('\n\n') if x.strip()) + +for topic in order: + guide=guides[topic] + article='

    '+E(guide.get('title',topic))+'

    ' + for section in guide.get('sections',[]): + article+='

    '+E(section.get('heading',''))+'

    '+paragraphs(section.get('body','')) + for command in section.get('commands',[]): + article+=code(command if isinstance(command,str) else json.dumps(command,indent=2)) + article+='

    This page is generated from the same versioned guide used by the CLI and MCP help tool. Use fb help '+E(topic)+' for the local reference.

    ' + dest=DIST/'docs'/topic;dest.mkdir(parents=True,exist_ok=True) + (dest/'index.html').write_text(page(guide.get('title',topic),'
    '+docs_nav(topic)+article+'
    ','docs',True)) +hub='

    The command guide.

    One reference for human operators and authorized ChatGPT agents. The commands fb and framebridge use the same interface.

    Start with the normal connection flow. Use advanced setup only when you need to change roles or profile settings.

    '+code('fb connect\nfb status\nfb help')+'' +if not guides:hub+='
    The shared command-guide export is being finalized. This staging page is not the complete published reference yet.
    ' +else:hub+='

    Read the versioned guide data

    ' +hub+='
    ' +(DIST/'docs/index.html').write_text(page('Command guide','
    '+docs_nav('index')+hub+'
    ','docs',True)) +(DIST/'404.html').write_text(page('Page not found','

    This path ends here.

    Return to FrameBridge or open the command guide.

    ')) +(DIST/'robots.txt').write_text('User-agent: *\n'+('Allow: /\n' if READY else 'Disallow: /\n')) +print(json.dumps({'web_root':str(DIST),'release_state':META['state'],'pages':len(list(DIST.rglob('*.html'))),'guide_topics':len(guides),'video_present':(DIST/'assets/framebridge-preview.mp4').is_file(),'ready_metadata_validated':READY},indent=2)) diff --git a/website/qa_site.py b/website/qa_site.py new file mode 100644 index 0000000..4e2b2dc --- /dev/null +++ b/website/qa_site.py @@ -0,0 +1,115 @@ +#!/usr/bin/python3 +"""Bounded static and loopback HTTP checks for the FrameBridge website.""" +from pathlib import Path +from html.parser import HTMLParser +from urllib.parse import urlsplit,unquote +from urllib.request import urlopen +from http.server import ThreadingHTTPServer,SimpleHTTPRequestHandler +import json,hashlib,threading,tempfile,shutil,subprocess,html,os +BASE=Path(__file__).resolve().parent +DIST=BASE/'dist' +REPORT=BASE/'qa/WEBSITE-STATUS.json' +REPORT.parent.mkdir(mode=0o700,exist_ok=True) +class Inspect(HTMLParser): + def __init__(self): + super().__init__();self.links=[];self.assets=[];self.ids=set();self.lang=None;self.main=False;self.scripts=[];self.code=[];self.in_code=False;self.current='' + def handle_starttag(self,tag,attrs): + a=dict(attrs) + if tag=='html':self.lang=a.get('lang') + if 'id' in a:self.ids.add(a['id']) + if tag=='main' and a.get('id')=='content':self.main=True + if tag=='a' and 'href'in a:self.links.append(a['href']) + if tag in ('script','source','img') and 'src'in a:self.assets.append(a['src']) + if tag=='video' and 'poster'in a:self.assets.append(a['poster']) + if tag=='link' and a.get('rel') in ('stylesheet','icon'):self.assets.append(a['href']) + if tag=='script':self.scripts.append(a) + if tag=='code':self.in_code=True;self.current='' + def handle_data(self,data): + if self.in_code:self.current+=data + def handle_endtag(self,tag): + if tag=='code':self.code.append(self.current);self.in_code=False +def resolve(path): + p=DIST/unquote(path.lstrip('/')) + return p/'index.html' if path.endswith('/') or p.is_dir() else p +def main(): + os.umask(0o077);pages=sorted(DIST.rglob('*.html'));assert pages + failures=[];pending=[];infra=set();http_paths=set();external=set() + for p in pages: + raw=p.read_text();q=Inspect();q.feed(raw);rel='/'+str(p.relative_to(DIST));http_paths.add(rel) + if q.lang!='en' or not q.main:failures.append(str(p.relative_to(DIST))+': missing English or main landmark') + if 'gerabh' in raw or '/home/gerabh' in raw:failures.append(str(p.relative_to(DIST))+': personal host path') + if "'unsafe-inline'" in raw:failures.append(str(p.relative_to(DIST))+': unsafe inline CSP') + for value in q.links+q.assets: + u=urlsplit(value) + if u.scheme or u.netloc: + external.add(value);continue + if not u.path: + if u.fragment and u.fragment not in q.ids:failures.append(str(p.relative_to(DIST))+': missing anchor '+u.fragment) + continue + if any(u.path.startswith(prefix) for prefix in ['/git/','/debian/','/downloads/']): + infra.add(u.path);continue + if not u.path.startswith('/'):failures.append('Non-root relative URL '+value);continue + target=resolve(u.path) + if not target.exists(): + if u.path=='/assets/framebridge-preview.mp4':pending.append('Preview video asset pending') + else:failures.append('Missing local target '+value+' referenced by '+str(p.relative_to(DIST))) + elif u.path in q.assets:http_paths.add(u.path) + if u.fragment and target.is_file() and target.suffix=='.html': + other=Inspect();other.feed(target.read_text()) + if u.fragment not in other.ids:failures.append('Missing target fragment '+value) + for item in q.scripts: + if item.get('src')!='/assets/site.js':failures.append('Unexpected script source') + css=(DIST/'assets/site.css').read_text() + assert css.count('{')==css.count('}'),'CSS brace mismatch' + class Quiet(SimpleHTTPRequestHandler): + def __init__(self,*a,**k):super().__init__(*a,directory=str(DIST),**k) + def log_message(self,*a):pass + server=ThreadingHTTPServer(('127.0.0.1',0),Quiet);thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start() + http_count=0 + try: + for path in sorted(http_paths): + with urlopen('http://127.0.0.1:'+str(server.server_port)+path,timeout=3) as response: + if response.status!=200:failures.append('HTTP status '+str(response.status)+' for '+path) + response.read();http_count+=1 + finally:server.shutdown();server.server_close();thread.join(timeout=2) + # Exercise the guarded final-metadata branch using clearly synthetic values + # in a disposable directory. No signature or installation command executes. + meta=json.loads((BASE/'release.json').read_text()) + with tempfile.TemporaryDirectory(prefix='framebridge-web-qa-') as td: + temp=Path(td);web=temp/'web';web.mkdir() + shutil.copyfile(BASE/'build_site.py',web/'build_site.py') + shutil.copytree(BASE/'src',web/'src') + (web/'assets').mkdir() + for p in (BASE/'assets').iterdir(): + if p.is_file() and p.suffix!='.mp4':shutil.copyfile(p,web/'assets'/p.name) + fixture=dict(meta,state='ready',sha256='a'*64,signing_fingerprint='A'*40,size_bytes=1,source_archive_url='/downloads/SYNTHETIC-SOURCE.tar.gz') + (web/'release.json').write_text(json.dumps(fixture)) + result=subprocess.run(['/usr/bin/python3','-I','-S',str(web/'build_site.py')],capture_output=True,text=True,timeout=10) + if result.returncode:failures.append('Synthetic-ready rendering failed: '+result.stderr[-600:]) + else: + q=Inspect();q.feed((web/'dist/download/index.html').read_text()) + blocks=[x for x in q.code if '\n' in x] + if len(blocks)!=3:failures.append('Expected manual verification, APT setup and after-install command blocks') + for text in blocks: + result=subprocess.run(['/bin/bash','-n'],input=text,text=True,capture_output=True,timeout=3) + if result.returncode:failures.append('Rendered install block shell syntax: '+result.stderr.strip()) + manual=next((x for x in blocks if 'gpgv --keyring' in x),'') + if not manual or 'sha256sum --check' not in manual or 'expected="' not in manual:failures.append('Manual flow missing required verification') + apt=next((x for x in blocks if 'sources.list.d' in x),'') + if 'signed-by=' not in apt or 'apt-key' in apt:failures.append('APT trust not scoped with Signed-By') + status={'host':'GERAMEG','website_source':str(BASE),'web_root':str(DIST),'language':'English','buildless':True,'html_pages_checked':len(pages),'loopback_http_paths_checked':http_count,'static_failures':failures,'pending_assets':sorted(set(pending)),'infra_routes_expected':sorted(infra),'external_links':sorted(external),'no_external_runtime_dependencies':True,'copy_controls_progressive':True,'ready_branch_rendered_with_synthetic_fixture':True,'install_blocks_syntax_checked_not_executed':True,'release_state':meta.get('state'),'package_sha256_present':bool(meta.get('sha256')),'signing_fingerprint_present':bool(meta.get('signing_fingerprint')),'video_present':(DIST/'assets/framebridge-preview.mp4').is_file(),'visual_browser_QA_performed':False,'visual_browser_QA_limit':'No browser or user desktop was operated by this subtask. Root may inspect the staged site via the authorized GERAMEG surface.','production_network_or_services_changed':False,'site_ready_for_private_staging':not failures,'public_release_ready':not failures and not pending and meta.get('state')=='ready','homepage_sha256':hashlib.sha256((DIST/'index.html').read_bytes()).hexdigest()} + visual=BASE/'qa/VISUAL-REVIEW.json' + if visual.exists(): + status['visual_browser_QA_performed']=True + status['visual_browser_QA_limit']=json.loads(visual.read_text())['limitations'] + status['visual_browser_QA_report']=str(visual) + status.update(support_page='/support/',support_funding_channel_configured=bool(meta.get('support_payment_url')),support_payment_form_present='div+div{margin-top:35px}.footer-main,.footer-bottom{display:block}.footer-links{margin:25px 0}.footer-bottom p+p{margin-top:12px}.page-header{padding:40px 0 25px}.page-header h1{font-size:43px}.page-content{padding:30px 0 55px}.download-grid aside{margin-top:40px}.docs-layout{display:block;padding:30px 0 55px}.docs-nav{border-right:0;border-bottom:1px solid var(--line);padding:0 0 16px;margin-bottom:30px}.docs-nav ul{display:flex;flex-wrap:wrap;gap:3px 20px;margin:10px 0}.docs-nav a{padding:3px 0}.docs h1{font-size:37px}.topic-links{grid-template-columns:1fr}.codebox{padding:20px 16px}pre{font-size:12px}.codebox pre{padding-right:0;padding-top:20px}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.cursor{animation:none}} diff --git a/website/src/site.js b/website/src/site.js new file mode 100644 index 0000000..3abdeb4 --- /dev/null +++ b/website/src/site.js @@ -0,0 +1,12 @@ +"use strict"; +document.querySelectorAll(".codebox pre").forEach(function(pre){ + if(!navigator.clipboard || !window.isSecureContext) return; + var button=document.createElement("button"); + button.type="button";button.className="copy-button";button.textContent="Copy"; + button.setAttribute("aria-label","Copy command block"); + button.addEventListener("click",async function(){ + try{await navigator.clipboard.writeText(pre.textContent);button.textContent="Copied";setTimeout(function(){button.textContent="Copy"},1800)} + catch(error){button.textContent="Select to copy";setTimeout(function(){button.textContent="Copy"},2000)} + }); + pre.parentElement.appendChild(button); +});